@opengsd/gsd-pi 1.1.1-dev.a5a2de8 → 1.1.1-dev.b2556262
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/headless-recover.js +56 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +18 -2
- package/dist/resources/extensions/browser-tools/engine/selection.js +1 -1
- package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/dist/resources/extensions/browser-tools/index.js +68 -24
- package/dist/resources/extensions/browser-tools/state.js +12 -0
- package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
- package/dist/resources/extensions/browser-tools/utils.js +3 -3
- package/dist/resources/extensions/browser-tools/web-app-detect.js +52 -0
- package/dist/resources/extensions/gsd/auto/loop.js +4 -2
- package/dist/resources/extensions/gsd/auto/phases.js +87 -12
- package/dist/resources/extensions/gsd/auto/session.js +22 -1
- package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +81 -13
- package/dist/resources/extensions/gsd/auto-model-selection.js +154 -9
- package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +26 -21
- package/dist/resources/extensions/gsd/auto-recovery.js +4 -2
- package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
- package/dist/resources/extensions/gsd/auto-start.js +1 -1
- package/dist/resources/extensions/gsd/auto-timers.js +24 -10
- package/dist/resources/extensions/gsd/auto.js +40 -15
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +192 -77
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
- package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -9
- package/dist/resources/extensions/gsd/commands-maintenance.js +93 -15
- package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
- package/dist/resources/extensions/gsd/config-overlay.js +1 -0
- package/dist/resources/extensions/gsd/context-masker.js +129 -5
- package/dist/resources/extensions/gsd/db-writer.js +35 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +50 -1
- package/dist/resources/extensions/gsd/gsd-db.js +480 -172
- package/dist/resources/extensions/gsd/guided-flow.js +4 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +37 -53
- package/dist/resources/extensions/gsd/md-importer.js +38 -3
- package/dist/resources/extensions/gsd/migration-auto-check.js +126 -31
- package/dist/resources/extensions/gsd/parsers-legacy.js +23 -0
- package/dist/resources/extensions/gsd/planner-handoff.js +98 -0
- package/dist/resources/extensions/gsd/planning-path-scope.js +22 -4
- package/dist/resources/extensions/gsd/pre-execution-checks.js +10 -2
- package/dist/resources/extensions/gsd/preferences-models.js +111 -43
- package/dist/resources/extensions/gsd/preferences-types.js +13 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +68 -3
- package/dist/resources/extensions/gsd/preferences.js +4 -1
- package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- 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/run-uat.md +2 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +5 -1
- package/dist/resources/extensions/gsd/safety/content-validator.js +6 -4
- package/dist/resources/extensions/gsd/skill-manifest.js +12 -0
- package/dist/resources/extensions/gsd/source-observations.js +306 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +15 -8
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +33 -5
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +34 -13
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +39 -14
- package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +4 -4
- package/dist/resources/extensions/gsd/state.js +7 -3
- package/dist/resources/extensions/gsd/tool-contract.js +15 -1
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +24 -2
- package/dist/resources/extensions/gsd/tools/complete-slice.js +28 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +42 -11
- package/dist/resources/extensions/gsd/tools/plan-task.js +7 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +62 -406
- package/dist/resources/extensions/gsd/uat-policy.js +130 -0
- package/dist/resources/extensions/gsd/uat-run.js +414 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +3 -4
- package/dist/resources/extensions/gsd/unit-tool-contracts.js +38 -14
- package/dist/resources/extensions/gsd/verdict-parser.js +3 -8
- package/dist/resources/extensions/gsd/workflow-manifest.js +132 -5
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -3
- package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +26 -0
- package/dist/resources/extensions/gsd/worktree-reentry.js +96 -0
- package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
- package/dist/resources/extensions/shared/gsd-browser-cli.js +6 -0
- package/dist/resources/extensions/subagent/agents.js +1 -0
- package/dist/resources/extensions/subagent/index.js +27 -12
- package/dist/resources/extensions/subagent/launch.js +7 -2
- 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/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/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/dist/native.js +22 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/package.json +4 -4
- package/packages/cloud-mcp-gateway/package.json +2 -2
- 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/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +3 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +66 -12
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- 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 +18 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/dist/native.js +22 -0
- 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 +30 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +30 -0
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +174 -29
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +178 -54
- package/packages/pi-ai/dist/models.generated.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 +8 -1
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/themes.js +1 -1
- package/packages/pi-coding-agent/dist/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/utils.d.ts +11 -0
- package/packages/pi-tui/dist/utils.d.ts.map +1 -1
- package/packages/pi-tui/dist/utils.js +119 -6
- package/packages/pi-tui/dist/utils.js.map +1 -1
- package/packages/pi-tui/package.json +2 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/theme/themes.js +1 -1
- package/pkg/dist/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/scripts/install/handoff.js +16 -3
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +21 -2
- package/src/resources/extensions/browser-tools/engine/selection.ts +1 -1
- package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/src/resources/extensions/browser-tools/index.ts +75 -27
- package/src/resources/extensions/browser-tools/state.ts +13 -0
- package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +2 -2
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +37 -0
- package/src/resources/extensions/browser-tools/tests/web-app-detect.test.mjs +68 -0
- package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
- package/src/resources/extensions/browser-tools/utils.ts +3 -3
- package/src/resources/extensions/browser-tools/web-app-detect.ts +63 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/loop.ts +4 -2
- package/src/resources/extensions/gsd/auto/phases.ts +89 -15
- package/src/resources/extensions/gsd/auto/session.ts +24 -1
- package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +117 -12
- package/src/resources/extensions/gsd/auto-model-selection.ts +190 -12
- package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +25 -22
- package/src/resources/extensions/gsd/auto-recovery.ts +22 -3
- package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
- package/src/resources/extensions/gsd/auto-start.ts +1 -1
- package/src/resources/extensions/gsd/auto-timers.ts +25 -9
- package/src/resources/extensions/gsd/auto.ts +41 -14
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +250 -78
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
- package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -17
- package/src/resources/extensions/gsd/commands-maintenance.ts +124 -13
- package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
- package/src/resources/extensions/gsd/config-overlay.ts +1 -0
- package/src/resources/extensions/gsd/context-masker.ts +152 -5
- package/src/resources/extensions/gsd/db-writer.ts +38 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +50 -1
- package/src/resources/extensions/gsd/gsd-db.ts +564 -186
- package/src/resources/extensions/gsd/guided-flow.ts +4 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +44 -66
- package/src/resources/extensions/gsd/md-importer.ts +49 -2
- package/src/resources/extensions/gsd/migration-auto-check.ts +154 -34
- package/src/resources/extensions/gsd/parsers-legacy.ts +20 -0
- package/src/resources/extensions/gsd/planner-handoff.ts +149 -0
- package/src/resources/extensions/gsd/planning-path-scope.ts +22 -4
- package/src/resources/extensions/gsd/pre-execution-checks.ts +9 -2
- package/src/resources/extensions/gsd/preferences-models.ts +113 -43
- package/src/resources/extensions/gsd/preferences-types.ts +47 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +76 -2
- package/src/resources/extensions/gsd/preferences.ts +5 -0
- package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- 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/run-uat.md +2 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +6 -1
- package/src/resources/extensions/gsd/safety/content-validator.ts +8 -5
- package/src/resources/extensions/gsd/skill-manifest.ts +12 -0
- package/src/resources/extensions/gsd/source-observations.ts +402 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +20 -8
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +44 -5
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +39 -11
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +45 -15
- package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +4 -4
- package/src/resources/extensions/gsd/state.ts +7 -4
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +66 -4
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +299 -1
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +75 -3
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -0
- package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/closeout-wizard.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +118 -0
- package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/context-masker.test.ts +56 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +17 -2
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +1 -11
- package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +62 -1
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +99 -2
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/planner-handoff.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +133 -0
- package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +101 -1
- package/src/resources/extensions/gsd/tests/repository-registry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +162 -18
- package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/source-observations.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -21
- package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/uat-policy.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +306 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +77 -10
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +260 -5
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
- package/src/resources/extensions/gsd/tool-contract.ts +29 -1
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +41 -6
- package/src/resources/extensions/gsd/tools/complete-slice.ts +29 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +54 -12
- package/src/resources/extensions/gsd/tools/plan-task.ts +8 -1
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +71 -489
- package/src/resources/extensions/gsd/types.ts +1 -0
- package/src/resources/extensions/gsd/uat-policy.ts +191 -0
- package/src/resources/extensions/gsd/uat-run.ts +550 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +3 -4
- package/src/resources/extensions/gsd/unit-tool-contracts.ts +38 -14
- package/src/resources/extensions/gsd/verdict-parser.ts +3 -10
- package/src/resources/extensions/gsd/workflow-manifest.ts +193 -7
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -3
- package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +32 -0
- package/src/resources/extensions/gsd/worktree-reentry.ts +103 -0
- package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
- package/src/resources/extensions/shared/gsd-browser-cli.ts +6 -0
- package/src/resources/extensions/shared/tests/format-utils.test.ts +8 -3
- package/src/resources/extensions/subagent/agents.ts +4 -0
- package/src/resources/extensions/subagent/index.ts +28 -3
- package/src/resources/extensions/subagent/launch.ts +8 -0
- package/src/resources/extensions/subagent/tests/model-override.test.ts +31 -0
- /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Active-unit source observations for provider payload context.
|
|
3
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
4
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { extractPlanningPathReference, normalizeFilePath } from "./pre-execution-checks.js";
|
|
6
|
+
export const WHOLE_FILE_OBSERVATION_MAX_BYTES = 50 * 1024;
|
|
7
|
+
export const WHOLE_FILE_OBSERVATION_MAX_LINES = 2000;
|
|
8
|
+
const SOURCE_CONTEXT_TITLE = "## Source Context Block";
|
|
9
|
+
const SOURCE_OBSERVATION_UNIT_TYPE = "execute-task";
|
|
10
|
+
export function supportsSourceObservationsForUnit(unitType) {
|
|
11
|
+
return unitType === SOURCE_OBSERVATION_UNIT_TYPE;
|
|
12
|
+
}
|
|
13
|
+
export class SourceObservationStore {
|
|
14
|
+
active = null;
|
|
15
|
+
beginUnit(unit) {
|
|
16
|
+
if (!supportsSourceObservationsForUnit(unit.unitType)) {
|
|
17
|
+
this.clear();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (this.matches(unit))
|
|
21
|
+
return;
|
|
22
|
+
this.active = { unit: { ...unit }, observations: new Map() };
|
|
23
|
+
}
|
|
24
|
+
clear() {
|
|
25
|
+
this.active = null;
|
|
26
|
+
}
|
|
27
|
+
degradeUnit(unit) {
|
|
28
|
+
if (!this.active)
|
|
29
|
+
return;
|
|
30
|
+
const current = this.active.unit;
|
|
31
|
+
if (current.unitType === unit.unitType &&
|
|
32
|
+
current.unitId === unit.unitId &&
|
|
33
|
+
current.startedAt === unit.startedAt) {
|
|
34
|
+
this.active = null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
observePlanTask(task) {
|
|
38
|
+
if (!this.active)
|
|
39
|
+
return;
|
|
40
|
+
for (const entry of planDeclaredSourceEntries(task)) {
|
|
41
|
+
this.observePath(entry.path, "plan");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
observeRead(input) {
|
|
45
|
+
if (!this.active)
|
|
46
|
+
return;
|
|
47
|
+
const rawPath = readPathFromInput(input);
|
|
48
|
+
if (!rawPath.trim())
|
|
49
|
+
return;
|
|
50
|
+
this.observePath(rawPath, "read");
|
|
51
|
+
}
|
|
52
|
+
observeMutation(input) {
|
|
53
|
+
if (!this.active)
|
|
54
|
+
return;
|
|
55
|
+
const rawPath = readPathFromInput(input);
|
|
56
|
+
if (!rawPath.trim())
|
|
57
|
+
return;
|
|
58
|
+
this.observePath(rawPath, "mutation", { replaceExisting: true });
|
|
59
|
+
}
|
|
60
|
+
renderActiveBlock() {
|
|
61
|
+
if (!this.active || this.active.observations.size === 0)
|
|
62
|
+
return null;
|
|
63
|
+
if (!supportsSourceObservationsForUnit(this.active.unit.unitType))
|
|
64
|
+
return null;
|
|
65
|
+
const observations = [...this.active.observations.values()]
|
|
66
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
67
|
+
const lines = [
|
|
68
|
+
SOURCE_CONTEXT_TITLE,
|
|
69
|
+
`Active Unit: ${this.active.unit.unitType} ${this.active.unit.unitId}`,
|
|
70
|
+
"",
|
|
71
|
+
"The files below are protected active-Unit source context. " +
|
|
72
|
+
"Use this block instead of rereading small files just to recover line windows.",
|
|
73
|
+
];
|
|
74
|
+
const wholeFiles = observations.filter((observation) => observation.status === "whole");
|
|
75
|
+
const unavailable = observations.filter((observation) => observation.status !== "whole");
|
|
76
|
+
if (wholeFiles.length > 0) {
|
|
77
|
+
lines.push("", "### Whole-File Observations");
|
|
78
|
+
for (const observation of wholeFiles) {
|
|
79
|
+
lines.push("", `#### ${observation.path}`, `Status: whole-file (${observation.lines ?? 0} lines, ${formatSize(observation.bytes ?? 0)})`, fencedSource(observation.text ?? ""));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (unavailable.length > 0) {
|
|
83
|
+
lines.push("", "### Unavailable Source Observations");
|
|
84
|
+
for (const observation of unavailable) {
|
|
85
|
+
const detail = observation.reason ? ` - ${observation.reason}` : "";
|
|
86
|
+
lines.push(`- ${observation.path}: ${observation.status}${detail}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return lines.join("\n");
|
|
90
|
+
}
|
|
91
|
+
matches(unit) {
|
|
92
|
+
if (!this.active)
|
|
93
|
+
return false;
|
|
94
|
+
const current = this.active.unit;
|
|
95
|
+
return current.unitType === unit.unitType &&
|
|
96
|
+
current.unitId === unit.unitId &&
|
|
97
|
+
current.startedAt === unit.startedAt &&
|
|
98
|
+
current.basePath === unit.basePath;
|
|
99
|
+
}
|
|
100
|
+
observePath(rawPath, source, options = {}) {
|
|
101
|
+
if (!this.active)
|
|
102
|
+
return;
|
|
103
|
+
const observation = observeSourcePath(this.active.unit.basePath, rawPath, source);
|
|
104
|
+
const key = observation.absolutePath ?? `${observation.status}:${observation.path}`;
|
|
105
|
+
const existing = this.active.observations.get(key);
|
|
106
|
+
if (options.replaceExisting || !existing || observation.status === "whole" || existing.status !== "whole") {
|
|
107
|
+
this.active.observations.set(key, observation);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export function planDeclaredSourceEntries(task) {
|
|
112
|
+
const entries = [];
|
|
113
|
+
for (const file of task.files) {
|
|
114
|
+
const path = extractPlanningPathReference(file) ?? file.trim();
|
|
115
|
+
if (path)
|
|
116
|
+
entries.push({ path, field: "files" });
|
|
117
|
+
}
|
|
118
|
+
for (const input of task.inputs) {
|
|
119
|
+
const path = extractPlanningPathReference(input);
|
|
120
|
+
if (path)
|
|
121
|
+
entries.push({ path, field: "inputs" });
|
|
122
|
+
}
|
|
123
|
+
return entries;
|
|
124
|
+
}
|
|
125
|
+
export function observeSourcePath(basePath, rawPath, source) {
|
|
126
|
+
const normalizedRaw = normalizeFilePath(rawPath.trim());
|
|
127
|
+
const displayPath = normalizedRaw || rawPath.trim();
|
|
128
|
+
if (!normalizedRaw) {
|
|
129
|
+
return unavailable(displayPath || "<empty>", null, "unresolved selector", source);
|
|
130
|
+
}
|
|
131
|
+
if (containsGlobPattern(normalizedRaw)) {
|
|
132
|
+
return unavailable(displayPath, null, "glob", source, "glob selectors are not whole files");
|
|
133
|
+
}
|
|
134
|
+
if (rawPath.trim().endsWith("/")) {
|
|
135
|
+
return unavailable(displayPath, null, "directory", source, "directory selectors are not whole files");
|
|
136
|
+
}
|
|
137
|
+
const rootPath = resolve(basePath);
|
|
138
|
+
const absolutePath = isAbsolute(normalizedRaw) ? resolve(normalizedRaw) : resolve(rootPath, normalizedRaw);
|
|
139
|
+
const path = formatObservationPath(rootPath, absolutePath, displayPath);
|
|
140
|
+
if (!isPathInsideRoot(rootPath, absolutePath)) {
|
|
141
|
+
return unavailable(displayPath, absolutePath, "unresolved selector", source, "path is outside active Unit root");
|
|
142
|
+
}
|
|
143
|
+
if (!existsSync(absolutePath)) {
|
|
144
|
+
return unavailable(path, absolutePath, "missing", source, "file does not exist in the active Unit root");
|
|
145
|
+
}
|
|
146
|
+
let stat;
|
|
147
|
+
try {
|
|
148
|
+
stat = statSync(absolutePath);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return unavailable(path, absolutePath, "missing", source, "file could not be inspected");
|
|
152
|
+
}
|
|
153
|
+
if (stat.isDirectory()) {
|
|
154
|
+
return unavailable(path, absolutePath, "directory", source, "directory selectors are not whole files");
|
|
155
|
+
}
|
|
156
|
+
if (!stat.isFile()) {
|
|
157
|
+
return unavailable(path, absolutePath, "unresolved selector", source, "path is not a regular file");
|
|
158
|
+
}
|
|
159
|
+
if (stat.size > WHOLE_FILE_OBSERVATION_MAX_BYTES) {
|
|
160
|
+
return unavailable(path, absolutePath, "over-threshold", source, `${formatSize(stat.size)} exceeds ${formatSize(WHOLE_FILE_OBSERVATION_MAX_BYTES)}`);
|
|
161
|
+
}
|
|
162
|
+
let buffer;
|
|
163
|
+
try {
|
|
164
|
+
buffer = readFileSync(absolutePath);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return unavailable(path, absolutePath, "missing", source, "file could not be read");
|
|
168
|
+
}
|
|
169
|
+
if (isBinaryOrImage(buffer)) {
|
|
170
|
+
return unavailable(path, absolutePath, "binary/image", source, "binary or image files are not inlined as source text");
|
|
171
|
+
}
|
|
172
|
+
const text = buffer.toString("utf8");
|
|
173
|
+
const lines = countLines(text);
|
|
174
|
+
if (lines > WHOLE_FILE_OBSERVATION_MAX_LINES) {
|
|
175
|
+
return unavailable(path, absolutePath, "over-threshold", source, `${lines} lines exceeds ${WHOLE_FILE_OBSERVATION_MAX_LINES}`);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
path,
|
|
179
|
+
absolutePath,
|
|
180
|
+
status: "whole",
|
|
181
|
+
source,
|
|
182
|
+
text,
|
|
183
|
+
bytes: buffer.byteLength,
|
|
184
|
+
lines,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export function injectSourceContextBlockIntoPayload(payload, block) {
|
|
188
|
+
const messages = payload.messages;
|
|
189
|
+
if (Array.isArray(messages)) {
|
|
190
|
+
return {
|
|
191
|
+
...payload,
|
|
192
|
+
messages: [
|
|
193
|
+
...withoutExistingSourceContextMessages(messages),
|
|
194
|
+
{ role: "user", content: [{ type: "text", text: block }] },
|
|
195
|
+
],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const input = payload.input;
|
|
199
|
+
if (Array.isArray(input)) {
|
|
200
|
+
return {
|
|
201
|
+
...payload,
|
|
202
|
+
input: [
|
|
203
|
+
...withoutExistingSourceContextItems(input),
|
|
204
|
+
{ role: "user", content: [{ type: "input_text", text: block }] },
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return payload;
|
|
209
|
+
}
|
|
210
|
+
function unavailable(path, absolutePath, status, source, reason) {
|
|
211
|
+
return { path, absolutePath, status, source, reason };
|
|
212
|
+
}
|
|
213
|
+
function formatObservationPath(basePath, absolutePath, fallback) {
|
|
214
|
+
const rel = relative(basePath, absolutePath);
|
|
215
|
+
if (rel && !rel.startsWith("..") && !isAbsolute(rel)) {
|
|
216
|
+
return rel.split(sep).join("/");
|
|
217
|
+
}
|
|
218
|
+
return fallback;
|
|
219
|
+
}
|
|
220
|
+
function isPathInsideRoot(rootPath, absolutePath) {
|
|
221
|
+
const rel = relative(rootPath, absolutePath);
|
|
222
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
223
|
+
}
|
|
224
|
+
function containsGlobPattern(candidate) {
|
|
225
|
+
return ["*", "?", "[", "]", "{", "}"].some((char) => candidate.includes(char));
|
|
226
|
+
}
|
|
227
|
+
function readPathFromInput(input) {
|
|
228
|
+
if (typeof input.path === "string")
|
|
229
|
+
return input.path;
|
|
230
|
+
if (typeof input.file_path === "string")
|
|
231
|
+
return input.file_path;
|
|
232
|
+
return "";
|
|
233
|
+
}
|
|
234
|
+
function formatSize(bytes) {
|
|
235
|
+
if (bytes < 1024)
|
|
236
|
+
return `${bytes}B`;
|
|
237
|
+
if (bytes < 1024 * 1024)
|
|
238
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
239
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
240
|
+
}
|
|
241
|
+
function isBinaryOrImage(buffer) {
|
|
242
|
+
if (buffer.includes(0))
|
|
243
|
+
return true;
|
|
244
|
+
if (startsWith(buffer, [0xff, 0xd8, 0xff]))
|
|
245
|
+
return true;
|
|
246
|
+
if (startsWith(buffer, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))
|
|
247
|
+
return true;
|
|
248
|
+
if (startsWithAscii(buffer, "GIF"))
|
|
249
|
+
return true;
|
|
250
|
+
if (startsWithAscii(buffer, "RIFF") &&
|
|
251
|
+
buffer.length >= 12 &&
|
|
252
|
+
buffer.subarray(8, 12).toString("ascii") === "WEBP")
|
|
253
|
+
return true;
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
function startsWith(buffer, signature) {
|
|
257
|
+
if (buffer.length < signature.length)
|
|
258
|
+
return false;
|
|
259
|
+
return signature.every((byte, index) => buffer[index] === byte);
|
|
260
|
+
}
|
|
261
|
+
function startsWithAscii(buffer, text) {
|
|
262
|
+
return buffer.length >= text.length && buffer.subarray(0, text.length).toString("ascii") === text;
|
|
263
|
+
}
|
|
264
|
+
function countLines(text) {
|
|
265
|
+
if (text.length === 0)
|
|
266
|
+
return 0;
|
|
267
|
+
return text.endsWith("\n") ? text.split("\n").length - 1 : text.split("\n").length;
|
|
268
|
+
}
|
|
269
|
+
function fencedSource(text) {
|
|
270
|
+
const longest = longestBacktickRun(text);
|
|
271
|
+
const fence = longest >= 3 ? "`".repeat(longest + 1) : "```";
|
|
272
|
+
return `${fence}\n${text}\n${fence}`;
|
|
273
|
+
}
|
|
274
|
+
function longestBacktickRun(text) {
|
|
275
|
+
let longest = 0;
|
|
276
|
+
let current = 0;
|
|
277
|
+
for (const char of text) {
|
|
278
|
+
if (char === "`") {
|
|
279
|
+
current += 1;
|
|
280
|
+
longest = Math.max(longest, current);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
current = 0;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return longest;
|
|
287
|
+
}
|
|
288
|
+
function firstText(content) {
|
|
289
|
+
if (typeof content === "string")
|
|
290
|
+
return content;
|
|
291
|
+
if (!Array.isArray(content))
|
|
292
|
+
return null;
|
|
293
|
+
const block = content.find((entry) => entry && typeof entry === "object" && "text" in entry);
|
|
294
|
+
return typeof block?.text === "string" ? block.text : null;
|
|
295
|
+
}
|
|
296
|
+
function isSourceContextMessage(message) {
|
|
297
|
+
if (!message || typeof message !== "object")
|
|
298
|
+
return false;
|
|
299
|
+
return firstText(message.content)?.startsWith(SOURCE_CONTEXT_TITLE) === true;
|
|
300
|
+
}
|
|
301
|
+
function withoutExistingSourceContextMessages(messages) {
|
|
302
|
+
return messages.filter((message) => !isSourceContextMessage(message));
|
|
303
|
+
}
|
|
304
|
+
function withoutExistingSourceContextItems(items) {
|
|
305
|
+
return items.filter((item) => !isSourceContextMessage(item));
|
|
306
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// SUMMARY.md mtime — deterministic and idempotent (re-running yields the
|
|
7
7
|
// same value).
|
|
8
8
|
import { existsSync, statSync } from "node:fs";
|
|
9
|
-
import { getMilestone, getMilestoneSlices, getSliceTasks, isDbAvailable, updateMilestoneStatus, updateSliceStatus, updateTaskStatus, } from "../../gsd-db.js";
|
|
9
|
+
import { getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, isDbAvailable, updateMilestoneStatus, updateSliceStatus, updateTaskStatus, } from "../../gsd-db.js";
|
|
10
10
|
import { resolveMilestoneFile, resolveSliceFile, resolveTaskFile, } from "../../paths.js";
|
|
11
11
|
const COMPLETE_STATUSES = new Set(["complete", "done"]);
|
|
12
12
|
function summaryMtimeIso(path) {
|
|
@@ -17,16 +17,24 @@ function summaryMtimeIso(path) {
|
|
|
17
17
|
return null;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
export function detectMissingCompletionTimestampDrift(
|
|
20
|
+
export function detectMissingCompletionTimestampDrift(_state, ctx) {
|
|
21
21
|
if (!isDbAvailable())
|
|
22
22
|
return [];
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
// Scan every milestone, not just the active one. Markdown artifacts exist on
|
|
24
|
+
// disk for all milestones, so a user can manually complete a queued/parked
|
|
25
|
+
// milestone (edit the roadmap + drop a SUMMARY) and leave completed_at=null
|
|
26
|
+
// in the DB. Gating on the active milestone left that drift unrepaired until
|
|
27
|
+
// the milestone happened to become active.
|
|
28
|
+
const drifts = [];
|
|
29
|
+
for (const { id: mid } of getAllMilestones()) {
|
|
30
|
+
collectMilestoneCompletionDrift(mid, ctx, drifts);
|
|
31
|
+
}
|
|
32
|
+
return drifts;
|
|
33
|
+
}
|
|
34
|
+
function collectMilestoneCompletionDrift(mid, ctx, drifts) {
|
|
26
35
|
const milestone = getMilestone(mid);
|
|
27
36
|
if (!milestone)
|
|
28
|
-
return
|
|
29
|
-
const drifts = [];
|
|
37
|
+
return;
|
|
30
38
|
// Milestone-level
|
|
31
39
|
if (COMPLETE_STATUSES.has(milestone.status) &&
|
|
32
40
|
milestone.completed_at === null) {
|
|
@@ -69,7 +77,6 @@ export function detectMissingCompletionTimestampDrift(state, ctx) {
|
|
|
69
77
|
}
|
|
70
78
|
}
|
|
71
79
|
}
|
|
72
|
-
return drifts;
|
|
73
80
|
}
|
|
74
81
|
export function repairMissingCompletionTimestamp(record, ctx) {
|
|
75
82
|
const composite = record.ids[0];
|
|
@@ -40,6 +40,18 @@ function isRepairableStaleRenderReason(reason) {
|
|
|
40
40
|
(reason.includes("SUMMARY.md missing") && /^S\d+/.test(reason)) ||
|
|
41
41
|
reason.includes("UAT.md missing"));
|
|
42
42
|
}
|
|
43
|
+
function canonicalizeMilestoneId(dirSegment) {
|
|
44
|
+
if (getMilestone(dirSegment))
|
|
45
|
+
return dirSegment;
|
|
46
|
+
const suffixId = dirSegment.match(/^(M\d+-[a-z0-9]{6})(?:$|-)/)?.[1];
|
|
47
|
+
if (suffixId && getMilestone(suffixId))
|
|
48
|
+
return suffixId;
|
|
49
|
+
// Descriptor layout: e.g. M001-DESCRIPTOR → M001
|
|
50
|
+
const baseId = dirSegment.match(/^(M\d+)(?:$|-)/i)?.[1];
|
|
51
|
+
if (baseId && getMilestone(baseId))
|
|
52
|
+
return baseId;
|
|
53
|
+
return suffixId ?? baseId ?? dirSegment;
|
|
54
|
+
}
|
|
43
55
|
function resolveRoadmapMilestoneIdFromPath(normPath) {
|
|
44
56
|
const milestoneMatch = normPath.match(/milestones\/([^/]+)\//);
|
|
45
57
|
if (!milestoneMatch) {
|
|
@@ -70,7 +82,12 @@ async function repairStaleRenderFromBasePath(record, basePath) {
|
|
|
70
82
|
if (!pathMatch) {
|
|
71
83
|
throw new Error(`stale-render drift: plan path missing milestone/slice segments: ${record.renderPath}`);
|
|
72
84
|
}
|
|
73
|
-
|
|
85
|
+
const milestoneId = canonicalizeMilestoneId(pathMatch[1]);
|
|
86
|
+
const wrote = await renderPlanCheckboxes(basePath, milestoneId, pathMatch[2], record.renderPath);
|
|
87
|
+
if (!wrote) {
|
|
88
|
+
throw new Error(`stale-render drift: plan re-render wrote nothing for ${milestoneId}/${pathMatch[2]} ` +
|
|
89
|
+
`(${record.renderPath}); slice has no tasks or its path is unresolvable`);
|
|
90
|
+
}
|
|
74
91
|
return;
|
|
75
92
|
}
|
|
76
93
|
if (reason.includes("SUMMARY.md missing") && /^T\d+/.test(reason)) {
|
|
@@ -79,7 +96,13 @@ async function repairStaleRenderFromBasePath(record, basePath) {
|
|
|
79
96
|
if (!pathMatch || !taskMatch) {
|
|
80
97
|
throw new Error(`stale-render drift: task summary path/reason malformed: ${record.renderPath} reason=${reason}`);
|
|
81
98
|
}
|
|
82
|
-
|
|
99
|
+
const milestoneId = canonicalizeMilestoneId(pathMatch[1]);
|
|
100
|
+
const wrote = await renderTaskSummary(basePath, milestoneId, pathMatch[2], taskMatch[1]);
|
|
101
|
+
if (!wrote) {
|
|
102
|
+
throw new Error(`stale-render drift: task summary re-render wrote nothing for ` +
|
|
103
|
+
`${milestoneId}/${pathMatch[2]}/${taskMatch[1]} (${record.renderPath}); ` +
|
|
104
|
+
`task has no summary in DB or its slice path is unresolvable`);
|
|
105
|
+
}
|
|
83
106
|
return;
|
|
84
107
|
}
|
|
85
108
|
if (reason.includes("SUMMARY.md missing") && /^S\d+/.test(reason)) {
|
|
@@ -87,7 +110,7 @@ async function repairStaleRenderFromBasePath(record, basePath) {
|
|
|
87
110
|
if (!pathMatch) {
|
|
88
111
|
throw new Error(`stale-render drift: slice summary path missing milestone/slice segments: ${record.renderPath}`);
|
|
89
112
|
}
|
|
90
|
-
const milestoneId = pathMatch[1];
|
|
113
|
+
const milestoneId = canonicalizeMilestoneId(pathMatch[1]);
|
|
91
114
|
const sliceId = pathMatch[2];
|
|
92
115
|
const slice = getSlice(milestoneId, sliceId);
|
|
93
116
|
const uatPath = join(dirname(record.renderPath), buildSliceFileName(sliceId, "UAT"));
|
|
@@ -95,7 +118,12 @@ async function repairStaleRenderFromBasePath(record, basePath) {
|
|
|
95
118
|
if (slice?.full_uat_md && !existsSync(uatPath)) {
|
|
96
119
|
setSliceSummaryMd(milestoneId, sliceId, slice.full_summary_md ?? "", "");
|
|
97
120
|
}
|
|
98
|
-
await renderSliceSummary(basePath, milestoneId, sliceId);
|
|
121
|
+
const wrote = await renderSliceSummary(basePath, milestoneId, sliceId);
|
|
122
|
+
if (!wrote) {
|
|
123
|
+
throw new Error(`stale-render drift: slice summary re-render wrote nothing for ` +
|
|
124
|
+
`${milestoneId}/${sliceId} (${record.renderPath}); slice has no summary/UAT ` +
|
|
125
|
+
`in DB or its path is unresolvable`);
|
|
126
|
+
}
|
|
99
127
|
return;
|
|
100
128
|
}
|
|
101
129
|
if (reason.includes("UAT.md missing")) {
|
|
@@ -105,7 +133,7 @@ async function repairStaleRenderFromBasePath(record, basePath) {
|
|
|
105
133
|
}
|
|
106
134
|
// When UAT.md is removed from disk, mirror that intent by clearing stale
|
|
107
135
|
// persisted UAT content instead of rehydrating it back onto disk.
|
|
108
|
-
const milestoneId = pathMatch[1];
|
|
136
|
+
const milestoneId = canonicalizeMilestoneId(pathMatch[1]);
|
|
109
137
|
const sliceId = pathMatch[2];
|
|
110
138
|
const slice = getSlice(milestoneId, sliceId);
|
|
111
139
|
if (!slice) {
|
|
@@ -4,26 +4,47 @@
|
|
|
4
4
|
// laptop sleep where the heartbeat wasn't released cleanly), and clears them
|
|
5
5
|
// before the next dispatch attempts to acquire the lock.
|
|
6
6
|
import { effectiveLockFile, isSessionLockProcessAlive, readSessionLockData, removeStaleSessionLock, } from "../../session-lock.js";
|
|
7
|
+
import { clearStaleWorkerLock } from "../../crash-recovery.js";
|
|
8
|
+
import { findStaleWorkerForProject } from "../../db/auto-workers.js";
|
|
9
|
+
import { isDbAvailable } from "../../gsd-db.js";
|
|
10
|
+
import { normalizeRealPath } from "../../paths.js";
|
|
11
|
+
import { logWarning } from "../../workflow-logger.js";
|
|
7
12
|
export function detectStaleWorkerDrift(_state, ctx) {
|
|
8
13
|
const data = readSessionLockData(ctx.basePath);
|
|
9
|
-
if (
|
|
10
|
-
return
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
if (data && typeof data.pid === "number") {
|
|
15
|
+
return isSessionLockProcessAlive(data)
|
|
16
|
+
? []
|
|
17
|
+
: [{ kind: "stale-worker", lockPath: effectiveLockFile(), pid: data.pid }];
|
|
18
|
+
}
|
|
19
|
+
// The lock file is missing or unparseable. It is not the only source of
|
|
20
|
+
// truth: a crashed worker can leave a workers row 'active' with held leases
|
|
21
|
+
// and in-flight dispatches even when its lock file is gone. Fall back to the
|
|
22
|
+
// DB worker registry so that state is still detected and repaired.
|
|
23
|
+
if (isDbAvailable()) {
|
|
24
|
+
try {
|
|
25
|
+
const stale = findStaleWorkerForProject(normalizeRealPath(ctx.basePath));
|
|
26
|
+
if (stale && typeof stale.pid === "number") {
|
|
27
|
+
return [{ kind: "stale-worker", lockPath: effectiveLockFile(), pid: stale.pid }];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
// Best-effort: detection must never throw and abort the reconcile cycle.
|
|
32
|
+
logWarning("reconcile", `stale-worker DB fallback detection failed: ${err.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
22
36
|
}
|
|
23
37
|
export function repairStaleWorker(_record, ctx) {
|
|
24
38
|
// removeStaleSessionLock is idempotent: it re-reads lock state and is a
|
|
25
39
|
// no-op when the lock is held by an alive process. Safe under cap=2 retry.
|
|
26
40
|
removeStaleSessionLock(ctx.basePath);
|
|
41
|
+
// Removing the lock file alone leaves the DB-side worker state dangling: the
|
|
42
|
+
// dead worker's milestone_leases stay 'held' and its unit_dispatches stay
|
|
43
|
+
// 'running'/'claimed', blocking new claims until the lease TTL expires.
|
|
44
|
+
// clearStaleWorkerLock cancels those dispatches, releases the leases, and
|
|
45
|
+
// marks the worker stopping — the same cleanup the startup crash-recovery
|
|
46
|
+
// path performs. It is DB-gated, idempotent, and best-effort.
|
|
47
|
+
clearStaleWorkerLock(ctx.basePath);
|
|
27
48
|
}
|
|
28
49
|
export const staleWorkerHandler = {
|
|
29
50
|
kind: "stale-worker",
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// reconcileBeforeDispatch runs before every Dispatch decision and worker spawn.
|
|
4
4
|
import { deriveState as defaultDeriveState, invalidateStateCache as defaultInvalidate, } from "../state.js";
|
|
5
5
|
import { clearParseCache as defaultClearParseCache } from "../files.js";
|
|
6
|
+
import { clearPathCache } from "../paths.js";
|
|
7
|
+
import { logWarning } from "../workflow-logger.js";
|
|
6
8
|
import { ReconciliationFailedError, } from "./errors.js";
|
|
7
9
|
import { DRIFT_REGISTRY } from "./registry.js";
|
|
8
10
|
export { ReconciliationFailedError } from "./errors.js";
|
|
@@ -35,17 +37,23 @@ export async function reconcileBeforeDispatch(basePath, deps = defaultDeps) {
|
|
|
35
37
|
deps.invalidateStateCache();
|
|
36
38
|
const stateSnapshot = await deps.deriveState(basePath, deps.deriveStateOptions);
|
|
37
39
|
const ctx = { basePath, state: stateSnapshot };
|
|
38
|
-
const
|
|
40
|
+
const detection = await detectAllDrift(stateSnapshot, ctx, registry);
|
|
41
|
+
const drift = detection.records;
|
|
39
42
|
if (drift.length === 0) {
|
|
40
43
|
return {
|
|
41
44
|
ok: true,
|
|
42
45
|
stateSnapshot,
|
|
43
46
|
repaired,
|
|
44
|
-
blockers:
|
|
47
|
+
blockers: [
|
|
48
|
+
...new Set([
|
|
49
|
+
...(stateSnapshot.blockers ?? []),
|
|
50
|
+
...detection.detectBlockers,
|
|
51
|
+
]),
|
|
52
|
+
],
|
|
45
53
|
};
|
|
46
54
|
}
|
|
47
55
|
const failures = [];
|
|
48
|
-
const blockers = [];
|
|
56
|
+
const blockers = [...detection.detectBlockers];
|
|
49
57
|
let repairedThisPass = false;
|
|
50
58
|
for (const record of drift) {
|
|
51
59
|
const handler = registry.find((h) => h.kind === record.kind);
|
|
@@ -71,7 +79,11 @@ export async function reconcileBeforeDispatch(basePath, deps = defaultDeps) {
|
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
81
|
if (repairedThisPass) {
|
|
82
|
+
// A repair may have mutated on-disk structure (e.g. quarantined a slice
|
|
83
|
+
// dir). Clear both the parse cache and the path/dir cache centrally so
|
|
84
|
+
// later passes and any subsequent repair see fresh filesystem state.
|
|
74
85
|
clearParseCache();
|
|
86
|
+
clearPathCache();
|
|
75
87
|
}
|
|
76
88
|
if (blockers.length > 0) {
|
|
77
89
|
let blockerState = stateSnapshot;
|
|
@@ -95,9 +107,10 @@ export async function reconcileBeforeDispatch(basePath, deps = defaultDeps) {
|
|
|
95
107
|
deps.invalidateStateCache();
|
|
96
108
|
const finalState = await deps.deriveState(basePath, deps.deriveStateOptions);
|
|
97
109
|
const finalCtx = { basePath, state: finalState };
|
|
98
|
-
const
|
|
110
|
+
const finalDetection = await detectAllDrift(finalState, finalCtx, registry);
|
|
111
|
+
const persistent = finalDetection.records;
|
|
99
112
|
if (persistent.length > 0) {
|
|
100
|
-
const blockers = [];
|
|
113
|
+
const blockers = [...finalDetection.detectBlockers];
|
|
101
114
|
const unblockedPersistent = [];
|
|
102
115
|
for (const record of persistent) {
|
|
103
116
|
const handler = registry.find((h) => h.kind === record.kind);
|
|
@@ -123,24 +136,36 @@ export async function reconcileBeforeDispatch(basePath, deps = defaultDeps) {
|
|
|
123
136
|
ok: true,
|
|
124
137
|
stateSnapshot: finalState,
|
|
125
138
|
repaired,
|
|
126
|
-
blockers:
|
|
139
|
+
blockers: [
|
|
140
|
+
...new Set([
|
|
141
|
+
...(finalState.blockers ?? []),
|
|
142
|
+
...finalDetection.detectBlockers,
|
|
143
|
+
]),
|
|
144
|
+
],
|
|
127
145
|
};
|
|
128
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Run every detector. A single detector throwing (e.g. a transient file read
|
|
149
|
+
* error) must NOT abort the whole cycle and hide every later handler's drift —
|
|
150
|
+
* it is collected as a blocker so dispatch is still gated, while the remaining
|
|
151
|
+
* detectors run and their drift gets repaired (graceful degradation, ADR-017).
|
|
152
|
+
*/
|
|
129
153
|
async function detectAllDrift(state, ctx,
|
|
130
154
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
131
|
-
registry
|
|
132
|
-
const
|
|
155
|
+
registry) {
|
|
156
|
+
const records = [];
|
|
157
|
+
const detectBlockers = [];
|
|
133
158
|
for (const handler of registry) {
|
|
134
159
|
try {
|
|
135
160
|
const detected = await handler.detect(state, ctx);
|
|
136
|
-
|
|
161
|
+
records.push(...detected);
|
|
137
162
|
}
|
|
138
163
|
catch (cause) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
164
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
165
|
+
const blocker = `Drift detection failed for "${handler.kind}": ${message}`;
|
|
166
|
+
logWarning("reconcile", blocker);
|
|
167
|
+
detectBlockers.push(blocker);
|
|
143
168
|
}
|
|
144
169
|
}
|
|
145
|
-
return
|
|
170
|
+
return { records, detectBlockers };
|
|
146
171
|
}
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
// startSliceParallel) share one contract.
|
|
8
8
|
import { reconcileBeforeDispatch, ReconciliationFailedError, } from "./index.js";
|
|
9
9
|
/**
|
|
10
|
-
* Run reconciliation before spawning workers. Returns ok=true when the run
|
|
11
|
-
* completed without throwing
|
|
12
|
-
*
|
|
13
|
-
* ReconciliationFailedError, returns ok=false with the error message so the
|
|
10
|
+
* Run reconciliation before spawning workers. Returns ok=true only when the run
|
|
11
|
+
* completed without throwing AND surfaced no blockers; any blocker fails the
|
|
12
|
+
* gate (ok=false, reason carries the first blocker) so callers must not spawn.
|
|
13
|
+
* On ReconciliationFailedError, returns ok=false with the error message so the
|
|
14
14
|
* caller can surface it to the user without re-throwing.
|
|
15
15
|
*
|
|
16
16
|
* Other unexpected errors propagate; they are not part of the drift
|
|
@@ -394,6 +394,8 @@ async function buildRegistryAndFindActive(basePath, milestones, completeMileston
|
|
|
394
394
|
}
|
|
395
395
|
const allSlicesDone = slices.length > 0 && slices.every(s => isStatusDone(s.status));
|
|
396
396
|
const title = stripMilestonePrefix(m.title) || m.id;
|
|
397
|
+
const hasContext = !!resolveMilestoneFile(basePath, m.id, "CONTEXT");
|
|
398
|
+
const hasDraftContext = !hasContext && !!resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
|
|
397
399
|
if (!activeMilestoneFound) {
|
|
398
400
|
const deps = m.depends_on;
|
|
399
401
|
const depsUnmet = deps.some(dep => !completeMilestoneIds.has(dep));
|
|
@@ -401,9 +403,9 @@ async function buildRegistryAndFindActive(basePath, milestones, completeMileston
|
|
|
401
403
|
registry.push({ id: m.id, title, status: 'pending', dependsOn: deps });
|
|
402
404
|
continue;
|
|
403
405
|
}
|
|
404
|
-
if (m.status === 'queued' && slices.length === 0) {
|
|
406
|
+
if (m.status === 'queued' && slices.length === 0 && !hasContext) {
|
|
405
407
|
if (!firstDeferredQueuedShell) {
|
|
406
|
-
firstDeferredQueuedShell = { id: m.id, title, deps };
|
|
408
|
+
firstDeferredQueuedShell = { id: m.id, title, deps, hasDraftContext };
|
|
407
409
|
}
|
|
408
410
|
registry.push({ id: m.id, title, status: 'pending', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
409
411
|
continue;
|
|
@@ -415,7 +417,7 @@ async function buildRegistryAndFindActive(basePath, milestones, completeMileston
|
|
|
415
417
|
registry.push({ id: m.id, title, status: 'active', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
416
418
|
continue;
|
|
417
419
|
}
|
|
418
|
-
if (m.status === 'needs-discussion')
|
|
420
|
+
if ((m.status === 'needs-discussion' && !hasContext) || hasDraftContext)
|
|
419
421
|
activeMilestoneHasDraft = true;
|
|
420
422
|
activeMilestone = { id: m.id, title };
|
|
421
423
|
activeMilestoneSlices = slices;
|
|
@@ -432,6 +434,8 @@ async function buildRegistryAndFindActive(basePath, milestones, completeMileston
|
|
|
432
434
|
activeMilestone = { id: shell.id, title: shell.title };
|
|
433
435
|
activeMilestoneSlices = [];
|
|
434
436
|
activeMilestoneFound = true;
|
|
437
|
+
if (shell.hasDraftContext)
|
|
438
|
+
activeMilestoneHasDraft = true;
|
|
435
439
|
const entry = registry.find(e => e.id === shell.id);
|
|
436
440
|
if (entry)
|
|
437
441
|
entry.status = 'active';
|