@opengsd/gsd-pi 1.2.0-dev.4813ead6 → 1.2.0-dev.5457a158
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/cli-style.d.ts +17 -0
- package/dist/cli-style.js +28 -0
- package/dist/cli.js +1 -1
- package/dist/headless-events.d.ts +4 -2
- package/dist/headless-events.js +7 -29
- package/dist/models-resolver.d.ts +3 -13
- package/dist/models-resolver.js +3 -22
- package/dist/resource-loader.js +2 -14
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/bg-shell/utilities.js +5 -2
- package/dist/resources/extensions/claude-code-cli/models.js +9 -0
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +35 -4
- package/dist/resources/extensions/gsd/auto/orchestrator.js +33 -4
- package/dist/resources/extensions/gsd/auto/phases.js +6 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +19 -8
- package/dist/resources/extensions/gsd/auto-prompts.js +3 -0
- package/dist/resources/extensions/gsd/auto-start.js +12 -14
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +18 -0
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +7 -16
- package/dist/resources/extensions/gsd/auto-worktree-repair.js +10 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +35 -352
- package/dist/resources/extensions/gsd/auto.js +8 -20
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +3 -2
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -6
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +86 -6
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +30 -4
- package/dist/resources/extensions/gsd/branch-patterns.js +2 -0
- package/dist/resources/extensions/gsd/captures.js +5 -15
- package/dist/resources/extensions/gsd/closeout-recovery.js +3 -2
- package/dist/resources/extensions/gsd/commands/catalog.js +6 -62
- package/dist/resources/extensions/gsd/crash-recovery.js +4 -12
- package/dist/resources/extensions/gsd/db/engine.js +755 -0
- package/dist/resources/extensions/gsd/db/queries.js +372 -0
- package/dist/resources/extensions/gsd/db/sql-constants.js +11 -0
- package/dist/resources/extensions/gsd/db/writers/cascades.js +194 -0
- package/dist/resources/extensions/gsd/db/writers/import-restore.js +182 -0
- package/dist/resources/extensions/gsd/db/writers/memory.js +149 -0
- package/dist/resources/extensions/gsd/db/writers/reconcile.js +458 -0
- package/dist/resources/extensions/gsd/db/writers/status.js +70 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +5 -11
- package/dist/resources/extensions/gsd/doctor-format.js +9 -6
- package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -3
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +21 -16
- package/dist/resources/extensions/gsd/error-classifier.js +9 -0
- package/dist/resources/extensions/gsd/git-service.js +1 -0
- package/dist/resources/extensions/gsd/gitignore.js +3 -0
- package/dist/resources/extensions/gsd/gsd-db.js +171 -2048
- package/dist/resources/extensions/gsd/guidance.js +98 -0
- package/dist/resources/extensions/gsd/guided-flow.js +51 -5
- package/dist/resources/extensions/gsd/mcp-tool-name.js +5 -13
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +1 -1
- package/dist/resources/extensions/gsd/migrate/safety.js +20 -9
- package/dist/resources/extensions/gsd/migration-auto-check.js +24 -3
- package/dist/resources/extensions/gsd/model-cost-table.js +1 -0
- package/dist/resources/extensions/gsd/model-router.js +3 -0
- package/dist/resources/extensions/gsd/notification-store.js +11 -4
- package/dist/resources/extensions/gsd/parallel-merge.js +14 -11
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +11 -7
- package/dist/resources/extensions/gsd/paths.js +37 -24
- package/dist/resources/extensions/gsd/pre-execution-checks.js +91 -3
- package/dist/resources/extensions/gsd/preferences-models.js +12 -46
- package/dist/resources/extensions/gsd/preferences.js +14 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/dist/resources/extensions/gsd/provider-error-guidance.js +1 -5
- package/dist/resources/extensions/gsd/provider-switch-observer.js +1 -1
- package/dist/resources/extensions/gsd/publication.js +87 -0
- package/dist/resources/extensions/gsd/recovery-classification.js +41 -87
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +37 -4
- package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +7 -2
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +10 -0
- package/dist/resources/extensions/gsd/state-transition-matrix.js +38 -0
- package/dist/resources/extensions/gsd/state.js +1 -20
- package/dist/resources/extensions/gsd/status-guards.js +56 -8
- package/dist/resources/extensions/gsd/stop-notice.js +57 -0
- package/dist/resources/extensions/gsd/tool-surface-readiness.js +56 -0
- package/dist/resources/extensions/gsd/tools/complete-slice.js +24 -43
- package/dist/resources/extensions/gsd/tools/exec-tool.js +5 -8
- package/dist/resources/extensions/gsd/tools/plan-slice.js +12 -6
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +11 -29
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +14 -33
- package/dist/resources/extensions/gsd/tools/skip-slice.js +18 -36
- package/dist/resources/extensions/gsd/undo.js +8 -7
- package/dist/resources/extensions/gsd/unit-closeout.js +138 -0
- package/dist/resources/extensions/gsd/unit-context-composer.js +9 -1
- package/dist/resources/extensions/gsd/unit-context-manifest.js +4 -27
- package/dist/resources/extensions/gsd/unit-registry.js +350 -0
- package/dist/resources/extensions/gsd/unit-tool-contracts.js +9 -182
- package/dist/resources/extensions/gsd/workflow-tool-surface.js +1 -1
- package/dist/resources/extensions/gsd/worktree-git-recovery.js +293 -0
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +9 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +45 -28
- package/dist/resources/extensions/gsd/worktree-placement.js +59 -0
- package/dist/resources/extensions/gsd/worktree-reentry.js +12 -8
- package/dist/resources/extensions/gsd/worktree-root.js +28 -6
- package/dist/resources/extensions/gsd/worktree-safety.js +8 -5
- package/dist/resources/extensions/gsd/worktree-session-state.js +12 -11
- package/dist/resources/skills/gsd-browser/SKILL.md +1 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
- 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/server/app/_global-error.html +1 -1
- 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/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +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 +6 -6
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
- package/dist/web/standalone/.next/server/chunks/{5047.js → 5942.js} +2 -2
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/worktree-cli.js +3 -6
- package/dist/worktree-status-banner.js +7 -11
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +4 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/cli.js +6 -3
- package/packages/mcp-server/dist/cli.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +8 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +46 -21
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/image-models.generated.d.ts +0 -30
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +0 -30
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +361 -255
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +311 -256
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js +3 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/bg-shell/utilities.ts +5 -2
- package/src/resources/extensions/claude-code-cli/models.ts +9 -0
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +37 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +28 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -1
- package/src/resources/extensions/gsd/auto/orchestrator.ts +39 -5
- package/src/resources/extensions/gsd/auto/phases.ts +10 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +25 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +3 -0
- package/src/resources/extensions/gsd/auto-start.ts +12 -15
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +10 -17
- package/src/resources/extensions/gsd/auto-worktree-repair.ts +13 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +41 -364
- package/src/resources/extensions/gsd/auto.ts +20 -24
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +3 -5
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +10 -6
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +87 -6
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +29 -3
- package/src/resources/extensions/gsd/branch-patterns.ts +3 -0
- package/src/resources/extensions/gsd/captures.ts +5 -16
- package/src/resources/extensions/gsd/closeout-recovery.ts +2 -1
- package/src/resources/extensions/gsd/commands/catalog.ts +6 -68
- package/src/resources/extensions/gsd/crash-recovery.ts +3 -9
- package/src/resources/extensions/gsd/db/engine.ts +809 -0
- package/src/resources/extensions/gsd/db/queries.ts +453 -0
- package/src/resources/extensions/gsd/db/sql-constants.ts +12 -0
- package/src/resources/extensions/gsd/db/writers/cascades.ts +237 -0
- package/src/resources/extensions/gsd/db/writers/import-restore.ts +310 -0
- package/src/resources/extensions/gsd/db/writers/memory.ts +220 -0
- package/src/resources/extensions/gsd/db/writers/reconcile.ts +500 -0
- package/src/resources/extensions/gsd/db/writers/status.ts +88 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +5 -13
- package/src/resources/extensions/gsd/doctor-format.ts +12 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +3 -3
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +22 -17
- package/src/resources/extensions/gsd/error-classifier.ts +11 -0
- package/src/resources/extensions/gsd/git-service.ts +1 -0
- package/src/resources/extensions/gsd/gitignore.ts +3 -0
- package/src/resources/extensions/gsd/gsd-db.ts +173 -2373
- package/src/resources/extensions/gsd/guidance.ts +139 -0
- package/src/resources/extensions/gsd/guided-flow.ts +50 -5
- package/src/resources/extensions/gsd/mcp-tool-name.ts +6 -11
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +1 -1
- package/src/resources/extensions/gsd/migrate/safety.ts +18 -7
- package/src/resources/extensions/gsd/migration-auto-check.ts +28 -3
- package/src/resources/extensions/gsd/model-cost-table.ts +1 -0
- package/src/resources/extensions/gsd/model-router.ts +3 -0
- package/src/resources/extensions/gsd/notification-store.ts +26 -3
- package/src/resources/extensions/gsd/parallel-merge.ts +12 -9
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -7
- package/src/resources/extensions/gsd/paths.ts +42 -22
- package/src/resources/extensions/gsd/pre-execution-checks.ts +109 -3
- package/src/resources/extensions/gsd/preferences-models.ts +10 -46
- package/src/resources/extensions/gsd/preferences.ts +18 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/src/resources/extensions/gsd/provider-error-guidance.ts +4 -9
- package/src/resources/extensions/gsd/provider-switch-observer.ts +1 -1
- package/src/resources/extensions/gsd/publication.ts +122 -0
- package/src/resources/extensions/gsd/recovery-classification.ts +47 -88
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +36 -4
- package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +7 -2
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +14 -0
- package/src/resources/extensions/gsd/state-transition-matrix.ts +42 -0
- package/src/resources/extensions/gsd/state.ts +4 -21
- package/src/resources/extensions/gsd/status-guards.ts +59 -8
- package/src/resources/extensions/gsd/stop-notice.ts +75 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/auto-post-unit-evidence-crossref-4909.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-worktree-repair.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +66 -1
- package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +8 -7
- package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/guidance.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +51 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +54 -1
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +85 -1
- package/src/resources/extensions/gsd/tests/notification-store.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +193 -1
- package/src/resources/extensions/gsd/tests/provider-error-guidance.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/publication.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/recovery-classification-illegal-transition.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +248 -1
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/session-switch-clears-pending-autostart.test.ts +108 -0
- package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +43 -6
- package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/stop-notice.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +155 -0
- package/src/resources/extensions/gsd/tests/unit-closeout.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +23 -2
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +163 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +41 -4
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/worktree-placement.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +12 -6
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +42 -0
- package/src/resources/extensions/gsd/tool-surface-readiness.ts +76 -0
- package/src/resources/extensions/gsd/tools/complete-slice.ts +23 -58
- package/src/resources/extensions/gsd/tools/exec-tool.ts +5 -8
- package/src/resources/extensions/gsd/tools/plan-slice.ts +12 -6
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +11 -38
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +14 -42
- package/src/resources/extensions/gsd/tools/skip-slice.ts +18 -44
- package/src/resources/extensions/gsd/undo.ts +9 -8
- package/src/resources/extensions/gsd/unit-closeout.ts +201 -0
- package/src/resources/extensions/gsd/unit-context-composer.ts +12 -1
- package/src/resources/extensions/gsd/unit-context-manifest.ts +4 -28
- package/src/resources/extensions/gsd/unit-registry.ts +425 -0
- package/src/resources/extensions/gsd/unit-tool-contracts.ts +27 -192
- package/src/resources/extensions/gsd/workflow-tool-surface.ts +4 -1
- package/src/resources/extensions/gsd/worktree-git-recovery.ts +314 -0
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +10 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +47 -28
- package/src/resources/extensions/gsd/worktree-placement.ts +63 -0
- package/src/resources/extensions/gsd/worktree-reentry.ts +10 -7
- package/src/resources/extensions/gsd/worktree-root.ts +29 -6
- package/src/resources/extensions/gsd/worktree-safety.ts +8 -5
- package/src/resources/extensions/gsd/worktree-session-state.ts +11 -11
- package/src/resources/skills/gsd-browser/SKILL.md +1 -1
- /package/dist/web/standalone/.next/static/{tkLHUSzPA2kMmWz4DmGwI → 2p9Rv9pQflAxCBbGVI2vb}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{tkLHUSzPA2kMmWz4DmGwI → 2p9Rv9pQflAxCBbGVI2vb}/_ssgManifest.js +0 -0
|
@@ -88,3 +88,49 @@ test("detects session execution tools supported by the evidence collector", () =
|
|
|
88
88
|
|
|
89
89
|
assert.equal(_hasExecutionToolCallsInSessionForTest(entries), true);
|
|
90
90
|
});
|
|
91
|
+
|
|
92
|
+
test("detects execution tool calls in bare agent-end messages (no session-entry wrapper)", () => {
|
|
93
|
+
// The auto loop passes opts.agentEndMessages as bare {role, content}
|
|
94
|
+
// messages — not {type: "message", message} session-manager entries.
|
|
95
|
+
const entries = [
|
|
96
|
+
{
|
|
97
|
+
role: "assistant",
|
|
98
|
+
content: [
|
|
99
|
+
{
|
|
100
|
+
type: "toolCall",
|
|
101
|
+
name: "Bash",
|
|
102
|
+
arguments: { command: "test -s index.html && grep -q localStorage index.html" },
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
assert.equal(_hasExecutionToolCallsInSessionForTest(entries), true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("does not suppress for bare agent-end messages without execution tools", () => {
|
|
112
|
+
const entries = [
|
|
113
|
+
{
|
|
114
|
+
role: "assistant",
|
|
115
|
+
content: [
|
|
116
|
+
{ type: "text", text: "Task complete." },
|
|
117
|
+
{ type: "toolCall", name: "Write", arguments: { file_path: "index.html" } },
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
assert.equal(_hasExecutionToolCallsInSessionForTest(entries), false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("ignores bare user messages with toolCall-shaped content", () => {
|
|
126
|
+
const entries = [
|
|
127
|
+
{
|
|
128
|
+
role: "user",
|
|
129
|
+
content: [
|
|
130
|
+
{ type: "toolCall", name: "bash", arguments: { command: "echo hi" } },
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
assert.equal(_hasExecutionToolCallsInSessionForTest(entries), false);
|
|
136
|
+
});
|
|
@@ -184,7 +184,7 @@ describe("auto-worktree workspace registry", () => {
|
|
|
184
184
|
git(["commit", "-m", "add milestone"], tempDir);
|
|
185
185
|
|
|
186
186
|
createAutoWorktree(tempDir, "M003");
|
|
187
|
-
const wtDir = join(tempDir, ".gsd
|
|
187
|
+
const wtDir = join(tempDir, ".gsd-worktrees", "M003");
|
|
188
188
|
writeFileSync(join(wtDir, "feature.txt"), "implemented\n");
|
|
189
189
|
git(["add", "feature.txt"], wtDir);
|
|
190
190
|
git(["commit", "-m", "feat: implement M003"], wtDir);
|
|
@@ -216,7 +216,7 @@ describe("auto-worktree workspace registry", () => {
|
|
|
216
216
|
git(["commit", "-m", "add milestone"], tempDir);
|
|
217
217
|
|
|
218
218
|
createAutoWorktree(tempDir, "M004");
|
|
219
|
-
const wtDir = join(tempDir, ".gsd
|
|
219
|
+
const wtDir = join(tempDir, ".gsd-worktrees", "M004");
|
|
220
220
|
writeFileSync(join(wtDir, "feature.txt"), "implemented\n");
|
|
221
221
|
git(["add", "feature.txt"], wtDir);
|
|
222
222
|
git(["commit", "-m", "feat: implement M004"], wtDir);
|
|
@@ -35,7 +35,8 @@ test("repair target accepts a missing expected milestone worktree", () => {
|
|
|
35
35
|
|
|
36
36
|
assert.equal(result.ok, true);
|
|
37
37
|
if (result.ok) {
|
|
38
|
-
|
|
38
|
+
// No worktree exists yet, so the expected path is the canonical container.
|
|
39
|
+
assert.equal(result.expectedPath, join(base, ".gsd-worktrees", "M001"));
|
|
39
40
|
}
|
|
40
41
|
} finally {
|
|
41
42
|
cleanup(base);
|
|
@@ -192,7 +193,8 @@ test("paused metadata path resolves to the expected worktree while paused at pro
|
|
|
192
193
|
baseIsAutoWorktree: false,
|
|
193
194
|
});
|
|
194
195
|
|
|
195
|
-
|
|
196
|
+
// No worktree exists yet, so resolution lands at the canonical container.
|
|
197
|
+
assert.equal(result, join(base, ".gsd-worktrees", "M001"));
|
|
196
198
|
} finally {
|
|
197
199
|
cleanup(base);
|
|
198
200
|
}
|
|
@@ -5,7 +5,7 @@ import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSy
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
|
|
8
|
-
import { checkoutBranchWithStashGuard } from "../
|
|
8
|
+
import { checkoutBranchWithStashGuard } from "../worktree-git-recovery.ts";
|
|
9
9
|
|
|
10
10
|
function git(args: string[], cwd: string): string {
|
|
11
11
|
return execFileSync("git", args, {
|
|
@@ -159,4 +159,69 @@ describe("checkoutBranchWithStashGuard", () => {
|
|
|
159
159
|
const stashList = git(["stash", "list"], repo).trim();
|
|
160
160
|
assert.equal(stashList, "");
|
|
161
161
|
});
|
|
162
|
+
|
|
163
|
+
test("auto-resolves combined non-.gsd untracked collision and .gsd index conflict", (t) => {
|
|
164
|
+
// Regression for: gate checked nonGsdUnmerged.length === 0 but ignored
|
|
165
|
+
// .gsd/ index conflicts, so a failed pop with both an untracked non-.gsd/
|
|
166
|
+
// collision AND a .gsd/ tracked conflict would drop the stash while leaving
|
|
167
|
+
// .gsd/ index unmerged entries unresolved.
|
|
168
|
+
const repo = createRepo(t);
|
|
169
|
+
// Add .gsd/DECISIONS.md to base so both branches diverge from it
|
|
170
|
+
writeFileSync(join(repo, ".gsd", "DECISIONS.md"), "base\n");
|
|
171
|
+
git(["add", ".gsd/DECISIONS.md"], repo);
|
|
172
|
+
git(["commit", "-m", "add decisions"], repo);
|
|
173
|
+
|
|
174
|
+
git(["checkout", "-b", "milestone/M003"], repo);
|
|
175
|
+
// Branch has its own version of DECISIONS.md (non-JSONL .gsd/ file)
|
|
176
|
+
writeFileSync(join(repo, ".gsd", "DECISIONS.md"), "target-version\n");
|
|
177
|
+
mkdirSync(join(repo, ".harness"), { recursive: true });
|
|
178
|
+
writeFileSync(join(repo, ".harness", "settings.json"), "{\"theme\":\"dark\"}\n");
|
|
179
|
+
git(["add", ".gsd/DECISIONS.md", ".harness/settings.json"], repo);
|
|
180
|
+
git(["commit", "-m", "branch state"], repo);
|
|
181
|
+
git(["checkout", "main"], repo);
|
|
182
|
+
|
|
183
|
+
// On main: tracked .gsd/ change (creates unmerged index entry on pop) +
|
|
184
|
+
// untracked harness file (triggers "already exists, no checkout").
|
|
185
|
+
writeFileSync(join(repo, ".gsd", "DECISIONS.md"), "local-version\n");
|
|
186
|
+
mkdirSync(join(repo, ".harness"), { recursive: true });
|
|
187
|
+
writeFileSync(join(repo, ".harness", "settings.json"), "{\"theme\":\"light\"}\n");
|
|
188
|
+
|
|
189
|
+
checkoutBranchWithStashGuard(repo, "milestone/M003", "test-combined-gsd-conflict");
|
|
190
|
+
|
|
191
|
+
const branch = git(["branch", "--show-current"], repo).trim();
|
|
192
|
+
assert.equal(branch, "milestone/M003");
|
|
193
|
+
const decisions = readFileSync(join(repo, ".gsd", "DECISIONS.md"), "utf8");
|
|
194
|
+
assert.equal(decisions, "target-version\n");
|
|
195
|
+
assert.doesNotMatch(decisions, /<<<<<<<|=======|>>>>>>>/);
|
|
196
|
+
const status = git(["status", "--porcelain"], repo).trim();
|
|
197
|
+
assert.equal(status, "");
|
|
198
|
+
const stashList = git(["stash", "list"], repo).trim();
|
|
199
|
+
assert.equal(stashList, "");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("auto-resolves non-.gsd untracked restore collisions (e.g. harness config outside .gsd/)", (t) => {
|
|
203
|
+
const repo = createRepo(t);
|
|
204
|
+
// Target branch has a harness config file committed; source (main) has it untracked.
|
|
205
|
+
// Use a path not affected by global gitignore rules (unlike .claude/settings.local.json).
|
|
206
|
+
git(["checkout", "-b", "milestone/M001"], repo);
|
|
207
|
+
mkdirSync(join(repo, ".harness"), { recursive: true });
|
|
208
|
+
writeFileSync(join(repo, ".harness", "settings.json"), "{\"theme\":\"dark\"}\n");
|
|
209
|
+
git(["add", ".harness/settings.json"], repo);
|
|
210
|
+
git(["commit", "-m", "add harness settings"], repo);
|
|
211
|
+
git(["checkout", "main"], repo);
|
|
212
|
+
|
|
213
|
+
mkdirSync(join(repo, ".harness"), { recursive: true });
|
|
214
|
+
writeFileSync(join(repo, ".harness", "settings.json"), "{\"theme\":\"light\"}\n");
|
|
215
|
+
|
|
216
|
+
checkoutBranchWithStashGuard(repo, "milestone/M001", "test-non-gsd-untracked-collision");
|
|
217
|
+
|
|
218
|
+
const branch = git(["branch", "--show-current"], repo).trim();
|
|
219
|
+
assert.equal(branch, "milestone/M001");
|
|
220
|
+
const wtContent = readFileSync(join(repo, ".harness", "settings.json"), "utf8");
|
|
221
|
+
assert.equal(wtContent, "{\"theme\":\"dark\"}\n");
|
|
222
|
+
const status = git(["status", "--porcelain"], repo).trim();
|
|
223
|
+
assert.equal(status, "");
|
|
224
|
+
const stashList = git(["stash", "list"], repo).trim();
|
|
225
|
+
assert.equal(stashList, "");
|
|
226
|
+
});
|
|
162
227
|
});
|
|
@@ -73,4 +73,48 @@ describe("clear stale pending auto-start (#3667)", () => {
|
|
|
73
73
|
"pending auto-start gate must clear stale map entries for completed discussions",
|
|
74
74
|
);
|
|
75
75
|
});
|
|
76
|
+
|
|
77
|
+
test("guided-flow recovers a finished-but-unconsumed discussion instead of dead-ending", () => {
|
|
78
|
+
// CONTEXT exists + no live turn means the discussion completed but the
|
|
79
|
+
// agent_end handoff never consumed the entry (e.g. an external-engine
|
|
80
|
+
// post-hoc gate re-arm wiped the depth verification after the save).
|
|
81
|
+
// Without recovery, every /gsd prints "Discussion already in progress"
|
|
82
|
+
// forever: the stale heuristic requires CONTEXT to be absent and
|
|
83
|
+
// discussPlanComplete requires a ROADMAP that planning never produced.
|
|
84
|
+
const source = readFileSync(join(__dirname, "..", "guided-flow.ts"), "utf-8");
|
|
85
|
+
assert.ok(
|
|
86
|
+
source.includes("milestoneHasContext && !isAgentTurnInFlight(ctx)"),
|
|
87
|
+
"pending-entry guard must have a recovery branch for CONTEXT-present, no-turn-in-flight entries",
|
|
88
|
+
);
|
|
89
|
+
assert.ok(
|
|
90
|
+
source.includes("extractDepthVerificationMilestoneId(pendingGateId) === entry.milestoneId"),
|
|
91
|
+
"recovery must only clear a pending gate belonging to the entry's own milestone",
|
|
92
|
+
);
|
|
93
|
+
assert.ok(
|
|
94
|
+
source.includes("if (checkAutoStartAfterDiscuss(basePath)) return;"),
|
|
95
|
+
"recovery must re-run the discuss→auto handoff after clearing the stale gate",
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("guided-flow does not treat a live discuss turn as a stale pending entry", () => {
|
|
100
|
+
const source = readFileSync(join(__dirname, "..", "guided-flow.ts"), "utf-8");
|
|
101
|
+
assert.ok(
|
|
102
|
+
source.includes("!isAgentTurnInFlight(ctx)"),
|
|
103
|
+
"stale-entry deletion must be gated on no agent turn being in flight — a dispatched " +
|
|
104
|
+
"discuss turn can think for over 30s before writing its first artifact, and deleting " +
|
|
105
|
+
"its entry re-dispatches the workflow (duplicate interview + duplicate completion message)",
|
|
106
|
+
);
|
|
107
|
+
assert.ok(
|
|
108
|
+
source.includes('const milestoneHasDraft = !!resolveMilestoneFile(basePath, entry.milestoneId, "CONTEXT-DRAFT");'),
|
|
109
|
+
"stale-entry check must treat an existing CONTEXT-DRAFT as proof of an in-progress interview",
|
|
110
|
+
);
|
|
111
|
+
assert.ok(
|
|
112
|
+
source.includes("!milestoneHasDraft"),
|
|
113
|
+
"stale-entry deletion must require the CONTEXT-DRAFT to be absent",
|
|
114
|
+
);
|
|
115
|
+
assert.ok(
|
|
116
|
+
source.includes("ctx.hasPendingMessages"),
|
|
117
|
+
"in-flight detection must also cover dispatched-but-not-yet-started queued messages",
|
|
118
|
+
);
|
|
119
|
+
});
|
|
76
120
|
});
|
|
@@ -472,18 +472,19 @@ test("auto-dispatch needs-attention pause message references /gsd verdict", asyn
|
|
|
472
472
|
}
|
|
473
473
|
});
|
|
474
474
|
|
|
475
|
-
test("
|
|
475
|
+
test("guidance.ts needs-remediation blocker messages reference /gsd verdict", async () => {
|
|
476
476
|
// We don't need to invoke deriveState — just assert the substring is in the
|
|
477
|
-
// source. The blocker strings are constructed
|
|
478
|
-
// verbatim, so a static check is sufficient and avoids
|
|
479
|
-
|
|
480
|
-
|
|
477
|
+
// source. The blocker strings are constructed in the guidance catalog and
|
|
478
|
+
// shipped to the user verbatim, so a static check is sufficient and avoids
|
|
479
|
+
// fragile DB setup.
|
|
480
|
+
const guidanceSource = readFileSync(
|
|
481
|
+
new URL("../guidance.ts", import.meta.url).pathname,
|
|
481
482
|
"utf-8",
|
|
482
483
|
);
|
|
483
|
-
const occurrences =
|
|
484
|
+
const occurrences = guidanceSource.match(/`\/gsd verdict /g) ?? [];
|
|
484
485
|
assert.ok(
|
|
485
486
|
occurrences.length >= 2,
|
|
486
|
-
`expected at least 2 references to /gsd verdict in
|
|
487
|
+
`expected at least 2 references to /gsd verdict in guidance.ts blockers, found ${occurrences.length}`,
|
|
487
488
|
);
|
|
488
489
|
});
|
|
489
490
|
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Regression tests for evidence cross-referencing of gsd_exec /
|
|
3
|
+
// gsd_uat_exec tool calls. Mirrors the live false-positive where an
|
|
4
|
+
// execute-task agent ran its verification commands through gsd_exec (script
|
|
5
|
+
// body in the `script` argument) and the cross-referencer reported
|
|
6
|
+
// "No bash tool call found" despite successful execution.
|
|
7
|
+
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
resetEvidence,
|
|
16
|
+
getEvidence,
|
|
17
|
+
recordToolCall,
|
|
18
|
+
recordToolResult,
|
|
19
|
+
isExecutionToolName,
|
|
20
|
+
type BashEvidence,
|
|
21
|
+
} from "../safety/evidence-collector.ts";
|
|
22
|
+
import { crossReferenceEvidence } from "../safety/evidence-cross-ref.ts";
|
|
23
|
+
|
|
24
|
+
function gsdExecResult(exitCode: number, id = "4858202d-2ed7-4a0a-9ef7-4e159e65da83"): unknown {
|
|
25
|
+
return {
|
|
26
|
+
content: [{
|
|
27
|
+
type: "text",
|
|
28
|
+
text: JSON.stringify({
|
|
29
|
+
operation: "gsd_exec",
|
|
30
|
+
id,
|
|
31
|
+
runtime: "bash",
|
|
32
|
+
exit_code: exitCode,
|
|
33
|
+
signal: null,
|
|
34
|
+
timed_out: false,
|
|
35
|
+
duration_ms: 272,
|
|
36
|
+
stdout_bytes: 592,
|
|
37
|
+
stderr_bytes: 0,
|
|
38
|
+
meta_path: `/tmp/does-not-exist/.gsd/exec/${id}.meta.json`,
|
|
39
|
+
}),
|
|
40
|
+
}],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
test("evidence-xref: verification run through gsd_exec script matches the claimed command", () => {
|
|
45
|
+
resetEvidence();
|
|
46
|
+
|
|
47
|
+
// The live false positive: agent runs `node --test tests/verify-s01.test.js`
|
|
48
|
+
// inside a gsd_exec script with a cd prefix and exit-code echo suffix.
|
|
49
|
+
recordToolCall("tc-exec-1", "gsd_exec", {
|
|
50
|
+
script: 'cd /work/.gsd/worktrees/M001 && node --test tests/verify-s01.test.js; echo "EXIT=$?"',
|
|
51
|
+
purpose: "T02: run node --test contract checks against T01 index.html",
|
|
52
|
+
});
|
|
53
|
+
recordToolResult("tc-exec-1", "gsd_exec", gsdExecResult(0), false);
|
|
54
|
+
|
|
55
|
+
const mismatches = crossReferenceEvidence(
|
|
56
|
+
[{ command: "node --test tests/verify-s01.test.js", exitCode: 0, verdict: "passed" }],
|
|
57
|
+
getEvidence(),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
assert.deepEqual(mismatches, [], "gsd_exec-executed verification must not be flagged as missing");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("evidence-xref: multi-line gsd_exec script matches claims for each embedded command", () => {
|
|
64
|
+
resetEvidence();
|
|
65
|
+
|
|
66
|
+
recordToolCall("tc-exec-2", "gsd_exec", {
|
|
67
|
+
script: [
|
|
68
|
+
"cd /work/.gsd/worktrees/M001",
|
|
69
|
+
"sed -i '' \"s/'todos'/'tasks-v1'/\" index.html",
|
|
70
|
+
"node --test tests/verify-s01.test.js > /dev/null 2>&1",
|
|
71
|
+
'echo "BROKEN_EXIT=$?"',
|
|
72
|
+
].join("\n"),
|
|
73
|
+
purpose: "T02: deliberate contract break must fail, then restore",
|
|
74
|
+
});
|
|
75
|
+
recordToolResult("tc-exec-2", "gsd_exec", gsdExecResult(0), false);
|
|
76
|
+
|
|
77
|
+
const mismatches = crossReferenceEvidence(
|
|
78
|
+
[{ command: "node --test tests/verify-s01.test.js > /dev/null 2>&1", exitCode: 0, verdict: "passed" }],
|
|
79
|
+
getEvidence(),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
assert.deepEqual(mismatches, [], "command embedded in a multi-line script must match");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("evidence-xref: claimed pass with failing gsd_exec exit_code is still an error", () => {
|
|
86
|
+
resetEvidence();
|
|
87
|
+
|
|
88
|
+
recordToolCall("tc-exec-3", "gsd_exec", {
|
|
89
|
+
script: "node --test tests/verify-s01.test.js",
|
|
90
|
+
purpose: "verification",
|
|
91
|
+
});
|
|
92
|
+
// gsd_exec reports failures via the JSON envelope's exit_code (and isError).
|
|
93
|
+
recordToolResult("tc-exec-3", "gsd_exec", gsdExecResult(1), true);
|
|
94
|
+
|
|
95
|
+
const mismatches = crossReferenceEvidence(
|
|
96
|
+
[{ command: "node --test tests/verify-s01.test.js", exitCode: 0, verdict: "passed" }],
|
|
97
|
+
getEvidence(),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
assert.equal(mismatches.length, 1);
|
|
101
|
+
assert.equal(mismatches[0].severity, "error");
|
|
102
|
+
assert.match(mismatches[0].reason, /Claimed exitCode=0 but actual exitCode=1/);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("evidence-collector: gsd_uat_exec and MCP-namespaced variants are execution tools", () => {
|
|
106
|
+
assert.equal(isExecutionToolName("gsd_uat_exec"), true);
|
|
107
|
+
assert.equal(isExecutionToolName("mcp__gsd-workflow__gsd_uat_exec"), true);
|
|
108
|
+
assert.equal(isExecutionToolName("mcp__gsd-workflow__gsd_exec"), true);
|
|
109
|
+
|
|
110
|
+
resetEvidence();
|
|
111
|
+
recordToolCall("tc-uat-1", "gsd_uat_exec", { script: "curl -fsS http://localhost:3000/health" });
|
|
112
|
+
const bash = getEvidence().filter((e): e is BashEvidence => e.kind === "bash");
|
|
113
|
+
assert.equal(bash.length, 1, "gsd_uat_exec must record bash evidence");
|
|
114
|
+
assert.equal(bash[0].command, "curl -fsS http://localhost:3000/health");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("evidence-xref: blank-command evidence does not satisfy arbitrary claims", () => {
|
|
118
|
+
// Before script extraction existed, gsd_exec calls were recorded with
|
|
119
|
+
// command: "" — and `"x".includes("")` made them match every claim,
|
|
120
|
+
// masking genuine fabrications. Blank entries must never match.
|
|
121
|
+
const mismatches = crossReferenceEvidence(
|
|
122
|
+
[{ command: "node --test tests/verify-s01.test.js", exitCode: 0, verdict: "passed" }],
|
|
123
|
+
[{
|
|
124
|
+
kind: "bash",
|
|
125
|
+
toolCallId: "tc-blank",
|
|
126
|
+
command: "",
|
|
127
|
+
exitCode: 0,
|
|
128
|
+
outputSnippet: "",
|
|
129
|
+
timestamp: 1,
|
|
130
|
+
}],
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
assert.equal(mismatches.length, 1);
|
|
134
|
+
assert.equal(mismatches[0].severity, "warning");
|
|
135
|
+
assert.match(mismatches[0].reason, /No bash tool call found/);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("evidence-collector: exit code falls back to .gsd/exec meta.json when result text omits it", (t) => {
|
|
139
|
+
const dir = mkdtempSync(join(tmpdir(), "gsd-exec-meta-"));
|
|
140
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
141
|
+
|
|
142
|
+
const metaPath = join(dir, "run-1.meta.json");
|
|
143
|
+
writeFileSync(metaPath, JSON.stringify({ id: "run-1", exit_code: 7 }));
|
|
144
|
+
|
|
145
|
+
resetEvidence();
|
|
146
|
+
recordToolCall("tc-meta-1", "gsd_exec", { script: "exit 7" });
|
|
147
|
+
// Truncated result: meta_path survives but exit_code was cut off.
|
|
148
|
+
recordToolResult(
|
|
149
|
+
"tc-meta-1",
|
|
150
|
+
"gsd_exec",
|
|
151
|
+
{ content: [{ type: "text", text: `{"operation":"gsd_exec","meta_path":${JSON.stringify(metaPath)}` }] },
|
|
152
|
+
false,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const bash = getEvidence().filter((e): e is BashEvidence => e.kind === "bash");
|
|
156
|
+
assert.equal(bash[0].exitCode, 7, "exit code must be recovered from meta.json");
|
|
157
|
+
});
|
|
@@ -5,7 +5,7 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
|
|
8
|
-
import { validateFileChanges } from "../safety/file-change-validator.ts";
|
|
8
|
+
import { validateFileChanges, effectiveFileChangeAllowlist } from "../safety/file-change-validator.ts";
|
|
9
9
|
|
|
10
10
|
function git(cwd: string, ...args: string[]): string {
|
|
11
11
|
return execFileSync("git", args, {
|
|
@@ -106,3 +106,35 @@ test("validateFileChanges ignores inline descriptions in expected output paths",
|
|
|
106
106
|
"described expected output should not trigger unexpected-file warnings",
|
|
107
107
|
);
|
|
108
108
|
});
|
|
109
|
+
|
|
110
|
+
test("effectiveFileChangeAllowlist includes .gitignore when GSD manages it", () => {
|
|
111
|
+
assert.deepEqual(effectiveFileChangeAllowlist([], undefined), [".gitignore"]);
|
|
112
|
+
assert.deepEqual(effectiveFileChangeAllowlist(["docs/**"], true), ["docs/**", ".gitignore"]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("effectiveFileChangeAllowlist keeps .gitignore auditable when management is disabled", () => {
|
|
116
|
+
assert.deepEqual(effectiveFileChangeAllowlist(["docs/**"], false), ["docs/**"]);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("GSD-managed .gitignore edit swept into a task commit is not flagged", (t) => {
|
|
120
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-file-change-validator-"));
|
|
121
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
122
|
+
|
|
123
|
+
git(base, "init");
|
|
124
|
+
git(base, "config", "user.email", "test@example.com");
|
|
125
|
+
git(base, "config", "user.name", "Test User");
|
|
126
|
+
writeFileSync(join(base, "index.html"), "<main></main>\n");
|
|
127
|
+
writeFileSync(join(base, ".gitignore"), "# ── GSD baseline (auto-generated) ──\n.gsd\n");
|
|
128
|
+
git(base, "add", ".");
|
|
129
|
+
git(base, "commit", "-m", "task commit with swept gitignore");
|
|
130
|
+
|
|
131
|
+
const audit = validateFileChanges(
|
|
132
|
+
base,
|
|
133
|
+
["index.html"],
|
|
134
|
+
[],
|
|
135
|
+
effectiveFileChangeAllowlist([], undefined),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
assert.ok(audit, "audit should be produced");
|
|
139
|
+
assert.deepEqual(audit.unexpectedFiles, [], ".gitignore must not be flagged when GSD manages it");
|
|
140
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// GSD Extension — Guidance module tests
|
|
2
|
+
// The catalog is the test surface: every typed finding must resolve to
|
|
3
|
+
// non-empty remediation, and load-bearing phrases must survive edits.
|
|
4
|
+
|
|
5
|
+
import { describe, test } from "node:test";
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
formatGuidance,
|
|
10
|
+
recoveryRemediation,
|
|
11
|
+
needsAttentionBlockerGuidance,
|
|
12
|
+
needsRemediationBlockerGuidance,
|
|
13
|
+
crashResumeHint,
|
|
14
|
+
doctorFixHint,
|
|
15
|
+
type RecoveryGuidanceKey,
|
|
16
|
+
} from "../guidance.js";
|
|
17
|
+
import { classifyFailure, type RecoveryFailureKind } from "../recovery-classification.js";
|
|
18
|
+
import { isValidationBlockedState } from "../validation-block-guard.js";
|
|
19
|
+
import type { GSDState } from "../types.js";
|
|
20
|
+
|
|
21
|
+
const RECOVERY_KEYS: RecoveryGuidanceKey[] = [
|
|
22
|
+
"tool-schema",
|
|
23
|
+
"tool-contract",
|
|
24
|
+
"tool-unavailable",
|
|
25
|
+
"deterministic-policy",
|
|
26
|
+
"lifecycle-progression",
|
|
27
|
+
"stale-worker",
|
|
28
|
+
"worktree-invalid",
|
|
29
|
+
"verification-drift",
|
|
30
|
+
"reconciliation-drift",
|
|
31
|
+
"illegal-transition",
|
|
32
|
+
"runtime-unknown",
|
|
33
|
+
"provider-transient",
|
|
34
|
+
"provider-permanent",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
describe("guidance catalog", () => {
|
|
38
|
+
test("every recovery guidance key resolves to non-empty remediation", () => {
|
|
39
|
+
for (const key of RECOVERY_KEYS) {
|
|
40
|
+
const remediation = recoveryRemediation(key);
|
|
41
|
+
assert.ok(remediation.length > 0, `empty remediation for ${key}`);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("classifyFailure carries catalog remediation for every failure kind", () => {
|
|
46
|
+
const kinds: RecoveryFailureKind[] = [
|
|
47
|
+
"tool-schema",
|
|
48
|
+
"tool-contract",
|
|
49
|
+
"deterministic-policy",
|
|
50
|
+
"lifecycle-progression",
|
|
51
|
+
"stale-worker",
|
|
52
|
+
"worktree-invalid",
|
|
53
|
+
"verification-drift",
|
|
54
|
+
"reconciliation-drift",
|
|
55
|
+
"runtime-unknown",
|
|
56
|
+
];
|
|
57
|
+
for (const kind of kinds) {
|
|
58
|
+
const result = classifyFailure({ error: new Error("boom"), failureKind: kind });
|
|
59
|
+
assert.equal(result.failureKind, kind);
|
|
60
|
+
assert.equal(result.exitReason, kind);
|
|
61
|
+
assert.ok(result.remediation.length > 0, `empty remediation for ${kind}`);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("escalation kinds with weak guidance now name a concrete command", () => {
|
|
66
|
+
for (const key of ["stale-worker", "worktree-invalid", "verification-drift", "reconciliation-drift"] as const) {
|
|
67
|
+
assert.match(recoveryRemediation(key), /\/gsd /, `no command in remediation for ${key}`);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("formatGuidance numbers steps under the summary", () => {
|
|
72
|
+
const text = formatGuidance({ summary: "It broke.", steps: ["Fix it.", "Retry."] });
|
|
73
|
+
assert.equal(text, "It broke.\n\n1. Fix it.\n2. Retry.");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("formatGuidance with no steps is just the summary", () => {
|
|
77
|
+
assert.equal(formatGuidance({ summary: "All good.", steps: [] }), "All good.");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("milestone blocker guidance", () => {
|
|
82
|
+
test("needs-attention blocker keeps the phrase the validation-block guard matches", () => {
|
|
83
|
+
const state = { phase: "blocked", blockers: [needsAttentionBlockerGuidance("M005")] } as unknown as GSDState;
|
|
84
|
+
assert.ok(isValidationBlockedState(state));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("needs-remediation blocker keeps the phrase the validation-block guard matches", () => {
|
|
88
|
+
const state = { phase: "blocked", blockers: [needsRemediationBlockerGuidance("M005")] } as unknown as GSDState;
|
|
89
|
+
assert.ok(isValidationBlockedState(state));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("blockers carry the milestone id and concrete fix commands", () => {
|
|
93
|
+
const text = needsAttentionBlockerGuidance("M007");
|
|
94
|
+
assert.match(text, /M007/);
|
|
95
|
+
assert.match(text, /\/gsd status/);
|
|
96
|
+
assert.match(text, /\/gsd validate-milestone/);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("crash resume hints", () => {
|
|
101
|
+
test("bootstrap crash reports no work lost", () => {
|
|
102
|
+
assert.match(crashResumeHint("starting", "bootstrap") ?? "", /No work was lost/);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("unit classes map to their resume hints", () => {
|
|
106
|
+
assert.match(crashResumeHint("research-milestone", "M001") ?? "", /may be incomplete/);
|
|
107
|
+
assert.match(crashResumeHint("execute-task", "T001") ?? "", /completed work is preserved/);
|
|
108
|
+
assert.match(crashResumeHint("complete-slice", "S001") ?? "", /interrupted/);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("unknown unit types yield no hint", () => {
|
|
112
|
+
assert.equal(crashResumeHint("triage-captures", "X"), undefined);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("doctor fix hints", () => {
|
|
117
|
+
test("known codes resolve to a hint", () => {
|
|
118
|
+
assert.ok(doctorFixHint("stale_crash_lock"));
|
|
119
|
+
assert.ok(doctorFixHint("db_unavailable"));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("codes without authored guidance resolve to undefined", () => {
|
|
123
|
+
assert.equal(doctorFixHint("delimiter_in_title"), undefined);
|
|
124
|
+
});
|
|
125
|
+
});
|
package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts
CHANGED
|
@@ -746,7 +746,7 @@ describe("auto-worktree-milestone-merge", { timeout: 300_000 }, () => {
|
|
|
746
746
|
});
|
|
747
747
|
|
|
748
748
|
test("#2156: mergeMilestoneToMain removes external-state worktrees using the milestone branch name", () => {
|
|
749
|
-
const { repo
|
|
749
|
+
const { repo } = freshRepoWithExternalGsd();
|
|
750
750
|
const wtPath = createAutoWorktree(repo, "M215");
|
|
751
751
|
|
|
752
752
|
addSliceToMilestone(repo, wtPath, "M215", "S01", "External cleanup", [
|
|
@@ -754,9 +754,10 @@ describe("auto-worktree-milestone-merge", { timeout: 300_000 }, () => {
|
|
|
754
754
|
]);
|
|
755
755
|
|
|
756
756
|
const realWtPath = realpathSync(wtPath);
|
|
757
|
-
assert.
|
|
758
|
-
realWtPath
|
|
759
|
-
|
|
757
|
+
assert.equal(
|
|
758
|
+
realWtPath,
|
|
759
|
+
join(repo, ".gsd-worktrees", "M215"),
|
|
760
|
+
`worktree should use canonical path under project root, got ${realWtPath}`,
|
|
760
761
|
);
|
|
761
762
|
|
|
762
763
|
// Recreate the exact divergence from #1852: local .gsd/ is replaced with a
|
|
@@ -783,6 +784,52 @@ describe("auto-worktree-milestone-merge", { timeout: 300_000 }, () => {
|
|
|
783
784
|
);
|
|
784
785
|
});
|
|
785
786
|
|
|
787
|
+
test("#2156 (legacy): mergeMilestoneToMain removes external-state worktrees created under .gsd/worktrees/", () => {
|
|
788
|
+
const { repo, externalState } = freshRepoWithExternalGsd();
|
|
789
|
+
// Worktrees created by older versions live at .gsd/worktrees/<MID>; git
|
|
790
|
+
// resolves the symlink and registers them under external state. Canonical
|
|
791
|
+
// .gsd-worktrees/ creation never crosses the symlink, so this coverage
|
|
792
|
+
// creates the legacy worktree explicitly. createAutoWorktree chdirs into
|
|
793
|
+
// the new worktree and mergeMilestoneToMain reads process.cwd() as the
|
|
794
|
+
// worktree cwd, so mirror that here.
|
|
795
|
+
const wtPath = join(repo, ".gsd", "worktrees", "M216");
|
|
796
|
+
run(`git worktree add -b milestone/M216 "${wtPath}"`, repo);
|
|
797
|
+
process.chdir(wtPath);
|
|
798
|
+
|
|
799
|
+
addSliceToMilestone(repo, wtPath, "M216", "S01", "Legacy external cleanup", [
|
|
800
|
+
{ file: "legacy-external-cleanup.ts", content: "export const legacyExternalCleanup = true;\n", message: "add legacy external cleanup" },
|
|
801
|
+
]);
|
|
802
|
+
|
|
803
|
+
const realWtPath = realpathSync(wtPath);
|
|
804
|
+
assert.ok(
|
|
805
|
+
realWtPath.startsWith(externalState),
|
|
806
|
+
`legacy worktree should be registered under external .gsd state, got ${realWtPath}`,
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
// Recreate the exact divergence from #1852: local .gsd/ is replaced with a
|
|
810
|
+
// stale real directory, so the computed path no longer matches git's record.
|
|
811
|
+
unlinkSync(join(repo, ".gsd"));
|
|
812
|
+
mkdirSync(join(repo, ".gsd", "worktrees", "M216"), { recursive: true });
|
|
813
|
+
writeFileSync(join(repo, ".gsd", "STATE.md"), "# Local stale state\n");
|
|
814
|
+
writeFileSync(join(repo, ".gsd", "worktrees", "M216", "stale.txt"), "stale local artifact\n");
|
|
815
|
+
|
|
816
|
+
const roadmap = makeRoadmap("M216", "Legacy external cleanup", [
|
|
817
|
+
{ id: "S01", title: "Legacy external cleanup" },
|
|
818
|
+
]);
|
|
819
|
+
|
|
820
|
+
mergeMilestoneToMain(repo, "M216", roadmap);
|
|
821
|
+
|
|
822
|
+
assert.ok(
|
|
823
|
+
!run("git worktree list", repo).includes("M216"),
|
|
824
|
+
"merged legacy milestone worktree should be removed from git worktree list",
|
|
825
|
+
);
|
|
826
|
+
assert.ok(!existsSync(realWtPath), "real external worktree directory should be removed");
|
|
827
|
+
assert.ok(
|
|
828
|
+
!run("git branch", repo).includes("milestone/M216"),
|
|
829
|
+
"milestone branch should be deleted after merge cleanup",
|
|
830
|
+
);
|
|
831
|
+
});
|
|
832
|
+
|
|
786
833
|
test("#2912: MERGE_HEAD cleaned up after squash-merge conflict", () => {
|
|
787
834
|
const repo = freshRepo();
|
|
788
835
|
const wtPath = createAutoWorktree(repo, "M291");
|