@opengsd/gsd-pi 1.10.0 → 1.11.0
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-model-selection.js +1 -0
- package/dist/resources/extensions/gsd/auto-start.js +1 -1
- package/dist/resources/extensions/gsd/auto-verification.js +25 -12
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +4 -3
- package/dist/resources/extensions/gsd/db/engine.js +8 -2
- package/dist/resources/extensions/gsd/db-canonical-foundation-schema.js +120 -0
- package/dist/resources/extensions/gsd/db-migration-steps.js +4 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +12 -3
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +16 -4
- 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/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 +7 -7
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- 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/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/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 +12 -2
- package/packages/gsd-cloud/dist/background-cli.test.js +157 -80
- package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -1
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cli.js +36 -12
- package/packages/gsd-cloud/dist/cli.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +6 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.js +106 -24
- 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 +5 -0
- package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/runtime-process.js +429 -61
- package/packages/gsd-cloud/dist/runtime-process.js.map +1 -1
- package/packages/gsd-cloud/dist/runtime-process.test.js +493 -47
- package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -1
- 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/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 +113 -2
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +84 -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/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-model-selection.ts +1 -0
- package/src/resources/extensions/gsd/auto-start.ts +1 -1
- package/src/resources/extensions/gsd/auto-verification.ts +46 -12
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +4 -3
- package/src/resources/extensions/gsd/db/engine.ts +9 -1
- package/src/resources/extensions/gsd/db-canonical-foundation-schema.ts +129 -0
- package/src/resources/extensions/gsd/db-migration-steps.ts +5 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +12 -3
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +15 -4
- 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/db-canonical-foundation.test.ts +459 -0
- package/src/resources/extensions/gsd/tests/db-engine-migrate-guards.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +64 -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/single-writer-invariant.test.ts +1 -0
- 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/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/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/{EAhnhYMt5UVOiG9-iPDjc → 8ub6oiEp2YnlOHQU20LJr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{EAhnhYMt5UVOiG9-iPDjc → 8ub6oiEp2YnlOHQU20LJr}/_ssgManifest.js +0 -0
|
@@ -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 {
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawnSync, type SpawnSyncReturns } from "node:child_process";
|
|
3
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
|
|
8
|
+
import type { CompleteSliceParams } from "../types.js";
|
|
9
|
+
import {
|
|
10
|
+
closeDatabase,
|
|
11
|
+
getSlice,
|
|
12
|
+
getTask,
|
|
13
|
+
insertSlice,
|
|
14
|
+
insertTask,
|
|
15
|
+
updateTaskStatus,
|
|
16
|
+
} from "../gsd-db.js";
|
|
17
|
+
import { relSliceFile } from "../paths.js";
|
|
18
|
+
import { handleCompleteSlice } from "../tools/complete-slice.js";
|
|
19
|
+
import { createWorkflowAuthorityFixture } from "./workflow-authority-fixture.js";
|
|
20
|
+
import {
|
|
21
|
+
createWorkflowFaultHarness,
|
|
22
|
+
type WorkflowFaultHarness,
|
|
23
|
+
type WorkflowFaultPoint,
|
|
24
|
+
} from "./workflow-fault-harness.js";
|
|
25
|
+
|
|
26
|
+
interface FaultScenario {
|
|
27
|
+
point: WorkflowFaultPoint;
|
|
28
|
+
committed: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface AuthoritySnapshot {
|
|
32
|
+
pid: number;
|
|
33
|
+
taskStatus: string | null;
|
|
34
|
+
sliceStatus: string | null;
|
|
35
|
+
activeSlice: string | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const SCENARIOS: FaultScenario[] = [
|
|
39
|
+
{ point: "before-transaction-commit", committed: false },
|
|
40
|
+
{ point: "after-db-commit-before-render", committed: true },
|
|
41
|
+
{ point: "during-projection-write", committed: true },
|
|
42
|
+
{ point: "before-independent-reopen", committed: true },
|
|
43
|
+
{ point: "after-independent-reopen", committed: true },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const COMPLETE_SLICE_PARAMS: CompleteSliceParams = {
|
|
47
|
+
milestoneId: "M001",
|
|
48
|
+
sliceId: "S02",
|
|
49
|
+
sliceTitle: "Ready dependent slice",
|
|
50
|
+
oneLiner: "Complete the dependent slice",
|
|
51
|
+
narrative: "The database records the completed slice before projections are refreshed.",
|
|
52
|
+
verification: "The focused authority matrix passed.",
|
|
53
|
+
uatContent: "## UAT Type\n\n- UAT mode: runtime-executable\n\n## Result\n\nPassed.",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function seedCompletionBoundary(): void {
|
|
57
|
+
updateTaskStatus("M001", "S02", "T01", "complete", "2026-07-11T00:00:00.000Z");
|
|
58
|
+
insertSlice({
|
|
59
|
+
id: "S03",
|
|
60
|
+
milestoneId: "M001",
|
|
61
|
+
title: "Blocked dependent slice",
|
|
62
|
+
status: "pending",
|
|
63
|
+
depends: ["S02"],
|
|
64
|
+
sequence: 3,
|
|
65
|
+
});
|
|
66
|
+
insertTask({
|
|
67
|
+
id: "T01",
|
|
68
|
+
milestoneId: "M001",
|
|
69
|
+
sliceId: "S03",
|
|
70
|
+
title: "Blocked task",
|
|
71
|
+
status: "pending",
|
|
72
|
+
sequence: 1,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function writeProjection(root: string, relativePath: string, content: string): void {
|
|
77
|
+
const path = join(root, ".gsd", relativePath);
|
|
78
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
79
|
+
writeFileSync(path, content, "utf8");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function writeContradictoryProjection(root: string, committed: boolean): void {
|
|
83
|
+
const s02Checked = committed ? " " : "x";
|
|
84
|
+
const s03Checked = committed ? "x" : " ";
|
|
85
|
+
writeProjection(
|
|
86
|
+
root,
|
|
87
|
+
"milestones/M001/M001-ROADMAP.md",
|
|
88
|
+
[
|
|
89
|
+
"# M001: Contradictory projection",
|
|
90
|
+
"",
|
|
91
|
+
"## Slices",
|
|
92
|
+
"- [x] **S01: Completed prerequisite** `risk:low` `depends:[]`",
|
|
93
|
+
`- [${s02Checked}] **S02: Ready dependent slice** \`risk:medium\` \`depends:[S01]\``,
|
|
94
|
+
`- [${s03Checked}] **S03: Blocked dependent slice** \`risk:low\` \`depends:[S02]\``,
|
|
95
|
+
].join("\n"),
|
|
96
|
+
);
|
|
97
|
+
writeProjection(
|
|
98
|
+
root,
|
|
99
|
+
"STATE.md",
|
|
100
|
+
[
|
|
101
|
+
"# GSD State",
|
|
102
|
+
"",
|
|
103
|
+
`**Active Slice:** ${committed ? "S02" : "S03"}`,
|
|
104
|
+
"**Phase:** executing",
|
|
105
|
+
].join("\n"),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function armProductionFault(
|
|
110
|
+
point: WorkflowFaultPoint,
|
|
111
|
+
harness: WorkflowFaultHarness,
|
|
112
|
+
root: string,
|
|
113
|
+
): void {
|
|
114
|
+
if (point === "before-transaction-commit") {
|
|
115
|
+
harness.armDatabaseAbort("status", "NEW.status = 'complete' AND OLD.status <> 'complete'");
|
|
116
|
+
} else if (point === "after-db-commit-before-render") {
|
|
117
|
+
harness.armDatabaseAbort("full_summary_md", "NEW.full_summary_md IS NOT OLD.full_summary_md");
|
|
118
|
+
} else if (point === "during-projection-write") {
|
|
119
|
+
const summaryPath = join(root, relSliceFile(root, "M001", "S02", "SUMMARY"));
|
|
120
|
+
harness.obstructProjection(summaryPath);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function runAuthorityProcess(
|
|
125
|
+
root: string,
|
|
126
|
+
dbPath: string,
|
|
127
|
+
faultPoint?: WorkflowFaultPoint,
|
|
128
|
+
): SpawnSyncReturns<string> {
|
|
129
|
+
const resolver = join(process.cwd(), "src/resources/extensions/gsd/tests/resolve-ts.mjs");
|
|
130
|
+
const databaseModule = pathToFileURL(join(process.cwd(), "src/resources/extensions/gsd/gsd-db.ts")).href;
|
|
131
|
+
const stateModule = pathToFileURL(join(process.cwd(), "src/resources/extensions/gsd/state.ts")).href;
|
|
132
|
+
const faultHarnessModule = pathToFileURL(join(
|
|
133
|
+
process.cwd(),
|
|
134
|
+
"src/resources/extensions/gsd/tests/workflow-fault-harness.ts",
|
|
135
|
+
)).href;
|
|
136
|
+
const script = `
|
|
137
|
+
const [
|
|
138
|
+
{ openDatabase, closeDatabase, getSlice, getTask },
|
|
139
|
+
{ deriveStateFromDb },
|
|
140
|
+
{ createWorkflowFaultHarness },
|
|
141
|
+
] = await Promise.all([
|
|
142
|
+
import(${JSON.stringify(databaseModule)}),
|
|
143
|
+
import(${JSON.stringify(stateModule)}),
|
|
144
|
+
import(${JSON.stringify(faultHarnessModule)}),
|
|
145
|
+
]);
|
|
146
|
+
const [root, dbPath, faultPoint] = process.argv.slice(-3);
|
|
147
|
+
if (!openDatabase(dbPath)) throw new Error("fresh process could not open workflow database");
|
|
148
|
+
if (faultPoint) {
|
|
149
|
+
process.stderr.write("DATABASE_OPENED_BEFORE_FAULT=" + process.pid + "\\n");
|
|
150
|
+
createWorkflowFaultHarness(faultPoint).hit(faultPoint, "fresh-process-reopen");
|
|
151
|
+
}
|
|
152
|
+
const state = await deriveStateFromDb(root);
|
|
153
|
+
const snapshot = {
|
|
154
|
+
pid: process.pid,
|
|
155
|
+
taskStatus: getTask("M001", "S02", "T01")?.status ?? null,
|
|
156
|
+
sliceStatus: getSlice("M001", "S02")?.status ?? null,
|
|
157
|
+
activeSlice: state.activeSlice?.id ?? null,
|
|
158
|
+
};
|
|
159
|
+
closeDatabase();
|
|
160
|
+
process.stdout.write("AUTHORITY_SNAPSHOT=" + JSON.stringify(snapshot) + "\\n");
|
|
161
|
+
`;
|
|
162
|
+
return spawnSync(
|
|
163
|
+
process.execPath,
|
|
164
|
+
[
|
|
165
|
+
"--import",
|
|
166
|
+
resolver,
|
|
167
|
+
"--experimental-strip-types",
|
|
168
|
+
"--input-type=module",
|
|
169
|
+
"--eval",
|
|
170
|
+
script,
|
|
171
|
+
root,
|
|
172
|
+
dbPath,
|
|
173
|
+
faultPoint ?? "",
|
|
174
|
+
],
|
|
175
|
+
{ cwd: process.cwd(), encoding: "utf8" },
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function readAuthorityInFreshProcess(root: string, dbPath: string): AuthoritySnapshot {
|
|
180
|
+
const child = runAuthorityProcess(root, dbPath);
|
|
181
|
+
|
|
182
|
+
assert.equal(child.status, 0, child.stderr || child.stdout);
|
|
183
|
+
const line = child.stdout.split("\n").find((entry) => entry.startsWith("AUTHORITY_SNAPSHOT="));
|
|
184
|
+
assert.ok(line, `fresh process did not return an authority snapshot: ${child.stdout}`);
|
|
185
|
+
return JSON.parse(line.slice("AUTHORITY_SNAPSHOT=".length)) as AuthoritySnapshot;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (const scenario of SCENARIOS) {
|
|
189
|
+
test(`database authority remains coherent at ${scenario.point}`, async (t) => {
|
|
190
|
+
const fixture = await createWorkflowAuthorityFixture();
|
|
191
|
+
t.after(() => fixture.cleanup());
|
|
192
|
+
seedCompletionBoundary();
|
|
193
|
+
const harness = createWorkflowFaultHarness(scenario.point);
|
|
194
|
+
armProductionFault(scenario.point, harness, fixture.root);
|
|
195
|
+
|
|
196
|
+
let completionError: unknown;
|
|
197
|
+
let completionStale = false;
|
|
198
|
+
try {
|
|
199
|
+
const result = await handleCompleteSlice(COMPLETE_SLICE_PARAMS, fixture.root);
|
|
200
|
+
assert.ok(!("error" in result), "production completion must reach its mutation boundary");
|
|
201
|
+
completionStale = result.stale === true;
|
|
202
|
+
harness.hit("before-independent-reopen", "complete-dependent-slice");
|
|
203
|
+
} catch (error) {
|
|
204
|
+
completionError = error;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (scenario.point === "during-projection-write") {
|
|
208
|
+
assert.equal(completionStale, true, "the production renderer must surface a stale projection");
|
|
209
|
+
} else if (scenario.point === "after-independent-reopen") {
|
|
210
|
+
assert.equal(completionError, undefined, "completion must succeed before the reopen fault");
|
|
211
|
+
assert.equal(completionStale, false, "completion must not be stale before the reopen fault");
|
|
212
|
+
} else {
|
|
213
|
+
assert.match(String(completionError), new RegExp(scenario.point));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
writeContradictoryProjection(fixture.root, scenario.committed);
|
|
217
|
+
closeDatabase();
|
|
218
|
+
if (scenario.point === "after-independent-reopen") {
|
|
219
|
+
const faultedChild = runAuthorityProcess(fixture.root, fixture.dbPath, scenario.point);
|
|
220
|
+
assert.notEqual(faultedChild.status, 0, "fresh process must fault after opening the database");
|
|
221
|
+
assert.match(faultedChild.stderr, /DATABASE_OPENED_BEFORE_FAULT=/);
|
|
222
|
+
assert.match(faultedChild.stderr, /after-independent-reopen/);
|
|
223
|
+
}
|
|
224
|
+
const snapshot = readAuthorityInFreshProcess(fixture.root, fixture.dbPath);
|
|
225
|
+
fixture.reopen();
|
|
226
|
+
|
|
227
|
+
const expectedStatus = scenario.committed ? "complete" : "pending";
|
|
228
|
+
const { pid, ...authority } = snapshot;
|
|
229
|
+
assert.notEqual(pid, process.pid, "authority must be verified by another process");
|
|
230
|
+
assert.deepEqual(authority, {
|
|
231
|
+
taskStatus: "complete",
|
|
232
|
+
sliceStatus: expectedStatus,
|
|
233
|
+
activeSlice: scenario.committed ? "S03" : "S02",
|
|
234
|
+
});
|
|
235
|
+
assert.equal(getTask("M001", "S02", "T01")?.status, "complete");
|
|
236
|
+
assert.equal(getSlice("M001", "S02")?.status, expectedStatus);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { getMilestoneSlices, getSliceTasks } from "../gsd-db.ts";
|
|
5
|
+
import { queryDecisionsFromMemories, queryRequirements } from "../context-store.ts";
|
|
6
|
+
import { deriveStateFromDb } from "../state.ts";
|
|
7
|
+
import { createWorkflowAuthorityFixture } from "./workflow-authority-fixture.ts";
|
|
8
|
+
|
|
9
|
+
test("workflow authority fixture seeds a reopenable DB-backed workflow", async (t) => {
|
|
10
|
+
const fixture = await createWorkflowAuthorityFixture();
|
|
11
|
+
t.after(() => fixture.cleanup());
|
|
12
|
+
fixture.reopen();
|
|
13
|
+
|
|
14
|
+
const state = await deriveStateFromDb(fixture.root);
|
|
15
|
+
const slices = getMilestoneSlices(fixture.ids.milestone);
|
|
16
|
+
const completedTasks = getSliceTasks(fixture.ids.milestone, fixture.ids.completedSlice);
|
|
17
|
+
const readyTasks = getSliceTasks(fixture.ids.milestone, fixture.ids.readySlice);
|
|
18
|
+
const requirements = queryRequirements({
|
|
19
|
+
milestoneId: fixture.ids.milestone,
|
|
20
|
+
sliceId: fixture.ids.readySlice,
|
|
21
|
+
});
|
|
22
|
+
const decisions = queryDecisionsFromMemories({ milestoneId: fixture.ids.milestone });
|
|
23
|
+
|
|
24
|
+
assert.equal(state.phase, "executing");
|
|
25
|
+
assert.equal(state.activeMilestone?.id, fixture.ids.milestone);
|
|
26
|
+
assert.equal(state.activeSlice?.id, fixture.ids.readySlice);
|
|
27
|
+
assert.equal(state.activeTask?.id, fixture.ids.readyTask);
|
|
28
|
+
assert.deepEqual(
|
|
29
|
+
slices.map((slice) => ({ id: slice.id, status: slice.status, depends: slice.depends })),
|
|
30
|
+
[
|
|
31
|
+
{ id: fixture.ids.completedSlice, status: "complete", depends: [] },
|
|
32
|
+
{ id: fixture.ids.readySlice, status: "pending", depends: [fixture.ids.completedSlice] },
|
|
33
|
+
],
|
|
34
|
+
);
|
|
35
|
+
assert.deepEqual(
|
|
36
|
+
completedTasks.map((task) => ({ id: task.id, status: task.status })),
|
|
37
|
+
[{ id: fixture.ids.completedTask, status: "complete" }],
|
|
38
|
+
);
|
|
39
|
+
assert.deepEqual(
|
|
40
|
+
readyTasks.map((task) => ({ id: task.id, status: task.status })),
|
|
41
|
+
[{ id: fixture.ids.readyTask, status: "pending" }],
|
|
42
|
+
);
|
|
43
|
+
assert.ok(state.requirements);
|
|
44
|
+
assert.equal(state.requirements.active, 1);
|
|
45
|
+
assert.equal(state.requirements.total, 1);
|
|
46
|
+
assert.deepEqual(requirements.map((requirement) => requirement.id), [fixture.ids.requirement]);
|
|
47
|
+
assert.deepEqual(decisions.map((decision) => decision.id), [fixture.ids.decision]);
|
|
48
|
+
});
|