@opengsd/gsd-pi 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/ask-user-questions.js +1 -1
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +114 -36
- package/dist/resources/extensions/gsd/activity-log.js +1 -1
- package/dist/resources/extensions/gsd/auto/closeout.js +97 -5
- package/dist/resources/extensions/gsd/auto/orchestrator.js +20 -13
- package/dist/resources/extensions/gsd/auto/pre-dispatch.js +6 -3
- package/dist/resources/extensions/gsd/auto/unit-phase.js +5 -9
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +14 -5
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -7
- package/dist/resources/extensions/gsd/auto-dispatch.js +69 -17
- package/dist/resources/extensions/gsd/auto-model-selection.js +28 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +29 -117
- package/dist/resources/extensions/gsd/auto-recovery.js +50 -6
- package/dist/resources/extensions/gsd/auto-start.js +57 -33
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +12 -2
- package/dist/resources/extensions/gsd/auto-timers.js +9 -2
- package/dist/resources/extensions/gsd/auto-worktree-branch-lifecycle.js +189 -0
- package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +133 -0
- package/dist/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.js +35 -0
- package/dist/resources/extensions/gsd/auto-worktree-creation.js +130 -0
- package/dist/resources/extensions/gsd/auto-worktree-entry.js +118 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-already-merged.js +98 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-branch-head.js +52 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-cleanup.js +87 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-code-changes.js +46 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-db-ready.js +68 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-integration-branch.js +51 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-message.js +106 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-pre-teardown.js +64 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-stash.js +168 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge.js +302 -0
- package/dist/resources/extensions/gsd/auto-worktree-milestone-shelter.js +119 -0
- package/dist/resources/extensions/gsd/auto-worktree-path-resolution.js +5 -0
- package/dist/resources/extensions/gsd/auto-worktree-path-utils.js +5 -0
- package/dist/resources/extensions/gsd/auto-worktree-resource-version.js +41 -0
- package/dist/resources/extensions/gsd/auto-worktree-runtime-cleanup.js +99 -0
- package/dist/resources/extensions/gsd/auto-worktree-session-registry.js +57 -0
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +249 -0
- package/dist/resources/extensions/gsd/auto-worktree-teardown.js +120 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +20 -2073
- package/dist/resources/extensions/gsd/auto.js +106 -34
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +20 -2
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +185 -27
- package/dist/resources/extensions/gsd/codebase-generator.js +7 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +3 -3
- package/dist/resources/extensions/gsd/compat/compat-marker.js +44 -0
- package/dist/resources/extensions/gsd/config-overlay.js +7 -2
- package/dist/resources/extensions/gsd/db/engine.js +33 -0
- package/dist/resources/extensions/gsd/db/queries.js +9 -9
- package/dist/resources/extensions/gsd/db/writers/cascades.js +20 -2
- package/dist/resources/extensions/gsd/db-adapter.js +30 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +93 -11
- package/dist/resources/extensions/gsd/flat-phase-migration.js +32 -3
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-conflict-resolve.js +2 -2
- package/dist/resources/extensions/gsd/git-service.js +45 -0
- package/dist/resources/extensions/gsd/gsd-db.js +23 -11
- package/dist/resources/extensions/gsd/guided-flow.js +22 -17
- package/dist/resources/extensions/gsd/journal.js +1 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +34 -79
- package/dist/resources/extensions/gsd/md-importer.js +89 -23
- package/dist/resources/extensions/gsd/metrics.js +2 -2
- package/dist/resources/extensions/gsd/migration-auto-check.js +30 -3
- package/dist/resources/extensions/gsd/milestone-merge-transaction.js +11 -0
- package/dist/resources/extensions/gsd/milestone-settlement.js +1 -1
- package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -1
- package/dist/resources/extensions/gsd/paths.js +87 -52
- package/dist/resources/extensions/gsd/post-unit-hooks.js +27 -0
- package/dist/resources/extensions/gsd/preferences-models.js +66 -15
- package/dist/resources/extensions/gsd/preferences-skills.js +10 -16
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +148 -10
- package/dist/resources/extensions/gsd/preferences.js +20 -0
- package/dist/resources/extensions/gsd/prompts/system.md +6 -6
- package/dist/resources/extensions/gsd/quick.js +12 -3
- package/dist/resources/extensions/gsd/rule-registry.js +106 -10
- package/dist/resources/extensions/gsd/skill-discovery.js +36 -23
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +2 -1
- package/dist/resources/extensions/gsd/state/derive/from-db.js +88 -153
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +1 -10
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +3 -24
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +10 -0
- package/dist/resources/extensions/gsd/tool-contract.js +14 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -5
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +16 -10
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +5 -0
- package/dist/resources/extensions/gsd/tools/reopen-task.js +13 -5
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -8
- package/dist/resources/extensions/gsd/uat-dispatch.js +94 -0
- package/dist/resources/extensions/gsd/unit-registry.js +12 -2
- package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +1 -1
- package/dist/resources/extensions/gsd/uok/audit-toggle.js +31 -2
- package/dist/resources/extensions/gsd/uok/audit.js +3 -0
- package/dist/resources/extensions/gsd/uok/gate-runner.js +22 -30
- package/dist/resources/extensions/gsd/uok/gitops.js +18 -15
- package/dist/resources/extensions/gsd/uok/kernel.js +120 -46
- package/dist/resources/extensions/gsd/uok/model-policy.js +17 -14
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -1
- package/dist/resources/extensions/gsd/workflow-event-ledger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-projections.js +9 -26
- package/dist/resources/extensions/gsd/workspace-git-preflight.js +2 -2
- package/dist/resources/extensions/gsd/workspace.js +5 -14
- package/dist/resources/extensions/gsd/worktree-command.js +1 -1
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +14 -9
- package/dist/resources/extensions/gsd/worktree-reentry.js +2 -1
- package/dist/resources/extensions/shared/gsd-browser-cli.js +29 -0
- package/dist/resources/extensions/shared/interview-ui.js +62 -9
- 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 +9 -9
- 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 +9 -9
- package/dist/web/standalone/.next/server/chunks/{1128.js → 1991.js} +1 -1
- 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/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/dist/worktree-cli-create.d.ts +10 -0
- package/dist/worktree-cli-create.js +11 -0
- package/dist/worktree-cli-format.d.ts +12 -0
- package/dist/worktree-cli-format.js +38 -0
- package/dist/worktree-cli-plan.d.ts +16 -0
- package/dist/worktree-cli-plan.js +19 -0
- package/dist/worktree-cli-session.d.ts +13 -0
- package/dist/worktree-cli-session.js +18 -0
- package/dist/worktree-cli-status.d.ts +35 -0
- package/dist/worktree-cli-status.js +54 -0
- package/dist/worktree-cli.d.ts +1 -16
- package/dist/worktree-cli.js +28 -122
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +25 -36
- package/integrations/hermes/pyproject.toml +1 -1
- package/integrations/hermes/tests/test_gsd_client_timeout.py +4 -4
- package/package.json +6 -6
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/dist/orchestrator-agent.d.ts +21 -0
- package/packages/daemon/dist/orchestrator-agent.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator-agent.js +113 -0
- package/packages/daemon/dist/orchestrator-agent.js.map +1 -0
- package/packages/daemon/dist/orchestrator-tools.d.ts +12 -0
- package/packages/daemon/dist/orchestrator-tools.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator-tools.js +137 -0
- package/packages/daemon/dist/orchestrator-tools.js.map +1 -0
- package/packages/daemon/dist/orchestrator.d.ts +4 -56
- package/packages/daemon/dist/orchestrator.d.ts.map +1 -1
- package/packages/daemon/dist/orchestrator.js +13 -303
- package/packages/daemon/dist/orchestrator.js.map +1 -1
- package/packages/daemon/dist/orchestrator.test.js +11 -1
- package/packages/daemon/dist/orchestrator.test.js.map +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/export-html/template.js +53 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +3 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +14 -9
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/index.d.ts +1 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +7 -3
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/dist/tool-result-content.d.ts +3 -0
- package/packages/pi-ai/dist/tool-result-content.d.ts.map +1 -0
- package/packages/pi-ai/dist/tool-result-content.js +62 -0
- package/packages/pi-ai/dist/tool-result-content.js.map +1 -0
- package/packages/pi-ai/dist/types.d.ts +8 -20
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +74 -53
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +30 -2
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skill-directories.d.ts +66 -0
- package/packages/pi-coding-agent/dist/core/skill-directories.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/skill-directories.js +138 -0
- package/packages/pi-coding-agent/dist/core/skill-directories.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts +4 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +4 -1
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -0
- package/packages/pi-coding-agent/dist/index.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/dist/core/export-html/template.js +53 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/ask-user-questions.ts +1 -1
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +146 -33
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +25 -0
- package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +93 -0
- package/src/resources/extensions/gsd/activity-log.ts +1 -1
- package/src/resources/extensions/gsd/auto/closeout.ts +114 -7
- package/src/resources/extensions/gsd/auto/orchestrator.ts +21 -14
- package/src/resources/extensions/gsd/auto/pre-dispatch.ts +6 -3
- package/src/resources/extensions/gsd/auto/unit-phase.ts +5 -9
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +14 -4
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +13 -7
- package/src/resources/extensions/gsd/auto-dispatch.ts +77 -16
- package/src/resources/extensions/gsd/auto-model-selection.ts +33 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +4 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +53 -113
- package/src/resources/extensions/gsd/auto-recovery.ts +50 -6
- package/src/resources/extensions/gsd/auto-start.ts +62 -31
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -2
- package/src/resources/extensions/gsd/auto-timers.ts +9 -2
- package/src/resources/extensions/gsd/auto-worktree-branch-lifecycle.ts +237 -0
- package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +166 -0
- package/src/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.ts +37 -0
- package/src/resources/extensions/gsd/auto-worktree-creation.ts +171 -0
- package/src/resources/extensions/gsd/auto-worktree-entry.ts +154 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-already-merged.ts +161 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-branch-head.ts +83 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-cleanup.ts +132 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-code-changes.ts +71 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-db-ready.ts +112 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-integration-branch.ts +99 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-message.ts +166 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-pre-teardown.ts +93 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-stash.ts +213 -0
- package/src/resources/extensions/gsd/auto-worktree-merge.ts +391 -0
- package/src/resources/extensions/gsd/auto-worktree-milestone-shelter.ts +159 -0
- package/src/resources/extensions/gsd/auto-worktree-path-resolution.ts +6 -0
- package/src/resources/extensions/gsd/auto-worktree-path-utils.ts +10 -0
- package/src/resources/extensions/gsd/auto-worktree-resource-version.ts +44 -0
- package/src/resources/extensions/gsd/auto-worktree-runtime-cleanup.ts +119 -0
- package/src/resources/extensions/gsd/auto-worktree-session-registry.ts +71 -0
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +370 -0
- package/src/resources/extensions/gsd/auto-worktree-teardown.ts +161 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +69 -2429
- package/src/resources/extensions/gsd/auto.ts +114 -47
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +20 -2
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +218 -27
- package/src/resources/extensions/gsd/codebase-generator.ts +7 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +3 -3
- package/src/resources/extensions/gsd/compat/compat-marker.ts +47 -0
- package/src/resources/extensions/gsd/config-overlay.ts +7 -1
- package/src/resources/extensions/gsd/db/engine.ts +36 -0
- package/src/resources/extensions/gsd/db/queries.ts +9 -9
- package/src/resources/extensions/gsd/db/writers/cascades.ts +23 -1
- package/src/resources/extensions/gsd/db-adapter.ts +29 -3
- package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +109 -10
- package/src/resources/extensions/gsd/flat-phase-migration.ts +34 -3
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-conflict-resolve.ts +2 -2
- package/src/resources/extensions/gsd/git-service.ts +53 -0
- package/src/resources/extensions/gsd/gsd-db.ts +33 -11
- package/src/resources/extensions/gsd/guided-flow.ts +26 -18
- package/src/resources/extensions/gsd/journal.ts +1 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +52 -91
- package/src/resources/extensions/gsd/md-importer.ts +93 -22
- package/src/resources/extensions/gsd/metrics.ts +2 -2
- package/src/resources/extensions/gsd/migration-auto-check.ts +32 -3
- package/src/resources/extensions/gsd/milestone-merge-transaction.ts +15 -0
- package/src/resources/extensions/gsd/milestone-settlement.ts +1 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +3 -1
- package/src/resources/extensions/gsd/paths.ts +108 -51
- package/src/resources/extensions/gsd/post-unit-hooks.ts +32 -0
- package/src/resources/extensions/gsd/preferences-models.ts +74 -20
- package/src/resources/extensions/gsd/preferences-skills.ts +10 -16
- package/src/resources/extensions/gsd/preferences-types.ts +54 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +146 -9
- package/src/resources/extensions/gsd/preferences.ts +20 -0
- package/src/resources/extensions/gsd/prompts/system.md +6 -6
- package/src/resources/extensions/gsd/quick.ts +11 -3
- package/src/resources/extensions/gsd/rule-registry.ts +111 -9
- package/src/resources/extensions/gsd/skill-discovery.ts +47 -23
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +3 -1
- package/src/resources/extensions/gsd/state/derive/from-db.ts +201 -157
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +1 -9
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +2 -22
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +14 -0
- package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +169 -1
- package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +41 -2
- package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +48 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/auto-timeout-recovery-supervisor-inflight-guard.test.ts +159 -0
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-already-merged.test.ts +177 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-branch-head.test.ts +118 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-cleanup.test.ts +115 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-code-changes.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-db-ready.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-integration-branch.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-message.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-pre-teardown.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-stash.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-shelter.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/auto-worktree-resource-version.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-untracked-content.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/compat-marker.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/db-adapter.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/db-writable-probe.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +71 -1
- package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +46 -1
- package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +72 -32
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/import-completion-lost-update.test.ts +196 -0
- package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +4 -6
- package/src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts +2 -5
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/merge-preserve-worktree-on-preteardown-error.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/integration/paths.test.ts +88 -1
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +2 -4
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -15
- package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +439 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +269 -0
- package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +1 -4
- package/src/resources/extensions/gsd/tests/preferences.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +95 -1
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/sibling-model-fallbacks.test.ts +189 -0
- package/src/resources/extensions/gsd/tests/single-field-model-thinking.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -5
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/supervisor-model-swap-inflight-guard.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/supervisor-model-swap-thinking-clamp.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/system-flat-phase-layout.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +196 -0
- package/src/resources/extensions/gsd/tests/uat-dispatch.test.ts +211 -0
- package/src/resources/extensions/gsd/tests/uat-stuck-loop-orphaned-worktree.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +226 -2
- package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +213 -2
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +1 -3
- package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +1 -1
- package/src/resources/extensions/gsd/tool-contract.ts +29 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +9 -5
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +19 -8
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +5 -0
- package/src/resources/extensions/gsd/tools/reopen-task.ts +13 -5
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +13 -9
- package/src/resources/extensions/gsd/types.ts +23 -4
- package/src/resources/extensions/gsd/uat-dispatch.ts +176 -0
- package/src/resources/extensions/gsd/unit-registry.ts +16 -2
- package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +1 -1
- package/src/resources/extensions/gsd/uok/audit-toggle.ts +32 -2
- package/src/resources/extensions/gsd/uok/audit.ts +2 -0
- package/src/resources/extensions/gsd/uok/gate-runner.ts +26 -36
- package/src/resources/extensions/gsd/uok/gitops.ts +21 -18
- package/src/resources/extensions/gsd/uok/kernel.ts +169 -48
- package/src/resources/extensions/gsd/uok/model-policy.ts +20 -17
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -1
- package/src/resources/extensions/gsd/workflow-event-ledger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-projections.ts +9 -24
- package/src/resources/extensions/gsd/workspace-git-preflight.ts +2 -2
- package/src/resources/extensions/gsd/workspace.ts +5 -21
- package/src/resources/extensions/gsd/worktree-command.ts +1 -1
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +19 -15
- package/src/resources/extensions/gsd/worktree-reentry.ts +2 -1
- package/src/resources/extensions/shared/gsd-browser-cli.ts +29 -0
- package/src/resources/extensions/shared/interview-ui.ts +58 -11
- package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +32 -0
- /package/dist/web/standalone/.next/static/{bCg3aYrFN1DKZr2dqgE7L → 6xYMOzxVhsQgryneJ5KRc}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{bCg3aYrFN1DKZr2dqgE7L → 6xYMOzxVhsQgryneJ5KRc}/_ssgManifest.js +0 -0
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
runPreDispatchHooks,
|
|
17
17
|
persistHookState,
|
|
18
18
|
restoreHookState,
|
|
19
|
+
reconcileRestoredHookDispatch,
|
|
19
20
|
clearPersistedHookState,
|
|
20
21
|
getHookStatus,
|
|
21
22
|
formatHookStatus,
|
|
@@ -225,6 +226,233 @@ test('Blocking hook restored from disk does not trust artifact without clean hoo
|
|
|
225
226
|
}
|
|
226
227
|
});
|
|
227
228
|
|
|
229
|
+
test('Restore reconciliation re-enqueues the lost hook dispatch (#1246)', () => {
|
|
230
|
+
resetHookState();
|
|
231
|
+
const base = createFixtureBase();
|
|
232
|
+
try {
|
|
233
|
+
writeHookPreferences(base, ` - name: plan-review
|
|
234
|
+
after:
|
|
235
|
+
- plan-slice
|
|
236
|
+
prompt: Review the plan for {milestoneId}/{sliceId}
|
|
237
|
+
artifact: PLAN-REVIEW.md
|
|
238
|
+
criticality: blocking
|
|
239
|
+
max_cycles: 2
|
|
240
|
+
`);
|
|
241
|
+
// Trigger unit completes: activeHook set + persisted, dispatch enqueued.
|
|
242
|
+
const dispatch = checkPostUnitHooks("plan-slice", "M002/S01", base);
|
|
243
|
+
assert.ok(dispatch, "gate dispatches on trigger unit completion");
|
|
244
|
+
assert.equal(dispatch.unitType, "hook/plan-review");
|
|
245
|
+
persistHookState(base);
|
|
246
|
+
|
|
247
|
+
// Pause/resume: activeHook restored, but the session-local sidecar queue is
|
|
248
|
+
// gone (never persisted).
|
|
249
|
+
resetHookState();
|
|
250
|
+
restoreHookState(base);
|
|
251
|
+
assert.ok(getActiveHook(), "activeHook restored from disk");
|
|
252
|
+
|
|
253
|
+
// Reconciliation re-enqueues the missing dispatch so the hook actually runs.
|
|
254
|
+
const sidecarQueue: any[] = [];
|
|
255
|
+
reconcileRestoredHookDispatch(base, sidecarQueue);
|
|
256
|
+
assert.equal(sidecarQueue.length, 1, "lost hook dispatch is re-enqueued");
|
|
257
|
+
assert.equal(sidecarQueue[0].kind, "hook");
|
|
258
|
+
assert.equal(sidecarQueue[0].unitType, "hook/plan-review");
|
|
259
|
+
assert.equal(sidecarQueue[0].unitId, "M002/S01");
|
|
260
|
+
assert.match(sidecarQueue[0].prompt, /Review the plan for M002\/S01/);
|
|
261
|
+
} finally {
|
|
262
|
+
resetHookState();
|
|
263
|
+
invalidateAllCaches();
|
|
264
|
+
rmSync(base, { recursive: true, force: true });
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('Restore reconciliation is a no-op when the dispatch is already queued (#1246)', () => {
|
|
269
|
+
resetHookState();
|
|
270
|
+
const base = createFixtureBase();
|
|
271
|
+
try {
|
|
272
|
+
writeHookPreferences(base, ` - name: plan-review
|
|
273
|
+
after:
|
|
274
|
+
- plan-slice
|
|
275
|
+
prompt: Review the plan
|
|
276
|
+
artifact: PLAN-REVIEW.md
|
|
277
|
+
criticality: blocking
|
|
278
|
+
max_cycles: 2
|
|
279
|
+
`);
|
|
280
|
+
const dispatch = checkPostUnitHooks("plan-slice", "M002/S01", base);
|
|
281
|
+
assert.ok(dispatch, "gate dispatches on trigger unit completion");
|
|
282
|
+
persistHookState(base);
|
|
283
|
+
resetHookState();
|
|
284
|
+
restoreHookState(base);
|
|
285
|
+
|
|
286
|
+
const sidecarQueue: any[] = [
|
|
287
|
+
{ kind: "hook", unitType: "hook/plan-review", unitId: "M002/S01", prompt: "already here" },
|
|
288
|
+
];
|
|
289
|
+
reconcileRestoredHookDispatch(base, sidecarQueue);
|
|
290
|
+
assert.equal(sidecarQueue.length, 1, "does not duplicate an existing hook dispatch");
|
|
291
|
+
assert.equal(sidecarQueue[0].prompt, "already here");
|
|
292
|
+
} finally {
|
|
293
|
+
resetHookState();
|
|
294
|
+
invalidateAllCaches();
|
|
295
|
+
rmSync(base, { recursive: true, force: true });
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test('Restore reconciliation is a no-op with no active hook (#1246)', () => {
|
|
300
|
+
resetHookState();
|
|
301
|
+
const base = createFixtureBase();
|
|
302
|
+
try {
|
|
303
|
+
const sidecarQueue: any[] = [];
|
|
304
|
+
reconcileRestoredHookDispatch(base, sidecarQueue);
|
|
305
|
+
assert.equal(sidecarQueue.length, 0, "nothing enqueued when no active hook");
|
|
306
|
+
} finally {
|
|
307
|
+
resetHookState();
|
|
308
|
+
invalidateAllCaches();
|
|
309
|
+
rmSync(base, { recursive: true, force: true });
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test('Blocking hook re-dispatches once after a lost dispatch at default max_cycles', () => {
|
|
314
|
+
resetHookState();
|
|
315
|
+
const base = createFixtureBase();
|
|
316
|
+
try {
|
|
317
|
+
// Default max_cycles (1): a cycle is charged at dispatch time.
|
|
318
|
+
writeHookPreferences(base, ` - name: plan-review
|
|
319
|
+
after:
|
|
320
|
+
- plan-slice
|
|
321
|
+
prompt: Review plan
|
|
322
|
+
artifact: PLAN-REVIEW.md
|
|
323
|
+
criticality: blocking
|
|
324
|
+
`);
|
|
325
|
+
|
|
326
|
+
const firstDispatch = checkPostUnitHooks("plan-slice", "M002/S01", base);
|
|
327
|
+
assert.ok(firstDispatch, "gate dispatches and consumes its cycle at dispatch");
|
|
328
|
+
assert.equal(firstDispatch.unitType, "hook/plan-review");
|
|
329
|
+
persistHookState(base);
|
|
330
|
+
|
|
331
|
+
// Simulate a lost dispatch: the hook never produced its own unit-end, the
|
|
332
|
+
// trigger unit resumes instead. The dispatch-consumed cycle is refunded once
|
|
333
|
+
// so the gate re-dispatches rather than hard-blocking on a hook that never ran.
|
|
334
|
+
resetHookState();
|
|
335
|
+
restoreHookState(base);
|
|
336
|
+
const redispatch = checkPostUnitHooks("plan-slice", "M002/S01", base);
|
|
337
|
+
assert.ok(redispatch, "lost dispatch re-dispatches the gate instead of blocking");
|
|
338
|
+
assert.equal(redispatch.unitType, "hook/plan-review");
|
|
339
|
+
assert.deepStrictEqual(consumeGateBlock(), null, "no gate block on the first lost dispatch");
|
|
340
|
+
persistHookState(base);
|
|
341
|
+
|
|
342
|
+
// A second lost dispatch consumes the budget and blocks — the refund is one-shot.
|
|
343
|
+
resetHookState();
|
|
344
|
+
restoreHookState(base);
|
|
345
|
+
const blocked = checkPostUnitHooks("plan-slice", "M002/S01", base);
|
|
346
|
+
assert.deepStrictEqual(blocked, null, "second lost dispatch exhausts the budget");
|
|
347
|
+
const block = consumeGateBlock();
|
|
348
|
+
assert.ok(block, "gate block recorded after the one-shot re-dispatch is used");
|
|
349
|
+
assert.equal(block.hookName, "plan-review");
|
|
350
|
+
assert.match(block.reason, /gate cycle budget exhausted/);
|
|
351
|
+
} finally {
|
|
352
|
+
resetHookState();
|
|
353
|
+
invalidateAllCaches();
|
|
354
|
+
rmSync(base, { recursive: true, force: true });
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test('Blocking hook lost-dispatch refund is one-shot and bounded at max_cycles=2', () => {
|
|
359
|
+
resetHookState();
|
|
360
|
+
const base = createFixtureBase();
|
|
361
|
+
try {
|
|
362
|
+
writeHookPreferences(base, ` - name: plan-review
|
|
363
|
+
after:
|
|
364
|
+
- plan-slice
|
|
365
|
+
prompt: Review plan
|
|
366
|
+
artifact: PLAN-REVIEW.md
|
|
367
|
+
criticality: blocking
|
|
368
|
+
max_cycles: 2
|
|
369
|
+
`);
|
|
370
|
+
|
|
371
|
+
// Simulate a lost dispatch: persist, drop in-memory state, restore, then the
|
|
372
|
+
// trigger unit resumes instead of the hook producing its own unit-end.
|
|
373
|
+
const lose = () => {
|
|
374
|
+
persistHookState(base);
|
|
375
|
+
resetHookState();
|
|
376
|
+
restoreHookState(base);
|
|
377
|
+
return checkPostUnitHooks("plan-slice", "M002/S01", base);
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
assert.ok(checkPostUnitHooks("plan-slice", "M002/S01", base), "initial dispatch");
|
|
381
|
+
|
|
382
|
+
// Refund is one-shot, but max_cycles=2 still has real budget, so lost#2 also
|
|
383
|
+
// re-dispatches by consuming a genuine cycle (not another refund).
|
|
384
|
+
assert.ok(lose(), "lost#1 re-dispatches (one-shot refund)");
|
|
385
|
+
assert.equal(consumeGateBlock(), null, "no block after lost#1");
|
|
386
|
+
assert.ok(lose(), "lost#2 re-dispatches from real budget");
|
|
387
|
+
assert.equal(consumeGateBlock(), null, "no block after lost#2");
|
|
388
|
+
|
|
389
|
+
// Total dispatches are bounded to max_cycles + 1 (the single forgiven loss),
|
|
390
|
+
// so the third lost dispatch blocks. redispatchedGateKeys guarantees this
|
|
391
|
+
// terminates rather than looping forever on repeated lost resumes.
|
|
392
|
+
assert.equal(lose(), null, "lost#3 exhausts the bounded budget");
|
|
393
|
+
const block = consumeGateBlock();
|
|
394
|
+
assert.ok(block, "gate block recorded on lost#3");
|
|
395
|
+
assert.match(block.reason, /gate cycle budget exhausted/);
|
|
396
|
+
} finally {
|
|
397
|
+
resetHookState();
|
|
398
|
+
invalidateAllCaches();
|
|
399
|
+
rmSync(base, { recursive: true, force: true });
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test('Blocking hook lost dispatch does not steal a real-run cycle at max_cycles=2', () => {
|
|
404
|
+
resetHookState();
|
|
405
|
+
const base = createFixtureBase();
|
|
406
|
+
try {
|
|
407
|
+
writeHookPreferences(base, ` - name: plan-review
|
|
408
|
+
after:
|
|
409
|
+
- plan-slice
|
|
410
|
+
prompt: Review plan
|
|
411
|
+
artifact: PLAN-REVIEW.md
|
|
412
|
+
criticality: blocking
|
|
413
|
+
max_cycles: 2
|
|
414
|
+
`);
|
|
415
|
+
|
|
416
|
+
// Dispatch, then lose it once (the one-shot refund is spent here).
|
|
417
|
+
assert.ok(checkPostUnitHooks("plan-slice", "M002/S01", base), "initial dispatch");
|
|
418
|
+
persistHookState(base);
|
|
419
|
+
resetHookState();
|
|
420
|
+
restoreHookState(base);
|
|
421
|
+
assert.ok(
|
|
422
|
+
checkPostUnitHooks("plan-slice", "M002/S01", base),
|
|
423
|
+
"lost dispatch re-dispatches (refund spent)",
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
// The hook now actually runs, each run reporting a failed verdict. Because a
|
|
427
|
+
// lost dispatch must not consume the hook's real-run budget, max_cycles=2
|
|
428
|
+
// still permits two genuine runs before the gate blocks — spending the
|
|
429
|
+
// refund early did not rob a later real cycle.
|
|
430
|
+
mkdirSync(join(base, ".gsd", "milestones", "M002", "slices", "S01"), { recursive: true });
|
|
431
|
+
writeFileSync(
|
|
432
|
+
resolveHookArtifactPath(base, "M002/S01", "PLAN-REVIEW.md"),
|
|
433
|
+
"---\nverdict: failed\n---\n\nRejected.\n",
|
|
434
|
+
"utf-8",
|
|
435
|
+
);
|
|
436
|
+
assert.ok(
|
|
437
|
+
checkPostUnitHooks("hook/plan-review", "M002/S01", base),
|
|
438
|
+
"real run #1 (failed verdict) re-dispatches",
|
|
439
|
+
);
|
|
440
|
+
assert.equal(consumeGateBlock(), null, "no block after the first real run");
|
|
441
|
+
assert.equal(
|
|
442
|
+
checkPostUnitHooks("hook/plan-review", "M002/S01", base),
|
|
443
|
+
null,
|
|
444
|
+
"real run #2 (failed verdict) exhausts the real budget",
|
|
445
|
+
);
|
|
446
|
+
const block = consumeGateBlock();
|
|
447
|
+
assert.ok(block, "gate block recorded only after two real runs");
|
|
448
|
+
assert.match(block.reason, /gate cycle budget exhausted/);
|
|
449
|
+
} finally {
|
|
450
|
+
resetHookState();
|
|
451
|
+
invalidateAllCaches();
|
|
452
|
+
rmSync(base, { recursive: true, force: true });
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
228
456
|
test('Blocking hook needs-rework verdict requests trigger unit retry', () => {
|
|
229
457
|
resetHookState();
|
|
230
458
|
const base = createFixtureBase();
|
|
@@ -454,4 +682,45 @@ test('triggerHookManually: with configured hook', () => {
|
|
|
454
682
|
}
|
|
455
683
|
});
|
|
456
684
|
|
|
685
|
+
// ─── Hook dispatch results omit a primary-only model (#1229) ───────────────
|
|
686
|
+
// Both the auto-mode (checkPostUnitHooks → _startHook) and manual
|
|
687
|
+
// (triggerHookManually) dispatch paths must NOT carry a resolved primary model
|
|
688
|
+
// on the dispatch result. Emitting the primary alone discards the configured
|
|
689
|
+
// fallbacks[] chain: the auto path would re-apply it over a fallback already
|
|
690
|
+
// selected by selectAndApplyModel, and the manual path (dispatchHookUnit) now
|
|
691
|
+
// resolves the full chain itself. The dispatch result therefore leaves `model`
|
|
692
|
+
// unset so downstream resolution honors fallbacks.
|
|
693
|
+
test('Dispatch results omit primary-only model so fallbacks survive (#1229)', () => {
|
|
694
|
+
resetHookState();
|
|
695
|
+
const base = createFixtureBase();
|
|
696
|
+
try {
|
|
697
|
+
writeHookPreferences(base, ` - name: code-review
|
|
698
|
+
after:
|
|
699
|
+
- execute-task
|
|
700
|
+
prompt: Review the change
|
|
701
|
+
artifact: REVIEW-PASS.md
|
|
702
|
+
model:
|
|
703
|
+
model: primary-model
|
|
704
|
+
fallbacks:
|
|
705
|
+
- fallback-a
|
|
706
|
+
- fallback-b
|
|
707
|
+
`);
|
|
708
|
+
|
|
709
|
+
const autoDispatch = checkPostUnitHooks("execute-task", "M001/S01/T01", base);
|
|
710
|
+
assert.ok(autoDispatch, "auto-mode dispatches the configured hook");
|
|
711
|
+
assert.equal(autoDispatch.unitType, "hook/code-review", "auto dispatch is hook-prefixed");
|
|
712
|
+
assert.equal(autoDispatch.model, undefined, "auto dispatch does not carry a primary-only model");
|
|
713
|
+
|
|
714
|
+
resetHookState();
|
|
715
|
+
|
|
716
|
+
const manualDispatch = triggerHookManually("code-review", "execute-task", "M001/S01/T01", base);
|
|
717
|
+
assert.ok(manualDispatch, "manual trigger dispatches the configured hook");
|
|
718
|
+
assert.equal(manualDispatch.unitType, "hook/code-review", "manual dispatch is hook-prefixed");
|
|
719
|
+
assert.equal(manualDispatch.model, undefined, "manual dispatch does not carry a primary-only model");
|
|
720
|
+
} finally {
|
|
721
|
+
resetHookState();
|
|
722
|
+
rmSync(base, { recursive: true, force: true });
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
|
|
457
726
|
});
|
|
@@ -260,7 +260,14 @@ test("post-unit blocking gate pauses auto-mode on needs-attention verdict", asyn
|
|
|
260
260
|
const result = await postUnitPostVerification(pctx);
|
|
261
261
|
assert.equal(result, "stopped");
|
|
262
262
|
assert.equal(pauseAuto.mock.callCount(), 1);
|
|
263
|
-
|
|
263
|
+
// Regression (#1245): the block message must name BOTH the hook's trigger
|
|
264
|
+
// unit (execute-task M001/S01/T01) and the just-completed unit that surfaced
|
|
265
|
+
// the block (s.currentUnit = hook/review-arbiter M001/S01/T01), so the pause
|
|
266
|
+
// is unambiguous to triage. The pre-fix message stopped at the trigger unit.
|
|
267
|
+
assert.match(
|
|
268
|
+
notifications.join("\n"),
|
|
269
|
+
/Post-unit gate "review-arbiter" blocked execute-task M001\/S01\/T01 \(detected on completion of hook\/review-arbiter M001\/S01\/T01\)/,
|
|
270
|
+
);
|
|
264
271
|
assert.match(notifications.join("\n"), /\/gsd status/);
|
|
265
272
|
} finally {
|
|
266
273
|
process.chdir(originalCwd);
|
|
@@ -12,7 +12,7 @@ import assert from "node:assert/strict";
|
|
|
12
12
|
import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, existsSync, readdirSync, rmSync } from "node:fs";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { tmpdir } from "node:os";
|
|
15
|
-
import { syncWorktreeStateBack } from "../auto-worktree.ts";
|
|
15
|
+
import { syncGsdStateToWorktree, syncWorktreeStateBack } from "../auto-worktree-sync.ts";
|
|
16
16
|
|
|
17
17
|
test("#2684: syncWorktreeStateBack does not overwrite project PREFERENCES.md", () => {
|
|
18
18
|
const mainBase = mkdtempSync(join(tmpdir(), "gsd-wt-prefs-main-"));
|
|
@@ -58,8 +58,6 @@ test("syncGsdStateToWorktree copies canonical PREFERENCES.md", async () => {
|
|
|
58
58
|
"---\nversion: 1\n---\n\npost_unit_hooks:\n - name: notify\n command: echo done\n",
|
|
59
59
|
);
|
|
60
60
|
|
|
61
|
-
// Import and call syncGsdStateToWorktree
|
|
62
|
-
const { syncGsdStateToWorktree } = await import("../auto-worktree.ts");
|
|
63
61
|
syncGsdStateToWorktree(srcBase, dstBase);
|
|
64
62
|
|
|
65
63
|
// Verify PREFERENCES.md was copied
|
|
@@ -93,7 +91,6 @@ test("syncGsdStateToWorktree falls back to legacy lowercase preferences.md", asy
|
|
|
93
91
|
"---\nversion: 1\n---\n\ngit:\n auto_push: true\n",
|
|
94
92
|
);
|
|
95
93
|
|
|
96
|
-
const { syncGsdStateToWorktree } = await import("../auto-worktree.ts");
|
|
97
94
|
const result = syncGsdStateToWorktree(srcBase, dstBase);
|
|
98
95
|
|
|
99
96
|
const copiedEntries = readdirSync(dstGsd)
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
loadProjectGSDPreferences,
|
|
25
25
|
parsePreferencesMarkdown,
|
|
26
26
|
renderPreferencesForSystemPrompt,
|
|
27
|
+
renderLanguageDirectiveForPrompt,
|
|
27
28
|
clearGSDPreferencesCache,
|
|
28
29
|
_resetParseWarningFlag,
|
|
29
30
|
} from "../preferences.ts";
|
|
@@ -1674,6 +1675,24 @@ test("language: renderPreferencesForSystemPrompt omits language line when not se
|
|
|
1674
1675
|
assert.ok(!output.includes("Always respond in"), `expected no language line in output, got:\n${output}`);
|
|
1675
1676
|
});
|
|
1676
1677
|
|
|
1678
|
+
test("language: renderLanguageDirectiveForPrompt emits directive when language set", () => {
|
|
1679
|
+
const output = renderLanguageDirectiveForPrompt({ language: "Chinese" });
|
|
1680
|
+
assert.ok(output.includes("## Response Language"), `expected language heading, got:\n${output}`);
|
|
1681
|
+
assert.ok(output.includes("Always respond in Chinese"), `expected language instruction, got:\n${output}`);
|
|
1682
|
+
});
|
|
1683
|
+
|
|
1684
|
+
test("language: renderLanguageDirectiveForPrompt returns empty when language unset", () => {
|
|
1685
|
+
assert.equal(renderLanguageDirectiveForPrompt({}), "");
|
|
1686
|
+
assert.equal(renderLanguageDirectiveForPrompt(undefined), "");
|
|
1687
|
+
});
|
|
1688
|
+
|
|
1689
|
+
test("language: renderLanguageDirectiveForPrompt sanitizes newlines and caps length", () => {
|
|
1690
|
+
const output = renderLanguageDirectiveForPrompt({ language: `Chinese\ninjected${"x".repeat(80)}` });
|
|
1691
|
+
assert.ok(!output.includes("\ninjected"), `expected newlines stripped, got:\n${output}`);
|
|
1692
|
+
// "Chinese injected" + padding, capped to 50 chars after newline replacement.
|
|
1693
|
+
assert.ok(output.includes("Always respond in Chinese injected"), `got:\n${output}`);
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1677
1696
|
test("language: parses from markdown frontmatter", () => {
|
|
1678
1697
|
const content = [
|
|
1679
1698
|
"---",
|
|
@@ -79,6 +79,11 @@ const PREF_SAMPLE_VALUES: Record<string, unknown> = {
|
|
|
79
79
|
min_request_interval_ms: 250,
|
|
80
80
|
stale_commit_threshold_minutes: 15,
|
|
81
81
|
context_management: { enabled: true },
|
|
82
|
+
tool_call_loop_guard: {
|
|
83
|
+
enabled: true,
|
|
84
|
+
identical_args: { enabled: true, max_consecutive_calls: 4 },
|
|
85
|
+
repeated_tool: { enabled: true, default_cap: 6, repeatable_cap: 15, exempt_tools: ["ctx_execute"] },
|
|
86
|
+
},
|
|
82
87
|
experimental: { rtk: true },
|
|
83
88
|
codebase: { indexing: "auto" },
|
|
84
89
|
slice_parallel: { enabled: true, max_workers: 2 },
|
|
@@ -160,6 +160,60 @@ describe('readUatGateVerdict — DB fallback for orphaned ASSESSMENT', () => {
|
|
|
160
160
|
assert.equal(result, null, 'roadmap-scoped assessments must not satisfy the UAT gate');
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
test('a backfilled assessment does NOT satisfy the UAT gate (#1258)', async () => {
|
|
164
|
+
// The milestone-validation backfill fabricates a `verdict: PASS` ASSESSMENT
|
|
165
|
+
// for completed slices that never produced a real UAT result (e.g.
|
|
166
|
+
// artifact-driven UAT that was never dispatched). Filed under scope
|
|
167
|
+
// 'backfill', that placeholder must never be read as a genuine UAT sign-off —
|
|
168
|
+
// otherwise "never checked" is silently treated as "passed". Even with the
|
|
169
|
+
// fabricated file present on disk, the gate must fall through to the
|
|
170
|
+
// authoritative run-uat row (absent here) and return null.
|
|
171
|
+
const file = canonicalAssessmentPath(basePath);
|
|
172
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
173
|
+
fs.writeFileSync(file, RUNTIME_PASS_BODY);
|
|
174
|
+
insertAssessment({
|
|
175
|
+
path: `.gsd/milestones/${MID}/slices/${SLICE}/${SLICE}-ASSESSMENT.md`,
|
|
176
|
+
milestoneId: MID,
|
|
177
|
+
sliceId: SLICE,
|
|
178
|
+
status: 'pass',
|
|
179
|
+
scope: 'backfill',
|
|
180
|
+
fullContent: RUNTIME_PASS_BODY,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const result = await readUatGateVerdict(basePath, MID, SLICE);
|
|
184
|
+
|
|
185
|
+
assert.equal(result, null, 'backfilled assessments must not satisfy the UAT gate');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('a genuine run-uat row still resolves even when a backfilled file exists (#1258)', async () => {
|
|
189
|
+
// If a real UAT was later recorded, its authoritative run-uat row must win
|
|
190
|
+
// over the fabricated placeholder rather than being masked by it.
|
|
191
|
+
const file = canonicalAssessmentPath(basePath);
|
|
192
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
193
|
+
fs.writeFileSync(file, RUNTIME_PASS_BODY);
|
|
194
|
+
insertAssessment({
|
|
195
|
+
path: `.gsd/milestones/${MID}/slices/${SLICE}/${SLICE}-ASSESSMENT.md`,
|
|
196
|
+
milestoneId: MID,
|
|
197
|
+
sliceId: SLICE,
|
|
198
|
+
status: 'pass',
|
|
199
|
+
scope: 'backfill',
|
|
200
|
+
fullContent: RUNTIME_PASS_BODY,
|
|
201
|
+
});
|
|
202
|
+
insertAssessment({
|
|
203
|
+
path: `.gsd/phases/01-some-feature/01-01-ASSESSMENT.md`,
|
|
204
|
+
milestoneId: MID,
|
|
205
|
+
sliceId: SLICE,
|
|
206
|
+
status: 'pass',
|
|
207
|
+
scope: 'run-uat',
|
|
208
|
+
fullContent: RUNTIME_PASS_BODY,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const result = await readUatGateVerdict(basePath, MID, SLICE);
|
|
212
|
+
|
|
213
|
+
assert.ok(result, 'a genuine run-uat sign-off must resolve');
|
|
214
|
+
assert.equal(result!.verdict, 'pass');
|
|
215
|
+
});
|
|
216
|
+
|
|
163
217
|
test('returns null when no assessment and no file exist (fallback does not hallucinate)', async () => {
|
|
164
218
|
const result = await readUatGateVerdict(basePath, MID, SLICE);
|
|
165
219
|
assert.equal(result, null);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtempSync, mkdirSync, rmSync, existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
|
|
@@ -394,6 +394,54 @@ test('handleReassessRoadmap invalidates stale milestone-validation when roadmap
|
|
|
394
394
|
}
|
|
395
395
|
});
|
|
396
396
|
|
|
397
|
+
test('handleReassessRoadmap deletes flat-phase compatibility validation files when roadmap changes (#2957)', async (t) => {
|
|
398
|
+
const base = makeTmpBase();
|
|
399
|
+
t.after(() => cleanup(base));
|
|
400
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
401
|
+
|
|
402
|
+
insertMilestone({ id: 'M001', title: 'Test Milestone', status: 'active' });
|
|
403
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice One', status: 'complete', demo: 'Demo' });
|
|
404
|
+
|
|
405
|
+
const legacyValidationPath = join(base, '.gsd', 'phases', '01-test', 'M001-VALIDATION.md');
|
|
406
|
+
const canonicalValidationPath = join(base, '.gsd', 'phases', '01-test', '01-VALIDATION.md');
|
|
407
|
+
const staleValidation = '---\nverdict: pass\n---\n\n# Stale validation\n';
|
|
408
|
+
writeFileSync(legacyValidationPath, staleValidation, 'utf-8');
|
|
409
|
+
writeFileSync(canonicalValidationPath, staleValidation, 'utf-8');
|
|
410
|
+
insertAssessment({
|
|
411
|
+
path: legacyValidationPath,
|
|
412
|
+
milestoneId: 'M001',
|
|
413
|
+
sliceId: null,
|
|
414
|
+
taskId: null,
|
|
415
|
+
status: 'pass',
|
|
416
|
+
scope: 'milestone-validation',
|
|
417
|
+
fullContent: staleValidation,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const result = await handleReassessRoadmap({
|
|
421
|
+
milestoneId: 'M001',
|
|
422
|
+
completedSliceId: 'S01',
|
|
423
|
+
verdict: 'confirmed',
|
|
424
|
+
assessment: 'S01 completed. Adding S02 changes roadmap structure.',
|
|
425
|
+
sliceChanges: {
|
|
426
|
+
modified: [],
|
|
427
|
+
added: [
|
|
428
|
+
{
|
|
429
|
+
sliceId: 'S02',
|
|
430
|
+
title: 'New Slice Two',
|
|
431
|
+
risk: 'low',
|
|
432
|
+
depends: ['S01'],
|
|
433
|
+
demo: 'Demo two.',
|
|
434
|
+
},
|
|
435
|
+
],
|
|
436
|
+
removed: [],
|
|
437
|
+
},
|
|
438
|
+
}, base);
|
|
439
|
+
|
|
440
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
441
|
+
assert.equal(existsSync(legacyValidationPath), false, 'stale compatibility validation artifact should be deleted');
|
|
442
|
+
assert.equal(existsSync(canonicalValidationPath), false, 'canonical validation artifact should not remain after reassess');
|
|
443
|
+
});
|
|
444
|
+
|
|
397
445
|
test('handleReassessRoadmap does NOT invalidate validation when no roadmap structural changes (#2957)', async () => {
|
|
398
446
|
const base = makeTmpBase();
|
|
399
447
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -125,6 +125,58 @@ test('handleReopenSlice: rejects slice in a closed milestone', async () => {
|
|
|
125
125
|
}
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
test('handleReopenSlice: recovers a desynced slice (open status, completed tasks) — #1205', async () => {
|
|
129
|
+
const base = makeTmpBase();
|
|
130
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
131
|
+
try {
|
|
132
|
+
// Desync signature: slice left "pending" by a UAT→planning fallback while
|
|
133
|
+
// its tasks stayed "complete" from the first execution pass.
|
|
134
|
+
insertMilestone({ id: 'M001', title: 'Active', status: 'active' });
|
|
135
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', status: 'pending' });
|
|
136
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', status: 'complete' });
|
|
137
|
+
insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', status: 'complete' });
|
|
138
|
+
|
|
139
|
+
const result = await handleReopenSlice({ milestoneId: 'M001', sliceId: 'S01' }, base);
|
|
140
|
+
|
|
141
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
142
|
+
assert.equal(result.tasksReset, 2, 'both completed tasks should be reset');
|
|
143
|
+
|
|
144
|
+
const slice = getSlice('M001', 'S01');
|
|
145
|
+
assert.equal(slice!.status, 'in_progress', 'slice should be reopened to in_progress');
|
|
146
|
+
const tasks = getSliceTasks('M001', 'S01');
|
|
147
|
+
assert.ok(tasks.every(t => t.status === 'pending'), 'all tasks should be reset to pending');
|
|
148
|
+
} finally {
|
|
149
|
+
cleanup(base);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('handleReopenSlice: rejects an in-flight slice with mixed task progress and preserves it — #1205', async () => {
|
|
154
|
+
const base = makeTmpBase();
|
|
155
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
156
|
+
try {
|
|
157
|
+
// Normal in-flight slice, NOT the #1205 desync: one task finished while
|
|
158
|
+
// another is still in progress. The desync guard must not treat a single
|
|
159
|
+
// completed task as reopenable, or a full reset would wipe live progress.
|
|
160
|
+
insertMilestone({ id: 'M001', title: 'Active', status: 'active' });
|
|
161
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', status: 'in_progress' });
|
|
162
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', status: 'complete' });
|
|
163
|
+
insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', status: 'in_progress' });
|
|
164
|
+
|
|
165
|
+
const result = await handleReopenSlice({ milestoneId: 'M001', sliceId: 'S01' }, base);
|
|
166
|
+
|
|
167
|
+
assert.ok('error' in result, 'in-flight slice with unfinished tasks must be rejected');
|
|
168
|
+
assert.match(result.error, /not complete/);
|
|
169
|
+
|
|
170
|
+
// Progress must be untouched — nothing reset to pending.
|
|
171
|
+
const byId = Object.fromEntries(getSliceTasks('M001', 'S01').map(t => [t.id, t.status]));
|
|
172
|
+
assert.equal(byId['T01'], 'complete', 'completed task must stay complete');
|
|
173
|
+
assert.equal(byId['T02'], 'in_progress', 'in-progress task must stay in_progress');
|
|
174
|
+
assert.equal(getSlice('M001', 'S01')!.status, 'in_progress', 'slice status must be untouched');
|
|
175
|
+
} finally {
|
|
176
|
+
cleanup(base);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
128
180
|
test('handleReopenSlice: rejects reopening a slice that is not complete', async () => {
|
|
129
181
|
const base = makeTmpBase();
|
|
130
182
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import assert from 'node:assert/strict';
|
|
7
7
|
import { test, describe, beforeEach } from "node:test";
|
|
8
|
-
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, realpathSync } from "node:fs";
|
|
9
9
|
import { tmpdir } from "node:os";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { emitJournalEvent } from "../journal.ts";
|
|
@@ -544,3 +544,97 @@ describe("RuleRegistry", () => {
|
|
|
544
544
|
assert.deepStrictEqual(result.matchedRule, "<no-match>", "matchedRule is '<no-match>' on fallback");
|
|
545
545
|
});
|
|
546
546
|
});
|
|
547
|
+
|
|
548
|
+
describe("resolveHookArtifactPath", () => {
|
|
549
|
+
test("resolves a phase-level artifact from the .gsd/phases layout", () => {
|
|
550
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
551
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-hook-phase-"));
|
|
552
|
+
const tempGsdHome = mkdtempSync(join(tmpdir(), "gsd-hook-home-"));
|
|
553
|
+
try {
|
|
554
|
+
process.env.GSD_HOME = tempGsdHome;
|
|
555
|
+
const phaseDir = join(realpathSync(projectRoot), ".gsd", "phases", "50-some-phase");
|
|
556
|
+
mkdirSync(phaseDir, { recursive: true });
|
|
557
|
+
const artifactPath = join(phaseDir, "BROWSER-RUNTIME-EVIDENCE.md");
|
|
558
|
+
writeFileSync(artifactPath, "---\nverdict: advisory\n---\n", "utf-8");
|
|
559
|
+
|
|
560
|
+
const resolved = resolveHookArtifactPath(projectRoot, "M050/S02/T01", "BROWSER-RUNTIME-EVIDENCE.md");
|
|
561
|
+
assert.equal(resolved, artifactPath, "resolves the canonical phase-level artifact");
|
|
562
|
+
} finally {
|
|
563
|
+
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
564
|
+
else process.env.GSD_HOME = originalGsdHome;
|
|
565
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
566
|
+
rmSync(tempGsdHome, { recursive: true, force: true });
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
test("falls back to the legacy milestones/slices/tasks layout", () => {
|
|
571
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
572
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-hook-legacy-"));
|
|
573
|
+
const tempGsdHome = mkdtempSync(join(tmpdir(), "gsd-hook-home-"));
|
|
574
|
+
try {
|
|
575
|
+
process.env.GSD_HOME = tempGsdHome;
|
|
576
|
+
const tasksDir = join(projectRoot, ".gsd", "milestones", "M050", "slices", "S02", "tasks");
|
|
577
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
578
|
+
const artifactPath = join(tasksDir, "T01-REVIEW.md");
|
|
579
|
+
writeFileSync(artifactPath, "---\nverdict: pass\n---\n", "utf-8");
|
|
580
|
+
|
|
581
|
+
const resolved = resolveHookArtifactPath(projectRoot, "M050/S02/T01", "REVIEW.md");
|
|
582
|
+
assert.equal(resolved, artifactPath, "resolves the task-prefixed legacy artifact");
|
|
583
|
+
} finally {
|
|
584
|
+
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
585
|
+
else process.env.GSD_HOME = originalGsdHome;
|
|
586
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
587
|
+
rmSync(tempGsdHome, { recursive: true, force: true });
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test("legacy: nested task artifact wins over a milestone-root file of the same name", () => {
|
|
592
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
593
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-hook-legacy-root-"));
|
|
594
|
+
const tempGsdHome = mkdtempSync(join(tmpdir(), "gsd-hook-home-"));
|
|
595
|
+
try {
|
|
596
|
+
process.env.GSD_HOME = tempGsdHome;
|
|
597
|
+
const milestoneDir = join(projectRoot, ".gsd", "milestones", "M050");
|
|
598
|
+
const tasksDir = join(milestoneDir, "slices", "S02", "tasks");
|
|
599
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
600
|
+
// The correct task-scoped artifact lives in the nested slices/tasks tree.
|
|
601
|
+
const nestedPath = join(tasksDir, "T01-REVIEW.md");
|
|
602
|
+
writeFileSync(nestedPath, "---\nverdict: pass\n---\n", "utf-8");
|
|
603
|
+
// A same-named decoy at the legacy milestone root must NOT win — before the
|
|
604
|
+
// fix, resolveMilestonePath returned the milestone root and it was probed
|
|
605
|
+
// ahead of the nested task path (#1264 Bugbot follow-up).
|
|
606
|
+
writeFileSync(join(milestoneDir, "REVIEW.md"), "---\nverdict: failed\n---\n", "utf-8");
|
|
607
|
+
|
|
608
|
+
const resolved = resolveHookArtifactPath(projectRoot, "M050/S02/T01", "REVIEW.md");
|
|
609
|
+
assert.equal(resolved, nestedPath, "task-scoped legacy path wins over the milestone-root file");
|
|
610
|
+
} finally {
|
|
611
|
+
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
612
|
+
else process.env.GSD_HOME = originalGsdHome;
|
|
613
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
614
|
+
rmSync(tempGsdHome, { recursive: true, force: true });
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
test("legacy: missing-artifact fallback points at the nested task path, not the milestone root", () => {
|
|
619
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
620
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-hook-legacy-miss-"));
|
|
621
|
+
const tempGsdHome = mkdtempSync(join(tmpdir(), "gsd-hook-home-"));
|
|
622
|
+
try {
|
|
623
|
+
process.env.GSD_HOME = tempGsdHome;
|
|
624
|
+
const milestoneDir = join(projectRoot, ".gsd", "milestones", "M050");
|
|
625
|
+
mkdirSync(milestoneDir, { recursive: true });
|
|
626
|
+
// Content-bearing legacy milestone dir (a non-META file) so resolveMilestonePath
|
|
627
|
+
// returns it, but the requested gate artifact does not exist anywhere.
|
|
628
|
+
writeFileSync(join(milestoneDir, "M050-ROADMAP.md"), "# roadmap\n", "utf-8");
|
|
629
|
+
|
|
630
|
+
const resolved = resolveHookArtifactPath(projectRoot, "M050/S02/T01", "REVIEW.md");
|
|
631
|
+
const expected = join(milestoneDir, "slices", "S02", "tasks", "T01-REVIEW.md");
|
|
632
|
+
assert.equal(resolved, expected, "diagnostic fallback uses the nested task path for a legacy task-scoped unit");
|
|
633
|
+
} finally {
|
|
634
|
+
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
635
|
+
else process.env.GSD_HOME = originalGsdHome;
|
|
636
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
637
|
+
rmSync(tempGsdHome, { recursive: true, force: true });
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
});
|