gsd-pi 2.82.0-dev.2841a1e44 → 2.82.0-dev.98ea09b1e
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 +2 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +7 -0
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +2 -1
- 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 +70 -9
- 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-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 +5 -2
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +13 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +17 -1
- 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 +13 -6
- package/dist/resources/extensions/gsd/migrate/parsers.js +10 -0
- package/dist/resources/extensions/gsd/migration-auto-check.js +12 -17
- 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/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -8
- package/dist/resources/extensions/gsd/prompts/discuss.md +9 -9
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +4 -4
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +3 -3
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -4
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +2 -2
- package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/merge-state.js +6 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +9 -14
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +19 -24
- 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 +7 -8
- package/dist/resources/extensions/gsd/validation.js +23 -1
- package/dist/resources/extensions/gsd/verification-gate.js +68 -7
- 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/visual-brief/page-contract.js +2 -0
- package/dist/resources/extensions/visual-brief/prompts.js +29 -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 +13 -13
- 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/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 +13 -13
- 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/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/app/layout-8c10ec293ae0f1d5.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-6a95bc41e0f7ec89.js → webpack-9a4db269f9ed63ad.js} +1 -1
- package/dist/web/standalone/.next/static/css/746ee28c929d1880.css +1 -0
- package/package.json +2 -2
- 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 +7 -0
- 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/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 +77 -7
- 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-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 +3 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +16 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +17 -1
- 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 +14 -7
- package/src/resources/extensions/gsd/migrate/parsers.ts +11 -0
- package/src/resources/extensions/gsd/migration-auto-check.ts +15 -23
- 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/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -8
- package/src/resources/extensions/gsd/prompts/discuss.md +9 -9
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +4 -4
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +3 -3
- package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/queue.md +4 -4
- package/src/resources/extensions/gsd/prompts/refine-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/merge-state.ts +8 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +12 -15
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +17 -25
- 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/complete-milestone.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +5 -9
- 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/db-authority-regression.test.ts +208 -0
- 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/export-html-enhancements.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-project-prompt-rendering.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/guided-flow.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +6 -6
- 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/migration-auto-check.test.ts +26 -18
- 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-prompt.test.ts +2 -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-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/state-reconciliation-drift.test.ts +119 -23
- 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 +65 -7
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +110 -1
- 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 +38 -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 +12 -9
- package/src/resources/extensions/gsd/validation.ts +23 -1
- package/src/resources/extensions/gsd/verification-gate.ts +78 -6
- 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/visual-brief/page-contract.ts +2 -0
- package/src/resources/extensions/visual-brief/prompts.ts +37 -1
- package/src/resources/extensions/visual-brief/tests/visual-brief.test.ts +40 -0
- package/dist/web/standalone/.next/server/chunks/5822.js +0 -2
- package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +0 -1
- package/dist/web/standalone/.next/static/css/0262768ec1b89d34.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/{Qgr2B_MRhPxC0z8fwv4vT → euQ0CLP_v8V4e76Tu3odJ}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{Qgr2B_MRhPxC0z8fwv4vT → euQ0CLP_v8V4e76Tu3odJ}/_ssgManifest.js +0 -0
|
@@ -6,7 +6,7 @@ import { mkdtempSync, mkdirSync, rmSync, readFileSync, existsSync, writeFileSync
|
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { tmpdir } from 'node:os';
|
|
8
8
|
|
|
9
|
-
import { openDatabase, closeDatabase, insertMilestone, insertSlice, getSlice, getSliceTasks, getTask } from '../gsd-db.ts';
|
|
9
|
+
import { openDatabase, closeDatabase, insertMilestone, insertSlice, getSlice, getSliceTasks, getTask, getGateResults, updateTaskStatus } from '../gsd-db.ts';
|
|
10
10
|
import { handlePlanSlice } from '../tools/plan-slice.ts';
|
|
11
11
|
import { parsePlan } from '../parsers-legacy.ts';
|
|
12
12
|
import { parseTaskPlanFile } from '../files.ts';
|
|
@@ -15,6 +15,10 @@ import { deriveState, invalidateStateCache } from '../state.ts';
|
|
|
15
15
|
function makeTmpBase(): string {
|
|
16
16
|
const base = mkdtempSync(join(tmpdir(), 'gsd-plan-slice-'));
|
|
17
17
|
mkdirSync(join(base, '.gsd', 'milestones', 'M001', 'slices', 'S02', 'tasks'), { recursive: true });
|
|
18
|
+
mkdirSync(join(base, 'src', 'resources', 'extensions', 'gsd', 'tools'), { recursive: true });
|
|
19
|
+
writeFileSync(join(base, 'src', 'resources', 'extensions', 'gsd', 'tools', 'plan-milestone.ts'), '// fixture\n', 'utf-8');
|
|
20
|
+
writeFileSync(join(base, 'src', 'resources', 'extensions', 'gsd', 'tools', 'plan-task.ts'), '// fixture\n', 'utf-8');
|
|
21
|
+
writeFileSync(join(base, 'stale-input.py'), '# fixture\n', 'utf-8');
|
|
18
22
|
return base;
|
|
19
23
|
}
|
|
20
24
|
|
|
@@ -125,6 +129,31 @@ test('handlePlanSlice advances DB-derived state out of planning immediately', as
|
|
|
125
129
|
}
|
|
126
130
|
});
|
|
127
131
|
|
|
132
|
+
test('handlePlanSlice clears sketch flag so DB-derived state leaves refining', async () => {
|
|
133
|
+
const base = makeTmpBase();
|
|
134
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
|
|
138
|
+
insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Planning slice', status: 'pending', demo: 'Rendered plans exist.', isSketch: true });
|
|
139
|
+
|
|
140
|
+
invalidateStateCache();
|
|
141
|
+
const before = await deriveState(base);
|
|
142
|
+
assert.equal(before.phase, 'refining');
|
|
143
|
+
|
|
144
|
+
const result = await handlePlanSlice(validParams(), base);
|
|
145
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
146
|
+
assert.equal(getSlice('M001', 'S02')?.is_sketch, 0, 'planned slice must no longer be treated as a sketch');
|
|
147
|
+
|
|
148
|
+
invalidateStateCache();
|
|
149
|
+
const after = await deriveState(base);
|
|
150
|
+
assert.notEqual(after.phase, 'refining');
|
|
151
|
+
assert.equal(after.progress?.tasks?.total, 2);
|
|
152
|
+
} finally {
|
|
153
|
+
cleanup(base);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
128
157
|
test('handlePlanSlice leaves omitted enrichment fields empty instead of rendering placeholders', async () => {
|
|
129
158
|
const base = makeTmpBase();
|
|
130
159
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -172,6 +201,28 @@ test('handlePlanSlice rejects invalid payloads', async () => {
|
|
|
172
201
|
}
|
|
173
202
|
});
|
|
174
203
|
|
|
204
|
+
test('handlePlanSlice explains string task IO fields must be arrays', async () => {
|
|
205
|
+
const base = makeTmpBase();
|
|
206
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
seedParentSlice();
|
|
210
|
+
const result = await handlePlanSlice({
|
|
211
|
+
...validParams(),
|
|
212
|
+
tasks: [
|
|
213
|
+
{
|
|
214
|
+
...validParams().tasks[0],
|
|
215
|
+
inputs: 'src/index.ts' as unknown as string[],
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
}, base);
|
|
219
|
+
assert.ok('error' in result);
|
|
220
|
+
assert.match(result.error, /validation failed: tasks\[0\]\.inputs must be an array of strings, not string/);
|
|
221
|
+
} finally {
|
|
222
|
+
cleanup(base);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
175
226
|
test('handlePlanSlice rejects absolute task IO paths outside the active worktree', async () => {
|
|
176
227
|
const base = makeTmpBase();
|
|
177
228
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -198,6 +249,63 @@ test('handlePlanSlice rejects absolute task IO paths outside the active worktree
|
|
|
198
249
|
}
|
|
199
250
|
});
|
|
200
251
|
|
|
252
|
+
test('handlePlanSlice rejects missing task input paths before persisting tasks', async () => {
|
|
253
|
+
const base = makeTmpBase();
|
|
254
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
seedParentSlice();
|
|
258
|
+
const result = await handlePlanSlice({
|
|
259
|
+
...validParams(),
|
|
260
|
+
tasks: [
|
|
261
|
+
{
|
|
262
|
+
...validParams().tasks[0],
|
|
263
|
+
inputs: ['fixtures/missing-source.json'],
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
}, base);
|
|
267
|
+
|
|
268
|
+
assert.ok('error' in result);
|
|
269
|
+
assert.match(result.error, /pre-execution validation failed:/);
|
|
270
|
+
assert.match(result.error, /fixtures\/missing-source\.json/);
|
|
271
|
+
assert.equal(getSliceTasks('M001', 'S02').length, 0, 'invalid planning IO must not persist tasks');
|
|
272
|
+
} finally {
|
|
273
|
+
cleanup(base);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('handlePlanSlice rejects task input paths created by later tasks before persisting tasks', async () => {
|
|
278
|
+
const base = makeTmpBase();
|
|
279
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
seedParentSlice();
|
|
283
|
+
const params = validParams();
|
|
284
|
+
const result = await handlePlanSlice({
|
|
285
|
+
...params,
|
|
286
|
+
tasks: [
|
|
287
|
+
{
|
|
288
|
+
...params.tasks[0],
|
|
289
|
+
inputs: ['generated/report.json'],
|
|
290
|
+
expectedOutput: ['generated/summary.json'],
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
...params.tasks[1],
|
|
294
|
+
inputs: [],
|
|
295
|
+
expectedOutput: ['generated/report.json'],
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
}, base);
|
|
299
|
+
|
|
300
|
+
assert.ok('error' in result);
|
|
301
|
+
assert.match(result.error, /pre-execution validation failed:/);
|
|
302
|
+
assert.match(result.error, /sequence violation/);
|
|
303
|
+
assert.equal(getSliceTasks('M001', 'S02').length, 0, 'invalid task ordering must not persist tasks');
|
|
304
|
+
} finally {
|
|
305
|
+
cleanup(base);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
201
309
|
test('handlePlanSlice accepts absolute task IO paths inside the active worktree', async () => {
|
|
202
310
|
const base = makeTmpBase();
|
|
203
311
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -304,3 +412,119 @@ test('handlePlanSlice reruns idempotently and refreshes parse-visible state', as
|
|
|
304
412
|
cleanup(base);
|
|
305
413
|
}
|
|
306
414
|
});
|
|
415
|
+
|
|
416
|
+
test('handlePlanSlice removes omitted pending tasks when replanning a smaller task set', async () => {
|
|
417
|
+
const base = makeTmpBase();
|
|
418
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
419
|
+
|
|
420
|
+
try {
|
|
421
|
+
seedParentSlice();
|
|
422
|
+
const fourTaskPlan = {
|
|
423
|
+
...validParams(),
|
|
424
|
+
tasks: [
|
|
425
|
+
...validParams().tasks,
|
|
426
|
+
{ ...validParams().tasks[0], taskId: 'T03', title: 'Third task' },
|
|
427
|
+
{ ...validParams().tasks[0], taskId: 'T04', title: 'Stale task', inputs: ['stale-input.py'] },
|
|
428
|
+
],
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const first = await handlePlanSlice(fourTaskPlan, base);
|
|
432
|
+
assert.ok(!('error' in first), `unexpected error: ${'error' in first ? first.error : ''}`);
|
|
433
|
+
const staleTaskPlanPath = join(base, '.gsd', 'milestones', 'M001', 'slices', 'S02', 'tasks', 'T04-PLAN.md');
|
|
434
|
+
assert.ok(existsSync(staleTaskPlanPath), 'initial plan should render T04');
|
|
435
|
+
|
|
436
|
+
const second = await handlePlanSlice({
|
|
437
|
+
...validParams(),
|
|
438
|
+
tasks: fourTaskPlan.tasks.filter((task) => task.taskId !== 'T04'),
|
|
439
|
+
}, base);
|
|
440
|
+
assert.ok(!('error' in second), `unexpected error: ${'error' in second ? second.error : ''}`);
|
|
441
|
+
|
|
442
|
+
assert.deepEqual(getSliceTasks('M001', 'S02').map((task) => task.id), ['T01', 'T02', 'T03']);
|
|
443
|
+
assert.equal(getGateResults('M001', 'S02', 'task').some((gate) => gate.task_id === 'T04'), false);
|
|
444
|
+
assert.equal(existsSync(staleTaskPlanPath), false, 'omitted task plan artifact should be removed');
|
|
445
|
+
} finally {
|
|
446
|
+
cleanup(base);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test('handlePlanSlice rejects omitted completed tasks without changing slice or task state', async () => {
|
|
451
|
+
const base = makeTmpBase();
|
|
452
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
seedParentSlice();
|
|
456
|
+
const fourTaskPlan = {
|
|
457
|
+
...validParams(),
|
|
458
|
+
tasks: [
|
|
459
|
+
...validParams().tasks,
|
|
460
|
+
{ ...validParams().tasks[0], taskId: 'T03', title: 'Third task' },
|
|
461
|
+
{ ...validParams().tasks[0], taskId: 'T04', title: 'Stale task', inputs: ['stale-input.py'] },
|
|
462
|
+
],
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const first = await handlePlanSlice(fourTaskPlan, base);
|
|
466
|
+
assert.ok(!('error' in first), `unexpected error: ${'error' in first ? first.error : ''}`);
|
|
467
|
+
const staleTaskPlanPath = join(base, '.gsd', 'milestones', 'M001', 'slices', 'S02', 'tasks', 'T04-PLAN.md');
|
|
468
|
+
assert.ok(existsSync(staleTaskPlanPath), 'initial plan should render T04');
|
|
469
|
+
|
|
470
|
+
updateTaskStatus('M001', 'S02', 'T04', 'complete', '2026-05-12T00:00:00.000Z');
|
|
471
|
+
const tasksBefore = getSliceTasks('M001', 'S02');
|
|
472
|
+
const gatesBefore = getGateResults('M001', 'S02', 'task');
|
|
473
|
+
|
|
474
|
+
const second = await handlePlanSlice({
|
|
475
|
+
...validParams(),
|
|
476
|
+
goal: 'Rejected replan should not persist.',
|
|
477
|
+
tasks: fourTaskPlan.tasks.filter((task) => task.taskId !== 'T04'),
|
|
478
|
+
}, base);
|
|
479
|
+
assert.deepEqual(second, { error: 'cannot remove completed task T04' });
|
|
480
|
+
|
|
481
|
+
assert.equal(getSlice('M001', 'S02')?.goal, 'Persist slice planning through the DB.');
|
|
482
|
+
assert.deepEqual(getSliceTasks('M001', 'S02'), tasksBefore);
|
|
483
|
+
assert.deepEqual(getGateResults('M001', 'S02', 'task'), gatesBefore);
|
|
484
|
+
assert.ok(existsSync(staleTaskPlanPath), 'completed task plan artifact should remain after rejected replan');
|
|
485
|
+
} finally {
|
|
486
|
+
cleanup(base);
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test('regression: validateTasks surfaces clean per-field errors for non-array IO inputs', async () => {
|
|
491
|
+
// Regression for the bug fixed in PR #5872: an earlier refactor on main
|
|
492
|
+
// (0b0e1a901) re-added validateStringArray() calls inside validateTasks
|
|
493
|
+
// without re-adding its import. The catch around validateParams swallowed
|
|
494
|
+
// the ReferenceError into a generic "validation failed: validateStringArray
|
|
495
|
+
// is not defined" message, so silent runtime breakage was possible.
|
|
496
|
+
//
|
|
497
|
+
// Exercise every validateStringArray call site (files, inputs, expectedOutput)
|
|
498
|
+
// so a future missing-import would surface as a per-field assertion failure
|
|
499
|
+
// here, not a deep ReferenceError that's easy to mis-diagnose.
|
|
500
|
+
const base = makeTmpBase();
|
|
501
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
502
|
+
|
|
503
|
+
try {
|
|
504
|
+
seedParentSlice();
|
|
505
|
+
|
|
506
|
+
for (const field of ['files', 'inputs', 'expectedOutput'] as const) {
|
|
507
|
+
const result = await handlePlanSlice({
|
|
508
|
+
...validParams(),
|
|
509
|
+
tasks: [{
|
|
510
|
+
...validParams().tasks[0],
|
|
511
|
+
[field]: 'not-an-array' as unknown as string[],
|
|
512
|
+
}],
|
|
513
|
+
}, base);
|
|
514
|
+
assert.ok('error' in result, `${field}: expected validation error, got success`);
|
|
515
|
+
assert.match(
|
|
516
|
+
result.error,
|
|
517
|
+
new RegExp(`tasks\\[0\\]\\.${field} must be an array`),
|
|
518
|
+
`${field}: expected per-field validation message, got: ${result.error}`,
|
|
519
|
+
);
|
|
520
|
+
assert.doesNotMatch(
|
|
521
|
+
result.error,
|
|
522
|
+
/is not defined/,
|
|
523
|
+
`${field}: validation surfaced ReferenceError — likely a missing import in plan-slice.ts`,
|
|
524
|
+
);
|
|
525
|
+
assert.equal(getSliceTasks('M001', 'S02').length, 0, `${field}: invalid input must not persist`);
|
|
526
|
+
}
|
|
527
|
+
} finally {
|
|
528
|
+
cleanup(base);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
@@ -79,6 +79,23 @@ test('handlePlanTask rejects invalid payloads', async () => {
|
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
+
test('handlePlanTask explains string IO fields must be arrays', async () => {
|
|
83
|
+
const base = makeTmpBase();
|
|
84
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
seedParent();
|
|
88
|
+
const result = await handlePlanTask({
|
|
89
|
+
...validParams(),
|
|
90
|
+
expectedOutput: 'src/output.ts' as unknown as string[],
|
|
91
|
+
}, base);
|
|
92
|
+
assert.ok('error' in result);
|
|
93
|
+
assert.match(result.error, /validation failed: expectedOutput must be an array of strings, not string/);
|
|
94
|
+
} finally {
|
|
95
|
+
cleanup(base);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
82
99
|
test('handlePlanTask rejects absolute task IO paths outside the active worktree', async () => {
|
|
83
100
|
const base = makeTmpBase();
|
|
84
101
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -127,6 +127,61 @@ import { b } from './b';
|
|
|
127
127
|
assert.equal(imports.length, 2);
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
+
test("ignores import-looking string literals in test fixtures", () => {
|
|
131
|
+
const source = `
|
|
132
|
+
const rewritten = source.replace(
|
|
133
|
+
'import { normalizeZagrebBusinessDeadline } from "./cutoff";',
|
|
134
|
+
'const helper = true;'
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
import realThing from "./real-thing";
|
|
138
|
+
`;
|
|
139
|
+
const imports = extractRelativeImports(source);
|
|
140
|
+
assert.deepEqual(imports, [
|
|
141
|
+
{ importPath: "./real-thing", lineNum: 7 },
|
|
142
|
+
]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("ignores import-looking lines inside template literals", () => {
|
|
146
|
+
const source = [
|
|
147
|
+
"const fixture = `",
|
|
148
|
+
"import missingThing from './missing-thing';",
|
|
149
|
+
"`;",
|
|
150
|
+
"",
|
|
151
|
+
"import realThing from './real-thing';",
|
|
152
|
+
].join("\n");
|
|
153
|
+
const imports = extractRelativeImports(source);
|
|
154
|
+
assert.deepEqual(imports, [
|
|
155
|
+
{ importPath: "./real-thing", lineNum: 5 },
|
|
156
|
+
]);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("ignores require() inside string literals", () => {
|
|
160
|
+
const source = [
|
|
161
|
+
'const fixture = "const x = require(\'./missing\');";',
|
|
162
|
+
"const otherFixture = 'const y = require(\"./also-missing\");';",
|
|
163
|
+
"const real = require('./real');",
|
|
164
|
+
].join("\n");
|
|
165
|
+
const imports = extractRelativeImports(source);
|
|
166
|
+
assert.deepEqual(imports, [
|
|
167
|
+
{ importPath: "./real", lineNum: 3 },
|
|
168
|
+
]);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("ignores require() inside template literals", () => {
|
|
172
|
+
const source = [
|
|
173
|
+
"const fixture = `",
|
|
174
|
+
"const x = require('./missing');",
|
|
175
|
+
"`;",
|
|
176
|
+
"",
|
|
177
|
+
"const real = require('./real');",
|
|
178
|
+
].join("\n");
|
|
179
|
+
const imports = extractRelativeImports(source);
|
|
180
|
+
assert.deepEqual(imports, [
|
|
181
|
+
{ importPath: "./real", lineNum: 5 },
|
|
182
|
+
]);
|
|
183
|
+
});
|
|
184
|
+
|
|
130
185
|
test("handles empty source", () => {
|
|
131
186
|
const imports = extractRelativeImports("");
|
|
132
187
|
assert.deepEqual(imports, []);
|
|
@@ -810,6 +865,37 @@ describe("runPostExecutionChecks", () => {
|
|
|
810
865
|
}
|
|
811
866
|
});
|
|
812
867
|
|
|
868
|
+
test("does not fail on import-looking strings in task key files", () => {
|
|
869
|
+
tempDir = join(tmpdir(), `post-exec-test-${Date.now()}`);
|
|
870
|
+
mkdirSync(tempDir, { recursive: true });
|
|
871
|
+
mkdirSync(join(tempDir, "tests"), { recursive: true });
|
|
872
|
+
writeFileSync(join(tempDir, "tests", "real-thing.ts"), "export default true;");
|
|
873
|
+
writeFileSync(
|
|
874
|
+
join(tempDir, "tests", "source-verifier.test.ts"),
|
|
875
|
+
`
|
|
876
|
+
const rewritten = source.replace(
|
|
877
|
+
'import { normalizeZagrebBusinessDeadline } from "./cutoff";',
|
|
878
|
+
'const helper = true;'
|
|
879
|
+
);
|
|
880
|
+
|
|
881
|
+
import realThing from "./real-thing";
|
|
882
|
+
assert.ok(realThing);
|
|
883
|
+
`
|
|
884
|
+
);
|
|
885
|
+
|
|
886
|
+
try {
|
|
887
|
+
const task = createTask({
|
|
888
|
+
id: "T03",
|
|
889
|
+
key_files: ["tests/source-verifier.test.ts"],
|
|
890
|
+
});
|
|
891
|
+
const result = runPostExecutionChecks(task, [], tempDir);
|
|
892
|
+
assert.equal(result.status, "pass");
|
|
893
|
+
assert.deepEqual(result.checks, []);
|
|
894
|
+
} finally {
|
|
895
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
|
|
813
899
|
test("returns fail status when blocking failure exists", () => {
|
|
814
900
|
tempDir = join(tmpdir(), `post-exec-test-${Date.now()}`);
|
|
815
901
|
mkdirSync(tempDir, { recursive: true });
|
|
@@ -11,7 +11,7 @@ const source = readFileSync(
|
|
|
11
11
|
|
|
12
12
|
test("postUnitPreVerification blocks on git action failure", () => {
|
|
13
13
|
const failureBlock = extractSourceRegion(source, 'if (gitResult.status === "failed")');
|
|
14
|
-
assert.ok(failureBlock.includes('ctx.ui.notify(failureMsg, "error")'));
|
|
14
|
+
assert.ok(failureBlock.includes('ctx.ui.notify(failureMsg, opts?.softFailure ? "warning" : "error")'));
|
|
15
15
|
assert.ok(failureBlock.includes("await pauseAuto(ctx, pi)"));
|
|
16
16
|
assert.ok(failureBlock.includes('return "dispatched"'));
|
|
17
17
|
assert.ok(!failureBlock.includes("git-action-failed-nonblocking"));
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* 2. File path consistency — files exist vs prior expected_output
|
|
10
10
|
* 3. Task ordering — detect impossible read-before-create
|
|
11
11
|
* 4. Interface contracts — contradictory function signatures
|
|
12
|
+
* 5. Verify commands — reject unsafe or non-runnable task verification
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { describe, test, mock } from "node:test";
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
checkFilePathConsistency,
|
|
23
24
|
checkTaskOrdering,
|
|
24
25
|
checkInterfaceContracts,
|
|
26
|
+
checkVerificationCommands,
|
|
25
27
|
runPreExecutionChecks,
|
|
26
28
|
normalizeFilePath,
|
|
27
29
|
type PreExecutionResult,
|
|
@@ -812,6 +814,33 @@ function process(a: number): number
|
|
|
812
814
|
});
|
|
813
815
|
});
|
|
814
816
|
|
|
817
|
+
describe("checkVerificationCommands", () => {
|
|
818
|
+
test("accepts pipe-free pytest Verify command", () => {
|
|
819
|
+
const results = checkVerificationCommands([
|
|
820
|
+
createTask({
|
|
821
|
+
id: "T01",
|
|
822
|
+
verify: "python3 -m pytest tests/ -q --tb=short",
|
|
823
|
+
}),
|
|
824
|
+
]);
|
|
825
|
+
|
|
826
|
+
assert.deepEqual(results, []);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
test("rejects piped pytest Verify command", () => {
|
|
830
|
+
const results = checkVerificationCommands([
|
|
831
|
+
createTask({
|
|
832
|
+
id: "T01",
|
|
833
|
+
verify: "python3 -m pytest tests/ -q --tb=short 2>&1 | tail -5",
|
|
834
|
+
}),
|
|
835
|
+
]);
|
|
836
|
+
|
|
837
|
+
assert.equal(results.length, 1);
|
|
838
|
+
assert.equal(results[0]?.category, "tool");
|
|
839
|
+
assert.equal(results[0]?.blocking, true);
|
|
840
|
+
assert.match(results[0]?.message ?? "", /shell control syntax/);
|
|
841
|
+
});
|
|
842
|
+
});
|
|
843
|
+
|
|
815
844
|
// ─── runPreExecutionChecks Integration Tests ─────────────────────────────────
|
|
816
845
|
|
|
817
846
|
describe("runPreExecutionChecks", () => {
|
|
@@ -847,6 +876,30 @@ describe("runPreExecutionChecks", () => {
|
|
|
847
876
|
}
|
|
848
877
|
});
|
|
849
878
|
|
|
879
|
+
test("returns fail status for unsafe Verify command before execution", async () => {
|
|
880
|
+
tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
|
|
881
|
+
mkdirSync(tempDir, { recursive: true });
|
|
882
|
+
|
|
883
|
+
try {
|
|
884
|
+
const tasks = [
|
|
885
|
+
createTask({
|
|
886
|
+
id: "T01",
|
|
887
|
+
verify: "python3 -m pytest tests/ -q --tb=short 2>&1 | tail -5",
|
|
888
|
+
}),
|
|
889
|
+
];
|
|
890
|
+
|
|
891
|
+
const result = await runPreExecutionChecks(tasks, tempDir);
|
|
892
|
+
|
|
893
|
+
assert.equal(result.status, "fail");
|
|
894
|
+
assert.equal(result.checks.length, 1);
|
|
895
|
+
assert.equal(result.checks[0]?.category, "tool");
|
|
896
|
+
assert.equal(result.checks[0]?.blocking, true);
|
|
897
|
+
assert.match(result.checks[0]?.message ?? "", /Unsafe or non-runnable Verify command/);
|
|
898
|
+
} finally {
|
|
899
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
|
|
850
903
|
test("returns fail status when blocking failure exists", async () => {
|
|
851
904
|
tempDir = join(tmpdir(), `pre-exec-test-${Date.now()}`);
|
|
852
905
|
mkdirSync(tempDir, { recursive: true });
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { loadPrompt } from "../prompt-loader.ts";
|
|
5
|
+
|
|
6
|
+
test("loadPrompt reports missing template variables with balanced braces", () => {
|
|
7
|
+
assert.throws(
|
|
8
|
+
() => loadPrompt("guided-discuss-milestone", {
|
|
9
|
+
milestoneId: "M001",
|
|
10
|
+
milestoneTitle: "Missing working directory",
|
|
11
|
+
structuredQuestionsAvailable: "false",
|
|
12
|
+
fastPathInstruction: "",
|
|
13
|
+
inlinedTemplates: "context template",
|
|
14
|
+
commitInstruction: "Do not commit during this test.",
|
|
15
|
+
}),
|
|
16
|
+
(error) => {
|
|
17
|
+
assert.ok(error instanceof Error);
|
|
18
|
+
assert.match(error.message, /template declares \{\{workingDirectory\}\} but no value was provided/);
|
|
19
|
+
assert.doesNotMatch(error.message, /\{\{workingDirectory\}\}\}/);
|
|
20
|
+
return true;
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Regression
|
|
3
|
-
* block completion when VALIDATION
|
|
2
|
+
* Regression tests for non-passing VALIDATION verdicts: completing-milestone
|
|
3
|
+
* dispatch must block completion when VALIDATION needs remediation or attention.
|
|
4
4
|
*
|
|
5
5
|
* Without this guard, needs-remediation + allSlicesDone causes a loop:
|
|
6
6
|
* complete-milestone dispatched → agent refuses (correct) → no SUMMARY
|
|
@@ -66,6 +66,50 @@ test("completing-milestone blocks when VALIDATION verdict is needs-remediation (
|
|
|
66
66
|
}
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
test("completing-milestone blocks when VALIDATION verdict is needs-attention (#5747)", async () => {
|
|
70
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-attention-"));
|
|
71
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
writeFileSync(
|
|
75
|
+
join(base, ".gsd", "milestones", "M001", "M001-VALIDATION.md"),
|
|
76
|
+
[
|
|
77
|
+
"---",
|
|
78
|
+
"verdict: needs-attention",
|
|
79
|
+
"remediation_round: 0",
|
|
80
|
+
"---",
|
|
81
|
+
"",
|
|
82
|
+
"# Validation Report",
|
|
83
|
+
"",
|
|
84
|
+
"Acceptance proof is incomplete and needs human attention.",
|
|
85
|
+
].join("\n"),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const ctx = {
|
|
89
|
+
mid: "M001",
|
|
90
|
+
midTitle: "Test Milestone",
|
|
91
|
+
basePath: base,
|
|
92
|
+
state: { phase: "completing-milestone" } as any,
|
|
93
|
+
prefs: {} as any,
|
|
94
|
+
session: undefined,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const result = await completingRule!.match(ctx);
|
|
98
|
+
|
|
99
|
+
assert.ok(result !== null, "rule should match");
|
|
100
|
+
assert.equal(result!.action, "stop", "should return stop action");
|
|
101
|
+
if (result!.action === "stop") {
|
|
102
|
+
assert.equal(result!.level, "warning", "should be warning level (pausable)");
|
|
103
|
+
assert.ok(
|
|
104
|
+
result!.reason.includes("needs-attention"),
|
|
105
|
+
"reason should mention needs-attention",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
} finally {
|
|
109
|
+
rmSync(base, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
69
113
|
test("completing-milestone proceeds normally when VALIDATION verdict is pass (#2675 guard)", async () => {
|
|
70
114
|
const base = mkdtempSync(join(tmpdir(), "gsd-remediation-"));
|
|
71
115
|
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
@@ -5,6 +5,7 @@ import test from "node:test";
|
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
+
_hasEmptyAgentEndContent,
|
|
8
9
|
_handleSessionSwitchAgentEnd,
|
|
9
10
|
isBareClaudeCodeStreamAbortPlaceholder,
|
|
10
11
|
isClaudeCodeSessionSwitchAbortMessage,
|
|
@@ -188,6 +189,15 @@ test("empty-content aborted during session-switch is silently ignored", () => {
|
|
|
188
189
|
assert.equal(cancelledWith, null);
|
|
189
190
|
});
|
|
190
191
|
|
|
192
|
+
test("missing agent_end content is classified as empty abort content", () => {
|
|
193
|
+
// Providers may omit content entirely for a late aborted agent_end. That is
|
|
194
|
+
// equivalent to empty content and must not pause/cancel the next unit.
|
|
195
|
+
assert.equal(_hasEmptyAgentEndContent(undefined), true);
|
|
196
|
+
assert.equal(_hasEmptyAgentEndContent(null), true);
|
|
197
|
+
assert.equal(_hasEmptyAgentEndContent([]), true);
|
|
198
|
+
assert.equal(_hasEmptyAgentEndContent([{ type: "text", text: "partial" }]), false);
|
|
199
|
+
});
|
|
200
|
+
|
|
191
201
|
test("completed assistant content with aborted stopReason during session-switch is ignored", () => {
|
|
192
202
|
// newSession() can abort the just-finished provider stream while the last
|
|
193
203
|
// assistant message still carries the completed unit summary. That is a
|
|
@@ -99,19 +99,49 @@ test("auto bootstrap validates blocked directories before touching .gsd migratio
|
|
|
99
99
|
|
|
100
100
|
test("fresh start registers the auto worker before bootstrap enters worktree flow (#5405)", () => {
|
|
101
101
|
const autoSrc = readGsdFile("auto.ts");
|
|
102
|
+
const autoStartSrc = readGsdFile("auto-start.ts");
|
|
102
103
|
const startAutoIdx = autoSrc.indexOf("export async function startAuto(");
|
|
103
104
|
const startAutoBody = autoSrc.slice(startAutoIdx);
|
|
105
|
+
const bootstrapIdx = autoStartSrc.indexOf("export async function bootstrapAutoSession(");
|
|
106
|
+
const bootstrapBody = autoStartSrc.slice(bootstrapIdx);
|
|
104
107
|
|
|
105
|
-
const preBootstrapRegisterIdx = startAutoBody.indexOf("registerAutoWorkerForSession(s, base);");
|
|
106
108
|
const bootstrapCallIdx = startAutoBody.indexOf("const ready = await bootstrapAutoSession(");
|
|
109
|
+
const preBootstrapBody = startAutoBody.slice(0, bootstrapCallIdx);
|
|
110
|
+
const preBootstrapRegisterIdx = preBootstrapBody.lastIndexOf("registerAutoWorkerForSession(s, base);");
|
|
111
|
+
const resumeSectionIdx = startAutoBody.indexOf("if (s.paused) {");
|
|
112
|
+
const freshStartSectionIdx = startAutoBody.indexOf("// ── Fresh start path — delegated to auto-start.ts ──");
|
|
113
|
+
const resumeBody = startAutoBody.slice(resumeSectionIdx, freshStartSectionIdx);
|
|
114
|
+
const resumeDbOpenIdx = resumeBody.indexOf("await openProjectDbIfPresent(base);");
|
|
115
|
+
const resumeRegisterIdx = resumeBody.indexOf("registerAutoWorkerForSession(s, base);");
|
|
116
|
+
const resumeEnterMilestoneIdx = resumeBody.indexOf("buildLifecycle().enterMilestone");
|
|
117
|
+
const dbOpenIdx = bootstrapBody.indexOf("await openProjectDbIfPresent(base);");
|
|
118
|
+
const bootstrapRegisterIdx = bootstrapBody.indexOf("registerAutoWorkerForSession(base);");
|
|
119
|
+
const enterMilestoneIdx = bootstrapBody.indexOf("buildLifecycle().enterMilestone");
|
|
107
120
|
|
|
108
121
|
assert.ok(startAutoIdx > -1, "startAuto should exist");
|
|
109
122
|
assert.ok(preBootstrapRegisterIdx > -1, "startAuto should register worker before bootstrap");
|
|
110
123
|
assert.ok(bootstrapCallIdx > -1, "startAuto should call bootstrapAutoSession");
|
|
124
|
+
assert.ok(resumeSectionIdx > -1, "startAuto should have resume milestone entry flow");
|
|
125
|
+
assert.ok(freshStartSectionIdx > resumeSectionIdx, "resume assertions should be scoped before fresh start");
|
|
126
|
+
assert.ok(resumeDbOpenIdx > -1, "resume should open DB before milestone entry");
|
|
127
|
+
assert.ok(resumeRegisterIdx > -1, "resume should register worker before milestone entry");
|
|
128
|
+
assert.ok(resumeEnterMilestoneIdx > -1, "resume should enter milestones through lifecycle");
|
|
129
|
+
assert.ok(bootstrapIdx > -1, "bootstrapAutoSession should exist");
|
|
130
|
+
assert.ok(dbOpenIdx > -1, "bootstrap should open the project DB");
|
|
131
|
+
assert.ok(bootstrapRegisterIdx > -1, "bootstrap should register worker after DB open");
|
|
132
|
+
assert.ok(enterMilestoneIdx > -1, "bootstrap should enter milestones through lifecycle");
|
|
111
133
|
assert.ok(
|
|
112
134
|
preBootstrapRegisterIdx < bootstrapCallIdx,
|
|
113
135
|
"worker registration must happen before bootstrap so enterMilestone can claim milestone leases on first entry",
|
|
114
136
|
);
|
|
137
|
+
assert.ok(
|
|
138
|
+
dbOpenIdx < bootstrapRegisterIdx && bootstrapRegisterIdx < enterMilestoneIdx,
|
|
139
|
+
"bootstrap must open DB and register worker before first enterMilestone",
|
|
140
|
+
);
|
|
141
|
+
assert.ok(
|
|
142
|
+
resumeDbOpenIdx < resumeRegisterIdx && resumeRegisterIdx < resumeEnterMilestoneIdx,
|
|
143
|
+
"resume must open DB and register worker before first enterMilestone",
|
|
144
|
+
);
|
|
115
145
|
});
|
|
116
146
|
|
|
117
147
|
test("startAutoDetached reports failures asynchronously (#3733)", () => {
|