gsd-pi 2.82.0-dev.c22380fc3 → 2.82.0-dev.dfbc5f58f
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 +4 -3
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +10 -1
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +2 -1
- package/dist/resources/extensions/cmux/index.js +5 -0
- package/dist/resources/extensions/gsd/auto/infra-errors.js +9 -3
- package/dist/resources/extensions/gsd/auto/loop.js +5 -5
- package/dist/resources/extensions/gsd/auto/orchestrator.js +11 -0
- package/dist/resources/extensions/gsd/auto/phases.js +8 -1
- package/dist/resources/extensions/gsd/auto/workflow-memory-pressure.js +12 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +13 -6
- package/dist/resources/extensions/gsd/auto-model-selection.js +2 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +233 -127
- package/dist/resources/extensions/gsd/auto-prompts.js +2 -2
- package/dist/resources/extensions/gsd/auto-recovery.js +31 -1
- package/dist/resources/extensions/gsd/auto-start.js +85 -12
- package/dist/resources/extensions/gsd/auto-verification.js +28 -22
- package/dist/resources/extensions/gsd/auto-worktree.js +111 -1
- package/dist/resources/extensions/gsd/auto.js +30 -3
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +4 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +9 -8
- package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +21 -9
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -2
- package/dist/resources/extensions/gsd/clean-root-preflight.js +170 -8
- package/dist/resources/extensions/gsd/commands/catalog.js +4 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +37 -0
- package/dist/resources/extensions/gsd/commands-bootstrap.js +5 -0
- package/dist/resources/extensions/gsd/crash-recovery.js +31 -5
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +3 -2
- package/dist/resources/extensions/gsd/dispatch-guard.js +2 -2
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +28 -11
- package/dist/resources/extensions/gsd/doctor.js +2 -28
- package/dist/resources/extensions/gsd/export-html.js +27 -425
- package/dist/resources/extensions/gsd/git-service.js +39 -1
- package/dist/resources/extensions/gsd/gsd-db.js +1 -0
- package/dist/resources/extensions/gsd/guided-flow.js +6 -0
- package/dist/resources/extensions/gsd/migrate/parsers.js +10 -0
- package/dist/resources/extensions/gsd/milestone-actions.js +11 -4
- package/dist/resources/extensions/gsd/native-git-bridge.js +48 -12
- package/dist/resources/extensions/gsd/post-execution-checks.js +73 -2
- package/dist/resources/extensions/gsd/pre-execution-checks.js +28 -1
- package/dist/resources/extensions/gsd/prompt-loader.js +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +3 -3
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/merge-state.js +6 -1
- package/dist/resources/extensions/gsd/status-guards.js +4 -0
- package/dist/resources/extensions/gsd/templates/plan.md +8 -5
- package/dist/resources/extensions/gsd/templates/task-plan.md +4 -2
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -8
- package/dist/resources/extensions/gsd/tools/complete-slice.js +6 -8
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +7 -1
- package/dist/resources/extensions/gsd/tools/plan-slice.js +89 -14
- package/dist/resources/extensions/gsd/unit-context-manifest.js +32 -10
- package/dist/resources/extensions/gsd/validation.js +23 -1
- package/dist/resources/extensions/gsd/verification-gate.js +68 -7
- package/dist/resources/extensions/gsd/verification-verdict.js +26 -0
- package/dist/resources/extensions/gsd/workflow-projections.js +6 -8
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +33 -8
- package/dist/resources/extensions/shared/html-shell.js +388 -0
- package/dist/resources/extensions/subagent/index.js +448 -78
- package/dist/resources/extensions/subagent/launch.js +77 -0
- package/dist/resources/extensions/subagent/run-store.js +148 -0
- package/dist/resources/extensions/visual-brief/artifact-policy.js +29 -0
- package/dist/resources/extensions/visual-brief/extension-manifest.json +8 -0
- package/dist/resources/extensions/visual-brief/index.js +5 -0
- package/dist/resources/extensions/visual-brief/page-contract.js +124 -0
- package/dist/resources/extensions/visual-brief/prompts.js +140 -0
- 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 +12 -12
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +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/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -7
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -7
- 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 +4 -5
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -5
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -7
- 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 +4 -7
- 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 +4 -5
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -5
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
- package/dist/web/standalone/.next/server/chunks/4266.js +2 -0
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +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/.next/static/chunks/2973.33f26573894b6153.js +2 -0
- package/dist/web/standalone/.next/static/chunks/{8359.e059d86b255fce1c.js → 8359.7eb3bb8f8ecf4c01.js} +2 -2
- package/dist/web/standalone/.next/static/chunks/app/layout-8c10ec293ae0f1d5.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-de742b64187e13fe.js → webpack-9a4db269f9ed63ad.js} +1 -1
- package/dist/web/standalone/.next/static/css/746ee28c929d1880.css +1 -0
- package/package.json +4 -4
- package/packages/mcp-server/src/workflow-tools.test.ts +1 -1
- package/packages/native/tsconfig.json +2 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +82 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +52 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/simple-options.d.ts +2 -4
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +5 -6
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/simple-options.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/simple-options.test.js +50 -0
- package/packages/pi-ai/dist/providers/simple-options.test.js.map +1 -0
- package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +63 -0
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +91 -1
- package/packages/pi-ai/src/providers/simple-options.test.ts +60 -0
- package/packages/pi-ai/src/providers/simple-options.ts +5 -6
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.js +66 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-thinking-level.test.ts +79 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/GSD-WORKFLOW.md +10 -1
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +2 -1
- package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +19 -2
- package/src/resources/extensions/cmux/index.ts +6 -0
- package/src/resources/extensions/gsd/auto/contracts.ts +14 -6
- package/src/resources/extensions/gsd/auto/infra-errors.ts +9 -3
- package/src/resources/extensions/gsd/auto/loop.ts +8 -5
- package/src/resources/extensions/gsd/auto/orchestrator.ts +11 -0
- package/src/resources/extensions/gsd/auto/phases.ts +7 -1
- package/src/resources/extensions/gsd/auto/workflow-memory-pressure.ts +13 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +14 -6
- package/src/resources/extensions/gsd/auto-model-selection.ts +2 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +266 -139
- package/src/resources/extensions/gsd/auto-prompts.ts +2 -2
- package/src/resources/extensions/gsd/auto-recovery.ts +29 -0
- package/src/resources/extensions/gsd/auto-start.ts +92 -9
- package/src/resources/extensions/gsd/auto-verification.ts +36 -34
- package/src/resources/extensions/gsd/auto-worktree.ts +119 -1
- package/src/resources/extensions/gsd/auto.ts +32 -3
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +6 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +9 -8
- package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +19 -7
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +19 -3
- package/src/resources/extensions/gsd/clean-root-preflight.ts +174 -8
- package/src/resources/extensions/gsd/commands/catalog.ts +4 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +40 -0
- package/src/resources/extensions/gsd/commands-bootstrap.ts +10 -0
- package/src/resources/extensions/gsd/crash-recovery.ts +30 -4
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +4 -3
- package/src/resources/extensions/gsd/dispatch-guard.ts +2 -2
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +25 -13
- package/src/resources/extensions/gsd/doctor.ts +2 -27
- package/src/resources/extensions/gsd/export-html.ts +27 -427
- package/src/resources/extensions/gsd/git-service.ts +45 -1
- package/src/resources/extensions/gsd/gsd-db.ts +3 -0
- package/src/resources/extensions/gsd/guided-flow.ts +6 -0
- package/src/resources/extensions/gsd/migrate/parsers.ts +11 -0
- package/src/resources/extensions/gsd/milestone-actions.ts +10 -4
- package/src/resources/extensions/gsd/native-git-bridge.ts +54 -12
- package/src/resources/extensions/gsd/post-execution-checks.ts +87 -2
- package/src/resources/extensions/gsd/pre-execution-checks.ts +32 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/merge-state.ts +8 -1
- package/src/resources/extensions/gsd/status-guards.ts +5 -0
- package/src/resources/extensions/gsd/templates/plan.md +8 -5
- package/src/resources/extensions/gsd/templates/task-plan.md +4 -2
- package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +80 -1
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +12 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +15 -1
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +69 -1
- package/src/resources/extensions/gsd/tests/brief-command.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +107 -2
- package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -2
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +59 -2
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/export-html-enhancements.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/guided-flow.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/hook-model-resolution.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/infra-error.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +103 -1
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +121 -1
- package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +55 -1
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +225 -1
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +79 -1
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +86 -0
- package/src/resources/extensions/gsd/tests/post-unit-git-failure.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/prompt-loader.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +46 -2
- package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +64 -1
- package/src/resources/extensions/gsd/tests/summary-render-parity.test.ts +7 -3
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +86 -7
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +110 -1
- package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/workflow-memory-pressure.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +64 -12
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +54 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +8 -10
- package/src/resources/extensions/gsd/tools/complete-slice.ts +6 -8
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +5 -1
- package/src/resources/extensions/gsd/tools/plan-slice.ts +98 -12
- package/src/resources/extensions/gsd/types.ts +1 -1
- package/src/resources/extensions/gsd/unit-context-manifest.ts +47 -11
- package/src/resources/extensions/gsd/validation.ts +23 -1
- package/src/resources/extensions/gsd/verification-gate.ts +78 -6
- package/src/resources/extensions/gsd/verification-verdict.ts +47 -0
- package/src/resources/extensions/gsd/workflow-projections.ts +6 -8
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +41 -8
- package/src/resources/extensions/shared/html-shell.ts +412 -0
- package/src/resources/extensions/subagent/index.ts +567 -103
- package/src/resources/extensions/subagent/launch.ts +131 -0
- package/src/resources/extensions/subagent/run-store.ts +218 -0
- package/src/resources/extensions/subagent/tests/launch.test.ts +115 -0
- package/src/resources/extensions/subagent/tests/run-store.test.ts +111 -0
- package/src/resources/extensions/visual-brief/artifact-policy.ts +41 -0
- package/src/resources/extensions/visual-brief/extension-manifest.json +8 -0
- package/src/resources/extensions/visual-brief/index.ts +8 -0
- package/src/resources/extensions/visual-brief/page-contract.ts +136 -0
- package/src/resources/extensions/visual-brief/prompts.ts +183 -0
- package/src/resources/extensions/visual-brief/tests/visual-brief.test.ts +212 -0
- package/dist/web/standalone/.next/server/chunks/5822.js +0 -2
- package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +0 -1
- package/dist/web/standalone/.next/static/css/54ec2745c1da488b.css +0 -1
- package/dist/web/standalone/.next/static/css/de70bee13400563f.css +0 -1
- package/dist/web/standalone/.next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/747892c23ea88013-s.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/8d697b304b401681-s.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
- /package/dist/web/standalone/.next/static/{Wop3A7KRGyR06H3rla_1- → q0WYuDVbHeFFYbdd-fei2}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{Wop3A7KRGyR06H3rla_1- → q0WYuDVbHeFFYbdd-fei2}/_ssgManifest.js +0 -0
|
@@ -14,6 +14,8 @@ import { isInfrastructureError } from "./auto/infra-errors.js";
|
|
|
14
14
|
// Issue #453: keep auto-mode bookkeeping on the stable git CLI path unless a
|
|
15
15
|
// caller explicitly opts into the native helper.
|
|
16
16
|
const NATIVE_GSD_GIT_ENABLED = process.env.GSD_ENABLE_NATIVE_GSD_GIT === "1";
|
|
17
|
+
const TRANSIENT_GIT_RETRY_CODES = new Set(["ENOBUFS", "EAGAIN"]);
|
|
18
|
+
const GIT_RETRY_DELAY_MS = 200;
|
|
17
19
|
// ─── Native Module Loading ──────────────────────────────────────────────────
|
|
18
20
|
let nativeModule = null;
|
|
19
21
|
let loadAttempted = false;
|
|
@@ -46,10 +48,42 @@ function gitExec(basePath, args, allowFailure = false) {
|
|
|
46
48
|
env: GIT_NO_PROMPT_ENV,
|
|
47
49
|
}).trim();
|
|
48
50
|
}
|
|
49
|
-
catch {
|
|
51
|
+
catch (err) {
|
|
50
52
|
if (allowFailure)
|
|
51
53
|
return "";
|
|
52
|
-
throw new GSDError(GSD_GIT_ERROR, `git ${args.join(" ")} failed in ${basePath}`);
|
|
54
|
+
throw new GSDError(GSD_GIT_ERROR, `git ${args.join(" ")} failed in ${basePath}: ${getErrorMessage(err)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** sleepSync uses Atomics.wait for a blocking pause without busy-waiting; it blocks the current thread and requires Atomics.wait support. */
|
|
58
|
+
function sleepSync(ms) {
|
|
59
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
60
|
+
}
|
|
61
|
+
function isRetryableGitError(err) {
|
|
62
|
+
const code = isInfrastructureError(err)
|
|
63
|
+
?? isInfrastructureError(err?.stderr ?? "");
|
|
64
|
+
return code !== null && TRANSIENT_GIT_RETRY_CODES.has(code);
|
|
65
|
+
}
|
|
66
|
+
function execGitFileSyncWithRetry(basePath, args, options) {
|
|
67
|
+
try {
|
|
68
|
+
return execFileSync("git", args, {
|
|
69
|
+
cwd: basePath,
|
|
70
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
71
|
+
encoding: "utf-8",
|
|
72
|
+
env: GIT_NO_PROMPT_ENV,
|
|
73
|
+
...options,
|
|
74
|
+
}).trim();
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
if (!isRetryableGitError(err))
|
|
78
|
+
throw err;
|
|
79
|
+
sleepSync(GIT_RETRY_DELAY_MS);
|
|
80
|
+
return execFileSync("git", args, {
|
|
81
|
+
cwd: basePath,
|
|
82
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
83
|
+
encoding: "utf-8",
|
|
84
|
+
env: GIT_NO_PROMPT_ENV,
|
|
85
|
+
...options,
|
|
86
|
+
}).trim();
|
|
53
87
|
}
|
|
54
88
|
}
|
|
55
89
|
/** Run a git command via execFileSync. Returns trimmed stdout. */
|
|
@@ -62,10 +96,10 @@ function gitFileExec(basePath, args, allowFailure = false) {
|
|
|
62
96
|
env: GIT_NO_PROMPT_ENV,
|
|
63
97
|
}).trim();
|
|
64
98
|
}
|
|
65
|
-
catch {
|
|
99
|
+
catch (err) {
|
|
66
100
|
if (allowFailure)
|
|
67
101
|
return "";
|
|
68
|
-
throw new GSDError(GSD_GIT_ERROR, `git ${args.join(" ")} failed in ${basePath}`);
|
|
102
|
+
throw new GSDError(GSD_GIT_ERROR, `git ${args.join(" ")} failed in ${basePath}: ${getErrorMessage(err)}`);
|
|
69
103
|
}
|
|
70
104
|
}
|
|
71
105
|
// ─── Existing Read Functions ──────────────────────────────────────────────
|
|
@@ -303,15 +337,20 @@ export function nativeDiffNameStatus(basePath, fromRef, toRef, pathspec, useMerg
|
|
|
303
337
|
}
|
|
304
338
|
/**
|
|
305
339
|
* Get numstat diff between two refs.
|
|
340
|
+
* useMergeBase: if true, uses three-dot semantics.
|
|
306
341
|
* Native: libgit2 patch line stats.
|
|
307
342
|
* Fallback: `git diff --numstat`.
|
|
308
343
|
*/
|
|
309
|
-
export function nativeDiffNumstat(basePath, fromRef, toRef) {
|
|
344
|
+
export function nativeDiffNumstat(basePath, fromRef, toRef, useMergeBase) {
|
|
310
345
|
const native = loadNative();
|
|
311
|
-
if (native) {
|
|
346
|
+
if (native && !useMergeBase) {
|
|
312
347
|
return native.gitDiffNumstat(basePath, fromRef, toRef);
|
|
313
348
|
}
|
|
314
|
-
const
|
|
349
|
+
const refspec = useMergeBase ? `${fromRef}...${toRef}` : undefined;
|
|
350
|
+
const args = refspec
|
|
351
|
+
? ["diff", "--numstat", refspec]
|
|
352
|
+
: ["diff", "--numstat", fromRef, toRef];
|
|
353
|
+
const result = gitExec(basePath, args, true);
|
|
315
354
|
if (!result)
|
|
316
355
|
return [];
|
|
317
356
|
return result.split("\n").filter(Boolean).map(line => {
|
|
@@ -777,13 +816,10 @@ export function nativeCommit(basePath, message, options) {
|
|
|
777
816
|
const args = ["commit", "-F", "-"];
|
|
778
817
|
if (options?.allowEmpty)
|
|
779
818
|
args.push("--allow-empty");
|
|
780
|
-
const result =
|
|
781
|
-
cwd: basePath,
|
|
819
|
+
const result = execGitFileSyncWithRetry(basePath, args, {
|
|
782
820
|
stdio: ["pipe", "pipe", "pipe"],
|
|
783
|
-
encoding: "utf-8",
|
|
784
|
-
env: GIT_NO_PROMPT_ENV,
|
|
785
821
|
input: message,
|
|
786
|
-
})
|
|
822
|
+
});
|
|
787
823
|
return result;
|
|
788
824
|
}
|
|
789
825
|
catch (err) {
|
|
@@ -17,6 +17,43 @@
|
|
|
17
17
|
import { existsSync, readFileSync } from "node:fs";
|
|
18
18
|
import { resolve, dirname, extname } from "node:path";
|
|
19
19
|
// ─── Import Resolution Check ─────────────────────────────────────────────────
|
|
20
|
+
/**
|
|
21
|
+
* Replace the contents of single- and double-quoted string literals on a single
|
|
22
|
+
* source line with spaces so import patterns do not match text inside strings.
|
|
23
|
+
* Template-literal spans are handled separately via the inTemplateLiteral flag.
|
|
24
|
+
*/
|
|
25
|
+
function stripStringLiterals(line) {
|
|
26
|
+
let result = "";
|
|
27
|
+
let i = 0;
|
|
28
|
+
while (i < line.length) {
|
|
29
|
+
const ch = line[i];
|
|
30
|
+
if (ch === '"' || ch === "'") {
|
|
31
|
+
result += ch;
|
|
32
|
+
i++;
|
|
33
|
+
while (i < line.length) {
|
|
34
|
+
const c = line[i];
|
|
35
|
+
if (c === "\\" && i + 1 < line.length) {
|
|
36
|
+
result += " ";
|
|
37
|
+
i += 2;
|
|
38
|
+
}
|
|
39
|
+
else if (c === ch) {
|
|
40
|
+
result += ch;
|
|
41
|
+
i++;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
result += " ";
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
result += ch;
|
|
52
|
+
i++;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
20
57
|
/**
|
|
21
58
|
* Extract relative import paths from TypeScript/JavaScript source code.
|
|
22
59
|
* Returns array of { importPath, lineNum } for relative imports.
|
|
@@ -30,11 +67,19 @@ export function extractRelativeImports(source) {
|
|
|
30
67
|
// import './path'
|
|
31
68
|
// require('./path')
|
|
32
69
|
// require("../path")
|
|
33
|
-
const importPattern = /(
|
|
70
|
+
const importPattern = /(?:^|[;{}]\s*)import\s+(?:.*?\s+from\s+)?(['"])(\.\.?\/[^'"]+)\1/g;
|
|
71
|
+
const requirePattern = /require\s*\(\s*(['"])(\.\.?\/[^'"]+)\1/g;
|
|
34
72
|
// Track if we're inside a block comment
|
|
35
73
|
let inBlockComment = false;
|
|
74
|
+
let inTemplateLiteral = false;
|
|
36
75
|
for (let i = 0; i < lines.length; i++) {
|
|
37
76
|
const line = lines[i];
|
|
77
|
+
if (inTemplateLiteral) {
|
|
78
|
+
if ((line.match(/(?<!\\)`/g) ?? []).length % 2 === 1) {
|
|
79
|
+
inTemplateLiteral = false;
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
38
83
|
// Handle block comment boundaries
|
|
39
84
|
if (inBlockComment) {
|
|
40
85
|
if (line.includes("*/")) {
|
|
@@ -61,9 +106,17 @@ export function extractRelativeImports(source) {
|
|
|
61
106
|
let match;
|
|
62
107
|
// Reset lastIndex for each line
|
|
63
108
|
importPattern.lastIndex = 0;
|
|
109
|
+
requirePattern.lastIndex = 0;
|
|
110
|
+
const strippedLine = stripStringLiterals(line);
|
|
64
111
|
while ((match = importPattern.exec(line)) !== null) {
|
|
112
|
+
const importOffset = match[0].indexOf("import");
|
|
113
|
+
const importStart = match.index + importOffset;
|
|
114
|
+
if (strippedLine.slice(importStart, importStart + "import".length) !==
|
|
115
|
+
"import") {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
65
118
|
// Check if this match is after a // comment marker on the same line
|
|
66
|
-
const beforeMatch =
|
|
119
|
+
const beforeMatch = strippedLine.substring(0, match.index);
|
|
67
120
|
if (beforeMatch.includes("//")) {
|
|
68
121
|
continue;
|
|
69
122
|
}
|
|
@@ -72,6 +125,24 @@ export function extractRelativeImports(source) {
|
|
|
72
125
|
lineNum: i + 1,
|
|
73
126
|
});
|
|
74
127
|
}
|
|
128
|
+
while ((match = requirePattern.exec(line)) !== null) {
|
|
129
|
+
if (strippedLine.slice(match.index, match.index + "require".length) !==
|
|
130
|
+
"require") {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
// Check if this match is after a // comment marker on the same line
|
|
134
|
+
const beforeMatch = strippedLine.substring(0, match.index);
|
|
135
|
+
if (beforeMatch.includes("//")) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
imports.push({
|
|
139
|
+
importPath: match[2],
|
|
140
|
+
lineNum: i + 1,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if ((strippedLine.match(/(?<!\\)`/g) ?? []).length % 2 === 1) {
|
|
144
|
+
inTemplateLiteral = true;
|
|
145
|
+
}
|
|
75
146
|
}
|
|
76
147
|
return imports;
|
|
77
148
|
}
|
|
@@ -19,7 +19,33 @@ import { existsSync } from "node:fs";
|
|
|
19
19
|
import { spawn } from "node:child_process";
|
|
20
20
|
import { homedir } from "node:os";
|
|
21
21
|
import { resolve } from "node:path";
|
|
22
|
+
import { validateVerificationCommand } from "./verification-gate.js";
|
|
22
23
|
const NPM_COMMAND = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
24
|
+
export function checkVerificationCommands(tasks) {
|
|
25
|
+
const results = [];
|
|
26
|
+
for (const task of tasks) {
|
|
27
|
+
const verify = task.verify.trim();
|
|
28
|
+
if (!verify)
|
|
29
|
+
continue;
|
|
30
|
+
const commands = verify
|
|
31
|
+
.split("&&")
|
|
32
|
+
.map((command) => command.trim())
|
|
33
|
+
.filter(Boolean);
|
|
34
|
+
for (const command of commands) {
|
|
35
|
+
const validation = validateVerificationCommand(command);
|
|
36
|
+
if (!validation.ok) {
|
|
37
|
+
results.push({
|
|
38
|
+
category: "tool",
|
|
39
|
+
target: `${task.id} Verify`,
|
|
40
|
+
passed: false,
|
|
41
|
+
message: `Unsafe or non-runnable Verify command: ${command} (${validation.reason})`,
|
|
42
|
+
blocking: true,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return results;
|
|
48
|
+
}
|
|
23
49
|
// ─── Package Existence Check ─────────────────────────────────────────────────
|
|
24
50
|
/**
|
|
25
51
|
* Extract npm package names from task descriptions.
|
|
@@ -647,7 +673,8 @@ export async function runPreExecutionChecks(tasks, basePath) {
|
|
|
647
673
|
const fileChecks = checkFilePathConsistency(tasks, basePath);
|
|
648
674
|
const orderingChecks = checkTaskOrdering(tasks, basePath);
|
|
649
675
|
const contractChecks = checkInterfaceContracts(tasks, basePath);
|
|
650
|
-
|
|
676
|
+
const verificationChecks = checkVerificationCommands(tasks);
|
|
677
|
+
allChecks.push(...fileChecks, ...orderingChecks, ...contractChecks, ...verificationChecks);
|
|
651
678
|
// Run async package checks
|
|
652
679
|
const packageChecks = await checkPackageExistence(tasks, basePath);
|
|
653
680
|
allChecks.push(...packageChecks);
|
|
@@ -181,7 +181,7 @@ export function loadPrompt(name, vars = {}) {
|
|
|
181
181
|
.map(m => m.slice(2, -2))
|
|
182
182
|
.filter(key => !(key in effectiveVars));
|
|
183
183
|
if (missing.length > 0) {
|
|
184
|
-
throw new GSDError(GSD_PARSE_ERROR, `loadPrompt("${name}"): template declares {{${missing.join("}}, {{")}}}
|
|
184
|
+
throw new GSDError(GSD_PARSE_ERROR, `loadPrompt("${name}"): template declares {{${missing.join("}}, {{")}}} but no value was provided. ` +
|
|
185
185
|
`This usually means the extension code in memory is older than the template on disk. ` +
|
|
186
186
|
`Restart pi to reload the extension.`);
|
|
187
187
|
}
|
|
@@ -26,7 +26,7 @@ Before planning, validate roadmap assumptions against code and dependency summar
|
|
|
26
26
|
|
|
27
27
|
{{sourceFilePaths}}
|
|
28
28
|
|
|
29
|
-
If slice research is inlined, trust
|
|
29
|
+
If slice research is inlined, trust its architectural findings, but verify every concrete file path you place in task `inputs` or `expectedOutput` against the current tree or prior/same-task outputs. Explore enough code to confirm paths, boundaries, and verification. Executors later get only task plans, slice excerpt, and prior summaries, so put required paths, steps, inputs, and outputs in task plans.
|
|
30
30
|
|
|
31
31
|
{{executorContextConstraints}}
|
|
32
32
|
|
|
@@ -39,8 +39,8 @@ If slice research is inlined, trust it. Explore enough code to confirm paths, bo
|
|
|
39
39
|
5. Define slice verification before tasks. Non-trivial slices need real tests or executable assertions; boundary contracts need contract-exercising checks. Tests must not read .gitignore/gitignored paths such as `.gsd/`, `.planning/`, or `.audits/`.
|
|
40
40
|
6. Include Threat Surface (Q3), Requirement Impact (Q4), proof level, observability, integration closure, Failure Modes (Q5), Load Profile (Q6), and Negative Tests (Q7) only where applicable.
|
|
41
41
|
7. Right-size tasks. Simple slices can be one task; split only when context, ownership, or verification boundaries justify it.
|
|
42
|
-
8. Each task needs
|
|
43
|
-
9. Persist with `gsd_plan_slice` using goal
|
|
42
|
+
8. Each task needs the exact `gsd_plan_slice.tasks[]` shape: `taskId`, `title`, `description`, `estimate`, `files`, `verify`, `inputs`, `expectedOutput`, and optional `observabilityImpact`. `description` should contain the Why / Do / Done-when narrative. `files`, `inputs`, and `expectedOutput` must be JSON arrays of strings, even when there is only one path (for example, `"inputs": ["src/index.ts"]`, never `"inputs": "src/index.ts"`). Use paths relative to `{{workingDirectory}}`; do not put absolute paths to the original checkout or any directory outside `{{workingDirectory}}` in `files`, `inputs`, `expectedOutput`, or verification commands. **`expectedOutput` must only list files the task actually creates or overwrites on disk.** Do NOT include files the task merely reads, verifies, or tests — those belong only in `inputs`. If a task is a pure verification or test task that produces no new files, `expectedOutput` may be `[]` or limited to test-result artifacts (e.g. a log or assertion output). A file that does not yet exist on disk and is needed as an `input` must be produced by an earlier task's `expectedOutput` — if no prior task creates it, add a task before this one that does.
|
|
43
|
+
9. Persist with `gsd_plan_slice` using `milestoneId`, `sliceId`, `goal`, optional `successCriteria`/`proofLevel`/`integrationClosure`/`observabilityImpact`, and `tasks`. `gsd_plan_slice` handles task persistence transactionally and renders `{{outputPath}}` plus task plans; do not call `gsd_plan_task`. The DB-backed tool is the canonical write path. Do **not** rely on direct `PLAN.md` writes as the source of truth.
|
|
44
44
|
10. Self-audit before finishing: goal/demo closure, requirement coverage, locked decisions, concrete paths, dependency order, wiring, scope size, proof truthfulness, feature completeness, and quality gates. Quality gates: non-trivial slices/tasks include specific Q3-Q7 coverage where applicable.
|
|
45
45
|
11. If planning creates structural decisions, append them to `.gsd/DECISIONS.md`.
|
|
46
46
|
12. {{commitInstruction}}
|
|
@@ -64,7 +64,7 @@ Then:
|
|
|
64
64
|
2. {{skillActivation}} Record the installed skills you expect executors to use in each task plan's `skills_used` frontmatter.
|
|
65
65
|
3. Define slice-level verification: the objective stopping condition. Plan real test files with real assertions; for simple slices, executable commands are fine.
|
|
66
66
|
4. For non-trivial slices, plan observability / proof level / integration closure, threat surface, and requirement impact. Omit entirely for simple slices.
|
|
67
|
-
5. Decompose the slice into tasks that fit one context window each. Every task must
|
|
67
|
+
5. Decompose the slice into tasks that fit one context window each. Every task passed to `gsd_plan_slice` must use the exact keys `taskId`, `title`, `description`, `estimate`, `files`, `verify`, `inputs`, `expectedOutput`, and optional `observabilityImpact`. Put Why / Do / Done-when detail in `description`. `files`, `inputs`, and `expectedOutput` must be JSON arrays of strings, even for one path (for example, `"expectedOutput": ["src/index.ts"]`, never `"expectedOutput": "src/index.ts"`).
|
|
68
68
|
6. **Persist planning state through `gsd_plan_slice`.** Call it with the full payload. The tool writes to the DB and renders `{{outputPath}}` and `{{slicePath}}/tasks/T##-PLAN.md` automatically. Do NOT rely on direct `PLAN.md` writes.
|
|
69
69
|
7. **Self-audit the plan.** If every task were completed exactly as written, the slice goal/demo should be true. Every must-have maps to a task. Inputs and Expected Output are backtick-wrapped file paths.
|
|
70
70
|
8. If refinement produced structural decisions that diverge from the sketch, append them to `.gsd/DECISIONS.md`.
|
|
@@ -10,6 +10,7 @@ import { isAbsolute, join, resolve } from "node:path";
|
|
|
10
10
|
import { getErrorMessage } from "../../error-utils.js";
|
|
11
11
|
import { nativeAddPaths, nativeCheckoutTheirs, nativeCommit, nativeConflictFiles, nativeMergeAbort, nativeRebaseAbort, nativeResetHard, } from "../../native-git-bridge.js";
|
|
12
12
|
import { logError, logWarning } from "../../workflow-logger.js";
|
|
13
|
+
import { isGsdWorktreePath } from "../../worktree-root.js";
|
|
13
14
|
const SILENT_NOTIFY = () => { };
|
|
14
15
|
function resolveGitDir(basePath) {
|
|
15
16
|
try {
|
|
@@ -23,7 +24,11 @@ function resolveGitDir(basePath) {
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
catch (err) {
|
|
26
|
-
|
|
27
|
+
const message = getErrorMessage(err);
|
|
28
|
+
logWarning("recovery", `gitdir resolution failed: ${message}`);
|
|
29
|
+
if (isGsdWorktreePath(basePath)) {
|
|
30
|
+
throw new Error(`Worktree integrity failure: ${basePath} is not a valid git worktree (git rev-parse failed: ${message.split("\n")[0]}). Repair or recreate the worktree before retrying.`);
|
|
31
|
+
}
|
|
27
32
|
}
|
|
28
33
|
return join(basePath, ".git");
|
|
29
34
|
}
|
|
@@ -22,3 +22,7 @@ export function isDeferredStatus(status) {
|
|
|
22
22
|
export function isInactiveStatus(status) {
|
|
23
23
|
return isClosedStatus(status) || isDeferredStatus(status);
|
|
24
24
|
}
|
|
25
|
+
/** Returns true when a prior milestone should not block dispatch ordering. */
|
|
26
|
+
export function isSkippedForDispatch(status) {
|
|
27
|
+
return isClosedStatus(status) || status === "parked" || isDeferredStatus(status);
|
|
28
|
+
}
|
|
@@ -99,11 +99,11 @@
|
|
|
99
99
|
- "Improve UI"
|
|
100
100
|
|
|
101
101
|
Each task should usually include:
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
102
|
+
- description: why this task exists, concrete steps, and done-when acceptance
|
|
103
|
+
- files: JSON array of likely touched paths
|
|
104
|
+
- verify: the command, test, or runtime check that proves it worked
|
|
105
|
+
- inputs: JSON array of existing paths or prior task outputs this task consumes
|
|
106
|
+
- expectedOutput: JSON array of paths this task creates or overwrites
|
|
107
107
|
|
|
108
108
|
Keep the checkbox line format exactly:
|
|
109
109
|
- [ ] **T01: Title** `est:30m`
|
|
@@ -131,10 +131,13 @@
|
|
|
131
131
|
|
|
132
132
|
Verify field rules:
|
|
133
133
|
- MUST be a mechanically executable command: `npm test`, `grep -q "pattern" file`, `test -f path`
|
|
134
|
+
- MUST NOT use shell pipes, redirects, semicolons, backticks, command substitution, or output trimming
|
|
134
135
|
- For content/document tasks: verify file existence, section count, YAML validity, or word count
|
|
135
136
|
NOT exact phrasing, specific formulas, or "zero TBD" aspirational criteria
|
|
136
137
|
- If no command can verify the output, write: "Manual review — file exists and is non-empty"
|
|
138
|
+
- BAD: `python3 -m pytest tests/ -q --tb=short 2>&1 | tail -5`
|
|
137
139
|
- BAD: "Sections 3.1 and 3.2 exist with exact formulas. Zero TBD/TODO."
|
|
140
|
+
- GOOD: `python3 -m pytest tests/ -q --tb=short`
|
|
138
141
|
- GOOD: `grep -c "^## " doc.md` returns >= 4 (4+ sections), `! grep -q "TBD\|TODO" doc.md`
|
|
139
142
|
|
|
140
143
|
Integration closure rule:
|
|
@@ -72,7 +72,8 @@ skills_used:
|
|
|
72
72
|
<!-- Every input MUST be a backtick-wrapped file path. These paths are machine-parsed to
|
|
73
73
|
derive task dependencies — vague descriptions without paths break dependency detection.
|
|
74
74
|
For the first task in a slice with no prior task outputs, list the existing source files
|
|
75
|
-
this task reads or modifies.
|
|
75
|
+
this task reads or modifies.
|
|
76
|
+
Tool field: inputs must be an array of strings, e.g. ["src/index.ts"], never a single string. -->
|
|
76
77
|
|
|
77
78
|
- `{{filePath}}` — {{whatThisTaskNeedsFromPriorWork}}
|
|
78
79
|
|
|
@@ -82,6 +83,7 @@ skills_used:
|
|
|
82
83
|
or modifies. These paths are machine-parsed to derive task dependencies.
|
|
83
84
|
This task should produce a real increment toward making the slice goal/demo true. A full
|
|
84
85
|
slice plan should not be able to mark every task complete while the claimed slice behavior
|
|
85
|
-
still does not work at the stated proof level.
|
|
86
|
+
still does not work at the stated proof level.
|
|
87
|
+
Tool field: expectedOutput must be an array of strings, e.g. ["src/index.ts"], never a single string. -->
|
|
86
88
|
|
|
87
89
|
- `{{filePath}}` — {{whatThisTaskCreatesOrModifies}}
|
|
@@ -24,11 +24,11 @@ function renderMilestoneSummaryMarkdown(params, completedAt) {
|
|
|
24
24
|
const keyFiles = params.keyFiles ?? [];
|
|
25
25
|
const lessonsLearned = params.lessonsLearned ?? [];
|
|
26
26
|
const keyDecisionsYaml = keyDecisions.length > 0
|
|
27
|
-
? keyDecisions.map(d => ` - ${d}`).join("\n")
|
|
28
|
-
: "
|
|
27
|
+
? `\n${keyDecisions.map(d => ` - ${d}`).join("\n")}`
|
|
28
|
+
: " []";
|
|
29
29
|
const keyFilesYaml = keyFiles.length > 0
|
|
30
|
-
? keyFiles.map(f => ` - ${f}`).join("\n")
|
|
31
|
-
: "
|
|
30
|
+
? `\n${keyFiles.map(f => ` - ${f}`).join("\n")}`
|
|
31
|
+
: " []";
|
|
32
32
|
const lessonsYaml = lessonsLearned.length > 0
|
|
33
33
|
? lessonsLearned.map(l => ` - ${l}`).join("\n")
|
|
34
34
|
: " - (none)";
|
|
@@ -37,10 +37,8 @@ id: ${params.milestoneId}
|
|
|
37
37
|
title: "${displayTitle}"
|
|
38
38
|
status: complete
|
|
39
39
|
completed_at: ${completedAt}
|
|
40
|
-
key_decisions
|
|
41
|
-
|
|
42
|
-
key_files:
|
|
43
|
-
${keyFilesYaml}
|
|
40
|
+
key_decisions:${keyDecisionsYaml}
|
|
41
|
+
key_files:${keyFilesYaml}
|
|
44
42
|
lessons_learned:
|
|
45
43
|
${lessonsYaml}
|
|
46
44
|
---
|
|
@@ -65,11 +65,11 @@ function renderSliceSummaryMarkdown(params) {
|
|
|
65
65
|
? affects.map(a => ` - ${a}`).join("\n")
|
|
66
66
|
: " []";
|
|
67
67
|
const keyFilesYaml = keyFiles.length > 0
|
|
68
|
-
? keyFiles.map(f => ` - ${f}`).join("\n")
|
|
69
|
-
: "
|
|
68
|
+
? `\n${keyFiles.map(f => ` - ${f}`).join("\n")}`
|
|
69
|
+
: " []";
|
|
70
70
|
const keyDecisionsYaml = keyDecisions.length > 0
|
|
71
|
-
? keyDecisions.map(d => ` - ${d}`).join("\n")
|
|
72
|
-
: "
|
|
71
|
+
? `\n${keyDecisions.map(d => ` - ${d}`).join("\n")}`
|
|
72
|
+
: " []";
|
|
73
73
|
const patternsYaml = patternsEstablished.length > 0
|
|
74
74
|
? patternsEstablished.map(p => ` - ${p}`).join("\n")
|
|
75
75
|
: " - (none)";
|
|
@@ -106,10 +106,8 @@ requires:
|
|
|
106
106
|
${requiresYaml}
|
|
107
107
|
affects:
|
|
108
108
|
${affectsYaml}
|
|
109
|
-
key_files
|
|
110
|
-
|
|
111
|
-
key_decisions:
|
|
112
|
-
${keyDecisionsYaml}
|
|
109
|
+
key_files:${keyFilesYaml}
|
|
110
|
+
key_decisions:${keyDecisionsYaml}
|
|
113
111
|
patterns_established:
|
|
114
112
|
${patternsYaml}
|
|
115
113
|
observability_surfaces:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { clearParseCache } from "../files.js";
|
|
2
2
|
import { isClosedStatus } from "../status-guards.js";
|
|
3
|
-
import { isNonEmptyString, validateStringArray } from "../validation.js";
|
|
3
|
+
import { isNonEmptyString, validateStringArray, validateTitle } from "../validation.js";
|
|
4
4
|
import { transaction, getMilestone, getMilestoneSlices, getSlice, insertMilestone, insertSlice, upsertMilestonePlanning, upsertSlicePlanning, } from "../gsd-db.js";
|
|
5
5
|
import { invalidateStateCache } from "../state.js";
|
|
6
6
|
import { renderRoadmapFromDb } from "../markdown-renderer.js";
|
|
@@ -77,6 +77,9 @@ function validateSlices(value) {
|
|
|
77
77
|
seen.add(sliceId);
|
|
78
78
|
if (!isNonEmptyString(title))
|
|
79
79
|
throw new Error(`slices[${index}].title must be a non-empty string`);
|
|
80
|
+
const titleIssue = validateTitle(title);
|
|
81
|
+
if (titleIssue)
|
|
82
|
+
throw new Error(`slices[${index}].title is invalid: ${titleIssue}`);
|
|
80
83
|
if (!isNonEmptyString(risk))
|
|
81
84
|
throw new Error(`slices[${index}].risk must be a non-empty string`);
|
|
82
85
|
if (!Array.isArray(depends) || depends.some((item) => !isNonEmptyString(item))) {
|
|
@@ -127,6 +130,9 @@ function validateParams(params) {
|
|
|
127
130
|
throw new Error("title is required");
|
|
128
131
|
if (!isNonEmptyString(params?.vision))
|
|
129
132
|
throw new Error("vision is required");
|
|
133
|
+
const milestoneTitleIssue = validateTitle(params.title);
|
|
134
|
+
if (milestoneTitleIssue)
|
|
135
|
+
throw new Error(`title is invalid: ${milestoneTitleIssue}`);
|
|
130
136
|
return {
|
|
131
137
|
...params,
|
|
132
138
|
dependsOn: params.dependsOn ? validateStringArray(params.dependsOn, "dependsOn") : [],
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { existsSync, rmSync } from "node:fs";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
1
3
|
import { clearParseCache } from "../files.js";
|
|
2
4
|
import { isClosedStatus, isDeferredStatus } from "../status-guards.js";
|
|
3
|
-
import { isNonEmptyString } from "../validation.js";
|
|
4
|
-
import { transaction, getMilestone, getSlice, insertTask, upsertSlicePlanning, upsertTaskPlanning, insertGateRow, updateSliceStatus, } from "../gsd-db.js";
|
|
5
|
+
import { isNonEmptyString, validateStringArray } from "../validation.js";
|
|
6
|
+
import { transaction, getMilestone, getSlice, getSliceTasks, insertTask, upsertSlicePlanning, upsertTaskPlanning, insertGateRow, updateSliceStatus, setSliceSketchFlag, deleteTask, deleteArtifactByPath, } from "../gsd-db.js";
|
|
5
7
|
import { invalidateStateCache } from "../state.js";
|
|
6
8
|
import { renderPlanFromDb } from "../markdown-renderer.js";
|
|
7
9
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
@@ -9,6 +11,8 @@ import { writeManifest } from "../workflow-manifest.js";
|
|
|
9
11
|
import { appendEvent } from "../workflow-events.js";
|
|
10
12
|
import { logWarning } from "../workflow-logger.js";
|
|
11
13
|
import { validatePlanningPathScope } from "../planning-path-scope.js";
|
|
14
|
+
import { checkFilePathConsistency, checkTaskOrdering } from "../pre-execution-checks.js";
|
|
15
|
+
import { buildTaskFileName, gsdRoot, resolveTasksDir } from "../paths.js";
|
|
12
16
|
function validateTasks(value) {
|
|
13
17
|
if (!Array.isArray(value) || value.length === 0) {
|
|
14
18
|
throw new Error("tasks must be a non-empty array");
|
|
@@ -39,17 +43,11 @@ function validateTasks(value) {
|
|
|
39
43
|
throw new Error(`tasks[${index}].description must be a non-empty string`);
|
|
40
44
|
if (!isNonEmptyString(estimate))
|
|
41
45
|
throw new Error(`tasks[${index}].estimate must be a non-empty string`);
|
|
42
|
-
|
|
43
|
-
throw new Error(`tasks[${index}].files must be an array of non-empty strings`);
|
|
44
|
-
}
|
|
46
|
+
const validatedFiles = validateStringArray(files, `tasks[${index}].files`);
|
|
45
47
|
if (!isNonEmptyString(verify))
|
|
46
48
|
throw new Error(`tasks[${index}].verify must be a non-empty string`);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
if (!Array.isArray(expectedOutput) || expectedOutput.some((item) => !isNonEmptyString(item))) {
|
|
51
|
-
throw new Error(`tasks[${index}].expectedOutput must be an array of non-empty strings`);
|
|
52
|
-
}
|
|
49
|
+
const validatedInputs = validateStringArray(inputs, `tasks[${index}].inputs`);
|
|
50
|
+
const validatedExpectedOutput = validateStringArray(expectedOutput, `tasks[${index}].expectedOutput`);
|
|
53
51
|
if (observabilityImpact !== undefined && !isNonEmptyString(observabilityImpact)) {
|
|
54
52
|
throw new Error(`tasks[${index}].observabilityImpact must be a non-empty string when provided`);
|
|
55
53
|
}
|
|
@@ -58,10 +56,10 @@ function validateTasks(value) {
|
|
|
58
56
|
title,
|
|
59
57
|
description,
|
|
60
58
|
estimate,
|
|
61
|
-
files,
|
|
59
|
+
files: validatedFiles,
|
|
62
60
|
verify,
|
|
63
|
-
inputs,
|
|
64
|
-
expectedOutput,
|
|
61
|
+
inputs: validatedInputs,
|
|
62
|
+
expectedOutput: validatedExpectedOutput,
|
|
65
63
|
observabilityImpact: typeof observabilityImpact === "string" ? observabilityImpact : "",
|
|
66
64
|
};
|
|
67
65
|
});
|
|
@@ -84,6 +82,53 @@ function validateParams(params) {
|
|
|
84
82
|
tasks: validateTasks(params.tasks),
|
|
85
83
|
};
|
|
86
84
|
}
|
|
85
|
+
function toTaskRows(params) {
|
|
86
|
+
return params.tasks.map((task, index) => ({
|
|
87
|
+
milestone_id: params.milestoneId,
|
|
88
|
+
slice_id: params.sliceId,
|
|
89
|
+
id: task.taskId,
|
|
90
|
+
title: task.title,
|
|
91
|
+
status: "pending",
|
|
92
|
+
one_liner: "",
|
|
93
|
+
narrative: "",
|
|
94
|
+
verification_result: "",
|
|
95
|
+
duration: "",
|
|
96
|
+
completed_at: null,
|
|
97
|
+
blocker_discovered: false,
|
|
98
|
+
deviations: "",
|
|
99
|
+
known_issues: "",
|
|
100
|
+
key_files: [],
|
|
101
|
+
key_decisions: [],
|
|
102
|
+
full_summary_md: "",
|
|
103
|
+
description: task.description,
|
|
104
|
+
estimate: task.estimate,
|
|
105
|
+
files: task.files,
|
|
106
|
+
verify: task.verify,
|
|
107
|
+
inputs: task.inputs,
|
|
108
|
+
expected_output: task.expectedOutput,
|
|
109
|
+
observability_impact: task.observabilityImpact ?? "",
|
|
110
|
+
full_plan_md: task.fullPlanMd ?? "",
|
|
111
|
+
sequence: index + 1,
|
|
112
|
+
blocker_source: "",
|
|
113
|
+
escalation_pending: 0,
|
|
114
|
+
escalation_awaiting_review: 0,
|
|
115
|
+
escalation_artifact_path: null,
|
|
116
|
+
escalation_override_applied_at: null,
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
function validateTaskPathsBeforePersist(params, basePath) {
|
|
120
|
+
const taskRows = toTaskRows(params);
|
|
121
|
+
const checks = [
|
|
122
|
+
...checkFilePathConsistency(taskRows, basePath),
|
|
123
|
+
...checkTaskOrdering(taskRows, basePath),
|
|
124
|
+
];
|
|
125
|
+
const blocking = checks.filter((check) => !check.passed && check.blocking);
|
|
126
|
+
if (blocking.length === 0)
|
|
127
|
+
return null;
|
|
128
|
+
return blocking
|
|
129
|
+
.map((check) => `[${check.category}] ${check.target}: ${check.message}`)
|
|
130
|
+
.join("\n");
|
|
131
|
+
}
|
|
87
132
|
export async function handlePlanSlice(rawParams, basePath) {
|
|
88
133
|
let params;
|
|
89
134
|
try {
|
|
@@ -100,10 +145,15 @@ export async function handlePlanSlice(rawParams, basePath) {
|
|
|
100
145
|
if (pathScopeError) {
|
|
101
146
|
return { error: `validation failed: ${pathScopeError}` };
|
|
102
147
|
}
|
|
148
|
+
const pathError = validateTaskPathsBeforePersist(params, basePath);
|
|
149
|
+
if (pathError) {
|
|
150
|
+
return { error: `pre-execution validation failed:\n${pathError}` };
|
|
151
|
+
}
|
|
103
152
|
// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
|
|
104
153
|
// Guards must be inside the transaction so the state they check cannot
|
|
105
154
|
// change between the read and the write (#2723).
|
|
106
155
|
let guardError = null;
|
|
156
|
+
let omittedTaskIds = [];
|
|
107
157
|
try {
|
|
108
158
|
transaction(() => {
|
|
109
159
|
const parentMilestone = getMilestone(params.milestoneId);
|
|
@@ -124,9 +174,21 @@ export async function handlePlanSlice(rawParams, basePath) {
|
|
|
124
174
|
guardError = `cannot re-plan slice ${params.sliceId}: it is already complete — use gsd_slice_reopen first`;
|
|
125
175
|
return;
|
|
126
176
|
}
|
|
177
|
+
const newTaskIds = new Set(params.tasks.map((task) => task.taskId));
|
|
178
|
+
const existingTasks = getSliceTasks(params.milestoneId, params.sliceId);
|
|
179
|
+
omittedTaskIds = existingTasks
|
|
180
|
+
.filter((task) => !newTaskIds.has(task.id))
|
|
181
|
+
.map((task) => task.id);
|
|
182
|
+
for (const task of existingTasks) {
|
|
183
|
+
if (!newTaskIds.has(task.id) && isClosedStatus(task.status)) {
|
|
184
|
+
guardError = `cannot remove completed task ${task.id}`;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
127
188
|
if (isDeferredStatus(parentSlice.status)) {
|
|
128
189
|
updateSliceStatus(params.milestoneId, params.sliceId, "pending");
|
|
129
190
|
}
|
|
191
|
+
setSliceSketchFlag(params.milestoneId, params.sliceId, false);
|
|
130
192
|
upsertSlicePlanning(params.milestoneId, params.sliceId, {
|
|
131
193
|
goal: params.goal,
|
|
132
194
|
successCriteria: params.successCriteria,
|
|
@@ -134,6 +196,9 @@ export async function handlePlanSlice(rawParams, basePath) {
|
|
|
134
196
|
integrationClosure: params.integrationClosure,
|
|
135
197
|
observabilityImpact: params.observabilityImpact,
|
|
136
198
|
});
|
|
199
|
+
for (const taskId of omittedTaskIds) {
|
|
200
|
+
deleteTask(params.milestoneId, params.sliceId, taskId);
|
|
201
|
+
}
|
|
137
202
|
for (const task of params.tasks) {
|
|
138
203
|
insertTask({
|
|
139
204
|
id: task.taskId,
|
|
@@ -176,6 +241,16 @@ export async function handlePlanSlice(rawParams, basePath) {
|
|
|
176
241
|
return { error: guardError };
|
|
177
242
|
}
|
|
178
243
|
try {
|
|
244
|
+
const tasksDir = resolveTasksDir(basePath, params.milestoneId, params.sliceId);
|
|
245
|
+
for (const taskId of omittedTaskIds) {
|
|
246
|
+
if (!tasksDir)
|
|
247
|
+
continue;
|
|
248
|
+
const taskPlanPath = join(tasksDir, buildTaskFileName(taskId, "PLAN"));
|
|
249
|
+
if (existsSync(taskPlanPath))
|
|
250
|
+
rmSync(taskPlanPath, { force: true });
|
|
251
|
+
const artifactPath = relative(gsdRoot(basePath), taskPlanPath).replace(/\\/g, "/");
|
|
252
|
+
deleteArtifactByPath(artifactPath);
|
|
253
|
+
}
|
|
179
254
|
const renderResult = await renderPlanFromDb(basePath, params.milestoneId, params.sliceId);
|
|
180
255
|
invalidateStateCache();
|
|
181
256
|
clearParseCache();
|