gsd-pi 2.78.1-dev.b0759e59b → 2.78.1-dev.d8826a445
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 +8 -5
- package/dist/headless-recover.d.ts +23 -0
- package/dist/headless-recover.js +93 -0
- package/dist/headless.js +9 -0
- package/dist/help-text.js +1 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/tools/intent.js +8 -1
- package/dist/resources/extensions/gsd/auto/phases.js +7 -2
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +7 -58
- package/dist/resources/extensions/gsd/auto-post-unit.js +14 -28
- package/dist/resources/extensions/gsd/auto-start.js +1 -8
- package/dist/resources/extensions/gsd/auto-worktree.js +244 -216
- package/dist/resources/extensions/gsd/auto.js +86 -7
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -77
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -16
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
- package/dist/resources/extensions/gsd/commands-codebase.js +2 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +5 -5
- package/dist/resources/extensions/gsd/commands-logs.js +2 -2
- package/dist/resources/extensions/gsd/commands-scan.js +2 -2
- package/dist/resources/extensions/gsd/commands-ship.js +2 -2
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +5 -5
- package/dist/resources/extensions/gsd/db-writer.js +106 -95
- package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
- package/dist/resources/extensions/gsd/dispatch-guard.js +6 -10
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -2
- package/dist/resources/extensions/gsd/gsd-db.js +268 -8
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +141 -32
- package/dist/resources/extensions/gsd/markdown-renderer.js +14 -51
- package/dist/resources/extensions/gsd/metrics.js +287 -1
- package/dist/resources/extensions/gsd/parallel-merge.js +14 -13
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +5 -2
- package/dist/resources/extensions/gsd/paths.js +114 -9
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
- package/dist/resources/extensions/gsd/queue-order.js +6 -1
- package/dist/resources/extensions/gsd/rethink.js +2 -2
- package/dist/resources/extensions/gsd/state.js +91 -372
- package/dist/resources/extensions/gsd/templates/project.md +10 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -5
- package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -12
- package/dist/resources/extensions/gsd/tools/complete-task.js +19 -31
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -5
- package/dist/resources/extensions/gsd/workflow-manifest.js +2 -1
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -21
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/workflow-reconcile.js +3 -3
- package/dist/resources/extensions/gsd/workspace.js +59 -0
- package/dist/resources/extensions/gsd/worktree-command.js +4 -3
- package/dist/resources/extensions/gsd/worktree-resolver.js +15 -2
- package/dist/resources/extensions/gsd/write-intercept.js +3 -3
- 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 +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +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.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
- package/dist/web/standalone/.next/server/chunks/6336.js +1 -0
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/README.md +2 -11
- package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +28 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +28 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +94 -4
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +6 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +56 -2
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +226 -0
- package/packages/mcp-server/src/parse-workflow-args.test.ts +80 -0
- package/packages/mcp-server/src/remote-questions.test.ts +103 -0
- package/packages/mcp-server/src/remote-questions.ts +35 -0
- package/packages/mcp-server/src/server.ts +129 -6
- package/packages/mcp-server/src/workflow-tools.ts +62 -3
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/browser-tools/tools/intent.ts +13 -2
- package/src/resources/extensions/gsd/auto/phases.ts +8 -2
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +14 -62
- package/src/resources/extensions/gsd/auto-post-unit.ts +15 -27
- package/src/resources/extensions/gsd/auto-start.ts +1 -8
- package/src/resources/extensions/gsd/auto-worktree.ts +286 -251
- package/src/resources/extensions/gsd/auto.ts +102 -7
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +9 -84
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -17
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
- package/src/resources/extensions/gsd/commands-codebase.ts +2 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +5 -5
- package/src/resources/extensions/gsd/commands-logs.ts +2 -2
- package/src/resources/extensions/gsd/commands-scan.ts +2 -2
- package/src/resources/extensions/gsd/commands-ship.ts +2 -2
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +5 -5
- package/src/resources/extensions/gsd/db-writer.ts +123 -94
- package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +6 -11
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -2
- package/src/resources/extensions/gsd/gsd-db.ts +269 -8
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +181 -32
- package/src/resources/extensions/gsd/markdown-renderer.ts +13 -64
- package/src/resources/extensions/gsd/metrics.ts +321 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +14 -13
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +5 -2
- package/src/resources/extensions/gsd/paths.ts +122 -9
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
- package/src/resources/extensions/gsd/queue-order.ts +6 -1
- package/src/resources/extensions/gsd/rethink.ts +2 -2
- package/src/resources/extensions/gsd/state.ts +91 -389
- package/src/resources/extensions/gsd/templates/project.md +10 -0
- package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +21 -34
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +6 -7
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +8 -6
- package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +12 -27
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +18 -5
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
- package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +14 -16
- package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +10 -38
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +136 -56
- package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +119 -61
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +6 -20
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +4 -5
- package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +14 -15
- package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +11 -16
- package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
- package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
- package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +15 -36
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/handler-worktree-write-isolation.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +15 -15
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +15 -5
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +371 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +14 -8
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
- package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +25 -16
- package/src/resources/extensions/gsd/tests/projection-regression.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +184 -0
- package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +28 -16
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +4 -0
- package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +3 -4
- package/src/resources/extensions/gsd/tests/slice-disk-reconcile.test.ts +10 -56
- package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +15 -16
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +23 -27
- package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +13 -14
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +453 -0
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +10 -33
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
- package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +12 -7
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +26 -3
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/workspace.test.ts +190 -0
- package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +65 -71
- package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +26 -151
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -52
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -5
- package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -14
- package/src/resources/extensions/gsd/tools/complete-task.ts +19 -34
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +7 -5
- package/src/resources/extensions/gsd/workflow-manifest.ts +4 -1
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -18
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -3
- package/src/resources/extensions/gsd/workspace.ts +95 -0
- package/src/resources/extensions/gsd/worktree-command.ts +4 -3
- package/src/resources/extensions/gsd/worktree-resolver.ts +16 -2
- package/src/resources/extensions/gsd/write-intercept.ts +3 -3
- package/dist/web/standalone/.next/server/chunks/8527.js +0 -1
- /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → AT5qi39nKXkdmQIOIoh0f}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → AT5qi39nKXkdmQIOIoh0f}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// GSD-2 + metrics-lock-retry-sleep.test.ts: verify sleep between lock acquire retries (M3 follow-up)
|
|
2
|
+
/**
|
|
3
|
+
* Verifies that acquireLock sleeps between non-stale-evicting retries:
|
|
4
|
+
*
|
|
5
|
+
* 1. Under contention: a child process holds the lock for 100ms; the main
|
|
6
|
+
* process acquireLock attempt should make at most ~30 sleepy retries
|
|
7
|
+
* (100ms contention / 5ms sleep) — not thousands as would occur without
|
|
8
|
+
* the sleep.
|
|
9
|
+
*
|
|
10
|
+
* 2. Stale-lock eviction path: when a stale lock is detected and forcibly
|
|
11
|
+
* removed, the subsequent acquire attempt does NOT sleep — the sleepy
|
|
12
|
+
* retry counter stays at zero and the acquire succeeds immediately.
|
|
13
|
+
*
|
|
14
|
+
* 3. Regression: M3 lock-hardening tests still pass (invoked separately
|
|
15
|
+
* as part of the test suite — tested here by exercising the same
|
|
16
|
+
* stale-lock + PID-stamp code paths through snapshotUnitMetrics).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
20
|
+
import assert from "node:assert/strict";
|
|
21
|
+
import {
|
|
22
|
+
mkdtempSync,
|
|
23
|
+
mkdirSync,
|
|
24
|
+
rmSync,
|
|
25
|
+
writeFileSync,
|
|
26
|
+
existsSync,
|
|
27
|
+
utimesSync,
|
|
28
|
+
} from "node:fs";
|
|
29
|
+
import { join } from "node:path";
|
|
30
|
+
import { tmpdir } from "node:os";
|
|
31
|
+
import { spawnSync, spawn } from "node:child_process";
|
|
32
|
+
|
|
33
|
+
import {
|
|
34
|
+
initMetrics,
|
|
35
|
+
resetMetrics,
|
|
36
|
+
snapshotUnitMetrics,
|
|
37
|
+
STALE_LOCK_THRESHOLD_MS,
|
|
38
|
+
LOCK_RETRY_INTERVAL_MS,
|
|
39
|
+
getLockSleepyRetries,
|
|
40
|
+
resetLockSleepyRetries,
|
|
41
|
+
} from "../metrics.js";
|
|
42
|
+
|
|
43
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
function makeProjectDir(): string {
|
|
46
|
+
const dir = mkdtempSync(join(tmpdir(), "gsd-metrics-sleep-"));
|
|
47
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
48
|
+
return dir;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function metricsPath(base: string): string {
|
|
52
|
+
return join(base, ".gsd", "metrics.json");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function lockPath(base: string): string {
|
|
56
|
+
return metricsPath(base) + ".lock";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function assistantCtx(): any {
|
|
60
|
+
return {
|
|
61
|
+
sessionManager: {
|
|
62
|
+
getEntries: () => [
|
|
63
|
+
{
|
|
64
|
+
type: "message",
|
|
65
|
+
id: "entry-0",
|
|
66
|
+
parentId: null,
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
message: {
|
|
69
|
+
role: "assistant",
|
|
70
|
+
content: [{ type: "text", text: "Done" }],
|
|
71
|
+
usage: {
|
|
72
|
+
input: 100,
|
|
73
|
+
output: 50,
|
|
74
|
+
cacheRead: 0,
|
|
75
|
+
cacheWrite: 0,
|
|
76
|
+
totalTokens: 150,
|
|
77
|
+
cost: 0.001,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Worker that acquires the lock using O_EXCL and holds it for holdMs, then releases.
|
|
87
|
+
// Writes its PID to stdout once the lock is acquired so the caller can synchronize.
|
|
88
|
+
const LOCK_HOLDER_WORKER = `
|
|
89
|
+
const { openSync, closeSync, writeFileSync, unlinkSync } = require('node:fs');
|
|
90
|
+
const lockPath = process.env.GSD_TEST_LOCK_PATH;
|
|
91
|
+
const holdMs = parseInt(process.env.GSD_TEST_HOLD_MS || '100', 10);
|
|
92
|
+
|
|
93
|
+
const deadline = Date.now() + 3000;
|
|
94
|
+
let acquired = false;
|
|
95
|
+
while (Date.now() < deadline) {
|
|
96
|
+
try {
|
|
97
|
+
const fd = openSync(lockPath, 'wx');
|
|
98
|
+
closeSync(fd);
|
|
99
|
+
writeFileSync(lockPath, process.pid + '\\n' + new Date().toISOString() + '\\n', 'utf-8');
|
|
100
|
+
acquired = true;
|
|
101
|
+
break;
|
|
102
|
+
} catch { /* retry */ }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!acquired) {
|
|
106
|
+
process.stderr.write('Worker failed to acquire lock\\n');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Signal that the lock is held.
|
|
111
|
+
process.stdout.write(String(process.pid) + '\\n');
|
|
112
|
+
|
|
113
|
+
// Hold the lock.
|
|
114
|
+
const releaseAt = Date.now() + holdMs;
|
|
115
|
+
while (Date.now() < releaseAt) { /* spin for short hold */ }
|
|
116
|
+
|
|
117
|
+
try { unlinkSync(lockPath); } catch {}
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
// ─── Tests ────────────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
describe("metrics lock retry sleep (M3 follow-up)", () => {
|
|
123
|
+
let tmpDir: string;
|
|
124
|
+
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
tmpDir = makeProjectDir();
|
|
127
|
+
resetLockSleepyRetries();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
afterEach(() => {
|
|
131
|
+
resetMetrics();
|
|
132
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ── Test 1: Under contention, sleep caps sleepy retries ───────────────────
|
|
136
|
+
|
|
137
|
+
test("sleepy retry count is bounded under lock contention (5ms sleep, 500ms timeout)", () => {
|
|
138
|
+
const lp = lockPath(tmpDir);
|
|
139
|
+
|
|
140
|
+
// Spawn a child that holds the lock for 100ms.
|
|
141
|
+
// We use spawnSync with a timeout > holdMs so it completes before our check.
|
|
142
|
+
// But we need the child to hold first THEN we attempt. Since spawnSync blocks,
|
|
143
|
+
// we pre-create the lock file instead to simulate contention for 100ms.
|
|
144
|
+
|
|
145
|
+
// Simulate: lock is held for 100ms from now, then released.
|
|
146
|
+
// We create the lock file manually (as if another process holds it) and
|
|
147
|
+
// schedule its removal after 100ms using a child process that holds then deletes.
|
|
148
|
+
//
|
|
149
|
+
// Strategy: use a background worker via spawnSync with hold=100ms,
|
|
150
|
+
// but we can't overlap with spawnSync. Instead, we directly test with
|
|
151
|
+
// snapshotUnitMetrics + a pre-placed lock + a child that will remove it.
|
|
152
|
+
//
|
|
153
|
+
// Simplest approach: place the lock file, run snapshotUnitMetrics
|
|
154
|
+
// which calls saveLedger → acquireLock. acquireLock will retry for 100ms
|
|
155
|
+
// (lock file just sits there), then we remove it from a thread... but
|
|
156
|
+
// there are no threads in Node main.
|
|
157
|
+
//
|
|
158
|
+
// The correct approach is to spawn a background child that holds the lock
|
|
159
|
+
// for 100ms, and run acquireLock from the main process concurrently via
|
|
160
|
+
// snapshotUnitMetrics (which is synchronous). The child is started as a
|
|
161
|
+
// background process using spawn (not spawnSync), then we call
|
|
162
|
+
// snapshotUnitMetrics synchronously and block until it acquires or times out.
|
|
163
|
+
|
|
164
|
+
const child = spawn(process.execPath, ["-e", LOCK_HOLDER_WORKER], {
|
|
165
|
+
env: {
|
|
166
|
+
...process.env,
|
|
167
|
+
GSD_TEST_LOCK_PATH: lp,
|
|
168
|
+
GSD_TEST_HOLD_MS: "100",
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Wait until the child has acquired the lock (it writes PID to stdout).
|
|
173
|
+
// Poll until the lock file exists (the child acquired it).
|
|
174
|
+
const waitStart = Date.now();
|
|
175
|
+
while (!existsSync(lp) && Date.now() - waitStart < 2000) {
|
|
176
|
+
// Busy-wait for child to acquire the lock — this is test setup, not
|
|
177
|
+
// production code. Short window (child acquires almost immediately).
|
|
178
|
+
const arr = new Int32Array(new SharedArrayBuffer(4));
|
|
179
|
+
Atomics.wait(arr, 0, 0, 5);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
assert.ok(existsSync(lp), "child must have acquired the lock before we attempt");
|
|
183
|
+
|
|
184
|
+
// Now call snapshotUnitMetrics synchronously. It will call saveLedger →
|
|
185
|
+
// acquireLock, which retries with 5ms sleep until the child releases (~100ms).
|
|
186
|
+
initMetrics(tmpDir);
|
|
187
|
+
const ctx = assistantCtx();
|
|
188
|
+
const start = Date.now();
|
|
189
|
+
const unit = snapshotUnitMetrics(ctx, "execute-task", "M001/S01/T01", Date.now() - 500, "test-model");
|
|
190
|
+
const elapsed = Date.now() - start;
|
|
191
|
+
|
|
192
|
+
// Clean up child.
|
|
193
|
+
child.kill();
|
|
194
|
+
|
|
195
|
+
// The operation must succeed (either by acquiring after child releases, or
|
|
196
|
+
// by timeout-fallback write in saveLedger).
|
|
197
|
+
assert.ok(unit !== null, "snapshotUnitMetrics must succeed under contention");
|
|
198
|
+
|
|
199
|
+
const retries = getLockSleepyRetries();
|
|
200
|
+
|
|
201
|
+
// With 5ms sleep and ~100ms contention, expect roughly 20 sleepy retries.
|
|
202
|
+
// Upper bound: 500ms timeout / 5ms = 100. With 2s default timeout, upper
|
|
203
|
+
// bound is 2000ms / 5ms = 400. But we want far fewer than the ~20,000
|
|
204
|
+
// that would occur without any sleep.
|
|
205
|
+
//
|
|
206
|
+
// Conservative assertion: fewer than 200 sleepy retries across the wait
|
|
207
|
+
// (vs ~20,000 without sleep). This is the key regression guard.
|
|
208
|
+
assert.ok(
|
|
209
|
+
retries < 200,
|
|
210
|
+
`Expected < 200 sleepy retries with 5ms sleep, got ${retries}. ` +
|
|
211
|
+
`Elapsed: ${elapsed}ms. LOCK_RETRY_INTERVAL_MS=${LOCK_RETRY_INTERVAL_MS}`,
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// Sanity: at least a few retries happened (lock was actually contested).
|
|
215
|
+
assert.ok(
|
|
216
|
+
retries >= 1,
|
|
217
|
+
`Expected at least 1 sleepy retry (lock was held by child), got ${retries}`,
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// ── Test 2: Stale-lock eviction does NOT sleep ────────────────────────────
|
|
222
|
+
|
|
223
|
+
test("stale-lock eviction path retries immediately without incrementing sleepy counter", () => {
|
|
224
|
+
const lp = lockPath(tmpDir);
|
|
225
|
+
|
|
226
|
+
// Create a stale lock (mtime older than STALE_LOCK_THRESHOLD_MS).
|
|
227
|
+
writeFileSync(lp, `999999\n${new Date(Date.now() - STALE_LOCK_THRESHOLD_MS - 500).toISOString()}\n`, "utf-8");
|
|
228
|
+
const staleTime = (Date.now() - STALE_LOCK_THRESHOLD_MS - 500) / 1000;
|
|
229
|
+
utimesSync(lp, staleTime, staleTime);
|
|
230
|
+
|
|
231
|
+
assert.ok(existsSync(lp), "stale lock file must exist before acquire");
|
|
232
|
+
|
|
233
|
+
initMetrics(tmpDir);
|
|
234
|
+
const ctx = assistantCtx();
|
|
235
|
+
|
|
236
|
+
const start = Date.now();
|
|
237
|
+
const unit = snapshotUnitMetrics(ctx, "execute-task", "M002/S01/T01", Date.now() - 200, "test-model");
|
|
238
|
+
const elapsed = Date.now() - start;
|
|
239
|
+
|
|
240
|
+
assert.ok(unit !== null, "snapshotUnitMetrics must succeed after stale-lock eviction");
|
|
241
|
+
|
|
242
|
+
const retries = getLockSleepyRetries();
|
|
243
|
+
|
|
244
|
+
// The stale-lock path uses `continue` (no sleep), so the sleepy retry
|
|
245
|
+
// counter must be zero: the lock was evicted and the very next openSync
|
|
246
|
+
// call succeeded — no sleepy retry was needed.
|
|
247
|
+
assert.equal(
|
|
248
|
+
retries,
|
|
249
|
+
0,
|
|
250
|
+
`Expected 0 sleepy retries after stale-lock eviction, got ${retries}. ` +
|
|
251
|
+
`Elapsed: ${elapsed}ms`,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// Should complete very quickly (no artificial delay).
|
|
255
|
+
assert.ok(
|
|
256
|
+
elapsed < 200,
|
|
257
|
+
`Stale-lock recovery should complete quickly, took ${elapsed}ms`,
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// ── Test 3: M3 regression — stale lock recovers + metrics written ─────────
|
|
262
|
+
|
|
263
|
+
test("M3 regression: stale lock from dead process is cleared and metrics are written", () => {
|
|
264
|
+
const lp = lockPath(tmpDir);
|
|
265
|
+
|
|
266
|
+
// Backdate the lock file to simulate a crashed process.
|
|
267
|
+
const stalePid = 9999999;
|
|
268
|
+
writeFileSync(
|
|
269
|
+
lp,
|
|
270
|
+
`${stalePid}\n${new Date(Date.now() - STALE_LOCK_THRESHOLD_MS - 1000).toISOString()}\n`,
|
|
271
|
+
"utf-8",
|
|
272
|
+
);
|
|
273
|
+
const staleTime = (Date.now() - STALE_LOCK_THRESHOLD_MS - 1000) / 1000;
|
|
274
|
+
utimesSync(lp, staleTime, staleTime);
|
|
275
|
+
|
|
276
|
+
initMetrics(tmpDir);
|
|
277
|
+
const ctx = assistantCtx();
|
|
278
|
+
const unit = snapshotUnitMetrics(ctx, "execute-task", "M003/S01/T01", Date.now() - 300, "test-model");
|
|
279
|
+
|
|
280
|
+
assert.ok(unit !== null, "snapshotUnitMetrics must succeed after stale lock from dead process");
|
|
281
|
+
assert.equal(unit!.id, "M003/S01/T01");
|
|
282
|
+
assert.ok(
|
|
283
|
+
existsSync(metricsPath(tmpDir)),
|
|
284
|
+
"metrics.json must exist after recovery",
|
|
285
|
+
);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// GSD-2 + metrics prune cache invalidation: pruned units must not reappear after snapshotUnitMetricsByScope
|
|
2
|
+
|
|
3
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import {
|
|
6
|
+
mkdtempSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
initMetricsByScope,
|
|
17
|
+
resetMetricsByScope,
|
|
18
|
+
snapshotUnitMetricsByScope,
|
|
19
|
+
pruneMetricsLedger,
|
|
20
|
+
type MetricsLedger,
|
|
21
|
+
} from "../metrics.js";
|
|
22
|
+
import { createWorkspace, scopeMilestone } from "../workspace.ts";
|
|
23
|
+
|
|
24
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
function makeProjectDir(): string {
|
|
27
|
+
const dir = mkdtempSync(join(tmpdir(), "gsd-metrics-prune-"));
|
|
28
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
29
|
+
return dir;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function metricsPath(base: string): string {
|
|
33
|
+
return join(base, ".gsd", "metrics.json");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function makeUnit(id: string, startedAt: number): any {
|
|
37
|
+
return {
|
|
38
|
+
type: "execute-task",
|
|
39
|
+
id,
|
|
40
|
+
model: "test-model",
|
|
41
|
+
startedAt,
|
|
42
|
+
finishedAt: startedAt + 1000,
|
|
43
|
+
tokens: { input: 100, output: 50, cacheRead: 0, cacheWrite: 0, total: 150 },
|
|
44
|
+
cost: 0.001,
|
|
45
|
+
toolCalls: 1,
|
|
46
|
+
assistantMessages: 1,
|
|
47
|
+
userMessages: 1,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function makeProjectLedger(units: any[]): MetricsLedger {
|
|
52
|
+
return { version: 1, projectStartedAt: 1000, units } as MetricsLedger;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function assistantCtx(): any {
|
|
56
|
+
const entries = [
|
|
57
|
+
{
|
|
58
|
+
type: "message",
|
|
59
|
+
id: "e0",
|
|
60
|
+
parentId: null,
|
|
61
|
+
timestamp: new Date().toISOString(),
|
|
62
|
+
message: {
|
|
63
|
+
role: "assistant",
|
|
64
|
+
content: [{ type: "text", text: "Done" }],
|
|
65
|
+
usage: {
|
|
66
|
+
input: 100,
|
|
67
|
+
output: 50,
|
|
68
|
+
cacheRead: 0,
|
|
69
|
+
cacheWrite: 0,
|
|
70
|
+
totalTokens: 150,
|
|
71
|
+
cost: 0.001,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
return { sessionManager: { getEntries: () => entries } };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
describe("pruneMetricsLedger: invalidates scoped ledger cache", () => {
|
|
82
|
+
let tmpDir: string;
|
|
83
|
+
let ws: ReturnType<typeof createWorkspace>;
|
|
84
|
+
let scope: ReturnType<typeof scopeMilestone>;
|
|
85
|
+
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
tmpDir = makeProjectDir();
|
|
88
|
+
mkdirSync(join(tmpDir, ".gsd", "milestones"), { recursive: true });
|
|
89
|
+
ws = createWorkspace(tmpDir);
|
|
90
|
+
scope = scopeMilestone(ws, "M001");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
afterEach(() => {
|
|
94
|
+
resetMetricsByScope(scope);
|
|
95
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("pruned units do not reappear in subsequent snapshotUnitMetricsByScope call", () => {
|
|
99
|
+
// 1. Write a ledger with 5 units to disk.
|
|
100
|
+
const oldUnits = [
|
|
101
|
+
makeUnit("M001/S01/T01", 1000),
|
|
102
|
+
makeUnit("M001/S01/T02", 2000),
|
|
103
|
+
makeUnit("M001/S01/T03", 3000),
|
|
104
|
+
makeUnit("M001/S01/T04", 4000),
|
|
105
|
+
makeUnit("M001/S01/T05", 5000),
|
|
106
|
+
];
|
|
107
|
+
writeFileSync(metricsPath(tmpDir), JSON.stringify(makeProjectLedger(oldUnits), null, 2));
|
|
108
|
+
|
|
109
|
+
// 2. Load the scoped cache — this populates scopedLedgers with all 5 units.
|
|
110
|
+
initMetricsByScope(scope);
|
|
111
|
+
|
|
112
|
+
// 3. Prune to keepCount=2 — should evict 3 old units from disk AND clear scopedLedgers.
|
|
113
|
+
const removed = pruneMetricsLedger(tmpDir, 2);
|
|
114
|
+
assert.equal(removed, 3, "pruneMetricsLedger should report 3 removed units");
|
|
115
|
+
|
|
116
|
+
// 4. Snapshot a new unit via scope. This exercises the lazy-reload path
|
|
117
|
+
// (scopedLedgers was cleared by prune) and writes the result to disk.
|
|
118
|
+
const ctx = assistantCtx();
|
|
119
|
+
const newUnit = snapshotUnitMetricsByScope(
|
|
120
|
+
scope,
|
|
121
|
+
ctx,
|
|
122
|
+
"execute-task",
|
|
123
|
+
"M001/S02/T01",
|
|
124
|
+
Date.now(),
|
|
125
|
+
"test-model",
|
|
126
|
+
);
|
|
127
|
+
assert.ok(newUnit !== null, "snapshotUnitMetricsByScope should return a unit");
|
|
128
|
+
|
|
129
|
+
// 5. Read the on-disk result and verify pruned units did NOT reappear.
|
|
130
|
+
const raw = readFileSync(metricsPath(tmpDir), "utf-8");
|
|
131
|
+
const ledger: MetricsLedger = JSON.parse(raw);
|
|
132
|
+
|
|
133
|
+
const unitIds = ledger.units.map((u) => u.id);
|
|
134
|
+
|
|
135
|
+
// The pruned units must not be in the output.
|
|
136
|
+
const prunedIds = ["M001/S01/T01", "M001/S01/T02", "M001/S01/T03"];
|
|
137
|
+
for (const id of prunedIds) {
|
|
138
|
+
assert.ok(
|
|
139
|
+
!unitIds.includes(id),
|
|
140
|
+
`pruned unit "${id}" must not reappear after prune + snapshot`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// The 2 kept units and the new snapshot unit must be present.
|
|
145
|
+
assert.ok(unitIds.includes("M001/S01/T04"), "kept unit T04 should still be present");
|
|
146
|
+
assert.ok(unitIds.includes("M001/S01/T05"), "kept unit T05 should still be present");
|
|
147
|
+
assert.ok(unitIds.includes("M001/S02/T01"), "new snapshot unit should be present");
|
|
148
|
+
});
|
|
149
|
+
});
|