Skip to content

Commit 0c29d89

Browse files
BenBtgCopilotCopilot
authored
feat: add /speckit.converge command (#3001)
* Add /speckit.converge SDD artifacts and project scaffolding Dogfood the converge feature through Spec Kit's own workflow: - spec.md, plan.md, tasks.md, research, data-model, contracts, quickstart - requirements checklist for the feature - ratified constitution v1.0.0 (.specify/memory) - Specify project scaffolding (.specify/, .github agent + prompt files) Defines a built-in /speckit.converge command that assesses spec/plan/tasks against the codebase and appends remaining work as new tasks (no git, no change tracking, append-only). Implementation not yet started. Excludes unrelated working-tree changes to agents.py, extensions.py, test_extensions.py, catalog.community.json, and README.md. * Implement /speckit.converge command Add the built-in converge command that assesses the codebase against a feature's spec.md, plan.md, and tasks.md and appends remaining unbuilt work as new traceable tasks to tasks.md (append-only; no git, no change tracking). - templates/commands/converge.md: full command body (load artifacts, assess code, classify findings missing/partial/contradicts/unrequested, append '## Phase N — Convergence' tasks with source-ref + gap-type, read-only guardrails, converged branch, handoff, before/after_converge hooks) - Register converge as a core command across all enumeration sites (SKILL_DESCRIPTIONS, _FALLBACK_CORE_COMMAND_NAMES, ARGUMENT_HINTS, and the integration test command lists incl. copilot/generic file inventories) - init.py Next Steps panel + README Core Commands table - tasks.md: T001-T024 complete (T025 manual quickstart pending) Full suite green: 2343 passed. * Record quickstart validation results for /speckit.converge (T025) All six quickstart scenarios validated (GitHub Copilot agent, macOS/zsh): S1 gap->appended traceable task, S2 implement+re-converge, S3 converged leaves tasks.md unchanged, S4 read-only boundaries, S5 missing-prereq stop, S6 cross- integration install (copilot + windsurf). Automated suite: 2343 passed. * Record 2026-06-16 re-verification results for /speckit.converge (T025) * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Fix integration upgrade deleting settings.json and dropping script +x Two upgrade-path bugs surfaced during converge E2E validation: - copilot upgrade stale-deleted .vscode/settings.json because setup() only tracks the file when it creates it; on upgrade the pre-existing file is merged and left untracked, so Phase 2 stale cleanup removed it. Add an integration-level stale_cleanup_exclusions() hook (CopilotIntegration returns {.vscode/settings.json}) and subtract it from stale_keys. - shared .specify/scripts/*.sh lost their execute bit because the managed refresh rewrites them with the bundled source mode (often 0o644) and nothing restored perms. Call ensure_executable_scripts() after the managed-refresh block (POSIX only). Add regression tests in TestIntegrationUpgrade covering both fixes (validated to fail without the fixes). * fix: resolve markdownlint errors in PR files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: clean up runtime state files from PR Remove .specify state files that are per-project runtime artifacts: - feature.json, init-options.json, integration.json - manifest files, extension registry, bug artifacts These are generated by 'specify init' and should not be committed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: fold converge artifacts from #3003 and #3005 - Add speckit.converge Copilot agent and prompt files (#3003) - Add regression test for Claude argument hints (#3005) - Remove invalid converge entry from Claude argument hints - Fix documentation removing branch-prefix fallback claims Supersedes: #3003, #3005 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: remove non-converge specify scaffolding from PR Remove .specify/ artifacts, non-converge .github/agents and prompts, and copilot-instructions.md that were generated by 'specify init' and are not part of the converge command feature. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: remove SDD spec artifacts from PR Remove specs/001-converge-command/ — the spec/plan/tasks/research SDD artifacts produced while building this feature. spec-kit does not track a specs/ directory on main (those are outputs of running the workflow on the repo, not part of the shipped tool). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: remove generated Copilot converge command files Remove .github/agents/speckit.converge.agent.md and .github/prompts/speckit.converge.prompt.md — these are generated by 'specify init --integration copilot' from templates/commands/converge.md (all __SPECKIT_COMMAND_*__/{SCRIPT} tokens are resolved). main tracks no .github/agents or .github/prompts files; the template is the source of truth. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: split out unrelated integration-upgrade fix Move the stale_cleanup_exclusions / executable-bit upgrade fix (base.py, copilot, _migrate_commands.py, test_integration_subcommand.py) out of this PR into its own change. This PR is now scoped purely to the /speckit.converge command. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: add converge to core command template ordering converge is a core command in SKILL_DESCRIPTIONS but was missing from _CORE_COMMAND_TEMPLATE_ORDER, so it sorted with the fallback rank. Add it after 'implement' to keep core-command ordering consistent across integrations. Addresses review feedback on #3001. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: make converge findings example neutral Replace the self-referential sample evidence text in the Convergence Findings table with a neutral placeholder so agents are less likely to copy nonsensical template-specific findings into real output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * docs: clarify converge scope and hook outcome wording - Remove FR-specific parenthetical from code-scope rule so it doesn't imply a hard FR-001 reference exists in every feature - Replace unsupported 'pass outcome to hook context' instruction with explicit in-session outcome reporting before hook listing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: align converge task example with tasks format Use (no colon) in the convergence task example so it matches tasks-template formatting and downstream expectations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Clarification of usage Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * docs: align converge phase/task-id format with tasks template - Use (colon) for consistency with tasks template - Clarify appended task IDs must be zero-padded ( style) - Update checklist example to a concrete zero-padded ID () Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: standardize converge phase heading format Use consistently in converge.md (including the append-only contract section) to match Step 7 and tasks template style. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 84db931 commit 0c29d89

13 files changed

Lines changed: 338 additions & 22 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ Essential commands for the Spec-Driven Development workflow:
163163
| `/speckit.tasks` | `speckit-tasks` | Generate actionable task lists for implementation |
164164
| `/speckit.taskstoissues` | `speckit-taskstoissues`| Convert generated task lists into GitHub issues for tracking and execution |
165165
| `/speckit.implement` | `speckit-implement` | Execute all tasks to build the feature according to the plan |
166+
| `/speckit.converge` | `speckit-converge` | Assess the codebase against spec/plan/tasks and append remaining work as new tasks |
166167

167168
### Optional Commands
168169

src/specify_cli/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ def _print_cli_warning(
429429
"plan": "Generate technical implementation plans from feature specifications.",
430430
"tasks": "Break down implementation plans into actionable task lists.",
431431
"implement": "Execute all tasks from the task breakdown to build the feature.",
432+
"converge": "Assess the codebase against spec.md, plan.md, and tasks.md and append remaining work as new tasks.",
432433
"analyze": "Perform cross-artifact consistency analysis across spec.md, plan.md, and tasks.md.",
433434
"clarify": "Structured clarification workflow for underspecified requirements.",
434435
"constitution": "Create or update project governing principles and development guidelines.",

src/specify_cli/commands/init.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,9 @@ def _display_cmd(name: str) -> str:
781781
steps_lines.append(
782782
f" {step_num}.5 [cyan]{_display_cmd('implement')}[/] - Execute implementation"
783783
)
784+
steps_lines.append(
785+
f" {step_num}.6 [cyan]{_display_cmd('converge')}[/] - Assess the codebase and append remaining work as tasks"
786+
)
784787

785788
steps_panel = Panel(
786789
"\n".join(steps_lines),

src/specify_cli/extensions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"checklist",
3939
"clarify",
4040
"constitution",
41+
"converge",
4142
"implement",
4243
"plan",
4344
"specify",

src/specify_cli/integrations/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"clarify",
4040
"constitution",
4141
"implement",
42+
"converge",
4243
"plan",
4344
"checklist",
4445
"specify",

templates/commands/converge.md

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
---
2+
description: Assess the current codebase against the feature's spec, plan, and tasks, then append any remaining unbuilt work as new tasks to tasks.md so implement can complete it.
3+
scripts:
4+
sh: scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks
5+
ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks
6+
---
7+
8+
## User Input
9+
10+
```text
11+
$ARGUMENTS
12+
```
13+
14+
You **MUST** consider the user input before proceeding (if not empty).
15+
16+
## Pre-Execution Checks
17+
18+
**Check for extension hooks (before convergence)**:
19+
20+
- Check if `.specify/extensions.yml` exists in the project root.
21+
- If it exists, read it and look for entries under the `hooks.before_converge` key
22+
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
23+
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
24+
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
25+
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
26+
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
27+
- For each executable hook, output the following based on its `optional` flag:
28+
- **Optional hook** (`optional: true`):
29+
30+
```text
31+
## Extension Hooks
32+
33+
**Optional Pre-Hook**: {extension}
34+
Command: `/{command}`
35+
Description: {description}
36+
37+
Prompt: {prompt}
38+
To execute: `/{command}`
39+
```
40+
41+
- **Mandatory hook** (`optional: false`):
42+
43+
```text
44+
## Extension Hooks
45+
46+
**Automatic Pre-Hook**: {extension}
47+
Executing: `/{command}`
48+
EXECUTE_COMMAND: {command}
49+
50+
Wait for the result of the hook command before proceeding to the Goal.
51+
```
52+
53+
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
54+
55+
## Goal
56+
57+
Close the gap between what a feature's specification, plan, and tasks call for and what the
58+
codebase currently implements. Read `spec.md`, `plan.md`, and `tasks.md` as the **sole
59+
source of intent** (with the constitution as governing constraints), assess the current
60+
state of the code, determine which requirements, acceptance criteria, plan decisions, and
61+
existing tasks are unmet, incomplete, or only partially satisfied, and **append each piece
62+
of remaining work as a new, traceable task** at the bottom of `tasks.md` so that
63+
`__SPECKIT_COMMAND_IMPLEMENT__` can complete it. This command MUST run only after
64+
`__SPECKIT_COMMAND_IMPLEMENT__` has run on the current `tasks.md`, and after `__SPECKIT_COMMAND_TASKS__` has produced a complete `tasks.md`.
65+
66+
This is **not** a diff tool and does **not** track changes. It assesses the present state
67+
of the code relative to the feature's artifacts — no git, no branch comparison, no history.
68+
69+
## Operating Constraints
70+
71+
**APPEND-ONLY, NEVER REWRITE**: The command's **only** write is appending a new
72+
`## Phase N: Convergence` section to `tasks.md`. It MUST NOT:
73+
74+
- modify `spec.md` or `plan.md` in any way;
75+
- rewrite, renumber, reorder, or delete any existing task (including tasks from a prior
76+
Convergence phase);
77+
- modify, create, or delete any application code — completing the appended tasks is the
78+
job of `__SPECKIT_COMMAND_IMPLEMENT__`.
79+
80+
When the codebase already satisfies everything, the command MUST leave `tasks.md`
81+
**byte-for-byte unchanged** (no empty Convergence header) and report a clean result.
82+
83+
**Constitution Authority**: The project constitution (`/memory/constitution.md`) is
84+
**non-negotiable**. Code that violates a MUST principle is the highest-severity finding and
85+
produces a corresponding remediation task. If the constitution is an unfilled template,
86+
skip constitution checks gracefully rather than failing.
87+
88+
## Execution Steps
89+
90+
### 1. Initialize Convergence Context
91+
92+
Run `{SCRIPT}` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths:
93+
94+
- SPEC = FEATURE_DIR/spec.md
95+
- PLAN = FEATURE_DIR/plan.md
96+
- TASKS = FEATURE_DIR/tasks.md
97+
- CONSTITUTION = `/memory/constitution.md` (if present)
98+
If `spec.md`, `plan.md`, or `tasks.md` is missing, STOP with a clear, actionable message naming the
99+
prerequisite command to run (`__SPECKIT_COMMAND_SPECIFY__` for a missing spec, `__SPECKIT_COMMAND_PLAN__` for a missing plan,
100+
`__SPECKIT_COMMAND_TASKS__` for missing tasks). Do not produce partial output.
101+
For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
102+
103+
### 2. Load Artifacts (Progressive Disclosure)
104+
105+
Load only the minimal necessary context from each artifact:
106+
107+
**From spec.md:**
108+
109+
- Functional Requirements (FR-###)
110+
- Success Criteria (SC-###) — include only items requiring buildable work; exclude
111+
post-launch outcome metrics and business KPIs
112+
- User Stories and their Acceptance Scenarios
113+
- Edge Cases (if present)
114+
115+
**From plan.md:**
116+
117+
- Architecture/stack choices and technical decisions
118+
- Data Model references
119+
- Phases and named touch-points (files/components the plan says will be created or edited)
120+
- Technical constraints
121+
122+
**From tasks.md:**
123+
124+
- Task IDs (to compute the next ID and next phase number)
125+
- Descriptions, phase grouping, and referenced file paths
126+
127+
**From constitution (if not an unfilled template):**
128+
129+
- Principle names and MUST/SHOULD normative statements
130+
131+
### 3. Build the Intent Inventory
132+
133+
Create an internal model (do not echo raw artifacts):
134+
135+
- **Requirements inventory**: one stable key per FR-### / SC-### / user-story acceptance
136+
scenario (e.g. `US1/AC2`), plus the plan decisions and constitution principles that
137+
impose buildable obligations.
138+
- **Code-scope map**: from the file paths named in `plan.md` and `tasks.md`, plus a keyword
139+
search for the concepts each requirement describes, derive the set of source files and
140+
components in scope for assessment. Bound the assessment to these — do **not** infer
141+
scope beyond what the artifacts define.
142+
143+
### 4. Assess the Codebase and Classify Findings
144+
145+
For each item in the intent inventory, inspect the current code in scope and produce a
146+
`Finding` only where there is a gap. Classify every finding by **gap type**:
147+
148+
- **`missing`**: the required work is absent from the code entirely.
149+
- **`partial`**: the work exists but does not yet fully satisfy the requirement /
150+
acceptance criterion / plan decision.
151+
- **`contradicts`**: the code does something that conflicts with stated intent or a
152+
constitution MUST principle.
153+
- **`unrequested`**: the code contains work not called for by the spec, plan, or tasks
154+
(surfaced for awareness — converge does **not** delete code, it only appends a task to
155+
review/justify or remove it).
156+
157+
Each `Finding` records: a stable id, the `source-ref` it traces to, the `gap-type`, a
158+
severity, and a short human-readable description with the evidence (the file/area observed).
159+
160+
**Edge cases:**
161+
162+
- **Little or no code yet**: treat the entire specified scope as `missing` remaining work
163+
rather than failing.
164+
- **Nothing remains**: produce zero findings and follow the converged branch in Step 7.
165+
166+
### 5. Assign Severity
167+
168+
- **CRITICAL**: violates a constitution MUST principle, or a `missing`/`contradicts` gap
169+
that blocks baseline functionality of a P1 user story.
170+
- **HIGH**: a `missing` or `partial` gap on a core functional requirement or acceptance
171+
criterion.
172+
- **MEDIUM**: a `partial` gap on a secondary requirement, or an `unrequested` addition with
173+
unclear justification.
174+
- **LOW**: minor partial gaps, polish, or low-risk `unrequested` additions.
175+
176+
### 6. Present the In-Session Findings Summary
177+
178+
Before appending anything, output a compact, severity-graded summary (no file writes yet):
179+
180+
## Convergence Findings
181+
182+
| ID | Gap Type | Severity | Source | Evidence | Remaining Work |
183+
|----|----------|----------|--------|----------|----------------|
184+
| F1 | missing | HIGH | FR-008 | Example: no append-only guard detected in path/to/module.py when writing tasks.md | Add append-only enforcement |
185+
186+
**Summary metrics:**
187+
188+
- Requirements / acceptance criteria checked
189+
- Plan decisions checked
190+
- Constitution principles checked (or "skipped — template")
191+
- Findings by gap type (missing / partial / contradicts / unrequested)
192+
- Findings by severity
193+
194+
### 7. Append Convergence Tasks (or report converged)
195+
196+
**If there are one or more actionable findings** (`tasks_appended` outcome):
197+
198+
Append to the **end** of `tasks.md`, per the append contract:
199+
200+
1. Scan all existing task IDs; let `M` be the maximum. Determine the next phase number `N`
201+
(highest existing phase + 1).
202+
2. Write a single new section header `## Phase N: Convergence`.
203+
3. Emit one checklist item per actionable finding, ordered CRITICAL/HIGH first, assigning
204+
zero-padded IDs `T{M+1:03d}, T{M+2:03d}, …`:
205+
206+
```markdown
207+
- [ ] T042 <imperative description> per <source-ref> (<gap-type>)
208+
```
209+
210+
`<source-ref>` traces the task to its origin: e.g. `FR-003`, `SC-002`,
211+
`US1/AC2`, `plan: storage decision`, `Constitution II`.
212+
213+
`<gap-type>` is one of `missing`, `partial`, `contradicts`, `unrequested`.
214+
215+
Constitution-violation tasks MUST be emitted first and described as
216+
`CRITICAL`.
217+
4. Never reuse or renumber existing IDs. If a prior Convergence phase exists, add a new,
218+
separately-numbered one below it — do not touch the old one.
219+
220+
**If there are no actionable findings** (`converged` outcome):
221+
222+
- Do **not** modify `tasks.md` at all — no empty phase header.
223+
- Report: **"✅ Converged — the implementation satisfies the spec, plan, and tasks."**
224+
- Include the summary counts of what was checked.
225+
226+
### 8. Provide Next Actions (Handoff)
227+
228+
- On `tasks_appended`: state how many tasks were appended under which phase, and recommend
229+
running `__SPECKIT_COMMAND_IMPLEMENT__` to complete them; note that a follow-up converge
230+
run will find fewer or no remaining items.
231+
- On `converged`: recommend proceeding to review / opening a PR. No further implement pass
232+
is needed for this feature's specified scope.
233+
234+
### 9. Check for extension hooks
235+
236+
After producing the result, check if `.specify/extensions.yml` exists in the project root.
237+
238+
- If it exists, read it and look for entries under the `hooks.after_converge` key
239+
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
240+
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
241+
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
242+
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
243+
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
244+
- Report the convergence outcome (`converged` or `tasks_appended`) in-session before listing
245+
any hooks, so users can decide whether to run optional follow-up commands.
246+
- For each executable hook, output the following based on its `optional` flag:
247+
- **Optional hook** (`optional: true`):
248+
249+
```text
250+
## Extension Hooks
251+
252+
**Optional Hook**: {extension}
253+
Command: `/{command}`
254+
Description: {description}
255+
256+
Prompt: {prompt}
257+
To execute: `/{command}`
258+
```
259+
260+
- **Mandatory hook** (`optional: false`):
261+
262+
```text
263+
## Extension Hooks
264+
265+
**Automatic Hook**: {extension}
266+
Executing: `/{command}`
267+
EXECUTE_COMMAND: {command}
268+
```
269+
270+
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently

tests/integrations/test_integration_base_markdown.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ def test_init_options_includes_context_file(self, tmp_path):
254254

255255
COMMAND_STEMS = [
256256
"agent-context.update",
257-
"analyze", "clarify", "constitution", "implement",
257+
"analyze", "clarify", "constitution", "converge", "implement",
258258
"plan", "checklist", "specify", "tasks", "taskstoissues",
259259
]
260260

tests/integrations/test_integration_base_skills.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def test_skill_directory_structure(self, tmp_path):
100100
skill_files = [f for f in created if "scripts" not in f.parts]
101101

102102
expected_commands = {
103-
"analyze", "clarify", "constitution", "implement",
103+
"analyze", "clarify", "constitution", "converge", "implement",
104104
"plan", "checklist", "specify", "tasks", "taskstoissues",
105105
}
106106

@@ -393,7 +393,7 @@ def test_options_include_skills_flag(self):
393393
# -- Complete file inventory ------------------------------------------
394394

395395
_SKILL_COMMANDS = [
396-
"analyze", "clarify", "constitution", "implement",
396+
"analyze", "clarify", "constitution", "converge", "implement",
397397
"plan", "checklist", "specify", "tasks", "taskstoissues",
398398
]
399399

tests/integrations/test_integration_base_toml.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,7 @@ def test_init_options_includes_context_file(self, tmp_path):
486486
"analyze",
487487
"clarify",
488488
"constitution",
489+
"converge",
489490
"implement",
490491
"plan",
491492
"checklist",

tests/integrations/test_integration_base_yaml.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ def test_init_options_includes_context_file(self, tmp_path):
365365
"analyze",
366366
"clarify",
367367
"constitution",
368+
"converge",
368369
"implement",
369370
"plan",
370371
"checklist",

0 commit comments

Comments
 (0)