gsd-pi 2.63.0-dev.026d309 → 2.63.0-dev.786f0ff
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 +46 -134
- package/dist/cli.js +48 -6
- package/dist/headless-query.js +11 -1
- package/dist/help-text.js +4 -1
- package/dist/onboarding.js +15 -8
- package/dist/resource-loader.js +18 -3
- package/dist/resources/extensions/cmux/index.js +21 -12
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +27 -0
- package/dist/resources/extensions/gsd/auto/finalize-timeout.js +40 -0
- package/dist/resources/extensions/gsd/auto/loop.js +4 -0
- package/dist/resources/extensions/gsd/auto/phases.js +157 -22
- package/dist/resources/extensions/gsd/auto/session.js +12 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
- package/dist/resources/extensions/gsd/auto-model-selection.js +32 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +124 -10
- package/dist/resources/extensions/gsd/auto-prompts.js +25 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +15 -7
- package/dist/resources/extensions/gsd/auto-start.js +10 -21
- package/dist/resources/extensions/gsd/auto-timers.js +2 -1
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +17 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
- package/dist/resources/extensions/gsd/auto.js +19 -2
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +147 -75
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
- package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +30 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
- package/dist/resources/extensions/gsd/constants.js +42 -0
- package/dist/resources/extensions/gsd/db-writer.js +72 -4
- package/dist/resources/extensions/gsd/forensics.js +20 -4
- package/dist/resources/extensions/gsd/gsd-db.js +64 -17
- package/dist/resources/extensions/gsd/guided-flow.js +19 -0
- package/dist/resources/extensions/gsd/metrics.js +27 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
- package/dist/resources/extensions/gsd/preferences-types.js +2 -0
- package/dist/resources/extensions/gsd/preferences.js +7 -2
- package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
- package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
- package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
- package/dist/resources/extensions/gsd/prompts/system.md +4 -7
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
- package/dist/resources/extensions/gsd/safety/content-validator.js +73 -0
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +34 -0
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +109 -0
- package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +83 -0
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +71 -0
- package/dist/resources/extensions/gsd/safety/git-checkpoint.js +91 -0
- package/dist/resources/extensions/gsd/safety/safety-harness.js +64 -0
- package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
- package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
- package/dist/resources/extensions/gsd/state.js +74 -14
- package/dist/resources/extensions/gsd/status-guards.js +11 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
- package/dist/resources/extensions/gsd/tools/complete-slice.js +40 -26
- package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
- package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
- package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
- package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
- package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
- package/dist/resources/extensions/mcp-client/auth.js +101 -0
- package/dist/resources/extensions/mcp-client/index.js +10 -1
- package/dist/resources/extensions/ollama/index.js +28 -22
- package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
- package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
- package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
- package/dist/resources/extensions/ollama/ollama-client.js +23 -32
- package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
- package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
- package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
- package/dist/update-cmd.js +4 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- 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 +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 +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 +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 +2 -2
- 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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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/onboarding/route.js +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 +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 +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 +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 +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 +2 -2
- 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 +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 +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 +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 +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 +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 +2 -2
- 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 +2 -2
- 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 +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 +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 +20 -20
- package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +50 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
- package/packages/pi-agent-core/src/agent-loop.ts +53 -0
- package/packages/pi-ai/dist/types.d.ts +16 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/types.ts +18 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js +46 -0
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +12 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
- package/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts +81 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +14 -0
- package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
- package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
- package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
- package/src/resources/extensions/cmux/index.ts +18 -12
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +27 -0
- package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
- package/src/resources/extensions/gsd/auto/loop.ts +5 -0
- package/src/resources/extensions/gsd/auto/phases.ts +194 -33
- package/src/resources/extensions/gsd/auto/session.ts +14 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
- package/src/resources/extensions/gsd/auto-model-selection.ts +36 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +141 -12
- package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
- package/src/resources/extensions/gsd/auto-start.ts +11 -20
- package/src/resources/extensions/gsd/auto-timers.ts +2 -1
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
- package/src/resources/extensions/gsd/auto.ts +22 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +160 -88
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -1
- package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +31 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
- package/src/resources/extensions/gsd/constants.ts +44 -0
- package/src/resources/extensions/gsd/db-writer.ts +78 -4
- package/src/resources/extensions/gsd/forensics.ts +21 -5
- package/src/resources/extensions/gsd/gsd-db.ts +64 -17
- package/src/resources/extensions/gsd/guided-flow.ts +22 -0
- package/src/resources/extensions/gsd/metrics.ts +28 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
- package/src/resources/extensions/gsd/preferences-types.ts +16 -0
- package/src/resources/extensions/gsd/preferences.ts +9 -2
- package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
- package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
- package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
- package/src/resources/extensions/gsd/prompts/system.md +4 -7
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
- package/src/resources/extensions/gsd/safety/content-validator.ts +98 -0
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +49 -0
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +151 -0
- package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +120 -0
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +108 -0
- package/src/resources/extensions/gsd/safety/git-checkpoint.ts +106 -0
- package/src/resources/extensions/gsd/safety/safety-harness.ts +105 -0
- package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
- package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
- package/src/resources/extensions/gsd/state.ts +67 -12
- package/src/resources/extensions/gsd/status-guards.ts +13 -0
- package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
- package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/complete-slice-string-coercion.test.ts +211 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
- package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/git-checkpoint.test.ts +94 -0
- package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
- package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
- package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
- package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
- package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
- package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
- package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
- package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
- package/src/resources/extensions/gsd/types.ts +44 -22
- package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
- package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
- package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
- package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
- package/src/resources/extensions/mcp-client/auth.ts +149 -0
- package/src/resources/extensions/mcp-client/index.ts +16 -1
- package/src/resources/extensions/ollama/index.ts +26 -25
- package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
- package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
- package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +20 -0
- package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
- package/src/resources/extensions/ollama/ollama-client.ts +30 -30
- package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
- package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
- package/src/resources/extensions/ollama/tests/ollama-chat-provider-stream.test.ts +82 -0
- package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
- package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
- package/src/resources/extensions/ollama/types.ts +23 -0
- package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
- /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_ssgManifest.js +0 -0
|
@@ -33,6 +33,7 @@ import { debugLog } from "./debug-logger.js";
|
|
|
33
33
|
import { findMilestoneIds, nextMilestoneId, reserveMilestoneId, getReservedMilestoneIds, clearReservedMilestoneIds } from "./milestone-ids.js";
|
|
34
34
|
import { parkMilestone, discardMilestone } from "./milestone-actions.js";
|
|
35
35
|
import { selectAndApplyModel } from "./auto-model-selection.js";
|
|
36
|
+
import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
|
|
36
37
|
// ─── Re-exports (preserve public API for existing importers) ────────────────
|
|
37
38
|
export { MILESTONE_ID_RE, generateMilestoneSuffix, nextMilestoneId, extractMilestoneSeq, parseMilestoneId, milestoneIdSort, maxMilestoneNum, findMilestoneIds, reserveMilestoneId, claimReservedId, getReservedMilestoneIds, clearReservedMilestoneIds, } from "./milestone-ids.js";
|
|
38
39
|
export { showQueue, handleQueueReorder, showQueueAdd, buildExistingMilestonesContext, } from "./guided-flow-queue.js";
|
|
@@ -242,6 +243,24 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType)
|
|
|
242
243
|
});
|
|
243
244
|
}
|
|
244
245
|
}
|
|
246
|
+
// Scope tools for discuss flows (#2949).
|
|
247
|
+
// Providers with grammar-based constrained decoding (xAI/Grok) return
|
|
248
|
+
// "Grammar is too complex" when the combined tool schema is too large.
|
|
249
|
+
// Discuss flows only need a small subset of GSD tools — strip the heavy
|
|
250
|
+
// planning/execution/completion tools to keep the grammar within limits.
|
|
251
|
+
if (unitType?.startsWith("discuss-")) {
|
|
252
|
+
const currentTools = pi.getActiveTools();
|
|
253
|
+
// Keep all non-GSD tools (builtins, other extensions) and only the
|
|
254
|
+
// GSD tools on the discuss allowlist.
|
|
255
|
+
const scopedTools = currentTools.filter((t) => !t.startsWith("gsd_") || DISCUSS_TOOLS_ALLOWLIST.includes(t));
|
|
256
|
+
pi.setActiveTools(scopedTools);
|
|
257
|
+
debugLog("discuss-tool-scoping", {
|
|
258
|
+
unitType,
|
|
259
|
+
before: currentTools.length,
|
|
260
|
+
after: scopedTools.length,
|
|
261
|
+
removed: currentTools.length - scopedTools.length,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
245
264
|
const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
|
|
246
265
|
const workflow = readFileSync(workflowPath, "utf-8");
|
|
247
266
|
pi.sendMessage({
|
|
@@ -412,7 +412,33 @@ export function loadLedgerFromDisk(base) {
|
|
|
412
412
|
return loadJsonFileOrNull(metricsPath(base), isMetricsLedger);
|
|
413
413
|
}
|
|
414
414
|
function loadLedger(base) {
|
|
415
|
-
|
|
415
|
+
const raw = loadJsonFile(metricsPath(base), isMetricsLedger, defaultLedger);
|
|
416
|
+
const before = raw.units.length;
|
|
417
|
+
raw.units = deduplicateUnits(raw.units);
|
|
418
|
+
if (raw.units.length < before) {
|
|
419
|
+
// Persist the cleaned ledger so duplicates don't re-accumulate
|
|
420
|
+
saveLedger(base, raw);
|
|
421
|
+
}
|
|
422
|
+
return raw;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Collapse duplicate entries with the same (type, id, startedAt) triple.
|
|
426
|
+
* Keeps the entry with the highest finishedAt (the most complete snapshot).
|
|
427
|
+
*
|
|
428
|
+
* This is a defensive measure against idle-watchdog race conditions that can
|
|
429
|
+
* produce duplicate entries on disk despite the in-memory idempotency guard
|
|
430
|
+
* in snapshotUnitMetrics(). See #1943.
|
|
431
|
+
*/
|
|
432
|
+
function deduplicateUnits(units) {
|
|
433
|
+
const map = new Map();
|
|
434
|
+
for (const u of units) {
|
|
435
|
+
const key = `${u.type}\0${u.id}\0${u.startedAt}`;
|
|
436
|
+
const existing = map.get(key);
|
|
437
|
+
if (!existing || u.finishedAt > existing.finishedAt) {
|
|
438
|
+
map.set(key, u);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return Array.from(map.values());
|
|
416
442
|
}
|
|
417
443
|
function saveLedger(base, data) {
|
|
418
444
|
saveJsonFile(metricsPath(base), data);
|
|
@@ -569,10 +569,12 @@ export function nativeAddAllWithExclusions(basePath, exclusions) {
|
|
|
569
569
|
return;
|
|
570
570
|
}
|
|
571
571
|
// When .gsd is a symlink, git rejects `:!.gsd/...` pathspecs with
|
|
572
|
-
// "beyond a symbolic link". Fall back to
|
|
573
|
-
//
|
|
572
|
+
// "beyond a symbolic link". Fall back to `git add -u` which only
|
|
573
|
+
// stages changes to already-tracked files — O(tracked) not O(filesystem).
|
|
574
|
+
// Using `git add -A` here would traverse the entire working tree,
|
|
575
|
+
// hanging indefinitely on repos with large untracked data dirs. (#1977)
|
|
574
576
|
if (stderr.includes("beyond a symbolic link")) {
|
|
575
|
-
|
|
577
|
+
gitFileExec(basePath, ["add", "-u"]);
|
|
576
578
|
return;
|
|
577
579
|
}
|
|
578
580
|
throw new GSDError(GSD_GIT_ERROR, `git add -A with exclusions failed in ${basePath}: ${getErrorMessage(err)}`);
|
|
@@ -148,9 +148,11 @@ export function parsePreferencesMarkdown(content) {
|
|
|
148
148
|
if (/^##\s+\w/m.test(content)) {
|
|
149
149
|
return parseHeadingListFormat(content);
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
// Warn when a non-empty file exists but lacks frontmatter delimiters (#2036).
|
|
152
|
+
if (content.trim().length > 0 && !_warnedUnrecognizedFormat) {
|
|
152
153
|
_warnedUnrecognizedFormat = true;
|
|
153
|
-
console.warn("[
|
|
154
|
+
console.warn("[GSD] Warning: preferences file has unrecognized format — content does not use YAML frontmatter delimiters (---). " +
|
|
155
|
+
"Wrap your preferences in --- fences. See https://github.com/gsd-build/gsd-2/issues/2036");
|
|
154
156
|
}
|
|
155
157
|
return null;
|
|
156
158
|
}
|
|
@@ -301,6 +303,9 @@ function mergePreferences(base, override) {
|
|
|
301
303
|
].filter(Boolean),
|
|
302
304
|
}
|
|
303
305
|
: undefined,
|
|
306
|
+
slice_parallel: (base.slice_parallel || override.slice_parallel)
|
|
307
|
+
? { ...(base.slice_parallel ?? {}), ...(override.slice_parallel ?? {}) }
|
|
308
|
+
: undefined,
|
|
304
309
|
};
|
|
305
310
|
}
|
|
306
311
|
function mergeStringLists(base, override) {
|
|
@@ -47,6 +47,13 @@ function resolveExtensionDir() {
|
|
|
47
47
|
const __extensionDir = resolveExtensionDir();
|
|
48
48
|
const promptsDir = join(__extensionDir, "prompts");
|
|
49
49
|
const templatesDir = join(__extensionDir, "templates");
|
|
50
|
+
/**
|
|
51
|
+
* Return the resolved templates directory path for use in prompts.
|
|
52
|
+
* Avoids hardcoding `~/.gsd/agent/extensions/gsd/templates/` in templates. (#3575)
|
|
53
|
+
*/
|
|
54
|
+
export function getTemplatesDir() {
|
|
55
|
+
return templatesDir;
|
|
56
|
+
}
|
|
50
57
|
// Cache all templates eagerly at module load — a running session uses the
|
|
51
58
|
// template versions that were on disk at startup, immune to later overwrites.
|
|
52
59
|
const templateCache = new Map();
|
|
@@ -24,6 +24,8 @@ Then:
|
|
|
24
24
|
7. Fill the **Decision Re-evaluation** table in the milestone summary. For each key decision from `.gsd/DECISIONS.md` made during this milestone, evaluate whether it is still valid given what was actually built. Flag decisions that should be revisited next milestone.
|
|
25
25
|
8. Validate **requirement status transitions**. For each requirement that changed status during this milestone, confirm the transition is supported by evidence. Requirements can move between Active, Validated, Deferred, Blocked, or Out of Scope — but only with proof.
|
|
26
26
|
|
|
27
|
+
**DB access safety:** Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')` — the engine owns the WAL connection. Use `gsd_milestone_status` to read milestone and slice state. All data you need is already inlined in the context above or accessible via the `gsd_*` tools — never via direct SQL.
|
|
28
|
+
|
|
27
29
|
### Verification Gate — STOP if verification failed
|
|
28
30
|
|
|
29
31
|
**If ANY verification failure was recorded in steps 3, 4, or 5, you MUST follow the failure path below. Do NOT proceed to step 9.**
|
|
@@ -35,6 +35,8 @@ Then:
|
|
|
35
35
|
|
|
36
36
|
**Autonomous execution:** Do not call `ask_user_questions` or `secure_env_collect`. You are running in auto-mode — there is no human available to answer questions. Make reasonable assumptions and document them in the slice summary. If a decision genuinely requires human input, note it in the summary and proceed with the best available option.
|
|
37
37
|
|
|
38
|
+
**File system safety:** Task summaries are preloaded in the inlined context above. If you need to re-read any of them, use `find .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks -name "*-SUMMARY.md"` to list file paths first — never pass `{{slicePath}}` or any other directory path directly to the `read` tool. The `read` tool only accepts file paths, not directories.
|
|
39
|
+
|
|
38
40
|
**You MUST call `gsd_complete_slice` with the slice summary and UAT content before finishing. The tool persists to both DB and disk and renders `{{sliceSummaryPath}}` and `{{sliceUatPath}}` automatically.**
|
|
39
41
|
|
|
40
42
|
When done, say: "Slice {{sliceId}} complete."
|
|
@@ -9,6 +9,7 @@ Rules:
|
|
|
9
9
|
4. For missing summaries or UAT files, generate the real artifact from existing slice/task context when possible — do not leave placeholders if you can reconstruct the real content.
|
|
10
10
|
5. After each repair cluster, verify the relevant invariant directly from disk.
|
|
11
11
|
6. When done, rerun `/gsd doctor {{doctorCommandSuffix}}` mentally by ensuring the remaining issue set for this scope is reduced or cleared.
|
|
12
|
+
7. Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')` — use `gsd_milestone_status` to inspect DB state. Direct access bypasses the WAL connection owned by the engine and can corrupt in-flight writes.
|
|
12
13
|
|
|
13
14
|
## Doctor Summary
|
|
14
15
|
|
|
@@ -116,6 +116,8 @@ A unit dispatched more than once (`type/id` appears multiple times) indicates a
|
|
|
116
116
|
|
|
117
117
|
5. **Read the actual GSD source code** at `{{gsdSourceDir}}` to confirm or deny each hypothesis. Do not guess what code does — read it.
|
|
118
118
|
|
|
119
|
+
**DB inspection:** If you need to check DB state as part of investigation, use `gsd_milestone_status` — never run `sqlite3 .gsd/gsd.db` or `node -e require('better-sqlite3')` directly. The engine holds a WAL write lock; direct access will either fail or return stale data.
|
|
120
|
+
|
|
119
121
|
6. **Trace the code path** from the entry point (usually `auto-loop.ts` dispatch or `auto-dispatch.ts`) through to the failure point. Follow function calls across files.
|
|
120
122
|
|
|
121
123
|
7. **Identify the specific file and line** where the bug lives. Determine what kind of defect it is:
|
|
@@ -63,4 +63,6 @@ If `.gsd/REQUIREMENTS.md` exists and requirement ownership or status changed, up
|
|
|
63
63
|
|
|
64
64
|
{{commitInstruction}}
|
|
65
65
|
|
|
66
|
+
**DB access safety:** Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')`. Use `gsd_milestone_status` to read current milestone and slice state. All roadmap mutations go through `gsd_reassess_roadmap` — the tool writes to the DB and re-renders ROADMAP.md atomically.
|
|
67
|
+
|
|
66
68
|
When done, say: "Roadmap reassessed."
|
|
@@ -24,13 +24,9 @@ Leave the project in a state where the next agent can immediately understand wha
|
|
|
24
24
|
|
|
25
25
|
## Skills
|
|
26
26
|
|
|
27
|
-
GSD ships with bundled skills. Load the relevant skill file with the `read` tool before starting work when the task matches.
|
|
27
|
+
GSD ships with bundled skills. Load the relevant skill file with the `read` tool before starting work when the task matches. Use bare skill names — GSD resolves them to the correct path automatically.
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|---|---|
|
|
31
|
-
| Frontend UI - web components, pages, landing pages, dashboards, React/HTML/CSS, styling | `~/.gsd/agent/skills/frontend-design/SKILL.md` |
|
|
32
|
-
| macOS or iOS apps - SwiftUI, Xcode, App Store | `~/.gsd/agent/skills/swiftui/SKILL.md` |
|
|
33
|
-
| Debugging - complex bugs, failing tests, root-cause investigation after standard approaches fail | `~/.gsd/agent/skills/debug-like-expert/SKILL.md` |
|
|
29
|
+
{{bundledSkillsTable}}
|
|
34
30
|
|
|
35
31
|
## Hard Rules
|
|
36
32
|
|
|
@@ -119,7 +115,7 @@ In all modes, slices commit sequentially on the active branch; there are no per-
|
|
|
119
115
|
### Artifact Templates
|
|
120
116
|
|
|
121
117
|
Templates showing the expected format for each artifact type are in:
|
|
122
|
-
|
|
118
|
+
`{{templatesDir}}`
|
|
123
119
|
|
|
124
120
|
**Always read the relevant template before writing an artifact** to match the expected structure exactly. The parsers that read these files depend on specific formatting:
|
|
125
121
|
|
|
@@ -175,6 +171,7 @@ Templates showing the expected format for each artifact type are in:
|
|
|
175
171
|
- Never guess at library APIs from training data — use `get_library_docs`.
|
|
176
172
|
- Never ask the user to run a command, set a variable, or check something you can check yourself.
|
|
177
173
|
- Never await stale async jobs after editing source — `cancel_job` them first, then re-run.
|
|
174
|
+
- Never query `.gsd/gsd.db` directly via `sqlite3`, `better-sqlite3`, or `node -e require('better-sqlite3')` — the database uses a single-writer WAL connection managed by the engine. Direct access causes reader/writer conflicts and bypasses validation logic. Use `gsd_milestone_status`, `gsd_journal_query`, or other `gsd_*` tools exclusively for all DB reads and writes.
|
|
178
175
|
|
|
179
176
|
### Ask vs infer
|
|
180
177
|
|
|
@@ -38,6 +38,8 @@ All relevant context has been preloaded below — the roadmap, all slice summari
|
|
|
38
38
|
|
|
39
39
|
**Persist validation results through `gsd_validate_milestone`.** Call it with: `milestoneId`, `verdict`, `remediationRound`, `successCriteriaChecklist`, `sliceDeliveryAudit`, `crossSliceIntegration`, `requirementCoverage`, `verificationClasses` (when non-empty), `verdictRationale`, and `remediationPlan` (if verdict is `needs-remediation`). The tool writes the validation to the DB and renders VALIDATION.md to disk.
|
|
40
40
|
|
|
41
|
+
**DB access safety:** Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')` — the engine owns the WAL connection. Use `gsd_milestone_status` to read milestone and slice state. All data you need is already inlined in the context above or accessible via the `gsd_*` tools. Direct DB access corrupts the WAL and bypasses tool-level validation.
|
|
42
|
+
|
|
41
43
|
If verdict is `needs-remediation`:
|
|
42
44
|
- After calling `gsd_validate_milestone`, use `gsd_reassess_roadmap` to add remediation slices. Pass `milestoneId`, a synthetic `completedSliceId` (e.g. "VALIDATION"), `verdict: "roadmap-adjusted"`, `assessment` text, and `sliceChanges` with the new slices in the `added` array. The tool persists the changes to the DB and re-renders ROADMAP.md.
|
|
43
45
|
- These remediation slices will be planned and executed before validation re-runs.
|
|
@@ -31,7 +31,7 @@ export function markSliceDoneInRoadmap(basePath, mid, sid) {
|
|
|
31
31
|
if (updated === content) {
|
|
32
32
|
updated = content.replace(new RegExp(`^(#{1,4}\\s+(?:\\*{0,2})(?:Slice\\s+)?${sid}\\*{0,2}[:\\s.\\u2014\\u2013-]+\\s*)(.+)`, "m"), (match, prefix, title) => {
|
|
33
33
|
// Already marked done — no-op
|
|
34
|
-
if (
|
|
34
|
+
if (/^[\u2713\u2705]/.test(title) || /[\u2705]\s*$/.test(title) || /\(Complete\)\s*$/i.test(title))
|
|
35
35
|
return match;
|
|
36
36
|
return `${prefix}\u2713 ${title}`;
|
|
37
37
|
});
|
|
@@ -74,7 +74,7 @@ function parseTableSlices(section) {
|
|
|
74
74
|
// Determine completion status from any cell containing [x], "Done", or "Complete"
|
|
75
75
|
const fullRow = line.toLowerCase();
|
|
76
76
|
const done = /\[x\]/i.test(line) ||
|
|
77
|
-
/[
|
|
77
|
+
/[✅☑✓✔]/.test(line) ||
|
|
78
78
|
/\bdone\b/.test(fullRow) ||
|
|
79
79
|
/\bcomplete(?:d)?\b/.test(fullRow);
|
|
80
80
|
// Extract risk from any cell containing risk keywords
|
|
@@ -214,10 +214,10 @@ function parseProseSliceHeaders(content) {
|
|
|
214
214
|
// numeric prefixes (e.g., "1.", "(1)"), bracketed IDs (e.g., "[S01]"),
|
|
215
215
|
// optional checkmark completion marker, and optional leading indentation.
|
|
216
216
|
// Separator after the ID is flexible: colon, dash, em/en dash, dot, or just whitespace.
|
|
217
|
-
const headerPattern = /^\s*#{1,4}\s+\*{0,2}(
|
|
217
|
+
const headerPattern = /^\s*#{1,4}\s+\*{0,2}(?:[\u2713\u2705]\s+)?(?:\d+[.)]\s+)?(?:\(\d+\)\s+)?(?:Slice\s+)?\[?(S\d+)\]?\*{0,2}[:\s.\u2014\u2013-]*\s*(.+)/gm;
|
|
218
218
|
let match;
|
|
219
219
|
// Check for checkmark before the slice ID (e.g., "## checkmark S01: Title")
|
|
220
|
-
const prefixCheckPattern = /^\s*#{1,4}\s+\*{0,2}\u2713\s+/;
|
|
220
|
+
const prefixCheckPattern = /^\s*#{1,4}\s+\*{0,2}[\u2713\u2705]\s+/;
|
|
221
221
|
while ((match = headerPattern.exec(content)) !== null) {
|
|
222
222
|
const id = match[1];
|
|
223
223
|
let title = match[2].trim().replace(/\*{1,2}$/g, "").trim(); // strip trailing bold markers
|
|
@@ -229,9 +229,13 @@ function parseProseSliceHeaders(content) {
|
|
|
229
229
|
// 3. (Complete) suffix: "## S01: Title (Complete)"
|
|
230
230
|
const line = match[0];
|
|
231
231
|
let done = prefixCheckPattern.test(line);
|
|
232
|
-
if (!done &&
|
|
232
|
+
if (!done && /^[\u2713\u2705]/.test(title)) {
|
|
233
233
|
done = true;
|
|
234
|
-
title = title.replace(
|
|
234
|
+
title = title.replace(/^[\u2713\u2705]\s*/, "");
|
|
235
|
+
}
|
|
236
|
+
if (!done && /[\u2705]\s*$/.test(title)) {
|
|
237
|
+
done = true;
|
|
238
|
+
title = title.replace(/\s*[\u2705]\s*$/, "");
|
|
235
239
|
}
|
|
236
240
|
if (!done && /\(Complete\)\s*$/i.test(title)) {
|
|
237
241
|
done = true;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight content validator for auto-mode safety harness.
|
|
3
|
+
* Validates that high-value unit outputs contain minimum expected content.
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { logWarning } from "../workflow-logger.js";
|
|
9
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
10
|
+
/**
|
|
11
|
+
* Validate content quality for a completed unit.
|
|
12
|
+
* Returns an array of violations. Empty array = content looks acceptable.
|
|
13
|
+
*
|
|
14
|
+
* @param unitType - The type of unit that completed (e.g. "plan-slice")
|
|
15
|
+
* @param artifactPath - Absolute path to the primary artifact file
|
|
16
|
+
*/
|
|
17
|
+
export function validateContent(unitType, artifactPath) {
|
|
18
|
+
if (!artifactPath || !existsSync(artifactPath))
|
|
19
|
+
return [];
|
|
20
|
+
const validator = VALIDATORS[unitType];
|
|
21
|
+
if (!validator)
|
|
22
|
+
return [];
|
|
23
|
+
try {
|
|
24
|
+
const content = readFileSync(artifactPath, "utf-8");
|
|
25
|
+
return validator(content);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
logWarning("safety", `content validation read failed: ${e.message}`);
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const VALIDATORS = {
|
|
33
|
+
"plan-slice": validatePlanSlice,
|
|
34
|
+
"plan-milestone": validatePlanMilestone,
|
|
35
|
+
};
|
|
36
|
+
function validatePlanSlice(content) {
|
|
37
|
+
const violations = [];
|
|
38
|
+
// Must have at least 2 task entries (checkbox pattern)
|
|
39
|
+
const taskCount = (content.match(/- \[[ x]\] \*\*T\d+/g) || []).length;
|
|
40
|
+
if (taskCount < 2) {
|
|
41
|
+
violations.push({
|
|
42
|
+
severity: "warning",
|
|
43
|
+
reason: `Slice plan has only ${taskCount} task(s) — expected at least 2`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// Should have a Files Likely Touched section
|
|
47
|
+
if (!content.includes("## Files Likely Touched") && !content.includes("## Files")) {
|
|
48
|
+
violations.push({
|
|
49
|
+
severity: "warning",
|
|
50
|
+
reason: "Slice plan missing 'Files Likely Touched' section",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// Should have a verification section
|
|
54
|
+
if (!content.includes("Verify") && !content.includes("verify")) {
|
|
55
|
+
violations.push({
|
|
56
|
+
severity: "warning",
|
|
57
|
+
reason: "Slice plan has no verification instructions",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return violations;
|
|
61
|
+
}
|
|
62
|
+
function validatePlanMilestone(content) {
|
|
63
|
+
const violations = [];
|
|
64
|
+
// Must have at least 1 slice entry
|
|
65
|
+
const sliceCount = (content.match(/##\s+S\d+/g) || []).length;
|
|
66
|
+
if (sliceCount < 1) {
|
|
67
|
+
violations.push({
|
|
68
|
+
severity: "warning",
|
|
69
|
+
reason: `Milestone roadmap has ${sliceCount} slice(s) — expected at least 1`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return violations;
|
|
73
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Destructive command classifier for auto-mode safety harness.
|
|
3
|
+
* Classifies bash commands and warns on potentially destructive operations.
|
|
4
|
+
* Does NOT block — only classifies for logging/notification.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
7
|
+
*/
|
|
8
|
+
const DESTRUCTIVE_PATTERNS = [
|
|
9
|
+
{ pattern: /\brm\s+(-[^\s]*[rfRF][^\s]*\s+|.*\s+-[^\s]*[rfRF])/, label: "recursive delete" },
|
|
10
|
+
{ pattern: /\bgit\s+push\s+.*--force/, label: "force push" },
|
|
11
|
+
{ pattern: /\bgit\s+push\s+-f\b/, label: "force push" },
|
|
12
|
+
{ pattern: /\bgit\s+reset\s+--hard/, label: "hard reset" },
|
|
13
|
+
{ pattern: /\bgit\s+clean\s+-[^\s]*[fdxFDX]/, label: "git clean" },
|
|
14
|
+
{ pattern: /\bgit\s+checkout\s+--\s+\./, label: "discard all changes" },
|
|
15
|
+
{ pattern: /\bdrop\s+(database|table|index)\b/i, label: "SQL drop" },
|
|
16
|
+
{ pattern: /\btruncate\s+table\b/i, label: "SQL truncate" },
|
|
17
|
+
{ pattern: /\bchmod\s+777\b/, label: "world-writable permissions" },
|
|
18
|
+
{ pattern: /\bcurl\s.*\|\s*(bash|sh|zsh)\b/, label: "pipe to shell" },
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Classify a bash command for destructive operations.
|
|
22
|
+
* Returns the list of matched destructive pattern labels.
|
|
23
|
+
*/
|
|
24
|
+
export function classifyCommand(command) {
|
|
25
|
+
const labels = [];
|
|
26
|
+
for (const { pattern, label } of DESTRUCTIVE_PATTERNS) {
|
|
27
|
+
if (pattern.test(command)) {
|
|
28
|
+
// Deduplicate labels (e.g., two force-push patterns)
|
|
29
|
+
if (!labels.includes(label))
|
|
30
|
+
labels.push(label);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { destructive: labels.length > 0, labels };
|
|
34
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real-time tool call evidence collector for auto-mode safety harness.
|
|
3
|
+
* Tracks every bash command, file write, and file edit during a unit execution.
|
|
4
|
+
* Evidence is compared against LLM completion claims in evidence-cross-ref.ts.
|
|
5
|
+
*
|
|
6
|
+
* Follows the same module-level Map pattern as auto-tool-tracking.ts.
|
|
7
|
+
* Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
8
|
+
*/
|
|
9
|
+
// ─── Module State ───────────────────────────────────────────────────────────
|
|
10
|
+
let unitEvidence = [];
|
|
11
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
12
|
+
/** Reset all evidence for a new unit. Call at unit start. */
|
|
13
|
+
export function resetEvidence() {
|
|
14
|
+
unitEvidence = [];
|
|
15
|
+
}
|
|
16
|
+
/** Get a read-only view of all evidence collected for the current unit. */
|
|
17
|
+
export function getEvidence() {
|
|
18
|
+
return unitEvidence;
|
|
19
|
+
}
|
|
20
|
+
/** Get only bash evidence entries. */
|
|
21
|
+
export function getBashEvidence() {
|
|
22
|
+
return unitEvidence.filter((e) => e.kind === "bash");
|
|
23
|
+
}
|
|
24
|
+
/** Get all file paths touched (write + edit). */
|
|
25
|
+
export function getFilePaths() {
|
|
26
|
+
return unitEvidence
|
|
27
|
+
.filter((e) => e.kind === "write" || e.kind === "edit")
|
|
28
|
+
.map(e => e.path);
|
|
29
|
+
}
|
|
30
|
+
// ─── Recording (called from register-hooks.ts) ─────────────────────────────
|
|
31
|
+
/**
|
|
32
|
+
* Record a tool call at dispatch time (before execution).
|
|
33
|
+
* Exit codes and output are filled in by recordToolResult after execution.
|
|
34
|
+
*/
|
|
35
|
+
export function recordToolCall(toolName, input) {
|
|
36
|
+
if (toolName === "bash" || toolName === "Bash") {
|
|
37
|
+
unitEvidence.push({
|
|
38
|
+
kind: "bash",
|
|
39
|
+
toolCallId: "",
|
|
40
|
+
command: String(input.command ?? ""),
|
|
41
|
+
exitCode: -1,
|
|
42
|
+
outputSnippet: "",
|
|
43
|
+
timestamp: Date.now(),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else if (toolName === "write" || toolName === "Write") {
|
|
47
|
+
unitEvidence.push({
|
|
48
|
+
kind: "write",
|
|
49
|
+
toolCallId: "",
|
|
50
|
+
path: String(input.file_path ?? input.path ?? ""),
|
|
51
|
+
timestamp: Date.now(),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else if (toolName === "edit" || toolName === "Edit") {
|
|
55
|
+
unitEvidence.push({
|
|
56
|
+
kind: "edit",
|
|
57
|
+
toolCallId: "",
|
|
58
|
+
path: String(input.file_path ?? input.path ?? ""),
|
|
59
|
+
timestamp: Date.now(),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Record a tool execution result. Matches the most recent unresolved entry
|
|
65
|
+
* of the same kind and fills in the toolCallId, exit code, and output.
|
|
66
|
+
*/
|
|
67
|
+
export function recordToolResult(toolCallId, toolName, result, isError) {
|
|
68
|
+
const normalizedName = toolName.toLowerCase();
|
|
69
|
+
if (normalizedName === "bash") {
|
|
70
|
+
const entry = findLastUnresolved("bash");
|
|
71
|
+
if (entry) {
|
|
72
|
+
entry.toolCallId = toolCallId;
|
|
73
|
+
const text = extractResultText(result);
|
|
74
|
+
entry.outputSnippet = text.slice(0, 500);
|
|
75
|
+
const exitMatch = text.match(/Command exited with code (\d+)/);
|
|
76
|
+
entry.exitCode = exitMatch ? Number(exitMatch[1]) : (isError ? 1 : 0);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (normalizedName === "write" || normalizedName === "edit") {
|
|
80
|
+
const entry = findLastUnresolved(normalizedName);
|
|
81
|
+
if (entry) {
|
|
82
|
+
entry.toolCallId = toolCallId;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// ─── Internals ──────────────────────────────────────────────────────────────
|
|
87
|
+
function findLastUnresolved(kind) {
|
|
88
|
+
for (let i = unitEvidence.length - 1; i >= 0; i--) {
|
|
89
|
+
if (unitEvidence[i].kind === kind && unitEvidence[i].toolCallId === "") {
|
|
90
|
+
return unitEvidence[i];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
function extractResultText(result) {
|
|
96
|
+
if (typeof result === "string")
|
|
97
|
+
return result;
|
|
98
|
+
if (result && typeof result === "object") {
|
|
99
|
+
const r = result;
|
|
100
|
+
if (Array.isArray(r.content)) {
|
|
101
|
+
const textBlock = r.content.find((c) => typeof c === "object" && c !== null && c.type === "text");
|
|
102
|
+
if (textBlock && typeof textBlock.text === "string")
|
|
103
|
+
return textBlock.text;
|
|
104
|
+
}
|
|
105
|
+
if (typeof r.text === "string")
|
|
106
|
+
return r.text;
|
|
107
|
+
}
|
|
108
|
+
return String(result ?? "");
|
|
109
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence cross-reference for auto-mode safety harness.
|
|
3
|
+
* Compares the LLM's claimed verification evidence (command + exitCode)
|
|
4
|
+
* against actual bash tool calls recorded by the evidence collector.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
7
|
+
*/
|
|
8
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
9
|
+
/**
|
|
10
|
+
* Cross-reference claimed verification evidence against actual bash tool calls.
|
|
11
|
+
*
|
|
12
|
+
* Returns an array of mismatches. Empty array = all claims verified.
|
|
13
|
+
* Skips entries that were coerced from strings (already flagged by db-tools.ts).
|
|
14
|
+
*/
|
|
15
|
+
export function crossReferenceEvidence(claimedEvidence, actualEvidence) {
|
|
16
|
+
const bashCalls = actualEvidence.filter((e) => e.kind === "bash");
|
|
17
|
+
const mismatches = [];
|
|
18
|
+
for (const claimed of claimedEvidence) {
|
|
19
|
+
// Skip coerced entries — they're already flagged with exitCode: -1
|
|
20
|
+
// and verdict: "unknown (coerced from string)" by db-tools.ts
|
|
21
|
+
if (claimed.verdict?.includes("coerced from string"))
|
|
22
|
+
continue;
|
|
23
|
+
if (claimed.exitCode === -1)
|
|
24
|
+
continue;
|
|
25
|
+
// Skip entries with empty or generic commands
|
|
26
|
+
if (!claimed.command || claimed.command.length < 3)
|
|
27
|
+
continue;
|
|
28
|
+
// Find matching bash call by command substring match
|
|
29
|
+
const match = findBestMatch(claimed.command, bashCalls);
|
|
30
|
+
if (!match) {
|
|
31
|
+
mismatches.push({
|
|
32
|
+
severity: "warning",
|
|
33
|
+
claimed,
|
|
34
|
+
actual: null,
|
|
35
|
+
reason: `No bash tool call found matching "${claimed.command.slice(0, 80)}"`,
|
|
36
|
+
});
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
// Exit code mismatch: LLM claims success but actual command failed
|
|
40
|
+
if (claimed.exitCode === 0 && match.exitCode !== 0) {
|
|
41
|
+
mismatches.push({
|
|
42
|
+
severity: "error",
|
|
43
|
+
claimed,
|
|
44
|
+
actual: match,
|
|
45
|
+
reason: `Claimed exitCode=0 but actual exitCode=${match.exitCode}`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return mismatches;
|
|
50
|
+
}
|
|
51
|
+
// ─── Internals ──────────────────────────────────────────────────────────────
|
|
52
|
+
/**
|
|
53
|
+
* Find the best matching bash evidence entry for a claimed command.
|
|
54
|
+
* Uses substring matching — the claimed command may be a shortened version
|
|
55
|
+
* of the actual command, or vice versa.
|
|
56
|
+
*/
|
|
57
|
+
function findBestMatch(claimedCommand, bashCalls) {
|
|
58
|
+
const normalized = claimedCommand.trim();
|
|
59
|
+
// Exact match first
|
|
60
|
+
const exact = bashCalls.find(b => b.command.trim() === normalized);
|
|
61
|
+
if (exact)
|
|
62
|
+
return exact;
|
|
63
|
+
// Substring match: claimed is contained in actual or actual in claimed
|
|
64
|
+
const substring = bashCalls.find(b => b.command.includes(normalized) || normalized.includes(b.command));
|
|
65
|
+
if (substring)
|
|
66
|
+
return substring;
|
|
67
|
+
// Token match: split on whitespace and check significant overlap
|
|
68
|
+
const claimedTokens = normalized.split(/\s+/).filter(t => t.length > 2);
|
|
69
|
+
if (claimedTokens.length === 0)
|
|
70
|
+
return null;
|
|
71
|
+
let bestMatch = null;
|
|
72
|
+
let bestScore = 0;
|
|
73
|
+
for (const call of bashCalls) {
|
|
74
|
+
const callTokens = new Set(call.command.split(/\s+/));
|
|
75
|
+
const matchCount = claimedTokens.filter(t => callTokens.has(t)).length;
|
|
76
|
+
const score = matchCount / claimedTokens.length;
|
|
77
|
+
if (score > bestScore && score >= 0.5) {
|
|
78
|
+
bestScore = score;
|
|
79
|
+
bestMatch = call;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return bestMatch;
|
|
83
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-unit file change validator for auto-mode safety harness.
|
|
3
|
+
* Compares actual git diff against the task plan's expected output files.
|
|
4
|
+
*
|
|
5
|
+
* Uses tasks.expected_output (DB column, populated from per-task ## Expected Output)
|
|
6
|
+
* and tasks.files (from slice PLAN.md - Files: subline) as the expected set.
|
|
7
|
+
* Compares against git diff HEAD~1 --name-only after auto-commit.
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
10
|
+
*/
|
|
11
|
+
import { execFileSync } from "node:child_process";
|
|
12
|
+
import { logWarning } from "../workflow-logger.js";
|
|
13
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
14
|
+
/**
|
|
15
|
+
* Validate file changes after auto-commit for an execute-task unit.
|
|
16
|
+
* Returns null if task data is unavailable or DB is not loaded.
|
|
17
|
+
*
|
|
18
|
+
* @param basePath - Working directory (worktree or project root)
|
|
19
|
+
* @param expectedOutput - JSON array from tasks.expected_output DB column
|
|
20
|
+
* @param plannedFiles - JSON array from tasks.files DB column
|
|
21
|
+
*/
|
|
22
|
+
export function validateFileChanges(basePath, expectedOutput, plannedFiles) {
|
|
23
|
+
const allExpected = new Set([...expectedOutput, ...plannedFiles]);
|
|
24
|
+
// If no expected files were planned, skip validation
|
|
25
|
+
if (allExpected.size === 0)
|
|
26
|
+
return null;
|
|
27
|
+
// Get actual changed files from last commit
|
|
28
|
+
const actualFiles = getChangedFilesFromLastCommit(basePath);
|
|
29
|
+
if (!actualFiles)
|
|
30
|
+
return null;
|
|
31
|
+
// Filter out .gsd/ internal files — only validate project source files
|
|
32
|
+
const projectFiles = actualFiles.filter(f => !f.startsWith(".gsd/") && !f.startsWith(".gsd\\"));
|
|
33
|
+
// Normalize expected paths (strip leading ./ or /)
|
|
34
|
+
const normalizedExpected = new Set([...allExpected].map(f => f.replace(/^\.\//, "").replace(/^\//, "")));
|
|
35
|
+
// Compute symmetric difference
|
|
36
|
+
const unexpectedFiles = projectFiles.filter(f => !normalizedExpected.has(f));
|
|
37
|
+
const missingFiles = [...normalizedExpected].filter(f => !projectFiles.includes(f));
|
|
38
|
+
const violations = [];
|
|
39
|
+
for (const f of unexpectedFiles) {
|
|
40
|
+
violations.push({
|
|
41
|
+
severity: "warning",
|
|
42
|
+
file: f,
|
|
43
|
+
reason: "Modified but not in task plan's expected output",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
for (const f of missingFiles) {
|
|
47
|
+
violations.push({
|
|
48
|
+
severity: "info",
|
|
49
|
+
file: f,
|
|
50
|
+
reason: "Listed in task plan but not modified",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
expectedFiles: [...normalizedExpected],
|
|
55
|
+
actualFiles: projectFiles,
|
|
56
|
+
unexpectedFiles,
|
|
57
|
+
missingFiles,
|
|
58
|
+
violations,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// ─── Internals ──────────────────────────────────────────────────────────────
|
|
62
|
+
function getChangedFilesFromLastCommit(basePath) {
|
|
63
|
+
try {
|
|
64
|
+
const result = execFileSync("git", ["diff", "--name-only", "HEAD~1", "HEAD"], { cwd: basePath, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
65
|
+
return result ? result.split("\n").filter(Boolean) : [];
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
logWarning("safety", `git diff failed in file-change-validator: ${e.message}`);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|