gsd-pi 2.76.0-dev.82e249f7b → 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/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 +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/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/file-change-validator.js +10 -4
- 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 +7 -7
- 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 +7 -7
- 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/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/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/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/file-change-validator.ts +14 -3
- 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 +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/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/{ecSsu49rxxcpbNmVP4mLD → pI48IF3dgfs0CBrYi2bh_}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → pI48IF3dgfs0CBrYi2bh_}/_ssgManifest.js +0 -0
|
@@ -135,7 +135,7 @@ function openRawDb(path) {
|
|
|
135
135
|
const Database = providerModule;
|
|
136
136
|
return new Database(path);
|
|
137
137
|
}
|
|
138
|
-
const SCHEMA_VERSION =
|
|
138
|
+
const SCHEMA_VERSION = 22;
|
|
139
139
|
function indexExists(db, name) {
|
|
140
140
|
return !!db.prepare("SELECT 1 as present FROM sqlite_master WHERE type = 'index' AND name = ?").get(name);
|
|
141
141
|
}
|
|
@@ -495,7 +495,9 @@ function initSchema(db, fileBacked) {
|
|
|
495
495
|
// that fresh installs already have. Add only columns needed by bootstrap
|
|
496
496
|
// indexes so old DBs can open far enough for the normal migration chain.
|
|
497
497
|
ensureBootstrapIndexColumns(db);
|
|
498
|
-
db
|
|
498
|
+
if (columnExists(db, "memories", "scope")) {
|
|
499
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
|
|
500
|
+
}
|
|
499
501
|
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_kind ON memory_sources(kind)");
|
|
500
502
|
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_scope ON memory_sources(scope)");
|
|
501
503
|
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_from ON memory_relations(from_id)");
|
|
@@ -854,19 +856,24 @@ function migrateSchema(db) {
|
|
|
854
856
|
});
|
|
855
857
|
}
|
|
856
858
|
if (currentVersion < 12) {
|
|
859
|
+
// NOTE: The original DDL used COALESCE(task_id, '') in the PRIMARY KEY
|
|
860
|
+
// expression, which is invalid SQLite syntax and causes startup errors on
|
|
861
|
+
// DBs that migrate through v12. The corrected DDL uses
|
|
862
|
+
// task_id TEXT NOT NULL DEFAULT '' with a plain column list PK. DBs that
|
|
863
|
+
// were created with the broken DDL are repaired by the v22 migration below.
|
|
857
864
|
db.exec(`
|
|
858
865
|
CREATE TABLE IF NOT EXISTS quality_gates (
|
|
859
866
|
milestone_id TEXT NOT NULL,
|
|
860
867
|
slice_id TEXT NOT NULL,
|
|
861
868
|
gate_id TEXT NOT NULL,
|
|
862
869
|
scope TEXT NOT NULL DEFAULT 'slice',
|
|
863
|
-
task_id TEXT DEFAULT
|
|
870
|
+
task_id TEXT NOT NULL DEFAULT '',
|
|
864
871
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
865
872
|
verdict TEXT NOT NULL DEFAULT '',
|
|
866
873
|
rationale TEXT NOT NULL DEFAULT '',
|
|
867
874
|
findings TEXT NOT NULL DEFAULT '',
|
|
868
875
|
evaluated_at TEXT DEFAULT NULL,
|
|
869
|
-
PRIMARY KEY (milestone_id, slice_id, gate_id,
|
|
876
|
+
PRIMARY KEY (milestone_id, slice_id, gate_id, task_id),
|
|
870
877
|
FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
|
|
871
878
|
)
|
|
872
879
|
`);
|
|
@@ -1085,6 +1092,51 @@ function migrateSchema(db) {
|
|
|
1085
1092
|
":applied_at": new Date().toISOString(),
|
|
1086
1093
|
});
|
|
1087
1094
|
}
|
|
1095
|
+
if (currentVersion < 22) {
|
|
1096
|
+
// v22: Repair quality_gates tables that were created by the broken v12
|
|
1097
|
+
// migration (which used COALESCE(task_id, '') as a PK expression — invalid
|
|
1098
|
+
// SQLite DDL). Those DBs have task_id nullable (dflt_value NULL, notnull 0).
|
|
1099
|
+
// Rebuild the table with the correct schema, migrating existing rows via
|
|
1100
|
+
// COALESCE so no data is lost.
|
|
1101
|
+
const qgInfo = db.prepare("PRAGMA table_info(quality_gates)").all();
|
|
1102
|
+
const taskIdCol = qgInfo.find((r) => r["name"] === "task_id");
|
|
1103
|
+
const needsRepair = taskIdCol && (taskIdCol["notnull"] === 0 || taskIdCol["notnull"] === "0");
|
|
1104
|
+
if (needsRepair) {
|
|
1105
|
+
db.exec(`
|
|
1106
|
+
CREATE TABLE quality_gates_new (
|
|
1107
|
+
milestone_id TEXT NOT NULL,
|
|
1108
|
+
slice_id TEXT NOT NULL,
|
|
1109
|
+
gate_id TEXT NOT NULL,
|
|
1110
|
+
scope TEXT NOT NULL DEFAULT 'slice',
|
|
1111
|
+
task_id TEXT NOT NULL DEFAULT '',
|
|
1112
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
1113
|
+
verdict TEXT NOT NULL DEFAULT '',
|
|
1114
|
+
rationale TEXT NOT NULL DEFAULT '',
|
|
1115
|
+
findings TEXT NOT NULL DEFAULT '',
|
|
1116
|
+
evaluated_at TEXT DEFAULT NULL,
|
|
1117
|
+
PRIMARY KEY (milestone_id, slice_id, gate_id, task_id),
|
|
1118
|
+
FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
|
|
1119
|
+
)
|
|
1120
|
+
`);
|
|
1121
|
+
db.exec(`
|
|
1122
|
+
INSERT OR IGNORE INTO quality_gates_new
|
|
1123
|
+
(milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
|
|
1124
|
+
SELECT milestone_id, slice_id, gate_id, scope, COALESCE(task_id, ''), status, verdict, rationale, findings, evaluated_at
|
|
1125
|
+
FROM quality_gates
|
|
1126
|
+
`);
|
|
1127
|
+
db.exec("DROP TABLE quality_gates");
|
|
1128
|
+
db.exec("ALTER TABLE quality_gates_new RENAME TO quality_gates");
|
|
1129
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
|
|
1130
|
+
}
|
|
1131
|
+
// Ensure scope column exists on quality_gates and assessments (guard
|
|
1132
|
+
// against DBs that somehow lack it after a partial migration).
|
|
1133
|
+
ensureColumn(db, "quality_gates", "scope", "ALTER TABLE quality_gates ADD COLUMN scope TEXT NOT NULL DEFAULT 'slice'");
|
|
1134
|
+
ensureColumn(db, "assessments", "scope", "ALTER TABLE assessments ADD COLUMN scope TEXT NOT NULL DEFAULT ''");
|
|
1135
|
+
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1136
|
+
":version": 22,
|
|
1137
|
+
":applied_at": new Date().toISOString(),
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1088
1140
|
db.exec("COMMIT");
|
|
1089
1141
|
}
|
|
1090
1142
|
catch (err) {
|
|
@@ -1097,6 +1149,8 @@ let currentPath = null;
|
|
|
1097
1149
|
let currentPid = 0;
|
|
1098
1150
|
let _exitHandlerRegistered = false;
|
|
1099
1151
|
let _dbOpenAttempted = false;
|
|
1152
|
+
let _lastDbError = null;
|
|
1153
|
+
let _lastDbPhase = null;
|
|
1100
1154
|
export function getDbProvider() {
|
|
1101
1155
|
loadProvider();
|
|
1102
1156
|
return providerName;
|
|
@@ -1113,13 +1167,54 @@ export function isDbAvailable() {
|
|
|
1113
1167
|
export function wasDbOpenAttempted() {
|
|
1114
1168
|
return _dbOpenAttempted;
|
|
1115
1169
|
}
|
|
1170
|
+
export function getDbStatus() {
|
|
1171
|
+
loadProvider();
|
|
1172
|
+
return {
|
|
1173
|
+
available: currentDb !== null,
|
|
1174
|
+
provider: providerName,
|
|
1175
|
+
attempted: _dbOpenAttempted,
|
|
1176
|
+
lastError: _lastDbError,
|
|
1177
|
+
lastPhase: _lastDbPhase,
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1116
1180
|
export function openDatabase(path) {
|
|
1117
1181
|
_dbOpenAttempted = true;
|
|
1118
1182
|
if (currentDb && currentPath !== path)
|
|
1119
1183
|
closeDatabase();
|
|
1120
1184
|
if (currentDb && currentPath === path)
|
|
1121
1185
|
return true;
|
|
1122
|
-
|
|
1186
|
+
// Reset error state only when a new open attempt is actually going to run.
|
|
1187
|
+
_lastDbError = null;
|
|
1188
|
+
_lastDbPhase = null;
|
|
1189
|
+
let rawDb;
|
|
1190
|
+
let fallbackProvider = null;
|
|
1191
|
+
let fallbackModule = null;
|
|
1192
|
+
try {
|
|
1193
|
+
rawDb = openRawDb(path);
|
|
1194
|
+
}
|
|
1195
|
+
catch (primaryErr) {
|
|
1196
|
+
_lastDbPhase = "open";
|
|
1197
|
+
_lastDbError = primaryErr instanceof Error ? primaryErr : new Error(String(primaryErr));
|
|
1198
|
+
// node:sqlite loaded but failed to open this file — try better-sqlite3 as fallback.
|
|
1199
|
+
if (providerName === "node:sqlite") {
|
|
1200
|
+
try {
|
|
1201
|
+
const mod = _require("better-sqlite3");
|
|
1202
|
+
const Db = (mod && mod.default) ? mod.default : mod;
|
|
1203
|
+
if (typeof Db === "function") {
|
|
1204
|
+
rawDb = new Db(path);
|
|
1205
|
+
fallbackProvider = "better-sqlite3";
|
|
1206
|
+
fallbackModule = Db;
|
|
1207
|
+
_lastDbError = null;
|
|
1208
|
+
_lastDbPhase = null;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
catch {
|
|
1212
|
+
// fallback unavailable; surface original error
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
if (!rawDb)
|
|
1216
|
+
throw primaryErr;
|
|
1217
|
+
}
|
|
1123
1218
|
if (!rawDb)
|
|
1124
1219
|
return false;
|
|
1125
1220
|
const adapter = createAdapter(rawDb);
|
|
@@ -1137,6 +1232,8 @@ export function openDatabase(path) {
|
|
|
1137
1232
|
process.stderr.write("gsd-db: recovered corrupt database via VACUUM\n");
|
|
1138
1233
|
}
|
|
1139
1234
|
catch (retryErr) {
|
|
1235
|
+
_lastDbPhase = "vacuum-recovery";
|
|
1236
|
+
_lastDbError = retryErr instanceof Error ? retryErr : new Error(String(retryErr));
|
|
1140
1237
|
try {
|
|
1141
1238
|
adapter.close();
|
|
1142
1239
|
}
|
|
@@ -1147,15 +1244,22 @@ export function openDatabase(path) {
|
|
|
1147
1244
|
}
|
|
1148
1245
|
}
|
|
1149
1246
|
else {
|
|
1247
|
+
_lastDbPhase = "initSchema";
|
|
1248
|
+
_lastDbError = err instanceof Error ? err : new Error(String(err));
|
|
1150
1249
|
try {
|
|
1151
1250
|
adapter.close();
|
|
1152
1251
|
}
|
|
1153
1252
|
catch (e) {
|
|
1154
|
-
logWarning("db", `close after
|
|
1253
|
+
logWarning("db", `close after initSchema failed: ${e.message}`);
|
|
1155
1254
|
}
|
|
1156
1255
|
throw err;
|
|
1157
1256
|
}
|
|
1158
1257
|
}
|
|
1258
|
+
// Commit fallback provider switch only after open + schema both succeeded.
|
|
1259
|
+
if (fallbackProvider) {
|
|
1260
|
+
providerName = fallbackProvider;
|
|
1261
|
+
providerModule = fallbackModule;
|
|
1262
|
+
}
|
|
1159
1263
|
currentDb = adapter;
|
|
1160
1264
|
currentPath = path;
|
|
1161
1265
|
currentPid = process.pid;
|
|
@@ -1194,8 +1298,12 @@ export function closeDatabase() {
|
|
|
1194
1298
|
currentDb = null;
|
|
1195
1299
|
currentPath = null;
|
|
1196
1300
|
currentPid = 0;
|
|
1197
|
-
_dbOpenAttempted = false;
|
|
1198
1301
|
}
|
|
1302
|
+
// Reset session-scoped state unconditionally so stale error info from a
|
|
1303
|
+
// failed open doesn't persist into the next open attempt or status check.
|
|
1304
|
+
_dbOpenAttempted = false;
|
|
1305
|
+
_lastDbError = null;
|
|
1306
|
+
_lastDbPhase = null;
|
|
1199
1307
|
}
|
|
1200
1308
|
/** Run a full VACUUM — call sparingly (e.g. after milestone completion). */
|
|
1201
1309
|
export function vacuumDatabase() {
|
|
@@ -79,6 +79,13 @@ function runPlanV2Gate(ctx, basePath, state) {
|
|
|
79
79
|
function buildDocsCommitInstruction(_message) {
|
|
80
80
|
return "Do not commit planning artifacts — .gsd/ is managed externally.";
|
|
81
81
|
}
|
|
82
|
+
// #4573: cap for how many times we nudge the LLM after a premature ready
|
|
83
|
+
// phrase before giving up and asking the user to re-run /gsd.
|
|
84
|
+
const MAX_READY_REJECTS = 2;
|
|
85
|
+
// #4573: matches the canonical ready phrase the discuss prompt asks the LLM
|
|
86
|
+
// to emit. Accepts any M-prefixed milestone ID (three digits + optional
|
|
87
|
+
// suffix) with optional trailing punctuation.
|
|
88
|
+
const READY_PHRASE_RE = /\bMilestone\s+M\d{3}[A-Z0-9-]*\s+ready\.?/i;
|
|
82
89
|
const pendingAutoStartMap = new Map();
|
|
83
90
|
/**
|
|
84
91
|
* Backward-compat bridge: returns a mutable reference to the entry matching
|
|
@@ -225,6 +232,188 @@ export function checkAutoStartAfterDiscuss() {
|
|
|
225
232
|
startAutoDetached(ctx, pi, basePath, false, { step });
|
|
226
233
|
return true;
|
|
227
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Extract the concatenated text content from an assistant message, whether it
|
|
237
|
+
* stores content as a string or as an array of text blocks.
|
|
238
|
+
*/
|
|
239
|
+
function extractAssistantText(msg) {
|
|
240
|
+
if (!msg)
|
|
241
|
+
return "";
|
|
242
|
+
const content = msg.content;
|
|
243
|
+
if (typeof content === "string")
|
|
244
|
+
return content;
|
|
245
|
+
if (!Array.isArray(content))
|
|
246
|
+
return "";
|
|
247
|
+
const parts = [];
|
|
248
|
+
for (const block of content) {
|
|
249
|
+
if (!block || typeof block !== "object")
|
|
250
|
+
continue;
|
|
251
|
+
if (block.type === "text" && typeof block.text === "string")
|
|
252
|
+
parts.push(block.text);
|
|
253
|
+
}
|
|
254
|
+
return parts.join("\n");
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Return true if the assistant message contains any tool-use block.
|
|
258
|
+
*/
|
|
259
|
+
function hasToolUse(msg) {
|
|
260
|
+
if (!msg)
|
|
261
|
+
return false;
|
|
262
|
+
const content = msg.content;
|
|
263
|
+
if (!Array.isArray(content))
|
|
264
|
+
return false;
|
|
265
|
+
return content.some((b) => b && typeof b === "object" && (b.type === "tool_use" || b.type === "tool-use"));
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* #4573 — Detect and recover from the "ready phrase without files" failure mode.
|
|
269
|
+
*
|
|
270
|
+
* When the LLM emits "Milestone {{id}} ready." but has not written CONTEXT.md
|
|
271
|
+
* or ROADMAP.md, `checkAutoStartAfterDiscuss()` silently returns false and the
|
|
272
|
+
* next /gsd invocation loops into the "All milestones complete" warning.
|
|
273
|
+
*
|
|
274
|
+
* This function, called from `handleAgentEnd` after `checkAutoStartAfterDiscuss`
|
|
275
|
+
* returns false, pattern-matches the ready phrase on the last assistant message.
|
|
276
|
+
* If it fired AND neither CONTEXT.md nor ROADMAP.md exists, it:
|
|
277
|
+
* 1. Notifies the user that the signal was rejected.
|
|
278
|
+
* 2. Injects a system message via `pi.sendMessage(..., {triggerTurn:true})`
|
|
279
|
+
* telling the LLM the signal was premature and to emit the writes now.
|
|
280
|
+
* 3. Caps at `MAX_READY_REJECTS` per-entry; beyond that, gives up and asks
|
|
281
|
+
* the user to re-run /gsd.
|
|
282
|
+
*
|
|
283
|
+
* Returns true when a nudge (or give-up) was emitted, signaling the caller to
|
|
284
|
+
* skip `resolveAgentEnd`.
|
|
285
|
+
*/
|
|
286
|
+
export function maybeHandleReadyPhraseWithoutFiles(event) {
|
|
287
|
+
const entry = _getPendingAutoStart();
|
|
288
|
+
if (!entry)
|
|
289
|
+
return false;
|
|
290
|
+
const { ctx, pi, basePath, milestoneId } = entry;
|
|
291
|
+
// Gate: last assistant message must contain the ready phrase
|
|
292
|
+
const lastMsg = event.messages[event.messages.length - 1];
|
|
293
|
+
const text = extractAssistantText(lastMsg);
|
|
294
|
+
if (!READY_PHRASE_RE.test(text))
|
|
295
|
+
return false;
|
|
296
|
+
// Gate: artifacts must still be missing — if they exist, the happy path
|
|
297
|
+
// already fired and we have nothing to do.
|
|
298
|
+
const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
|
|
299
|
+
const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
300
|
+
if (contextFile || roadmapFile)
|
|
301
|
+
return false;
|
|
302
|
+
entry.readyRejectCount = (entry.readyRejectCount ?? 0) + 1;
|
|
303
|
+
if (entry.readyRejectCount > MAX_READY_REJECTS) {
|
|
304
|
+
// Give up: clear state and tell the user to re-run /gsd. Avoids an
|
|
305
|
+
// infinite nudge loop when the LLM never produces the writes.
|
|
306
|
+
pendingAutoStartMap.delete(basePath);
|
|
307
|
+
ctx.ui.notify(`Milestone ${milestoneId}: LLM signaled "ready" ${entry.readyRejectCount} times without writing files. ` +
|
|
308
|
+
`Stopping auto-nudge. Run /gsd to try again.`, "error");
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
ctx.ui.notify(`Milestone ${milestoneId}: "ready" signal rejected — CONTEXT.md and ROADMAP.md are missing. Asking the LLM to complete the writes.`, "warning");
|
|
312
|
+
const nudge = `You emitted "Milestone ${milestoneId} ready." but neither ` +
|
|
313
|
+
`.gsd/milestones/${milestoneId}/${milestoneId}-CONTEXT.md nor ` +
|
|
314
|
+
`.gsd/milestones/${milestoneId}/${milestoneId}-ROADMAP.md exists on disk. ` +
|
|
315
|
+
`The ready phrase is a POST-WRITE signal and has been rejected. ` +
|
|
316
|
+
`In this turn: (1) write PROJECT.md, REQUIREMENTS.md, and the milestone ` +
|
|
317
|
+
`CONTEXT.md, (2) call gsd_plan_milestone, then (3) emit the ready phrase. ` +
|
|
318
|
+
`Do not describe these steps — execute them as tool calls. ` +
|
|
319
|
+
`This is retry ${entry.readyRejectCount}/${MAX_READY_REJECTS}; further ` +
|
|
320
|
+
`premature signals will clear the session.`;
|
|
321
|
+
try {
|
|
322
|
+
pi.sendMessage({ customType: "gsd-ready-no-files", content: nudge, display: false }, { triggerTurn: true });
|
|
323
|
+
}
|
|
324
|
+
catch (e) {
|
|
325
|
+
logWarning("guided", `ready-phrase nudge sendMessage failed: ${e.message}`);
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* #4573 — Detect and recover from the "announces tool, never calls it" stall.
|
|
332
|
+
*
|
|
333
|
+
* The LLM emits text like "I'll now write the CONTEXT.md file" but the turn
|
|
334
|
+
* ends with zero tool-use blocks. The harness has no post-turn tool-call
|
|
335
|
+
* validation, so the unit promise resolves and the user sees a stalled state.
|
|
336
|
+
*
|
|
337
|
+
* This function, called from `handleAgentEnd`, inspects the last assistant
|
|
338
|
+
* message. If ALL of the following are true, it injects a recovery message:
|
|
339
|
+
* - Text-only (no tool-use blocks)
|
|
340
|
+
* - Contains a commit-intent phrase ("I'll write", "I'll call", etc.)
|
|
341
|
+
* - Auto-mode is active OR a discussion autostart is pending
|
|
342
|
+
* - `emptyTurnRetryCount` is under the cap
|
|
343
|
+
*
|
|
344
|
+
* Per-handler state is held on the `PendingAutoStartEntry` when present, and
|
|
345
|
+
* on a module-level map otherwise. The counter resets on any successful
|
|
346
|
+
* tool-use turn via `resetEmptyTurnCounter`.
|
|
347
|
+
*/
|
|
348
|
+
const emptyTurnCounterByBase = new Map();
|
|
349
|
+
const MAX_EMPTY_TURN_RETRIES = 2;
|
|
350
|
+
// Phrases that indicate the LLM is about to do something but has not yet.
|
|
351
|
+
// Kept tight to avoid flagging legitimate narration like "I'll wait for your answer."
|
|
352
|
+
const COMMIT_INTENT_RE = /\b(?:I['’]ll|I will|Next,? I['’]ll|Now I['’]ll|Let me|I['’]m going to|I am going to)\s+(?:now\s+)?(?:write|create|call|invoke|update|add|make|run|execute|generate|produce|emit|compose|implement|save|apply|commit)\b/i;
|
|
353
|
+
/**
|
|
354
|
+
* Reset the empty-turn counter for a basePath after a successful tool-use turn.
|
|
355
|
+
* Called from handleAgentEnd when the last message contains tool_use blocks.
|
|
356
|
+
*/
|
|
357
|
+
export function resetEmptyTurnCounter(basePath) {
|
|
358
|
+
if (basePath)
|
|
359
|
+
emptyTurnCounterByBase.delete(basePath);
|
|
360
|
+
else
|
|
361
|
+
emptyTurnCounterByBase.clear();
|
|
362
|
+
}
|
|
363
|
+
export function maybeHandleEmptyIntentTurn(event, isAuto) {
|
|
364
|
+
// Gate: only fire when there is system-driven work in flight. Interactive
|
|
365
|
+
// /gsd discuss (user-driven) produces legitimate text-only turns.
|
|
366
|
+
if (!isAuto && pendingAutoStartMap.size === 0)
|
|
367
|
+
return false;
|
|
368
|
+
const lastMsg = event.messages[event.messages.length - 1];
|
|
369
|
+
if (!lastMsg)
|
|
370
|
+
return false;
|
|
371
|
+
if (hasToolUse(lastMsg))
|
|
372
|
+
return false;
|
|
373
|
+
const text = extractAssistantText(lastMsg).trim();
|
|
374
|
+
if (!text)
|
|
375
|
+
return false;
|
|
376
|
+
// Skip if the LLM is emitting the ready phrase — that is the ready-no-files
|
|
377
|
+
// path, handled by maybeHandleReadyPhraseWithoutFiles.
|
|
378
|
+
if (READY_PHRASE_RE.test(text))
|
|
379
|
+
return false;
|
|
380
|
+
// Skip if the LLM is clearly handing back to the user. Keep the heuristic
|
|
381
|
+
// tight: a trailing question mark on the last non-empty line is the common
|
|
382
|
+
// signal for "I asked the user a question and stopped."
|
|
383
|
+
const lines = text.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
384
|
+
const lastLine = lines[lines.length - 1] ?? "";
|
|
385
|
+
if (lastLine.endsWith("?"))
|
|
386
|
+
return false;
|
|
387
|
+
// Must contain a commit-intent phrase — this is the stall we care about.
|
|
388
|
+
if (!COMMIT_INTENT_RE.test(text))
|
|
389
|
+
return false;
|
|
390
|
+
// Resolve the target basePath + pi for injection. Prefer the pending
|
|
391
|
+
// autostart entry (discuss flow); otherwise we cannot inject.
|
|
392
|
+
const entry = _getPendingAutoStart();
|
|
393
|
+
if (!entry)
|
|
394
|
+
return false;
|
|
395
|
+
const { ctx, pi, basePath } = entry;
|
|
396
|
+
const count = (emptyTurnCounterByBase.get(basePath) ?? 0) + 1;
|
|
397
|
+
emptyTurnCounterByBase.set(basePath, count);
|
|
398
|
+
if (count > MAX_EMPTY_TURN_RETRIES) {
|
|
399
|
+
ctx.ui.notify(`Empty-turn recovery: LLM announced intent ${count} times without calling any tool. ` +
|
|
400
|
+
`Stopping auto-nudge.`, "error");
|
|
401
|
+
return false; // let the normal flow resolve/pause the unit
|
|
402
|
+
}
|
|
403
|
+
ctx.ui.notify(`Empty-turn detected: LLM announced intent but called no tool. Prompting it to execute.`, "info");
|
|
404
|
+
const nudge = `Your last turn announced an action (e.g. "I'll write…" or "Let me call…") ` +
|
|
405
|
+
`but contained no tool call. The system records zero tool-use blocks for ` +
|
|
406
|
+
`that turn. Execute the announced action NOW as a tool call in this turn. ` +
|
|
407
|
+
`Do not describe it again. Retry ${count}/${MAX_EMPTY_TURN_RETRIES}.`;
|
|
408
|
+
try {
|
|
409
|
+
pi.sendMessage({ customType: "gsd-empty-turn-recovery", content: nudge, display: false }, { triggerTurn: true });
|
|
410
|
+
}
|
|
411
|
+
catch (e) {
|
|
412
|
+
logWarning("guided", `empty-turn nudge sendMessage failed: ${e.message}`);
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
228
417
|
/**
|
|
229
418
|
* Extract milestone IDs from PROJECT.md milestone sequence table.
|
|
230
419
|
* Looks for rows like "| M001 | Name | Status |" and extracts the ID column.
|
|
@@ -91,6 +91,7 @@ export function initHealthWidget(ctx) {
|
|
|
91
91
|
let data = initialData;
|
|
92
92
|
let cachedLines;
|
|
93
93
|
let refreshInFlight = false;
|
|
94
|
+
let isDisposed = false;
|
|
94
95
|
const refresh = async () => {
|
|
95
96
|
if (refreshInFlight)
|
|
96
97
|
return;
|
|
@@ -98,7 +99,8 @@ export function initHealthWidget(ctx) {
|
|
|
98
99
|
try {
|
|
99
100
|
data = loadHealthWidgetData(basePath);
|
|
100
101
|
cachedLines = undefined;
|
|
101
|
-
|
|
102
|
+
if (!isDisposed)
|
|
103
|
+
_tui.requestRender();
|
|
102
104
|
}
|
|
103
105
|
catch { /* non-fatal */ }
|
|
104
106
|
finally {
|
|
@@ -122,6 +124,7 @@ export function initHealthWidget(ctx) {
|
|
|
122
124
|
},
|
|
123
125
|
invalidate() { cachedLines = undefined; cachedWidth = undefined; },
|
|
124
126
|
dispose() {
|
|
127
|
+
isDisposed = true;
|
|
125
128
|
clearInterval(refreshTimer);
|
|
126
129
|
},
|
|
127
130
|
};
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { showNextAction } from "../shared/tui.js";
|
|
11
|
-
import { nativeInit } from "./native-git-bridge.js";
|
|
11
|
+
import { nativeInit, nativeAddAll, nativeCommit } from "./native-git-bridge.js";
|
|
12
12
|
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
13
13
|
import { gsdRoot } from "./paths.js";
|
|
14
14
|
import { assertSafeDirectory } from "./validate-directory.js";
|
|
@@ -40,6 +40,7 @@ export async function showProjectInit(ctx, pi, basePath, detection) {
|
|
|
40
40
|
ctx.ui.notify(`Project detected:\n${detectionSummary.join("\n")}`, "info");
|
|
41
41
|
}
|
|
42
42
|
// ── Step 2: Git setup ──────────────────────────────────────────────────────
|
|
43
|
+
let didInitGit = false;
|
|
43
44
|
if (!signals.isGitRepo) {
|
|
44
45
|
const gitChoice = await showNextAction(ctx, {
|
|
45
46
|
title: "GSD — Project Setup",
|
|
@@ -54,6 +55,7 @@ export async function showProjectInit(ctx, pi, basePath, detection) {
|
|
|
54
55
|
return { completed: false, bootstrapped: false };
|
|
55
56
|
if (gitChoice === "init_git") {
|
|
56
57
|
nativeInit(basePath, prefs.mainBranch);
|
|
58
|
+
didInitGit = true;
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
else {
|
|
@@ -244,6 +246,18 @@ export async function showProjectInit(ctx, pi, basePath, detection) {
|
|
|
244
246
|
// Ensure .gitignore
|
|
245
247
|
ensureGitignore(basePath);
|
|
246
248
|
untrackRuntimeFiles(basePath);
|
|
249
|
+
// Create initial commit so git log and git worktree work immediately (#4530).
|
|
250
|
+
// Without this, the branch is "unborn" (zero commits) and downstream operations
|
|
251
|
+
// like `git log` and `git worktree add` fail.
|
|
252
|
+
if (didInitGit) {
|
|
253
|
+
try {
|
|
254
|
+
nativeAddAll(basePath);
|
|
255
|
+
nativeCommit(basePath, "chore: init project");
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// Non-fatal — user can commit manually; don't block project init
|
|
259
|
+
}
|
|
260
|
+
}
|
|
247
261
|
// Auto-generate codebase map for instant agent orientation
|
|
248
262
|
try {
|
|
249
263
|
const result = generateCodebaseMap(basePath);
|
|
@@ -12,6 +12,12 @@ import { getErrorMessage } from "./error-utils.js";
|
|
|
12
12
|
export const PROVIDER_REGISTRY = [
|
|
13
13
|
// LLM Providers
|
|
14
14
|
{ id: "anthropic", label: "Anthropic (Claude)", category: "llm", envVar: "ANTHROPIC_API_KEY", prefixes: ["sk-ant-"], hasOAuth: true, dashboardUrl: "console.anthropic.com" },
|
|
15
|
+
// Claude Code CLI: routes through the local `claude` binary — no API key,
|
|
16
|
+
// authentication is handled by the CLI's own OAuth flow.
|
|
17
|
+
// Referenced by doctor-providers.ts, auto-model-selection.ts, and others;
|
|
18
|
+
// must be in the canonical registry so all consumers see the same catalog.
|
|
19
|
+
// See: https://github.com/gsd-build/gsd-2/issues/4541
|
|
20
|
+
{ id: "claude-code", label: "Claude Code CLI", category: "llm", hasOAuth: true },
|
|
15
21
|
{ id: "openai", label: "OpenAI", category: "llm", envVar: "OPENAI_API_KEY", prefixes: ["sk-"], dashboardUrl: "platform.openai.com/api-keys" },
|
|
16
22
|
{ id: "github-copilot", label: "GitHub Copilot", category: "llm", envVar: "GITHUB_TOKEN", hasOAuth: true },
|
|
17
23
|
{ id: "openai-codex", label: "ChatGPT Plus/Pro (Codex)", category: "llm", hasOAuth: true },
|
|
@@ -440,6 +440,20 @@ function getModelCost(modelId) {
|
|
|
440
440
|
function bareModelId(modelId) {
|
|
441
441
|
return modelId.includes("/") ? modelId.split("/").pop() : modelId;
|
|
442
442
|
}
|
|
443
|
+
// ─── Provider-specific Tool Limits ─────────────────────────────────────────
|
|
444
|
+
/**
|
|
445
|
+
* Groq enforces a hard limit of 128 tools per request.
|
|
446
|
+
* Requests exceeding this limit receive a 400 error:
|
|
447
|
+
* "maximum number of items is 128"
|
|
448
|
+
* @see https://console.groq.com/docs/tool-use
|
|
449
|
+
*/
|
|
450
|
+
export const GROQ_MAX_TOOLS = 128;
|
|
451
|
+
/**
|
|
452
|
+
* Provider IDs that map to the Groq API backend.
|
|
453
|
+
* Used to detect Groq at the GSD routing layer where only the provider string
|
|
454
|
+
* is available (the pi-ai openai-completions adapter is shared across providers).
|
|
455
|
+
*/
|
|
456
|
+
const GROQ_PROVIDER_IDS = new Set(["groq"]);
|
|
443
457
|
// ─── Tool Compatibility Filter (ADR-005 Phase 3) ───────────────────────────
|
|
444
458
|
/**
|
|
445
459
|
* Check if a tool is compatible with a provider's capabilities.
|
|
@@ -461,8 +475,14 @@ export function isToolCompatibleWithProvider(toolName, providerCaps) {
|
|
|
461
475
|
/**
|
|
462
476
|
* Filter a list of tool names to only those compatible with a provider.
|
|
463
477
|
* Used by the routing pipeline to adjust tool sets when switching providers.
|
|
478
|
+
*
|
|
479
|
+
* @param toolNames - The full list of active tool names to filter.
|
|
480
|
+
* @param providerApi - The pi-ai API string (e.g. "openai-completions").
|
|
481
|
+
* @param provider - Optional provider ID (e.g. "groq"). Used to apply
|
|
482
|
+
* provider-specific limits that can't be expressed as API-level capabilities
|
|
483
|
+
* (e.g. Groq's 128-tool hard limit on the shared openai-completions adapter).
|
|
464
484
|
*/
|
|
465
|
-
export function filterToolsForProvider(toolNames, providerApi) {
|
|
485
|
+
export function filterToolsForProvider(toolNames, providerApi, provider) {
|
|
466
486
|
const providerCaps = getProviderCapabilities(providerApi);
|
|
467
487
|
// Provider doesn't support tool calling at all
|
|
468
488
|
if (!providerCaps.toolCalling) {
|
|
@@ -478,6 +498,14 @@ export function filterToolsForProvider(toolNames, providerApi) {
|
|
|
478
498
|
filtered.push(name);
|
|
479
499
|
}
|
|
480
500
|
}
|
|
501
|
+
// Groq enforces a hard limit of 128 tools per request (#4376).
|
|
502
|
+
// Trim the compatible list to GROQ_MAX_TOOLS and move the excess to filtered.
|
|
503
|
+
if (provider && GROQ_PROVIDER_IDS.has(provider) && compatible.length > GROQ_MAX_TOOLS) {
|
|
504
|
+
const trimmed = compatible.splice(GROQ_MAX_TOOLS);
|
|
505
|
+
filtered.push(...trimmed);
|
|
506
|
+
console.warn(`[gsd] Groq tool limit: ${compatible.length + trimmed.length} tools active but Groq allows at most ${GROQ_MAX_TOOLS}. ` +
|
|
507
|
+
`Trimming to the first ${GROQ_MAX_TOOLS} tools. Removed: ${trimmed.join(", ")}`);
|
|
508
|
+
}
|
|
481
509
|
return { compatible, filtered };
|
|
482
510
|
}
|
|
483
511
|
/**
|
|
@@ -486,8 +514,13 @@ export function filterToolsForProvider(toolNames, providerApi) {
|
|
|
486
514
|
*
|
|
487
515
|
* This is a hard filter only — it removes tools that would fail at the
|
|
488
516
|
* provider level. It does NOT remove tools based on soft heuristics.
|
|
517
|
+
*
|
|
518
|
+
* @param activeToolNames - The full list of currently active tool names.
|
|
519
|
+
* @param selectedModelApi - The pi-ai API string for the selected model.
|
|
520
|
+
* @param provider - Optional provider ID (e.g. "groq") for provider-specific
|
|
521
|
+
* limits beyond what the API-level capability profile expresses.
|
|
489
522
|
*/
|
|
490
|
-
export function adjustToolSet(activeToolNames, selectedModelApi) {
|
|
491
|
-
const { compatible, filtered } = filterToolsForProvider(activeToolNames, selectedModelApi);
|
|
523
|
+
export function adjustToolSet(activeToolNames, selectedModelApi, provider) {
|
|
524
|
+
const { compatible, filtered } = filterToolsForProvider(activeToolNames, selectedModelApi, provider);
|
|
492
525
|
return { toolNames: compatible, removedTools: filtered };
|
|
493
526
|
}
|
|
@@ -68,8 +68,13 @@ export function extractPackageReferences(description) {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
-
// require('pkg') or import from 'pkg' in code blocks
|
|
72
|
-
|
|
71
|
+
// require('pkg') or `import ... from 'pkg'` in code blocks.
|
|
72
|
+
// The `from\s+['"]` branch MUST be preceded by an `import` keyword so that
|
|
73
|
+
// natural-language prose like `from "What's Next"` or `from 'master'` does
|
|
74
|
+
// not produce false package-existence failures. Requiring the leading import
|
|
75
|
+
// keyword anchors the match to JavaScript/TypeScript syntax.
|
|
76
|
+
// See: https://github.com/gsd-build/gsd-2/issues/4388
|
|
77
|
+
const importPattern = /(?:require\s*\(\s*['"]|import\b[\s\S]*?\bfrom\s+['"])([a-zA-Z0-9@/_-]+)['"\)]/g;
|
|
73
78
|
let importMatch;
|
|
74
79
|
while ((importMatch = importPattern.exec(description)) !== null) {
|
|
75
80
|
// Skip relative imports and node builtins
|
|
@@ -278,7 +283,12 @@ function extractPathFromAnnotation(raw) {
|
|
|
278
283
|
}
|
|
279
284
|
const annotatedMatch = trimmed.match(/^(.+?)\s+[—–-]\s+.+$/);
|
|
280
285
|
if (annotatedMatch) {
|
|
281
|
-
|
|
286
|
+
const prefix = annotatedMatch[1].trim();
|
|
287
|
+
const prefixBacktickMatch = prefix.match(/`([^`]+)`/);
|
|
288
|
+
if (prefixBacktickMatch && looksLikePathOrUrl(prefixBacktickMatch[1].trim())) {
|
|
289
|
+
return prefixBacktickMatch[1].trim();
|
|
290
|
+
}
|
|
291
|
+
return prefix.replace(/`/g, "").trim();
|
|
282
292
|
}
|
|
283
293
|
// Fallback: scan all backticked tokens and return the first one that looks
|
|
284
294
|
// like a path or URL. Handles prose-annotated bullets such as:
|
|
@@ -333,13 +343,19 @@ function containsGlobPattern(candidate) {
|
|
|
333
343
|
}
|
|
334
344
|
/**
|
|
335
345
|
* Build a set of files that will be created by tasks up to (but not including) taskIndex.
|
|
346
|
+
* Also includes outputs of completed tasks at any position — a completed task has already
|
|
347
|
+
* run and its outputs are available regardless of sequence position or disk state (#4071).
|
|
336
348
|
* All paths are normalized for consistent comparison.
|
|
337
349
|
*/
|
|
338
350
|
function getExpectedOutputsUpTo(tasks, taskIndex) {
|
|
339
351
|
const outputs = new Set();
|
|
340
|
-
for (let i = 0; i <
|
|
341
|
-
|
|
342
|
-
|
|
352
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
353
|
+
const task = tasks[i];
|
|
354
|
+
// Include prior tasks (i < taskIndex) OR completed tasks at any position
|
|
355
|
+
if (i < taskIndex || task.status === "completed") {
|
|
356
|
+
for (const file of task.expected_output) {
|
|
357
|
+
outputs.add(normalizeFilePath(file));
|
|
358
|
+
}
|
|
343
359
|
}
|
|
344
360
|
}
|
|
345
361
|
return outputs;
|
|
@@ -416,8 +432,14 @@ export function checkTaskOrdering(tasks, basePath) {
|
|
|
416
432
|
const task = tasks[i];
|
|
417
433
|
for (const file of task.expected_output) {
|
|
418
434
|
const normalizedFile = normalizeFilePath(file);
|
|
419
|
-
|
|
420
|
-
|
|
435
|
+
const existing = fileCreators.get(normalizedFile);
|
|
436
|
+
if (!existing || (!existing.completed && task.status === "completed")) {
|
|
437
|
+
fileCreators.set(normalizedFile, {
|
|
438
|
+
taskId: task.id,
|
|
439
|
+
index: i,
|
|
440
|
+
originalPath: file,
|
|
441
|
+
completed: task.status === "completed",
|
|
442
|
+
});
|
|
421
443
|
}
|
|
422
444
|
}
|
|
423
445
|
}
|
|
@@ -441,7 +463,11 @@ export function checkTaskOrdering(tasks, basePath) {
|
|
|
441
463
|
const creator = fileCreators.get(normalizedFile);
|
|
442
464
|
const absolutePath = resolve(basePath, normalizedFile);
|
|
443
465
|
const existsOnDisk = existsSync(absolutePath);
|
|
444
|
-
if
|
|
466
|
+
// Skip if the creating task has already completed — its output is available
|
|
467
|
+
// regardless of disk state (e.g. file was a temp artifact cleaned up after
|
|
468
|
+
// the task ran, or a replan introduced a new earlier-sequence task that
|
|
469
|
+
// reads this pre-execution output). (#4071)
|
|
470
|
+
if (creator && creator.index > i && !existsOnDisk && !creator.completed) {
|
|
445
471
|
// Task reads file that is created later — impossible ordering
|
|
446
472
|
results.push({
|
|
447
473
|
category: "file",
|
|
@@ -5,6 +5,14 @@
|
|
|
5
5
|
* both the validation and runtime modules can import them without pulling
|
|
6
6
|
* in filesystem or loading logic.
|
|
7
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
* Resolve whether context-mode features (gsd_exec sandbox + compaction
|
|
10
|
+
* snapshot) should be active. Default is ON: missing config or missing
|
|
11
|
+
* `enabled` is treated as true. Only `enabled: false` disables.
|
|
12
|
+
*/
|
|
13
|
+
export function isContextModeEnabled(prefs) {
|
|
14
|
+
return prefs?.context_mode?.enabled !== false;
|
|
15
|
+
}
|
|
8
16
|
/** Default preference values for each workflow mode. */
|
|
9
17
|
export const MODE_DEFAULTS = {
|
|
10
18
|
solo: {
|
|
@@ -87,6 +95,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
|
|
|
87
95
|
"flat_rate_providers",
|
|
88
96
|
"language",
|
|
89
97
|
"context_window_override",
|
|
98
|
+
"context_mode",
|
|
90
99
|
]);
|
|
91
100
|
/** Canonical list of all dispatch unit types. */
|
|
92
101
|
export const KNOWN_UNIT_TYPES = [
|