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
|
@@ -1,3 +1,5 @@
|
|
|
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
5
|
import { isNonEmptyString, validateStringArray } from "../validation.js";
|
|
@@ -5,11 +7,15 @@ import {
|
|
|
5
7
|
transaction,
|
|
6
8
|
getMilestone,
|
|
7
9
|
getSlice,
|
|
10
|
+
getSliceTasks,
|
|
8
11
|
insertTask,
|
|
9
12
|
upsertSlicePlanning,
|
|
10
13
|
upsertTaskPlanning,
|
|
11
14
|
insertGateRow,
|
|
12
15
|
updateSliceStatus,
|
|
16
|
+
setSliceSketchFlag,
|
|
17
|
+
deleteTask,
|
|
18
|
+
deleteArtifactByPath,
|
|
13
19
|
} from "../gsd-db.js";
|
|
14
20
|
import type { GateId } from "../types.js";
|
|
15
21
|
import { invalidateStateCache } from "../state.js";
|
|
@@ -19,6 +25,9 @@ import { writeManifest } from "../workflow-manifest.js";
|
|
|
19
25
|
import { appendEvent } from "../workflow-events.js";
|
|
20
26
|
import { logWarning } from "../workflow-logger.js";
|
|
21
27
|
import { validatePlanningPathScope } from "../planning-path-scope.js";
|
|
28
|
+
import { checkFilePathConsistency, checkTaskOrdering } from "../pre-execution-checks.js";
|
|
29
|
+
import type { TaskRow } from "../db-task-slice-rows.js";
|
|
30
|
+
import { buildTaskFileName, gsdRoot, resolveTasksDir } from "../paths.js";
|
|
22
31
|
|
|
23
32
|
export interface PlanSliceTaskInput {
|
|
24
33
|
taskId: string;
|
|
@@ -86,16 +95,10 @@ function validateTasks(value: unknown): PlanSliceTaskInput[] {
|
|
|
86
95
|
if (!isNonEmptyString(title)) throw new Error(`tasks[${index}].title must be a non-empty string`);
|
|
87
96
|
if (!isNonEmptyString(description)) throw new Error(`tasks[${index}].description must be a non-empty string`);
|
|
88
97
|
if (!isNonEmptyString(estimate)) throw new Error(`tasks[${index}].estimate must be a non-empty string`);
|
|
89
|
-
|
|
90
|
-
throw new Error(`tasks[${index}].files must be an array of non-empty strings`);
|
|
91
|
-
}
|
|
98
|
+
const validatedFiles = validateStringArray(files, `tasks[${index}].files`);
|
|
92
99
|
if (!isNonEmptyString(verify)) throw new Error(`tasks[${index}].verify must be a non-empty string`);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
if (!Array.isArray(expectedOutput) || expectedOutput.some((item) => !isNonEmptyString(item))) {
|
|
97
|
-
throw new Error(`tasks[${index}].expectedOutput must be an array of non-empty strings`);
|
|
98
|
-
}
|
|
100
|
+
const validatedInputs = validateStringArray(inputs, `tasks[${index}].inputs`);
|
|
101
|
+
const validatedExpectedOutput = validateStringArray(expectedOutput, `tasks[${index}].expectedOutput`);
|
|
99
102
|
if (observabilityImpact !== undefined && !isNonEmptyString(observabilityImpact)) {
|
|
100
103
|
throw new Error(`tasks[${index}].observabilityImpact must be a non-empty string when provided`);
|
|
101
104
|
}
|
|
@@ -105,10 +108,10 @@ function validateTasks(value: unknown): PlanSliceTaskInput[] {
|
|
|
105
108
|
title,
|
|
106
109
|
description,
|
|
107
110
|
estimate,
|
|
108
|
-
files,
|
|
111
|
+
files: validatedFiles,
|
|
109
112
|
verify,
|
|
110
|
-
inputs,
|
|
111
|
-
expectedOutput,
|
|
113
|
+
inputs: validatedInputs,
|
|
114
|
+
expectedOutput: validatedExpectedOutput,
|
|
112
115
|
observabilityImpact: typeof observabilityImpact === "string" ? observabilityImpact : "",
|
|
113
116
|
};
|
|
114
117
|
});
|
|
@@ -131,6 +134,56 @@ function validateParams(params: PlanSliceParams): PlanSliceParams {
|
|
|
131
134
|
};
|
|
132
135
|
}
|
|
133
136
|
|
|
137
|
+
function toTaskRows(params: PlanSliceParams): TaskRow[] {
|
|
138
|
+
return params.tasks.map((task, index) => ({
|
|
139
|
+
milestone_id: params.milestoneId,
|
|
140
|
+
slice_id: params.sliceId,
|
|
141
|
+
id: task.taskId,
|
|
142
|
+
title: task.title,
|
|
143
|
+
status: "pending",
|
|
144
|
+
one_liner: "",
|
|
145
|
+
narrative: "",
|
|
146
|
+
verification_result: "",
|
|
147
|
+
duration: "",
|
|
148
|
+
completed_at: null,
|
|
149
|
+
blocker_discovered: false,
|
|
150
|
+
deviations: "",
|
|
151
|
+
known_issues: "",
|
|
152
|
+
key_files: [],
|
|
153
|
+
key_decisions: [],
|
|
154
|
+
full_summary_md: "",
|
|
155
|
+
description: task.description,
|
|
156
|
+
estimate: task.estimate,
|
|
157
|
+
files: task.files,
|
|
158
|
+
verify: task.verify,
|
|
159
|
+
inputs: task.inputs,
|
|
160
|
+
expected_output: task.expectedOutput,
|
|
161
|
+
observability_impact: task.observabilityImpact ?? "",
|
|
162
|
+
full_plan_md: task.fullPlanMd ?? "",
|
|
163
|
+
sequence: index + 1,
|
|
164
|
+
blocker_source: "",
|
|
165
|
+
escalation_pending: 0,
|
|
166
|
+
escalation_awaiting_review: 0,
|
|
167
|
+
escalation_artifact_path: null,
|
|
168
|
+
escalation_override_applied_at: null,
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function validateTaskPathsBeforePersist(params: PlanSliceParams, basePath: string): string | null {
|
|
173
|
+
const taskRows = toTaskRows(params);
|
|
174
|
+
const checks = [
|
|
175
|
+
...checkFilePathConsistency(taskRows, basePath),
|
|
176
|
+
...checkTaskOrdering(taskRows, basePath),
|
|
177
|
+
];
|
|
178
|
+
const blocking = checks.filter((check) => !check.passed && check.blocking);
|
|
179
|
+
|
|
180
|
+
if (blocking.length === 0) return null;
|
|
181
|
+
|
|
182
|
+
return blocking
|
|
183
|
+
.map((check) => `[${check.category}] ${check.target}: ${check.message}`)
|
|
184
|
+
.join("\n");
|
|
185
|
+
}
|
|
186
|
+
|
|
134
187
|
export async function handlePlanSlice(
|
|
135
188
|
rawParams: PlanSliceParams,
|
|
136
189
|
basePath: string,
|
|
@@ -154,10 +207,16 @@ export async function handlePlanSlice(
|
|
|
154
207
|
return { error: `validation failed: ${pathScopeError}` };
|
|
155
208
|
}
|
|
156
209
|
|
|
210
|
+
const pathError = validateTaskPathsBeforePersist(params, basePath);
|
|
211
|
+
if (pathError) {
|
|
212
|
+
return { error: `pre-execution validation failed:\n${pathError}` };
|
|
213
|
+
}
|
|
214
|
+
|
|
157
215
|
// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
|
|
158
216
|
// Guards must be inside the transaction so the state they check cannot
|
|
159
217
|
// change between the read and the write (#2723).
|
|
160
218
|
let guardError: string | null = null;
|
|
219
|
+
let omittedTaskIds: string[] = [];
|
|
161
220
|
|
|
162
221
|
try {
|
|
163
222
|
transaction(() => {
|
|
@@ -181,9 +240,23 @@ export async function handlePlanSlice(
|
|
|
181
240
|
return;
|
|
182
241
|
}
|
|
183
242
|
|
|
243
|
+
const newTaskIds = new Set(params.tasks.map((task) => task.taskId));
|
|
244
|
+
const existingTasks = getSliceTasks(params.milestoneId, params.sliceId);
|
|
245
|
+
omittedTaskIds = existingTasks
|
|
246
|
+
.filter((task) => !newTaskIds.has(task.id))
|
|
247
|
+
.map((task) => task.id);
|
|
248
|
+
|
|
249
|
+
for (const task of existingTasks) {
|
|
250
|
+
if (!newTaskIds.has(task.id) && isClosedStatus(task.status)) {
|
|
251
|
+
guardError = `cannot remove completed task ${task.id}`;
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
184
256
|
if (isDeferredStatus(parentSlice.status)) {
|
|
185
257
|
updateSliceStatus(params.milestoneId, params.sliceId, "pending");
|
|
186
258
|
}
|
|
259
|
+
setSliceSketchFlag(params.milestoneId, params.sliceId, false);
|
|
187
260
|
|
|
188
261
|
upsertSlicePlanning(params.milestoneId, params.sliceId, {
|
|
189
262
|
goal: params.goal,
|
|
@@ -193,6 +266,10 @@ export async function handlePlanSlice(
|
|
|
193
266
|
observabilityImpact: params.observabilityImpact,
|
|
194
267
|
});
|
|
195
268
|
|
|
269
|
+
for (const taskId of omittedTaskIds) {
|
|
270
|
+
deleteTask(params.milestoneId, params.sliceId, taskId);
|
|
271
|
+
}
|
|
272
|
+
|
|
196
273
|
for (const task of params.tasks) {
|
|
197
274
|
insertTask({
|
|
198
275
|
id: task.taskId,
|
|
@@ -237,6 +314,15 @@ export async function handlePlanSlice(
|
|
|
237
314
|
}
|
|
238
315
|
|
|
239
316
|
try {
|
|
317
|
+
const tasksDir = resolveTasksDir(basePath, params.milestoneId, params.sliceId);
|
|
318
|
+
for (const taskId of omittedTaskIds) {
|
|
319
|
+
if (!tasksDir) continue;
|
|
320
|
+
const taskPlanPath = join(tasksDir, buildTaskFileName(taskId, "PLAN"));
|
|
321
|
+
if (existsSync(taskPlanPath)) rmSync(taskPlanPath, { force: true });
|
|
322
|
+
const artifactPath = relative(gsdRoot(basePath), taskPlanPath).replace(/\\/g, "/");
|
|
323
|
+
deleteArtifactByPath(artifactPath);
|
|
324
|
+
}
|
|
325
|
+
|
|
240
326
|
const renderResult = await renderPlanFromDb(basePath, params.milestoneId, params.sliceId);
|
|
241
327
|
invalidateStateCache();
|
|
242
328
|
clearParseCache();
|
|
@@ -106,7 +106,7 @@ export interface AuditWarning {
|
|
|
106
106
|
export interface VerificationResult {
|
|
107
107
|
passed: boolean; // true if all checks passed (or no checks discovered)
|
|
108
108
|
checks: VerificationCheck[]; // per-command results
|
|
109
|
-
discoverySource: "preference" | "task-plan" | "package-json" | "none";
|
|
109
|
+
discoverySource: "preference" | "task-plan" | "package-json" | "python-project" | "none";
|
|
110
110
|
timestamp: number; // Date.now() at gate start
|
|
111
111
|
runtimeErrors?: RuntimeError[]; // optional — populated by captureRuntimeErrors()
|
|
112
112
|
auditWarnings?: AuditWarning[]; // optional — populated by runDependencyAudit()
|
|
@@ -132,6 +132,9 @@ export type ContextModePolicy =
|
|
|
132
132
|
* the explicit `allowedPathGlobs` set; Bash safe-allowlist;
|
|
133
133
|
* no subagents. Reserved for rewrite-docs, which legitimately
|
|
134
134
|
* edits project markdown outside .gsd/.
|
|
135
|
+
* - "verification"
|
|
136
|
+
* — Read tools + Bash for verification commands, writes
|
|
137
|
+
* restricted to .gsd/**, no subagents.
|
|
135
138
|
*
|
|
136
139
|
* The allowlist for "docs" is declared per-manifest rather than hardcoded so
|
|
137
140
|
* projects with non-standard doc layouts can extend it without forking the
|
|
@@ -143,7 +146,8 @@ export type ToolsPolicy =
|
|
|
143
146
|
| { readonly mode: "read-only" }
|
|
144
147
|
| { readonly mode: "planning" }
|
|
145
148
|
| { readonly mode: "planning-dispatch"; readonly allowedSubagents: readonly string[] }
|
|
146
|
-
| { readonly mode: "docs"; readonly allowedPathGlobs: readonly string[] }
|
|
149
|
+
| { readonly mode: "docs"; readonly allowedPathGlobs: readonly string[] }
|
|
150
|
+
| { readonly mode: "verification" };
|
|
147
151
|
|
|
148
152
|
// ─── Computed-artifact registry (#4924 v2 contract) ───────────────────────
|
|
149
153
|
|
|
@@ -288,6 +292,7 @@ const COMMON_BUDGET_SMALL = 250_000; // ~65K tokens
|
|
|
288
292
|
|
|
289
293
|
const TOOLS_ALL: ToolsPolicy = { mode: "all" };
|
|
290
294
|
const TOOLS_PLANNING: ToolsPolicy = { mode: "planning" };
|
|
295
|
+
const TOOLS_VERIFICATION: ToolsPolicy = { mode: "verification" };
|
|
291
296
|
// Like TOOLS_PLANNING but permits dispatch to read-only recon/planning
|
|
292
297
|
// specialists. Runtime-enforced by write-gate.ts before the subagent tool runs.
|
|
293
298
|
const TOOLS_PLANNING_DISPATCH_RECON: ToolsPolicy = {
|
|
@@ -406,8 +411,8 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
406
411
|
contextMode: "verification",
|
|
407
412
|
// planning-dispatch: validation is a verification-fan-out unit. It reads
|
|
408
413
|
// the milestone surface and dispatches reviewer/security/tester subagents
|
|
409
|
-
// to report findings without touching user source.
|
|
410
|
-
//
|
|
414
|
+
// to report findings without touching user source. Write isolation to
|
|
415
|
+
// .gsd/ is preserved.
|
|
411
416
|
tools: TOOLS_PLANNING_DISPATCH_REVIEW,
|
|
412
417
|
artifacts: {
|
|
413
418
|
inline: ["roadmap", "slice-summary", "slice-uat", "requirements", "decisions", "templates"],
|
|
@@ -423,11 +428,9 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
423
428
|
codebaseMap: false,
|
|
424
429
|
preferences: "active-only",
|
|
425
430
|
contextMode: "verification",
|
|
426
|
-
//
|
|
427
|
-
//
|
|
428
|
-
|
|
429
|
-
// preserved.
|
|
430
|
-
tools: TOOLS_PLANNING_DISPATCH_REVIEW,
|
|
431
|
+
// Milestone closeout must run unrestricted shell verification commands
|
|
432
|
+
// against the final diff before recording completion.
|
|
433
|
+
tools: TOOLS_ALL,
|
|
431
434
|
artifacts: {
|
|
432
435
|
// #4780 landed slice-summary as excerpt for this unit; phase 2 of
|
|
433
436
|
// the architecture will read this manifest as the source of truth
|
|
@@ -447,7 +450,9 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
447
450
|
codebaseMap: true,
|
|
448
451
|
preferences: "active-only",
|
|
449
452
|
contextMode: "research",
|
|
450
|
-
|
|
453
|
+
// Multi-slice research dispatches use the research-slice unit contract to
|
|
454
|
+
// fan out scout subagents that write .gsd research artifacts.
|
|
455
|
+
tools: TOOLS_PLANNING_DISPATCH_RECON,
|
|
451
456
|
artifacts: {
|
|
452
457
|
inline: ["roadmap", "milestone-research", "dependency-summaries", "templates"],
|
|
453
458
|
excerpt: [],
|
|
@@ -587,7 +592,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
587
592
|
codebaseMap: false,
|
|
588
593
|
preferences: "active-only",
|
|
589
594
|
contextMode: "verification",
|
|
590
|
-
tools:
|
|
595
|
+
tools: TOOLS_VERIFICATION,
|
|
591
596
|
artifacts: {
|
|
592
597
|
// Phase 3 migration (#4782): manifest matches today's actual
|
|
593
598
|
// buildRunUatPrompt inlining. Prior phase-1 entry listed
|
|
@@ -606,7 +611,9 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
606
611
|
codebaseMap: false,
|
|
607
612
|
preferences: "active-only",
|
|
608
613
|
contextMode: "verification",
|
|
609
|
-
|
|
614
|
+
// Gate evaluation fans out tester-style subagents, which read the slice
|
|
615
|
+
// plan and report via the DB-backed gate-result tool.
|
|
616
|
+
tools: TOOLS_PLANNING_DISPATCH_REVIEW,
|
|
610
617
|
artifacts: {
|
|
611
618
|
inline: ["slice-plan", "prior-task-summaries"],
|
|
612
619
|
excerpt: [],
|
|
@@ -734,3 +741,32 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
734
741
|
export function resolveManifest(unitType: string): UnitContextManifest | null {
|
|
735
742
|
return (UNIT_MANIFESTS as Record<string, UnitContextManifest>)[unitType] ?? null;
|
|
736
743
|
}
|
|
744
|
+
|
|
745
|
+
export interface SubagentPermissionContract {
|
|
746
|
+
readonly allowed: boolean;
|
|
747
|
+
readonly allowedSubagents: readonly string[];
|
|
748
|
+
readonly toolsMode: ToolsPolicy["mode"] | "unknown";
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
export function compileSubagentPermissionContract(
|
|
752
|
+
policy: ToolsPolicy | null | undefined,
|
|
753
|
+
): SubagentPermissionContract {
|
|
754
|
+
if (!policy) {
|
|
755
|
+
return { allowed: false, allowedSubagents: [], toolsMode: "unknown" };
|
|
756
|
+
}
|
|
757
|
+
if (policy.mode === "all") {
|
|
758
|
+
return { allowed: true, allowedSubagents: ["*"], toolsMode: policy.mode };
|
|
759
|
+
}
|
|
760
|
+
if (policy.mode === "planning-dispatch") {
|
|
761
|
+
return {
|
|
762
|
+
allowed: true,
|
|
763
|
+
allowedSubagents: [...policy.allowedSubagents],
|
|
764
|
+
toolsMode: policy.mode,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
return { allowed: false, allowedSubagents: [], toolsMode: policy.mode };
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
export function resolveSubagentPermissionContract(unitType: string): SubagentPermissionContract {
|
|
771
|
+
return compileSubagentPermissionContract(resolveManifest(unitType)?.tools);
|
|
772
|
+
}
|
|
@@ -7,6 +7,27 @@ export function isNonEmptyString(value: unknown): value is string {
|
|
|
7
7
|
return typeof value === "string" && value.trim().length > 0;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Characters that are used as delimiters in GSD state management documents
|
|
12
|
+
* and should not appear in milestone or slice titles.
|
|
13
|
+
*/
|
|
14
|
+
const TITLE_DELIMITER_RE = /[\u2014\u2013\/]/; // em dash, en dash, forward slash
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check whether a milestone or slice title contains characters that conflict
|
|
18
|
+
* with GSD's state document delimiter conventions.
|
|
19
|
+
* Returns a human-readable description of the problem, or null if the title is safe.
|
|
20
|
+
*/
|
|
21
|
+
export function validateTitle(title: string): string | null {
|
|
22
|
+
if (TITLE_DELIMITER_RE.test(title)) {
|
|
23
|
+
const found: string[] = [];
|
|
24
|
+
if (/[\u2014\u2013]/.test(title)) found.push("em/en dash (\u2014 or \u2013)");
|
|
25
|
+
if (/\//.test(title)) found.push("forward slash (/)");
|
|
26
|
+
return `title contains ${found.join(" and ")}, which conflict with GSD state document delimiters`;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
10
31
|
/**
|
|
11
32
|
* Validate that `value` is an array of non-empty strings.
|
|
12
33
|
* Throws with a message referencing `field` on failure.
|
|
@@ -14,7 +35,8 @@ export function isNonEmptyString(value: unknown): value is string {
|
|
|
14
35
|
*/
|
|
15
36
|
export function validateStringArray(value: unknown, field: string): string[] {
|
|
16
37
|
if (!Array.isArray(value)) {
|
|
17
|
-
|
|
38
|
+
const received = value === null ? "null" : typeof value;
|
|
39
|
+
throw new Error(`${field} must be an array of strings, not ${received}`);
|
|
18
40
|
}
|
|
19
41
|
if (value.some((item) => !isNonEmptyString(item))) {
|
|
20
42
|
throw new Error(`${field} must contain only non-empty strings`);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// First non-empty source wins.
|
|
5
5
|
|
|
6
6
|
import { spawnSync, type SpawnSyncReturns } from "node:child_process";
|
|
7
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { existsSync, readFileSync, readdirSync, type Dirent } from "node:fs";
|
|
8
8
|
import { join, basename } from "node:path";
|
|
9
9
|
import type { AuditWarning, RuntimeError, VerificationCheck, VerificationResult } from "./types.js";
|
|
10
10
|
import { DEFAULT_COMMAND_TIMEOUT_MS } from "./constants.js";
|
|
@@ -44,7 +44,8 @@ const PACKAGE_SCRIPT_KEYS = ["typecheck", "lint", "test"] as const;
|
|
|
44
44
|
* 1. Explicit preference commands
|
|
45
45
|
* 2. Task plan verify field (split on &&)
|
|
46
46
|
* 3. package.json scripts (typecheck, lint, test)
|
|
47
|
-
* 4.
|
|
47
|
+
* 4. Python pytest project markers
|
|
48
|
+
* 5. None found
|
|
48
49
|
*/
|
|
49
50
|
export function discoverCommands(options: DiscoverCommandsOptions): DiscoveredCommands {
|
|
50
51
|
// 1. Preference commands
|
|
@@ -91,10 +92,67 @@ export function discoverCommands(options: DiscoverCommandsOptions): DiscoveredCo
|
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
|
|
95
|
+
const pythonCommand = discoverPythonPytestCommand(options.cwd);
|
|
96
|
+
if (pythonCommand) {
|
|
97
|
+
return { commands: [pythonCommand], source: "python-project" };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 5. Nothing found
|
|
95
101
|
return { commands: [], source: "none" };
|
|
96
102
|
}
|
|
97
103
|
|
|
104
|
+
function discoverPythonPytestCommand(cwd: string): string | null {
|
|
105
|
+
const hasPythonTestFiles = hasPythonTests(join(cwd, "tests"));
|
|
106
|
+
const hasPytestConfig = existsSync(join(cwd, "pytest.ini"));
|
|
107
|
+
const pyprojectPath = join(cwd, "pyproject.toml");
|
|
108
|
+
const hasPyproject = existsSync(pyprojectPath);
|
|
109
|
+
|
|
110
|
+
if (!hasPythonTestFiles && !hasPytestConfig && !hasPyproject) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (hasPytestConfig || hasPythonTestFiles) {
|
|
115
|
+
return "python3 -m pytest";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const pyproject = readFileSync(pyprojectPath, "utf-8");
|
|
120
|
+
if (
|
|
121
|
+
pyproject.includes("[tool.pytest]") ||
|
|
122
|
+
pyproject.includes("[tool.pytest.") ||
|
|
123
|
+
pyproject.includes("[pytest]") ||
|
|
124
|
+
pyproject.includes("[tool:pytest]")
|
|
125
|
+
) {
|
|
126
|
+
return "python3 -m pytest";
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
// Ignore unreadable pyproject.toml and fall through.
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function hasPythonTests(dir: string): boolean {
|
|
136
|
+
let entries: Dirent[];
|
|
137
|
+
try {
|
|
138
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
139
|
+
} catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
const path = join(dir, entry.name);
|
|
145
|
+
if (entry.isDirectory() && hasPythonTests(path)) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
if (entry.isFile() && /^test_.*\.py$|^.*_test\.py$/.test(entry.name)) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
98
156
|
// ─── Failure Context Formatting ──────────────────────────────────────────────
|
|
99
157
|
|
|
100
158
|
/** Maximum chars of stderr to include per failed check in failure context. */
|
|
@@ -144,7 +202,7 @@ export function formatFailureContext(result: VerificationResult): string {
|
|
|
144
202
|
// ─── Gate Execution ─────────────────────────────────────────────────────────
|
|
145
203
|
|
|
146
204
|
/** Characters that indicate shell injection when found in a command string. */
|
|
147
|
-
const SHELL_INJECTION_PATTERN = /[
|
|
205
|
+
const SHELL_INJECTION_PATTERN = /[;|`<>]|\$\(/;
|
|
148
206
|
|
|
149
207
|
/**
|
|
150
208
|
* Known executable first-tokens that are safe to run.
|
|
@@ -182,6 +240,7 @@ const KNOWN_COMMAND_PREFIXES = new Set([
|
|
|
182
240
|
* Heuristics (any true → prose-like):
|
|
183
241
|
* 1. First token starts with an uppercase letter and the string has 4+ words
|
|
184
242
|
* 2. String contains commas followed by spaces (prose clause structure)
|
|
243
|
+
* 3. First token has no ASCII letters or digits and the string has 4+ words
|
|
185
244
|
*/
|
|
186
245
|
export function isLikelyCommand(cmd: string): boolean {
|
|
187
246
|
const trimmed = cmd.trim();
|
|
@@ -208,6 +267,9 @@ export function isLikelyCommand(cmd: string): boolean {
|
|
|
208
267
|
// First token has uppercase letters and no path separators → prose
|
|
209
268
|
if (/[A-Z]/.test(firstToken) && !firstToken.includes("/")) return false;
|
|
210
269
|
|
|
270
|
+
// Non-ASCII prose with multiple words should not be executed as a command.
|
|
271
|
+
if (!/[A-Za-z0-9]/.test(firstToken) && tokens.length >= 4) return false;
|
|
272
|
+
|
|
211
273
|
return true;
|
|
212
274
|
}
|
|
213
275
|
|
|
@@ -215,9 +277,19 @@ export function isLikelyCommand(cmd: string): boolean {
|
|
|
215
277
|
* Validate a command string for obvious shell injection patterns.
|
|
216
278
|
* Returns the command unchanged if safe, or null if suspicious.
|
|
217
279
|
*/
|
|
280
|
+
export function validateVerificationCommand(cmd: string): { ok: true } | { ok: false; reason: string } {
|
|
281
|
+
if (SHELL_INJECTION_PATTERN.test(cmd)) {
|
|
282
|
+
return { ok: false, reason: "contains shell control syntax such as pipes, redirects, semicolons, backticks, or command substitution" };
|
|
283
|
+
}
|
|
284
|
+
if (!isLikelyCommand(cmd)) {
|
|
285
|
+
return { ok: false, reason: "does not look like a runnable command" };
|
|
286
|
+
}
|
|
287
|
+
return { ok: true };
|
|
288
|
+
}
|
|
289
|
+
|
|
218
290
|
function sanitizeCommand(cmd: string): string | null {
|
|
219
|
-
|
|
220
|
-
if (!
|
|
291
|
+
const validation = validateVerificationCommand(cmd);
|
|
292
|
+
if (!validation.ok) return null;
|
|
221
293
|
return cmd;
|
|
222
294
|
}
|
|
223
295
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Host-owned verification verdict policy for auto-mode units.
|
|
3
|
+
|
|
4
|
+
import type { VerificationResult as VerificationGateResult } from "./types.js";
|
|
5
|
+
|
|
6
|
+
export type VerificationVerdictReason =
|
|
7
|
+
| "passed"
|
|
8
|
+
| "no-host-checks"
|
|
9
|
+
| "checks-failed";
|
|
10
|
+
|
|
11
|
+
export interface VerificationVerdict {
|
|
12
|
+
passed: boolean;
|
|
13
|
+
reason: VerificationVerdictReason;
|
|
14
|
+
retryable: boolean;
|
|
15
|
+
failureContext: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function decideVerificationVerdict(
|
|
19
|
+
unitType: string,
|
|
20
|
+
result: VerificationGateResult,
|
|
21
|
+
): VerificationVerdict {
|
|
22
|
+
if (unitType === "execute-task" && result.discoverySource === "none" && result.checks.length === 0) {
|
|
23
|
+
return {
|
|
24
|
+
passed: false,
|
|
25
|
+
reason: "no-host-checks",
|
|
26
|
+
retryable: false,
|
|
27
|
+
failureContext:
|
|
28
|
+
"No runnable host-owned verification command was discovered. Add project verification_commands or a runnable task-plan Verify command before completing this execute-task.",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!result.passed) {
|
|
33
|
+
return {
|
|
34
|
+
passed: false,
|
|
35
|
+
reason: "checks-failed",
|
|
36
|
+
retryable: true,
|
|
37
|
+
failureContext: "",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
passed: true,
|
|
43
|
+
reason: "passed",
|
|
44
|
+
retryable: false,
|
|
45
|
+
failureContext: "",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -195,11 +195,11 @@ export function renderSummaryContent(
|
|
|
195
195
|
|
|
196
196
|
// ── Frontmatter (YAML list format, matches parseSummary() expectations) ──
|
|
197
197
|
const keyFilesYaml = taskRow.key_files && taskRow.key_files.length > 0
|
|
198
|
-
? taskRow.key_files.map(f => ` - ${f}`).join("\n")
|
|
199
|
-
: "
|
|
198
|
+
? `\n${taskRow.key_files.map(f => ` - ${f}`).join("\n")}`
|
|
199
|
+
: " []";
|
|
200
200
|
const keyDecisionsYaml = taskRow.key_decisions && taskRow.key_decisions.length > 0
|
|
201
|
-
? taskRow.key_decisions.map(d => ` - ${d}`).join("\n")
|
|
202
|
-
: "
|
|
201
|
+
? `\n${taskRow.key_decisions.map(d => ` - ${d}`).join("\n")}`
|
|
202
|
+
: " []";
|
|
203
203
|
|
|
204
204
|
// Derive verification_result from evidence if available
|
|
205
205
|
const evidenceList = evidence ?? [];
|
|
@@ -230,10 +230,8 @@ export function renderSummaryContent(
|
|
|
230
230
|
id: ${taskRow.id}
|
|
231
231
|
parent: ${sliceId}
|
|
232
232
|
milestone: ${milestoneId}
|
|
233
|
-
key_files
|
|
234
|
-
|
|
235
|
-
key_decisions:
|
|
236
|
-
${keyDecisionsYaml}
|
|
233
|
+
key_files:${keyFilesYaml}
|
|
234
|
+
key_decisions:${keyDecisionsYaml}
|
|
237
235
|
duration: ${taskRow.duration || ""}
|
|
238
236
|
verification_result: ${verificationResult}
|
|
239
237
|
completed_at: ${taskRow.completed_at || ""}
|
|
@@ -23,6 +23,7 @@ import { join } from "node:path";
|
|
|
23
23
|
|
|
24
24
|
import type { AutoSession } from "./auto/session.js";
|
|
25
25
|
import { debugLog } from "./debug-logger.js";
|
|
26
|
+
import { logWarning } from "./workflow-logger.js";
|
|
26
27
|
import { emitJournalEvent } from "./journal.js";
|
|
27
28
|
import { emitWorktreeCreated, emitWorktreeMerged } from "./worktree-telemetry.js";
|
|
28
29
|
import {
|
|
@@ -76,6 +77,13 @@ import {
|
|
|
76
77
|
teardownAutoWorktree,
|
|
77
78
|
} from "./auto-worktree.js";
|
|
78
79
|
|
|
80
|
+
const recentWorktreeMergeFailures = new Map<string, number>();
|
|
81
|
+
const MERGE_FAILURE_DEDUPE_MS = 60_000;
|
|
82
|
+
|
|
83
|
+
export function resetRecentWorktreeMergeFailuresForTest(): void {
|
|
84
|
+
recentWorktreeMergeFailures.clear();
|
|
85
|
+
}
|
|
86
|
+
|
|
79
87
|
// ─── Types ───────────────────────────────────────────────────────────────
|
|
80
88
|
|
|
81
89
|
export interface NotifyCtx {
|
|
@@ -525,7 +533,7 @@ export function _enterMilestoneCore(
|
|
|
525
533
|
|
|
526
534
|
// Phase B: claim a milestone lease before any worktree mutation. Two
|
|
527
535
|
// workers cannot enter the same milestone concurrently. Best-effort:
|
|
528
|
-
//
|
|
536
|
+
// warn if no worker registered (single-worker fallback) or skip if DB
|
|
529
537
|
// unavailable; reuse existing lease if we already hold it on this
|
|
530
538
|
// milestone (re-entry within the same session).
|
|
531
539
|
if (s.workerId) {
|
|
@@ -618,6 +626,11 @@ export function _enterMilestoneCore(
|
|
|
618
626
|
});
|
|
619
627
|
}
|
|
620
628
|
}
|
|
629
|
+
} else {
|
|
630
|
+
logWarning(
|
|
631
|
+
"worktree",
|
|
632
|
+
`enterMilestone(${milestoneId}) ran before auto worker registration; milestone lease was not claimed.`,
|
|
633
|
+
);
|
|
621
634
|
}
|
|
622
635
|
|
|
623
636
|
// Resolve the project root for worktree operations via shared helper.
|
|
@@ -836,6 +849,32 @@ function rebuildGitService(
|
|
|
836
849
|
s.gitService = deps.gitServiceFactory(s.basePath);
|
|
837
850
|
}
|
|
838
851
|
|
|
852
|
+
function emitWorktreeMergeFailedOnce(
|
|
853
|
+
basePath: string,
|
|
854
|
+
milestoneId: string,
|
|
855
|
+
err: unknown,
|
|
856
|
+
): void {
|
|
857
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
858
|
+
const errorCategory = err instanceof Error ? err.name : "Error";
|
|
859
|
+
const now = Date.now();
|
|
860
|
+
const key = `${basePath}\0${milestoneId}\0${errorCategory}`;
|
|
861
|
+
const previous = recentWorktreeMergeFailures.get(key);
|
|
862
|
+
if (previous && now - previous < MERGE_FAILURE_DEDUPE_MS) return;
|
|
863
|
+
for (const [candidate, ts] of recentWorktreeMergeFailures) {
|
|
864
|
+
if (now - ts >= MERGE_FAILURE_DEDUPE_MS) {
|
|
865
|
+
recentWorktreeMergeFailures.delete(candidate);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
emitJournalEvent(basePath, {
|
|
869
|
+
ts: new Date().toISOString(),
|
|
870
|
+
flowId: randomUUID(),
|
|
871
|
+
seq: 0,
|
|
872
|
+
eventType: "worktree-merge-failed",
|
|
873
|
+
data: { milestoneId, error: msg },
|
|
874
|
+
});
|
|
875
|
+
recentWorktreeMergeFailures.set(key, now);
|
|
876
|
+
}
|
|
877
|
+
|
|
839
878
|
// ─── Session-less merge entry (ADR-016 phase 2 / A1) ─────────────────────
|
|
840
879
|
|
|
841
880
|
/**
|
|
@@ -985,13 +1024,7 @@ function _mergeWorktreeModeImpl(
|
|
|
985
1024
|
error: msg,
|
|
986
1025
|
fallback: "chdir-to-project-root",
|
|
987
1026
|
});
|
|
988
|
-
|
|
989
|
-
ts: new Date().toISOString(),
|
|
990
|
-
flowId: randomUUID(),
|
|
991
|
-
seq: 0,
|
|
992
|
-
eventType: "worktree-merge-failed",
|
|
993
|
-
data: { milestoneId, error: msg },
|
|
994
|
-
});
|
|
1027
|
+
emitWorktreeMergeFailedOnce(originalBasePath || worktreeBasePath, milestoneId, err);
|
|
995
1028
|
// Surface a clear, actionable error. Worktree and milestone branch
|
|
996
1029
|
// are intentionally preserved — nothing has been deleted. User can
|
|
997
1030
|
// retry /gsd dispatch complete-milestone or merge manually once the
|