gsd-pi 2.76.0-dev.82e249f7b → 2.76.0-dev.97f5583d9
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/dist/claude-cli-check.js +32 -3
- package/dist/mcp-server.d.ts +7 -0
- package/dist/mcp-server.js +35 -1
- package/dist/resource-loader.d.ts +1 -1
- package/dist/resource-loader.js +2 -8
- package/dist/resources/extensions/claude-code-cli/readiness.js +4 -3
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +77 -17
- package/dist/resources/extensions/gsd/auto/phases.js +42 -1
- package/dist/resources/extensions/gsd/auto/run-unit.js +27 -0
- package/dist/resources/extensions/gsd/auto/session.js +12 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +16 -3
- package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +25 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +14 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +13 -0
- package/dist/resources/extensions/gsd/auto-start.js +27 -18
- package/dist/resources/extensions/gsd/auto-worktree.js +51 -53
- package/dist/resources/extensions/gsd/auto.js +55 -27
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +51 -5
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +34 -2
- package/dist/resources/extensions/gsd/clean-root-preflight.js +93 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
- package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
- package/dist/resources/extensions/gsd/error-classifier.js +10 -3
- package/dist/resources/extensions/gsd/exec-history.js +120 -0
- package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
- package/dist/resources/extensions/gsd/gsd-db.js +115 -7
- package/dist/resources/extensions/gsd/guided-flow.js +189 -0
- package/dist/resources/extensions/gsd/health-widget.js +4 -1
- package/dist/resources/extensions/gsd/init-wizard.js +15 -1
- package/dist/resources/extensions/gsd/key-manager.js +6 -0
- package/dist/resources/extensions/gsd/model-router.js +36 -3
- package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -9
- package/dist/resources/extensions/gsd/preferences-types.js +9 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
- package/dist/resources/extensions/gsd/preferences.js +17 -17
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +96 -0
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +13 -5
- package/dist/resources/extensions/gsd/safety/safety-harness.js +5 -1
- package/dist/resources/extensions/gsd/token-counter.js +22 -5
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
- package/dist/resources/extensions/gsd/uok/plan-v2.js +20 -3
- package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
- package/dist/resources/skills/verify-before-complete/SKILL.md +2 -1
- package/dist/resources/skills/write-docs/SKILL.md +2 -1
- 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 +10 -10
- 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/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 +10 -10
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- 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/dist/remote-questions.d.ts +45 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
- package/packages/mcp-server/dist/remote-questions.js +732 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -0
- package/packages/mcp-server/dist/server.d.ts +7 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +41 -4
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +64 -25
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +2 -1
- package/packages/mcp-server/src/mcp-server.test.ts +30 -0
- package/packages/mcp-server/src/remote-questions.test.ts +294 -0
- package/packages/mcp-server/src/remote-questions.ts +916 -0
- package/packages/mcp-server/src/server.ts +62 -10
- package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
- package/packages/mcp-server/src/workflow-tools.ts +84 -43
- package/packages/mcp-server/tsconfig.test.json +19 -0
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/providers/anthropic-auth.test.js +1 -1
- package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +27 -4
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +8 -3
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.js +80 -0
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +16 -1
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic-auth.test.ts +1 -1
- package/packages/pi-ai/src/providers/anthropic-shared.ts +26 -5
- package/packages/pi-ai/src/providers/anthropic.ts +9 -3
- package/packages/pi-ai/src/providers/minimax-tool-name.test.ts +98 -0
- package/packages/pi-ai/src/providers/simple-options.ts +17 -1
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +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 +14 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +9 -5
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +25 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +13 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +16 -0
- package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
- package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +36 -1
- package/packages/pi-coding-agent/src/core/session-manager.ts +9 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/claude-code-cli/readiness.ts +4 -3
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +78 -17
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +149 -5
- package/src/resources/extensions/gsd/auto/loop-deps.ts +13 -0
- package/src/resources/extensions/gsd/auto/phases.ts +66 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +29 -0
- package/src/resources/extensions/gsd/auto/session.ts +22 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +16 -3
- package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +28 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +15 -0
- package/src/resources/extensions/gsd/auto-start.ts +29 -19
- package/src/resources/extensions/gsd/auto-worktree.ts +62 -63
- package/src/resources/extensions/gsd/auto.ts +58 -27
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +23 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +53 -5
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +35 -2
- package/src/resources/extensions/gsd/clean-root-preflight.ts +111 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
- package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
- package/src/resources/extensions/gsd/error-classifier.ts +10 -3
- package/src/resources/extensions/gsd/exec-history.ts +153 -0
- package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
- package/src/resources/extensions/gsd/gsd-db.ts +122 -7
- package/src/resources/extensions/gsd/guided-flow.ts +221 -0
- package/src/resources/extensions/gsd/health-widget.ts +3 -1
- package/src/resources/extensions/gsd/init-wizard.ts +15 -1
- package/src/resources/extensions/gsd/journal.ts +2 -1
- package/src/resources/extensions/gsd/key-manager.ts +6 -0
- package/src/resources/extensions/gsd/model-router.ts +42 -1
- package/src/resources/extensions/gsd/pre-execution-checks.ts +36 -10
- package/src/resources/extensions/gsd/preferences-types.ts +46 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
- package/src/resources/extensions/gsd/preferences.ts +17 -17
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +119 -0
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +17 -4
- package/src/resources/extensions/gsd/safety/safety-harness.ts +9 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +119 -1
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +124 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +152 -1
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +272 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +234 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
- package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
- package/src/resources/extensions/gsd/token-counter.ts +22 -5
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
- package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
- package/src/resources/extensions/gsd/uok/plan-v2.ts +26 -3
- package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
- package/src/resources/skills/verify-before-complete/SKILL.md +2 -1
- package/src/resources/skills/write-docs/SKILL.md +2 -1
- /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → lLdDRDspgYzfz0bJAmUSz}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → lLdDRDspgYzfz0bJAmUSz}/_ssgManifest.js +0 -0
|
@@ -91,8 +91,13 @@ export function extractPackageReferences(description: string): string[] {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// require('pkg') or import from 'pkg' in code blocks
|
|
95
|
-
|
|
94
|
+
// require('pkg') or `import ... from 'pkg'` in code blocks.
|
|
95
|
+
// The `from\s+['"]` branch MUST be preceded by an `import` keyword so that
|
|
96
|
+
// natural-language prose like `from "What's Next"` or `from 'master'` does
|
|
97
|
+
// not produce false package-existence failures. Requiring the leading import
|
|
98
|
+
// keyword anchors the match to JavaScript/TypeScript syntax.
|
|
99
|
+
// See: https://github.com/gsd-build/gsd-2/issues/4388
|
|
100
|
+
const importPattern = /(?:require\s*\(\s*['"]|import\b[\s\S]*?\bfrom\s+['"])([a-zA-Z0-9@/_-]+)['"\)]/g;
|
|
96
101
|
let importMatch: RegExpExecArray | null;
|
|
97
102
|
while ((importMatch = importPattern.exec(description)) !== null) {
|
|
98
103
|
// Skip relative imports and node builtins
|
|
@@ -325,7 +330,12 @@ function extractPathFromAnnotation(raw: string): string {
|
|
|
325
330
|
|
|
326
331
|
const annotatedMatch = trimmed.match(/^(.+?)\s+[—–-]\s+.+$/);
|
|
327
332
|
if (annotatedMatch) {
|
|
328
|
-
|
|
333
|
+
const prefix = annotatedMatch[1].trim();
|
|
334
|
+
const prefixBacktickMatch = prefix.match(/`([^`]+)`/);
|
|
335
|
+
if (prefixBacktickMatch && looksLikePathOrUrl(prefixBacktickMatch[1].trim())) {
|
|
336
|
+
return prefixBacktickMatch[1].trim();
|
|
337
|
+
}
|
|
338
|
+
return prefix.replace(/`/g, "").trim();
|
|
329
339
|
}
|
|
330
340
|
|
|
331
341
|
// Fallback: scan all backticked tokens and return the first one that looks
|
|
@@ -388,13 +398,19 @@ function containsGlobPattern(candidate: string): boolean {
|
|
|
388
398
|
|
|
389
399
|
/**
|
|
390
400
|
* Build a set of files that will be created by tasks up to (but not including) taskIndex.
|
|
401
|
+
* Also includes outputs of completed tasks at any position — a completed task has already
|
|
402
|
+
* run and its outputs are available regardless of sequence position or disk state (#4071).
|
|
391
403
|
* All paths are normalized for consistent comparison.
|
|
392
404
|
*/
|
|
393
405
|
function getExpectedOutputsUpTo(tasks: TaskRow[], taskIndex: number): Set<string> {
|
|
394
406
|
const outputs = new Set<string>();
|
|
395
|
-
for (let i = 0; i <
|
|
396
|
-
|
|
397
|
-
|
|
407
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
408
|
+
const task = tasks[i];
|
|
409
|
+
// Include prior tasks (i < taskIndex) OR completed tasks at any position
|
|
410
|
+
if (i < taskIndex || task.status === "completed") {
|
|
411
|
+
for (const file of task.expected_output) {
|
|
412
|
+
outputs.add(normalizeFilePath(file));
|
|
413
|
+
}
|
|
398
414
|
}
|
|
399
415
|
}
|
|
400
416
|
return outputs;
|
|
@@ -481,13 +497,19 @@ export function checkTaskOrdering(
|
|
|
481
497
|
const results: PreExecutionCheckJSON[] = [];
|
|
482
498
|
|
|
483
499
|
// Build map: normalized file → task index that creates it
|
|
484
|
-
const fileCreators = new Map<string, { taskId: string; index: number; originalPath: string }>();
|
|
500
|
+
const fileCreators = new Map<string, { taskId: string; index: number; originalPath: string; completed: boolean }>();
|
|
485
501
|
for (let i = 0; i < tasks.length; i++) {
|
|
486
502
|
const task = tasks[i];
|
|
487
503
|
for (const file of task.expected_output) {
|
|
488
504
|
const normalizedFile = normalizeFilePath(file);
|
|
489
|
-
|
|
490
|
-
|
|
505
|
+
const existing = fileCreators.get(normalizedFile);
|
|
506
|
+
if (!existing || (!existing.completed && task.status === "completed")) {
|
|
507
|
+
fileCreators.set(normalizedFile, {
|
|
508
|
+
taskId: task.id,
|
|
509
|
+
index: i,
|
|
510
|
+
originalPath: file,
|
|
511
|
+
completed: task.status === "completed",
|
|
512
|
+
});
|
|
491
513
|
}
|
|
492
514
|
}
|
|
493
515
|
}
|
|
@@ -511,7 +533,11 @@ export function checkTaskOrdering(
|
|
|
511
533
|
const creator = fileCreators.get(normalizedFile);
|
|
512
534
|
const absolutePath = resolve(basePath, normalizedFile);
|
|
513
535
|
const existsOnDisk = existsSync(absolutePath);
|
|
514
|
-
if
|
|
536
|
+
// Skip if the creating task has already completed — its output is available
|
|
537
|
+
// regardless of disk state (e.g. file was a temp artifact cleaned up after
|
|
538
|
+
// the task ran, or a replan introduced a new earlier-sequence task that
|
|
539
|
+
// reads this pre-execution output). (#4071)
|
|
540
|
+
if (creator && creator.index > i && !existsOnDisk && !creator.completed) {
|
|
515
541
|
// Task reads file that is created later — impossible ordering
|
|
516
542
|
results.push({
|
|
517
543
|
category: "file",
|
|
@@ -28,6 +28,37 @@ export interface ContextManagementConfig {
|
|
|
28
28
|
compaction_threshold_percent?: number; // default: 0.70, range: 0.5-0.95
|
|
29
29
|
tool_result_max_chars?: number; // default: 800, range: 200-10000
|
|
30
30
|
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Opt-in tool-output sandboxing for sub-sessions. When enabled, the gsd_exec
|
|
34
|
+
* MCP tool runs scripts in an isolated subprocess and returns only a short
|
|
35
|
+
* digest to the calling agent's context window; full stdout/stderr persist
|
|
36
|
+
* in the project memory store and can be retrieved by id later.
|
|
37
|
+
*
|
|
38
|
+
* Inspired by mksglu/context-mode (Elastic License 2.0). This is an
|
|
39
|
+
* independent implementation — no upstream code is incorporated.
|
|
40
|
+
*/
|
|
41
|
+
export interface ContextModeConfig {
|
|
42
|
+
/** Master switch. Default: true (opt-out via `enabled: false`). */
|
|
43
|
+
enabled?: boolean;
|
|
44
|
+
/** Per-invocation timeout in milliseconds. Default: 30_000. Range: 1_000–600_000. */
|
|
45
|
+
exec_timeout_ms?: number;
|
|
46
|
+
/** Cap on persisted stdout bytes per invocation. Default: 1_048_576 (1 MiB). Range: 4_096–16_777_216. */
|
|
47
|
+
exec_stdout_cap_bytes?: number;
|
|
48
|
+
/** Number of trailing stdout characters returned in the digest. Default: 300. Range: 0–4_000. */
|
|
49
|
+
exec_digest_chars?: number;
|
|
50
|
+
/** Environment variables forwarded to sandboxed processes (case-sensitive names). PATH and HOME are always forwarded. */
|
|
51
|
+
exec_env_allowlist?: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolve whether context-mode features (gsd_exec sandbox + compaction
|
|
56
|
+
* snapshot) should be active. Default is ON: missing config or missing
|
|
57
|
+
* `enabled` is treated as true. Only `enabled: false` disables.
|
|
58
|
+
*/
|
|
59
|
+
export function isContextModeEnabled(prefs: { context_mode?: ContextModeConfig } | null | undefined): boolean {
|
|
60
|
+
return prefs?.context_mode?.enabled !== false;
|
|
61
|
+
}
|
|
31
62
|
import type { GitHubSyncConfig } from "../github-sync/types.js";
|
|
32
63
|
|
|
33
64
|
// ─── Workflow Modes ──────────────────────────────────────────────────────────
|
|
@@ -117,6 +148,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set<string>([
|
|
|
117
148
|
"flat_rate_providers",
|
|
118
149
|
"language",
|
|
119
150
|
"context_window_override",
|
|
151
|
+
"context_mode",
|
|
120
152
|
]);
|
|
121
153
|
|
|
122
154
|
/** Canonical list of all dispatch unit types. */
|
|
@@ -300,6 +332,12 @@ export interface GSDPreferences {
|
|
|
300
332
|
*/
|
|
301
333
|
context_window_override?: number;
|
|
302
334
|
context_management?: ContextManagementConfig;
|
|
335
|
+
/**
|
|
336
|
+
* Tool-output sandboxing via gsd_exec. Keeps sub-session context windows
|
|
337
|
+
* clean by running scripts in a subprocess and only surfacing a short
|
|
338
|
+
* digest. See `ContextModeConfig`. Default: disabled.
|
|
339
|
+
*/
|
|
340
|
+
context_mode?: ContextModeConfig;
|
|
303
341
|
token_profile?: TokenProfile;
|
|
304
342
|
phases?: PhaseSkipPreferences;
|
|
305
343
|
auto_visualize?: boolean;
|
|
@@ -354,6 +392,14 @@ export interface GSDPreferences {
|
|
|
354
392
|
checkpoints?: boolean;
|
|
355
393
|
auto_rollback?: boolean;
|
|
356
394
|
timeout_scale_cap?: number;
|
|
395
|
+
/**
|
|
396
|
+
* Glob patterns for files that are always expected side-effects of any task.
|
|
397
|
+
* Files matching any pattern here are excluded from unexpected-change warnings.
|
|
398
|
+
* Supports standard glob syntax (e.g. `tracking/history/**`, `*.log`).
|
|
399
|
+
* Fixes #4385/#4436 — audit-trail snapshots, build artifacts, and other
|
|
400
|
+
* project-level secondary writes shouldn't require per-task declaration.
|
|
401
|
+
*/
|
|
402
|
+
file_change_allowlist?: string[];
|
|
357
403
|
};
|
|
358
404
|
|
|
359
405
|
|
|
@@ -644,6 +644,50 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
644
644
|
}
|
|
645
645
|
}
|
|
646
646
|
|
|
647
|
+
// ─── Context Mode (gsd_exec sandbox) ────────────────────────────────────
|
|
648
|
+
if (preferences.context_mode !== undefined) {
|
|
649
|
+
if (typeof preferences.context_mode === "object" && preferences.context_mode !== null) {
|
|
650
|
+
const cmode = preferences.context_mode as unknown as Record<string, unknown>;
|
|
651
|
+
const validCmode: Record<string, unknown> = {};
|
|
652
|
+
|
|
653
|
+
if (cmode.enabled !== undefined) {
|
|
654
|
+
if (typeof cmode.enabled === "boolean") validCmode.enabled = cmode.enabled;
|
|
655
|
+
else errors.push("context_mode.enabled must be a boolean");
|
|
656
|
+
}
|
|
657
|
+
if (cmode.exec_timeout_ms !== undefined) {
|
|
658
|
+
const t = cmode.exec_timeout_ms;
|
|
659
|
+
if (typeof t === "number" && t >= 1000 && t <= 600_000) validCmode.exec_timeout_ms = Math.floor(t);
|
|
660
|
+
else errors.push("context_mode.exec_timeout_ms must be a number between 1000 and 600000");
|
|
661
|
+
}
|
|
662
|
+
if (cmode.exec_stdout_cap_bytes !== undefined) {
|
|
663
|
+
const b = cmode.exec_stdout_cap_bytes;
|
|
664
|
+
if (typeof b === "number" && b >= 4096 && b <= 16_777_216) validCmode.exec_stdout_cap_bytes = Math.floor(b);
|
|
665
|
+
else errors.push("context_mode.exec_stdout_cap_bytes must be a number between 4096 and 16777216");
|
|
666
|
+
}
|
|
667
|
+
if (cmode.exec_digest_chars !== undefined) {
|
|
668
|
+
const c = cmode.exec_digest_chars;
|
|
669
|
+
if (typeof c === "number" && c >= 0 && c <= 4000) validCmode.exec_digest_chars = Math.floor(c);
|
|
670
|
+
else errors.push("context_mode.exec_digest_chars must be a number between 0 and 4000");
|
|
671
|
+
}
|
|
672
|
+
if (cmode.exec_env_allowlist !== undefined) {
|
|
673
|
+
if (
|
|
674
|
+
Array.isArray(cmode.exec_env_allowlist) &&
|
|
675
|
+
cmode.exec_env_allowlist.every((v) => typeof v === "string" && /^[A-Z_][A-Z0-9_]*$/i.test(v))
|
|
676
|
+
) {
|
|
677
|
+
validCmode.exec_env_allowlist = cmode.exec_env_allowlist;
|
|
678
|
+
} else {
|
|
679
|
+
errors.push("context_mode.exec_env_allowlist must be an array of valid env var names");
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (Object.keys(validCmode).length > 0) {
|
|
684
|
+
validated.context_mode = validCmode as any;
|
|
685
|
+
}
|
|
686
|
+
} else {
|
|
687
|
+
errors.push("context_mode must be an object");
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
647
691
|
// ─── Parallel Config ────────────────────────────────────────────────────
|
|
648
692
|
if (preferences.parallel && typeof preferences.parallel === "object") {
|
|
649
693
|
const p = preferences.parallel as unknown as Record<string, unknown>;
|
|
@@ -697,6 +741,41 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
697
741
|
}
|
|
698
742
|
}
|
|
699
743
|
|
|
744
|
+
// ─── Slice Parallel Config ───────────────────────────────────────────────
|
|
745
|
+
if (preferences.slice_parallel !== undefined) {
|
|
746
|
+
if (typeof preferences.slice_parallel === "object" && preferences.slice_parallel !== null) {
|
|
747
|
+
const sp = preferences.slice_parallel as Record<string, unknown>;
|
|
748
|
+
const validSp: NonNullable<GSDPreferences["slice_parallel"]> = {};
|
|
749
|
+
|
|
750
|
+
if (sp.enabled !== undefined) {
|
|
751
|
+
if (typeof sp.enabled === "boolean") validSp.enabled = sp.enabled;
|
|
752
|
+
else errors.push("slice_parallel.enabled must be a boolean");
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (sp.max_workers !== undefined) {
|
|
756
|
+
const maxWorkers = typeof sp.max_workers === "number" ? sp.max_workers : Number(sp.max_workers);
|
|
757
|
+
if (Number.isFinite(maxWorkers) && maxWorkers >= 1 && maxWorkers <= 8) {
|
|
758
|
+
validSp.max_workers = Math.floor(maxWorkers);
|
|
759
|
+
} else {
|
|
760
|
+
errors.push("slice_parallel.max_workers must be a number between 1 and 8");
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const knownSliceParallelKeys = new Set(["enabled", "max_workers"]);
|
|
765
|
+
for (const key of Object.keys(sp)) {
|
|
766
|
+
if (!knownSliceParallelKeys.has(key)) {
|
|
767
|
+
warnings.push(`unknown slice_parallel key "${key}" — ignored`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (Object.keys(validSp).length > 0) {
|
|
772
|
+
validated.slice_parallel = validSp;
|
|
773
|
+
}
|
|
774
|
+
} else {
|
|
775
|
+
errors.push("slice_parallel must be an object");
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
700
779
|
// ─── Reactive Execution ─────────────────────────────────────────────────
|
|
701
780
|
if (preferences.reactive_execution !== undefined) {
|
|
702
781
|
if (typeof preferences.reactive_execution === "object" && preferences.reactive_execution !== null) {
|
|
@@ -68,13 +68,13 @@ export { resolveAllSkillReferences } from "./preferences-skills.js";
|
|
|
68
68
|
// These lived in preferences-skills.ts but imported loadEffectiveGSDPreferences
|
|
69
69
|
// back from this file, creating a circular dependency. Moved here since they
|
|
70
70
|
// are trivial wrappers over loadEffectiveGSDPreferences.
|
|
71
|
-
export function resolveSkillDiscoveryMode(): SkillDiscoveryMode {
|
|
72
|
-
const prefs = loadEffectiveGSDPreferences();
|
|
71
|
+
export function resolveSkillDiscoveryMode(basePath?: string): SkillDiscoveryMode {
|
|
72
|
+
const prefs = loadEffectiveGSDPreferences(basePath);
|
|
73
73
|
return prefs?.preferences.skill_discovery ?? "suggest";
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
export function resolveSkillStalenessDays(): number {
|
|
77
|
-
const prefs = loadEffectiveGSDPreferences();
|
|
76
|
+
export function resolveSkillStalenessDays(basePath?: string): number {
|
|
77
|
+
const prefs = loadEffectiveGSDPreferences(basePath);
|
|
78
78
|
return prefs?.preferences.skill_staleness_days ?? 60;
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -109,16 +109,16 @@ function legacyGlobalPreferencesPath(): string {
|
|
|
109
109
|
return join(homedir(), ".pi", "agent", "gsd-preferences.md");
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
function projectPreferencesPath(): string {
|
|
113
|
-
return join(gsdRoot(
|
|
112
|
+
function projectPreferencesPath(basePath: string = process.cwd()): string {
|
|
113
|
+
return join(gsdRoot(basePath), "PREFERENCES.md");
|
|
114
114
|
}
|
|
115
115
|
// Legacy lowercase files can still exist in older projects. Keep them as a
|
|
116
116
|
// compatibility-only fallback, but route new reads/writes through PREFERENCES.md.
|
|
117
117
|
function legacyGlobalPreferencesPathLowercase(): string {
|
|
118
118
|
return join(gsdHome(), "preferences.md");
|
|
119
119
|
}
|
|
120
|
-
function legacyProjectPreferencesPathLowercase(): string {
|
|
121
|
-
return join(gsdRoot(
|
|
120
|
+
function legacyProjectPreferencesPathLowercase(basePath: string = process.cwd()): string {
|
|
121
|
+
return join(gsdRoot(basePath), "preferences.md");
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
export function getGlobalGSDPreferencesPath(): string {
|
|
@@ -129,8 +129,8 @@ export function getLegacyGlobalGSDPreferencesPath(): string {
|
|
|
129
129
|
return legacyGlobalPreferencesPath();
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
export function getProjectGSDPreferencesPath(): string {
|
|
133
|
-
return projectPreferencesPath();
|
|
132
|
+
export function getProjectGSDPreferencesPath(basePath?: string): string {
|
|
133
|
+
return projectPreferencesPath(basePath);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
// ─── Loading ────────────────────────────────────────────────────────────────
|
|
@@ -141,14 +141,14 @@ export function loadGlobalGSDPreferences(): LoadedGSDPreferences | null {
|
|
|
141
141
|
?? loadPreferencesFile(legacyGlobalPreferencesPath(), "global");
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
export function loadProjectGSDPreferences(): LoadedGSDPreferences | null {
|
|
145
|
-
return loadPreferencesFile(projectPreferencesPath(), "project")
|
|
146
|
-
?? loadPreferencesFile(legacyProjectPreferencesPathLowercase(), "project");
|
|
144
|
+
export function loadProjectGSDPreferences(basePath?: string): LoadedGSDPreferences | null {
|
|
145
|
+
return loadPreferencesFile(projectPreferencesPath(basePath), "project")
|
|
146
|
+
?? loadPreferencesFile(legacyProjectPreferencesPathLowercase(basePath), "project");
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
export function loadEffectiveGSDPreferences(): LoadedGSDPreferences | null {
|
|
149
|
+
export function loadEffectiveGSDPreferences(basePath?: string): LoadedGSDPreferences | null {
|
|
150
150
|
const globalPreferences = loadGlobalGSDPreferences();
|
|
151
|
-
const projectPreferences = loadProjectGSDPreferences();
|
|
151
|
+
const projectPreferences = loadProjectGSDPreferences(basePath);
|
|
152
152
|
|
|
153
153
|
if (!globalPreferences && !projectPreferences) return null;
|
|
154
154
|
|
|
@@ -603,8 +603,8 @@ export function resolvePreDispatchHooks(): PreDispatchHookConfig[] {
|
|
|
603
603
|
* Worktree isolation requires explicit opt-in because it depends on git
|
|
604
604
|
* branch infrastructure that must be set up before use.
|
|
605
605
|
*/
|
|
606
|
-
export function getIsolationMode(): "none" | "worktree" | "branch" {
|
|
607
|
-
const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
606
|
+
export function getIsolationMode(basePath?: string): "none" | "worktree" | "branch" {
|
|
607
|
+
const prefs = loadEffectiveGSDPreferences(basePath)?.preferences?.git;
|
|
608
608
|
if (prefs?.isolation === "worktree") return "worktree";
|
|
609
609
|
if (prefs?.isolation === "branch") return "branch";
|
|
610
610
|
return "none"; // default — no isolation, work on current branch
|
|
@@ -162,6 +162,10 @@ Preserve the specification's exact terminology, emphasis, and specific framing.
|
|
|
162
162
|
6. For each architectural or pattern decision, call `gsd_decision_save` — the tool auto-assigns IDs and regenerates `.gsd/DECISIONS.md` automatically.
|
|
163
163
|
7. {{commitInstruction}}
|
|
164
164
|
|
|
165
|
+
### Ready-phrase pre-condition (NON-BYPASSABLE)
|
|
166
|
+
|
|
167
|
+
Before emitting the ready phrase, verify in the CURRENT turn that you have written `.gsd/PROJECT.md`, `.gsd/REQUIREMENTS.md`, `{{contextPath}}`, and called `gsd_plan_milestone`. If any is missing, **STOP** — emit the missing tool calls in this same turn. The system rejects premature ready signals and retries are capped.
|
|
168
|
+
|
|
165
169
|
After writing the files, say exactly: "Milestone {{milestoneId}} ready." — nothing else. Auto-mode will start automatically.
|
|
166
170
|
|
|
167
171
|
### Multi-Milestone
|
|
@@ -234,6 +238,10 @@ For single-milestone projects, do NOT write this file.
|
|
|
234
238
|
|
|
235
239
|
7. {{multiMilestoneCommitInstruction}}
|
|
236
240
|
|
|
241
|
+
### Ready-phrase pre-condition (NON-BYPASSABLE)
|
|
242
|
+
|
|
243
|
+
Before emitting the ready phrase, verify in the CURRENT turn that you have written `.gsd/PROJECT.md`, `.gsd/REQUIREMENTS.md`, the primary `CONTEXT.md`, called `gsd_plan_milestone` for the primary milestone, and written `.gsd/DISCUSSION-MANIFEST.json` with `gates_completed === total`. If any is missing, **STOP** — emit the missing tool calls in this same turn. The system rejects premature ready signals and retries are capped.
|
|
244
|
+
|
|
237
245
|
After writing the files, say exactly: "Milestone {{milestoneId}} ready." — nothing else. Auto-mode will start automatically.
|
|
238
246
|
|
|
239
247
|
## Critical Rules
|
|
@@ -339,7 +339,20 @@ These sections are in addition to whatever other context the discussion surfaced
|
|
|
339
339
|
6. For each architectural or pattern decision made during discussion, call `gsd_decision_save` — the tool auto-assigns IDs and regenerates `.gsd/DECISIONS.md` automatically.
|
|
340
340
|
7. {{commitInstruction}}
|
|
341
341
|
|
|
342
|
-
|
|
342
|
+
### Ready-phrase pre-condition (NON-BYPASSABLE)
|
|
343
|
+
|
|
344
|
+
Before emitting the ready phrase, verify in the CURRENT turn that you have:
|
|
345
|
+
|
|
346
|
+
- [ ] Written `.gsd/PROJECT.md` (step 2)
|
|
347
|
+
- [ ] Written `.gsd/REQUIREMENTS.md` (step 3)
|
|
348
|
+
- [ ] Written `{{contextPath}}` (step 4)
|
|
349
|
+
- [ ] Called `gsd_plan_milestone` (step 5)
|
|
350
|
+
|
|
351
|
+
If ANY box is unchecked, **STOP**. Do NOT emit the ready phrase. Emit the missing tool calls in this same turn. The system detects missing artifacts and will reject premature ready signals — you will be asked again and retries are capped.
|
|
352
|
+
|
|
353
|
+
Do not announce the ready phrase as something you are "about to" do. Do not narrate "now writing the files" as a substitute for actually writing them. The ready phrase is a post-write signal, not an intent signal.
|
|
354
|
+
|
|
355
|
+
After completing steps 1–7 above, say exactly: "Milestone {{milestoneId}} ready." — nothing else. Auto-mode will start automatically.
|
|
343
356
|
|
|
344
357
|
### Multi-Milestone
|
|
345
358
|
|
|
@@ -418,6 +431,20 @@ For single-milestone projects, do NOT write this file — it is only for multi-m
|
|
|
418
431
|
|
|
419
432
|
7. {{multiMilestoneCommitInstruction}}
|
|
420
433
|
|
|
421
|
-
|
|
434
|
+
### Ready-phrase pre-condition (NON-BYPASSABLE)
|
|
435
|
+
|
|
436
|
+
Before emitting the ready phrase, verify in the CURRENT turn that you have:
|
|
437
|
+
|
|
438
|
+
- [ ] Written `.gsd/PROJECT.md` (Phase 1)
|
|
439
|
+
- [ ] Written `.gsd/REQUIREMENTS.md` (Phase 1)
|
|
440
|
+
- [ ] Written primary-milestone `CONTEXT.md` (Phase 2)
|
|
441
|
+
- [ ] Called `gsd_plan_milestone` for the primary milestone (Phase 2)
|
|
442
|
+
- [ ] Written `.gsd/DISCUSSION-MANIFEST.json` with `gates_completed === total` (Phase 3)
|
|
443
|
+
|
|
444
|
+
If ANY box is unchecked, **STOP**. Do NOT emit the ready phrase. Emit the missing tool calls in this same turn. The system detects missing artifacts and will reject premature ready signals — you will be asked again and retries are capped.
|
|
445
|
+
|
|
446
|
+
Do not announce the ready phrase as something you are "about to" do. Do not narrate "now writing the files" as a substitute for actually writing them. The ready phrase is a post-write signal, not an intent signal.
|
|
447
|
+
|
|
448
|
+
After completing all phases above, say exactly: "Milestone M001 ready." — nothing else. Auto-mode will start automatically.
|
|
422
449
|
|
|
423
450
|
{{inlinedTemplates}}
|
|
@@ -15,8 +15,11 @@ Dispatch ALL slices simultaneously using the `subagent` tool in **parallel mode*
|
|
|
15
15
|
1. Call `subagent` with `tasks: [...]` containing one entry per slice below
|
|
16
16
|
2. Wait for ALL subagents to complete
|
|
17
17
|
3. Verify each slice's RESEARCH file was written (check the `.gsd/{{mid}}/` directory)
|
|
18
|
-
4. If
|
|
19
|
-
5.
|
|
18
|
+
4. If a subagent failed to write its RESEARCH file, retry it **once** individually
|
|
19
|
+
5. If it fails a second time, write a partial RESEARCH file for that slice with a `## BLOCKER` section explaining the failure — do NOT retry again
|
|
20
|
+
6. Report which slices completed research and which (if any) needed a blocker note
|
|
21
|
+
|
|
22
|
+
**Important**: Each failed slice gets exactly one retry. After that, write the blocker and move on. Never retry the same slice more than once.
|
|
20
23
|
|
|
21
24
|
## Subagent Prompts
|
|
22
25
|
|
|
@@ -3,10 +3,26 @@
|
|
|
3
3
|
* Tracks every bash command, file write, and file edit during a unit execution.
|
|
4
4
|
* Evidence is compared against LLM completion claims in evidence-cross-ref.ts.
|
|
5
5
|
*
|
|
6
|
+
* Evidence is persisted to .gsd/safety/evidence-<mid>-<sid>-<tid>.json so it
|
|
7
|
+
* survives session restarts (pause/resume, crash recovery). On unit start,
|
|
8
|
+
* call resetEvidence() then loadEvidenceFromDisk(). On every new tool call,
|
|
9
|
+
* saveEvidenceToDisk() is called automatically by recordToolCall/recordToolResult.
|
|
10
|
+
*
|
|
6
11
|
* Follows the same module-level Map pattern as auto-tool-tracking.ts.
|
|
7
12
|
* Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
8
13
|
*/
|
|
9
14
|
|
|
15
|
+
import {
|
|
16
|
+
existsSync,
|
|
17
|
+
mkdirSync,
|
|
18
|
+
readFileSync,
|
|
19
|
+
writeFileSync,
|
|
20
|
+
renameSync,
|
|
21
|
+
unlinkSync,
|
|
22
|
+
} from "node:fs";
|
|
23
|
+
import { join, dirname } from "node:path";
|
|
24
|
+
import { randomBytes } from "node:crypto";
|
|
25
|
+
|
|
10
26
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
11
27
|
|
|
12
28
|
export interface BashEvidence {
|
|
@@ -62,6 +78,109 @@ export function getFilePaths(): string[] {
|
|
|
62
78
|
.map(e => e.path);
|
|
63
79
|
}
|
|
64
80
|
|
|
81
|
+
// ─── Persistence (Bug #4385 — evidence must survive session restarts) ────────
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build the path for the evidence JSON file for a given unit.
|
|
85
|
+
* Lives under .gsd/safety/ which is gitignored and session-scoped.
|
|
86
|
+
*/
|
|
87
|
+
function evidencePath(basePath: string, milestoneId: string, sliceId: string, taskId: string): string {
|
|
88
|
+
return join(basePath, ".gsd", "safety", `evidence-${milestoneId}-${sliceId}-${taskId}.json`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validate that a parsed value is an array of EvidenceEntry objects.
|
|
93
|
+
* Rejects corrupt / schema-mismatch data rather than letting it poison state.
|
|
94
|
+
*/
|
|
95
|
+
function isEvidenceArray(data: unknown): data is EvidenceEntry[] {
|
|
96
|
+
if (!Array.isArray(data)) return false;
|
|
97
|
+
return data.every((e) => {
|
|
98
|
+
if (e === null || typeof e !== "object") return false;
|
|
99
|
+
const rec = e as Record<string, unknown>;
|
|
100
|
+
if (typeof rec.toolCallId !== "string") return false;
|
|
101
|
+
if (typeof rec.timestamp !== "number") return false;
|
|
102
|
+
if (rec.kind === "bash") {
|
|
103
|
+
return (
|
|
104
|
+
typeof rec.command === "string" &&
|
|
105
|
+
typeof rec.exitCode === "number" &&
|
|
106
|
+
typeof rec.outputSnippet === "string"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (rec.kind === "write" || rec.kind === "edit") {
|
|
110
|
+
return typeof rec.path === "string";
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Persist the current in-memory evidence to disk so it survives a session
|
|
118
|
+
* restart. Called from saveEvidenceToDisk after recordToolCall/recordToolResult.
|
|
119
|
+
* Non-fatal — persistence failures must never break unit execution.
|
|
120
|
+
*/
|
|
121
|
+
export function saveEvidenceToDisk(
|
|
122
|
+
basePath: string,
|
|
123
|
+
milestoneId: string,
|
|
124
|
+
sliceId: string,
|
|
125
|
+
taskId: string,
|
|
126
|
+
): void {
|
|
127
|
+
try {
|
|
128
|
+
const path = evidencePath(basePath, milestoneId, sliceId, taskId);
|
|
129
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
130
|
+
const tmp = `${path}.tmp.${randomBytes(4).toString("hex")}`;
|
|
131
|
+
writeFileSync(tmp, JSON.stringify(unitEvidence, null, 2) + "\n", "utf-8");
|
|
132
|
+
renameSync(tmp, path);
|
|
133
|
+
} catch {
|
|
134
|
+
// Non-fatal — don't let persistence failures break unit execution
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Load persisted evidence from disk into the in-memory array.
|
|
140
|
+
* Call after resetEvidence() on session resume to restore context for a
|
|
141
|
+
* partially-executed unit. If the file does not exist (fresh unit), this
|
|
142
|
+
* is a no-op — getEvidence() will return [] which is correct.
|
|
143
|
+
*/
|
|
144
|
+
export function loadEvidenceFromDisk(
|
|
145
|
+
basePath: string,
|
|
146
|
+
milestoneId: string,
|
|
147
|
+
sliceId: string,
|
|
148
|
+
taskId: string,
|
|
149
|
+
): void {
|
|
150
|
+
try {
|
|
151
|
+
const path = evidencePath(basePath, milestoneId, sliceId, taskId);
|
|
152
|
+
if (!existsSync(path)) return;
|
|
153
|
+
const raw = readFileSync(path, "utf-8");
|
|
154
|
+
const parsed = JSON.parse(raw);
|
|
155
|
+
if (isEvidenceArray(parsed)) {
|
|
156
|
+
unitEvidence = parsed;
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
// Non-fatal — corrupt / missing file is treated as empty evidence
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Delete the persisted evidence file for a unit after it has been fully
|
|
165
|
+
* processed. Prevents stale evidence from affecting future retries of
|
|
166
|
+
* the same unit ID.
|
|
167
|
+
*/
|
|
168
|
+
export function clearEvidenceFromDisk(
|
|
169
|
+
basePath: string,
|
|
170
|
+
milestoneId: string,
|
|
171
|
+
sliceId: string,
|
|
172
|
+
taskId: string,
|
|
173
|
+
): void {
|
|
174
|
+
try {
|
|
175
|
+
const path = evidencePath(basePath, milestoneId, sliceId, taskId);
|
|
176
|
+
if (existsSync(path)) {
|
|
177
|
+
unlinkSync(path);
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
// Non-fatal
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
65
184
|
// ─── Recording (called from register-hooks.ts) ─────────────────────────────
|
|
66
185
|
|
|
67
186
|
/**
|
|
@@ -4,15 +4,23 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Uses tasks.expected_output (DB column, populated from per-task ## Expected Output)
|
|
6
6
|
* and tasks.files (from slice PLAN.md - Files: subline) as the expected set.
|
|
7
|
-
* Compares against git diff
|
|
7
|
+
* Compares against `git diff-tree --root --no-commit-id -r --name-only HEAD` after auto-commit.
|
|
8
|
+
* Using diff-tree --root handles initial commits, shallow clones, and merge commits correctly
|
|
9
|
+
* (Bug #4385 — git diff HEAD~1 failed on initial commits).
|
|
8
10
|
*
|
|
9
11
|
* Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
10
12
|
*/
|
|
11
13
|
|
|
14
|
+
import { createRequire } from "node:module";
|
|
12
15
|
import { execFileSync } from "node:child_process";
|
|
13
16
|
import { normalizePlannedFileReference } from "../files.js";
|
|
14
17
|
import { logWarning } from "../workflow-logger.js";
|
|
15
18
|
|
|
19
|
+
const _require = createRequire(import.meta.url);
|
|
20
|
+
type PicomatchMatcher = (input: string) => boolean;
|
|
21
|
+
type PicomatchFn = (pattern: string, opts?: { dot?: boolean }) => PicomatchMatcher;
|
|
22
|
+
const picomatch = _require("picomatch") as PicomatchFn;
|
|
23
|
+
|
|
16
24
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
17
25
|
|
|
18
26
|
export interface FileViolation {
|
|
@@ -43,6 +51,7 @@ export function validateFileChanges(
|
|
|
43
51
|
basePath: string,
|
|
44
52
|
expectedOutput: string[],
|
|
45
53
|
plannedFiles: string[],
|
|
54
|
+
fileChangeAllowlist: string[] = [],
|
|
46
55
|
): FileChangeAudit | null {
|
|
47
56
|
const allExpected = new Set([...expectedOutput, ...plannedFiles]);
|
|
48
57
|
|
|
@@ -63,8 +72,12 @@ export function validateFileChanges(
|
|
|
63
72
|
),
|
|
64
73
|
);
|
|
65
74
|
|
|
66
|
-
//
|
|
67
|
-
const
|
|
75
|
+
// Build allowlist matchers once (dot: true so patterns like `**/.hidden` work).
|
|
76
|
+
const allowlistMatchers = fileChangeAllowlist.map(p => picomatch(p, { dot: true }));
|
|
77
|
+
const isAllowlisted = (f: string) => allowlistMatchers.some(m => m(f));
|
|
78
|
+
|
|
79
|
+
// Compute symmetric difference, excluding allowlisted files
|
|
80
|
+
const unexpectedFiles = projectFiles.filter(f => !normalizedExpected.has(f) && !isAllowlisted(f));
|
|
68
81
|
const missingFiles = [...normalizedExpected].filter(f => !projectFiles.includes(f));
|
|
69
82
|
|
|
70
83
|
const violations: FileViolation[] = [];
|
|
@@ -100,7 +113,7 @@ function getChangedFilesFromLastCommit(basePath: string): string[] | null {
|
|
|
100
113
|
try {
|
|
101
114
|
const result = execFileSync(
|
|
102
115
|
"git",
|
|
103
|
-
["diff", "--
|
|
116
|
+
["diff-tree", "--root", "--no-commit-id", "-r", "--name-only", "HEAD"],
|
|
104
117
|
{ cwd: basePath, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" },
|
|
105
118
|
).trim();
|
|
106
119
|
return result ? result.split("\n").filter(Boolean) : [];
|
|
@@ -25,6 +25,8 @@ export interface SafetyHarnessConfig {
|
|
|
25
25
|
checkpoints: boolean;
|
|
26
26
|
auto_rollback: boolean;
|
|
27
27
|
timeout_scale_cap: number;
|
|
28
|
+
/** Glob patterns for files excluded from unexpected-change warnings (#4385). */
|
|
29
|
+
file_change_allowlist: string[];
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
// ─── Defaults ───────────────────────────────────────────────────────────────
|
|
@@ -39,6 +41,7 @@ const DEFAULTS: SafetyHarnessConfig = {
|
|
|
39
41
|
checkpoints: true,
|
|
40
42
|
auto_rollback: false,
|
|
41
43
|
timeout_scale_cap: 6,
|
|
44
|
+
file_change_allowlist: [],
|
|
42
45
|
};
|
|
43
46
|
|
|
44
47
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
@@ -62,6 +65,9 @@ export function resolveSafetyHarnessConfig(
|
|
|
62
65
|
checkpoints: typeof raw.checkpoints === "boolean" ? raw.checkpoints : DEFAULTS.checkpoints,
|
|
63
66
|
auto_rollback: typeof raw.auto_rollback === "boolean" ? raw.auto_rollback : DEFAULTS.auto_rollback,
|
|
64
67
|
timeout_scale_cap: typeof raw.timeout_scale_cap === "number" ? raw.timeout_scale_cap : DEFAULTS.timeout_scale_cap,
|
|
68
|
+
file_change_allowlist: Array.isArray(raw.file_change_allowlist)
|
|
69
|
+
? (raw.file_change_allowlist as unknown[]).filter((p): p is string => typeof p === "string")
|
|
70
|
+
: DEFAULTS.file_change_allowlist,
|
|
65
71
|
};
|
|
66
72
|
}
|
|
67
73
|
|
|
@@ -86,6 +92,9 @@ export {
|
|
|
86
92
|
getFilePaths,
|
|
87
93
|
recordToolCall,
|
|
88
94
|
recordToolResult,
|
|
95
|
+
saveEvidenceToDisk,
|
|
96
|
+
loadEvidenceFromDisk,
|
|
97
|
+
clearEvidenceFromDisk,
|
|
89
98
|
} from "./evidence-collector.js";
|
|
90
99
|
|
|
91
100
|
export type { EvidenceEntry, BashEvidence, FileWriteEvidence, FileEditEvidence } from "./evidence-collector.js";
|