gsd-pi 2.79.0 → 2.80.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 +94 -47
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/contracts.js +1 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +146 -0
- package/dist/resources/extensions/gsd/auto/phases.js +61 -7
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +2 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +52 -29
- package/dist/resources/extensions/gsd/auto-recovery.js +63 -55
- package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -0
- package/dist/resources/extensions/gsd/auto-start.js +3 -2
- package/dist/resources/extensions/gsd/auto.js +159 -2
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +41 -45
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +8 -8
- package/dist/resources/extensions/gsd/commands/context.js +1 -1
- package/dist/resources/extensions/gsd/gsd-db.js +34 -1
- package/dist/resources/extensions/gsd/guided-flow.js +40 -0
- package/dist/resources/extensions/gsd/paths.js +5 -1
- package/dist/resources/extensions/gsd/post-execution-checks.js +25 -6
- package/dist/resources/extensions/gsd/preferences-types.js +20 -2
- package/dist/resources/extensions/gsd/preferences-validation.js +3 -3
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +82 -2
- package/dist/resources/extensions/gsd/unit-context-composer.js +32 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +21 -0
- package/dist/resources/extensions/gsd/uok/audit.js +23 -9
- package/dist/resources/extensions/gsd/uok/contracts.js +69 -1
- package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +3 -0
- package/dist/resources/extensions/gsd/uok/loop-adapter.js +48 -33
- package/dist/resources/extensions/gsd/uok/timeline.js +125 -0
- package/dist/resources/extensions/shared/gsd-phase-state.js +45 -3
- package/dist/resources/extensions/shared/interview-ui.js +15 -4
- 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 +9 -9
- 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 +9 -9
- 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/package.json +1 -1
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/dist/workflow-tools.d.ts +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +53 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/workflow-tools.test.ts +129 -2
- package/packages/mcp-server/src/workflow-tools.ts +81 -0
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- 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 +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/contracts.ts +87 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +10 -3
- package/src/resources/extensions/gsd/auto/orchestrator.ts +161 -0
- package/src/resources/extensions/gsd/auto/phases.ts +88 -9
- package/src/resources/extensions/gsd/auto/session.ts +11 -0
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +106 -28
- package/src/resources/extensions/gsd/auto-recovery.ts +59 -53
- package/src/resources/extensions/gsd/auto-runtime-state.ts +7 -0
- package/src/resources/extensions/gsd/auto-start.ts +3 -2
- package/src/resources/extensions/gsd/auto.ts +167 -1
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +14 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +49 -46
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-shouldblock-basepath.test.ts +97 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +8 -4
- package/src/resources/extensions/gsd/commands/context.ts +1 -1
- package/src/resources/extensions/gsd/gsd-db.ts +35 -1
- package/src/resources/extensions/gsd/guided-flow.ts +47 -0
- package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
- package/src/resources/extensions/gsd/paths.ts +6 -1
- package/src/resources/extensions/gsd/post-execution-checks.ts +31 -6
- package/src/resources/extensions/gsd/preferences-types.ts +23 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +3 -3
- package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +353 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +108 -1
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +148 -0
- package/src/resources/extensions/gsd/tests/current-directory-root-homedir-fallback.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/deep-planning-mode-dispatch.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/execute-summary-save-empty-project.test.ts +109 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +109 -1
- package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +132 -3
- package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +3 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +84 -1
- package/src/resources/extensions/gsd/unit-context-composer.ts +49 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +34 -0
- package/src/resources/extensions/gsd/uok/audit.ts +25 -9
- package/src/resources/extensions/gsd/uok/contracts.ts +105 -0
- package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +4 -0
- package/src/resources/extensions/gsd/uok/loop-adapter.ts +60 -45
- package/src/resources/extensions/gsd/uok/timeline.ts +158 -0
- package/src/resources/extensions/shared/gsd-phase-state.ts +56 -3
- package/src/resources/extensions/shared/interview-ui.ts +18 -5
- package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +43 -1
- package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +41 -0
- /package/dist/web/standalone/.next/static/{J-CU-p_sp45CJHT3R9TJS → V-3Ehy4B24f9FCGiLPWIM}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{J-CU-p_sp45CJHT3R9TJS → V-3Ehy4B24f9FCGiLPWIM}/_ssgManifest.js +0 -0
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
_getAdapter,
|
|
12
12
|
insertGateRow,
|
|
13
13
|
upsertRequirement,
|
|
14
|
+
getAllMilestones,
|
|
14
15
|
} from "../gsd-db.ts";
|
|
16
|
+
import { deriveState, invalidateStateCache } from "../state.ts";
|
|
15
17
|
import { markApprovalGateVerified, markDepthVerified, clearDiscussionFlowState, loadWriteGateSnapshot, setPendingGate } from "../bootstrap/write-gate.ts";
|
|
16
18
|
import {
|
|
17
19
|
executeCompleteMilestone,
|
|
@@ -690,7 +692,18 @@ test("executeSummarySave supports root-level deep planning artifacts", async ()
|
|
|
690
692
|
|
|
691
693
|
const project = await inProjectDir(base, () => executeSummarySave({
|
|
692
694
|
artifact_type: "PROJECT",
|
|
693
|
-
content:
|
|
695
|
+
content: [
|
|
696
|
+
"# Project",
|
|
697
|
+
"",
|
|
698
|
+
"## What This Is",
|
|
699
|
+
"",
|
|
700
|
+
"A root project artifact.",
|
|
701
|
+
"",
|
|
702
|
+
"## Milestone Sequence",
|
|
703
|
+
"",
|
|
704
|
+
"- [ ] M001: Foundation - Establish the first runnable slice.",
|
|
705
|
+
"",
|
|
706
|
+
].join("\n"),
|
|
694
707
|
}, base));
|
|
695
708
|
assert.equal(project.isError, undefined);
|
|
696
709
|
assert.equal(project.details.path, "PROJECT.md");
|
|
@@ -737,6 +750,109 @@ test("executeSummarySave supports root-level deep planning artifacts", async ()
|
|
|
737
750
|
}
|
|
738
751
|
});
|
|
739
752
|
|
|
753
|
+
test("executeSummarySave registers PROJECT milestone sequence for the next run", async () => {
|
|
754
|
+
const base = makeTmpBase();
|
|
755
|
+
try {
|
|
756
|
+
openTestDb(base);
|
|
757
|
+
|
|
758
|
+
const result = await inProjectDir(base, () => executeSummarySave({
|
|
759
|
+
artifact_type: "PROJECT",
|
|
760
|
+
content: [
|
|
761
|
+
"# Project",
|
|
762
|
+
"",
|
|
763
|
+
"## What This Is",
|
|
764
|
+
"",
|
|
765
|
+
"Deep project setup output.",
|
|
766
|
+
"",
|
|
767
|
+
"## Project Shape",
|
|
768
|
+
"",
|
|
769
|
+
"**Complexity:** complex",
|
|
770
|
+
"**Why:** It spans multiple delivery steps.",
|
|
771
|
+
"",
|
|
772
|
+
"## Capability Contract",
|
|
773
|
+
"",
|
|
774
|
+
"See .gsd/REQUIREMENTS.md.",
|
|
775
|
+
"",
|
|
776
|
+
"## Milestone Sequence",
|
|
777
|
+
"",
|
|
778
|
+
"- [ ] M001: Foundation - Establish the first runnable slice.",
|
|
779
|
+
"- [ ] M002: Polish - Follow-up experience work.",
|
|
780
|
+
"",
|
|
781
|
+
].join("\n"),
|
|
782
|
+
}, base));
|
|
783
|
+
|
|
784
|
+
assert.equal(result.isError, undefined);
|
|
785
|
+
assert.deepEqual(result.details.registeredMilestones, ["M001", "M002"]);
|
|
786
|
+
|
|
787
|
+
const milestones = getAllMilestones();
|
|
788
|
+
assert.deepEqual(
|
|
789
|
+
milestones.map((m) => [m.id, m.title, m.status]),
|
|
790
|
+
[
|
|
791
|
+
["M001", "Foundation", "queued"],
|
|
792
|
+
["M002", "Polish", "queued"],
|
|
793
|
+
],
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
invalidateStateCache();
|
|
797
|
+
const state = await deriveState(base);
|
|
798
|
+
assert.equal(state.activeMilestone?.id, "M001");
|
|
799
|
+
assert.equal(state.phase, "pre-planning");
|
|
800
|
+
assert.equal(state.registry[0]?.status, "active");
|
|
801
|
+
assert.equal(state.registry[1]?.status, "pending");
|
|
802
|
+
} finally {
|
|
803
|
+
closeDatabase();
|
|
804
|
+
cleanup(base);
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
test("executeSummarySave hard-fails when milestone registration throws so silent No-Active-Milestone is impossible", async () => {
|
|
809
|
+
const base = makeTmpBase();
|
|
810
|
+
try {
|
|
811
|
+
openTestDb(base);
|
|
812
|
+
const db = _getAdapter();
|
|
813
|
+
assert.ok(db, "DB should be open");
|
|
814
|
+
const originalPrepare = db.prepare.bind(db);
|
|
815
|
+
(db as any).prepare = (sql: string) => {
|
|
816
|
+
if (sql.includes("INSERT OR IGNORE INTO milestones")) {
|
|
817
|
+
throw new Error("simulated milestone registration failure");
|
|
818
|
+
}
|
|
819
|
+
return originalPrepare(sql);
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
const result = await inProjectDir(base, () => executeSummarySave({
|
|
823
|
+
artifact_type: "PROJECT",
|
|
824
|
+
content: [
|
|
825
|
+
"# Project",
|
|
826
|
+
"",
|
|
827
|
+
"## What This Is",
|
|
828
|
+
"",
|
|
829
|
+
"Deep project setup output.",
|
|
830
|
+
"",
|
|
831
|
+
"## Milestone Sequence",
|
|
832
|
+
"",
|
|
833
|
+
"- [ ] M001: Foundation - Establish the first runnable slice.",
|
|
834
|
+
"",
|
|
835
|
+
].join("\n"),
|
|
836
|
+
}, base));
|
|
837
|
+
|
|
838
|
+
// The artifact is persisted before registration runs, but registration must
|
|
839
|
+
// surface as isError so the LLM retries (INSERT OR IGNORE makes it idempotent)
|
|
840
|
+
// instead of announcing "ready" while the DB has zero milestone rows.
|
|
841
|
+
assert.equal(result.isError, true);
|
|
842
|
+
assert.equal(result.details.path, "PROJECT.md");
|
|
843
|
+
assert.equal(result.details.error, "milestone_registration_threw");
|
|
844
|
+
assert.match(String(result.details.registration_error), /simulated milestone registration failure/);
|
|
845
|
+
assert.match(result.content[0].text, /milestone registration failed/);
|
|
846
|
+
assert.match(result.content[0].text, /idempotent/);
|
|
847
|
+
assert.ok(existsSync(join(base, ".gsd", "PROJECT.md")));
|
|
848
|
+
const artifact = originalPrepare("SELECT path FROM artifacts WHERE path = ?").get("PROJECT.md");
|
|
849
|
+
assert.equal(artifact?.path, "PROJECT.md");
|
|
850
|
+
} finally {
|
|
851
|
+
closeDatabase();
|
|
852
|
+
cleanup(base);
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
|
|
740
856
|
test("executeSummarySave blocks final root artifacts while approval gate is pending", async () => {
|
|
741
857
|
const base = makeTmpBase();
|
|
742
858
|
try {
|
|
@@ -772,9 +888,22 @@ test("executeSummarySave requires verified root approval in deep mode", async ()
|
|
|
772
888
|
writeFileSync(join(base, ".gsd", "PREFERENCES.md"), "---\nplanning_depth: deep\n---\n");
|
|
773
889
|
openTestDb(base);
|
|
774
890
|
|
|
891
|
+
const projectFixture = [
|
|
892
|
+
"# Project",
|
|
893
|
+
"",
|
|
894
|
+
"## What This Is",
|
|
895
|
+
"",
|
|
896
|
+
"A root project artifact.",
|
|
897
|
+
"",
|
|
898
|
+
"## Milestone Sequence",
|
|
899
|
+
"",
|
|
900
|
+
"- [ ] M001: Foundation - Establish the first runnable slice.",
|
|
901
|
+
"",
|
|
902
|
+
].join("\n");
|
|
903
|
+
|
|
775
904
|
const blocked = await inProjectDir(base, () => executeSummarySave({
|
|
776
905
|
artifact_type: "PROJECT",
|
|
777
|
-
content:
|
|
906
|
+
content: projectFixture,
|
|
778
907
|
}, base));
|
|
779
908
|
|
|
780
909
|
assert.equal(blocked.isError, true);
|
|
@@ -786,7 +915,7 @@ test("executeSummarySave requires verified root approval in deep mode", async ()
|
|
|
786
915
|
|
|
787
916
|
const unblocked = await inProjectDir(base, () => executeSummarySave({
|
|
788
917
|
artifact_type: "PROJECT",
|
|
789
|
-
content:
|
|
918
|
+
content: projectFixture,
|
|
790
919
|
}, base));
|
|
791
920
|
|
|
792
921
|
assert.equal(unblocked.isError, undefined);
|
|
@@ -229,6 +229,9 @@ test("worktree-aware prompt builders include the explicit working directory", as
|
|
|
229
229
|
),
|
|
230
230
|
]);
|
|
231
231
|
|
|
232
|
+
assert.ok(prompts[0].includes("## Context Mode"), "discuss-milestone should include standalone Context Mode guidance");
|
|
233
|
+
assert.ok(prompts[0].includes("interview lane"), "discuss-milestone should render the interview lane");
|
|
234
|
+
|
|
232
235
|
for (const prompt of prompts) {
|
|
233
236
|
assert.match(prompt, /working directory/i);
|
|
234
237
|
assert.ok(prompt.includes(base), "prompt should include the provided working directory");
|
|
@@ -3,6 +3,7 @@ import { sanitizeCompleteMilestoneParams } from "../bootstrap/sanitize-complete-
|
|
|
3
3
|
import { loadWriteGateSnapshot, shouldBlockContextArtifactSaveInSnapshot, shouldBlockRootArtifactSaveInSnapshot } from "../bootstrap/write-gate.js";
|
|
4
4
|
import {
|
|
5
5
|
getActiveRequirements,
|
|
6
|
+
insertMilestone,
|
|
6
7
|
getMilestone,
|
|
7
8
|
getSliceStatusSummary,
|
|
8
9
|
getSliceTaskCounts,
|
|
@@ -31,6 +32,7 @@ import { handleValidateMilestone } from "./validate-milestone.js";
|
|
|
31
32
|
import { logError, logWarning } from "../workflow-logger.js";
|
|
32
33
|
import { invalidateStateCache } from "../state.js";
|
|
33
34
|
import { loadEffectiveGSDPreferences } from "../preferences.js";
|
|
35
|
+
import { parseProject } from "../schemas/parsers.js";
|
|
34
36
|
|
|
35
37
|
export const SUPPORTED_SUMMARY_ARTIFACT_TYPES = [
|
|
36
38
|
"SUMMARY",
|
|
@@ -71,6 +73,20 @@ export interface SummarySaveParams {
|
|
|
71
73
|
content: string;
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
function registerProjectMilestoneSequence(content: string): string[] {
|
|
77
|
+
const parsed = parseProject(content);
|
|
78
|
+
const registered: string[] = [];
|
|
79
|
+
for (const milestone of parsed.milestones) {
|
|
80
|
+
insertMilestone({
|
|
81
|
+
id: milestone.id,
|
|
82
|
+
title: milestone.title,
|
|
83
|
+
status: milestone.done ? "complete" : "queued",
|
|
84
|
+
});
|
|
85
|
+
registered.push(milestone.id);
|
|
86
|
+
}
|
|
87
|
+
return registered;
|
|
88
|
+
}
|
|
89
|
+
|
|
74
90
|
export async function executeSummarySave(
|
|
75
91
|
params: SummarySaveParams,
|
|
76
92
|
basePath: string = process.cwd(),
|
|
@@ -173,6 +189,67 @@ export async function executeSummarySave(
|
|
|
173
189
|
basePath,
|
|
174
190
|
);
|
|
175
191
|
|
|
192
|
+
let registeredMilestones: string[] = [];
|
|
193
|
+
if (params.artifact_type === "PROJECT") {
|
|
194
|
+
try {
|
|
195
|
+
registeredMilestones = registerProjectMilestoneSequence(contentToSave);
|
|
196
|
+
if (registeredMilestones.length > 0) invalidateStateCache();
|
|
197
|
+
} catch (err) {
|
|
198
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
199
|
+
logError("tool", `gsd_summary_save: PROJECT artifact persisted but milestone registration threw: ${msg}`, {
|
|
200
|
+
tool: "gsd_summary_save",
|
|
201
|
+
error: String(err),
|
|
202
|
+
stack: err instanceof Error ? err.stack ?? "" : "",
|
|
203
|
+
});
|
|
204
|
+
// PROJECT.md was persisted by saveArtifactToDb above; the artifacts row
|
|
205
|
+
// changed even though no milestones registered. Invalidate so subsequent
|
|
206
|
+
// /gsd reads see the persisted artifact instead of the pre-save cache.
|
|
207
|
+
invalidateStateCache();
|
|
208
|
+
return {
|
|
209
|
+
content: [{
|
|
210
|
+
type: "text",
|
|
211
|
+
text:
|
|
212
|
+
`Error: PROJECT.md was saved to ${relativePath} but milestone registration failed: ${msg}. ` +
|
|
213
|
+
`The DB has no milestone rows for this project, so /gsd will report "No Active Milestone". ` +
|
|
214
|
+
`Re-call gsd_summary_save(PROJECT) once the underlying error is resolved — INSERT OR IGNORE makes registration idempotent.`,
|
|
215
|
+
}],
|
|
216
|
+
details: {
|
|
217
|
+
operation: "save_summary",
|
|
218
|
+
path: relativePath,
|
|
219
|
+
artifact_type: params.artifact_type,
|
|
220
|
+
error: "milestone_registration_threw",
|
|
221
|
+
registration_error: msg,
|
|
222
|
+
},
|
|
223
|
+
isError: true,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
if (registeredMilestones.length === 0) {
|
|
227
|
+
logError("tool", `gsd_summary_save: PROJECT.md saved to ${relativePath} but parsed zero milestones — registration produced no DB rows`, {
|
|
228
|
+
tool: "gsd_summary_save",
|
|
229
|
+
});
|
|
230
|
+
// PROJECT.md was persisted; invalidate so subsequent reads see the new
|
|
231
|
+
// artifacts row even though no milestones registered.
|
|
232
|
+
invalidateStateCache();
|
|
233
|
+
return {
|
|
234
|
+
content: [{
|
|
235
|
+
type: "text",
|
|
236
|
+
text:
|
|
237
|
+
`Error: PROJECT.md was saved to ${relativePath} but contains zero parseable milestone lines, ` +
|
|
238
|
+
`so no milestones were registered in the DB. /gsd will report "No Active Milestone". ` +
|
|
239
|
+
`Rewrite PROJECT.md so the "Milestone Sequence" section uses canonical lines: ` +
|
|
240
|
+
`\`- [ ] M001: <Title> — <One-liner>\` (em-dash, double-dash \`--\`, or single-dash \`-\` separator), then re-call gsd_summary_save(PROJECT).`,
|
|
241
|
+
}],
|
|
242
|
+
details: {
|
|
243
|
+
operation: "save_summary",
|
|
244
|
+
path: relativePath,
|
|
245
|
+
artifact_type: params.artifact_type,
|
|
246
|
+
error: "milestone_registration_empty_parse",
|
|
247
|
+
},
|
|
248
|
+
isError: true,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
176
253
|
if (params.artifact_type === "CONTEXT" && !params.task_id) {
|
|
177
254
|
try {
|
|
178
255
|
const draftFile = params.slice_id
|
|
@@ -186,7 +263,13 @@ export async function executeSummarySave(
|
|
|
186
263
|
|
|
187
264
|
return {
|
|
188
265
|
content: [{ type: "text", text: `Saved ${params.artifact_type} artifact to ${relativePath}` }],
|
|
189
|
-
details: {
|
|
266
|
+
details: {
|
|
267
|
+
operation: "save_summary",
|
|
268
|
+
path: relativePath,
|
|
269
|
+
artifact_type: params.artifact_type,
|
|
270
|
+
content_source: contentSource,
|
|
271
|
+
...(registeredMilestones.length > 0 ? { registeredMilestones } : {}),
|
|
272
|
+
},
|
|
190
273
|
};
|
|
191
274
|
} catch (err) {
|
|
192
275
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
type BaseResolverContext,
|
|
43
43
|
type ComputedArtifactId,
|
|
44
44
|
type ComputedArtifactRegistry,
|
|
45
|
+
type ContextModePolicy,
|
|
45
46
|
type UnitContextManifest,
|
|
46
47
|
} from "./unit-context-manifest.js";
|
|
47
48
|
|
|
@@ -92,6 +93,54 @@ export function manifestBudgetChars(unitType: string): number | null {
|
|
|
92
93
|
return manifest ? manifest.maxSystemPromptChars : null;
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
// ─── Context Mode lane guidance ──────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
export type ContextModeRenderMode = "standalone" | "nested";
|
|
99
|
+
|
|
100
|
+
export interface ComposeContextModeInstructionOptions {
|
|
101
|
+
readonly enabled: boolean;
|
|
102
|
+
readonly renderMode: ContextModeRenderMode;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const CONTEXT_MODE_LANE_LABELS: Record<Exclude<ContextModePolicy, "none">, string> = {
|
|
106
|
+
interview: "interview",
|
|
107
|
+
research: "research",
|
|
108
|
+
planning: "planning",
|
|
109
|
+
execution: "execution",
|
|
110
|
+
verification: "verification",
|
|
111
|
+
orchestration: "orchestration",
|
|
112
|
+
docs: "documentation",
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const CONTEXT_MODE_GUIDANCE =
|
|
116
|
+
"Use `gsd_exec` for noisy scans, builds, and tests so full output stays out of prompt context; call `gsd_exec_search` before repeating prior runs; call `gsd_resume` after compaction or resume to recover stored execution context.";
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Render the Context Mode instruction lane for a unit type. Unknown unit
|
|
120
|
+
* types, disabled config, and explicit `contextMode: "none"` all omit the
|
|
121
|
+
* block so callers can prefix this safely without extra branching.
|
|
122
|
+
*/
|
|
123
|
+
export function composeContextModeInstructions(
|
|
124
|
+
unitType: string,
|
|
125
|
+
opts: ComposeContextModeInstructionOptions,
|
|
126
|
+
): string {
|
|
127
|
+
if (!opts.enabled) return "";
|
|
128
|
+
const manifest = resolveManifest(unitType);
|
|
129
|
+
if (!manifest || manifest.contextMode === "none") return "";
|
|
130
|
+
|
|
131
|
+
const lane = CONTEXT_MODE_LANE_LABELS[manifest.contextMode];
|
|
132
|
+
if (opts.renderMode === "nested") {
|
|
133
|
+
return `Context Mode (${lane} lane): ${CONTEXT_MODE_GUIDANCE}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return [
|
|
137
|
+
"## Context Mode",
|
|
138
|
+
"",
|
|
139
|
+
`Lane: **${lane} lane**.`,
|
|
140
|
+
CONTEXT_MODE_GUIDANCE,
|
|
141
|
+
].join("\n");
|
|
142
|
+
}
|
|
143
|
+
|
|
95
144
|
// ─── v2 surface (#4924) ───────────────────────────────────────────────────
|
|
96
145
|
|
|
97
146
|
/**
|
|
@@ -90,6 +90,17 @@ export type MemoryPolicy = "none" | "critical-only" | "prompt-relevant";
|
|
|
90
90
|
/** Preferences block policy. */
|
|
91
91
|
export type PreferencesPolicy = "none" | "active-only" | "full";
|
|
92
92
|
|
|
93
|
+
/** Context Mode lane guidance policy for each auto-mode unit. */
|
|
94
|
+
export type ContextModePolicy =
|
|
95
|
+
| "none"
|
|
96
|
+
| "interview"
|
|
97
|
+
| "research"
|
|
98
|
+
| "planning"
|
|
99
|
+
| "execution"
|
|
100
|
+
| "verification"
|
|
101
|
+
| "orchestration"
|
|
102
|
+
| "docs";
|
|
103
|
+
|
|
93
104
|
/**
|
|
94
105
|
* Tool-access policy per unit type (#4934).
|
|
95
106
|
*
|
|
@@ -220,6 +231,8 @@ export interface UnitContextManifest {
|
|
|
220
231
|
readonly codebaseMap: boolean;
|
|
221
232
|
/** Preferences block policy. */
|
|
222
233
|
readonly preferences: PreferencesPolicy;
|
|
234
|
+
/** Context Mode guidance lane. */
|
|
235
|
+
readonly contextMode: ContextModePolicy;
|
|
223
236
|
/**
|
|
224
237
|
* Tool-access policy (#4934). Runtime enforcement covers path-scoped write
|
|
225
238
|
* blocking, subagent denial, and bash allowlisting for active auto-mode
|
|
@@ -343,6 +356,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
343
356
|
memory: "prompt-relevant",
|
|
344
357
|
codebaseMap: true,
|
|
345
358
|
preferences: "active-only",
|
|
359
|
+
contextMode: "research",
|
|
346
360
|
tools: TOOLS_PLANNING,
|
|
347
361
|
artifacts: {
|
|
348
362
|
// Phase 3 migration (#4782): matches today's actual
|
|
@@ -359,6 +373,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
359
373
|
memory: "prompt-relevant",
|
|
360
374
|
codebaseMap: true,
|
|
361
375
|
preferences: "active-only",
|
|
376
|
+
contextMode: "planning",
|
|
362
377
|
tools: TOOLS_PLANNING,
|
|
363
378
|
artifacts: {
|
|
364
379
|
inline: ["project", "requirements", "decisions", "milestone-research", "templates"],
|
|
@@ -373,6 +388,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
373
388
|
memory: "prompt-relevant",
|
|
374
389
|
codebaseMap: true,
|
|
375
390
|
preferences: "active-only",
|
|
391
|
+
contextMode: "interview",
|
|
376
392
|
tools: TOOLS_PLANNING,
|
|
377
393
|
artifacts: {
|
|
378
394
|
inline: ["project", "requirements", "decisions", "milestone-context", "templates"],
|
|
@@ -387,6 +403,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
387
403
|
memory: "prompt-relevant",
|
|
388
404
|
codebaseMap: false,
|
|
389
405
|
preferences: "active-only",
|
|
406
|
+
contextMode: "verification",
|
|
390
407
|
// planning-dispatch: validation is a verification-fan-out unit. It reads
|
|
391
408
|
// the milestone surface and dispatches reviewer/security/tester subagents
|
|
392
409
|
// to report findings without touching user source. Mirrors
|
|
@@ -405,6 +422,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
405
422
|
memory: "prompt-relevant",
|
|
406
423
|
codebaseMap: false,
|
|
407
424
|
preferences: "active-only",
|
|
425
|
+
contextMode: "verification",
|
|
408
426
|
// planning-dispatch: completion is a high-leverage place to fan out to
|
|
409
427
|
// reviewer / security / tester subagents. They read the diff and report
|
|
410
428
|
// findings; they do not write user source. Write isolation to .gsd/ is
|
|
@@ -428,6 +446,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
428
446
|
memory: "prompt-relevant",
|
|
429
447
|
codebaseMap: true,
|
|
430
448
|
preferences: "active-only",
|
|
449
|
+
contextMode: "research",
|
|
431
450
|
tools: TOOLS_PLANNING,
|
|
432
451
|
artifacts: {
|
|
433
452
|
inline: ["roadmap", "milestone-research", "dependency-summaries", "templates"],
|
|
@@ -442,6 +461,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
442
461
|
memory: "prompt-relevant",
|
|
443
462
|
codebaseMap: true,
|
|
444
463
|
preferences: "active-only",
|
|
464
|
+
contextMode: "planning",
|
|
445
465
|
// planning-dispatch: allows subagent dispatch so the planner can fan out
|
|
446
466
|
// to scout for codebase recon and to planner/decompose-style specialists
|
|
447
467
|
// for sub-decomposition. Write-isolation to .gsd/ is preserved.
|
|
@@ -459,6 +479,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
459
479
|
memory: "prompt-relevant",
|
|
460
480
|
codebaseMap: true,
|
|
461
481
|
preferences: "active-only",
|
|
482
|
+
contextMode: "planning",
|
|
462
483
|
// See plan-slice — same rationale: dispatch to scout/planner-style
|
|
463
484
|
// specialists during refinement is materially better than re-doing recon
|
|
464
485
|
// inline.
|
|
@@ -476,6 +497,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
476
497
|
memory: "prompt-relevant",
|
|
477
498
|
codebaseMap: true,
|
|
478
499
|
preferences: "active-only",
|
|
500
|
+
contextMode: "planning",
|
|
479
501
|
tools: TOOLS_PLANNING,
|
|
480
502
|
artifacts: {
|
|
481
503
|
inline: ["slice-plan", "slice-research", "dependency-summaries", "prior-task-summaries", "templates"],
|
|
@@ -490,6 +512,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
490
512
|
memory: "prompt-relevant",
|
|
491
513
|
codebaseMap: false,
|
|
492
514
|
preferences: "active-only",
|
|
515
|
+
contextMode: "verification",
|
|
493
516
|
// See complete-milestone — same rationale: dispatch to reviewer / security /
|
|
494
517
|
// tester subagents to fan out review work without bloating this unit's
|
|
495
518
|
// context.
|
|
@@ -511,6 +534,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
511
534
|
memory: "critical-only",
|
|
512
535
|
codebaseMap: false,
|
|
513
536
|
preferences: "none",
|
|
537
|
+
contextMode: "planning",
|
|
514
538
|
tools: TOOLS_PLANNING,
|
|
515
539
|
artifacts: {
|
|
516
540
|
// Phase 2 pilot (#4782): manifest now matches today's actual
|
|
@@ -530,6 +554,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
530
554
|
memory: "prompt-relevant",
|
|
531
555
|
codebaseMap: true,
|
|
532
556
|
preferences: "active-only",
|
|
557
|
+
contextMode: "execution",
|
|
533
558
|
tools: TOOLS_ALL,
|
|
534
559
|
artifacts: {
|
|
535
560
|
inline: ["task-plan", "slice-plan", "prior-task-summaries", "templates"],
|
|
@@ -544,6 +569,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
544
569
|
memory: "prompt-relevant",
|
|
545
570
|
codebaseMap: true,
|
|
546
571
|
preferences: "active-only",
|
|
572
|
+
contextMode: "execution",
|
|
547
573
|
tools: TOOLS_ALL,
|
|
548
574
|
artifacts: {
|
|
549
575
|
inline: ["slice-plan", "prior-task-summaries", "templates"],
|
|
@@ -560,6 +586,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
560
586
|
memory: "critical-only",
|
|
561
587
|
codebaseMap: false,
|
|
562
588
|
preferences: "active-only",
|
|
589
|
+
contextMode: "verification",
|
|
563
590
|
tools: TOOLS_PLANNING,
|
|
564
591
|
artifacts: {
|
|
565
592
|
// Phase 3 migration (#4782): manifest matches today's actual
|
|
@@ -578,6 +605,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
578
605
|
memory: "critical-only",
|
|
579
606
|
codebaseMap: false,
|
|
580
607
|
preferences: "active-only",
|
|
608
|
+
contextMode: "verification",
|
|
581
609
|
tools: TOOLS_PLANNING,
|
|
582
610
|
artifacts: {
|
|
583
611
|
inline: ["slice-plan", "prior-task-summaries"],
|
|
@@ -592,6 +620,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
592
620
|
memory: "prompt-relevant",
|
|
593
621
|
codebaseMap: true,
|
|
594
622
|
preferences: "active-only",
|
|
623
|
+
contextMode: "docs",
|
|
595
624
|
tools: TOOLS_DOCS,
|
|
596
625
|
artifacts: {
|
|
597
626
|
inline: ["project", "requirements", "decisions", "templates"],
|
|
@@ -611,6 +640,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
611
640
|
memory: "none",
|
|
612
641
|
codebaseMap: false,
|
|
613
642
|
preferences: "none",
|
|
643
|
+
contextMode: "none",
|
|
614
644
|
tools: TOOLS_PLANNING,
|
|
615
645
|
artifacts: {
|
|
616
646
|
inline: [],
|
|
@@ -628,6 +658,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
628
658
|
memory: "prompt-relevant",
|
|
629
659
|
codebaseMap: true,
|
|
630
660
|
preferences: "active-only",
|
|
661
|
+
contextMode: "interview",
|
|
631
662
|
tools: TOOLS_PLANNING,
|
|
632
663
|
artifacts: {
|
|
633
664
|
inline: ["templates"],
|
|
@@ -644,6 +675,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
644
675
|
memory: "prompt-relevant",
|
|
645
676
|
codebaseMap: true,
|
|
646
677
|
preferences: "active-only",
|
|
678
|
+
contextMode: "interview",
|
|
647
679
|
tools: TOOLS_PLANNING,
|
|
648
680
|
artifacts: {
|
|
649
681
|
inline: ["project", "templates"],
|
|
@@ -660,6 +692,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
660
692
|
memory: "none",
|
|
661
693
|
codebaseMap: false,
|
|
662
694
|
preferences: "none",
|
|
695
|
+
contextMode: "none",
|
|
663
696
|
tools: TOOLS_PLANNING,
|
|
664
697
|
artifacts: {
|
|
665
698
|
inline: [],
|
|
@@ -678,6 +711,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
678
711
|
memory: "prompt-relevant",
|
|
679
712
|
codebaseMap: true,
|
|
680
713
|
preferences: "active-only",
|
|
714
|
+
contextMode: "research",
|
|
681
715
|
tools: { mode: "planning-dispatch", allowedSubagents: ["scout"] },
|
|
682
716
|
artifacts: {
|
|
683
717
|
inline: ["project", "requirements", "templates"],
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// GSD2 UOK Audit Events and DB-First Projection Writes
|
|
2
|
+
|
|
1
3
|
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync } from "node:fs";
|
|
2
4
|
import { join } from "node:path";
|
|
3
5
|
import { randomUUID } from "node:crypto";
|
|
@@ -6,7 +8,7 @@ import { isStaleWrite } from "../auto/turn-epoch.js";
|
|
|
6
8
|
import { withFileLockSync } from "../file-lock.js";
|
|
7
9
|
import { gsdRoot } from "../paths.js";
|
|
8
10
|
import { isDbAvailable, insertAuditEvent } from "../gsd-db.js";
|
|
9
|
-
import type
|
|
11
|
+
import { CURRENT_UOK_CONTRACT_VERSION, validateAuditEvent, type AuditEventEnvelope } from "./contracts.js";
|
|
10
12
|
|
|
11
13
|
function auditLogPath(basePath: string): string {
|
|
12
14
|
return join(gsdRoot(basePath), "audit", "events.jsonl");
|
|
@@ -25,6 +27,7 @@ export function buildAuditEnvelope(args: {
|
|
|
25
27
|
payload?: Record<string, unknown>;
|
|
26
28
|
}): AuditEventEnvelope {
|
|
27
29
|
return {
|
|
30
|
+
version: CURRENT_UOK_CONTRACT_VERSION,
|
|
28
31
|
eventId: randomUUID(),
|
|
29
32
|
traceId: args.traceId,
|
|
30
33
|
turnId: args.turnId,
|
|
@@ -39,6 +42,26 @@ export function buildAuditEnvelope(args: {
|
|
|
39
42
|
export function emitUokAuditEvent(basePath: string, event: AuditEventEnvelope): void {
|
|
40
43
|
// Drop writes from a turn superseded by timeout recovery / cancellation.
|
|
41
44
|
if (isStaleWrite("uok-audit")) return;
|
|
45
|
+
const validation = validateAuditEvent(event);
|
|
46
|
+
if (!validation.ok) {
|
|
47
|
+
throw new Error(`Invalid UOK audit event: ${validation.issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`);
|
|
48
|
+
}
|
|
49
|
+
const canonical = validation.value;
|
|
50
|
+
|
|
51
|
+
if (isDbAvailable()) {
|
|
52
|
+
try {
|
|
53
|
+
insertAuditEvent({
|
|
54
|
+
...canonical,
|
|
55
|
+
payload: {
|
|
56
|
+
...canonical.payload,
|
|
57
|
+
contractVersion: canonical.version ?? CURRENT_UOK_CONTRACT_VERSION,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
} catch (err) {
|
|
61
|
+
throw new Error(`DB authoritative audit write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
42
65
|
try {
|
|
43
66
|
ensureAuditDir(basePath);
|
|
44
67
|
const path = auditLogPath(basePath);
|
|
@@ -52,18 +75,11 @@ export function emitUokAuditEvent(basePath: string, event: AuditEventEnvelope):
|
|
|
52
75
|
withFileLockSync(
|
|
53
76
|
path,
|
|
54
77
|
() => {
|
|
55
|
-
appendFileSync(path, `${JSON.stringify(
|
|
78
|
+
appendFileSync(path, `${JSON.stringify(canonical)}\n`, "utf-8");
|
|
56
79
|
},
|
|
57
80
|
{ onLocked: "skip" },
|
|
58
81
|
);
|
|
59
82
|
} catch {
|
|
60
83
|
// Best-effort: audit writes must never break orchestration.
|
|
61
84
|
}
|
|
62
|
-
|
|
63
|
-
if (!isDbAvailable()) return;
|
|
64
|
-
try {
|
|
65
|
-
insertAuditEvent(event);
|
|
66
|
-
} catch {
|
|
67
|
-
// Projection failures are non-fatal while legacy readers are still active.
|
|
68
|
-
}
|
|
69
85
|
}
|