@opengsd/gsd-pi 1.9.0-dev.e10b60d4 → 1.10.0-dev.a5e42e99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -9
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +24 -4
- package/dist/resources/extensions/gsd/auto/run-unit.js +19 -1
- package/dist/resources/extensions/gsd/auto-model-selection.js +1 -0
- package/dist/resources/extensions/gsd/auto-start.js +30 -10
- package/dist/resources/extensions/gsd/auto-verification.js +25 -12
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +7 -3
- package/dist/resources/extensions/gsd/auto.js +12 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +4 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +12 -3
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +37 -6
- package/dist/resources/extensions/gsd/flat-phase-migration.js +12 -0
- package/dist/resources/extensions/gsd/gsd-db.js +75 -31
- package/dist/resources/extensions/gsd/model-cost-table.js +6 -3
- package/dist/resources/extensions/gsd/model-router.js +10 -5
- package/dist/resources/extensions/gsd/planning-subagent-registry.js +12 -4
- package/dist/resources/extensions/gsd/preferences-models.js +50 -10
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +41 -6
- package/dist/resources/extensions/gsd/preferences.js +7 -0
- package/dist/resources/extensions/gsd/templates/PREFERENCES.md +3 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -1
- package/dist/resources/extensions/gsd/verification-evidence.js +5 -3
- package/dist/resources/extensions/gsd/worktree-state-projection.js +20 -18
- package/dist/resources/extensions/ollama/ollama-chat-provider.js +2 -2
- package/dist/resources/extensions/subagent/index.js +1 -1
- package/dist/startup-model-validation.d.ts +1 -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 +6 -6
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +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 +6 -6
- package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
- package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +1 -1
- package/package.json +7 -6
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/rpc.d.ts +1 -1
- package/packages/contracts/dist/rpc.d.ts.map +1 -1
- package/packages/contracts/dist/rpc.js +1 -1
- package/packages/contracts/dist/rpc.js.map +1 -1
- package/packages/contracts/dist/rpc.test.js +1 -1
- package/packages/contracts/dist/rpc.test.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/cli/args.js +2 -2
- package/packages/gsd-agent-modes/dist/cli/args.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/README.md +21 -5
- package/packages/gsd-cloud/dist/background-cli.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/background-cli.test.js +325 -0
- package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -0
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cli.js +146 -16
- package/packages/gsd-cloud/dist/cli.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.js +7 -1
- package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +7 -4
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.js +115 -31
- package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.test.js +224 -59
- package/packages/gsd-cloud/dist/cloud-runtime.test.js.map +1 -1
- package/packages/gsd-cloud/dist/runtime-artifacts.d.ts +3 -0
- package/packages/gsd-cloud/dist/runtime-artifacts.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-artifacts.js +36 -0
- package/packages/gsd-cloud/dist/runtime-artifacts.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.d.ts +32 -0
- package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.js +640 -0
- package/packages/gsd-cloud/dist/runtime-process.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js +543 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.d.ts +117 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.js +291 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.test.js +434 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.test.js.map +1 -0
- package/packages/gsd-cloud/package.json +2 -2
- package/packages/mcp-server/dist/cli-runner.d.ts +3 -0
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
- package/packages/mcp-server/dist/cli-runner.js +119 -33
- package/packages/mcp-server/dist/cli-runner.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +2 -2
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +130 -2
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +101 -9
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.js +18 -6
- package/packages/pi-ai/dist/models.js.map +1 -1
- package/packages/pi-ai/dist/providers/amazon-bedrock.js +3 -2
- package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/packages/pi-ai/dist/providers/google.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +9 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -3
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +54 -0
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.d.ts +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.js +1 -0
- package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/theme/theme.d.ts +1 -1
- package/pkg/dist/theme/theme.d.ts.map +1 -1
- package/pkg/dist/theme/theme.js +1 -0
- package/pkg/dist/theme/theme.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop.ts +48 -4
- package/src/resources/extensions/gsd/auto/run-unit.ts +20 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +1 -0
- package/src/resources/extensions/gsd/auto-start.ts +32 -9
- package/src/resources/extensions/gsd/auto-verification.ts +46 -12
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +10 -3
- package/src/resources/extensions/gsd/auto.ts +13 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +4 -3
- package/src/resources/extensions/gsd/docs/preferences-reference.md +12 -3
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +42 -6
- package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -0
- package/src/resources/extensions/gsd/gsd-db.ts +85 -33
- package/src/resources/extensions/gsd/model-cost-table.ts +14 -3
- package/src/resources/extensions/gsd/model-router.ts +10 -5
- package/src/resources/extensions/gsd/planning-subagent-registry.ts +18 -4
- package/src/resources/extensions/gsd/preferences-models.ts +54 -10
- package/src/resources/extensions/gsd/preferences-types.ts +10 -2
- package/src/resources/extensions/gsd/preferences-validation.ts +49 -6
- package/src/resources/extensions/gsd/preferences.ts +13 -0
- package/src/resources/extensions/gsd/templates/PREFERENCES.md +3 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +74 -1
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +147 -0
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +8 -9
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +22 -7
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +38 -1
- package/src/resources/extensions/gsd/tests/preferences.test.ts +71 -4
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/workflow-authority-faults.test.ts +238 -0
- package/src/resources/extensions/gsd/tests/workflow-authority-fixture.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/workflow-authority-fixture.ts +141 -0
- package/src/resources/extensions/gsd/tests/workflow-authority-projection-conflict.test.ts +177 -0
- package/src/resources/extensions/gsd/tests/workflow-fault-harness.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/workflow-fault-harness.ts +72 -0
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +40 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +5 -1
- package/src/resources/extensions/gsd/verification-evidence.ts +5 -2
- package/src/resources/extensions/gsd/worktree-state-projection.ts +21 -24
- package/src/resources/extensions/ollama/ollama-chat-provider.ts +2 -2
- package/src/resources/extensions/ollama/tests/ollama-chat-provider-think.test.ts +4 -0
- package/src/resources/extensions/subagent/index.ts +1 -1
- /package/dist/web/standalone/.next/static/{V9MAVyYVPboQUB9KTTAMi → xwUaOqui04jIIFVsIBLiL}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{V9MAVyYVPboQUB9KTTAMi → xwUaOqui04jIIFVsIBLiL}/_ssgManifest.js +0 -0
|
@@ -167,6 +167,77 @@ test("checkEngineHealth keeps PLAN checkbox divergence after stale projection fl
|
|
|
167
167
|
assert.match(readFileSync(plan.planPath, "utf-8"), /- \[ \] \*\*T01\*\*:/);
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
+
test("checkEngineHealth ignores stale suffixed flat-phase duplicate when bare milestone exists", async (t) => {
|
|
171
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-checkbox-flat-duplicate-"));
|
|
172
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
173
|
+
|
|
174
|
+
const gsdDir = join(base, ".gsd");
|
|
175
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
176
|
+
|
|
177
|
+
openDatabase(join(gsdDir, "gsd.db"));
|
|
178
|
+
insertMilestone({ id: "M003", title: "M003-vaz73w: New milestone M003-vaz73w", status: "active" });
|
|
179
|
+
insertSlice({ id: "S01", milestoneId: "M003", title: "Slice", status: "pending", risk: "low", depends: [], sequence: 1 });
|
|
180
|
+
for (const taskId of ["T01", "T02", "T03"]) {
|
|
181
|
+
insertTask({ id: taskId, milestoneId: "M003", sliceId: "S01", title: taskId, status: "complete", sequence: Number(taskId.slice(1)) });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
insertMilestone({ id: "M003-vaz73w", title: "New milestone M003-vaz73w", status: "active" });
|
|
185
|
+
insertSlice({ id: "S01", milestoneId: "M003-vaz73w", title: "Slice", status: "pending", risk: "low", depends: [], sequence: 1 });
|
|
186
|
+
for (const taskId of ["T01", "T02", "T03"]) {
|
|
187
|
+
insertTask({ id: taskId, milestoneId: "M003-vaz73w", sliceId: "S01", title: taskId, status: "complete", sequence: Number(taskId.slice(1)) });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const canonicalPlan = await renderPlanFromDb(base, "M003", "S01");
|
|
191
|
+
const staleDir = join(gsdDir, "phases", "03-vaz73w-new-milestone-m003-vaz73w");
|
|
192
|
+
mkdirSync(staleDir, { recursive: true });
|
|
193
|
+
writeFileSync(
|
|
194
|
+
join(staleDir, "03-01-PLAN.md"),
|
|
195
|
+
canonicalPlan.content.replace("- [x] **T03**:", "- [ ] **T03**:"),
|
|
196
|
+
"utf-8",
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const issues: any[] = [];
|
|
200
|
+
await checkEngineHealth(base, issues, []);
|
|
201
|
+
|
|
202
|
+
assert.deepEqual(
|
|
203
|
+
issues.filter((issue) => issue.code === "checkbox_db_status_divergence").map((issue) => issue.unitId),
|
|
204
|
+
[],
|
|
205
|
+
"stale suffixed duplicate phase projection must not compete with the bare milestone projection",
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("checkEngineHealth reads task checkboxes from the canonical milestone worktree", async (t) => {
|
|
210
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-checkbox-worktree-"));
|
|
211
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
212
|
+
|
|
213
|
+
const gsdDir = join(base, ".gsd");
|
|
214
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
215
|
+
|
|
216
|
+
openDatabase(join(gsdDir, "gsd.db"));
|
|
217
|
+
insertMilestone({ id: "M003", title: "Milestone", status: "active" });
|
|
218
|
+
insertSlice({ id: "S01", milestoneId: "M003", title: "Slice", status: "pending", risk: "low", depends: [], sequence: 1 });
|
|
219
|
+
insertTask({ id: "T03", milestoneId: "M003", sliceId: "S01", title: "Task", status: "complete", sequence: 1 });
|
|
220
|
+
|
|
221
|
+
const plan = await renderPlanFromDb(base, "M003", "S01");
|
|
222
|
+
const checkedPlan = readFileSync(plan.planPath, "utf-8");
|
|
223
|
+
writeFileSync(plan.planPath, checkedPlan.replace("- [x] **T03**:", "- [ ] **T03**:"), "utf-8");
|
|
224
|
+
|
|
225
|
+
const worktree = join(base, ".gsd-worktrees", "M003");
|
|
226
|
+
const worktreePlan = join(worktree, ".gsd", "phases", "03-milestone", "03-01-PLAN.md");
|
|
227
|
+
mkdirSync(join(worktree, ".gsd", "phases", "03-milestone"), { recursive: true });
|
|
228
|
+
writeFileSync(join(worktree, ".git"), `gitdir: ${join(base, ".git", "worktrees", "M003")}\n`);
|
|
229
|
+
writeFileSync(worktreePlan, checkedPlan, "utf-8");
|
|
230
|
+
|
|
231
|
+
const issues: any[] = [];
|
|
232
|
+
await checkEngineHealth(base, issues, []);
|
|
233
|
+
|
|
234
|
+
assert.deepEqual(
|
|
235
|
+
issues.filter((issue) => issue.code === "checkbox_db_status_divergence").map((issue) => issue.unitId),
|
|
236
|
+
[],
|
|
237
|
+
"the stale project-root PLAN must not compete with the live milestone worktree projection",
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
|
|
170
241
|
test("checkEngineHealth reads task checkboxes from the <tasks> block, not a stray line above it", async (t) => {
|
|
171
242
|
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-task-section-"));
|
|
172
243
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
@@ -336,6 +407,38 @@ test("checkEngineHealth reports artifact rows whose files are missing on disk",
|
|
|
336
407
|
assert.equal(missing.fixable, false);
|
|
337
408
|
});
|
|
338
409
|
|
|
410
|
+
test("checkEngineHealth resolves artifact rows through the canonical milestone worktree", async (t) => {
|
|
411
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-artifact-worktree-"));
|
|
412
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
413
|
+
|
|
414
|
+
const gsdDir = join(base, ".gsd");
|
|
415
|
+
const artifactPath = "phases/03-milestone/03-01-PLAN.md";
|
|
416
|
+
const worktree = join(base, ".gsd-worktrees", "M003");
|
|
417
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
418
|
+
mkdirSync(join(worktree, ".gsd", "phases", "03-milestone"), { recursive: true });
|
|
419
|
+
writeFileSync(join(worktree, ".git"), `gitdir: ${join(base, ".git", "worktrees", "M003")}\n`);
|
|
420
|
+
writeFileSync(join(worktree, ".gsd", artifactPath), "# Plan\n", "utf-8");
|
|
421
|
+
|
|
422
|
+
openDatabase(join(gsdDir, "gsd.db"));
|
|
423
|
+
insertArtifact({
|
|
424
|
+
path: artifactPath,
|
|
425
|
+
artifact_type: "PLAN",
|
|
426
|
+
milestone_id: "M003",
|
|
427
|
+
slice_id: "S01",
|
|
428
|
+
task_id: null,
|
|
429
|
+
full_content: "# Plan\n",
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const issues: any[] = [];
|
|
433
|
+
await checkEngineHealth(base, issues, []);
|
|
434
|
+
|
|
435
|
+
assert.equal(
|
|
436
|
+
issues.some((issue) => issue.code === "artifact_file_missing" && issue.file === artifactPath),
|
|
437
|
+
false,
|
|
438
|
+
"worktree-local artifact rows must not be reported missing from the project root",
|
|
439
|
+
);
|
|
440
|
+
});
|
|
441
|
+
|
|
339
442
|
test("checkEngineHealth resolves escaped .gsd artifact rows against the project .gsd directory", async (t) => {
|
|
340
443
|
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-escaped-artifact-"));
|
|
341
444
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
@@ -428,6 +531,50 @@ test("checkEngineHealth repair prunes stale phases artifact rows with present mi
|
|
|
428
531
|
assert.deepEqual(rows.map((row) => row.path), []);
|
|
429
532
|
});
|
|
430
533
|
|
|
534
|
+
test("checkEngineHealth repair prunes stale phases artifact rows with renamed flat-phase files", async (t) => {
|
|
535
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-prune-renamed-flat-phase-artifact-"));
|
|
536
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
537
|
+
|
|
538
|
+
const gsdDir = join(base, ".gsd");
|
|
539
|
+
const stalePath = "phases/01-new-milestone-m001/01-01-PLAN.md";
|
|
540
|
+
const replacementPath = "phases/01-lokably-brand-foundation-and-welcome-pag/01-01-PLAN.md";
|
|
541
|
+
mkdirSync(join(gsdDir, "phases", "01-lokably-brand-foundation-and-welcome-pag"), { recursive: true });
|
|
542
|
+
writeFileSync(join(gsdDir, replacementPath), "# Plan\n", "utf-8");
|
|
543
|
+
|
|
544
|
+
openDatabase(join(gsdDir, "gsd.db"));
|
|
545
|
+
insertArtifact({
|
|
546
|
+
path: stalePath,
|
|
547
|
+
artifact_type: "PLAN",
|
|
548
|
+
milestone_id: "M001",
|
|
549
|
+
slice_id: "S01",
|
|
550
|
+
task_id: null,
|
|
551
|
+
full_content: "# stale plan\n",
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
const beforeIssues: any[] = [];
|
|
555
|
+
await checkEngineHealth(base, beforeIssues, []);
|
|
556
|
+
|
|
557
|
+
const beforeIssue = beforeIssues.find((issue) => issue.code === "artifact_file_missing" && issue.file === stalePath);
|
|
558
|
+
assert.ok(beforeIssue, "stale renamed phases row should be reported when repair is off");
|
|
559
|
+
assert.equal(beforeIssue.fixable, true, "renamed phases rows with a flat replacement should be fixable");
|
|
560
|
+
|
|
561
|
+
const repairIssues: any[] = [];
|
|
562
|
+
const fixes: string[] = [];
|
|
563
|
+
await checkEngineHealth(base, repairIssues, fixes, { repair: true });
|
|
564
|
+
|
|
565
|
+
assert.equal(
|
|
566
|
+
repairIssues.some((issue) => issue.code === "artifact_file_missing" && issue.file === stalePath),
|
|
567
|
+
false,
|
|
568
|
+
"repair should not report stale rows it pruned",
|
|
569
|
+
);
|
|
570
|
+
assert.ok(fixes.includes(`pruned stale flat-phase artifact row ${stalePath}`));
|
|
571
|
+
|
|
572
|
+
const rows = _getAdapter()!
|
|
573
|
+
.prepare("SELECT path FROM artifacts ORDER BY path")
|
|
574
|
+
.all() as Array<{ path: string }>;
|
|
575
|
+
assert.deepEqual(rows.map((row) => row.path), []);
|
|
576
|
+
});
|
|
577
|
+
|
|
431
578
|
test("checkEngineHealth repair keeps phases rows whose own file is still present", async (t) => {
|
|
432
579
|
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-keep-present-phase-artifact-"));
|
|
433
580
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
@@ -122,7 +122,7 @@ test("migrateToFlatPhase creates a backup", async () => {
|
|
|
122
122
|
assert.ok(backups.length >= 1, "at least one migrate-* backup dir should exist");
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
test("migrateToFlatPhase
|
|
125
|
+
test("migrateToFlatPhase removes disposable phases snapshot after successful migration", async () => {
|
|
126
126
|
const base = makeTmp();
|
|
127
127
|
const reviewPath = join(base, ".gsd", "phases", "01-foundation", "PLAN-REVIEW.md");
|
|
128
128
|
mkdirSync(join(base, ".gsd", "phases", "01-foundation"), { recursive: true });
|
|
@@ -133,11 +133,10 @@ test("migrateToFlatPhase backs up existing phases projection before clearing it"
|
|
|
133
133
|
const backupRoot = join(base, ".gsd-backups");
|
|
134
134
|
const backups = readdirSync(backupRoot).filter(d => d.startsWith("migrate-"));
|
|
135
135
|
assert.equal(backups.length, 1, "one migrate backup should exist");
|
|
136
|
-
const backedUpReview = join(backupRoot, backups[0]!, "__phases", "01-foundation", "PLAN-REVIEW.md");
|
|
137
136
|
assert.equal(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
"
|
|
137
|
+
existsSync(join(backupRoot, backups[0]!, "__phases")),
|
|
138
|
+
false,
|
|
139
|
+
"temporary phases/ recovery snapshot should be removed after success",
|
|
141
140
|
);
|
|
142
141
|
});
|
|
143
142
|
|
|
@@ -202,7 +201,7 @@ test("failed migration rolls back without leaking a .gsd-backups/migrate-* dir",
|
|
|
202
201
|
assert.equal(leaked.length, 0, "rollback must delete the migrate-* backup it created");
|
|
203
202
|
});
|
|
204
203
|
|
|
205
|
-
test("resumed migration
|
|
204
|
+
test("resumed migration removes disposable phases snapshot after success", async () => {
|
|
206
205
|
const base = makeTmp();
|
|
207
206
|
const milestonesPath = join(base, ".gsd", "milestones");
|
|
208
207
|
const migratingPath = join(base, ".gsd", "milestones.migrating");
|
|
@@ -218,9 +217,9 @@ test("resumed migration stores phases backup in retained migrate snapshot", asyn
|
|
|
218
217
|
const backups = readdirSync(backupRoot).filter((d) => d.startsWith("migrate-"));
|
|
219
218
|
assert.equal(backups.length, 1, "resumed migration should create one retained migrate backup");
|
|
220
219
|
assert.equal(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
"
|
|
220
|
+
existsSync(join(backupRoot, backups[0]!, "__phases")),
|
|
221
|
+
false,
|
|
222
|
+
"temporary phases/ recovery snapshot should be removed after resumed success",
|
|
224
223
|
);
|
|
225
224
|
assert.equal(
|
|
226
225
|
existsSync(join(migratingPath, "__phases")),
|
|
@@ -46,6 +46,9 @@ import {
|
|
|
46
46
|
tryCreateMemoriesFts,
|
|
47
47
|
_isLikelyWslDrvFsPathForTest,
|
|
48
48
|
_shouldAttemptVacuumRecoveryForTest,
|
|
49
|
+
insertArtifact,
|
|
50
|
+
getArtifact,
|
|
51
|
+
upsertMilestonePlanning,
|
|
49
52
|
} from '../gsd-db.ts';
|
|
50
53
|
import { MigrationBackupError } from '../db-migration-backup.ts';
|
|
51
54
|
import { _resetLogs, peekLogs, setStderrLoggingEnabled } from '../workflow-logger.ts';
|
|
@@ -357,6 +360,35 @@ describe('gsd-db', () => {
|
|
|
357
360
|
closeDatabase();
|
|
358
361
|
});
|
|
359
362
|
|
|
363
|
+
test("gsd-db: upsertMilestonePlanning rewrites placeholder phase artifact paths to final title slug", () => {
|
|
364
|
+
openDatabase(":memory:");
|
|
365
|
+
|
|
366
|
+
const stalePath = "phases/01-new-milestone-m001/01-ROADMAP.md";
|
|
367
|
+
const finalPath = "phases/01-lokably-brand-foundation-and-welcome-pag/01-ROADMAP.md";
|
|
368
|
+
insertMilestone({ id: "M001", title: "New milestone M001", status: "active" });
|
|
369
|
+
insertArtifact({
|
|
370
|
+
path: stalePath,
|
|
371
|
+
artifact_type: "ROADMAP",
|
|
372
|
+
milestone_id: "M001",
|
|
373
|
+
slice_id: null,
|
|
374
|
+
task_id: null,
|
|
375
|
+
full_content: "# Placeholder roadmap\n",
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
upsertMilestonePlanning("M001", {
|
|
379
|
+
title: "Lokably brand foundation and welcome page rebuild",
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
assert.equal(getArtifact(stalePath), null, "placeholder-derived artifact row should be removed");
|
|
383
|
+
assert.equal(
|
|
384
|
+
getArtifact(finalPath)?.full_content,
|
|
385
|
+
"# Placeholder roadmap\n",
|
|
386
|
+
"artifact row should move to the final title-derived phase path",
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
closeDatabase();
|
|
390
|
+
});
|
|
391
|
+
|
|
360
392
|
test('gsd-db: active_requirements view excludes superseded', () => {
|
|
361
393
|
openDatabase(':memory:');
|
|
362
394
|
|
|
@@ -1428,6 +1428,19 @@ process.exit(result.status ?? 0);
|
|
|
1428
1428
|
rmSync(repo, { recursive: true, force: true });
|
|
1429
1429
|
});
|
|
1430
1430
|
|
|
1431
|
+
test('Integration branch: write does not create legacy milestones scaffold', () => {
|
|
1432
|
+
const repo = initBranchTestRepo();
|
|
1433
|
+
mkdirSync(join(repo, ".gsd", "phases"), { recursive: true });
|
|
1434
|
+
|
|
1435
|
+
writeIntegrationBranch(repo, "M001", "f-123-new-thing");
|
|
1436
|
+
|
|
1437
|
+
assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), "f-123-new-thing");
|
|
1438
|
+
assert.ok(existsSync(join(repo, ".gsd", "M001-META.json")), "metadata is written at the flat .gsd root");
|
|
1439
|
+
assert.ok(!existsSync(join(repo, ".gsd", "milestones")), "legacy milestones/ root is not created");
|
|
1440
|
+
|
|
1441
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1442
|
+
});
|
|
1443
|
+
|
|
1431
1444
|
// ─── writeIntegrationBranch: updates when branch changes (#300) ──────
|
|
1432
1445
|
|
|
1433
1446
|
test('Integration branch: updates on branch change', () => {
|
|
@@ -74,7 +74,8 @@ test("#2885: cost table includes openai-codex provider models", () => {
|
|
|
74
74
|
const ids = BUNDLED_COST_TABLE.map(e => e.id);
|
|
75
75
|
const codexModels = [
|
|
76
76
|
"gpt-5.1", "gpt-5.1-codex-max", "gpt-5.1-codex-mini",
|
|
77
|
-
"gpt-5.2", "gpt-5.2-codex", "gpt-5.3-codex", "gpt-5.3-codex-spark", "gpt-5.4", "gpt-5.4-mini", "gpt-5.5",
|
|
77
|
+
"gpt-5.2", "gpt-5.2-codex", "gpt-5.3-codex", "gpt-5.3-codex-spark", "gpt-5.4", "gpt-5.4-mini", "gpt-5.5",
|
|
78
|
+
"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
|
|
78
79
|
];
|
|
79
80
|
for (const model of codexModels) {
|
|
80
81
|
assert.ok(ids.includes(model), `cost table should include openai-codex model "${model}"`);
|
|
@@ -110,10 +111,24 @@ test("gpt-5.5 uses official OpenAI list pricing", () => {
|
|
|
110
111
|
assert.equal(entry.updatedAt, "2026-04-23");
|
|
111
112
|
});
|
|
112
113
|
|
|
113
|
-
test("gpt-5.6
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
test("gpt-5.6 bare alias is not treated as a real cost-table model", () => {
|
|
115
|
+
assert.equal(lookupModelCost("gpt-5.6"), undefined);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("gpt-5.6 variants use published pricing", () => {
|
|
119
|
+
const sol = lookupModelCost("openai-codex/gpt-5.6-sol");
|
|
120
|
+
assert.ok(sol, "lookupModelCost should find gpt-5.6-sol");
|
|
121
|
+
assert.equal(sol.inputPer1k, 0.005);
|
|
122
|
+
assert.equal(sol.outputPer1k, 0.03);
|
|
123
|
+
assert.deepEqual(sol.tiers?.[0], { inputTokensAbove: 272000, inputPer1k: 0.01, outputPer1k: 0.045 });
|
|
124
|
+
|
|
125
|
+
const terra = lookupModelCost("gpt-5.6-terra");
|
|
126
|
+
assert.ok(terra, "lookupModelCost should find gpt-5.6-terra");
|
|
127
|
+
assert.equal(terra.inputPer1k, 0.0025);
|
|
128
|
+
assert.equal(terra.outputPer1k, 0.015);
|
|
129
|
+
|
|
130
|
+
const luna = lookupModelCost("gpt-5.6-luna");
|
|
131
|
+
assert.ok(luna, "lookupModelCost should find gpt-5.6-luna");
|
|
132
|
+
assert.equal(luna.inputPer1k, 0.001);
|
|
133
|
+
assert.equal(luna.outputPer1k, 0.006);
|
|
119
134
|
});
|
|
@@ -10,6 +10,7 @@ import assert from "node:assert/strict";
|
|
|
10
10
|
import { mkdirSync, writeFileSync, rmSync, existsSync, readFileSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
|
+
import { isCustomProvider } from "../preferences-models.ts";
|
|
13
14
|
|
|
14
15
|
// ─── Test helpers ─────────────────────────────────────────────────────────────
|
|
15
16
|
|
|
@@ -303,3 +304,57 @@ describe("custom provider session model overrides PREFERENCES.md (#4122)", () =>
|
|
|
303
304
|
});
|
|
304
305
|
});
|
|
305
306
|
|
|
307
|
+
describe("custom provider detection (#801)", () => {
|
|
308
|
+
let originalGsdHome: string | undefined;
|
|
309
|
+
let tmpGsdHome: string;
|
|
310
|
+
|
|
311
|
+
beforeEach(() => {
|
|
312
|
+
originalGsdHome = process.env.GSD_HOME;
|
|
313
|
+
tmpGsdHome = makeTmpDir("gsd-home");
|
|
314
|
+
process.env.GSD_HOME = tmpGsdHome;
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
afterEach(() => {
|
|
318
|
+
if (originalGsdHome === undefined) {
|
|
319
|
+
delete process.env.GSD_HOME;
|
|
320
|
+
} else {
|
|
321
|
+
process.env.GSD_HOME = originalGsdHome;
|
|
322
|
+
}
|
|
323
|
+
try { rmSync(tmpGsdHome, { recursive: true, force: true }); } catch {}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("detects extension-registered providers that are not declared in models.json", () => {
|
|
327
|
+
const registry = {
|
|
328
|
+
getAllWithDiscovered: () => [
|
|
329
|
+
{ provider: "pi-llama-cpp" },
|
|
330
|
+
],
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
assert.equal(isCustomProvider("pi-llama-cpp", registry), true,
|
|
334
|
+
"runtime providers outside the built-in catalog should count as custom");
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("does not treat built-in providers as custom just because they are in the registry", () => {
|
|
338
|
+
const registry = {
|
|
339
|
+
getAll: () => [
|
|
340
|
+
{ provider: "claude-code" },
|
|
341
|
+
],
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
assert.equal(isCustomProvider("claude-code", registry), false,
|
|
345
|
+
"built-in providers must still defer to PREFERENCES.md");
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("does not treat bundled external-CLI providers as custom", () => {
|
|
349
|
+
// claude-code/cursor-agent/google-gemini-cli/google-antigravity register at
|
|
350
|
+
// runtime (so they are absent from the generated @gsd/pi-ai catalog), but
|
|
351
|
+
// they are first-party built-ins that PREFERENCES.md can route to. They must
|
|
352
|
+
// never be classified as custom, otherwise auto-mode silently ignores
|
|
353
|
+
// PREFERENCES.md for the default provider.
|
|
354
|
+
for (const provider of ["cursor-agent", "google-gemini-cli", "google-antigravity"]) {
|
|
355
|
+
const registry = { getAll: () => [{ provider }] };
|
|
356
|
+
assert.equal(isCustomProvider(provider, registry), false,
|
|
357
|
+
`${provider} is a built-in CLI provider and must defer to PREFERENCES.md`);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
});
|
|
@@ -900,7 +900,7 @@ test("#2885: openai-codex standard-tier models are recognized", () => {
|
|
|
900
900
|
|
|
901
901
|
test("#2885: openai-codex heavy-tier models are recognized", () => {
|
|
902
902
|
const config = { ...defaultRoutingConfig(), enabled: true };
|
|
903
|
-
const heavyModels = ["gpt-5", "gpt-5-pro", "gpt-5.1", "gpt-5.2", "gpt-5.2-codex", "gpt-5.3-codex", "gpt-5.4", "gpt-5.5", "gpt-5.6", "o4-mini", "o4-mini-deep-research"];
|
|
903
|
+
const heavyModels = ["gpt-5", "gpt-5-pro", "gpt-5.1", "gpt-5.2", "gpt-5.2-codex", "gpt-5.3-codex", "gpt-5.4", "gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "o4-mini", "o4-mini-deep-research"];
|
|
904
904
|
for (const model of heavyModels) {
|
|
905
905
|
const result = resolveModelForComplexity(
|
|
906
906
|
makeClassification("heavy"),
|
|
@@ -104,6 +104,30 @@ test('handlePlanSlice writes slice/task planning state and renders plan artifact
|
|
|
104
104
|
}
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
+
test('handlePlanSlice re-dispatch preserves completed task checkboxes', async () => {
|
|
108
|
+
const base = makeTmpBase();
|
|
109
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
seedParentSlice();
|
|
113
|
+
|
|
114
|
+
const first = await handlePlanSlice(validParams(), base);
|
|
115
|
+
assert.ok(!('error' in first), `unexpected error: ${'error' in first ? first.error : ''}`);
|
|
116
|
+
|
|
117
|
+
updateTaskStatus('M001', 'S02', 'T01', 'complete', '2026-07-11T00:00:00.000Z');
|
|
118
|
+
|
|
119
|
+
const second = await handlePlanSlice(validParams(), base);
|
|
120
|
+
assert.ok(!('error' in second), `unexpected error: ${'error' in second ? second.error : ''}`);
|
|
121
|
+
|
|
122
|
+
const planPath = join(base, '.gsd', 'phases', '01-test', '01-02-PLAN.md');
|
|
123
|
+
const parsedPlan = parsePlan(readFileSync(planPath, 'utf-8'));
|
|
124
|
+
assert.equal(parsedPlan.tasks.find((task) => task.id === 'T01')?.done, true);
|
|
125
|
+
assert.equal(parsedPlan.tasks.find((task) => task.id === 'T02')?.done, false);
|
|
126
|
+
} finally {
|
|
127
|
+
cleanup(base);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
107
131
|
test('handlePlanSlice persists explicit slice/task target repositories', async () => {
|
|
108
132
|
const base = makeTmpBase();
|
|
109
133
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { describe, test, mock, beforeEach, afterEach } from "node:test";
|
|
12
12
|
import assert from "node:assert/strict";
|
|
13
13
|
import { tmpdir } from "node:os";
|
|
14
|
-
import { mkdirSync, readFileSync, writeFileSync, rmSync } from "node:fs";
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from "node:fs";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
|
|
17
17
|
import { runPostUnitVerification, type VerificationContext } from "../auto-verification.ts";
|
|
@@ -113,6 +113,15 @@ ${yamlLines.join("\n")}
|
|
|
113
113
|
_clearGsdRootCache();
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
function useFlatPhaseLayout(): string {
|
|
117
|
+
rmSync(join(tempDir, ".gsd", "milestones"), { recursive: true, force: true });
|
|
118
|
+
const phaseDir = join(tempDir, ".gsd", "phases", "01-m001");
|
|
119
|
+
mkdirSync(phaseDir, { recursive: true });
|
|
120
|
+
invalidateAllCaches();
|
|
121
|
+
_clearGsdRootCache();
|
|
122
|
+
return phaseDir;
|
|
123
|
+
}
|
|
124
|
+
|
|
116
125
|
/**
|
|
117
126
|
* Create a task in DB that will pass basic verification but allows us to test the flow.
|
|
118
127
|
*/
|
|
@@ -461,6 +470,34 @@ describe("Post-execution blocking failure retry bypass", () => {
|
|
|
461
470
|
);
|
|
462
471
|
});
|
|
463
472
|
|
|
473
|
+
test("flat-phase post-unit evidence writes slice-task VERIFY.json at phase level", async () => {
|
|
474
|
+
const phaseDir = useFlatPhaseLayout();
|
|
475
|
+
createPostExecFailureTask();
|
|
476
|
+
writePreferences({
|
|
477
|
+
enhanced_verification: true,
|
|
478
|
+
enhanced_verification_post: true,
|
|
479
|
+
verification_auto_fix: true,
|
|
480
|
+
verification_max_retries: 3,
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const ctx = makeMockCtx();
|
|
484
|
+
const pi = makeMockPi();
|
|
485
|
+
const pauseAutoMock = mock.fn(async () => {});
|
|
486
|
+
const s = makeMockSession(tempDir, { type: "execute-task", id: "M001/S01/T01" });
|
|
487
|
+
|
|
488
|
+
const result = await runPostUnitVerification({ s, ctx, pi }, pauseAutoMock);
|
|
489
|
+
|
|
490
|
+
assert.equal(result, "pause");
|
|
491
|
+
const evidencePath = join(phaseDir, "S01-T01-VERIFY.json");
|
|
492
|
+
const legacyEvidencePath = join(phaseDir, "tasks", "T01-VERIFY.json");
|
|
493
|
+
assert.ok(existsSync(evidencePath), "flat-phase evidence should be written at phase level");
|
|
494
|
+
assert.equal(existsSync(legacyEvidencePath), false, "flat-phase evidence should not create legacy tasks/ path");
|
|
495
|
+
|
|
496
|
+
const evidence = JSON.parse(readFileSync(evidencePath, "utf-8"));
|
|
497
|
+
assert.equal(evidence.passed, false);
|
|
498
|
+
assert.ok(Array.isArray(evidence.postExecutionChecks), "post-execution checks should be included in rewritten evidence");
|
|
499
|
+
});
|
|
500
|
+
|
|
464
501
|
test("strict post-exec warning pause includes warning details", async () => {
|
|
465
502
|
createPostExecWarningTask();
|
|
466
503
|
writePreferences({
|
|
@@ -836,6 +836,31 @@ test("planning_subagents validation accepts configured read-only planning specia
|
|
|
836
836
|
assert.deepEqual(preferences.planning_subagents?.["plan-slice"]?.allowed, ["scout", "security"]);
|
|
837
837
|
});
|
|
838
838
|
|
|
839
|
+
test("planning_subagents validation accepts custom agents registered as read-only planning specialists", () => {
|
|
840
|
+
const { preferences, errors } = validatePreferences({
|
|
841
|
+
planning_subagent_registry: {
|
|
842
|
+
"my-custom-planner": { read_only_specialist: true },
|
|
843
|
+
},
|
|
844
|
+
planning_subagents: {
|
|
845
|
+
"plan-milestone": { allowed: ["planner", "my-custom-planner"] },
|
|
846
|
+
"plan-slice": { allowed: ["scout", "my-custom-planner"] },
|
|
847
|
+
},
|
|
848
|
+
} as any);
|
|
849
|
+
|
|
850
|
+
assert.equal(errors.length, 0);
|
|
851
|
+
assert.deepEqual(preferences.planning_subagent_registry?.["my-custom-planner"], {
|
|
852
|
+
read_only_specialist: true,
|
|
853
|
+
});
|
|
854
|
+
assert.deepEqual(preferences.planning_subagents?.["plan-milestone"]?.allowed, [
|
|
855
|
+
"planner",
|
|
856
|
+
"my-custom-planner",
|
|
857
|
+
]);
|
|
858
|
+
assert.deepEqual(preferences.planning_subagents?.["plan-slice"]?.allowed, [
|
|
859
|
+
"scout",
|
|
860
|
+
"my-custom-planner",
|
|
861
|
+
]);
|
|
862
|
+
});
|
|
863
|
+
|
|
839
864
|
test("planning_subagents validation rejects unsupported units and unsafe agents", () => {
|
|
840
865
|
const { preferences, errors } = validatePreferences({
|
|
841
866
|
planning_subagents: {
|
|
@@ -849,6 +874,33 @@ test("planning_subagents validation rejects unsupported units and unsafe agents"
|
|
|
849
874
|
assert.ok(errors.some(e => e.includes("\"worker\"") && e.includes("read-only planning specialist")));
|
|
850
875
|
});
|
|
851
876
|
|
|
877
|
+
test("planning_subagents validation explains how to register custom planning agents", () => {
|
|
878
|
+
const { preferences, errors } = validatePreferences({
|
|
879
|
+
planning_subagents: {
|
|
880
|
+
"plan-slice": { allowed: ["my-custom-planner"] },
|
|
881
|
+
},
|
|
882
|
+
} as any);
|
|
883
|
+
|
|
884
|
+
assert.equal(preferences.planning_subagents, undefined);
|
|
885
|
+
assert.ok(errors.some(e =>
|
|
886
|
+
e.includes("my-custom-planner") &&
|
|
887
|
+
e.includes("planning_subagent_registry.my-custom-planner.read_only_specialist: true")
|
|
888
|
+
));
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
test("planning_subagent_registry validation requires explicit read_only_specialist true", () => {
|
|
892
|
+
const { preferences, errors } = validatePreferences({
|
|
893
|
+
planning_subagent_registry: {
|
|
894
|
+
"write-capable-agent": { read_only_specialist: false },
|
|
895
|
+
},
|
|
896
|
+
} as any);
|
|
897
|
+
|
|
898
|
+
assert.equal(preferences.planning_subagent_registry, undefined);
|
|
899
|
+
assert.ok(errors.some(e =>
|
|
900
|
+
e.includes("planning_subagent_registry.write-capable-agent.read_only_specialist must be true")
|
|
901
|
+
));
|
|
902
|
+
});
|
|
903
|
+
|
|
852
904
|
test("loadEffectiveGSDPreferences merges planning_subagents allowlists", (t) => {
|
|
853
905
|
const originalCwd = process.cwd();
|
|
854
906
|
const originalGsdHome = process.env.GSD_HOME;
|
|
@@ -874,9 +926,12 @@ test("loadEffectiveGSDPreferences merges planning_subagents allowlists", (t) =>
|
|
|
874
926
|
[
|
|
875
927
|
"---",
|
|
876
928
|
"version: 1",
|
|
929
|
+
"planning_subagent_registry:",
|
|
930
|
+
" global-custom-planner:",
|
|
931
|
+
" read_only_specialist: true",
|
|
877
932
|
"planning_subagents:",
|
|
878
933
|
" plan-slice:",
|
|
879
|
-
" allowed: [scout, reviewer]",
|
|
934
|
+
" allowed: [scout, reviewer, global-custom-planner]",
|
|
880
935
|
"---",
|
|
881
936
|
"",
|
|
882
937
|
].join("\n"),
|
|
@@ -887,11 +942,14 @@ test("loadEffectiveGSDPreferences merges planning_subagents allowlists", (t) =>
|
|
|
887
942
|
[
|
|
888
943
|
"---",
|
|
889
944
|
"version: 1",
|
|
945
|
+
"planning_subagent_registry:",
|
|
946
|
+
" project-custom-planner:",
|
|
947
|
+
" read_only_specialist: true",
|
|
890
948
|
"planning_subagents:",
|
|
891
949
|
" plan-slice:",
|
|
892
|
-
" allowed: [security]",
|
|
950
|
+
" allowed: [security, project-custom-planner]",
|
|
893
951
|
" plan-milestone:",
|
|
894
|
-
" allowed: [planner]",
|
|
952
|
+
" allowed: [planner, project-custom-planner]",
|
|
895
953
|
"---",
|
|
896
954
|
"",
|
|
897
955
|
].join("\n"),
|
|
@@ -899,12 +957,21 @@ test("loadEffectiveGSDPreferences merges planning_subagents allowlists", (t) =>
|
|
|
899
957
|
);
|
|
900
958
|
|
|
901
959
|
const loaded = loadEffectiveGSDPreferences(tempProject);
|
|
960
|
+
assert.deepEqual(loaded?.preferences.planning_subagent_registry, {
|
|
961
|
+
"global-custom-planner": { read_only_specialist: true },
|
|
962
|
+
"project-custom-planner": { read_only_specialist: true },
|
|
963
|
+
});
|
|
902
964
|
assert.deepEqual(loaded?.preferences.planning_subagents?.["plan-slice"]?.allowed, [
|
|
903
965
|
"scout",
|
|
904
966
|
"reviewer",
|
|
967
|
+
"global-custom-planner",
|
|
905
968
|
"security",
|
|
969
|
+
"project-custom-planner",
|
|
970
|
+
]);
|
|
971
|
+
assert.deepEqual(loaded?.preferences.planning_subagents?.["plan-milestone"]?.allowed, [
|
|
972
|
+
"planner",
|
|
973
|
+
"project-custom-planner",
|
|
906
974
|
]);
|
|
907
|
-
assert.deepEqual(loaded?.preferences.planning_subagents?.["plan-milestone"]?.allowed, ["planner"]);
|
|
908
975
|
});
|
|
909
976
|
|
|
910
977
|
// ── Model config parsing ─────────────────────────────────────────────────────
|
|
@@ -54,6 +54,9 @@ const PREF_SAMPLE_VALUES: Record<string, unknown> = {
|
|
|
54
54
|
},
|
|
55
55
|
post_unit_hooks: [{ command: "npm test" }],
|
|
56
56
|
pre_dispatch_hooks: [{ command: "npm run lint" }],
|
|
57
|
+
planning_subagent_registry: {
|
|
58
|
+
"custom-planner": { read_only_specialist: true },
|
|
59
|
+
},
|
|
57
60
|
planning_subagents: {
|
|
58
61
|
"plan-milestone": { allowed: ["scout", "planner"] },
|
|
59
62
|
"plan-slice": { allowed: ["scout"] },
|
|
@@ -35,8 +35,8 @@ describe("supportsServiceTier", () => {
|
|
|
35
35
|
assert.equal(supportsServiceTier("gpt-5.5"), false);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
test("returns false for gpt-5.6 until service_tier payload support is verified", () => {
|
|
39
|
-
assert.equal(supportsServiceTier("gpt-5.6"), false);
|
|
38
|
+
test("returns false for gpt-5.6 variants until service_tier payload support is verified", () => {
|
|
39
|
+
assert.equal(supportsServiceTier("gpt-5.6-sol"), false);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
test("returns false for provider-only identifier without gpt-5.4 model suffix", () => {
|
|
@@ -150,7 +150,7 @@ test("validation warns on an unknown phase key in the block", () => {
|
|
|
150
150
|
|
|
151
151
|
test("validation warns on AND strips an illegal inline models thinking level", () => {
|
|
152
152
|
const result = validatePreferences({
|
|
153
|
-
models: { planning: { model: "m", thinking: "
|
|
153
|
+
models: { planning: { model: "m", thinking: "ultra" } },
|
|
154
154
|
} as never);
|
|
155
155
|
assert.ok(result.warnings.some((w) => w.includes("models.planning.thinking") && w.includes("not a valid thinking level")));
|
|
156
156
|
// The bad thinking field must be stripped so it can't reach the resolver,
|
|
@@ -99,6 +99,18 @@ test("verification-evidence: writeVerificationJSON creates directory if it doesn
|
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
+
test("verification-evidence: writeVerificationJSON can write flat-phase slice-task filename", () => {
|
|
103
|
+
const tmp = makeTempDir("ve-flat-name");
|
|
104
|
+
try {
|
|
105
|
+
writeVerificationJSON(makeResult(), tmp, "T02", "M001/S03/T02", undefined, undefined, "S03");
|
|
106
|
+
|
|
107
|
+
assert.ok(existsSync(join(tmp, "S03-T02-VERIFY.json")), "flat-phase JSON file should exist");
|
|
108
|
+
assert.equal(existsSync(join(tmp, "T02-VERIFY.json")), false, "legacy JSON file should not be written");
|
|
109
|
+
} finally {
|
|
110
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
102
114
|
test("verification-evidence: writeVerificationJSON maps exitCode to verdict correctly", () => {
|
|
103
115
|
const tmp = makeTempDir("ve-verdict");
|
|
104
116
|
try {
|