gsd-pi 2.44.0-dev.d25d507 → 2.45.0-dev.6b9da3e
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/gsd/activity-log.js +7 -0
- package/dist/resources/extensions/gsd/auto/infra-errors.js +3 -0
- package/dist/resources/extensions/gsd/auto/phases.js +37 -36
- package/dist/resources/extensions/gsd/auto-prompts.js +24 -1
- package/dist/resources/extensions/gsd/auto-start.js +21 -2
- package/dist/resources/extensions/gsd/auto-timers.js +57 -3
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +4 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +9 -6
- package/dist/resources/extensions/gsd/auto.js +30 -3
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +156 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +7 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +2 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +10 -0
- package/dist/resources/extensions/gsd/commands-mcp-status.js +187 -0
- package/dist/resources/extensions/gsd/db-writer.js +34 -16
- package/dist/resources/extensions/gsd/doctor.js +8 -0
- package/dist/resources/extensions/gsd/git-service.js +8 -3
- package/dist/resources/extensions/gsd/gsd-db.js +12 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -4
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +3 -14
- package/dist/resources/extensions/gsd/prompts/rethink.md +78 -0
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
- package/dist/resources/extensions/gsd/provider-error-pause.js +7 -0
- package/dist/resources/extensions/gsd/repo-identity.js +45 -7
- package/dist/resources/extensions/gsd/rethink.js +115 -0
- package/dist/resources/extensions/gsd/state.js +41 -3
- package/dist/resources/extensions/gsd/tools/plan-slice.js +1 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +1 -0
- package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -0
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +88 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +32 -2
- package/dist/resources/extensions/gsd/worktree-resolver.js +6 -0
- package/dist/resources/extensions/mcp-client/index.js +14 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +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/page.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- 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 +2 -2
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/page.js +1 -1
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +18 -18
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- 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/.next/static/chunks/4024.11ca5c01938e5948.js +9 -0
- package/dist/web/standalone/.next/static/chunks/{3721.bf31263de6d5fa46.js → 485.243af25f0cdf50d6.js} +2 -2
- package/dist/web/standalone/.next/static/chunks/app/{page-b9367c5ae13b99c6.js → page-6654a8cca61a3d1c.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/webpack-0a4cd455ec4197d2.js +1 -0
- package/dist/web/standalone/.next/static/css/dd4ae3f58ac9b600.css +1 -0
- package/package.json +1 -1
- package/packages/native/dist/stream-process/index.js +2 -2
- package/packages/native/src/__tests__/stream-process.test.mjs +34 -0
- package/packages/native/src/stream-process/index.ts +2 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +15 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/local-model-check.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/local-model-check.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/local-model-check.js +41 -0
- package/packages/pi-coding-agent/dist/core/local-model-check.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +11 -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 +20 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -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 +6 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.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 -0
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js +32 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +8 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts +15 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js +40 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +4 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +5 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +13 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +17 -8
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- 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 +7 -3
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.ts +15 -1
- package/packages/pi-coding-agent/src/core/local-model-check.ts +45 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +21 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +9 -0
- package/packages/pi-coding-agent/src/main.ts +19 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/timestamp.test.ts +38 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +10 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/timestamp.ts +48 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +3 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +18 -3
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +16 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +8 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/activity-log.ts +1 -0
- package/src/resources/extensions/gsd/auto/infra-errors.ts +3 -0
- package/src/resources/extensions/gsd/auto/phases.ts +46 -48
- package/src/resources/extensions/gsd/auto-prompts.ts +24 -1
- package/src/resources/extensions/gsd/auto-start.ts +25 -2
- package/src/resources/extensions/gsd/auto-timers.ts +64 -3
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +5 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +9 -6
- package/src/resources/extensions/gsd/auto.ts +37 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +148 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +7 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +2 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +10 -0
- package/src/resources/extensions/gsd/commands-mcp-status.ts +247 -0
- package/src/resources/extensions/gsd/db-writer.ts +39 -17
- package/src/resources/extensions/gsd/doctor.ts +7 -1
- package/src/resources/extensions/gsd/git-service.ts +6 -2
- package/src/resources/extensions/gsd/gsd-db.ts +16 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +1 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -4
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
- package/src/resources/extensions/gsd/prompts/replan-slice.md +3 -14
- package/src/resources/extensions/gsd/prompts/rethink.md +78 -0
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
- package/src/resources/extensions/gsd/provider-error-pause.ts +9 -0
- package/src/resources/extensions/gsd/repo-identity.ts +46 -7
- package/src/resources/extensions/gsd/rethink.ts +154 -0
- package/src/resources/extensions/gsd/state.ts +41 -1
- package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +121 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/est-annotation-timeout.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/infra-error.test.ts +20 -2
- package/src/resources/extensions/gsd/tests/inherited-repo-home-dir.test.ts +121 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +11 -7
- package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/stop-auto-merge-back.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +108 -0
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +65 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +2 -0
- package/src/resources/extensions/gsd/tools/replan-slice.ts +3 -0
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +127 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +43 -2
- package/src/resources/extensions/gsd/worktree-resolver.ts +7 -0
- package/src/resources/extensions/mcp-client/index.ts +20 -0
- package/dist/web/standalone/.next/static/chunks/4024.0de81b543b28b9fe.js +0 -9
- package/dist/web/standalone/.next/static/chunks/webpack-9014b5adb127a98a.js +0 -1
- package/dist/web/standalone/.next/static/css/8a727f372cf53002.css +0 -1
- /package/dist/web/standalone/.next/static/{tokoGmfkYfWf1_Yl_Gz7i → rzO54ZboyINyEt7cVM_uS}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{tokoGmfkYfWf1_Yl_Gz7i → rzO54ZboyINyEt7cVM_uS}/_ssgManifest.js +0 -0
|
@@ -16,6 +16,8 @@ All relevant context has been preloaded below — the roadmap, all slice summari
|
|
|
16
16
|
|
|
17
17
|
{{inlinedContext}}
|
|
18
18
|
|
|
19
|
+
{{skillActivation}}
|
|
20
|
+
|
|
19
21
|
## Validation Steps
|
|
20
22
|
|
|
21
23
|
1. For each **success criterion** in `{{roadmapPath}}`, check whether slice summaries and UAT results provide evidence that it was met. Record pass/fail per criterion.
|
|
@@ -25,47 +27,15 @@ All relevant context has been preloaded below — the roadmap, all slice summari
|
|
|
25
27
|
5. Determine a verdict:
|
|
26
28
|
- `pass` — all criteria met, all slices delivered, no gaps
|
|
27
29
|
- `needs-attention` — minor gaps that do not block completion (document them)
|
|
28
|
-
- `needs-remediation` — material gaps found;
|
|
29
|
-
|
|
30
|
-
## Output
|
|
31
|
-
|
|
32
|
-
Write `{{validationPath}}` with this structure:
|
|
33
|
-
|
|
34
|
-
```markdown
|
|
35
|
-
---
|
|
36
|
-
verdict: <pass|needs-attention|needs-remediation>
|
|
37
|
-
remediation_round: {{remediationRound}}
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
# Milestone Validation: {{milestoneId}}
|
|
30
|
+
- `needs-remediation` — material gaps found; remediation slices must be added to the roadmap
|
|
41
31
|
|
|
42
|
-
##
|
|
43
|
-
- [x] Criterion 1 — evidence: ...
|
|
44
|
-
- [ ] Criterion 2 — gap: ...
|
|
32
|
+
## Persist Validation
|
|
45
33
|
|
|
46
|
-
|
|
47
|
-
| Slice | Claimed | Delivered | Status |
|
|
48
|
-
|-------|---------|-----------|--------|
|
|
49
|
-
| S01 | ... | ... | pass |
|
|
50
|
-
|
|
51
|
-
## Cross-Slice Integration
|
|
52
|
-
(any boundary mismatches)
|
|
53
|
-
|
|
54
|
-
## Requirement Coverage
|
|
55
|
-
(any unaddressed requirements)
|
|
56
|
-
|
|
57
|
-
## Verdict Rationale
|
|
58
|
-
(why this verdict was chosen)
|
|
59
|
-
|
|
60
|
-
## Remediation Plan
|
|
61
|
-
(only if verdict is needs-remediation — list new slices to add to the roadmap)
|
|
62
|
-
```
|
|
34
|
+
**Persist validation results through `gsd_validate_milestone`.** Call it with: `milestoneId`, `verdict`, `remediationRound`, `successCriteriaChecklist`, `sliceDeliveryAudit`, `crossSliceIntegration`, `requirementCoverage`, `verdictRationale`, and `remediationPlan` (if verdict is `needs-remediation`). The tool writes the validation to the DB and renders VALIDATION.md to disk.
|
|
63
35
|
|
|
64
36
|
If verdict is `needs-remediation`:
|
|
65
|
-
-
|
|
66
|
-
- These slices will be planned and executed before validation re-runs
|
|
67
|
-
|
|
68
|
-
**You MUST write `{{validationPath}}` before finishing.**
|
|
37
|
+
- After calling `gsd_validate_milestone`, use `gsd_reassess_roadmap` to add remediation slices. Pass `milestoneId`, a synthetic `completedSliceId` (e.g. "VALIDATION"), `verdict: "roadmap-adjusted"`, `assessment` text, and `sliceChanges` with the new slices in the `added` array. The tool persists the changes to the DB and re-renders ROADMAP.md.
|
|
38
|
+
- These remediation slices will be planned and executed before validation re-runs.
|
|
69
39
|
|
|
70
40
|
**File system safety:** When scanning milestone directories for evidence, use `ls` or `find` to list directory contents first — never pass a directory path (e.g. `tasks/`, `slices/`) directly to the `read` tool. The `read` tool only accepts file paths, not directories.
|
|
71
41
|
|
|
@@ -19,6 +19,11 @@ export function classifyProviderError(errorMsg: string): {
|
|
|
19
19
|
const isRateLimit = /rate.?limit|too many requests|429/i.test(errorMsg);
|
|
20
20
|
const isServerError = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable/i.test(errorMsg);
|
|
21
21
|
|
|
22
|
+
// Connection/process errors — transient, auto-resume after brief backoff (#2309).
|
|
23
|
+
// These indicate the process was killed, the connection was reset, or a network
|
|
24
|
+
// blip occurred. They are NOT permanent failures.
|
|
25
|
+
const isConnectionError = /terminated|connection.?reset|connection.?refused|other side closed|fetch failed|network.?(?:is\s+)?unavailable|ECONNREFUSED|ECONNRESET|EPIPE/i.test(errorMsg);
|
|
26
|
+
|
|
22
27
|
// Permanent errors — never auto-resume
|
|
23
28
|
const isPermanent = /auth|unauthorized|forbidden|invalid.*key|invalid.*api|billing|quota exceeded|account/i.test(errorMsg);
|
|
24
29
|
|
|
@@ -37,6 +42,10 @@ export function classifyProviderError(errorMsg: string): {
|
|
|
37
42
|
return { isTransient: true, isRateLimit: false, suggestedDelayMs: 30_000 }; // 30s for server errors
|
|
38
43
|
}
|
|
39
44
|
|
|
45
|
+
if (isConnectionError) {
|
|
46
|
+
return { isTransient: true, isRateLimit: false, suggestedDelayMs: 15_000 }; // 15s for connection errors
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
// Unknown error — treat as permanent (user reviews)
|
|
41
50
|
return { isTransient: false, isRateLimit: false, suggestedDelayMs: 0 };
|
|
42
51
|
}
|
|
@@ -104,16 +104,17 @@ export function readRepoMeta(externalPath: string): RepoMeta | null {
|
|
|
104
104
|
* Returns true when ALL of:
|
|
105
105
|
* 1. basePath is inside a git repo (git rev-parse succeeds)
|
|
106
106
|
* 2. The resolved git root is a proper ancestor of basePath
|
|
107
|
-
* 3. There is no `.gsd` directory at the git root
|
|
108
|
-
* has not been
|
|
107
|
+
* 3. There is no *project* `.gsd` directory at the git root or any
|
|
108
|
+
* intermediate ancestor (the parent project has not been
|
|
109
|
+
* initialised with GSD)
|
|
109
110
|
*
|
|
110
111
|
* When true, the caller should run `git init` at basePath so that
|
|
111
112
|
* `repoIdentity()` produces a hash unique to this directory, preventing
|
|
112
113
|
* cross-project state leaks (#1639).
|
|
113
114
|
*
|
|
114
|
-
* When the git root already has `.gsd`, the directory is a
|
|
115
|
-
* subdirectory of an existing GSD project — `cd src/ && /gsd`
|
|
116
|
-
* still load the parent project's milestones.
|
|
115
|
+
* When the git root already has a project `.gsd`, the directory is a
|
|
116
|
+
* legitimate subdirectory of an existing GSD project — `cd src/ && /gsd`
|
|
117
|
+
* should still load the parent project's milestones.
|
|
117
118
|
*/
|
|
118
119
|
export function isInheritedRepo(basePath: string): boolean {
|
|
119
120
|
try {
|
|
@@ -124,12 +125,12 @@ export function isInheritedRepo(basePath: string): boolean {
|
|
|
124
125
|
|
|
125
126
|
// The git root is a proper ancestor. Check whether it already has .gsd
|
|
126
127
|
// (i.e. the parent project was initialised with GSD).
|
|
127
|
-
if (
|
|
128
|
+
if (isProjectGsd(join(root, ".gsd"))) return false;
|
|
128
129
|
|
|
129
130
|
// Also walk up from basePath to the git root checking for .gsd
|
|
130
131
|
let dir = normalizedBase;
|
|
131
132
|
while (dir !== normalizedRoot && dir !== dirname(dir)) {
|
|
132
|
-
if (
|
|
133
|
+
if (isProjectGsd(join(dir, ".gsd"))) return false;
|
|
133
134
|
dir = dirname(dir);
|
|
134
135
|
}
|
|
135
136
|
|
|
@@ -139,6 +140,44 @@ export function isInheritedRepo(basePath: string): boolean {
|
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Distinguish a *project* `.gsd` from the global `~/.gsd` state directory.
|
|
145
|
+
*
|
|
146
|
+
* A project `.gsd` is either:
|
|
147
|
+
* - A symlink to an external state directory (normal post-migration layout)
|
|
148
|
+
* - A legacy real directory that is NOT the global GSD home
|
|
149
|
+
*
|
|
150
|
+
* When the user's home directory is itself a git repo (e.g. dotfile managers),
|
|
151
|
+
* `~/.gsd` exists but is the global state directory — not a project `.gsd`.
|
|
152
|
+
* Treating it as a project `.gsd` would cause isInheritedRepo() to wrongly
|
|
153
|
+
* conclude that subdirectories are part of the home "project" (#2393).
|
|
154
|
+
*/
|
|
155
|
+
function isProjectGsd(gsdPath: string): boolean {
|
|
156
|
+
if (!existsSync(gsdPath)) return false;
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const stat = lstatSync(gsdPath);
|
|
160
|
+
|
|
161
|
+
// Symlinks are always project .gsd (created by ensureGsdSymlink).
|
|
162
|
+
if (stat.isSymbolicLink()) return true;
|
|
163
|
+
|
|
164
|
+
// For real directories, check that this isn't the global GSD home.
|
|
165
|
+
// Recompute gsdHome dynamically so env overrides (GSD_HOME) are
|
|
166
|
+
// picked up at call time, not just at module load time.
|
|
167
|
+
if (stat.isDirectory()) {
|
|
168
|
+
const currentGsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
169
|
+
const normalizedGsdPath = canonicalizeExistingPath(gsdPath);
|
|
170
|
+
const normalizedGsdHome = canonicalizeExistingPath(currentGsdHome);
|
|
171
|
+
if (normalizedGsdPath === normalizedGsdHome) return false;
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
// lstat failed — treat as no .gsd present
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
142
181
|
// ─── Repo Identity ──────────────────────────────────────────────────────────
|
|
143
182
|
|
|
144
183
|
/**
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Rethink — Conversational project reorganization.
|
|
3
|
+
*
|
|
4
|
+
* Collects a snapshot of all milestones (status, dependencies, slice progress,
|
|
5
|
+
* queue order) and dispatches a prompt that turns Claude into a reorganization
|
|
6
|
+
* assistant. Claude can then reorder, park, unpark, discard, or add milestones
|
|
7
|
+
* through conversation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
|
|
13
|
+
import { isAutoActive } from "./auto.js";
|
|
14
|
+
import { deriveState } from "./state.js";
|
|
15
|
+
import { gsdRoot } from "./paths.js";
|
|
16
|
+
import { findMilestoneIds } from "./milestone-ids.js";
|
|
17
|
+
import { loadQueueOrder, validateQueueOrder } from "./queue-order.js";
|
|
18
|
+
import { isParked, getParkedReason } from "./milestone-actions.js";
|
|
19
|
+
import { getMilestoneSlices, isDbAvailable } from "./gsd-db.js";
|
|
20
|
+
import { buildExistingMilestonesContext } from "./guided-flow-queue.js";
|
|
21
|
+
import { loadPrompt } from "./prompt-loader.js";
|
|
22
|
+
|
|
23
|
+
// ─── Entry Point ──────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export async function handleRethink(
|
|
26
|
+
_args: string,
|
|
27
|
+
ctx: ExtensionCommandContext,
|
|
28
|
+
pi: ExtensionAPI,
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
if (isAutoActive()) {
|
|
31
|
+
ctx.ui.notify("Cannot rethink while auto-mode is active. Stop auto-mode first.", "error");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const basePath = process.cwd();
|
|
36
|
+
const root = gsdRoot(basePath);
|
|
37
|
+
if (!existsSync(root)) {
|
|
38
|
+
ctx.ui.notify("No GSD project found. Run /gsd init first.", "warning");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
ctx.ui.notify("Building project snapshot for rethink...", "info");
|
|
43
|
+
|
|
44
|
+
const state = await deriveState(basePath);
|
|
45
|
+
const milestoneIds = findMilestoneIds(basePath);
|
|
46
|
+
|
|
47
|
+
if (milestoneIds.length === 0) {
|
|
48
|
+
ctx.ui.notify("No milestones exist yet. Nothing to rethink.", "warning");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const queueOrder = loadQueueOrder(basePath);
|
|
53
|
+
const rethinkData = buildRethinkData(basePath, milestoneIds, state, queueOrder);
|
|
54
|
+
const existingMilestonesContext = await buildExistingMilestonesContext(basePath, milestoneIds, state);
|
|
55
|
+
|
|
56
|
+
const content = loadPrompt("rethink", {
|
|
57
|
+
rethinkData,
|
|
58
|
+
existingMilestonesContext,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
pi.sendMessage(
|
|
62
|
+
{ customType: "gsd-rethink", content, display: false },
|
|
63
|
+
{ triggerTurn: true },
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── Data Builder ─────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
function buildRethinkData(
|
|
70
|
+
basePath: string,
|
|
71
|
+
milestoneIds: string[],
|
|
72
|
+
state: Awaited<ReturnType<typeof deriveState>>,
|
|
73
|
+
queueOrder: string[] | null,
|
|
74
|
+
): string {
|
|
75
|
+
const lines: string[] = [];
|
|
76
|
+
const dbAvailable = isDbAvailable();
|
|
77
|
+
|
|
78
|
+
// ── Summary stats ───────────────────────────────────────────────────
|
|
79
|
+
const counts = { complete: 0, active: 0, pending: 0, parked: 0 };
|
|
80
|
+
for (const entry of state.registry) {
|
|
81
|
+
if (entry.status in counts) counts[entry.status as keyof typeof counts]++;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
lines.push("### Summary");
|
|
85
|
+
lines.push(`${counts.complete} complete, ${counts.active} active, ${counts.pending} pending, ${counts.parked} parked — ${milestoneIds.length} total`);
|
|
86
|
+
lines.push(`Queue order source: ${queueOrder ? "explicit QUEUE-ORDER.json" : "default numeric (by ID)"}`);
|
|
87
|
+
if (state.activeMilestone) {
|
|
88
|
+
lines.push(`Active milestone: ${state.activeMilestone}`);
|
|
89
|
+
}
|
|
90
|
+
lines.push("");
|
|
91
|
+
|
|
92
|
+
// ── Milestone table ─────────────────────────────────────────────────
|
|
93
|
+
lines.push("### Execution Order");
|
|
94
|
+
lines.push("");
|
|
95
|
+
lines.push("| # | ID | Title | Status | Dependencies | Slices |");
|
|
96
|
+
lines.push("|---|-----|-------|--------|--------------|--------|");
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < milestoneIds.length; i++) {
|
|
99
|
+
const mid = milestoneIds[i];
|
|
100
|
+
const entry = state.registry.find(m => m.id === mid);
|
|
101
|
+
const title = entry?.title ?? mid;
|
|
102
|
+
const status = entry?.status ?? "unknown";
|
|
103
|
+
const deps = entry?.dependsOn?.length ? entry.dependsOn.join(", ") : "—";
|
|
104
|
+
|
|
105
|
+
let sliceInfo = "—";
|
|
106
|
+
if (dbAvailable && status !== "complete") {
|
|
107
|
+
const slices = getMilestoneSlices(mid);
|
|
108
|
+
if (slices.length > 0) {
|
|
109
|
+
const done = slices.filter(s => s.status === "complete").length;
|
|
110
|
+
sliceInfo = `${done}/${slices.length} complete`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Add parked reason if applicable
|
|
115
|
+
let statusDisplay = status;
|
|
116
|
+
if (status === "parked") {
|
|
117
|
+
const reason = getParkedReason(basePath, mid);
|
|
118
|
+
if (reason) statusDisplay = `parked (${reason})`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
lines.push(`| ${i + 1} | ${mid} | ${title} | ${statusDisplay} | ${deps} | ${sliceInfo} |`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── Dependency validation ───────────────────────────────────────────
|
|
125
|
+
const pendingIds = milestoneIds.filter(mid => {
|
|
126
|
+
const entry = state.registry.find(m => m.id === mid);
|
|
127
|
+
return entry?.status !== "complete";
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const completedIds = new Set(
|
|
131
|
+
state.registry.filter(m => m.status === "complete").map(m => m.id),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const depsMap = new Map<string, string[]>();
|
|
135
|
+
for (const entry of state.registry) {
|
|
136
|
+
if (entry.dependsOn?.length) {
|
|
137
|
+
depsMap.set(entry.id, entry.dependsOn);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (pendingIds.length > 0 && depsMap.size > 0) {
|
|
142
|
+
const validation = validateQueueOrder(pendingIds, depsMap, completedIds);
|
|
143
|
+
|
|
144
|
+
if (validation.violations.length > 0) {
|
|
145
|
+
lines.push("");
|
|
146
|
+
lines.push("### Dependency Issues");
|
|
147
|
+
for (const v of validation.violations) {
|
|
148
|
+
lines.push(`- **${v.type}**: ${v.message}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return lines.join("\n");
|
|
154
|
+
}
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
getSliceTasks,
|
|
49
49
|
getReplanHistory,
|
|
50
50
|
getSlice,
|
|
51
|
+
insertMilestone,
|
|
51
52
|
type MilestoneRow,
|
|
52
53
|
type SliceRow,
|
|
53
54
|
type TaskRow,
|
|
@@ -257,7 +258,46 @@ function isStatusDone(status: string): boolean {
|
|
|
257
258
|
export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
258
259
|
const requirements = parseRequirementCounts(await loadFile(resolveGsdRootFile(basePath, "REQUIREMENTS")));
|
|
259
260
|
|
|
260
|
-
|
|
261
|
+
let allMilestones = getAllMilestones();
|
|
262
|
+
|
|
263
|
+
// Incremental disk→DB sync: milestone directories created outside the DB
|
|
264
|
+
// write path (via /gsd queue, manual mkdir, or complete-milestone writing the
|
|
265
|
+
// next CONTEXT.md) are never inserted by the initial migration guard in
|
|
266
|
+
// auto-start.ts because that guard only runs when gsd.db doesn't exist yet.
|
|
267
|
+
// Reconcile here so deriveStateFromDb never silently misses queued milestones.
|
|
268
|
+
// insertMilestone uses INSERT OR IGNORE, so this is safe to call every time.
|
|
269
|
+
const dbIdSet = new Set(allMilestones.map(m => m.id));
|
|
270
|
+
const diskIds = findMilestoneIds(basePath);
|
|
271
|
+
let synced = false;
|
|
272
|
+
for (const diskId of diskIds) {
|
|
273
|
+
if (!dbIdSet.has(diskId) && !isGhostMilestone(basePath, diskId)) {
|
|
274
|
+
insertMilestone({ id: diskId, status: 'active' });
|
|
275
|
+
synced = true;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (synced) allMilestones = getAllMilestones();
|
|
279
|
+
|
|
280
|
+
// Reconcile: discover milestones that exist on disk but are missing from
|
|
281
|
+
// the DB. This happens when milestones were created before the DB migration
|
|
282
|
+
// or were manually added to the filesystem. Without this, disk-only
|
|
283
|
+
// milestones are invisible after migration (#2416).
|
|
284
|
+
const dbMilestoneIds = new Set(allMilestones.map(m => m.id));
|
|
285
|
+
const diskMilestoneIds = findMilestoneIds(basePath);
|
|
286
|
+
for (const diskId of diskMilestoneIds) {
|
|
287
|
+
if (!dbMilestoneIds.has(diskId)) {
|
|
288
|
+
// Synthesize a minimal MilestoneRow for the disk-only milestone.
|
|
289
|
+
// Title and status will be resolved from disk files in the loop below.
|
|
290
|
+
allMilestones.push({
|
|
291
|
+
id: diskId,
|
|
292
|
+
title: diskId,
|
|
293
|
+
status: 'active',
|
|
294
|
+
depends_on: [] as string[],
|
|
295
|
+
created_at: new Date().toISOString(),
|
|
296
|
+
} as MilestoneRow);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// Re-sort so milestones are in canonical order after injection
|
|
300
|
+
allMilestones.sort((a, b) => milestoneIdSort(a.id, b.id));
|
|
261
301
|
|
|
262
302
|
// Parallel worker isolation: when locked, filter to just the locked milestone
|
|
263
303
|
const milestoneLock = process.env.GSD_MILESTONE_LOCK;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auto-pr-bugs.test.ts — Regression tests for #2302.
|
|
3
|
+
*
|
|
4
|
+
* Three interacting bugs prevented auto_pr from ever creating a PR:
|
|
5
|
+
* 1. auto_pr was gated on `pushed` (which requires auto_push)
|
|
6
|
+
* 2. Milestone branch was not pushed to remote before PR creation
|
|
7
|
+
* 3. createDraftPR in git-service.ts lacked --head/--base parameters
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
// ─── Bug 1: auto_pr should not depend on auto_push / pushed flag ────────────
|
|
16
|
+
|
|
17
|
+
const autoWorktreeSrcPath = join(import.meta.dirname, "..", "auto-worktree.ts");
|
|
18
|
+
const autoWorktreeSrc = readFileSync(autoWorktreeSrcPath, "utf-8");
|
|
19
|
+
|
|
20
|
+
test("#2302 bug 1: auto_pr condition should not require pushed flag", () => {
|
|
21
|
+
// Find the auto_pr block in mergeMilestoneToMain
|
|
22
|
+
const autoPrIdx = autoWorktreeSrc.indexOf("auto_pr");
|
|
23
|
+
assert.ok(autoPrIdx !== -1, "auto_pr reference exists in auto-worktree.ts");
|
|
24
|
+
|
|
25
|
+
// Get context around the auto_pr check
|
|
26
|
+
const lineStart = autoWorktreeSrc.lastIndexOf("\n", autoPrIdx) + 1;
|
|
27
|
+
const lineEnd = autoWorktreeSrc.indexOf("\n", autoPrIdx);
|
|
28
|
+
const autoPrLine = autoWorktreeSrc.slice(lineStart, lineEnd);
|
|
29
|
+
|
|
30
|
+
// The condition should NOT include `&& pushed`
|
|
31
|
+
assert.ok(
|
|
32
|
+
!autoPrLine.includes("&& pushed"),
|
|
33
|
+
"auto_pr condition should not be gated on pushed flag (auto_push dependency)",
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// ─── Bug 2: phases.ts should not duplicate PR creation ──────────────────────
|
|
38
|
+
|
|
39
|
+
const phasesSrcPath = join(import.meta.dirname, "..", "auto", "phases.ts");
|
|
40
|
+
const phasesSrc = readFileSync(phasesSrcPath, "utf-8");
|
|
41
|
+
|
|
42
|
+
test("#2302 bug 2: phases.ts should not call createDraftPR (handled by mergeMilestoneToMain)", () => {
|
|
43
|
+
// After fix, phases.ts should not import or call createDraftPR because
|
|
44
|
+
// PR creation is handled inside mergeMilestoneToMain in auto-worktree.ts
|
|
45
|
+
const createDraftPRCalls = phasesSrc.match(/createDraftPR\(/g) || [];
|
|
46
|
+
|
|
47
|
+
assert.equal(
|
|
48
|
+
createDraftPRCalls.length,
|
|
49
|
+
0,
|
|
50
|
+
"phases.ts should not call createDraftPR — it's handled by mergeMilestoneToMain",
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ─── Bug 3: createDraftPR should accept head and base branch parameters ─────
|
|
55
|
+
|
|
56
|
+
const gitServiceSrcPath = join(import.meta.dirname, "..", "git-service.ts");
|
|
57
|
+
const gitServiceSrc = readFileSync(gitServiceSrcPath, "utf-8");
|
|
58
|
+
|
|
59
|
+
test("#2302 bug 3: createDraftPR should accept head and base branch parameters", () => {
|
|
60
|
+
// Find the createDraftPR function signature
|
|
61
|
+
const fnIdx = gitServiceSrc.indexOf("function createDraftPR");
|
|
62
|
+
assert.ok(fnIdx !== -1, "createDraftPR function exists");
|
|
63
|
+
|
|
64
|
+
// Get the function signature (up to the closing paren)
|
|
65
|
+
const sigEnd = gitServiceSrc.indexOf(")", fnIdx);
|
|
66
|
+
const signature = gitServiceSrc.slice(fnIdx, sigEnd);
|
|
67
|
+
|
|
68
|
+
// Should have head and base parameters
|
|
69
|
+
assert.ok(
|
|
70
|
+
signature.includes("head") || signature.includes("branch"),
|
|
71
|
+
"createDraftPR should accept a head/branch parameter",
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("#2302 bug 3: createDraftPR should pass --head and --base to gh pr create", () => {
|
|
76
|
+
const fnIdx = gitServiceSrc.indexOf("function createDraftPR");
|
|
77
|
+
const fnEnd = gitServiceSrc.indexOf("\n}", fnIdx);
|
|
78
|
+
const fnBody = gitServiceSrc.slice(fnIdx, fnEnd);
|
|
79
|
+
|
|
80
|
+
assert.ok(
|
|
81
|
+
fnBody.includes("--head"),
|
|
82
|
+
"createDraftPR should pass --head to gh pr create",
|
|
83
|
+
);
|
|
84
|
+
assert.ok(
|
|
85
|
+
fnBody.includes("--base"),
|
|
86
|
+
"createDraftPR should pass --base to gh pr create",
|
|
87
|
+
);
|
|
88
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* completed-units-metrics-sync.test.ts — Regression tests for #2313.
|
|
3
|
+
*
|
|
4
|
+
* 1. completed-units.json should be archived (not wiped) on milestone transition
|
|
5
|
+
* 2. metrics.json should be in the worktree → project root sync file list
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, existsSync, cpSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
|
|
14
|
+
// ─── Bug 1: completed-units.json should be archived, not wiped ─────────────
|
|
15
|
+
|
|
16
|
+
const phasesSrcPath = join(import.meta.dirname, "..", "auto", "phases.ts");
|
|
17
|
+
const phasesSrc = readFileSync(phasesSrcPath, "utf-8");
|
|
18
|
+
|
|
19
|
+
test("#2313: completed-units.json should not be blindly wiped to [] on milestone transition", () => {
|
|
20
|
+
// The milestone transition block should NOT write an empty array to completed-units.json
|
|
21
|
+
// without first archiving the existing data. Look for the archive/rename pattern.
|
|
22
|
+
const transitionIdx = phasesSrc.indexOf("Milestone transition");
|
|
23
|
+
assert.ok(transitionIdx !== -1, "Milestone transition section exists");
|
|
24
|
+
|
|
25
|
+
// Find the completed-units handling block
|
|
26
|
+
const completedUnitsIdx = phasesSrc.indexOf("completed-units", transitionIdx);
|
|
27
|
+
assert.ok(completedUnitsIdx !== -1, "completed-units handling exists in transition");
|
|
28
|
+
|
|
29
|
+
// Get a window around the completed-units handling (1200 chars to
|
|
30
|
+
// accommodate CRLF line endings on Windows which inflate byte offsets).
|
|
31
|
+
const windowStart = Math.max(0, completedUnitsIdx - 300);
|
|
32
|
+
const windowEnd = Math.min(phasesSrc.length, completedUnitsIdx + 900);
|
|
33
|
+
const window = phasesSrc.slice(windowStart, windowEnd).toLowerCase();
|
|
34
|
+
|
|
35
|
+
// Should archive/rename the old file before resetting
|
|
36
|
+
const hasArchive = window.includes("archive") ||
|
|
37
|
+
window.includes("rename") ||
|
|
38
|
+
window.includes("cpsync") ||
|
|
39
|
+
window.includes("safecopy") ||
|
|
40
|
+
window.includes("completed-units-");
|
|
41
|
+
|
|
42
|
+
assert.ok(
|
|
43
|
+
hasArchive,
|
|
44
|
+
"completed-units.json should be archived before reset during milestone transition",
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// ─── Bug 2: metrics.json should be in the sync file lists ──────────────────
|
|
49
|
+
|
|
50
|
+
test("#2313: syncStateToProjectRoot should sync metrics.json", () => {
|
|
51
|
+
const syncSrcPath = join(import.meta.dirname, "..", "auto-worktree-sync.ts");
|
|
52
|
+
const syncSrc = readFileSync(syncSrcPath, "utf-8");
|
|
53
|
+
|
|
54
|
+
// syncStateToProjectRoot should copy metrics.json from worktree to project root
|
|
55
|
+
assert.ok(
|
|
56
|
+
syncSrc.includes("metrics.json"),
|
|
57
|
+
"auto-worktree-sync.ts should reference metrics.json for sync",
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("#2313: syncWorktreeStateBack should include metrics.json in root files list", () => {
|
|
62
|
+
const autoWorktreeSrcPath = join(import.meta.dirname, "..", "auto-worktree.ts");
|
|
63
|
+
const autoWorktreeSrc = readFileSync(autoWorktreeSrcPath, "utf-8");
|
|
64
|
+
|
|
65
|
+
// Find the rootFiles array in syncWorktreeStateBack
|
|
66
|
+
const syncBackIdx = autoWorktreeSrc.indexOf("syncWorktreeStateBack");
|
|
67
|
+
assert.ok(syncBackIdx !== -1, "syncWorktreeStateBack exists");
|
|
68
|
+
|
|
69
|
+
const rootFilesIdx = autoWorktreeSrc.indexOf("rootFiles", syncBackIdx);
|
|
70
|
+
assert.ok(rootFilesIdx !== -1, "rootFiles list exists in syncWorktreeStateBack");
|
|
71
|
+
|
|
72
|
+
// Get the rootFiles array content
|
|
73
|
+
const arrayStart = autoWorktreeSrc.indexOf("[", rootFilesIdx);
|
|
74
|
+
const arrayEnd = autoWorktreeSrc.indexOf("]", arrayStart);
|
|
75
|
+
const rootFilesBlock = autoWorktreeSrc.slice(arrayStart, arrayEnd);
|
|
76
|
+
|
|
77
|
+
assert.ok(
|
|
78
|
+
rootFilesBlock.includes("metrics.json"),
|
|
79
|
+
"metrics.json should be in syncWorktreeStateBack rootFiles list",
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ─── Functional test: completed-units archive ────────────────────────────────
|
|
84
|
+
|
|
85
|
+
test("#2313: functional — completed-units archive creates milestone-specific file", () => {
|
|
86
|
+
const tmpBase = mkdtempSync(join(tmpdir(), "gsd-completed-units-"));
|
|
87
|
+
const gsdDir = join(tmpBase, ".gsd");
|
|
88
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
89
|
+
|
|
90
|
+
// Simulate existing completed-units.json with data
|
|
91
|
+
const existing = [
|
|
92
|
+
{ type: "task", id: "T01" },
|
|
93
|
+
{ type: "slice", id: "S01" },
|
|
94
|
+
];
|
|
95
|
+
const completedKeysPath = join(gsdDir, "completed-units.json");
|
|
96
|
+
writeFileSync(completedKeysPath, JSON.stringify(existing, null, 2));
|
|
97
|
+
|
|
98
|
+
// Simulate the archive behavior: copy to milestone-specific file
|
|
99
|
+
const milestoneId = "M001";
|
|
100
|
+
const archivePath = join(gsdDir, `completed-units-${milestoneId}.json`);
|
|
101
|
+
cpSync(completedKeysPath, archivePath);
|
|
102
|
+
|
|
103
|
+
// Reset the main file
|
|
104
|
+
writeFileSync(completedKeysPath, JSON.stringify([], null, 2));
|
|
105
|
+
|
|
106
|
+
// Verify archive exists with original data
|
|
107
|
+
assert.ok(existsSync(archivePath), "archive file should exist");
|
|
108
|
+
const archived = JSON.parse(readFileSync(archivePath, "utf-8"));
|
|
109
|
+
assert.deepEqual(archived, existing, "archived data should match original");
|
|
110
|
+
|
|
111
|
+
// Verify main file is reset
|
|
112
|
+
const current = JSON.parse(readFileSync(completedKeysPath, "utf-8"));
|
|
113
|
+
assert.deepEqual(current, [], "current completed-units should be empty after transition");
|
|
114
|
+
});
|
|
@@ -483,6 +483,85 @@ describe('db-writer', () => {
|
|
|
483
483
|
}
|
|
484
484
|
});
|
|
485
485
|
|
|
486
|
+
test('saveArtifactToDb — shrinkage guard preserves larger existing file', async () => {
|
|
487
|
+
const tmpDir = makeTmpDir();
|
|
488
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
489
|
+
openDatabase(dbPath);
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
const fullContent = '# Full Research\n\n' + 'x'.repeat(20000) + '\n';
|
|
493
|
+
const abbreviatedContent = '# Summary\n\nShort version.\n';
|
|
494
|
+
|
|
495
|
+
// Pre-create the file with full content (simulating a prior `write` tool call)
|
|
496
|
+
const relPath = 'milestones/M001/M001-RESEARCH.md';
|
|
497
|
+
const filePath = path.join(tmpDir, '.gsd', relPath);
|
|
498
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
499
|
+
fs.writeFileSync(filePath, fullContent);
|
|
500
|
+
|
|
501
|
+
// Call saveArtifactToDb with abbreviated content — should trigger shrinkage guard
|
|
502
|
+
await saveArtifactToDb({
|
|
503
|
+
path: relPath,
|
|
504
|
+
artifact_type: 'RESEARCH',
|
|
505
|
+
content: abbreviatedContent,
|
|
506
|
+
milestone_id: 'M001',
|
|
507
|
+
}, tmpDir);
|
|
508
|
+
|
|
509
|
+
// Disk file should be preserved (not overwritten)
|
|
510
|
+
assert.deepStrictEqual(
|
|
511
|
+
fs.readFileSync(filePath, 'utf-8'),
|
|
512
|
+
fullContent,
|
|
513
|
+
'disk file preserved — shrinkage guard prevented overwrite',
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
// DB should contain the full disk content, not the abbreviated content
|
|
517
|
+
const adapter = _getAdapter();
|
|
518
|
+
const row = adapter!
|
|
519
|
+
.prepare('SELECT full_content FROM artifacts WHERE path = ?')
|
|
520
|
+
.get(relPath);
|
|
521
|
+
assert.deepStrictEqual(
|
|
522
|
+
row!['full_content'],
|
|
523
|
+
fullContent,
|
|
524
|
+
'DB stores the richer disk content instead of abbreviated content',
|
|
525
|
+
);
|
|
526
|
+
} finally {
|
|
527
|
+
closeDatabase();
|
|
528
|
+
cleanupDir(tmpDir);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test('saveArtifactToDb — allows overwrite when new content is similar size', async () => {
|
|
533
|
+
const tmpDir = makeTmpDir();
|
|
534
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
535
|
+
openDatabase(dbPath);
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
const oldContent = '# Summary v1\n\nOriginal content here.\n';
|
|
539
|
+
const newContent = '# Summary v2\n\nUpdated content here with more details.\n';
|
|
540
|
+
|
|
541
|
+
const relPath = 'milestones/M001/M001-SUMMARY.md';
|
|
542
|
+
const filePath = path.join(tmpDir, '.gsd', relPath);
|
|
543
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
544
|
+
fs.writeFileSync(filePath, oldContent);
|
|
545
|
+
|
|
546
|
+
await saveArtifactToDb({
|
|
547
|
+
path: relPath,
|
|
548
|
+
artifact_type: 'SUMMARY',
|
|
549
|
+
content: newContent,
|
|
550
|
+
milestone_id: 'M001',
|
|
551
|
+
}, tmpDir);
|
|
552
|
+
|
|
553
|
+
// Disk file should be updated (new content is >=50% of old size)
|
|
554
|
+
assert.deepStrictEqual(
|
|
555
|
+
fs.readFileSync(filePath, 'utf-8'),
|
|
556
|
+
newContent,
|
|
557
|
+
'disk file updated when new content is similar size',
|
|
558
|
+
);
|
|
559
|
+
} finally {
|
|
560
|
+
closeDatabase();
|
|
561
|
+
cleanupDir(tmpDir);
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
|
|
486
565
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
487
566
|
// Full Round-Trip: DB → Markdown → Parse → Compare
|
|
488
567
|
// ═══════════════════════════════════════════════════════════════════════════
|