gsd-pi 2.66.1-dev.3c26b49 → 2.66.1-dev.3cea7ac
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/dist/resources/extensions/ask-user-questions.js +79 -11
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +4 -3
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -3
- package/dist/resources/extensions/gsd/auto/loop.js +13 -1
- package/dist/resources/extensions/gsd/auto/phases.js +10 -4
- package/dist/resources/extensions/gsd/auto/run-unit.js +10 -2
- package/dist/resources/extensions/gsd/auto/session.js +1 -1
- package/dist/resources/extensions/gsd/auto-dashboard.js +65 -15
- package/dist/resources/extensions/gsd/auto-dispatch.js +30 -28
- package/dist/resources/extensions/gsd/auto-prompts.js +6 -6
- package/dist/resources/extensions/gsd/auto-recovery.js +11 -12
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +18 -6
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +59 -5
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +8 -5
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +186 -14
- package/dist/resources/extensions/gsd/codebase-generator.js +4 -0
- package/dist/resources/extensions/gsd/commands/handlers/core.js +3 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +10 -4
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -1
- package/dist/resources/extensions/gsd/detection.js +6 -0
- package/dist/resources/extensions/gsd/files.js +19 -2
- package/dist/resources/extensions/gsd/guided-flow.js +12 -8
- package/dist/resources/extensions/gsd/index.js +1 -1
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +2 -0
- package/dist/resources/extensions/gsd/parsers-legacy.js +3 -1
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
- package/dist/resources/extensions/gsd/prompts/discuss.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
- package/dist/resources/extensions/gsd/prompts/rethink.md +6 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +4 -4
- package/dist/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +2 -1
- package/dist/resources/extensions/gsd/state.js +2 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.js +27 -26
- package/dist/resources/extensions/gsd/workflow-reconcile.js +46 -7
- package/dist/resources/extensions/remote-questions/manager.js +8 -0
- package/dist/resources/extensions/shared/interview-ui.js +10 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +20 -20
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +4 -3
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/utils/json-parse.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/json-parse.js +11 -1
- package/packages/pi-ai/dist/utils/json-parse.js.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.js +60 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
- package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts +2 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.js +14 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.js.map +1 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +10 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic-shared.ts +4 -3
- package/packages/pi-ai/src/utils/json-parse.ts +11 -1
- package/packages/pi-ai/src/utils/repair-tool-json.ts +69 -1
- package/packages/pi-ai/src/utils/tests/json-parse.test.ts +17 -0
- package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +13 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
- 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.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +18 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +11 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +2 -2
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js +13 -0
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +35 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -0
- package/packages/pi-tui/dist/__tests__/tui.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/tui.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/tui.test.js +43 -0
- package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -0
- package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/packages/pi-tui/dist/autocomplete.js +9 -7
- package/packages/pi-tui/dist/autocomplete.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts +2 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.js +54 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -0
- package/packages/pi-tui/dist/components/editor.d.ts +3 -1
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +14 -3
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.js +6 -0
- package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +8 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/__tests__/autocomplete.test.ts +15 -0
- package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +43 -0
- package/packages/pi-tui/src/__tests__/tui.test.ts +50 -0
- package/packages/pi-tui/src/autocomplete.ts +9 -7
- package/packages/pi-tui/src/components/__tests__/editor.test.ts +64 -0
- package/packages/pi-tui/src/components/editor.ts +14 -3
- package/packages/pi-tui/src/stdin-buffer.ts +7 -0
- package/packages/pi-tui/src/tui.ts +9 -0
- package/src/resources/extensions/ask-user-questions.ts +103 -11
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +4 -3
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +12 -3
- package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +17 -0
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +18 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -1
- package/src/resources/extensions/gsd/auto/loop.ts +14 -1
- package/src/resources/extensions/gsd/auto/phases.ts +10 -5
- package/src/resources/extensions/gsd/auto/run-unit.ts +14 -2
- package/src/resources/extensions/gsd/auto/session.ts +1 -1
- package/src/resources/extensions/gsd/auto-dashboard.ts +76 -16
- package/src/resources/extensions/gsd/auto-dispatch.ts +36 -35
- package/src/resources/extensions/gsd/auto-prompts.ts +5 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +15 -15
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +27 -6
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +67 -6
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +11 -8
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +209 -16
- package/src/resources/extensions/gsd/codebase-generator.ts +4 -0
- package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -6
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +11 -4
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +3 -1
- package/src/resources/extensions/gsd/detection.ts +6 -0
- package/src/resources/extensions/gsd/files.ts +21 -2
- package/src/resources/extensions/gsd/guided-flow.ts +15 -8
- package/src/resources/extensions/gsd/index.ts +6 -0
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +2 -0
- package/src/resources/extensions/gsd/parsers-legacy.ts +3 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
- package/src/resources/extensions/gsd/prompts/discuss.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/rethink.md +6 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +4 -4
- package/src/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +4 -1
- package/src/resources/extensions/gsd/state.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +52 -1
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +50 -2
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/detection.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +53 -13
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +3 -4
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +71 -2
- package/src/resources/extensions/gsd/tests/parsers.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/queue-execution-guard.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +210 -35
- package/src/resources/extensions/gsd/visualizer-overlay.ts +31 -27
- package/src/resources/extensions/gsd/workflow-reconcile.ts +59 -8
- package/src/resources/extensions/remote-questions/manager.ts +9 -0
- package/src/resources/extensions/shared/interview-ui.ts +13 -0
- /package/dist/web/standalone/.next/static/{ZzNRjwBFLOhqEu4BYCQi9 → HxFcJ8GrYNPsg9ARz7GPz}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{ZzNRjwBFLOhqEu4BYCQi9 → HxFcJ8GrYNPsg9ARz7GPz}/_ssgManifest.js +0 -0
|
@@ -12,7 +12,7 @@ You are a project reorganization assistant for a GSD (Get Shit Done) project. Th
|
|
|
12
12
|
|
|
13
13
|
1. Present the current milestone order as a clear numbered list with status indicators (e.g. ✅ complete, ▶ active, ⏳ pending, ⏸ parked)
|
|
14
14
|
2. Ask: **"What would you like to change?"**
|
|
15
|
-
3. Execute changes conversationally, confirming destructive operations before proceeding
|
|
15
|
+
3. Execute changes conversationally, confirming destructive operations before proceeding. **Non-bypassable:** For any destructive operation (discard, skip, reorder that breaks dependencies), you MUST get explicit user confirmation before executing. If the user does not respond, gives an ambiguous answer, or `ask_user_questions` fails, you MUST re-ask — never rationalize past the block. A missing confirmation is a "do not proceed."
|
|
16
16
|
|
|
17
17
|
## Supported Operations
|
|
18
18
|
|
|
@@ -53,8 +53,12 @@ gsd_skip_slice({ milestoneId: "M003", sliceId: "S02", reason: "Descoped — feat
|
|
|
53
53
|
Skipped slices are treated as closed by the state machine (like "complete" but distinct). Use when a slice is no longer needed or has been superseded. The slice data is preserved for reference.
|
|
54
54
|
**Do NOT** just check the slice checkbox in the roadmap — this does not update the DB and auto-mode will resume the slice.
|
|
55
55
|
|
|
56
|
+
**CRITICAL — Non-bypassable gate:** Skipping a slice is a permanent DB operation. You MUST confirm with the user before calling `gsd_skip_slice`. If the user does not respond or gives an ambiguous answer, you MUST re-ask — never proceed without explicit approval.
|
|
57
|
+
|
|
56
58
|
### Discard a milestone
|
|
57
|
-
**Permanently** delete a milestone directory and prune it from QUEUE-ORDER.json.
|
|
59
|
+
**Permanently** delete a milestone directory and prune it from QUEUE-ORDER.json.
|
|
60
|
+
|
|
61
|
+
**CRITICAL — Non-bypassable gate:** Discarding is irreversible. You MUST confirm with the user before discarding. Warn explicitly if the milestone has completed work. If the user does not respond or gives an ambiguous answer, you MUST re-ask — never rationalize past the block. A missing confirmation is a "do not discard."
|
|
58
62
|
|
|
59
63
|
### Add a new milestone
|
|
60
64
|
Use the `gsd_milestone_generate_id` tool to get the next ID, then call `gsd_summary_save` with `milestone_id: {ID}`, `artifact_type: "CONTEXT"`, and the scope/goals/success criteria as `content` — the tool writes the context file to disk and persists to DB. Update QUEUE-ORDER.json to place it at the desired position.
|
|
@@ -38,7 +38,7 @@ GSD ships with bundled skills. Load the relevant skill file with the `read` tool
|
|
|
38
38
|
- Never print, echo, log, or restate secrets or credentials. Report only key names and applied/skipped status.
|
|
39
39
|
- Never ask the user to edit `.env` files or set secrets manually. Use `secure_env_collect`.
|
|
40
40
|
- In enduring files, write current state only unless the file is explicitly historical.
|
|
41
|
-
- **Never take outward-facing actions on GitHub (or any external service) without explicit user confirmation.** This includes: creating issues, closing issues, merging PRs, approving PRs, posting comments, pushing to remote branches, publishing packages, or any other action that affects state outside the local filesystem. Read-only operations (listing, viewing, diffing) are fine. Always present what you intend to do and get a clear "yes" before executing.
|
|
41
|
+
- **Never take outward-facing actions on GitHub (or any external service) without explicit user confirmation.** This includes: creating issues, closing issues, merging PRs, approving PRs, posting comments, pushing to remote branches, publishing packages, or any other action that affects state outside the local filesystem. Read-only operations (listing, viewing, diffing) are fine. Always present what you intend to do and get a clear "yes" before executing. **Non-bypassable:** If the user does not respond, gives an ambiguous answer, or `ask_user_questions` fails, you MUST re-ask — never rationalize past the block ("tool not responding, I'll proceed" is forbidden). A missing "yes" is a "no."
|
|
42
42
|
|
|
43
43
|
If a `GSD Skill Preferences` block is present below this contract, treat it as explicit durable guidance for which skills to use, prefer, or avoid during GSD work. Follow it where it does not conflict with required GSD artifact rules, verification requirements, or higher-priority system/developer instructions.
|
|
44
44
|
|
|
@@ -51,7 +51,7 @@ For each capture, classify it as one of:
|
|
|
51
51
|
|
|
52
52
|
For captures classified as **note** or **defer**, auto-confirm without asking — these are low-impact.
|
|
53
53
|
For captures classified as **stop** or **backtrack**, auto-confirm without asking — these are urgent user directives that must be honored immediately.
|
|
54
|
-
For captures classified as **quick-task**, **inject**, or **replan**, ask the user to confirm or choose a different classification.
|
|
54
|
+
For captures classified as **quick-task**, **inject**, or **replan**, ask the user to confirm or choose a different classification. **Non-bypassable:** If `ask_user_questions` fails, errors, or the user does not respond, you MUST re-ask — never auto-confirm these classifications without explicit user approval.
|
|
55
55
|
|
|
56
56
|
3. **Update** `.gsd/CAPTURES.md` — for each capture, update its section with the confirmed classification:
|
|
57
57
|
- Change `**Status:** pending` to `**Status:** resolved`
|
|
@@ -14,7 +14,7 @@ This is remediation round {{remediationRound}}. If this is round 0, this is the
|
|
|
14
14
|
|
|
15
15
|
## Context
|
|
16
16
|
|
|
17
|
-
All relevant context has been preloaded below — the roadmap, all slice summaries,
|
|
17
|
+
All relevant context has been preloaded below — the roadmap, all slice summaries, assessment results, requirements, decisions, and project context are inlined. Start working immediately without re-reading these files.
|
|
18
18
|
|
|
19
19
|
{{inlinedContext}}
|
|
20
20
|
|
|
@@ -30,8 +30,8 @@ Prompt: "Review milestone {{milestoneId}} requirements coverage. Working directo
|
|
|
30
30
|
**Reviewer B — Cross-Slice Integration**
|
|
31
31
|
Prompt: "Review milestone {{milestoneId}} cross-slice integration. Working directory: {{workingDirectory}}. Read `{{roadmapPath}}` and find the boundary map (produces/consumes contracts). For each boundary, check that the producing slice's SUMMARY confirms it produced the artifact, and the consuming slice's SUMMARY confirms it consumed it. Output a markdown table: Boundary | Producer Summary | Consumer Summary | Status. End with a one-line verdict: PASS if all boundaries honored, NEEDS-ATTENTION if any gaps."
|
|
32
32
|
|
|
33
|
-
**Reviewer C —
|
|
34
|
-
Prompt: "Review milestone {{milestoneId}}
|
|
33
|
+
**Reviewer C — Assessment & Acceptance Criteria**
|
|
34
|
+
Prompt: "Review milestone {{milestoneId}} assessment evidence and acceptance criteria. Working directory: {{workingDirectory}}. Read `.gsd/{{milestoneId}}/CONTEXT.md` for acceptance criteria. Check for ASSESSMENT files in each slice directory. Verify each acceptance criterion maps to either a passing assessment result or clear SUMMARY evidence. Output a checklist: [ ] Criterion | Evidence. End with a one-line verdict: PASS if all criteria met, NEEDS-ATTENTION if gaps exist."
|
|
35
35
|
|
|
36
36
|
### Step 2 — Synthesize Findings
|
|
37
37
|
|
|
@@ -59,7 +59,7 @@ reviewers: 3
|
|
|
59
59
|
## Reviewer B — Cross-Slice Integration
|
|
60
60
|
<paste Reviewer B output>
|
|
61
61
|
|
|
62
|
-
## Reviewer C —
|
|
62
|
+
## Reviewer C — Assessment & Acceptance Criteria
|
|
63
63
|
<paste Reviewer C output>
|
|
64
64
|
|
|
65
65
|
## Synthesis
|
|
@@ -90,9 +90,11 @@ Present a merge plan to the user:
|
|
|
90
90
|
|
|
91
91
|
Ask the user to confirm the merge plan before proceeding.
|
|
92
92
|
|
|
93
|
+
**CRITICAL — Non-bypassable gate:** Do NOT execute any merge commands until the user explicitly approves the merge plan. If `ask_user_questions` fails, errors, returns no response, or the user's response is ambiguous, you MUST re-ask — never rationalize past the block. "No response, I'll proceed with the clean merges," "the plan looks safe, merging," or any other self-authorization is **forbidden**. The gate exists to protect the user's branches; treat a block as an instruction to wait, not an obstacle to work around.
|
|
94
|
+
|
|
93
95
|
### Step 4: Execute Merge
|
|
94
96
|
|
|
95
|
-
Once confirmed, run all commands from `{{mainTreePath}}` (your CWD):
|
|
97
|
+
Once the user has explicitly confirmed, run all commands from `{{mainTreePath}}` (your CWD):
|
|
96
98
|
|
|
97
99
|
1. Ensure you are on the target branch: `git checkout {{mainBranch}}`
|
|
98
100
|
2. If there are conflicts requiring manual reconciliation, apply the reconciled versions first
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { execFileSync } from "node:child_process";
|
|
13
|
+
import { normalizePlannedFileReference } from "../files.js";
|
|
13
14
|
import { logWarning } from "../workflow-logger.js";
|
|
14
15
|
|
|
15
16
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
@@ -57,7 +58,9 @@ export function validateFileChanges(
|
|
|
57
58
|
|
|
58
59
|
// Normalize expected paths (strip leading ./ or /)
|
|
59
60
|
const normalizedExpected = new Set(
|
|
60
|
-
[...allExpected].map(f =>
|
|
61
|
+
[...allExpected].map((f) =>
|
|
62
|
+
normalizePlannedFileReference(f).replace(/^\.\//, "").replace(/^\//, ""),
|
|
63
|
+
),
|
|
61
64
|
);
|
|
62
65
|
|
|
63
66
|
// Compute symmetric difference
|
|
@@ -1317,7 +1317,8 @@ export async function _deriveStateImpl(basePath: string): Promise<GSDState> {
|
|
|
1317
1317
|
? `All milestones complete. ${activeReqs} active requirement${activeReqs === 1 ? '' : 's'} in REQUIREMENTS.md ${activeReqs === 1 ? 'has' : 'have'} not been mapped to a milestone.`
|
|
1318
1318
|
: 'All milestones complete.';
|
|
1319
1319
|
return {
|
|
1320
|
-
activeMilestone:
|
|
1320
|
+
activeMilestone: null,
|
|
1321
|
+
lastCompletedMilestone: lastEntry ? { id: lastEntry.id, title: lastEntry.title } : null,
|
|
1321
1322
|
activeSlice: null,
|
|
1322
1323
|
activeTask: null,
|
|
1323
1324
|
phase: 'complete',
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
3
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
5
6
|
|
|
6
7
|
import {
|
|
7
8
|
unitVerb,
|
|
@@ -11,11 +12,29 @@ import {
|
|
|
11
12
|
formatWidgetTokens,
|
|
12
13
|
estimateTimeRemaining,
|
|
13
14
|
extractUatSliceId,
|
|
15
|
+
getWidgetMode,
|
|
16
|
+
cycleWidgetMode,
|
|
17
|
+
_resetWidgetModeForTests,
|
|
14
18
|
} from "../auto-dashboard.ts";
|
|
15
19
|
|
|
16
20
|
const autoSource = readFileSync(join(process.cwd(), "src", "resources", "extensions", "gsd", "auto.ts"), "utf-8");
|
|
17
21
|
const dashboardSource = readFileSync(join(process.cwd(), "src", "resources", "extensions", "gsd", "auto-dashboard.ts"), "utf-8");
|
|
18
22
|
|
|
23
|
+
function makeTempDir(prefix: string): string {
|
|
24
|
+
return join(
|
|
25
|
+
tmpdir(),
|
|
26
|
+
`gsd-auto-dashboard-test-${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function cleanup(dir: string): void {
|
|
31
|
+
try {
|
|
32
|
+
rmSync(dir, { recursive: true, force: true });
|
|
33
|
+
} catch {
|
|
34
|
+
// best-effort
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
19
38
|
// ─── unitVerb ─────────────────────────────────────────────────────────────
|
|
20
39
|
|
|
21
40
|
test("unitVerb maps known unit types to verbs", () => {
|
|
@@ -209,3 +228,35 @@ test("extractUatSliceId returns null for invalid formats", () => {
|
|
|
209
228
|
assert.equal(extractUatSliceId(""), null);
|
|
210
229
|
assert.equal(extractUatSliceId("M001/T01"), null);
|
|
211
230
|
});
|
|
231
|
+
|
|
232
|
+
test("widget mode respects project preference precedence and persists there", (t) => {
|
|
233
|
+
const homeDir = makeTempDir("home");
|
|
234
|
+
const projectDir = makeTempDir("project");
|
|
235
|
+
const globalPrefsPath = join(homeDir, ".gsd", "preferences.md");
|
|
236
|
+
const projectPrefsPath = join(projectDir, ".gsd", "preferences.md");
|
|
237
|
+
|
|
238
|
+
mkdirSync(join(homeDir, ".gsd"), { recursive: true });
|
|
239
|
+
mkdirSync(join(projectDir, ".gsd"), { recursive: true });
|
|
240
|
+
writeFileSync(globalPrefsPath, "---\nversion: 1\nwidget_mode: off\n---\n", "utf-8");
|
|
241
|
+
writeFileSync(projectPrefsPath, "---\nversion: 1\nwidget_mode: small\n---\n", "utf-8");
|
|
242
|
+
|
|
243
|
+
t.after(() => {
|
|
244
|
+
cleanup(homeDir);
|
|
245
|
+
cleanup(projectDir);
|
|
246
|
+
_resetWidgetModeForTests();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
_resetWidgetModeForTests();
|
|
250
|
+
|
|
251
|
+
assert.equal(getWidgetMode(projectPrefsPath, globalPrefsPath), "small", "project widget_mode overrides global");
|
|
252
|
+
assert.equal(
|
|
253
|
+
cycleWidgetMode(projectPrefsPath, globalPrefsPath),
|
|
254
|
+
"min",
|
|
255
|
+
"cycling advances from the project-owned mode",
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const projectPrefs = readFileSync(projectPrefsPath, "utf-8");
|
|
259
|
+
const globalPrefs = readFileSync(globalPrefsPath, "utf-8");
|
|
260
|
+
assert.match(projectPrefs, /widget_mode:\s*min/);
|
|
261
|
+
assert.match(globalPrefs, /widget_mode:\s*off/);
|
|
262
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import test from "node:test";
|
|
1
|
+
import test, { mock } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { resolve } from "node:path";
|
|
@@ -191,6 +191,54 @@ test("runUnit returns cancelled when session creation times out", async () => {
|
|
|
191
191
|
assert.equal(pi.calls.length, 0);
|
|
192
192
|
});
|
|
193
193
|
|
|
194
|
+
test("runUnit keeps the session-switch guard across a late newSession settlement", async () => {
|
|
195
|
+
_resetPendingResolve();
|
|
196
|
+
mock.timers.enable();
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const ctx = makeMockCtx();
|
|
200
|
+
const pi = makeMockPi();
|
|
201
|
+
// Use delays longer than NEW_SESSION_TIMEOUT_MS (120s) so the timeout fires
|
|
202
|
+
const firstSession = makeMockSession({ newSessionDelayMs: 200_000 });
|
|
203
|
+
const secondSession = makeMockSession({ newSessionDelayMs: 200_000 });
|
|
204
|
+
|
|
205
|
+
const firstRun = runUnit(ctx, pi, firstSession, "task", "T01", "prompt");
|
|
206
|
+
|
|
207
|
+
// Tick past the 120s session timeout
|
|
208
|
+
mock.timers.tick(121_000);
|
|
209
|
+
await Promise.resolve();
|
|
210
|
+
|
|
211
|
+
const firstResult = await firstRun;
|
|
212
|
+
assert.equal(firstResult.status, "cancelled");
|
|
213
|
+
assert.equal(isSessionSwitchInFlight(), true, "guard should remain set after the timed-out session");
|
|
214
|
+
|
|
215
|
+
mock.timers.tick(1);
|
|
216
|
+
const secondRun = runUnit(ctx, pi, secondSession, "task", "T02", "prompt");
|
|
217
|
+
|
|
218
|
+
mock.timers.tick(100_000);
|
|
219
|
+
await Promise.resolve();
|
|
220
|
+
assert.equal(
|
|
221
|
+
isSessionSwitchInFlight(),
|
|
222
|
+
true,
|
|
223
|
+
"late settlement from the first session must not clear the newer session guard",
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Tick past the second session's timeout (121s total > 120s NEW_SESSION_TIMEOUT_MS)
|
|
227
|
+
mock.timers.tick(21_001);
|
|
228
|
+
await Promise.resolve();
|
|
229
|
+
|
|
230
|
+
const secondResult = await secondRun;
|
|
231
|
+
assert.equal(secondResult.status, "cancelled");
|
|
232
|
+
|
|
233
|
+
// Tick past the second session's delayed promise (200s) so .finally() fires
|
|
234
|
+
mock.timers.tick(80_000);
|
|
235
|
+
await Promise.resolve();
|
|
236
|
+
assert.equal(isSessionSwitchInFlight(), false, "guard should clear after the newer session settles");
|
|
237
|
+
} finally {
|
|
238
|
+
mock.timers.reset();
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
194
242
|
test("runUnit returns cancelled when s.active is false before sendMessage", async () => {
|
|
195
243
|
_resetPendingResolve();
|
|
196
244
|
|
|
@@ -412,7 +460,7 @@ function makeMockDeps(
|
|
|
412
460
|
getCurrentBranch: () => "main",
|
|
413
461
|
autoWorktreeBranch: () => "auto/M001",
|
|
414
462
|
resolveMilestoneFile: () => null,
|
|
415
|
-
reconcileMergeState: () =>
|
|
463
|
+
reconcileMergeState: () => "clean",
|
|
416
464
|
getLedger: () => null,
|
|
417
465
|
getProjectTotals: () => ({ cost: 0 }),
|
|
418
466
|
formatCost: (c: number) => `$${c.toFixed(2)}`,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import test, { afterEach } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
import { verifyExpectedArtifact } from "../auto-recovery.ts";
|
|
8
|
+
import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertGateRow } from "../gsd-db.ts";
|
|
9
|
+
|
|
10
|
+
const tmpDirs: string[] = [];
|
|
11
|
+
|
|
12
|
+
function makeTmpProject(): string {
|
|
13
|
+
const dir = mkdtempSync(join(tmpdir(), "auto-recovery-"));
|
|
14
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
15
|
+
openDatabase(join(dir, ".gsd", "gsd.db"));
|
|
16
|
+
insertMilestone({ id: "M001", title: "Test Milestone", status: "active" });
|
|
17
|
+
insertSlice({
|
|
18
|
+
milestoneId: "M001",
|
|
19
|
+
id: "S01",
|
|
20
|
+
title: "Test Slice",
|
|
21
|
+
status: "pending",
|
|
22
|
+
risk: "low",
|
|
23
|
+
depends: [],
|
|
24
|
+
});
|
|
25
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
|
|
26
|
+
tmpDirs.push(dir);
|
|
27
|
+
return dir;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
closeDatabase();
|
|
32
|
+
for (const dir of tmpDirs) {
|
|
33
|
+
try {
|
|
34
|
+
rmSync(dir, { recursive: true, force: true });
|
|
35
|
+
} catch {
|
|
36
|
+
// Best-effort cleanup only.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
tmpDirs.length = 0;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("verifyExpectedArtifact checks pending gate-evaluate artifacts without ESM require failures", () => {
|
|
43
|
+
const base = makeTmpProject();
|
|
44
|
+
|
|
45
|
+
const verified = verifyExpectedArtifact("gate-evaluate", "M001/S01/gates+Q3", base);
|
|
46
|
+
|
|
47
|
+
assert.equal(verified, false, "pending gates should keep gate-evaluate unverified");
|
|
48
|
+
});
|
|
@@ -138,6 +138,28 @@ test("generateCodebaseMap: excludes .gsd/ files", () => {
|
|
|
138
138
|
}
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
+
test("generateCodebaseMap: excludes .claude/ and other tool directories", () => {
|
|
142
|
+
const base = makeTmpRepo();
|
|
143
|
+
try {
|
|
144
|
+
addFile(base, "src/main.ts");
|
|
145
|
+
addFile(base, ".claude/CLAUDE.md");
|
|
146
|
+
addFile(base, ".claude/memory/user.md");
|
|
147
|
+
addFile(base, ".plans/plan.md");
|
|
148
|
+
addFile(base, ".cursor/settings.json");
|
|
149
|
+
addFile(base, ".vscode/settings.json");
|
|
150
|
+
|
|
151
|
+
const result = generateCodebaseMap(base);
|
|
152
|
+
assert.ok(result.content.includes("`src/main.ts`"), "should include src/main.ts");
|
|
153
|
+
assert.ok(!result.content.includes("CLAUDE.md"), "should exclude .claude/ files");
|
|
154
|
+
assert.ok(!result.content.includes("user.md"), "should exclude .claude/memory/ files");
|
|
155
|
+
assert.ok(!result.content.includes(".plans"), "should exclude .plans/ files");
|
|
156
|
+
assert.ok(!result.content.includes(".cursor"), "should exclude .cursor/ files");
|
|
157
|
+
assert.ok(!result.content.includes(".vscode"), "should exclude .vscode/ files");
|
|
158
|
+
} finally {
|
|
159
|
+
cleanup(base);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
141
163
|
test("generateCodebaseMap: excludes binary and lock files", () => {
|
|
142
164
|
const base = makeTmpRepo();
|
|
143
165
|
try {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { handleCoreCommand } from "../commands/handlers/core.ts";
|
|
5
|
+
|
|
6
|
+
function makeCtx(customResult: unknown) {
|
|
7
|
+
const notices: Array<{ message: string; type?: string }> = [];
|
|
8
|
+
return {
|
|
9
|
+
hasUI: true,
|
|
10
|
+
ui: {
|
|
11
|
+
custom: async () => customResult,
|
|
12
|
+
notify: (message: string, type?: string) => {
|
|
13
|
+
notices.push({ message, type });
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
notices,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
test("visualize only falls back when ctx.ui.custom() is unavailable", async () => {
|
|
21
|
+
const successCtx = makeCtx(true);
|
|
22
|
+
const success = await handleCoreCommand("visualize", successCtx as any);
|
|
23
|
+
assert.equal(success, true);
|
|
24
|
+
assert.equal(successCtx.notices.length, 0, "successful overlay close does not trigger fallback");
|
|
25
|
+
|
|
26
|
+
const fallbackCtx = makeCtx(undefined);
|
|
27
|
+
const fallback = await handleCoreCommand("visualize", fallbackCtx as any);
|
|
28
|
+
assert.equal(fallback, true);
|
|
29
|
+
assert.equal(fallbackCtx.notices.length, 1, "unavailable overlay triggers fallback warning");
|
|
30
|
+
assert.match(fallbackCtx.notices[0]!.message, /interactive terminal/i);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("show-config only falls back when ctx.ui.custom() is unavailable", async () => {
|
|
34
|
+
const successCtx = makeCtx(true);
|
|
35
|
+
const success = await handleCoreCommand("show-config", successCtx as any);
|
|
36
|
+
assert.equal(success, true);
|
|
37
|
+
assert.equal(successCtx.notices.length, 0, "successful overlay close does not trigger fallback");
|
|
38
|
+
|
|
39
|
+
const fallbackCtx = makeCtx(undefined);
|
|
40
|
+
const fallback = await handleCoreCommand("show-config", fallbackCtx as any);
|
|
41
|
+
assert.equal(fallback, true);
|
|
42
|
+
assert.equal(fallbackCtx.notices.length, 1, "unavailable overlay triggers text fallback");
|
|
43
|
+
assert.match(fallbackCtx.notices[0]!.message, /GSD Configuration/);
|
|
44
|
+
});
|
|
@@ -178,7 +178,7 @@ function makeMockDeps(overrides?: Partial<LoopDeps>): LoopDeps & { callLog: stri
|
|
|
178
178
|
getCurrentBranch: () => "main",
|
|
179
179
|
autoWorktreeBranch: () => "auto/M001",
|
|
180
180
|
resolveMilestoneFile: () => null,
|
|
181
|
-
reconcileMergeState: () =>
|
|
181
|
+
reconcileMergeState: () => "clean",
|
|
182
182
|
getLedger: () => null,
|
|
183
183
|
getProjectTotals: () => ({ cost: 0 }),
|
|
184
184
|
formatCost: (c: number) => `$${c.toFixed(2)}`,
|
|
@@ -311,6 +311,12 @@ describe("Custom engine loop integration", () => {
|
|
|
311
311
|
`stopAuto reason should include "Workflow complete", got: ${stopEntry}`,
|
|
312
312
|
);
|
|
313
313
|
|
|
314
|
+
assert.equal(
|
|
315
|
+
deps.callLog.filter((e: string) => e === "deriveState").length,
|
|
316
|
+
3,
|
|
317
|
+
"custom engine should stop immediately after a milestone-complete reconcile",
|
|
318
|
+
);
|
|
319
|
+
|
|
314
320
|
// Verify dev path was NOT used (resolveDispatch should not appear)
|
|
315
321
|
assert.ok(
|
|
316
322
|
!deps.callLog.includes("resolveDispatch"),
|
|
@@ -249,6 +249,37 @@ describe("CustomWorkflowEngine.reconcile", () => {
|
|
|
249
249
|
const graph = readGraph(runDir);
|
|
250
250
|
assert.equal(graph.steps[0].status, "complete");
|
|
251
251
|
});
|
|
252
|
+
|
|
253
|
+
it("re-reads GRAPH.yaml before reconcile so concurrent edits are preserved", async () => {
|
|
254
|
+
const { engine, runDir } = setupEngine([
|
|
255
|
+
makeStep({ id: "step-1" }),
|
|
256
|
+
makeStep({ id: "step-2", dependsOn: ["step-1"] }),
|
|
257
|
+
], "wf");
|
|
258
|
+
|
|
259
|
+
const staleState = await engine.deriveState("/unused");
|
|
260
|
+
|
|
261
|
+
// Simulate another process appending a new step after deriveState() ran.
|
|
262
|
+
writeGraph(runDir, makeGraph([
|
|
263
|
+
makeStep({ id: "step-1" }),
|
|
264
|
+
makeStep({ id: "step-2", dependsOn: ["step-1"] }),
|
|
265
|
+
makeStep({ id: "step-3", dependsOn: ["step-2"] }),
|
|
266
|
+
], "wf"));
|
|
267
|
+
|
|
268
|
+
const result = await engine.reconcile(staleState, {
|
|
269
|
+
unitType: "custom-step",
|
|
270
|
+
unitId: "wf/step-1",
|
|
271
|
+
startedAt: Date.now() - 1000,
|
|
272
|
+
finishedAt: Date.now(),
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
assert.equal(result.outcome, "continue");
|
|
276
|
+
|
|
277
|
+
const graph = readGraph(runDir);
|
|
278
|
+
assert.equal(graph.steps.length, 3, "reconcile should preserve the concurrent graph edit");
|
|
279
|
+
assert.equal(graph.steps[0].status, "complete");
|
|
280
|
+
assert.equal(graph.steps[1].status, "pending");
|
|
281
|
+
assert.equal(graph.steps[2].status, "pending");
|
|
282
|
+
});
|
|
252
283
|
});
|
|
253
284
|
|
|
254
285
|
// ─── getDisplayMetadata ──────────────────────────────────────────────────
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
detectProjectState,
|
|
18
18
|
detectV1Planning,
|
|
19
19
|
detectProjectSignals,
|
|
20
|
+
scanProjectFiles,
|
|
20
21
|
} from "../detection.ts";
|
|
21
22
|
|
|
22
23
|
function makeTempDir(prefix: string): string {
|
|
@@ -1188,3 +1189,39 @@ test("detectProjectSignals: Spring Boot settings-defined catalog accessor emits
|
|
|
1188
1189
|
cleanup(dir);
|
|
1189
1190
|
}
|
|
1190
1191
|
});
|
|
1192
|
+
|
|
1193
|
+
// ─── scanProjectFiles: RECURSIVE_SCAN_IGNORED_DIRS ──────────────────────
|
|
1194
|
+
|
|
1195
|
+
test("scanProjectFiles: excludes .claude, .gsd, .planning, .plans, .cursor, .vscode directories", () => {
|
|
1196
|
+
const dir = makeTempDir("scan-ignore-dotdirs");
|
|
1197
|
+
try {
|
|
1198
|
+
// Create project files that should be included
|
|
1199
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
1200
|
+
writeFileSync(join(dir, "src", "main.ts"), "// main\n", "utf-8");
|
|
1201
|
+
writeFileSync(join(dir, "README.md"), "# Project\n", "utf-8");
|
|
1202
|
+
|
|
1203
|
+
// Create tool directories that should be excluded
|
|
1204
|
+
const excludedDirs = [".claude", ".gsd", ".planning", ".plans", ".cursor", ".vscode"];
|
|
1205
|
+
for (const d of excludedDirs) {
|
|
1206
|
+
mkdirSync(join(dir, d), { recursive: true });
|
|
1207
|
+
writeFileSync(join(dir, d, "config.json"), "{}\n", "utf-8");
|
|
1208
|
+
}
|
|
1209
|
+
// Nested .claude directory
|
|
1210
|
+
mkdirSync(join(dir, ".claude", "memory"), { recursive: true });
|
|
1211
|
+
writeFileSync(join(dir, ".claude", "memory", "user.md"), "# Memory\n", "utf-8");
|
|
1212
|
+
|
|
1213
|
+
const files = scanProjectFiles(dir);
|
|
1214
|
+
|
|
1215
|
+
// Should include project files
|
|
1216
|
+
assert.ok(files.includes("src/main.ts"), "should include src/main.ts");
|
|
1217
|
+
assert.ok(files.includes("README.md"), "should include README.md");
|
|
1218
|
+
|
|
1219
|
+
// Should exclude all tool directories
|
|
1220
|
+
for (const d of excludedDirs) {
|
|
1221
|
+
const hasExcluded = files.some((f) => f.startsWith(`${d}/`));
|
|
1222
|
+
assert.ok(!hasExcluded, `should exclude ${d}/ directory but found: ${files.filter((f) => f.startsWith(`${d}/`)).join(", ")}`);
|
|
1223
|
+
}
|
|
1224
|
+
} finally {
|
|
1225
|
+
cleanup(dir);
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
|
|
8
|
+
import { validateFileChanges } from "../safety/file-change-validator.ts";
|
|
9
|
+
|
|
10
|
+
function git(cwd: string, ...args: string[]): string {
|
|
11
|
+
return execFileSync("git", args, {
|
|
12
|
+
cwd,
|
|
13
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
14
|
+
encoding: "utf-8",
|
|
15
|
+
}).trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test("validateFileChanges ignores inline descriptions in expected output paths", (t) => {
|
|
19
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-file-change-validator-"));
|
|
20
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
21
|
+
|
|
22
|
+
mkdirSync(join(base, "definitions"), { recursive: true });
|
|
23
|
+
git(base, "init");
|
|
24
|
+
git(base, "config", "user.email", "test@example.com");
|
|
25
|
+
git(base, "config", "user.name", "Test User");
|
|
26
|
+
|
|
27
|
+
const target = join(base, "definitions", "ac-audit.md");
|
|
28
|
+
writeFileSync(target, "initial\n");
|
|
29
|
+
git(base, "add", ".");
|
|
30
|
+
git(base, "commit", "-m", "initial");
|
|
31
|
+
|
|
32
|
+
writeFileSync(target, "updated\n");
|
|
33
|
+
git(base, "add", ".");
|
|
34
|
+
git(base, "commit", "-m", "update");
|
|
35
|
+
|
|
36
|
+
const audit = validateFileChanges(
|
|
37
|
+
base,
|
|
38
|
+
["definitions/ac-audit.md — current state of AC CRM, tags, pipelines, automations"],
|
|
39
|
+
[],
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
assert.ok(audit, "audit should be produced when expected output exists");
|
|
43
|
+
assert.deepEqual(audit.unexpectedFiles, []);
|
|
44
|
+
assert.deepEqual(audit.missingFiles, []);
|
|
45
|
+
assert.equal(
|
|
46
|
+
audit.violations.some((v) => v.severity === "warning"),
|
|
47
|
+
false,
|
|
48
|
+
"described expected output should not trigger unexpected-file warnings",
|
|
49
|
+
);
|
|
50
|
+
});
|
|
@@ -245,6 +245,41 @@ describe('gsd-tools', () => {
|
|
|
245
245
|
}
|
|
246
246
|
});
|
|
247
247
|
|
|
248
|
+
test('gsd_summary_save supports CONTEXT-DRAFT persistence', async () => {
|
|
249
|
+
const tmpDir = makeTmpDir();
|
|
250
|
+
try {
|
|
251
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
252
|
+
openDatabase(dbPath);
|
|
253
|
+
|
|
254
|
+
await saveArtifactToDb(
|
|
255
|
+
{
|
|
256
|
+
path: 'milestones/M001/M001-CONTEXT-DRAFT.md',
|
|
257
|
+
artifact_type: 'CONTEXT-DRAFT',
|
|
258
|
+
content: '# M001 Draft Context\n\nDraft notes.',
|
|
259
|
+
milestone_id: 'M001',
|
|
260
|
+
},
|
|
261
|
+
tmpDir,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const draftPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'M001-CONTEXT-DRAFT.md');
|
|
265
|
+
assert.ok(fs.existsSync(draftPath), 'Draft context file should be created');
|
|
266
|
+
const draftContent = fs.readFileSync(draftPath, 'utf-8');
|
|
267
|
+
assert.ok(draftContent.includes('Draft Context'), 'Draft context file should contain draft content');
|
|
268
|
+
|
|
269
|
+
const adapter = _getAdapter();
|
|
270
|
+
assert.ok(adapter !== null, 'Adapter should be available');
|
|
271
|
+
const rows = adapter!.prepare(
|
|
272
|
+
"SELECT * FROM artifacts WHERE path = 'milestones/M001/M001-CONTEXT-DRAFT.md'",
|
|
273
|
+
).all();
|
|
274
|
+
assert.deepStrictEqual(rows.length, 1, 'Should have 1 draft artifact row');
|
|
275
|
+
assert.deepStrictEqual(rows[0]['artifact_type'] as string, 'CONTEXT-DRAFT', 'Artifact type should be CONTEXT-DRAFT');
|
|
276
|
+
|
|
277
|
+
closeDatabase();
|
|
278
|
+
} finally {
|
|
279
|
+
cleanupDir(tmpDir);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
248
283
|
test('DB unavailable error paths', async () => {
|
|
249
284
|
// (d) All tools return isError when DB unavailable
|
|
250
285
|
// Close any open DB and don't open a new one
|
|
@@ -10,11 +10,15 @@
|
|
|
10
10
|
|
|
11
11
|
import { describe, test, beforeEach } from "node:test";
|
|
12
12
|
import assert from "node:assert/strict";
|
|
13
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
13
16
|
|
|
14
17
|
import {
|
|
15
18
|
getDiscussionMilestoneId,
|
|
16
19
|
setPendingAutoStart,
|
|
17
20
|
clearPendingAutoStart,
|
|
21
|
+
checkAutoStartAfterDiscuss,
|
|
18
22
|
} from "../guided-flow.ts";
|
|
19
23
|
|
|
20
24
|
// ─── Tests ─────────────────────────────────────────────────────────────────
|
|
@@ -95,3 +99,33 @@ describe("#2985 Bug 4 — getDiscussionMilestoneId must be keyed by basePath", (
|
|
|
95
99
|
assert.equal(result, "M001", "should return the only active milestone for backward compat");
|
|
96
100
|
});
|
|
97
101
|
});
|
|
102
|
+
|
|
103
|
+
test("checkAutoStartAfterDiscuss fails closed when a multi-milestone manifest is missing", () => {
|
|
104
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-auto-start-manifest-"));
|
|
105
|
+
try {
|
|
106
|
+
const gsdDir = join(base, ".gsd");
|
|
107
|
+
const milestoneDir = join(gsdDir, "milestones", "M001");
|
|
108
|
+
mkdirSync(milestoneDir, { recursive: true });
|
|
109
|
+
mkdirSync(join(gsdDir, "milestones", "M002"), { recursive: true });
|
|
110
|
+
writeFileSync(
|
|
111
|
+
join(gsdDir, "PROJECT.md"),
|
|
112
|
+
`# Project\n\n| M001 | First milestone | active |\n| M002 | Second milestone | queued |\n`,
|
|
113
|
+
);
|
|
114
|
+
writeFileSync(join(gsdDir, "STATE.md"), "# State\n");
|
|
115
|
+
writeFileSync(join(milestoneDir, "M001-CONTEXT.md"), "# M001 Context\n");
|
|
116
|
+
|
|
117
|
+
clearPendingAutoStart();
|
|
118
|
+
setPendingAutoStart(base, {
|
|
119
|
+
basePath: base,
|
|
120
|
+
milestoneId: "M001",
|
|
121
|
+
ctx: { ui: { notify: () => undefined } } as any,
|
|
122
|
+
pi: { setActiveTools: () => undefined, getActiveTools: () => [] } as any,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const started = checkAutoStartAfterDiscuss();
|
|
126
|
+
assert.equal(started, false, "auto-start should fail closed without the manifest");
|
|
127
|
+
} finally {
|
|
128
|
+
clearPendingAutoStart();
|
|
129
|
+
rmSync(base, { recursive: true, force: true });
|
|
130
|
+
}
|
|
131
|
+
});
|