gsd-pi 2.17.0 → 2.19.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-dashboard.ts +14 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +65 -16
- package/dist/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/dist/resources/extensions/gsd/auto.ts +399 -29
- package/dist/resources/extensions/gsd/captures.ts +384 -0
- package/dist/resources/extensions/gsd/commands.ts +382 -23
- package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +10 -0
- 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/metrics.ts +48 -0
- package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/dist/resources/extensions/gsd/model-router.ts +256 -0
- package/dist/resources/extensions/gsd/paths.ts +9 -0
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +2 -1
- package/dist/resources/extensions/gsd/preferences.ts +132 -1
- package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/dist/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -0
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -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/captures.test.ts +438 -0
- package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -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/milestone-transition-worktree.test.ts +144 -0
- package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -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/remote-questions.test.ts +227 -1
- package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/dist/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +198 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +255 -0
- package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
- package/dist/resources/extensions/gsd/visualizer-data.ts +154 -0
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +193 -0
- package/dist/resources/extensions/gsd/visualizer-views.ts +293 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/dist/resources/extensions/gsd/worktree.ts +22 -0
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +33 -0
- package/dist/resources/extensions/remote-questions/format.ts +12 -6
- package/dist/resources/extensions/remote-questions/manager.ts +8 -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-dashboard.ts +14 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +65 -16
- package/src/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/src/resources/extensions/gsd/auto.ts +399 -29
- package/src/resources/extensions/gsd/captures.ts +384 -0
- package/src/resources/extensions/gsd/commands.ts +382 -23
- package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
- 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/metrics.ts +48 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/src/resources/extensions/gsd/model-router.ts +256 -0
- package/src/resources/extensions/gsd/paths.ts +9 -0
- package/src/resources/extensions/gsd/post-unit-hooks.ts +2 -1
- package/src/resources/extensions/gsd/preferences.ts +132 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/src/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -0
- package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -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/captures.test.ts +438 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -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/milestone-transition-worktree.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -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/remote-questions.test.ts +227 -1
- package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +198 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +255 -0
- package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/src/resources/extensions/gsd/triage-ui.ts +175 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +154 -0
- package/src/resources/extensions/gsd/visualizer-overlay.ts +193 -0
- package/src/resources/extensions/gsd/visualizer-views.ts +293 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/src/resources/extensions/gsd/worktree.ts +22 -0
- package/src/resources/extensions/remote-questions/discord-adapter.ts +33 -0
- package/src/resources/extensions/remote-questions/format.ts +12 -6
- package/src/resources/extensions/remote-questions/manager.ts +8 -0
- package/src/resources/extensions/shared/next-action-ui.ts +16 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
You are triaging user-captured thoughts during a GSD session.
|
|
2
|
+
|
|
3
|
+
## UNIT: Triage Captures
|
|
4
|
+
|
|
5
|
+
The user captured thoughts during execution using `/gsd capture`. Your job is to classify each capture, present your proposals, get user confirmation, and update CAPTURES.md with the final classifications.
|
|
6
|
+
|
|
7
|
+
## Pending Captures
|
|
8
|
+
|
|
9
|
+
{{pendingCaptures}}
|
|
10
|
+
|
|
11
|
+
## Current Slice Plan
|
|
12
|
+
|
|
13
|
+
{{currentPlan}}
|
|
14
|
+
|
|
15
|
+
## Current Roadmap
|
|
16
|
+
|
|
17
|
+
{{roadmapContext}}
|
|
18
|
+
|
|
19
|
+
## Classification Criteria
|
|
20
|
+
|
|
21
|
+
For each capture, classify it as one of:
|
|
22
|
+
|
|
23
|
+
- **quick-task**: Small, self-contained, no downstream impact. Can be done in minutes without modifying the plan. Examples: fix a typo, add a missing import, tweak a config value.
|
|
24
|
+
- **inject**: Belongs in the current slice but wasn't planned. Needs a new task added to the slice plan. Examples: add error handling to a module being built, add a missing test case for current work.
|
|
25
|
+
- **defer**: Belongs in a future slice or milestone. Not urgent for current work. Examples: performance optimization, feature that depends on unbuilt infrastructure, nice-to-have enhancement.
|
|
26
|
+
- **replan**: Changes the shape of remaining work in the current slice. Existing incomplete tasks may need rewriting. Examples: "the approach is wrong, we need to use X instead of Y", discovering a fundamental constraint.
|
|
27
|
+
- **note**: Informational only. No action needed right now. Good context for future reference. Examples: "remember that the API has a rate limit", observations about code quality.
|
|
28
|
+
|
|
29
|
+
## Decision Guidelines
|
|
30
|
+
|
|
31
|
+
- Prefer **quick-task** when the work is clearly small and self-contained.
|
|
32
|
+
- Prefer **inject** over **replan** when only a new task is needed, not rewriting existing ones.
|
|
33
|
+
- Prefer **defer** over **inject** when the work doesn't belong in the current slice's scope.
|
|
34
|
+
- Use **replan** only when remaining incomplete tasks need to change — not just for adding work.
|
|
35
|
+
- Use **note** for observations that don't require action.
|
|
36
|
+
- When unsure between quick-task and inject, consider: will this take more than 10 minutes? If yes, inject.
|
|
37
|
+
|
|
38
|
+
## Instructions
|
|
39
|
+
|
|
40
|
+
1. **Classify** each pending capture using the criteria above.
|
|
41
|
+
|
|
42
|
+
2. **Present** your classifications to the user using `ask_user_questions`. For each capture, show:
|
|
43
|
+
- The capture text
|
|
44
|
+
- Your proposed classification
|
|
45
|
+
- Your rationale
|
|
46
|
+
- If applicable, which files would be affected
|
|
47
|
+
|
|
48
|
+
For captures classified as **note** or **defer**, auto-confirm without asking — these are low-impact.
|
|
49
|
+
For captures classified as **quick-task**, **inject**, or **replan**, ask the user to confirm or choose a different classification.
|
|
50
|
+
|
|
51
|
+
3. **Update** `.gsd/CAPTURES.md` — for each capture, update its section with the confirmed classification:
|
|
52
|
+
- Change `**Status:** pending` to `**Status:** resolved`
|
|
53
|
+
- Add `**Classification:** <type>`
|
|
54
|
+
- Add `**Resolution:** <brief description of what will happen>`
|
|
55
|
+
- Add `**Rationale:** <why this classification>`
|
|
56
|
+
- Add `**Resolved:** <current ISO timestamp>`
|
|
57
|
+
|
|
58
|
+
4. **Summarize** what was triaged: how many captures, what classifications were assigned, and what actions are pending (e.g., "2 quick-tasks ready for execution, 1 deferred to S03").
|
|
59
|
+
|
|
60
|
+
**Important:** Do NOT execute any resolutions. Only classify and update CAPTURES.md. Resolution execution happens separately (in auto-mode dispatch or manually by the user).
|
|
61
|
+
|
|
62
|
+
When done, say: "Triage complete."
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Queue Order — Custom milestone execution ordering.
|
|
3
|
+
*
|
|
4
|
+
* Stores an explicit execution order in `.gsd/QUEUE-ORDER.json`.
|
|
5
|
+
* When present, `findMilestoneIds()` uses this order instead of
|
|
6
|
+
* the default numeric sort (milestoneIdSort).
|
|
7
|
+
*
|
|
8
|
+
* The file is committed to git (not gitignored) so ordering
|
|
9
|
+
* survives branch switches and is shared across sessions.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { gsdRoot } from "./paths.js";
|
|
15
|
+
import { milestoneIdSort } from "./guided-flow.js";
|
|
16
|
+
|
|
17
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
interface QueueOrderFile {
|
|
20
|
+
order: string[];
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DependencyViolation {
|
|
25
|
+
milestone: string;
|
|
26
|
+
dependsOn: string;
|
|
27
|
+
type: 'would_block' | 'circular' | 'missing_dep';
|
|
28
|
+
message: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DependencyRedundancy {
|
|
32
|
+
milestone: string;
|
|
33
|
+
dependsOn: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DependencyValidation {
|
|
37
|
+
valid: boolean;
|
|
38
|
+
violations: DependencyViolation[];
|
|
39
|
+
redundant: DependencyRedundancy[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── Path ────────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
function queueOrderPath(basePath: string): string {
|
|
45
|
+
return join(gsdRoot(basePath), "QUEUE-ORDER.json");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Read / Write ────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Load the custom queue order. Returns null if no file exists or if
|
|
52
|
+
* the file is corrupt/unreadable.
|
|
53
|
+
*/
|
|
54
|
+
export function loadQueueOrder(basePath: string): string[] | null {
|
|
55
|
+
const p = queueOrderPath(basePath);
|
|
56
|
+
if (!existsSync(p)) return null;
|
|
57
|
+
try {
|
|
58
|
+
const data: QueueOrderFile = JSON.parse(readFileSync(p, "utf-8"));
|
|
59
|
+
if (!Array.isArray(data.order)) return null;
|
|
60
|
+
return data.order;
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Save a custom queue order to disk.
|
|
68
|
+
*/
|
|
69
|
+
export function saveQueueOrder(basePath: string, order: string[]): void {
|
|
70
|
+
const data: QueueOrderFile = {
|
|
71
|
+
order,
|
|
72
|
+
updatedAt: new Date().toISOString(),
|
|
73
|
+
};
|
|
74
|
+
writeFileSync(queueOrderPath(basePath), JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─── Sorting ─────────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Sort milestone IDs respecting a custom order.
|
|
81
|
+
*
|
|
82
|
+
* - IDs present in `customOrder` appear in that exact sequence.
|
|
83
|
+
* - IDs on disk but NOT in `customOrder` are appended at the end,
|
|
84
|
+
* sorted by the default `milestoneIdSort` (numeric).
|
|
85
|
+
* - IDs in `customOrder` but NOT on disk are silently skipped.
|
|
86
|
+
* - When `customOrder` is null, falls back to `milestoneIdSort`.
|
|
87
|
+
*/
|
|
88
|
+
export function sortByQueueOrder(ids: string[], customOrder: string[] | null): string[] {
|
|
89
|
+
if (!customOrder) return [...ids].sort(milestoneIdSort);
|
|
90
|
+
|
|
91
|
+
const idSet = new Set(ids);
|
|
92
|
+
const ordered: string[] = [];
|
|
93
|
+
|
|
94
|
+
// First: IDs from customOrder that exist on disk
|
|
95
|
+
for (const id of customOrder) {
|
|
96
|
+
if (idSet.has(id)) {
|
|
97
|
+
ordered.push(id);
|
|
98
|
+
idSet.delete(id);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Then: remaining IDs not in customOrder, in default sort order
|
|
103
|
+
const remaining = [...idSet].sort(milestoneIdSort);
|
|
104
|
+
return [...ordered, ...remaining];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ─── Pruning ─────────────────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Remove IDs from the queue order file that are no longer valid
|
|
111
|
+
* (completed or deleted milestones). No-op if file doesn't exist.
|
|
112
|
+
*/
|
|
113
|
+
export function pruneQueueOrder(basePath: string, validIds: string[]): void {
|
|
114
|
+
const order = loadQueueOrder(basePath);
|
|
115
|
+
if (!order) return;
|
|
116
|
+
|
|
117
|
+
const validSet = new Set(validIds);
|
|
118
|
+
const pruned = order.filter(id => validSet.has(id));
|
|
119
|
+
|
|
120
|
+
if (pruned.length !== order.length) {
|
|
121
|
+
saveQueueOrder(basePath, pruned);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ─── Validation ──────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Validate a proposed queue order against dependency constraints.
|
|
129
|
+
*
|
|
130
|
+
* Checks:
|
|
131
|
+
* - would_block: A milestone is placed before one of its dependencies
|
|
132
|
+
* - circular: Two or more milestones form a dependency cycle
|
|
133
|
+
* - missing_dep: A milestone depends on an ID that doesn't exist
|
|
134
|
+
* - redundant: A dependency is satisfied by queue position (dep comes earlier)
|
|
135
|
+
*/
|
|
136
|
+
export function validateQueueOrder(
|
|
137
|
+
order: string[],
|
|
138
|
+
depsMap: Map<string, string[]>,
|
|
139
|
+
completedIds: Set<string>,
|
|
140
|
+
): DependencyValidation {
|
|
141
|
+
const violations: DependencyViolation[] = [];
|
|
142
|
+
const redundant: DependencyRedundancy[] = [];
|
|
143
|
+
|
|
144
|
+
const positionMap = new Map<string, number>();
|
|
145
|
+
for (let i = 0; i < order.length; i++) {
|
|
146
|
+
positionMap.set(order[i], i);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const allKnownIds = new Set([...order, ...completedIds]);
|
|
150
|
+
|
|
151
|
+
for (const [mid, deps] of depsMap) {
|
|
152
|
+
const midPos = positionMap.get(mid);
|
|
153
|
+
if (midPos === undefined) continue; // not in pending order
|
|
154
|
+
|
|
155
|
+
for (const dep of deps) {
|
|
156
|
+
// Dep already completed — always satisfied
|
|
157
|
+
if (completedIds.has(dep)) continue;
|
|
158
|
+
|
|
159
|
+
// Dep doesn't exist anywhere
|
|
160
|
+
if (!allKnownIds.has(dep)) {
|
|
161
|
+
violations.push({
|
|
162
|
+
milestone: mid,
|
|
163
|
+
dependsOn: dep,
|
|
164
|
+
type: 'missing_dep',
|
|
165
|
+
message: `${mid} depends on ${dep}, but ${dep} does not exist.`,
|
|
166
|
+
});
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const depPos = positionMap.get(dep);
|
|
171
|
+
if (depPos === undefined) continue; // dep not in pending order (edge case)
|
|
172
|
+
|
|
173
|
+
if (depPos > midPos) {
|
|
174
|
+
// Dep comes AFTER this milestone in the order — violation
|
|
175
|
+
violations.push({
|
|
176
|
+
milestone: mid,
|
|
177
|
+
dependsOn: dep,
|
|
178
|
+
type: 'would_block',
|
|
179
|
+
message: `${mid} cannot run before ${dep} — ${mid} depends_on: [${dep}].`,
|
|
180
|
+
});
|
|
181
|
+
} else {
|
|
182
|
+
// Dep comes before — satisfied by position, redundant
|
|
183
|
+
redundant.push({ milestone: mid, dependsOn: dep });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check for circular dependencies
|
|
189
|
+
const visited = new Set<string>();
|
|
190
|
+
const inStack = new Set<string>();
|
|
191
|
+
|
|
192
|
+
function hasCycle(node: string, path: string[]): string[] | null {
|
|
193
|
+
if (inStack.has(node)) return [...path, node];
|
|
194
|
+
if (visited.has(node)) return null;
|
|
195
|
+
|
|
196
|
+
visited.add(node);
|
|
197
|
+
inStack.add(node);
|
|
198
|
+
|
|
199
|
+
const deps = depsMap.get(node) ?? [];
|
|
200
|
+
for (const dep of deps) {
|
|
201
|
+
if (completedIds.has(dep)) continue;
|
|
202
|
+
const cycle = hasCycle(dep, [...path, node]);
|
|
203
|
+
if (cycle) return cycle;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
inStack.delete(node);
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
for (const mid of order) {
|
|
211
|
+
if (!visited.has(mid)) {
|
|
212
|
+
const cycle = hasCycle(mid, []);
|
|
213
|
+
if (cycle) {
|
|
214
|
+
const cycleStr = cycle.join(' → ');
|
|
215
|
+
violations.push({
|
|
216
|
+
milestone: cycle[0],
|
|
217
|
+
dependsOn: cycle[cycle.length - 2],
|
|
218
|
+
type: 'circular',
|
|
219
|
+
message: `Circular dependency: ${cycleStr}`,
|
|
220
|
+
});
|
|
221
|
+
break; // one cycle report is enough
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
valid: violations.length === 0,
|
|
228
|
+
violations,
|
|
229
|
+
redundant,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Queue Reorder UI
|
|
3
|
+
*
|
|
4
|
+
* Interactive TUI overlay for reordering pending milestones.
|
|
5
|
+
* ↑/↓ navigates cursor. Space grabs/releases item for moving.
|
|
6
|
+
* While grabbed, ↑/↓ swaps the item with its neighbor.
|
|
7
|
+
* Enter confirms all changes. Esc cancels.
|
|
8
|
+
* Conflicting depends_on entries are auto-removed on confirm.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ExtensionContext } from "@gsd/pi-coding-agent";
|
|
12
|
+
import { type Theme } from "@gsd/pi-coding-agent";
|
|
13
|
+
import { Key, matchesKey, truncateToWidth, type TUI } from "@gsd/pi-tui";
|
|
14
|
+
import { makeUI, GLYPH } from "../shared/ui.js";
|
|
15
|
+
import { validateQueueOrder, type DependencyValidation } from "./queue-order.js";
|
|
16
|
+
|
|
17
|
+
export interface ReorderItem {
|
|
18
|
+
id: string;
|
|
19
|
+
title: string;
|
|
20
|
+
dependsOn?: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ReorderResult {
|
|
24
|
+
order: string[];
|
|
25
|
+
/** depends_on entries to remove from CONTEXT.md files */
|
|
26
|
+
depsToRemove: Array<{ milestone: string; dep: string }>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Show the queue reorder overlay.
|
|
31
|
+
* Returns the new order + deps to remove, or null if cancelled.
|
|
32
|
+
*/
|
|
33
|
+
export async function showQueueReorder(
|
|
34
|
+
ctx: ExtensionContext,
|
|
35
|
+
completed: ReorderItem[],
|
|
36
|
+
pending: ReorderItem[],
|
|
37
|
+
): Promise<ReorderResult | null> {
|
|
38
|
+
if (!ctx.hasUI) return null;
|
|
39
|
+
if (pending.length < 2) return null;
|
|
40
|
+
|
|
41
|
+
return ctx.ui.custom<ReorderResult | null>((tui: TUI, theme: Theme, _kb, done) => {
|
|
42
|
+
const items = [...pending];
|
|
43
|
+
let cursor = 0;
|
|
44
|
+
let grabbed = false;
|
|
45
|
+
let cachedLines: string[] | undefined;
|
|
46
|
+
let validation: DependencyValidation;
|
|
47
|
+
|
|
48
|
+
// Mutable deps map — tracks removals during this session
|
|
49
|
+
const liveDeps = new Map<string, string[]>();
|
|
50
|
+
for (const item of [...completed, ...pending]) {
|
|
51
|
+
if (item.dependsOn && item.dependsOn.length > 0) {
|
|
52
|
+
liveDeps.set(item.id, [...item.dependsOn]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const removedDeps: Array<{ milestone: string; dep: string }> = [];
|
|
57
|
+
const completedIds = new Set(completed.map(c => c.id));
|
|
58
|
+
|
|
59
|
+
function revalidate() {
|
|
60
|
+
validation = validateQueueOrder(items.map(i => i.id), liveDeps, completedIds);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
revalidate();
|
|
64
|
+
|
|
65
|
+
function refresh() {
|
|
66
|
+
cachedLines = undefined;
|
|
67
|
+
tui.requestRender();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function swapItems(fromIdx: number, toIdx: number) {
|
|
71
|
+
if (toIdx < 0 || toIdx >= items.length) return;
|
|
72
|
+
const [item] = items.splice(fromIdx, 1);
|
|
73
|
+
items.splice(toIdx, 0, item);
|
|
74
|
+
cursor = toIdx;
|
|
75
|
+
revalidate();
|
|
76
|
+
refresh();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function removeDep(milestone: string, dep: string) {
|
|
80
|
+
const deps = liveDeps.get(milestone);
|
|
81
|
+
if (!deps) return;
|
|
82
|
+
const idx = deps.indexOf(dep);
|
|
83
|
+
if (idx >= 0) {
|
|
84
|
+
deps.splice(idx, 1);
|
|
85
|
+
if (deps.length === 0) liveDeps.delete(milestone);
|
|
86
|
+
removedDeps.push({ milestone, dep });
|
|
87
|
+
const item = items.find(i => i.id === milestone);
|
|
88
|
+
if (item?.dependsOn) {
|
|
89
|
+
item.dependsOn = item.dependsOn.filter(d => d !== dep);
|
|
90
|
+
}
|
|
91
|
+
revalidate();
|
|
92
|
+
refresh();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function handleInput(data: string) {
|
|
97
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
|
|
98
|
+
done(null);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Confirm — auto-resolve would_block violations
|
|
103
|
+
if (matchesKey(data, Key.enter)) {
|
|
104
|
+
const wouldBlock = validation.violations.filter(v => v.type === 'would_block');
|
|
105
|
+
for (const v of wouldBlock) {
|
|
106
|
+
removeDep(v.milestone, v.dependsOn);
|
|
107
|
+
}
|
|
108
|
+
done({ order: items.map(i => i.id), depsToRemove: removedDeps });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Space — toggle grab mode
|
|
113
|
+
if (data === " ") {
|
|
114
|
+
grabbed = !grabbed;
|
|
115
|
+
refresh();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ↑/↓ — move grabbed item OR navigate cursor
|
|
120
|
+
if (matchesKey(data, Key.up)) {
|
|
121
|
+
if (grabbed) {
|
|
122
|
+
swapItems(cursor, cursor - 1);
|
|
123
|
+
} else {
|
|
124
|
+
cursor = Math.max(0, cursor - 1);
|
|
125
|
+
refresh();
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (matchesKey(data, Key.down)) {
|
|
130
|
+
if (grabbed) {
|
|
131
|
+
swapItems(cursor, cursor + 1);
|
|
132
|
+
} else {
|
|
133
|
+
cursor = Math.min(items.length - 1, cursor + 1);
|
|
134
|
+
refresh();
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 'd' — manually remove a dep on the cursor item
|
|
140
|
+
if (data === "d" || data === "D") {
|
|
141
|
+
const item = items[cursor];
|
|
142
|
+
const deps = liveDeps.get(item.id);
|
|
143
|
+
if (deps) {
|
|
144
|
+
const activeDep = deps.find(d => !completedIds.has(d));
|
|
145
|
+
if (activeDep) removeDep(item.id, activeDep);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function render(width: number): string[] {
|
|
152
|
+
if (cachedLines) return cachedLines;
|
|
153
|
+
|
|
154
|
+
const ui = makeUI(theme, width);
|
|
155
|
+
const lines: string[] = [];
|
|
156
|
+
const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
|
|
157
|
+
const add = (s: string) => truncateToWidth(s, width);
|
|
158
|
+
|
|
159
|
+
const headerText = grabbed ? " Queue Reorder — Moving Item" : " Queue Reorder";
|
|
160
|
+
push(ui.bar(), ui.blank(), ui.header(headerText), ui.blank());
|
|
161
|
+
|
|
162
|
+
// Completed milestones (dimmed)
|
|
163
|
+
if (completed.length > 0) {
|
|
164
|
+
lines.push(add(theme.fg("dim", " Completed:")));
|
|
165
|
+
for (const m of completed) {
|
|
166
|
+
const label = m.title && m.title !== m.id ? `${m.id} ${m.title}` : m.id;
|
|
167
|
+
lines.push(add(` ${theme.fg("dim", `${GLYPH.statusDone} ${label}`)}`));
|
|
168
|
+
}
|
|
169
|
+
push(ui.blank());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Pending milestones
|
|
173
|
+
const queueLabel = grabbed ? " Queue (space to release, ↑/↓ to move):" : " Queue (space to grab, ↑/↓ to navigate):";
|
|
174
|
+
lines.push(add(theme.fg("text", queueLabel)));
|
|
175
|
+
|
|
176
|
+
const violatedPairs = new Set(
|
|
177
|
+
validation.violations.filter(v => v.type === 'would_block').map(v => `${v.milestone}:${v.dependsOn}`),
|
|
178
|
+
);
|
|
179
|
+
const redundantPairs = new Set(
|
|
180
|
+
validation.redundant.map(r => `${r.milestone}:${r.dependsOn}`),
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
for (let i = 0; i < items.length; i++) {
|
|
184
|
+
const item = items[i];
|
|
185
|
+
const isCursor = i === cursor;
|
|
186
|
+
const num = i + 1;
|
|
187
|
+
const label = item.title && item.title !== item.id ? `${item.id} ${item.title}` : item.id;
|
|
188
|
+
|
|
189
|
+
if (isCursor && grabbed) {
|
|
190
|
+
lines.push(add(` ${theme.fg("warning", `▸▸ ${num}. ${label}`)}`));
|
|
191
|
+
} else if (isCursor) {
|
|
192
|
+
lines.push(add(` ${theme.fg("accent", `${GLYPH.cursor} ${num}. ${label}`)}`));
|
|
193
|
+
} else {
|
|
194
|
+
lines.push(add(` ${theme.fg("text", `${num}. ${label}`)}`));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// depends_on annotations
|
|
198
|
+
const deps = liveDeps.get(item.id) ?? [];
|
|
199
|
+
for (const dep of deps) {
|
|
200
|
+
if (completedIds.has(dep)) continue;
|
|
201
|
+
const pairKey = `${item.id}:${dep}`;
|
|
202
|
+
if (violatedPairs.has(pairKey)) {
|
|
203
|
+
lines.push(add(` ${theme.fg("warning", `${GLYPH.statusWarning} depends_on: ${dep} — auto-removed on confirm`)}`));
|
|
204
|
+
} else if (redundantPairs.has(pairKey)) {
|
|
205
|
+
lines.push(add(` ${theme.fg("dim", `↳ depends_on: ${dep} (redundant)`)}`));
|
|
206
|
+
} else {
|
|
207
|
+
lines.push(add(` ${theme.fg("dim", `↳ depends_on: ${dep}`)}`));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Missing deps
|
|
212
|
+
for (const v of validation.violations.filter(v => v.milestone === item.id && v.type === 'missing_dep')) {
|
|
213
|
+
lines.push(add(` ${theme.fg("error", `${GLYPH.statusWarning} depends_on: ${v.dependsOn} (does not exist)`)}`));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Removed deps feedback
|
|
218
|
+
if (removedDeps.length > 0) {
|
|
219
|
+
push(ui.blank());
|
|
220
|
+
for (const r of removedDeps) {
|
|
221
|
+
lines.push(add(` ${theme.fg("success", `${GLYPH.statusDone} Removed: ${r.milestone} depends_on ${r.dep}`)}`));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Circular warning
|
|
226
|
+
const circ = validation.violations.find(v => v.type === 'circular');
|
|
227
|
+
if (circ) {
|
|
228
|
+
push(ui.blank());
|
|
229
|
+
lines.push(add(` ${theme.fg("error", `${GLYPH.statusWarning} ${circ.message}`)}`));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
push(ui.blank());
|
|
233
|
+
|
|
234
|
+
// Hints — context-sensitive based on grab state
|
|
235
|
+
const hints: string[] = [];
|
|
236
|
+
if (grabbed) {
|
|
237
|
+
hints.push("↑/↓ move item", "space release");
|
|
238
|
+
} else {
|
|
239
|
+
hints.push("↑/↓ navigate", "space grab");
|
|
240
|
+
}
|
|
241
|
+
const hasDeps = liveDeps.get(items[cursor]?.id)?.some(d => !completedIds.has(d));
|
|
242
|
+
if (hasDeps) hints.push("d del dep");
|
|
243
|
+
|
|
244
|
+
const wouldBlockCount = validation.violations.filter(v => v.type === 'would_block').length;
|
|
245
|
+
if (wouldBlockCount > 0) {
|
|
246
|
+
hints.push(`enter (fixes ${wouldBlockCount} dep)`);
|
|
247
|
+
} else {
|
|
248
|
+
hints.push("enter ok");
|
|
249
|
+
}
|
|
250
|
+
hints.push("esc");
|
|
251
|
+
|
|
252
|
+
push(ui.hints(hints), ui.bar());
|
|
253
|
+
|
|
254
|
+
cachedLines = lines;
|
|
255
|
+
return lines;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { render, invalidate: () => { cachedLines = undefined; }, handleInput };
|
|
259
|
+
}, {
|
|
260
|
+
overlay: true,
|
|
261
|
+
overlayOptions: { width: "70%", minWidth: 50, maxHeight: "80%", anchor: "center" },
|
|
262
|
+
});
|
|
263
|
+
}
|
|
@@ -224,9 +224,21 @@ async function _deriveStateImpl(basePath: string): Promise<GSDState> {
|
|
|
224
224
|
const draftFile = resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT");
|
|
225
225
|
if (draftFile) activeMilestoneHasDraft = true;
|
|
226
226
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
|
|
228
|
+
// Check milestone-level dependencies before promoting to active.
|
|
229
|
+
// Without this, a queued milestone with depends_on in its CONTEXT
|
|
230
|
+
// frontmatter would be promoted to active even when its deps are unmet
|
|
231
|
+
// (the dep check only existed in the has-roadmap path previously).
|
|
232
|
+
const contextContent = contextFile ? await cachedLoadFile(contextFile) : null;
|
|
233
|
+
const deps = parseContextDependsOn(contextContent);
|
|
234
|
+
const depsUnmet = deps.some(dep => !completeMilestoneIds.has(dep));
|
|
235
|
+
if (depsUnmet) {
|
|
236
|
+
registry.push({ id: mid, title: mid, status: 'pending', dependsOn: deps });
|
|
237
|
+
} else {
|
|
238
|
+
activeMilestone = { id: mid, title: mid };
|
|
239
|
+
activeMilestoneFound = true;
|
|
240
|
+
registry.push({ id: mid, title: mid, status: 'active', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
241
|
+
}
|
|
230
242
|
} else {
|
|
231
243
|
registry.push({ id: mid, title: mid, status: 'pending' });
|
|
232
244
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Project Knowledge
|
|
2
|
+
|
|
3
|
+
Append-only register of project-specific rules, patterns, and lessons learned.
|
|
4
|
+
Agents read this before every unit. Add entries when you discover something worth remembering.
|
|
5
|
+
|
|
6
|
+
## Rules
|
|
7
|
+
|
|
8
|
+
| # | Scope | Rule | Why | Added |
|
|
9
|
+
|---|-------|------|-----|-------|
|
|
10
|
+
|
|
11
|
+
## Patterns
|
|
12
|
+
|
|
13
|
+
| # | Pattern | Where | Notes |
|
|
14
|
+
|---|---------|-------|-------|
|
|
15
|
+
|
|
16
|
+
## Lessons Learned
|
|
17
|
+
|
|
18
|
+
| # | What Happened | Root Cause | Fix | Scope |
|
|
19
|
+
|---|--------------|------------|-----|-------|
|
|
@@ -15,7 +15,21 @@ git:
|
|
|
15
15
|
snapshots:
|
|
16
16
|
pre_merge_check:
|
|
17
17
|
commit_type:
|
|
18
|
+
main_branch:
|
|
19
|
+
merge_strategy:
|
|
20
|
+
isolation:
|
|
18
21
|
unique_milestone_ids:
|
|
22
|
+
budget_ceiling:
|
|
23
|
+
budget_enforcement:
|
|
24
|
+
context_pause_threshold:
|
|
25
|
+
notifications:
|
|
26
|
+
enabled:
|
|
27
|
+
on_complete:
|
|
28
|
+
on_error:
|
|
29
|
+
on_budget:
|
|
30
|
+
on_milestone:
|
|
31
|
+
on_attention:
|
|
32
|
+
uat_dispatch:
|
|
19
33
|
---
|
|
20
34
|
|
|
21
35
|
# GSD Skill Preferences
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
getAutoWorktreePath,
|
|
18
18
|
enterAutoWorktree,
|
|
19
19
|
getAutoWorktreeOriginalBase,
|
|
20
|
+
getActiveAutoWorktreeContext,
|
|
20
21
|
} from "../auto-worktree.ts";
|
|
21
22
|
|
|
22
23
|
import { createTestContext } from "./test-helpers.ts";
|
|
@@ -76,6 +77,15 @@ async function main(): Promise<void> {
|
|
|
76
77
|
|
|
77
78
|
// ─── getAutoWorktreeOriginalBase ─────────────────────────────────
|
|
78
79
|
assertEq(getAutoWorktreeOriginalBase(), tempDir, "originalBase returns temp dir");
|
|
80
|
+
assertEq(
|
|
81
|
+
getActiveAutoWorktreeContext(),
|
|
82
|
+
{
|
|
83
|
+
originalBase: tempDir,
|
|
84
|
+
worktreeName: "M003",
|
|
85
|
+
branch: "milestone/M003",
|
|
86
|
+
},
|
|
87
|
+
"active auto-worktree context reflects the worktree cwd",
|
|
88
|
+
);
|
|
79
89
|
|
|
80
90
|
// ─── getAutoWorktreePath ─────────────────────────────────────────
|
|
81
91
|
assertEq(getAutoWorktreePath(tempDir, "M003"), wtPath, "getAutoWorktreePath returns correct path");
|
|
@@ -88,6 +98,7 @@ async function main(): Promise<void> {
|
|
|
88
98
|
assertTrue(!existsSync(wtPath), "worktree directory removed after teardown");
|
|
89
99
|
assertTrue(!isInAutoWorktree(tempDir), "isInAutoWorktree returns false after teardown");
|
|
90
100
|
assertEq(getAutoWorktreeOriginalBase(), null, "originalBase is null after teardown");
|
|
101
|
+
assertEq(getActiveAutoWorktreeContext(), null, "active auto-worktree context clears after teardown");
|
|
91
102
|
|
|
92
103
|
// ─── Re-entry: create again, exit without teardown, re-enter ─────
|
|
93
104
|
console.log("\n=== re-entry ===");
|
|
@@ -103,6 +114,15 @@ async function main(): Promise<void> {
|
|
|
103
114
|
assertEq(process.cwd(), entered, "re-entered worktree via enterAutoWorktree");
|
|
104
115
|
assertEq(getAutoWorktreeOriginalBase(), tempDir, "originalBase restored on re-entry");
|
|
105
116
|
assertTrue(isInAutoWorktree(tempDir), "isInAutoWorktree true after re-entry");
|
|
117
|
+
assertEq(
|
|
118
|
+
getActiveAutoWorktreeContext(),
|
|
119
|
+
{
|
|
120
|
+
originalBase: tempDir,
|
|
121
|
+
worktreeName: "M003",
|
|
122
|
+
branch: "milestone/M003",
|
|
123
|
+
},
|
|
124
|
+
"active auto-worktree context is restored on re-entry",
|
|
125
|
+
);
|
|
106
126
|
|
|
107
127
|
// Cleanup
|
|
108
128
|
teardownAutoWorktree(tempDir, "M003");
|