gsd-pi 2.76.0-dev.76f9a2dc5 → 2.76.0-dev.97807402
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/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 +14 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +27 -0
- package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
- 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 +30 -48
- package/dist/resources/extensions/gsd/auto.js +13 -17
- 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 +40 -4
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +12 -1
- 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/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/file-change-validator.js +9 -3
- package/dist/resources/extensions/gsd/safety/safety-harness.js +4 -0
- 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/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 +14 -14
- 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 +14 -14
- 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.map +1 -1
- package/packages/mcp-server/dist/server.js +18 -1
- 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/remote-questions.test.ts +294 -0
- package/packages/mcp-server/src/remote-questions.ts +916 -0
- package/packages/mcp-server/src/server.ts +19 -1
- 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-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +2 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- 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-shared.ts +3 -1
- 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/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/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/phases.ts +14 -0
- package/src/resources/extensions/gsd/auto/run-unit.ts +29 -0
- package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +1 -2
- 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 +34 -52
- package/src/resources/extensions/gsd/auto.ts +12 -17
- 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 +42 -4
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +13 -1
- 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/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/file-change-validator.ts +13 -2
- package/src/resources/extensions/gsd/safety/safety-harness.ts +6 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +49 -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/doctor-providers.test.ts +31 -0
- 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 +38 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +152 -1
- 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/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-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/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/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/workflow-logger.ts +2 -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/{UMCfv_sVnLXawpUAjvArc → pI48IF3dgfs0CBrYi2bh_}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{UMCfv_sVnLXawpUAjvArc → pI48IF3dgfs0CBrYi2bh_}/_ssgManifest.js +0 -0
|
@@ -660,6 +660,55 @@ export function validatePreferences(preferences) {
|
|
|
660
660
|
errors.push("context_management must be an object");
|
|
661
661
|
}
|
|
662
662
|
}
|
|
663
|
+
// ─── Context Mode (gsd_exec sandbox) ────────────────────────────────────
|
|
664
|
+
if (preferences.context_mode !== undefined) {
|
|
665
|
+
if (typeof preferences.context_mode === "object" && preferences.context_mode !== null) {
|
|
666
|
+
const cmode = preferences.context_mode;
|
|
667
|
+
const validCmode = {};
|
|
668
|
+
if (cmode.enabled !== undefined) {
|
|
669
|
+
if (typeof cmode.enabled === "boolean")
|
|
670
|
+
validCmode.enabled = cmode.enabled;
|
|
671
|
+
else
|
|
672
|
+
errors.push("context_mode.enabled must be a boolean");
|
|
673
|
+
}
|
|
674
|
+
if (cmode.exec_timeout_ms !== undefined) {
|
|
675
|
+
const t = cmode.exec_timeout_ms;
|
|
676
|
+
if (typeof t === "number" && t >= 1000 && t <= 600_000)
|
|
677
|
+
validCmode.exec_timeout_ms = Math.floor(t);
|
|
678
|
+
else
|
|
679
|
+
errors.push("context_mode.exec_timeout_ms must be a number between 1000 and 600000");
|
|
680
|
+
}
|
|
681
|
+
if (cmode.exec_stdout_cap_bytes !== undefined) {
|
|
682
|
+
const b = cmode.exec_stdout_cap_bytes;
|
|
683
|
+
if (typeof b === "number" && b >= 4096 && b <= 16_777_216)
|
|
684
|
+
validCmode.exec_stdout_cap_bytes = Math.floor(b);
|
|
685
|
+
else
|
|
686
|
+
errors.push("context_mode.exec_stdout_cap_bytes must be a number between 4096 and 16777216");
|
|
687
|
+
}
|
|
688
|
+
if (cmode.exec_digest_chars !== undefined) {
|
|
689
|
+
const c = cmode.exec_digest_chars;
|
|
690
|
+
if (typeof c === "number" && c >= 0 && c <= 4000)
|
|
691
|
+
validCmode.exec_digest_chars = Math.floor(c);
|
|
692
|
+
else
|
|
693
|
+
errors.push("context_mode.exec_digest_chars must be a number between 0 and 4000");
|
|
694
|
+
}
|
|
695
|
+
if (cmode.exec_env_allowlist !== undefined) {
|
|
696
|
+
if (Array.isArray(cmode.exec_env_allowlist) &&
|
|
697
|
+
cmode.exec_env_allowlist.every((v) => typeof v === "string" && /^[A-Z_][A-Z0-9_]*$/i.test(v))) {
|
|
698
|
+
validCmode.exec_env_allowlist = cmode.exec_env_allowlist;
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
errors.push("context_mode.exec_env_allowlist must be an array of valid env var names");
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
if (Object.keys(validCmode).length > 0) {
|
|
705
|
+
validated.context_mode = validCmode;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
errors.push("context_mode must be an object");
|
|
710
|
+
}
|
|
711
|
+
}
|
|
663
712
|
// ─── Parallel Config ────────────────────────────────────────────────────
|
|
664
713
|
if (preferences.parallel && typeof preferences.parallel === "object") {
|
|
665
714
|
const p = preferences.parallel;
|
|
@@ -716,6 +765,40 @@ export function validatePreferences(preferences) {
|
|
|
716
765
|
validated.parallel = parallel;
|
|
717
766
|
}
|
|
718
767
|
}
|
|
768
|
+
// ─── Slice Parallel Config ───────────────────────────────────────────────
|
|
769
|
+
if (preferences.slice_parallel !== undefined) {
|
|
770
|
+
if (typeof preferences.slice_parallel === "object" && preferences.slice_parallel !== null) {
|
|
771
|
+
const sp = preferences.slice_parallel;
|
|
772
|
+
const validSp = {};
|
|
773
|
+
if (sp.enabled !== undefined) {
|
|
774
|
+
if (typeof sp.enabled === "boolean")
|
|
775
|
+
validSp.enabled = sp.enabled;
|
|
776
|
+
else
|
|
777
|
+
errors.push("slice_parallel.enabled must be a boolean");
|
|
778
|
+
}
|
|
779
|
+
if (sp.max_workers !== undefined) {
|
|
780
|
+
const maxWorkers = typeof sp.max_workers === "number" ? sp.max_workers : Number(sp.max_workers);
|
|
781
|
+
if (Number.isFinite(maxWorkers) && maxWorkers >= 1 && maxWorkers <= 8) {
|
|
782
|
+
validSp.max_workers = Math.floor(maxWorkers);
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
errors.push("slice_parallel.max_workers must be a number between 1 and 8");
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
const knownSliceParallelKeys = new Set(["enabled", "max_workers"]);
|
|
789
|
+
for (const key of Object.keys(sp)) {
|
|
790
|
+
if (!knownSliceParallelKeys.has(key)) {
|
|
791
|
+
warnings.push(`unknown slice_parallel key "${key}" — ignored`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (Object.keys(validSp).length > 0) {
|
|
795
|
+
validated.slice_parallel = validSp;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
errors.push("slice_parallel must be an object");
|
|
800
|
+
}
|
|
801
|
+
}
|
|
719
802
|
// ─── Reactive Execution ─────────────────────────────────────────────────
|
|
720
803
|
if (preferences.reactive_execution !== undefined) {
|
|
721
804
|
if (typeof preferences.reactive_execution === "object" && preferences.reactive_execution !== null) {
|
|
@@ -26,12 +26,12 @@ export { resolveAllSkillReferences } from "./preferences-skills.js";
|
|
|
26
26
|
// These lived in preferences-skills.ts but imported loadEffectiveGSDPreferences
|
|
27
27
|
// back from this file, creating a circular dependency. Moved here since they
|
|
28
28
|
// are trivial wrappers over loadEffectiveGSDPreferences.
|
|
29
|
-
export function resolveSkillDiscoveryMode() {
|
|
30
|
-
const prefs = loadEffectiveGSDPreferences();
|
|
29
|
+
export function resolveSkillDiscoveryMode(basePath) {
|
|
30
|
+
const prefs = loadEffectiveGSDPreferences(basePath);
|
|
31
31
|
return prefs?.preferences.skill_discovery ?? "suggest";
|
|
32
32
|
}
|
|
33
|
-
export function resolveSkillStalenessDays() {
|
|
34
|
-
const prefs = loadEffectiveGSDPreferences();
|
|
33
|
+
export function resolveSkillStalenessDays(basePath) {
|
|
34
|
+
const prefs = loadEffectiveGSDPreferences(basePath);
|
|
35
35
|
return prefs?.preferences.skill_staleness_days ?? 60;
|
|
36
36
|
}
|
|
37
37
|
// ─── Re-exports: models ─────────────────────────────────────────────────────
|
|
@@ -46,16 +46,16 @@ function globalPreferencesPath() {
|
|
|
46
46
|
function legacyGlobalPreferencesPath() {
|
|
47
47
|
return join(homedir(), ".pi", "agent", "gsd-preferences.md");
|
|
48
48
|
}
|
|
49
|
-
function projectPreferencesPath() {
|
|
50
|
-
return join(gsdRoot(
|
|
49
|
+
function projectPreferencesPath(basePath = process.cwd()) {
|
|
50
|
+
return join(gsdRoot(basePath), "PREFERENCES.md");
|
|
51
51
|
}
|
|
52
52
|
// Legacy lowercase files can still exist in older projects. Keep them as a
|
|
53
53
|
// compatibility-only fallback, but route new reads/writes through PREFERENCES.md.
|
|
54
54
|
function legacyGlobalPreferencesPathLowercase() {
|
|
55
55
|
return join(gsdHome(), "preferences.md");
|
|
56
56
|
}
|
|
57
|
-
function legacyProjectPreferencesPathLowercase() {
|
|
58
|
-
return join(gsdRoot(
|
|
57
|
+
function legacyProjectPreferencesPathLowercase(basePath = process.cwd()) {
|
|
58
|
+
return join(gsdRoot(basePath), "preferences.md");
|
|
59
59
|
}
|
|
60
60
|
export function getGlobalGSDPreferencesPath() {
|
|
61
61
|
return globalPreferencesPath();
|
|
@@ -63,8 +63,8 @@ export function getGlobalGSDPreferencesPath() {
|
|
|
63
63
|
export function getLegacyGlobalGSDPreferencesPath() {
|
|
64
64
|
return legacyGlobalPreferencesPath();
|
|
65
65
|
}
|
|
66
|
-
export function getProjectGSDPreferencesPath() {
|
|
67
|
-
return projectPreferencesPath();
|
|
66
|
+
export function getProjectGSDPreferencesPath(basePath) {
|
|
67
|
+
return projectPreferencesPath(basePath);
|
|
68
68
|
}
|
|
69
69
|
// ─── Loading ────────────────────────────────────────────────────────────────
|
|
70
70
|
export function loadGlobalGSDPreferences() {
|
|
@@ -72,13 +72,13 @@ export function loadGlobalGSDPreferences() {
|
|
|
72
72
|
?? loadPreferencesFile(legacyGlobalPreferencesPathLowercase(), "global")
|
|
73
73
|
?? loadPreferencesFile(legacyGlobalPreferencesPath(), "global");
|
|
74
74
|
}
|
|
75
|
-
export function loadProjectGSDPreferences() {
|
|
76
|
-
return loadPreferencesFile(projectPreferencesPath(), "project")
|
|
77
|
-
?? loadPreferencesFile(legacyProjectPreferencesPathLowercase(), "project");
|
|
75
|
+
export function loadProjectGSDPreferences(basePath) {
|
|
76
|
+
return loadPreferencesFile(projectPreferencesPath(basePath), "project")
|
|
77
|
+
?? loadPreferencesFile(legacyProjectPreferencesPathLowercase(basePath), "project");
|
|
78
78
|
}
|
|
79
|
-
export function loadEffectiveGSDPreferences() {
|
|
79
|
+
export function loadEffectiveGSDPreferences(basePath) {
|
|
80
80
|
const globalPreferences = loadGlobalGSDPreferences();
|
|
81
|
-
const projectPreferences = loadProjectGSDPreferences();
|
|
81
|
+
const projectPreferences = loadProjectGSDPreferences(basePath);
|
|
82
82
|
if (!globalPreferences && !projectPreferences)
|
|
83
83
|
return null;
|
|
84
84
|
let result;
|
|
@@ -489,8 +489,8 @@ export function resolvePreDispatchHooks() {
|
|
|
489
489
|
* Worktree isolation requires explicit opt-in because it depends on git
|
|
490
490
|
* branch infrastructure that must be set up before use.
|
|
491
491
|
*/
|
|
492
|
-
export function getIsolationMode() {
|
|
493
|
-
const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
492
|
+
export function getIsolationMode(basePath) {
|
|
493
|
+
const prefs = loadEffectiveGSDPreferences(basePath)?.preferences?.git;
|
|
494
494
|
if (prefs?.isolation === "worktree")
|
|
495
495
|
return "worktree";
|
|
496
496
|
if (prefs?.isolation === "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
|
|
|
@@ -8,9 +8,12 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
10
10
|
*/
|
|
11
|
+
import { createRequire } from "node:module";
|
|
11
12
|
import { execFileSync } from "node:child_process";
|
|
12
13
|
import { normalizePlannedFileReference } from "../files.js";
|
|
13
14
|
import { logWarning } from "../workflow-logger.js";
|
|
15
|
+
const _require = createRequire(import.meta.url);
|
|
16
|
+
const picomatch = _require("picomatch");
|
|
14
17
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
15
18
|
/**
|
|
16
19
|
* Validate file changes after auto-commit for an execute-task unit.
|
|
@@ -20,7 +23,7 @@ import { logWarning } from "../workflow-logger.js";
|
|
|
20
23
|
* @param expectedOutput - JSON array from tasks.expected_output DB column
|
|
21
24
|
* @param plannedFiles - JSON array from tasks.files DB column
|
|
22
25
|
*/
|
|
23
|
-
export function validateFileChanges(basePath, expectedOutput, plannedFiles) {
|
|
26
|
+
export function validateFileChanges(basePath, expectedOutput, plannedFiles, fileChangeAllowlist = []) {
|
|
24
27
|
const allExpected = new Set([...expectedOutput, ...plannedFiles]);
|
|
25
28
|
// If no expected files were planned, skip validation
|
|
26
29
|
if (allExpected.size === 0)
|
|
@@ -33,8 +36,11 @@ export function validateFileChanges(basePath, expectedOutput, plannedFiles) {
|
|
|
33
36
|
const projectFiles = actualFiles.filter(f => !f.startsWith(".gsd/") && !f.startsWith(".gsd\\"));
|
|
34
37
|
// Normalize expected paths (strip leading ./ or /)
|
|
35
38
|
const normalizedExpected = new Set([...allExpected].map((f) => normalizePlannedFileReference(f).replace(/^\.\//, "").replace(/^\//, "")));
|
|
36
|
-
//
|
|
37
|
-
const
|
|
39
|
+
// Build allowlist matchers once (dot: true so patterns like `**/.hidden` work).
|
|
40
|
+
const allowlistMatchers = fileChangeAllowlist.map(p => picomatch(p, { dot: true }));
|
|
41
|
+
const isAllowlisted = (f) => allowlistMatchers.some(m => m(f));
|
|
42
|
+
// Compute symmetric difference, excluding allowlisted files
|
|
43
|
+
const unexpectedFiles = projectFiles.filter(f => !normalizedExpected.has(f) && !isAllowlisted(f));
|
|
38
44
|
const missingFiles = [...normalizedExpected].filter(f => !projectFiles.includes(f));
|
|
39
45
|
const violations = [];
|
|
40
46
|
for (const f of unexpectedFiles) {
|
|
@@ -23,6 +23,7 @@ const DEFAULTS = {
|
|
|
23
23
|
checkpoints: true,
|
|
24
24
|
auto_rollback: false,
|
|
25
25
|
timeout_scale_cap: 6,
|
|
26
|
+
file_change_allowlist: [],
|
|
26
27
|
};
|
|
27
28
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
28
29
|
/**
|
|
@@ -42,6 +43,9 @@ export function resolveSafetyHarnessConfig(raw) {
|
|
|
42
43
|
checkpoints: typeof raw.checkpoints === "boolean" ? raw.checkpoints : DEFAULTS.checkpoints,
|
|
43
44
|
auto_rollback: typeof raw.auto_rollback === "boolean" ? raw.auto_rollback : DEFAULTS.auto_rollback,
|
|
44
45
|
timeout_scale_cap: typeof raw.timeout_scale_cap === "number" ? raw.timeout_scale_cap : DEFAULTS.timeout_scale_cap,
|
|
46
|
+
file_change_allowlist: Array.isArray(raw.file_change_allowlist)
|
|
47
|
+
? raw.file_change_allowlist.filter((p) => typeof p === "string")
|
|
48
|
+
: DEFAULTS.file_change_allowlist,
|
|
45
49
|
};
|
|
46
50
|
}
|
|
47
51
|
/**
|
|
@@ -16,7 +16,11 @@ async function getEncoder() {
|
|
|
16
16
|
try {
|
|
17
17
|
// @ts-ignore — tiktoken may not have type declarations in extensions tsconfig
|
|
18
18
|
const tiktoken = await import("tiktoken");
|
|
19
|
-
|
|
19
|
+
// Use cl100k_base — the most conservative and broadly compatible BPE encoding.
|
|
20
|
+
// It is shared by GPT-3.5/GPT-4 and gives a safer (larger) estimate than
|
|
21
|
+
// gpt-4o's o200k_base encoding, which produces fewer tokens for the same text
|
|
22
|
+
// and would cause context windows for non-OpenAI providers to be under-counted.
|
|
23
|
+
encoder = tiktoken.get_encoding("cl100k_base");
|
|
20
24
|
return encoder;
|
|
21
25
|
}
|
|
22
26
|
catch {
|
|
@@ -24,19 +28,32 @@ async function getEncoder() {
|
|
|
24
28
|
return null;
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Count tokens in `text` using tiktoken (cl100k_base) when available.
|
|
33
|
+
*
|
|
34
|
+
* When tiktoken is not loaded, falls back to a provider-aware character-ratio
|
|
35
|
+
* estimate via `estimateTokensForProvider`. Passing `provider` is recommended
|
|
36
|
+
* so the heuristic fallback is as accurate as possible.
|
|
37
|
+
*/
|
|
38
|
+
export async function countTokens(text, provider) {
|
|
28
39
|
const enc = await getEncoder();
|
|
29
40
|
if (enc) {
|
|
30
41
|
const tokens = enc.encode(text);
|
|
31
42
|
return tokens.length;
|
|
32
43
|
}
|
|
33
|
-
return
|
|
44
|
+
return estimateTokensForProvider(text, provider ?? "unknown");
|
|
34
45
|
}
|
|
35
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Synchronous token count — only accurate after `initTokenCounter()` resolves.
|
|
48
|
+
*
|
|
49
|
+
* Before init, or when tiktoken is unavailable, falls back to a provider-aware
|
|
50
|
+
* character-ratio estimate. Passing `provider` is recommended.
|
|
51
|
+
*/
|
|
52
|
+
export function countTokensSync(text, provider) {
|
|
36
53
|
if (encoder) {
|
|
37
54
|
return encoder.encode(text).length;
|
|
38
55
|
}
|
|
39
|
-
return
|
|
56
|
+
return estimateTokensForProvider(text, provider ?? "unknown");
|
|
40
57
|
}
|
|
41
58
|
export async function initTokenCounter() {
|
|
42
59
|
const enc = await getEncoder();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// GSD Exec Search Tool — lists and filters prior gsd_exec runs.
|
|
2
|
+
//
|
|
3
|
+
// Scans .gsd/exec/*.meta.json and returns a ranked summary so agents can
|
|
4
|
+
// re-discover past runs without re-executing. Read-only; no DB writes.
|
|
5
|
+
import { searchExecHistory } from "../exec-history.js";
|
|
6
|
+
export function executeExecSearch(params, opts) {
|
|
7
|
+
const searchOpts = {
|
|
8
|
+
query: typeof params.query === "string" ? params.query : undefined,
|
|
9
|
+
runtime: params.runtime,
|
|
10
|
+
failing_only: params.failing_only === true,
|
|
11
|
+
limit: typeof params.limit === "number" ? params.limit : undefined,
|
|
12
|
+
};
|
|
13
|
+
const hits = searchExecHistory(opts.baseDir, searchOpts);
|
|
14
|
+
if (hits.length === 0) {
|
|
15
|
+
return {
|
|
16
|
+
content: [{ type: "text", text: "No prior gsd_exec runs match those filters." }],
|
|
17
|
+
details: { operation: "gsd_exec_search", matches: 0 },
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const lines = [`Found ${hits.length} exec run(s), most recent first:`];
|
|
21
|
+
for (const hit of hits) {
|
|
22
|
+
const e = hit.entry;
|
|
23
|
+
const status = formatStatus(e);
|
|
24
|
+
const purpose = e.purpose ? ` — ${e.purpose}` : "";
|
|
25
|
+
const truncated = e.stdout_truncated ? " (stdout truncated)" : "";
|
|
26
|
+
lines.push(`- [${e.id}] ${e.runtime} ${status} ${e.duration_ms}ms${truncated}${purpose}`, ` stdout: ${e.stdout_path}`);
|
|
27
|
+
if (hit.digest_preview) {
|
|
28
|
+
const preview = hit.digest_preview.replace(/\n/g, "\n ");
|
|
29
|
+
lines.push(` preview:\n ${preview}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
34
|
+
details: {
|
|
35
|
+
operation: "gsd_exec_search",
|
|
36
|
+
matches: hits.length,
|
|
37
|
+
results: hits.map((hit) => ({
|
|
38
|
+
id: hit.entry.id,
|
|
39
|
+
runtime: hit.entry.runtime,
|
|
40
|
+
exit_code: hit.entry.exit_code,
|
|
41
|
+
timed_out: hit.entry.timed_out,
|
|
42
|
+
duration_ms: hit.entry.duration_ms,
|
|
43
|
+
purpose: hit.entry.purpose,
|
|
44
|
+
stdout_path: hit.entry.stdout_path,
|
|
45
|
+
stderr_path: hit.entry.stderr_path,
|
|
46
|
+
meta_path: hit.entry.meta_path,
|
|
47
|
+
})),
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function formatStatus(entry) {
|
|
52
|
+
if (entry.timed_out)
|
|
53
|
+
return "timeout";
|
|
54
|
+
if (entry.signal)
|
|
55
|
+
return `signal:${entry.signal}`;
|
|
56
|
+
if (entry.exit_code === null)
|
|
57
|
+
return "exit:null";
|
|
58
|
+
return `exit:${entry.exit_code}`;
|
|
59
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// GSD Exec Tool — executor for the gsd_exec MCP tool.
|
|
2
|
+
//
|
|
3
|
+
// Thin wrapper around exec-sandbox.ts that reads effective options from
|
|
4
|
+
// the project preferences (context_mode block) and formats the result
|
|
5
|
+
// for MCP return.
|
|
6
|
+
import { EXEC_DEFAULTS, runExecSandbox, } from "../exec-sandbox.js";
|
|
7
|
+
import { isContextModeEnabled } from "../preferences-types.js";
|
|
8
|
+
export function buildExecOptions(baseDir, cfg, extras) {
|
|
9
|
+
const allowlist = Array.isArray(cfg?.exec_env_allowlist) ? cfg.exec_env_allowlist : EXEC_DEFAULTS.envAllowlist;
|
|
10
|
+
const stdoutCap = clampNumber(cfg?.exec_stdout_cap_bytes, EXEC_DEFAULTS.stdoutCapBytes, 4_096, 16_777_216);
|
|
11
|
+
const defaultTimeout = clampNumber(cfg?.exec_timeout_ms, EXEC_DEFAULTS.defaultTimeoutMs, 1_000, EXEC_DEFAULTS.clampTimeoutMs);
|
|
12
|
+
const digestChars = clampNumber(cfg?.exec_digest_chars, EXEC_DEFAULTS.digestChars, 0, 4_000);
|
|
13
|
+
return {
|
|
14
|
+
baseDir,
|
|
15
|
+
clamp_timeout_ms: EXEC_DEFAULTS.clampTimeoutMs,
|
|
16
|
+
default_timeout_ms: defaultTimeout,
|
|
17
|
+
stdout_cap_bytes: stdoutCap,
|
|
18
|
+
stderr_cap_bytes: EXEC_DEFAULTS.stderrCapBytes,
|
|
19
|
+
digest_chars: digestChars,
|
|
20
|
+
env_allowlist: allowlist,
|
|
21
|
+
...extras,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function clampNumber(value, fallback, min, max) {
|
|
25
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
26
|
+
return fallback;
|
|
27
|
+
if (value < min)
|
|
28
|
+
return min;
|
|
29
|
+
if (value > max)
|
|
30
|
+
return max;
|
|
31
|
+
return Math.floor(value);
|
|
32
|
+
}
|
|
33
|
+
function isEnabled(prefs) {
|
|
34
|
+
return isContextModeEnabled(prefs);
|
|
35
|
+
}
|
|
36
|
+
function disabledResult() {
|
|
37
|
+
return {
|
|
38
|
+
content: [
|
|
39
|
+
{
|
|
40
|
+
type: "text",
|
|
41
|
+
text: "gsd_exec is disabled by `context_mode.enabled: false` in preferences. Remove that " +
|
|
42
|
+
"override (or set it to true) to re-enable sandboxed tool-output execution.",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
details: { operation: "gsd_exec", error: "context_mode_disabled" },
|
|
46
|
+
isError: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function paramError(message) {
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
52
|
+
details: { operation: "gsd_exec", error: "invalid_params", detail: message },
|
|
53
|
+
isError: true,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export async function executeGsdExec(params, deps) {
|
|
57
|
+
if (!isEnabled(deps.preferences))
|
|
58
|
+
return disabledResult();
|
|
59
|
+
const runtime = params.runtime;
|
|
60
|
+
if (runtime !== "bash" && runtime !== "node" && runtime !== "python") {
|
|
61
|
+
return paramError(`invalid runtime "${String(runtime)}" — must be bash | node | python`);
|
|
62
|
+
}
|
|
63
|
+
const script = typeof params.script === "string" ? params.script : "";
|
|
64
|
+
if (script.trim().length === 0) {
|
|
65
|
+
return paramError("script is required and must be a non-empty string");
|
|
66
|
+
}
|
|
67
|
+
if (Buffer.byteLength(script, "utf8") > 200_000) {
|
|
68
|
+
return paramError("script exceeds the 200 KB length limit");
|
|
69
|
+
}
|
|
70
|
+
const opts = buildExecOptions(deps.baseDir, deps.preferences?.context_mode, { now: deps.now, generateId: deps.generateId });
|
|
71
|
+
const run = deps.run ?? runExecSandbox;
|
|
72
|
+
try {
|
|
73
|
+
const result = await run({
|
|
74
|
+
runtime,
|
|
75
|
+
script,
|
|
76
|
+
...(typeof params.purpose === "string" ? { purpose: params.purpose } : {}),
|
|
77
|
+
...(typeof params.timeout_ms === "number" ? { timeout_ms: params.timeout_ms } : {}),
|
|
78
|
+
}, opts);
|
|
79
|
+
return formatResult(result);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
83
|
+
return {
|
|
84
|
+
content: [{ type: "text", text: `Error: gsd_exec failed — ${message}` }],
|
|
85
|
+
details: { operation: "gsd_exec", error: message },
|
|
86
|
+
isError: true,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function formatResult(result) {
|
|
91
|
+
const headerLines = [
|
|
92
|
+
`gsd_exec[${result.id}] runtime=${result.runtime} exit=${formatExit(result)} duration=${result.duration_ms}ms`,
|
|
93
|
+
` stdout: ${result.stdout_bytes}B${result.stdout_truncated ? " (truncated)" : ""} → ${result.stdout_path}`,
|
|
94
|
+
` stderr: ${result.stderr_bytes}B${result.stderr_truncated ? " (truncated)" : ""} → ${result.stderr_path}`,
|
|
95
|
+
];
|
|
96
|
+
const summary = `${headerLines.join("\n")}\n--- digest ---\n${result.digest}`.trimEnd();
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: "text", text: summary }],
|
|
99
|
+
details: {
|
|
100
|
+
operation: "gsd_exec",
|
|
101
|
+
id: result.id,
|
|
102
|
+
runtime: result.runtime,
|
|
103
|
+
exit_code: result.exit_code,
|
|
104
|
+
signal: result.signal,
|
|
105
|
+
timed_out: result.timed_out,
|
|
106
|
+
duration_ms: result.duration_ms,
|
|
107
|
+
stdout_bytes: result.stdout_bytes,
|
|
108
|
+
stderr_bytes: result.stderr_bytes,
|
|
109
|
+
stdout_truncated: result.stdout_truncated,
|
|
110
|
+
stderr_truncated: result.stderr_truncated,
|
|
111
|
+
stdout_path: result.stdout_path,
|
|
112
|
+
stderr_path: result.stderr_path,
|
|
113
|
+
meta_path: result.meta_path,
|
|
114
|
+
},
|
|
115
|
+
isError: result.timed_out || result.signal !== null || result.exit_code !== 0,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function formatExit(result) {
|
|
119
|
+
if (result.timed_out)
|
|
120
|
+
return "timeout";
|
|
121
|
+
if (result.signal)
|
|
122
|
+
return `signal:${result.signal}`;
|
|
123
|
+
if (result.exit_code === null)
|
|
124
|
+
return "null";
|
|
125
|
+
return String(result.exit_code);
|
|
126
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// GSD Resume Tool — returns the contents of .gsd/last-snapshot.md so
|
|
2
|
+
// agents can re-orient after compaction or session resume without
|
|
3
|
+
// re-deriving project memory state.
|
|
4
|
+
import { readCompactionSnapshot } from "../compaction-snapshot.js";
|
|
5
|
+
export function executeResume(_params, opts) {
|
|
6
|
+
const snapshot = readCompactionSnapshot(opts.baseDir);
|
|
7
|
+
if (snapshot == null) {
|
|
8
|
+
return {
|
|
9
|
+
content: [
|
|
10
|
+
{
|
|
11
|
+
type: "text",
|
|
12
|
+
text: "No snapshot found at .gsd/last-snapshot.md. The snapshot is written automatically " +
|
|
13
|
+
"on session_before_compact (enabled by default; set context_mode.enabled=false to opt out).",
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
details: { operation: "gsd_resume", found: false },
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: "text", text: snapshot }],
|
|
21
|
+
details: { operation: "gsd_resume", found: true, bytes: Buffer.byteLength(snapshot, "utf-8") },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -5,6 +5,9 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
5
5
|
const MCP_WORKFLOW_TOOL_SURFACE = new Set([
|
|
6
6
|
"ask_user_questions",
|
|
7
7
|
"gsd_decision_save",
|
|
8
|
+
"gsd_exec",
|
|
9
|
+
"gsd_exec_search",
|
|
10
|
+
"gsd_resume",
|
|
8
11
|
"gsd_complete_milestone",
|
|
9
12
|
"gsd_complete_task",
|
|
10
13
|
"gsd_complete_slice",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: verify-before-complete
|
|
3
|
-
description:
|
|
3
|
+
description: >-
|
|
4
|
+
Block completion claims until verification evidence has been produced in the current message. Use before marking a task/slice/milestone complete, before creating a commit or PR, before saying "it works" or "tests pass", and any time you are about to claim work is done. The rule is: evidence before claims, always — running the verification must happen now, not "earlier in the session". Fresh output or no claim.
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
<objective>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: write-docs
|
|
3
|
-
description:
|
|
3
|
+
description: >-
|
|
4
|
+
Collaborative document authoring workflow for proposals, technical specs, decision docs, README sections, ADRs, and long-form prose that must work for fresh readers. Use when asked to "write the docs", "draft a proposal", "write a spec", "write an RFC", "write the README", or when a document needs to be understandable by someone without this session's context. Three stages: gather context, iterate on structure, reader-test for a stranger.
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
<objective>
|