gsd-pi 2.17.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/onboarding.js +2 -2
- package/dist/remote-questions-config.d.ts +10 -0
- package/dist/remote-questions-config.js +36 -0
- package/dist/resources/extensions/gsd/activity-log.ts +37 -7
- package/dist/resources/extensions/gsd/auto-prompts.ts +20 -1
- package/dist/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/dist/resources/extensions/gsd/auto.ts +123 -10
- package/dist/resources/extensions/gsd/commands.ts +245 -22
- package/dist/resources/extensions/gsd/dispatch-guard.ts +7 -19
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +201 -2
- package/dist/resources/extensions/gsd/files.ts +123 -1
- package/dist/resources/extensions/gsd/guided-flow.ts +237 -4
- package/dist/resources/extensions/gsd/index.ts +47 -3
- package/dist/resources/extensions/gsd/paths.ts +9 -0
- package/dist/resources/extensions/gsd/preferences.ts +59 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/dist/resources/extensions/gsd/prompts/system.md +2 -0
- package/dist/resources/extensions/gsd/queue-order.ts +231 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
- package/dist/resources/extensions/gsd/state.ts +15 -3
- package/dist/resources/extensions/gsd/templates/knowledge.md +19 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +14 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
- package/dist/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
- package/dist/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
- package/dist/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
- package/dist/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
- package/dist/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/dist/resources/extensions/gsd/worktree.ts +22 -0
- package/dist/resources/extensions/shared/next-action-ui.ts +16 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/cli/args.d.ts +5 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +21 -0
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/cli/list-models.d.ts +14 -3
- package/packages/pi-coding-agent/dist/cli/list-models.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/list-models.js +52 -17
- package/packages/pi-coding-agent/dist/cli/list-models.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts +27 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.js +79 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +140 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.js +162 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js +100 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +113 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +26 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +98 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts +62 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.js +145 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.js +118 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +5 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +4 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +17 -2
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +25 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +121 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +32 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +21 -0
- package/packages/pi-coding-agent/src/cli/list-models.ts +70 -17
- package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +170 -0
- package/packages/pi-coding-agent/src/core/discovery-cache.ts +97 -0
- package/packages/pi-coding-agent/src/core/model-discovery.test.ts +125 -0
- package/packages/pi-coding-agent/src/core/model-discovery.ts +231 -0
- package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +135 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +107 -0
- package/packages/pi-coding-agent/src/core/models-json-writer.test.ts +145 -0
- package/packages/pi-coding-agent/src/core/models-json-writer.ts +188 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +21 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/index.ts +5 -0
- package/packages/pi-coding-agent/src/main.ts +19 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/index.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +163 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +37 -0
- package/src/resources/extensions/gsd/activity-log.ts +37 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +20 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/src/resources/extensions/gsd/auto.ts +123 -10
- package/src/resources/extensions/gsd/commands.ts +245 -22
- package/src/resources/extensions/gsd/dispatch-guard.ts +7 -19
- package/src/resources/extensions/gsd/docs/preferences-reference.md +201 -2
- package/src/resources/extensions/gsd/files.ts +123 -1
- package/src/resources/extensions/gsd/guided-flow.ts +237 -4
- package/src/resources/extensions/gsd/index.ts +47 -3
- package/src/resources/extensions/gsd/paths.ts +9 -0
- package/src/resources/extensions/gsd/preferences.ts +59 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/src/resources/extensions/gsd/prompts/system.md +2 -0
- package/src/resources/extensions/gsd/queue-order.ts +231 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
- package/src/resources/extensions/gsd/state.ts +15 -3
- package/src/resources/extensions/gsd/templates/knowledge.md +19 -0
- package/src/resources/extensions/gsd/templates/preferences.md +14 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
- package/src/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/src/resources/extensions/gsd/worktree.ts +22 -0
- package/src/resources/extensions/shared/next-action-ui.ts +16 -1
|
@@ -80,9 +80,9 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
|
|
|
80
80
|
|
|
81
81
|
- `skill_rules`: situational rules with a human-readable `when` trigger and one or more of `use`, `prefer`, or `avoid`.
|
|
82
82
|
|
|
83
|
-
- `custom_instructions`: extra durable instructions related to skill use.
|
|
83
|
+
- `custom_instructions`: extra durable instructions related to skill use. For operational project knowledge (recurring rules, gotchas, patterns), use `.gsd/KNOWLEDGE.md` instead — it's injected into every agent prompt automatically and agents can append to it during execution.
|
|
84
84
|
|
|
85
|
-
- `models`: per-stage model selection for auto-mode. Keys: `research`, `planning`, `execution`, `completion`. Values can be:
|
|
85
|
+
- `models`: per-stage model selection for auto-mode. Keys: `research`, `planning`, `execution`, `execution_simple`, `completion`, `subagent`. Values can be:
|
|
86
86
|
- Simple string: `"claude-sonnet-4-6"` — single model, no fallbacks
|
|
87
87
|
- Provider-qualified string: `"bedrock/claude-sonnet-4-6"` — targets a specific provider when the same model ID exists across multiple providers
|
|
88
88
|
- Object with fallbacks: `{ model: "claude-opus-4-6", fallbacks: ["glm-5", "minimax-m2.5"] }` — tries fallbacks in order if primary fails
|
|
@@ -108,10 +108,75 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
|
|
|
108
108
|
- `pre_merge_check`: boolean or `"auto"` — run pre-merge checks before merging a worktree back to the integration branch. `true` always runs, `false` never runs, `"auto"` runs when CI is detected. Default: `false`.
|
|
109
109
|
- `commit_type`: string — override the conventional commit type prefix. Must be one of: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci`, `build`, `style`. Default: inferred from diff content.
|
|
110
110
|
- `main_branch`: string — the primary branch name for new git repos (e.g., `"main"`, `"master"`, `"trunk"`). Also used by `getMainBranch()` as the preferred branch when auto-detection is ambiguous. Default: `"main"`.
|
|
111
|
+
- `merge_strategy`: `"squash"` or `"merge"` — controls how worktree branches are merged back. `"squash"` combines all commits into one; `"merge"` preserves individual commits. Default: `"squash"`.
|
|
112
|
+
- `isolation`: `"worktree"` or `"branch"` — controls auto-mode git isolation strategy. `"worktree"` creates a milestone worktree for isolated work; `"branch"` works directly in the project root (useful for submodule-heavy repos). Default: `"worktree"`.
|
|
111
113
|
- `commit_docs`: boolean — when `false`, prevents GSD from committing `.gsd/` planning artifacts to git. The `.gsd/` folder is added to `.gitignore` and kept local-only. Useful for teams where only some members use GSD, or when company policy requires a clean repository. Default: `true`.
|
|
112
114
|
|
|
113
115
|
- `unique_milestone_ids`: boolean — when `true`, generates milestone IDs in `M{seq}-{rand6}` format (e.g. `M001-eh88as`) instead of plain sequential `M001`. Prevents ID collisions in team workflows where multiple contributors create milestones concurrently. Both formats coexist — existing `M001`-style milestones remain valid. Default: `false`.
|
|
114
116
|
|
|
117
|
+
- `budget_ceiling`: number — maximum dollar amount to spend on auto-mode. When reached, behavior is controlled by `budget_enforcement`. Default: no limit.
|
|
118
|
+
|
|
119
|
+
- `budget_enforcement`: `"warn"`, `"pause"`, or `"halt"` — action taken when `budget_ceiling` is reached.
|
|
120
|
+
- `warn` — log a warning but continue execution.
|
|
121
|
+
- `pause` — pause auto-mode and wait for user confirmation.
|
|
122
|
+
- `halt` — stop auto-mode immediately.
|
|
123
|
+
- Default: `"pause"`.
|
|
124
|
+
|
|
125
|
+
- `context_pause_threshold`: number (0-100) — context window usage percentage at which auto-mode should pause to suggest checkpointing. Set to `0` to disable. Default: `0` (disabled).
|
|
126
|
+
|
|
127
|
+
- `token_profile`: `"budget"`, `"balanced"`, or `"quality"` — coordinates model selection, phase skipping, and context compression. `budget` skips research/reassessment and uses cheaper models; `balanced` (default) runs all phases; `quality` prefers higher-quality models. See token-optimization docs.
|
|
128
|
+
|
|
129
|
+
- `phases`: fine-grained control over which phases run. Usually set by `token_profile`, but can be overridden. Keys:
|
|
130
|
+
- `skip_research`: boolean — skip milestone-level research. Default: `false`.
|
|
131
|
+
- `skip_reassess`: boolean — skip roadmap reassessment after each slice. Default: `false`.
|
|
132
|
+
- `skip_slice_research`: boolean — skip per-slice research. Default: `false`.
|
|
133
|
+
|
|
134
|
+
- `remote_questions`: route interactive questions to Slack/Discord for headless auto-mode. Keys:
|
|
135
|
+
- `channel`: `"slack"` or `"discord"` — channel type.
|
|
136
|
+
- `channel_id`: string or number — channel ID.
|
|
137
|
+
- `timeout_minutes`: number — question timeout in minutes (clamped 1-30).
|
|
138
|
+
- `poll_interval_seconds`: number — poll interval in seconds (clamped 2-30).
|
|
139
|
+
|
|
140
|
+
- `notifications`: configures desktop notification behavior during auto-mode. Keys:
|
|
141
|
+
- `enabled`: boolean — master toggle for all notifications. Default: `true`.
|
|
142
|
+
- `on_complete`: boolean — notify when a unit completes. Default: `true`.
|
|
143
|
+
- `on_error`: boolean — notify on errors. Default: `true`.
|
|
144
|
+
- `on_budget`: boolean — notify when budget thresholds are reached. Default: `true`.
|
|
145
|
+
- `on_milestone`: boolean — notify when a milestone finishes. Default: `true`.
|
|
146
|
+
- `on_attention`: boolean — notify when manual attention is needed. Default: `true`.
|
|
147
|
+
|
|
148
|
+
- `uat_dispatch`: boolean — when `true`, enables UAT (User Acceptance Testing) dispatch mode. Default: `false`.
|
|
149
|
+
|
|
150
|
+
- `post_unit_hooks`: array — hooks that fire after a unit completes. Each entry has:
|
|
151
|
+
- `name`: string — unique hook identifier.
|
|
152
|
+
- `after`: string[] — unit types that trigger this hook (e.g., `["execute-task"]`).
|
|
153
|
+
- `prompt`: string — prompt sent to the LLM. Supports `{milestoneId}`, `{sliceId}`, `{taskId}` substitutions.
|
|
154
|
+
- `max_cycles`: number — max times this hook fires per trigger (default: 1, max: 10).
|
|
155
|
+
- `model`: string — optional model override.
|
|
156
|
+
- `artifact`: string — expected output file name (relative to task/slice dir). Hook is skipped if file already exists (idempotent).
|
|
157
|
+
- `retry_on`: string — if this file is produced instead of the artifact, re-run the trigger unit then re-run hooks.
|
|
158
|
+
- `agent`: string — agent definition file to use for hook execution.
|
|
159
|
+
- `enabled`: boolean — toggle without removing (default: `true`).
|
|
160
|
+
|
|
161
|
+
- `pre_dispatch_hooks`: array — hooks that fire before a unit is dispatched. Each entry has:
|
|
162
|
+
- `name`: string — unique hook identifier.
|
|
163
|
+
- `before`: string[] — unit types to intercept.
|
|
164
|
+
- `action`: `"modify"`, `"skip"`, or `"replace"` — what to do with the unit.
|
|
165
|
+
- `prepend`: string — text prepended to unit prompt (for `"modify"` action).
|
|
166
|
+
- `append`: string — text appended to unit prompt (for `"modify"` action).
|
|
167
|
+
- `prompt`: string — replacement prompt (for `"replace"` action; required when action is `"replace"`).
|
|
168
|
+
- `unit_type`: string — override unit type label (for `"replace"` action).
|
|
169
|
+
- `skip_if`: string — for `"skip"` action: only skip if this file exists (relative to unit dir).
|
|
170
|
+
- `model`: string — optional model override when this hook fires.
|
|
171
|
+
- `enabled`: boolean — toggle without removing (default: `true`).
|
|
172
|
+
|
|
173
|
+
**Action validation:**
|
|
174
|
+
- `"modify"` requires at least one of `prepend` or `append`.
|
|
175
|
+
- `"replace"` requires `prompt`.
|
|
176
|
+
- `"skip"` is valid with no additional fields.
|
|
177
|
+
|
|
178
|
+
**Known unit types for `before`/`after`:** `research-milestone`, `plan-milestone`, `research-slice`, `plan-slice`, `execute-task`, `complete-slice`, `replan-slice`, `reassess-roadmap`, `run-uat`.
|
|
179
|
+
|
|
115
180
|
---
|
|
116
181
|
|
|
117
182
|
## Best Practices
|
|
@@ -277,3 +342,137 @@ git:
|
|
|
277
342
|
```
|
|
278
343
|
|
|
279
344
|
All git fields are optional. Omit any field to use the default behavior. Project-level preferences override global preferences on a per-field basis.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Budget & Cost Control Example
|
|
349
|
+
|
|
350
|
+
```yaml
|
|
351
|
+
---
|
|
352
|
+
version: 1
|
|
353
|
+
budget_ceiling: 10.00
|
|
354
|
+
budget_enforcement: pause
|
|
355
|
+
context_pause_threshold: 80
|
|
356
|
+
---
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Sets a $10 budget ceiling. Auto-mode pauses when the ceiling is reached. Context window pauses at 80% usage for checkpointing.
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Notifications Example
|
|
364
|
+
|
|
365
|
+
```yaml
|
|
366
|
+
---
|
|
367
|
+
version: 1
|
|
368
|
+
notifications:
|
|
369
|
+
enabled: true
|
|
370
|
+
on_complete: false
|
|
371
|
+
on_error: true
|
|
372
|
+
on_budget: true
|
|
373
|
+
on_milestone: true
|
|
374
|
+
on_attention: true
|
|
375
|
+
---
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Disables per-unit completion notifications (noisy in long runs) while keeping error, budget, milestone, and attention notifications enabled.
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Post-Unit Hooks Example
|
|
383
|
+
|
|
384
|
+
```yaml
|
|
385
|
+
---
|
|
386
|
+
version: 1
|
|
387
|
+
post_unit_hooks:
|
|
388
|
+
- name: code-review
|
|
389
|
+
after:
|
|
390
|
+
- execute-task
|
|
391
|
+
prompt: "Review the code changes in {sliceId}/{taskId} for quality, security, and test coverage."
|
|
392
|
+
max_cycles: 1
|
|
393
|
+
artifact: REVIEW.md
|
|
394
|
+
---
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Runs an automated code review after each task execution. Skips if `REVIEW.md` already exists (idempotent).
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Pre-Dispatch Hooks Examples
|
|
402
|
+
|
|
403
|
+
**Modify — inject instructions before every task:**
|
|
404
|
+
|
|
405
|
+
```yaml
|
|
406
|
+
---
|
|
407
|
+
version: 1
|
|
408
|
+
pre_dispatch_hooks:
|
|
409
|
+
- name: enforce-standards
|
|
410
|
+
before:
|
|
411
|
+
- execute-task
|
|
412
|
+
action: modify
|
|
413
|
+
prepend: "Follow our TypeScript coding standards and always run linting."
|
|
414
|
+
---
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Skip — skip per-slice research when a research file already exists:**
|
|
418
|
+
|
|
419
|
+
```yaml
|
|
420
|
+
---
|
|
421
|
+
version: 1
|
|
422
|
+
pre_dispatch_hooks:
|
|
423
|
+
- name: skip-existing-research
|
|
424
|
+
before:
|
|
425
|
+
- research-slice
|
|
426
|
+
action: skip
|
|
427
|
+
skip_if: RESEARCH.md
|
|
428
|
+
---
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**Replace — substitute a custom prompt for task execution:**
|
|
432
|
+
|
|
433
|
+
```yaml
|
|
434
|
+
---
|
|
435
|
+
version: 1
|
|
436
|
+
pre_dispatch_hooks:
|
|
437
|
+
- name: tdd-execute
|
|
438
|
+
before:
|
|
439
|
+
- execute-task
|
|
440
|
+
action: replace
|
|
441
|
+
prompt: "Implement the task using strict TDD. Write failing tests first, then implement, then refactor."
|
|
442
|
+
model: claude-opus-4-6
|
|
443
|
+
---
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Token Profile & Phases Example
|
|
449
|
+
|
|
450
|
+
```yaml
|
|
451
|
+
---
|
|
452
|
+
version: 1
|
|
453
|
+
token_profile: budget
|
|
454
|
+
phases:
|
|
455
|
+
skip_research: true
|
|
456
|
+
skip_reassess: true
|
|
457
|
+
skip_slice_research: false
|
|
458
|
+
---
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Uses the `budget` profile to minimize token usage, with explicit override to keep slice-level research enabled.
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## Remote Questions Example
|
|
466
|
+
|
|
467
|
+
```yaml
|
|
468
|
+
---
|
|
469
|
+
version: 1
|
|
470
|
+
remote_questions:
|
|
471
|
+
channel: slack
|
|
472
|
+
channel_id: "C0123456789"
|
|
473
|
+
timeout_minutes: 15
|
|
474
|
+
poll_interval_seconds: 10
|
|
475
|
+
---
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Routes interactive questions to a Slack channel for headless auto-mode sessions. Questions time out after 15 minutes if unanswered.
|
|
@@ -849,7 +849,7 @@ export function parseContextDependsOn(content: string | null): string[] {
|
|
|
849
849
|
const fm = parseFrontmatterMap(fmLines);
|
|
850
850
|
const raw = fm['depends_on'];
|
|
851
851
|
if (!Array.isArray(raw) || raw.length === 0) return [];
|
|
852
|
-
return (raw as string[]).map(s => String(s).
|
|
852
|
+
return (raw as string[]).map(s => String(s).trim()).filter(Boolean);
|
|
853
853
|
}
|
|
854
854
|
|
|
855
855
|
/**
|
|
@@ -951,6 +951,128 @@ export async function appendOverride(basePath: string, change: string, appliedAt
|
|
|
951
951
|
}
|
|
952
952
|
}
|
|
953
953
|
|
|
954
|
+
export async function appendKnowledge(
|
|
955
|
+
basePath: string,
|
|
956
|
+
type: "rule" | "pattern" | "lesson",
|
|
957
|
+
entry: string,
|
|
958
|
+
scope: string,
|
|
959
|
+
): Promise<void> {
|
|
960
|
+
const knowledgePath = resolveGsdRootFile(basePath, "KNOWLEDGE");
|
|
961
|
+
const existing = await loadFile(knowledgePath);
|
|
962
|
+
|
|
963
|
+
if (existing) {
|
|
964
|
+
// Find the next ID for this type
|
|
965
|
+
const prefix = type === "rule" ? "K" : type === "pattern" ? "P" : "L";
|
|
966
|
+
const idPattern = new RegExp(`^\\| ${prefix}(\\d+)`, "gm");
|
|
967
|
+
let maxId = 0;
|
|
968
|
+
let match;
|
|
969
|
+
while ((match = idPattern.exec(existing)) !== null) {
|
|
970
|
+
const num = parseInt(match[1], 10);
|
|
971
|
+
if (num > maxId) maxId = num;
|
|
972
|
+
}
|
|
973
|
+
const nextId = `${prefix}${String(maxId + 1).padStart(3, "0")}`;
|
|
974
|
+
|
|
975
|
+
// Build the table row
|
|
976
|
+
let row: string;
|
|
977
|
+
if (type === "rule") {
|
|
978
|
+
row = `| ${nextId} | ${scope} | ${entry} | — | manual |`;
|
|
979
|
+
} else if (type === "pattern") {
|
|
980
|
+
row = `| ${nextId} | ${entry} | — | ${scope} |`;
|
|
981
|
+
} else {
|
|
982
|
+
row = `| ${nextId} | ${entry} | — | — | ${scope} |`;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// Find the right section and append after the table header
|
|
986
|
+
const sectionHeading = type === "rule" ? "## Rules" : type === "pattern" ? "## Patterns" : "## Lessons Learned";
|
|
987
|
+
const sectionIdx = existing.indexOf(sectionHeading);
|
|
988
|
+
if (sectionIdx !== -1) {
|
|
989
|
+
// Find the end of the table header row (the |---|...| line)
|
|
990
|
+
const afterHeading = existing.indexOf("\n", sectionIdx);
|
|
991
|
+
// Find the next section or end
|
|
992
|
+
const nextSection = existing.indexOf("\n## ", afterHeading + 1);
|
|
993
|
+
const insertPoint = nextSection !== -1 ? nextSection : existing.length;
|
|
994
|
+
|
|
995
|
+
// Insert row before the next section (or at end)
|
|
996
|
+
const before = existing.slice(0, insertPoint).trimEnd();
|
|
997
|
+
const after = existing.slice(insertPoint);
|
|
998
|
+
await saveFile(knowledgePath, before + "\n" + row + "\n" + after);
|
|
999
|
+
} else {
|
|
1000
|
+
// Section not found — append at end
|
|
1001
|
+
await saveFile(knowledgePath, existing.trimEnd() + "\n\n" + row + "\n");
|
|
1002
|
+
}
|
|
1003
|
+
} else {
|
|
1004
|
+
// Create file from scratch with template header
|
|
1005
|
+
const header = [
|
|
1006
|
+
"# Project Knowledge",
|
|
1007
|
+
"",
|
|
1008
|
+
"Append-only register of project-specific rules, patterns, and lessons learned.",
|
|
1009
|
+
"Agents read this before every unit. Add entries when you discover something worth remembering.",
|
|
1010
|
+
"",
|
|
1011
|
+
].join("\n");
|
|
1012
|
+
|
|
1013
|
+
let content: string;
|
|
1014
|
+
if (type === "rule") {
|
|
1015
|
+
content = header + [
|
|
1016
|
+
"## Rules",
|
|
1017
|
+
"",
|
|
1018
|
+
"| # | Scope | Rule | Why | Added |",
|
|
1019
|
+
"|---|-------|------|-----|-------|",
|
|
1020
|
+
`| K001 | ${scope} | ${entry} | — | manual |`,
|
|
1021
|
+
"",
|
|
1022
|
+
"## Patterns",
|
|
1023
|
+
"",
|
|
1024
|
+
"| # | Pattern | Where | Notes |",
|
|
1025
|
+
"|---|---------|-------|-------|",
|
|
1026
|
+
"",
|
|
1027
|
+
"## Lessons Learned",
|
|
1028
|
+
"",
|
|
1029
|
+
"| # | What Happened | Root Cause | Fix | Scope |",
|
|
1030
|
+
"|---|--------------|------------|-----|-------|",
|
|
1031
|
+
"",
|
|
1032
|
+
].join("\n");
|
|
1033
|
+
} else if (type === "pattern") {
|
|
1034
|
+
content = header + [
|
|
1035
|
+
"## Rules",
|
|
1036
|
+
"",
|
|
1037
|
+
"| # | Scope | Rule | Why | Added |",
|
|
1038
|
+
"|---|-------|------|-----|-------|",
|
|
1039
|
+
"",
|
|
1040
|
+
"## Patterns",
|
|
1041
|
+
"",
|
|
1042
|
+
"| # | Pattern | Where | Notes |",
|
|
1043
|
+
"|---|---------|-------|-------|",
|
|
1044
|
+
`| P001 | ${entry} | — | ${scope} |`,
|
|
1045
|
+
"",
|
|
1046
|
+
"## Lessons Learned",
|
|
1047
|
+
"",
|
|
1048
|
+
"| # | What Happened | Root Cause | Fix | Scope |",
|
|
1049
|
+
"|---|--------------|------------|-----|-------|",
|
|
1050
|
+
"",
|
|
1051
|
+
].join("\n");
|
|
1052
|
+
} else {
|
|
1053
|
+
content = header + [
|
|
1054
|
+
"## Rules",
|
|
1055
|
+
"",
|
|
1056
|
+
"| # | Scope | Rule | Why | Added |",
|
|
1057
|
+
"|---|-------|------|-----|-------|",
|
|
1058
|
+
"",
|
|
1059
|
+
"## Patterns",
|
|
1060
|
+
"",
|
|
1061
|
+
"| # | Pattern | Where | Notes |",
|
|
1062
|
+
"|---|---------|-------|-------|",
|
|
1063
|
+
"",
|
|
1064
|
+
"## Lessons Learned",
|
|
1065
|
+
"",
|
|
1066
|
+
"| # | What Happened | Root Cause | Fix | Scope |",
|
|
1067
|
+
"|---|--------------|------------|-----|-------|",
|
|
1068
|
+
`| L001 | ${entry} | — | — | ${scope} |`,
|
|
1069
|
+
"",
|
|
1070
|
+
].join("\n");
|
|
1071
|
+
}
|
|
1072
|
+
await saveFile(knowledgePath, content);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
954
1076
|
export async function loadActiveOverrides(basePath: string): Promise<Override[]> {
|
|
955
1077
|
const overridesPath = resolveGsdRootFile(basePath, "OVERRIDES");
|
|
956
1078
|
const content = await loadFile(overridesPath);
|
|
@@ -22,11 +22,12 @@ import {
|
|
|
22
22
|
} from "./paths.js";
|
|
23
23
|
import { randomInt } from "node:crypto";
|
|
24
24
|
import { join } from "node:path";
|
|
25
|
-
import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from "node:fs";
|
|
25
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from "node:fs";
|
|
26
26
|
import { nativeIsRepo, nativeInit, nativeAddPaths, nativeCommit } from "./native-git-bridge.js";
|
|
27
27
|
import { ensureGitignore, ensurePreferences, untrackRuntimeFiles } from "./gitignore.js";
|
|
28
28
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
29
29
|
import { showConfirm } from "../shared/confirm-ui.js";
|
|
30
|
+
import { loadQueueOrder, sortByQueueOrder, saveQueueOrder } from "./queue-order.js";
|
|
30
31
|
|
|
31
32
|
// ─── Auto-start after discuss ─────────────────────────────────────────────────
|
|
32
33
|
|
|
@@ -203,13 +204,16 @@ function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string)
|
|
|
203
204
|
export function findMilestoneIds(basePath: string): string[] {
|
|
204
205
|
const dir = milestonesDir(basePath);
|
|
205
206
|
try {
|
|
206
|
-
|
|
207
|
+
const ids = readdirSync(dir, { withFileTypes: true })
|
|
207
208
|
.filter((d) => d.isDirectory())
|
|
208
209
|
.map((d) => {
|
|
209
210
|
const match = d.name.match(/^(M\d+(?:-[a-z0-9]{6})?)/);
|
|
210
211
|
return match ? match[1] : d.name;
|
|
211
|
-
})
|
|
212
|
-
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Apply custom queue order if available, else fall back to numeric sort
|
|
215
|
+
const customOrder = loadQueueOrder(basePath);
|
|
216
|
+
return sortByQueueOrder(ids, customOrder);
|
|
213
217
|
} catch {
|
|
214
218
|
return [];
|
|
215
219
|
}
|
|
@@ -305,6 +309,235 @@ export async function showQueue(
|
|
|
305
309
|
return;
|
|
306
310
|
}
|
|
307
311
|
|
|
312
|
+
// ── Count pending milestones ────────────────────────────────────────
|
|
313
|
+
const pendingMilestones = state.registry.filter(
|
|
314
|
+
m => m.status === "pending" || m.status === "active",
|
|
315
|
+
);
|
|
316
|
+
const completeCount = state.registry.filter(m => m.status === "complete").length;
|
|
317
|
+
|
|
318
|
+
// ── If multiple pending milestones, show queue management hub ──────
|
|
319
|
+
if (pendingMilestones.length > 1) {
|
|
320
|
+
const choice = await showNextAction(ctx, {
|
|
321
|
+
title: "GSD — Queue Management",
|
|
322
|
+
summary: [
|
|
323
|
+
`${completeCount} complete, ${pendingMilestones.length} pending.`,
|
|
324
|
+
],
|
|
325
|
+
actions: [
|
|
326
|
+
{
|
|
327
|
+
id: "reorder",
|
|
328
|
+
label: "Reorder queue",
|
|
329
|
+
description: `Change execution order of ${pendingMilestones.length} pending milestones.`,
|
|
330
|
+
recommended: true,
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
id: "add",
|
|
334
|
+
label: "Add new work",
|
|
335
|
+
description: "Queue new milestones via discussion.",
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
|
+
notYetMessage: "Run /gsd queue when ready.",
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
if (choice === "reorder") {
|
|
342
|
+
await handleQueueReorder(ctx, basePath, state);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (choice === "not_yet") return;
|
|
346
|
+
// "add" falls through to existing queue-add logic below
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ── Existing queue-add flow ─────────────────────────────────────────
|
|
350
|
+
await showQueueAdd(ctx, pi, basePath, state);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function handleQueueReorder(
|
|
354
|
+
ctx: ExtensionCommandContext,
|
|
355
|
+
basePath: string,
|
|
356
|
+
state: Awaited<ReturnType<typeof deriveState>>,
|
|
357
|
+
): Promise<void> {
|
|
358
|
+
const { showQueueReorder: showReorderUI } = await import("./queue-reorder-ui.js");
|
|
359
|
+
const { invalidateStateCache } = await import("./state.js");
|
|
360
|
+
|
|
361
|
+
const completed = state.registry
|
|
362
|
+
.filter(m => m.status === "complete")
|
|
363
|
+
.map(m => ({ id: m.id, title: m.title, dependsOn: m.dependsOn }));
|
|
364
|
+
|
|
365
|
+
const pending = state.registry
|
|
366
|
+
.filter(m => m.status !== "complete")
|
|
367
|
+
.map(m => ({ id: m.id, title: m.title, dependsOn: m.dependsOn }));
|
|
368
|
+
|
|
369
|
+
const result = await showReorderUI(ctx, completed, pending);
|
|
370
|
+
if (!result) {
|
|
371
|
+
ctx.ui.notify("Queue reorder cancelled.", "info");
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Save the new order
|
|
376
|
+
saveQueueOrder(basePath, result.order);
|
|
377
|
+
invalidateStateCache();
|
|
378
|
+
|
|
379
|
+
// Remove conflicting depends_on entries from CONTEXT.md files
|
|
380
|
+
if (result.depsToRemove.length > 0) {
|
|
381
|
+
removeDependsOnFromContextFiles(basePath, result.depsToRemove);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Sync PROJECT.md milestone sequence table
|
|
385
|
+
syncProjectMdSequence(basePath, state.registry, result.order);
|
|
386
|
+
|
|
387
|
+
// Commit the change
|
|
388
|
+
const filesToAdd = [".gsd/QUEUE-ORDER.json", ".gsd/PROJECT.md"];
|
|
389
|
+
for (const r of result.depsToRemove) {
|
|
390
|
+
filesToAdd.push(`.gsd/milestones/${r.milestone}/${r.milestone}-CONTEXT.md`);
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
nativeAddPaths(basePath, filesToAdd);
|
|
394
|
+
nativeCommit(basePath, "docs: reorder queue");
|
|
395
|
+
} catch {
|
|
396
|
+
// Commit may fail if nothing changed or git hooks block — non-fatal
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const depInfo = result.depsToRemove.length > 0
|
|
400
|
+
? ` (removed ${result.depsToRemove.length} depends_on)`
|
|
401
|
+
: "";
|
|
402
|
+
ctx.ui.notify(`Queue reordered: ${result.order.join(" → ")}${depInfo}`, "info");
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Remove specific depends_on entries from milestone CONTEXT.md frontmatter.
|
|
407
|
+
*/
|
|
408
|
+
function removeDependsOnFromContextFiles(
|
|
409
|
+
basePath: string,
|
|
410
|
+
depsToRemove: Array<{ milestone: string; dep: string }>,
|
|
411
|
+
): void {
|
|
412
|
+
// Group removals by milestone
|
|
413
|
+
const byMilestone = new Map<string, string[]>();
|
|
414
|
+
for (const { milestone, dep } of depsToRemove) {
|
|
415
|
+
const existing = byMilestone.get(milestone) ?? [];
|
|
416
|
+
existing.push(dep);
|
|
417
|
+
byMilestone.set(milestone, existing);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
for (const [mid, depsToRemoveForMid] of byMilestone) {
|
|
421
|
+
const contextFile = resolveMilestoneFile(basePath, mid, "CONTEXT");
|
|
422
|
+
if (!contextFile || !existsSync(contextFile)) continue;
|
|
423
|
+
|
|
424
|
+
const content = readFileSync(contextFile, "utf-8");
|
|
425
|
+
|
|
426
|
+
// Parse frontmatter
|
|
427
|
+
const trimmed = content.trimStart();
|
|
428
|
+
if (!trimmed.startsWith("---")) continue;
|
|
429
|
+
const afterFirst = trimmed.indexOf("\n");
|
|
430
|
+
if (afterFirst === -1) continue;
|
|
431
|
+
const rest = trimmed.slice(afterFirst + 1);
|
|
432
|
+
const endIdx = rest.indexOf("\n---");
|
|
433
|
+
if (endIdx === -1) continue;
|
|
434
|
+
|
|
435
|
+
const fmText = rest.slice(0, endIdx);
|
|
436
|
+
const body = rest.slice(endIdx + 4);
|
|
437
|
+
|
|
438
|
+
// Parse depends_on line(s)
|
|
439
|
+
const fmLines = fmText.split("\n");
|
|
440
|
+
const removeSet = new Set(depsToRemoveForMid.map(d => d.toUpperCase()));
|
|
441
|
+
|
|
442
|
+
// Handle inline format: depends_on: [M009, M010]
|
|
443
|
+
const inlineMatch = fmLines.findIndex(l => /^depends_on:\s*\[/.test(l));
|
|
444
|
+
if (inlineMatch >= 0) {
|
|
445
|
+
const line = fmLines[inlineMatch];
|
|
446
|
+
const inner = line.match(/\[([^\]]*)\]/);
|
|
447
|
+
if (inner) {
|
|
448
|
+
const remaining = inner[1]
|
|
449
|
+
.split(",")
|
|
450
|
+
.map(s => s.trim())
|
|
451
|
+
.filter(s => s && !removeSet.has(s.toUpperCase()));
|
|
452
|
+
if (remaining.length === 0) {
|
|
453
|
+
fmLines.splice(inlineMatch, 1);
|
|
454
|
+
} else {
|
|
455
|
+
fmLines[inlineMatch] = `depends_on: [${remaining.join(", ")}]`;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
} else {
|
|
459
|
+
// Handle multi-line format
|
|
460
|
+
const keyIdx = fmLines.findIndex(l => /^depends_on:\s*$/.test(l));
|
|
461
|
+
if (keyIdx >= 0) {
|
|
462
|
+
let end = keyIdx + 1;
|
|
463
|
+
while (end < fmLines.length && /^\s+-\s/.test(fmLines[end])) {
|
|
464
|
+
const val = fmLines[end].replace(/^\s+-\s*/, "").trim().toUpperCase();
|
|
465
|
+
if (removeSet.has(val)) {
|
|
466
|
+
fmLines.splice(end, 1);
|
|
467
|
+
} else {
|
|
468
|
+
end++;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (end === keyIdx + 1 || (end <= fmLines.length && !/^\s+-\s/.test(fmLines[keyIdx + 1] ?? ""))) {
|
|
472
|
+
fmLines.splice(keyIdx, 1);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Rebuild file
|
|
478
|
+
const newFm = fmLines.filter(l => l !== undefined).join("\n");
|
|
479
|
+
const newContent = newFm.trim()
|
|
480
|
+
? `---\n${newFm}\n---${body}`
|
|
481
|
+
: body.replace(/^\n+/, "");
|
|
482
|
+
writeFileSync(contextFile, newContent, "utf-8");
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function syncProjectMdSequence(
|
|
487
|
+
basePath: string,
|
|
488
|
+
registry: Array<{ id: string; title: string; status: string }>,
|
|
489
|
+
newOrder: string[],
|
|
490
|
+
): void {
|
|
491
|
+
const projectPath = resolveGsdRootFile(basePath, "PROJECT");
|
|
492
|
+
if (!projectPath || !existsSync(projectPath)) return;
|
|
493
|
+
|
|
494
|
+
const content = readFileSync(projectPath, "utf-8");
|
|
495
|
+
const lines = content.split("\n");
|
|
496
|
+
|
|
497
|
+
const headerIdx = lines.findIndex(l => /^##\s+Milestone Sequence/.test(l));
|
|
498
|
+
if (headerIdx < 0) return;
|
|
499
|
+
|
|
500
|
+
let tableStart = headerIdx + 1;
|
|
501
|
+
while (tableStart < lines.length && !lines[tableStart].startsWith("|")) tableStart++;
|
|
502
|
+
if (tableStart >= lines.length) return;
|
|
503
|
+
|
|
504
|
+
let tableEnd = tableStart + 1;
|
|
505
|
+
while (tableEnd < lines.length && lines[tableEnd].startsWith("|")) tableEnd++;
|
|
506
|
+
|
|
507
|
+
const registryMap = new Map(registry.map(m => [m.id, m]));
|
|
508
|
+
const completedSet = new Set(registry.filter(m => m.status === "complete").map(m => m.id));
|
|
509
|
+
|
|
510
|
+
const newRows: string[] = [];
|
|
511
|
+
for (const m of registry) {
|
|
512
|
+
if (m.status === "complete") {
|
|
513
|
+
newRows.push(`| ${m.id} | ${m.title} | ✅ Complete |`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
let isFirst = true;
|
|
517
|
+
for (const id of newOrder) {
|
|
518
|
+
if (completedSet.has(id)) continue;
|
|
519
|
+
const m = registryMap.get(id);
|
|
520
|
+
if (!m) continue;
|
|
521
|
+
const status = isFirst ? "📋 Next" : "📋 Queued";
|
|
522
|
+
newRows.push(`| ${m.id} | ${m.title} | ${status} |`);
|
|
523
|
+
isFirst = false;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const headerLine = lines[tableStart];
|
|
527
|
+
const separatorLine = lines[tableStart + 1];
|
|
528
|
+
const newTable = [headerLine, separatorLine, ...newRows];
|
|
529
|
+
lines.splice(tableStart, tableEnd - tableStart, ...newTable);
|
|
530
|
+
writeFileSync(projectPath, lines.join("\n"), "utf-8");
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function showQueueAdd(
|
|
534
|
+
ctx: ExtensionCommandContext,
|
|
535
|
+
pi: ExtensionAPI,
|
|
536
|
+
basePath: string,
|
|
537
|
+
state: Awaited<ReturnType<typeof deriveState>>,
|
|
538
|
+
): Promise<void> {
|
|
539
|
+
const milestoneIds = findMilestoneIds(basePath);
|
|
540
|
+
|
|
308
541
|
// ── Build existing milestones context for the prompt ────────────────
|
|
309
542
|
const existingContext = await buildExistingMilestonesContext(basePath, milestoneIds, state);
|
|
310
543
|
|