@opengsd/gsd-pi 1.5.0-dev.4fb2d460 → 1.5.0-dev.c4c8885f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +114 -36
- package/dist/resources/extensions/gsd/activity-log.js +1 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +16 -10
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +53 -15
- package/dist/resources/extensions/gsd/auto-model-selection.js +28 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +29 -117
- package/dist/resources/extensions/gsd/auto-start.js +53 -30
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +12 -2
- package/dist/resources/extensions/gsd/auto-timers.js +9 -2
- package/dist/resources/extensions/gsd/auto.js +97 -31
- package/dist/resources/extensions/gsd/compat/compat-marker.js +44 -0
- package/dist/resources/extensions/gsd/config-overlay.js +7 -2
- package/dist/resources/extensions/gsd/db/engine.js +33 -0
- package/dist/resources/extensions/gsd/db-adapter.js +30 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/dist/resources/extensions/gsd/journal.js +1 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +34 -91
- package/dist/resources/extensions/gsd/metrics.js +2 -2
- package/dist/resources/extensions/gsd/paths.js +70 -49
- package/dist/resources/extensions/gsd/post-unit-hooks.js +27 -0
- package/dist/resources/extensions/gsd/preferences-models.js +66 -15
- package/dist/resources/extensions/gsd/preferences-validation.js +66 -10
- package/dist/resources/extensions/gsd/rule-registry.js +106 -10
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +1 -10
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +3 -24
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +10 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -5
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +16 -10
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -8
- package/dist/resources/extensions/gsd/uat-dispatch.js +94 -0
- package/dist/resources/extensions/gsd/uok/audit-toggle.js +31 -2
- package/dist/resources/extensions/gsd/uok/audit.js +3 -0
- package/dist/resources/extensions/gsd/uok/gate-runner.js +22 -30
- package/dist/resources/extensions/gsd/uok/gitops.js +18 -15
- package/dist/resources/extensions/gsd/uok/kernel.js +120 -46
- package/dist/resources/extensions/gsd/uok/model-policy.js +17 -14
- package/dist/resources/extensions/gsd/workflow-event-ledger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-projections.js +9 -26
- package/dist/resources/extensions/gsd/workspace.js +5 -14
- package/dist/resources/extensions/shared/gsd-browser-cli.js +29 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/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/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
- package/dist/web/standalone/.next/server/chunks/{1128.js → 1991.js} +1 -1
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- 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/worktree-cli-create.d.ts +10 -0
- package/dist/worktree-cli-create.js +11 -0
- package/dist/worktree-cli-format.d.ts +12 -0
- package/dist/worktree-cli-format.js +38 -0
- package/dist/worktree-cli-plan.d.ts +16 -0
- package/dist/worktree-cli-plan.js +19 -0
- package/dist/worktree-cli-session.d.ts +13 -0
- package/dist/worktree-cli-session.js +18 -0
- package/dist/worktree-cli-status.d.ts +35 -0
- package/dist/worktree-cli-status.js +54 -0
- package/dist/worktree-cli.d.ts +1 -16
- package/dist/worktree-cli.js +28 -122
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/dist/orchestrator-agent.d.ts +21 -0
- package/packages/daemon/dist/orchestrator-agent.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator-agent.js +113 -0
- package/packages/daemon/dist/orchestrator-agent.js.map +1 -0
- package/packages/daemon/dist/orchestrator-tools.d.ts +12 -0
- package/packages/daemon/dist/orchestrator-tools.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator-tools.js +137 -0
- package/packages/daemon/dist/orchestrator-tools.js.map +1 -0
- package/packages/daemon/dist/orchestrator.d.ts +4 -56
- package/packages/daemon/dist/orchestrator.d.ts.map +1 -1
- package/packages/daemon/dist/orchestrator.js +13 -303
- package/packages/daemon/dist/orchestrator.js.map +1 -1
- package/packages/daemon/dist/orchestrator.test.js +11 -1
- package/packages/daemon/dist/orchestrator.test.js.map +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +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/package.json +1 -1
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +146 -33
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +25 -0
- package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +93 -0
- package/src/resources/extensions/gsd/activity-log.ts +1 -1
- package/src/resources/extensions/gsd/auto/orchestrator.ts +17 -11
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +59 -14
- package/src/resources/extensions/gsd/auto-model-selection.ts +33 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +4 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +53 -113
- package/src/resources/extensions/gsd/auto-start.ts +57 -28
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -2
- package/src/resources/extensions/gsd/auto-timers.ts +9 -2
- package/src/resources/extensions/gsd/auto.ts +103 -30
- package/src/resources/extensions/gsd/compat/compat-marker.ts +47 -0
- package/src/resources/extensions/gsd/config-overlay.ts +7 -1
- package/src/resources/extensions/gsd/db/engine.ts +36 -0
- package/src/resources/extensions/gsd/db-adapter.ts +29 -3
- package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/src/resources/extensions/gsd/journal.ts +1 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +52 -103
- package/src/resources/extensions/gsd/metrics.ts +2 -2
- package/src/resources/extensions/gsd/paths.ts +87 -48
- package/src/resources/extensions/gsd/post-unit-hooks.ts +32 -0
- package/src/resources/extensions/gsd/preferences-models.ts +74 -20
- package/src/resources/extensions/gsd/preferences-types.ts +24 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +77 -9
- package/src/resources/extensions/gsd/rule-registry.ts +111 -9
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +1 -9
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +2 -22
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +14 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +41 -2
- package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +48 -1
- package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/auto-timeout-recovery-supervisor-inflight-guard.test.ts +159 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/compat-marker.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/db-adapter.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/db-writable-probe.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +72 -32
- package/src/resources/extensions/gsd/tests/integration/paths.test.ts +88 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +269 -0
- package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +95 -1
- package/src/resources/extensions/gsd/tests/sibling-model-fallbacks.test.ts +189 -0
- package/src/resources/extensions/gsd/tests/single-field-model-thinking.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/supervisor-model-swap-inflight-guard.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/supervisor-model-swap-thinking-clamp.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/uat-dispatch.test.ts +211 -0
- package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +226 -2
- package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +213 -2
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +9 -5
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +19 -8
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +13 -9
- package/src/resources/extensions/gsd/types.ts +23 -4
- package/src/resources/extensions/gsd/uat-dispatch.ts +176 -0
- package/src/resources/extensions/gsd/uok/audit-toggle.ts +32 -2
- package/src/resources/extensions/gsd/uok/audit.ts +2 -0
- package/src/resources/extensions/gsd/uok/gate-runner.ts +26 -36
- package/src/resources/extensions/gsd/uok/gitops.ts +21 -18
- package/src/resources/extensions/gsd/uok/kernel.ts +169 -48
- package/src/resources/extensions/gsd/uok/model-policy.ts +20 -17
- package/src/resources/extensions/gsd/workflow-event-ledger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-projections.ts +9 -24
- package/src/resources/extensions/gsd/workspace.ts +5 -21
- package/src/resources/extensions/shared/gsd-browser-cli.ts +29 -0
- /package/dist/web/standalone/.next/static/{gXoWec4UNqcR0tkuHwYCr → NPMNi2HV8BuK3baQsEUhk}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{gXoWec4UNqcR0tkuHwYCr → NPMNi2HV8BuK3baQsEUhk}/_ssgManifest.js +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// File Purpose: Verifies UOK kernel path selection and legacy fallback telemetry.
|
|
3
3
|
import test from "node:test";
|
|
4
4
|
import assert from "node:assert/strict";
|
|
5
|
-
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
5
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
|
|
@@ -14,6 +14,16 @@ import type { LoopDeps } from "../auto/loop-deps.ts";
|
|
|
14
14
|
import { gsdRoot } from "../paths.ts";
|
|
15
15
|
import type { GSDPreferences } from "../preferences.ts";
|
|
16
16
|
import { getLegacyTelemetry, resetLegacyTelemetry } from "../legacy-telemetry.ts";
|
|
17
|
+
import { _getAdapter, closeDatabase, openDatabase } from "../gsd-db.ts";
|
|
18
|
+
import { UokGateRunner } from "../uok/gate-runner.ts";
|
|
19
|
+
import { applyModelPolicyFilter } from "../uok/model-policy.ts";
|
|
20
|
+
import {
|
|
21
|
+
clearUnifiedAuditOverrideForTests,
|
|
22
|
+
isUnifiedAuditEnabled,
|
|
23
|
+
setUnifiedAuditEnabled,
|
|
24
|
+
} from "../uok/audit-toggle.ts";
|
|
25
|
+
import { writeEscalationArtifact } from "../escalation.ts";
|
|
26
|
+
import { peekLogs, _resetLogs } from "../workflow-logger.ts";
|
|
17
27
|
|
|
18
28
|
function makeBasePath(): string {
|
|
19
29
|
return mkdtempSync(join(tmpdir(), "gsd-uok-kernel-"));
|
|
@@ -104,7 +114,8 @@ test("runAutoLoopWithUok uses kernel path by default and records uok-kernel pari
|
|
|
104
114
|
assert.ok(args.calls.kernelDeps);
|
|
105
115
|
assert.notEqual(args.calls.kernelDeps, args.deps);
|
|
106
116
|
assert.ok(args.calls.kernelDeps?.uokObserver);
|
|
107
|
-
assert.equal(
|
|
117
|
+
assert.equal(isUnifiedAuditEnabled(), true);
|
|
118
|
+
assert.equal(isUnifiedAuditEnabled(basePath), false);
|
|
108
119
|
|
|
109
120
|
const events = readParityEvents(basePath);
|
|
110
121
|
assert.equal(events.length, 2);
|
|
@@ -120,6 +131,37 @@ test("runAutoLoopWithUok uses kernel path by default and records uok-kernel pari
|
|
|
120
131
|
}
|
|
121
132
|
});
|
|
122
133
|
|
|
134
|
+
test("runAutoLoopWithUok keeps audit disabled for legacy-wrapper while restoring process override", async () => {
|
|
135
|
+
const basePath = makeBasePath();
|
|
136
|
+
clearUnifiedAuditOverrideForTests();
|
|
137
|
+
try {
|
|
138
|
+
resetLegacyTelemetry();
|
|
139
|
+
const args = makeArgs(basePath, {
|
|
140
|
+
uok: {
|
|
141
|
+
enabled: false,
|
|
142
|
+
audit_unified: { enabled: false },
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
await runAutoLoopWithUok(args);
|
|
146
|
+
|
|
147
|
+
assert.equal(args.calls.kernel, 0);
|
|
148
|
+
assert.equal(args.calls.legacy, 1);
|
|
149
|
+
assert.equal(isUnifiedAuditEnabled(), true);
|
|
150
|
+
assert.equal(isUnifiedAuditEnabled(basePath), false);
|
|
151
|
+
|
|
152
|
+
const events = readParityEvents(basePath);
|
|
153
|
+
assert.equal(events.length, 2);
|
|
154
|
+
assert.equal(events[0]?.path, "legacy-wrapper");
|
|
155
|
+
assert.equal(events[1]?.path, "legacy-wrapper");
|
|
156
|
+
assert.equal(events[1]?.status, "ok");
|
|
157
|
+
assert.equal(getLegacyTelemetry()["legacy.uokFallbackUsed"], 1);
|
|
158
|
+
} finally {
|
|
159
|
+
clearUnifiedAuditOverrideForTests();
|
|
160
|
+
resetLegacyTelemetry();
|
|
161
|
+
rmSync(basePath, { recursive: true, force: true });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
123
165
|
test("runAutoLoopWithUok uses legacy path when explicit legacy fallback is enabled", async () => {
|
|
124
166
|
const basePath = makeBasePath();
|
|
125
167
|
try {
|
|
@@ -176,3 +218,185 @@ test("runAutoLoopWithUok respects GSD_UOK_FORCE_LEGACY emergency switch", async
|
|
|
176
218
|
rmSync(basePath, { recursive: true, force: true });
|
|
177
219
|
}
|
|
178
220
|
});
|
|
221
|
+
|
|
222
|
+
test("runAutoLoopWithUok records error exit and restores previous audit override when kernel fails", async () => {
|
|
223
|
+
const basePath = makeBasePath();
|
|
224
|
+
clearUnifiedAuditOverrideForTests();
|
|
225
|
+
setUnifiedAuditEnabled(false);
|
|
226
|
+
try {
|
|
227
|
+
resetLegacyTelemetry();
|
|
228
|
+
const args = makeArgs(basePath, {
|
|
229
|
+
uok: {
|
|
230
|
+
enabled: true,
|
|
231
|
+
audit_unified: { enabled: true },
|
|
232
|
+
gitops: { enabled: false },
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
args.runKernelLoop = async (_ctx, _pi, _s, loopDeps): Promise<void> => {
|
|
236
|
+
args.calls.kernel += 1;
|
|
237
|
+
args.calls.kernelDeps = loopDeps;
|
|
238
|
+
throw new Error("kernel exploded");
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
await assert.rejects(
|
|
242
|
+
() => runAutoLoopWithUok(args),
|
|
243
|
+
/kernel exploded/,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
assert.equal(args.calls.kernel, 1);
|
|
247
|
+
assert.equal(args.calls.legacy, 0);
|
|
248
|
+
assert.equal(isUnifiedAuditEnabled(), false);
|
|
249
|
+
|
|
250
|
+
const events = readParityEvents(basePath);
|
|
251
|
+
assert.equal(events.length, 2);
|
|
252
|
+
assert.equal(events[0]?.phase, "enter");
|
|
253
|
+
assert.equal(events[1]?.phase, "exit");
|
|
254
|
+
assert.equal(events[1]?.status, "error");
|
|
255
|
+
assert.match(String(events[1]?.error), /kernel exploded/);
|
|
256
|
+
} finally {
|
|
257
|
+
clearUnifiedAuditOverrideForTests();
|
|
258
|
+
resetLegacyTelemetry();
|
|
259
|
+
rmSync(basePath, { recursive: true, force: true });
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("runAutoLoopWithUok treats kernel-enter audit failures as telemetry-only", async (t) => {
|
|
264
|
+
const basePath = makeBasePath();
|
|
265
|
+
const dbPath = join(basePath, ".gsd", "gsd.db");
|
|
266
|
+
clearUnifiedAuditOverrideForTests();
|
|
267
|
+
_resetLogs();
|
|
268
|
+
mkdirSync(join(basePath, ".gsd"), { recursive: true });
|
|
269
|
+
assert.equal(openDatabase(dbPath), true, "DB must open for this scenario");
|
|
270
|
+
t.after(() => {
|
|
271
|
+
clearUnifiedAuditOverrideForTests();
|
|
272
|
+
closeDatabase();
|
|
273
|
+
_resetLogs();
|
|
274
|
+
rmSync(basePath, { recursive: true, force: true });
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
_getAdapter()!.exec("DROP TABLE audit_events");
|
|
278
|
+
|
|
279
|
+
const args = makeArgs(basePath, {
|
|
280
|
+
uok: {
|
|
281
|
+
enabled: true,
|
|
282
|
+
audit_unified: { enabled: true },
|
|
283
|
+
gitops: { enabled: false },
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
args.runKernelLoop = async (_ctx, _pi, _s, loopDeps): Promise<void> => {
|
|
287
|
+
args.calls.kernel += 1;
|
|
288
|
+
args.calls.kernelDeps = loopDeps;
|
|
289
|
+
const observer = loopDeps.uokObserver;
|
|
290
|
+
assert.ok(observer, "kernel path must still install a turn observer");
|
|
291
|
+
observer.onTurnStart({
|
|
292
|
+
basePath,
|
|
293
|
+
traceId: "trace-audit-down",
|
|
294
|
+
turnId: "turn-audit-down",
|
|
295
|
+
iteration: 1,
|
|
296
|
+
unitType: "execute-task",
|
|
297
|
+
unitId: "M001/S01/T01",
|
|
298
|
+
startedAt: new Date().toISOString(),
|
|
299
|
+
});
|
|
300
|
+
observer.onTurnResult({
|
|
301
|
+
traceId: "trace-audit-down",
|
|
302
|
+
turnId: "turn-audit-down",
|
|
303
|
+
iteration: 1,
|
|
304
|
+
unitType: "execute-task",
|
|
305
|
+
unitId: "M001/S01/T01",
|
|
306
|
+
status: "completed",
|
|
307
|
+
failureClass: "none",
|
|
308
|
+
phaseResults: [],
|
|
309
|
+
startedAt: new Date().toISOString(),
|
|
310
|
+
finishedAt: new Date().toISOString(),
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const gateRunner = new UokGateRunner();
|
|
314
|
+
gateRunner.register({
|
|
315
|
+
id: "test-gate",
|
|
316
|
+
type: "policy",
|
|
317
|
+
execute: async () => ({
|
|
318
|
+
outcome: "pass",
|
|
319
|
+
failureClass: "none",
|
|
320
|
+
rationale: "ok",
|
|
321
|
+
}),
|
|
322
|
+
});
|
|
323
|
+
await gateRunner.run("test-gate", {
|
|
324
|
+
basePath,
|
|
325
|
+
traceId: "trace-audit-down",
|
|
326
|
+
turnId: "turn-audit-down",
|
|
327
|
+
unitType: "execute-task",
|
|
328
|
+
unitId: "M001/S01/T01",
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const filtered = applyModelPolicyFilter(
|
|
332
|
+
[{ id: "model-a", provider: "openai", api: "responses" }],
|
|
333
|
+
{
|
|
334
|
+
basePath,
|
|
335
|
+
traceId: "trace-audit-down",
|
|
336
|
+
turnId: "turn-audit-down",
|
|
337
|
+
unitType: "execute-task",
|
|
338
|
+
},
|
|
339
|
+
);
|
|
340
|
+
assert.equal(filtered.eligible.length, 1);
|
|
341
|
+
|
|
342
|
+
mkdirSync(join(basePath, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), {
|
|
343
|
+
recursive: true,
|
|
344
|
+
});
|
|
345
|
+
writeEscalationArtifact(basePath, {
|
|
346
|
+
version: 1,
|
|
347
|
+
taskId: "T01",
|
|
348
|
+
sliceId: "S01",
|
|
349
|
+
milestoneId: "M001",
|
|
350
|
+
question: "Choose a path",
|
|
351
|
+
options: [
|
|
352
|
+
{ id: "a", label: "A", tradeoffs: "First path" },
|
|
353
|
+
{ id: "b", label: "B", tradeoffs: "Second path" },
|
|
354
|
+
],
|
|
355
|
+
recommendation: "a",
|
|
356
|
+
recommendationRationale: "Test recommendation",
|
|
357
|
+
continueWithDefault: false,
|
|
358
|
+
createdAt: new Date().toISOString(),
|
|
359
|
+
});
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
await runAutoLoopWithUok(args);
|
|
363
|
+
|
|
364
|
+
assert.equal(args.calls.kernel, 1);
|
|
365
|
+
assert.equal(args.calls.legacy, 0);
|
|
366
|
+
|
|
367
|
+
const events = readParityEvents(basePath);
|
|
368
|
+
assert.equal(events.length, 3);
|
|
369
|
+
assert.equal(events[0]?.phase, "enter");
|
|
370
|
+
assert.equal(events[1]?.phase, "telemetry-error");
|
|
371
|
+
assert.equal(events[1]?.telemetry, "uok-kernel-enter");
|
|
372
|
+
assert.equal(events[2]?.phase, "exit");
|
|
373
|
+
assert.equal(events[2]?.status, "ok");
|
|
374
|
+
assert.equal(isUnifiedAuditEnabled(), true);
|
|
375
|
+
assert.equal(isUnifiedAuditEnabled(basePath), false);
|
|
376
|
+
assert.ok(
|
|
377
|
+
peekLogs().some(entry =>
|
|
378
|
+
entry.severity === "warn" &&
|
|
379
|
+
entry.component === "db" &&
|
|
380
|
+
entry.message.includes("uok-kernel-enter audit emit failed"),
|
|
381
|
+
),
|
|
382
|
+
"degraded audit path should emit a non-fatal warning",
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
assert.doesNotThrow(() => {
|
|
386
|
+
writeEscalationArtifact(basePath, {
|
|
387
|
+
version: 1,
|
|
388
|
+
taskId: "T02",
|
|
389
|
+
sliceId: "S01",
|
|
390
|
+
milestoneId: "M001",
|
|
391
|
+
question: "Continue?",
|
|
392
|
+
options: [
|
|
393
|
+
{ id: "yes", label: "Yes", tradeoffs: "Continue" },
|
|
394
|
+
{ id: "no", label: "No", tradeoffs: "Stop" },
|
|
395
|
+
],
|
|
396
|
+
recommendation: "yes",
|
|
397
|
+
recommendationRationale: "Audit stayed disabled after degraded enter",
|
|
398
|
+
continueWithDefault: true,
|
|
399
|
+
createdAt: new Date().toISOString(),
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
});
|
|
@@ -12,8 +12,13 @@ import {
|
|
|
12
12
|
registerToolCompatibility,
|
|
13
13
|
resetToolCompatibilityRegistry,
|
|
14
14
|
} from "@gsd/pi-coding-agent";
|
|
15
|
+
import {
|
|
16
|
+
clearUnifiedAuditOverrideForTests,
|
|
17
|
+
setUnifiedAuditEnabled,
|
|
18
|
+
} from "../uok/audit-toggle.ts";
|
|
15
19
|
|
|
16
20
|
test.afterEach(() => {
|
|
21
|
+
clearUnifiedAuditOverrideForTests();
|
|
17
22
|
resetToolCompatibilityRegistry();
|
|
18
23
|
});
|
|
19
24
|
|
|
@@ -32,6 +37,7 @@ test("uok model policy builds requirement vectors from unit metadata", () => {
|
|
|
32
37
|
test("uok model policy enforces provider/api/tool constraints and emits decision audit events", () => {
|
|
33
38
|
const basePath = mkdtempSync(join(tmpdir(), "gsd-uok-model-policy-"));
|
|
34
39
|
try {
|
|
40
|
+
setUnifiedAuditEnabled(true);
|
|
35
41
|
mkdirSync(join(basePath, ".gsd"), { recursive: true });
|
|
36
42
|
registerToolCompatibility("screenshot", { producesImages: true });
|
|
37
43
|
|
|
@@ -6,10 +6,10 @@ import assert from 'node:assert/strict';
|
|
|
6
6
|
import { existsSync, mkdtempSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { tmpdir } from 'node:os';
|
|
9
|
-
import { regenerateIfMissing, renderPlanContent, renderStateProjection } from '../workflow-projections.ts';
|
|
9
|
+
import { regenerateIfMissing, renderPlanContent, renderStateProjection, renderSummaryProjection } from '../workflow-projections.ts';
|
|
10
10
|
import type { SliceRow, TaskRow } from '../gsd-db.ts';
|
|
11
11
|
import { closeDatabase, insertMilestone, insertSlice, insertTask, openDatabase } from '../gsd-db.ts';
|
|
12
|
-
import { clearPathCache, _clearGsdRootCache } from '../paths.ts';
|
|
12
|
+
import { clearPathCache, _clearGsdRootCache, normalizeRealPath, resolveMilestoneFile, resolveTaskFile } from '../paths.ts';
|
|
13
13
|
import { invalidateStateCache } from '../state.ts';
|
|
14
14
|
import { clearParseCache } from '../files.ts';
|
|
15
15
|
|
|
@@ -243,6 +243,217 @@ test('workflow-projections: regenerateIfMissing PLAN restores slice plan and tas
|
|
|
243
243
|
}
|
|
244
244
|
});
|
|
245
245
|
|
|
246
|
+
test('workflow-projections: regenerateIfMissing ROADMAP is idempotent for flat-phase roadmap projections', async () => {
|
|
247
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-projections-flat-roadmap-'));
|
|
248
|
+
const dbPath = join(base, '.gsd', 'gsd.db');
|
|
249
|
+
const phaseDir = join(base, '.gsd', 'phases', '01-milestone');
|
|
250
|
+
mkdirSync(phaseDir, { recursive: true });
|
|
251
|
+
openDatabase(dbPath);
|
|
252
|
+
clearParseCache();
|
|
253
|
+
clearPathCache();
|
|
254
|
+
_clearGsdRootCache();
|
|
255
|
+
invalidateStateCache();
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
insertMilestone({ id: 'M001', title: 'Milestone', status: 'active', planning: { vision: 'Ship a layout-aware roadmap.' } });
|
|
259
|
+
insertSlice({
|
|
260
|
+
id: 'S01',
|
|
261
|
+
milestoneId: 'M001',
|
|
262
|
+
title: 'Flat slice',
|
|
263
|
+
status: 'pending',
|
|
264
|
+
demo: 'Roadmap exists at the flat-phase path.',
|
|
265
|
+
planning: { goal: 'Keep ROADMAP regeneration idempotent.' },
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const roadmapPath = join(phaseDir, '01-ROADMAP.md');
|
|
269
|
+
writeFileSync(roadmapPath, '# existing flat roadmap\n');
|
|
270
|
+
|
|
271
|
+
const regenerated = await regenerateIfMissing(base, 'M001', 'S01', 'ROADMAP');
|
|
272
|
+
|
|
273
|
+
assert.equal(regenerated, false, 'existing flat-phase ROADMAP is detected without rewriting');
|
|
274
|
+
assert.equal(readFileSync(roadmapPath, 'utf-8'), '# existing flat roadmap\n');
|
|
275
|
+
assert.equal(normalizeRealPath(resolveMilestoneFile(base, 'M001', 'ROADMAP') ?? ''), normalizeRealPath(roadmapPath));
|
|
276
|
+
} finally {
|
|
277
|
+
closeDatabase();
|
|
278
|
+
rmSync(base, { recursive: true, force: true });
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('workflow-projections: regenerateIfMissing ROADMAP regenerates missing flat-phase roadmap projections', async () => {
|
|
283
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-projections-missing-roadmap-'));
|
|
284
|
+
const dbPath = join(base, '.gsd', 'gsd.db');
|
|
285
|
+
const phaseDir = join(base, '.gsd', 'phases', '01-milestone');
|
|
286
|
+
mkdirSync(phaseDir, { recursive: true });
|
|
287
|
+
openDatabase(dbPath);
|
|
288
|
+
clearParseCache();
|
|
289
|
+
clearPathCache();
|
|
290
|
+
_clearGsdRootCache();
|
|
291
|
+
invalidateStateCache();
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
insertMilestone({ id: 'M001', title: 'Milestone', status: 'active', planning: { vision: 'Ship a layout-aware roadmap.' } });
|
|
295
|
+
insertSlice({
|
|
296
|
+
id: 'S01',
|
|
297
|
+
milestoneId: 'M001',
|
|
298
|
+
title: 'Flat slice',
|
|
299
|
+
status: 'pending',
|
|
300
|
+
demo: 'Roadmap regenerates at the flat-phase path.',
|
|
301
|
+
planning: { goal: 'Recover ROADMAP from DB.' },
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const roadmapPath = join(phaseDir, '01-ROADMAP.md');
|
|
305
|
+
const legacyRoadmapPath = join(base, '.gsd', 'milestones', 'M001', 'M001-ROADMAP.md');
|
|
306
|
+
assert.ok(!existsSync(roadmapPath), 'precondition: flat-phase ROADMAP is absent');
|
|
307
|
+
|
|
308
|
+
const regenerated = await regenerateIfMissing(base, 'M001', 'S01', 'ROADMAP');
|
|
309
|
+
|
|
310
|
+
assert.equal(regenerated, true, 'missing flat-phase ROADMAP is regenerated');
|
|
311
|
+
assert.match(readFileSync(roadmapPath, 'utf-8'), /Ship a layout-aware roadmap/);
|
|
312
|
+
assert.equal(existsSync(legacyRoadmapPath), false, 'regeneration does not create a legacy ROADMAP path');
|
|
313
|
+
assert.equal(normalizeRealPath(resolveMilestoneFile(base, 'M001', 'ROADMAP') ?? ''), normalizeRealPath(roadmapPath));
|
|
314
|
+
} finally {
|
|
315
|
+
closeDatabase();
|
|
316
|
+
rmSync(base, { recursive: true, force: true });
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test('workflow-projections: regenerateIfMissing SUMMARY is idempotent for flat-phase task summaries', async () => {
|
|
321
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-projections-flat-summary-'));
|
|
322
|
+
const dbPath = join(base, '.gsd', 'gsd.db');
|
|
323
|
+
const phaseDir = join(base, '.gsd', 'phases', '01-milestone');
|
|
324
|
+
mkdirSync(phaseDir, { recursive: true });
|
|
325
|
+
openDatabase(dbPath);
|
|
326
|
+
clearParseCache();
|
|
327
|
+
clearPathCache();
|
|
328
|
+
_clearGsdRootCache();
|
|
329
|
+
invalidateStateCache();
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
|
|
333
|
+
insertSlice({
|
|
334
|
+
id: 'S01',
|
|
335
|
+
milestoneId: 'M001',
|
|
336
|
+
title: 'Flat slice',
|
|
337
|
+
status: 'complete',
|
|
338
|
+
demo: 'Summary regenerates once.',
|
|
339
|
+
planning: { goal: 'Recover flat-phase summaries from DB.' },
|
|
340
|
+
});
|
|
341
|
+
insertTask({
|
|
342
|
+
id: 'T01',
|
|
343
|
+
sliceId: 'S01',
|
|
344
|
+
milestoneId: 'M001',
|
|
345
|
+
title: 'Completed task',
|
|
346
|
+
status: 'complete',
|
|
347
|
+
oneLiner: 'Completed the flat task.',
|
|
348
|
+
narrative: 'The task was completed through the DB-backed projection path.',
|
|
349
|
+
verificationResult: 'passed',
|
|
350
|
+
duration: '5m',
|
|
351
|
+
keyFiles: ['src/example.ts'],
|
|
352
|
+
keyDecisions: ['Use centralized task summary paths.'],
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const summaryPath = join(phaseDir, 'T01-SUMMARY.md');
|
|
356
|
+
assert.ok(!existsSync(summaryPath), 'precondition: flat-phase task summary absent');
|
|
357
|
+
|
|
358
|
+
const first = await regenerateIfMissing(base, 'M001', 'S01', 'SUMMARY');
|
|
359
|
+
const second = await regenerateIfMissing(base, 'M001', 'S01', 'SUMMARY');
|
|
360
|
+
|
|
361
|
+
assert.equal(first, true, 'first call regenerates the missing task summary');
|
|
362
|
+
assert.equal(second, false, 'second call sees the flat-phase task summary and does not rewrite it');
|
|
363
|
+
assert.equal(normalizeRealPath(resolveTaskFile(base, 'M001', 'S01', 'T01', 'SUMMARY') ?? ''), normalizeRealPath(summaryPath));
|
|
364
|
+
assert.match(readFileSync(summaryPath, 'utf-8'), /# T01: Completed the flat task\./);
|
|
365
|
+
} finally {
|
|
366
|
+
closeDatabase();
|
|
367
|
+
rmSync(base, { recursive: true, force: true });
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test('workflow-projections: flat-phase SUMMARY regeneration ignores stale nested task summaries', async () => {
|
|
372
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-projections-flat-stale-nested-'));
|
|
373
|
+
const dbPath = join(base, '.gsd', 'gsd.db');
|
|
374
|
+
const phaseDir = join(base, '.gsd', 'phases', '01-milestone');
|
|
375
|
+
const nestedTasksDir = join(phaseDir, 'tasks');
|
|
376
|
+
mkdirSync(nestedTasksDir, { recursive: true });
|
|
377
|
+
writeFileSync(join(nestedTasksDir, 'T01-SUMMARY.md'), '# stale nested summary\n');
|
|
378
|
+
openDatabase(dbPath);
|
|
379
|
+
clearParseCache();
|
|
380
|
+
clearPathCache();
|
|
381
|
+
_clearGsdRootCache();
|
|
382
|
+
invalidateStateCache();
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
|
|
386
|
+
insertSlice({
|
|
387
|
+
id: 'S01',
|
|
388
|
+
milestoneId: 'M001',
|
|
389
|
+
title: 'Flat slice',
|
|
390
|
+
status: 'complete',
|
|
391
|
+
demo: 'Summary regenerates at phase root.',
|
|
392
|
+
planning: { goal: 'Recover canonical flat-phase summaries from DB.' },
|
|
393
|
+
});
|
|
394
|
+
insertTask({
|
|
395
|
+
id: 'T01',
|
|
396
|
+
sliceId: 'S01',
|
|
397
|
+
milestoneId: 'M001',
|
|
398
|
+
title: 'Completed task',
|
|
399
|
+
status: 'complete',
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const rootSummaryPath = join(phaseDir, 'T01-SUMMARY.md');
|
|
403
|
+
assert.ok(!existsSync(rootSummaryPath), 'precondition: canonical flat-phase task summary absent');
|
|
404
|
+
|
|
405
|
+
const regenerated = await regenerateIfMissing(base, 'M001', 'S01', 'SUMMARY');
|
|
406
|
+
|
|
407
|
+
assert.equal(regenerated, true, 'missing phase-root summary is regenerated despite stale nested summary');
|
|
408
|
+
assert.equal(normalizeRealPath(resolveTaskFile(base, 'M001', 'S01', 'T01', 'SUMMARY') ?? ''), normalizeRealPath(rootSummaryPath));
|
|
409
|
+
assert.match(readFileSync(rootSummaryPath, 'utf-8'), /# T01: Completed task/);
|
|
410
|
+
assert.equal(readFileSync(join(nestedTasksDir, 'T01-SUMMARY.md'), 'utf-8'), '# stale nested summary\n');
|
|
411
|
+
} finally {
|
|
412
|
+
closeDatabase();
|
|
413
|
+
rmSync(base, { recursive: true, force: true });
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test('workflow-projections: renderSummaryProjection uses milestone title when creating fresh flat-phase dirs', async () => {
|
|
418
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-projections-fresh-summary-'));
|
|
419
|
+
const dbPath = join(base, '.gsd', 'gsd.db');
|
|
420
|
+
mkdirSync(join(base, '.gsd'), { recursive: true });
|
|
421
|
+
openDatabase(dbPath);
|
|
422
|
+
clearParseCache();
|
|
423
|
+
clearPathCache();
|
|
424
|
+
_clearGsdRootCache();
|
|
425
|
+
invalidateStateCache();
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
|
|
429
|
+
insertSlice({
|
|
430
|
+
id: 'S01',
|
|
431
|
+
milestoneId: 'M001',
|
|
432
|
+
title: 'Flat slice',
|
|
433
|
+
status: 'complete',
|
|
434
|
+
});
|
|
435
|
+
insertTask({
|
|
436
|
+
id: 'T01',
|
|
437
|
+
sliceId: 'S01',
|
|
438
|
+
milestoneId: 'M001',
|
|
439
|
+
title: 'Completed task',
|
|
440
|
+
status: 'complete',
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const titleSummaryPath = join(base, '.gsd', 'phases', '01-milestone', 'T01-SUMMARY.md');
|
|
444
|
+
const idSummaryPath = join(base, '.gsd', 'phases', '01-m001', 'T01-SUMMARY.md');
|
|
445
|
+
|
|
446
|
+
renderSummaryProjection(base, 'M001', 'S01', 'T01');
|
|
447
|
+
|
|
448
|
+
assert.ok(existsSync(titleSummaryPath), 'fresh summary projection uses the milestone title slug');
|
|
449
|
+
assert.equal(existsSync(idSummaryPath), false, 'fresh summary projection does not create an id-slug orphan dir');
|
|
450
|
+
assert.equal(normalizeRealPath(resolveTaskFile(base, 'M001', 'S01', 'T01', 'SUMMARY') ?? ''), normalizeRealPath(titleSummaryPath));
|
|
451
|
+
} finally {
|
|
452
|
+
closeDatabase();
|
|
453
|
+
rmSync(base, { recursive: true, force: true });
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
246
457
|
test('workflow-projections: renderStateProjection does not clobber non-empty STATE.md when manifest has milestones', async () => {
|
|
247
458
|
const base = mkdtempSync(join(tmpdir(), 'gsd-projection-stale-'));
|
|
248
459
|
const gsdDir = join(base, '.gsd');
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* for recovery, and invalidates caches.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { join } from "node:path";
|
|
14
13
|
import { existsSync } from "node:fs";
|
|
15
14
|
|
|
16
15
|
import {
|
|
@@ -21,7 +20,7 @@ import {
|
|
|
21
20
|
getLatestAssessmentByScope,
|
|
22
21
|
updateMilestoneStatus,
|
|
23
22
|
} from "../gsd-db.js";
|
|
24
|
-
import {
|
|
23
|
+
import { clearPathCache, resolveMilestoneFile, targetMilestoneFile } from "../paths.js";
|
|
25
24
|
import { resolveCanonicalMilestoneRoot } from "../worktree-manager.js";
|
|
26
25
|
import { isClosedStatus, isDeferredStatus } from "../status-guards.js";
|
|
27
26
|
import { saveFile, clearParseCache } from "../files.js";
|
|
@@ -210,9 +209,14 @@ export async function handleCompleteMilestone(
|
|
|
210
209
|
// ── Filesystem operations (outside transaction) ─────────────────────────
|
|
211
210
|
const summaryMd = renderMilestoneSummaryMarkdown(params, completedAt);
|
|
212
211
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
const summaryPath =
|
|
213
|
+
resolveMilestoneFile(artifactBasePath, params.milestoneId, "SUMMARY") ??
|
|
214
|
+
targetMilestoneFile(
|
|
215
|
+
artifactBasePath,
|
|
216
|
+
params.milestoneId,
|
|
217
|
+
"SUMMARY",
|
|
218
|
+
getMilestone(params.milestoneId)?.title,
|
|
219
|
+
);
|
|
216
220
|
|
|
217
221
|
// Guard (#4598): if SUMMARY.md already exists on disk, do not overwrite it.
|
|
218
222
|
// This handles re-dispatch scenarios (DB/disk state divergence) where a prior
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
1
|
import { existsSync, unlinkSync } from "node:fs";
|
|
3
|
-
import {
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { relSliceFile, resolveMilestoneFile, resolveMilestonePath, targetMilestoneFile } from "../paths.js";
|
|
4
4
|
import { clearParseCache } from "../files.js";
|
|
5
5
|
import { isClosedStatus } from "../status-guards.js";
|
|
6
6
|
import { isNonEmptyString } from "../validation.js";
|
|
@@ -290,12 +290,23 @@ export async function handleReassessRoadmap(
|
|
|
290
290
|
params.sliceChanges.removed.length > 0;
|
|
291
291
|
|
|
292
292
|
if (hasStructuralChanges) {
|
|
293
|
-
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
293
|
+
const milestoneDir = resolveMilestonePath(basePath, params.milestoneId);
|
|
294
|
+
const validationFiles = new Set([
|
|
295
|
+
resolveMilestoneFile(basePath, params.milestoneId, "VALIDATION"),
|
|
296
|
+
targetMilestoneFile(
|
|
297
|
+
basePath,
|
|
298
|
+
params.milestoneId,
|
|
299
|
+
"VALIDATION",
|
|
300
|
+
getMilestone(params.milestoneId)?.title,
|
|
301
|
+
),
|
|
302
|
+
milestoneDir ? join(milestoneDir, `${params.milestoneId}-VALIDATION.md`) : null,
|
|
303
|
+
].filter((file): file is string => Boolean(file)));
|
|
304
|
+
for (const validationFile of validationFiles) {
|
|
305
|
+
try {
|
|
306
|
+
if (existsSync(validationFile)) unlinkSync(validationFile);
|
|
307
|
+
} catch (e) {
|
|
308
|
+
logWarning("tool", `validation file cleanup failed: ${(e as Error).message}`);
|
|
309
|
+
}
|
|
299
310
|
}
|
|
300
311
|
}
|
|
301
312
|
|
|
@@ -12,15 +12,13 @@
|
|
|
12
12
|
* despite passing validation.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { join } from "node:path";
|
|
16
|
-
|
|
17
15
|
import {
|
|
18
16
|
transaction,
|
|
19
17
|
insertAssessment,
|
|
20
18
|
getMilestoneSlices,
|
|
21
19
|
getMilestone,
|
|
22
20
|
} from "../gsd-db.js";
|
|
23
|
-
import {
|
|
21
|
+
import { clearPathCache, targetMilestoneFile } from "../paths.js";
|
|
24
22
|
import { resolveCanonicalMilestoneRoot } from "../worktree-manager.js";
|
|
25
23
|
import { resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
26
24
|
import { saveFile, clearParseCache } from "../files.js";
|
|
@@ -160,11 +158,12 @@ export async function handleValidateMilestone(
|
|
|
160
158
|
// worktree exists for this milestone, validation reads/writes the
|
|
161
159
|
// worktree's artifacts instead of stale project-root state.
|
|
162
160
|
const validationMd = renderValidationMarkdown(effectiveParams);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
const validationPath = targetMilestoneFile(
|
|
162
|
+
artifactBasePath,
|
|
163
|
+
effectiveParams.milestoneId,
|
|
164
|
+
"VALIDATION",
|
|
165
|
+
getMilestone(effectiveParams.milestoneId)?.title,
|
|
166
|
+
);
|
|
168
167
|
|
|
169
168
|
// ── DB write first — matches complete-task/complete-slice pattern ───
|
|
170
169
|
// Write DB before disk so a crash between the two leaves a recoverable
|
|
@@ -204,7 +203,12 @@ export async function handleValidateMilestone(
|
|
|
204
203
|
const projectRoot = resolveWorktreeProjectRoot(basePath);
|
|
205
204
|
if (projectRoot !== artifactBasePath) {
|
|
206
205
|
// Mirror to project root using the project root's layout (same logic as above).
|
|
207
|
-
const projectValidationPath =
|
|
206
|
+
const projectValidationPath = targetMilestoneFile(
|
|
207
|
+
projectRoot,
|
|
208
|
+
effectiveParams.milestoneId,
|
|
209
|
+
"VALIDATION",
|
|
210
|
+
getMilestone(effectiveParams.milestoneId)?.title,
|
|
211
|
+
);
|
|
208
212
|
try {
|
|
209
213
|
await saveFile(projectValidationPath, validationMd);
|
|
210
214
|
} catch (mirrorErr) {
|