gsd-pi 2.45.0-dev.fdcf73c → 2.46.0-dev.cc9d310
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/extensions/gsd/auto/phases.js +14 -35
- package/dist/resources/extensions/gsd/auto/session.js +0 -11
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +112 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +25 -96
- package/dist/resources/extensions/gsd/auto-start.js +2 -3
- package/dist/resources/extensions/gsd/auto.js +8 -52
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +18 -0
- package/dist/resources/extensions/gsd/commands/context.js +0 -4
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +1 -1
- package/dist/resources/extensions/gsd/crash-recovery.js +2 -4
- package/dist/resources/extensions/gsd/dashboard-overlay.js +0 -44
- package/dist/resources/extensions/gsd/doctor-checks.js +166 -1
- package/dist/resources/extensions/gsd/doctor.js +3 -1
- package/dist/resources/extensions/gsd/gsd-db.js +11 -2
- package/dist/resources/extensions/gsd/guided-flow.js +1 -2
- package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -18
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +10 -23
- package/dist/resources/extensions/gsd/prompts/discuss.md +2 -2
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -15
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/dist/resources/extensions/gsd/prompts/queue.md +2 -2
- package/dist/resources/extensions/gsd/prompts/quick-task.md +2 -0
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -3
- package/dist/resources/extensions/gsd/prompts/rethink.md +7 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/session-lock.js +1 -3
- package/dist/resources/extensions/gsd/state.js +7 -0
- package/dist/resources/extensions/gsd/sync-lock.js +89 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +58 -12
- package/dist/resources/extensions/gsd/tools/complete-slice.js +56 -11
- package/dist/resources/extensions/gsd/tools/complete-task.js +50 -2
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +37 -1
- package/dist/resources/extensions/gsd/tools/plan-slice.js +30 -1
- package/dist/resources/extensions/gsd/tools/plan-task.js +27 -1
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +32 -2
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +86 -0
- package/dist/resources/extensions/gsd/tools/reopen-task.js +90 -0
- package/dist/resources/extensions/gsd/tools/replan-slice.js +32 -2
- package/dist/resources/extensions/gsd/unit-ownership.js +85 -0
- package/dist/resources/extensions/gsd/workflow-events.js +102 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +56 -1
- package/dist/resources/extensions/gsd/workflow-manifest.js +244 -0
- package/dist/resources/extensions/gsd/workflow-migration.js +280 -0
- package/dist/resources/extensions/gsd/workflow-projections.js +373 -0
- package/dist/resources/extensions/gsd/workflow-reconcile.js +411 -0
- package/dist/resources/extensions/gsd/write-intercept.js +84 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
- 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 +2 -2
- 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 +17 -17
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -19
- package/src/resources/extensions/gsd/auto/phases.ts +11 -35
- package/src/resources/extensions/gsd/auto/session.ts +0 -18
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +131 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +0 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +25 -106
- package/src/resources/extensions/gsd/auto-start.ts +1 -3
- package/src/resources/extensions/gsd/auto.ts +4 -80
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
- package/src/resources/extensions/gsd/commands/context.ts +0 -5
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +1 -1
- package/src/resources/extensions/gsd/crash-recovery.ts +1 -5
- package/src/resources/extensions/gsd/dashboard-overlay.ts +0 -50
- package/src/resources/extensions/gsd/doctor-checks.ts +179 -1
- package/src/resources/extensions/gsd/doctor-types.ts +7 -1
- package/src/resources/extensions/gsd/doctor.ts +4 -1
- package/src/resources/extensions/gsd/gsd-db.ts +11 -2
- package/src/resources/extensions/gsd/guided-flow.ts +1 -2
- package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +5 -21
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -23
- package/src/resources/extensions/gsd/prompts/discuss.md +2 -2
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -15
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/src/resources/extensions/gsd/prompts/queue.md +2 -2
- package/src/resources/extensions/gsd/prompts/quick-task.md +2 -0
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/rethink.md +7 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/session-lock.ts +0 -4
- package/src/resources/extensions/gsd/state.ts +8 -0
- package/src/resources/extensions/gsd/sync-lock.ts +94 -0
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +5 -13
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +6 -10
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +264 -228
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +317 -250
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +2 -8
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +15 -24
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +8 -9
- package/src/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +0 -7
- package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +20 -24
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +0 -2
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +9 -6
- package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/projection-regression.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +15 -14
- package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +155 -0
- package/src/resources/extensions/gsd/tests/reopen-task.test.ts +165 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +1 -4
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/sync-lock.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/unit-ownership.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/workflow-events.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +76 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +70 -13
- package/src/resources/extensions/gsd/tools/complete-slice.ts +68 -11
- package/src/resources/extensions/gsd/tools/complete-task.ts +63 -1
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +45 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +38 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +35 -1
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +39 -1
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +125 -0
- package/src/resources/extensions/gsd/tools/reopen-task.ts +129 -0
- package/src/resources/extensions/gsd/tools/replan-slice.ts +38 -1
- package/src/resources/extensions/gsd/types.ts +8 -0
- package/src/resources/extensions/gsd/unit-ownership.ts +104 -0
- package/src/resources/extensions/gsd/workflow-events.ts +154 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +51 -1
- package/src/resources/extensions/gsd/workflow-manifest.ts +334 -0
- package/src/resources/extensions/gsd/workflow-migration.ts +345 -0
- package/src/resources/extensions/gsd/workflow-projections.ts +425 -0
- package/src/resources/extensions/gsd/workflow-reconcile.ts +503 -0
- package/src/resources/extensions/gsd/write-intercept.ts +90 -0
- /package/dist/web/standalone/.next/static/{zWYDSwB-terOjfhmWzqk1 → ZIDqryyYDroh_8AnaAOSG}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{zWYDSwB-terOjfhmWzqk1 → ZIDqryyYDroh_8AnaAOSG}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { appendFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { atomicWriteSync } from "./atomic-write.js";
|
|
5
|
+
|
|
6
|
+
// ─── Session ID ───────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Engine-generated session ID — stable for the lifetime of this process.
|
|
10
|
+
* Agents can reference this to correlate all events from one run.
|
|
11
|
+
*/
|
|
12
|
+
const ENGINE_SESSION_ID: string = randomUUID();
|
|
13
|
+
|
|
14
|
+
export function getSessionId(): string {
|
|
15
|
+
return ENGINE_SESSION_ID;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ─── Event Types ─────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export interface WorkflowEvent {
|
|
21
|
+
cmd: string; // e.g. "complete_task"
|
|
22
|
+
params: Record<string, unknown>;
|
|
23
|
+
ts: string; // ISO 8601
|
|
24
|
+
hash: string; // content hash (hex, 16 chars)
|
|
25
|
+
actor: "agent" | "system";
|
|
26
|
+
actor_name?: string; // e.g. "executor-agent-01" — caller-provided identity
|
|
27
|
+
trigger_reason?: string; // e.g. "plan-phase complete" — caller-provided causation
|
|
28
|
+
session_id: string; // engine-generated UUID, stable per process lifetime
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─── appendEvent ─────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Append one event to .gsd/event-log.jsonl.
|
|
35
|
+
* Computes a content hash from cmd+params (deterministic, independent of ts/actor/session).
|
|
36
|
+
* Creates .gsd directory if needed.
|
|
37
|
+
*/
|
|
38
|
+
export function appendEvent(
|
|
39
|
+
basePath: string,
|
|
40
|
+
event: Omit<WorkflowEvent, "hash" | "session_id"> & { actor_name?: string; trigger_reason?: string },
|
|
41
|
+
): void {
|
|
42
|
+
const hash = createHash("sha256")
|
|
43
|
+
.update(JSON.stringify({ cmd: event.cmd, params: event.params, ts: event.ts }))
|
|
44
|
+
.digest("hex")
|
|
45
|
+
.slice(0, 16);
|
|
46
|
+
|
|
47
|
+
const fullEvent: WorkflowEvent = {
|
|
48
|
+
...event,
|
|
49
|
+
hash,
|
|
50
|
+
session_id: ENGINE_SESSION_ID,
|
|
51
|
+
};
|
|
52
|
+
const dir = join(basePath, ".gsd");
|
|
53
|
+
mkdirSync(dir, { recursive: true });
|
|
54
|
+
appendFileSync(join(dir, "event-log.jsonl"), JSON.stringify(fullEvent) + "\n", "utf-8");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── readEvents ──────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Read all events from a JSONL file.
|
|
61
|
+
* Returns empty array if file doesn't exist.
|
|
62
|
+
* Corrupted lines are skipped with stderr warning.
|
|
63
|
+
*/
|
|
64
|
+
export function readEvents(logPath: string): WorkflowEvent[] {
|
|
65
|
+
if (!existsSync(logPath)) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const content = readFileSync(logPath, "utf-8");
|
|
70
|
+
const lines = content.split("\n").filter((l) => l.length > 0);
|
|
71
|
+
const events: WorkflowEvent[] = [];
|
|
72
|
+
|
|
73
|
+
for (const line of lines) {
|
|
74
|
+
try {
|
|
75
|
+
events.push(JSON.parse(line) as WorkflowEvent);
|
|
76
|
+
} catch {
|
|
77
|
+
process.stderr.write(`workflow-events: skipping corrupted event line: ${line.slice(0, 80)}\n`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return events;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ─── findForkPoint ───────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Find the index of the last common event between two logs by comparing hashes.
|
|
88
|
+
* Returns -1 if the first events differ (completely diverged).
|
|
89
|
+
* If one log is a prefix of the other, returns length of shorter - 1.
|
|
90
|
+
*/
|
|
91
|
+
export function findForkPoint(
|
|
92
|
+
logA: WorkflowEvent[],
|
|
93
|
+
logB: WorkflowEvent[],
|
|
94
|
+
): number {
|
|
95
|
+
const minLen = Math.min(logA.length, logB.length);
|
|
96
|
+
let lastCommon = -1;
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < minLen; i++) {
|
|
99
|
+
if (logA[i]!.hash === logB[i]!.hash) {
|
|
100
|
+
lastCommon = i;
|
|
101
|
+
} else {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return lastCommon;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ─── compactMilestoneEvents ─────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Archive a milestone's events from the active log to a separate file.
|
|
113
|
+
* Active log retains only events from other milestones.
|
|
114
|
+
* Archived file is kept on disk for forensics.
|
|
115
|
+
*
|
|
116
|
+
* @param basePath - Project root (parent of .gsd/)
|
|
117
|
+
* @param milestoneId - The milestone whose events should be archived
|
|
118
|
+
* @returns { archived: number } — count of events moved to archive
|
|
119
|
+
*/
|
|
120
|
+
export function compactMilestoneEvents(
|
|
121
|
+
basePath: string,
|
|
122
|
+
milestoneId: string,
|
|
123
|
+
): { archived: number } {
|
|
124
|
+
const logPath = join(basePath, ".gsd", "event-log.jsonl");
|
|
125
|
+
const archivePath = join(basePath, ".gsd", `event-log-${milestoneId}.jsonl.archived`);
|
|
126
|
+
|
|
127
|
+
const allEvents = readEvents(logPath);
|
|
128
|
+
const toArchive = allEvents.filter(
|
|
129
|
+
(e) => (e.params as { milestoneId?: string }).milestoneId === milestoneId,
|
|
130
|
+
);
|
|
131
|
+
const remaining = allEvents.filter(
|
|
132
|
+
(e) => (e.params as { milestoneId?: string }).milestoneId !== milestoneId,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (toArchive.length === 0) {
|
|
136
|
+
return { archived: 0 };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Write archived events to .jsonl.archived file (crash-safe)
|
|
140
|
+
atomicWriteSync(
|
|
141
|
+
archivePath,
|
|
142
|
+
toArchive.map((e) => JSON.stringify(e)).join("\n") + "\n",
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Truncate active log to remaining events only
|
|
146
|
+
atomicWriteSync(
|
|
147
|
+
logPath,
|
|
148
|
+
remaining.length > 0
|
|
149
|
+
? remaining.map((e) => JSON.stringify(e)).join("\n") + "\n"
|
|
150
|
+
: "",
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return { archived: toArchive.length };
|
|
154
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Centralized warning/error accumulator for the workflow engine pipeline.
|
|
3
3
|
// Captures structured entries that the auto-loop can drain after each unit
|
|
4
4
|
// to surface root causes for stuck loops, silent degradation, and blocked writes.
|
|
5
|
+
// All entries are also persisted to .gsd/audit-log.jsonl for post-mortem analysis.
|
|
5
6
|
//
|
|
6
7
|
// Stderr policy: every logWarning/logError call writes immediately to stderr
|
|
7
8
|
// for terminal visibility. This is intentional — unlike debug-logger (which is
|
|
@@ -13,6 +14,9 @@
|
|
|
13
14
|
// the start of each unit to prevent log bleed between units running in the same
|
|
14
15
|
// Node process.
|
|
15
16
|
|
|
17
|
+
import { appendFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
|
|
16
20
|
// ─── Types ──────────────────────────────────────────────────────────────
|
|
17
21
|
|
|
18
22
|
export type LogSeverity = "warn" | "error";
|
|
@@ -38,10 +42,20 @@ export interface LogEntry {
|
|
|
38
42
|
context?: Record<string, string>;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
// ─── Buffer
|
|
45
|
+
// ─── Buffer & Persistent Audit ──────────────────────────────────────────
|
|
42
46
|
|
|
43
47
|
const MAX_BUFFER = 100;
|
|
44
48
|
let _buffer: LogEntry[] = [];
|
|
49
|
+
let _auditBasePath: string | null = null;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Set the base path for persistent audit log writes.
|
|
53
|
+
* Should be called once at engine init with the project root.
|
|
54
|
+
* Until set, log entries are buffered in-memory only.
|
|
55
|
+
*/
|
|
56
|
+
export function setLogBasePath(basePath: string): void {
|
|
57
|
+
_auditBasePath = basePath;
|
|
58
|
+
}
|
|
45
59
|
|
|
46
60
|
// ─── Public API ─────────────────────────────────────────────────────────
|
|
47
61
|
|
|
@@ -156,12 +170,36 @@ export function formatForNotification(entries: readonly LogEntry[]): string {
|
|
|
156
170
|
.join("\n");
|
|
157
171
|
}
|
|
158
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Read all entries from the persistent audit log.
|
|
175
|
+
* Returns empty array if no basePath is set or the file doesn't exist.
|
|
176
|
+
*/
|
|
177
|
+
export function readAuditLog(basePath?: string): LogEntry[] {
|
|
178
|
+
const bp = basePath ?? _auditBasePath;
|
|
179
|
+
if (!bp) return [];
|
|
180
|
+
const auditPath = join(bp, ".gsd", "audit-log.jsonl");
|
|
181
|
+
if (!existsSync(auditPath)) return [];
|
|
182
|
+
try {
|
|
183
|
+
const content = readFileSync(auditPath, "utf-8");
|
|
184
|
+
return content
|
|
185
|
+
.split("\n")
|
|
186
|
+
.filter((l) => l.length > 0)
|
|
187
|
+
.map((l) => {
|
|
188
|
+
try { return JSON.parse(l) as LogEntry; } catch { return null; }
|
|
189
|
+
})
|
|
190
|
+
.filter((e): e is LogEntry => e !== null);
|
|
191
|
+
} catch {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
159
196
|
/**
|
|
160
197
|
* Reset buffer. Call at the start of each auto-loop unit to prevent log bleed
|
|
161
198
|
* between units running in the same process. Also used in tests via _resetLogs().
|
|
162
199
|
*/
|
|
163
200
|
export function _resetLogs(): void {
|
|
164
201
|
_buffer = [];
|
|
202
|
+
_auditBasePath = null;
|
|
165
203
|
}
|
|
166
204
|
|
|
167
205
|
// ─── Internal ───────────────────────────────────────────────────────────
|
|
@@ -190,4 +228,16 @@ function _push(
|
|
|
190
228
|
if (_buffer.length > MAX_BUFFER) {
|
|
191
229
|
_buffer.shift();
|
|
192
230
|
}
|
|
231
|
+
|
|
232
|
+
// Persist to .gsd/audit-log.jsonl so entries survive context resets
|
|
233
|
+
if (_auditBasePath) {
|
|
234
|
+
try {
|
|
235
|
+
const auditDir = join(_auditBasePath, ".gsd");
|
|
236
|
+
mkdirSync(auditDir, { recursive: true });
|
|
237
|
+
appendFileSync(join(auditDir, "audit-log.jsonl"), JSON.stringify(entry) + "\n", "utf-8");
|
|
238
|
+
} catch (auditErr) {
|
|
239
|
+
// Best-effort — never let audit write failures bubble up
|
|
240
|
+
process.stderr.write(`[gsd:audit] failed to persist log entry: ${(auditErr as Error).message}\n`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
193
243
|
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import {
|
|
2
|
+
_getAdapter,
|
|
3
|
+
transaction,
|
|
4
|
+
type MilestoneRow,
|
|
5
|
+
type SliceRow,
|
|
6
|
+
type TaskRow,
|
|
7
|
+
} from "./gsd-db.js";
|
|
8
|
+
import type { Decision } from "./types.js";
|
|
9
|
+
import { atomicWriteSync } from "./atomic-write.js";
|
|
10
|
+
import { readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
// ─── Manifest Types ──────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export interface VerificationEvidenceRow {
|
|
16
|
+
id: number;
|
|
17
|
+
task_id: string;
|
|
18
|
+
slice_id: string;
|
|
19
|
+
milestone_id: string;
|
|
20
|
+
command: string;
|
|
21
|
+
exit_code: number | null;
|
|
22
|
+
verdict: string;
|
|
23
|
+
duration_ms: number | null;
|
|
24
|
+
created_at: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface StateManifest {
|
|
28
|
+
version: 1;
|
|
29
|
+
exported_at: string; // ISO 8601
|
|
30
|
+
milestones: MilestoneRow[];
|
|
31
|
+
slices: SliceRow[];
|
|
32
|
+
tasks: TaskRow[];
|
|
33
|
+
decisions: Decision[];
|
|
34
|
+
verification_evidence: VerificationEvidenceRow[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── helpers ─────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
function requireDb() {
|
|
40
|
+
const db = _getAdapter();
|
|
41
|
+
if (!db) throw new Error("workflow-manifest: No database open");
|
|
42
|
+
return db;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── snapshotState ───────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Capture complete DB state as a StateManifest.
|
|
49
|
+
* Reads all rows from milestones, slices, tasks, decisions, verification_evidence.
|
|
50
|
+
*
|
|
51
|
+
* Note: rows returned from raw queries are plain objects with TEXT columns for
|
|
52
|
+
* JSON arrays. We parse them into typed Row objects using the same logic as
|
|
53
|
+
* gsd-db helper functions.
|
|
54
|
+
*/
|
|
55
|
+
export function snapshotState(): StateManifest {
|
|
56
|
+
const db = requireDb();
|
|
57
|
+
|
|
58
|
+
// Wrap all reads in a deferred transaction so the snapshot is consistent
|
|
59
|
+
// (all SELECTs see the same DB state even if a concurrent write lands between them).
|
|
60
|
+
db.exec("BEGIN DEFERRED");
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const rawMilestones = db.prepare("SELECT * FROM milestones ORDER BY id").all() as Record<string, unknown>[];
|
|
64
|
+
const milestones: MilestoneRow[] = rawMilestones.map((r) => ({
|
|
65
|
+
id: r["id"] as string,
|
|
66
|
+
title: r["title"] as string,
|
|
67
|
+
status: r["status"] as string,
|
|
68
|
+
depends_on: JSON.parse((r["depends_on"] as string) || "[]"),
|
|
69
|
+
created_at: r["created_at"] as string,
|
|
70
|
+
completed_at: (r["completed_at"] as string) ?? null,
|
|
71
|
+
vision: (r["vision"] as string) ?? "",
|
|
72
|
+
success_criteria: JSON.parse((r["success_criteria"] as string) || "[]"),
|
|
73
|
+
key_risks: JSON.parse((r["key_risks"] as string) || "[]"),
|
|
74
|
+
proof_strategy: JSON.parse((r["proof_strategy"] as string) || "[]"),
|
|
75
|
+
verification_contract: (r["verification_contract"] as string) ?? "",
|
|
76
|
+
verification_integration: (r["verification_integration"] as string) ?? "",
|
|
77
|
+
verification_operational: (r["verification_operational"] as string) ?? "",
|
|
78
|
+
verification_uat: (r["verification_uat"] as string) ?? "",
|
|
79
|
+
definition_of_done: JSON.parse((r["definition_of_done"] as string) || "[]"),
|
|
80
|
+
requirement_coverage: (r["requirement_coverage"] as string) ?? "",
|
|
81
|
+
boundary_map_markdown: (r["boundary_map_markdown"] as string) ?? "",
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
const rawSlices = db.prepare("SELECT * FROM slices ORDER BY milestone_id, sequence, id").all() as Record<string, unknown>[];
|
|
85
|
+
const slices: SliceRow[] = rawSlices.map((r) => ({
|
|
86
|
+
milestone_id: r["milestone_id"] as string,
|
|
87
|
+
id: r["id"] as string,
|
|
88
|
+
title: r["title"] as string,
|
|
89
|
+
status: r["status"] as string,
|
|
90
|
+
risk: r["risk"] as string,
|
|
91
|
+
depends: JSON.parse((r["depends"] as string) || "[]"),
|
|
92
|
+
demo: (r["demo"] as string) ?? "",
|
|
93
|
+
created_at: r["created_at"] as string,
|
|
94
|
+
completed_at: (r["completed_at"] as string) ?? null,
|
|
95
|
+
full_summary_md: (r["full_summary_md"] as string) ?? "",
|
|
96
|
+
full_uat_md: (r["full_uat_md"] as string) ?? "",
|
|
97
|
+
goal: (r["goal"] as string) ?? "",
|
|
98
|
+
success_criteria: (r["success_criteria"] as string) ?? "",
|
|
99
|
+
proof_level: (r["proof_level"] as string) ?? "",
|
|
100
|
+
integration_closure: (r["integration_closure"] as string) ?? "",
|
|
101
|
+
observability_impact: (r["observability_impact"] as string) ?? "",
|
|
102
|
+
sequence: (r["sequence"] as number) ?? 0,
|
|
103
|
+
replan_triggered_at: (r["replan_triggered_at"] as string) ?? null,
|
|
104
|
+
}));
|
|
105
|
+
|
|
106
|
+
const rawTasks = db.prepare("SELECT * FROM tasks ORDER BY milestone_id, slice_id, sequence, id").all() as Record<string, unknown>[];
|
|
107
|
+
const tasks: TaskRow[] = rawTasks.map((r) => ({
|
|
108
|
+
milestone_id: r["milestone_id"] as string,
|
|
109
|
+
slice_id: r["slice_id"] as string,
|
|
110
|
+
id: r["id"] as string,
|
|
111
|
+
title: r["title"] as string,
|
|
112
|
+
status: r["status"] as string,
|
|
113
|
+
one_liner: (r["one_liner"] as string) ?? "",
|
|
114
|
+
narrative: (r["narrative"] as string) ?? "",
|
|
115
|
+
verification_result: (r["verification_result"] as string) ?? "",
|
|
116
|
+
duration: (r["duration"] as string) ?? "",
|
|
117
|
+
completed_at: (r["completed_at"] as string) ?? null,
|
|
118
|
+
blocker_discovered: (r["blocker_discovered"] as number) === 1,
|
|
119
|
+
deviations: (r["deviations"] as string) ?? "",
|
|
120
|
+
known_issues: (r["known_issues"] as string) ?? "",
|
|
121
|
+
key_files: JSON.parse((r["key_files"] as string) || "[]"),
|
|
122
|
+
key_decisions: JSON.parse((r["key_decisions"] as string) || "[]"),
|
|
123
|
+
full_summary_md: (r["full_summary_md"] as string) ?? "",
|
|
124
|
+
description: (r["description"] as string) ?? "",
|
|
125
|
+
estimate: (r["estimate"] as string) ?? "",
|
|
126
|
+
files: JSON.parse((r["files"] as string) || "[]"),
|
|
127
|
+
verify: (r["verify"] as string) ?? "",
|
|
128
|
+
inputs: JSON.parse((r["inputs"] as string) || "[]"),
|
|
129
|
+
expected_output: JSON.parse((r["expected_output"] as string) || "[]"),
|
|
130
|
+
observability_impact: (r["observability_impact"] as string) ?? "",
|
|
131
|
+
full_plan_md: (r["full_plan_md"] as string) ?? "",
|
|
132
|
+
sequence: (r["sequence"] as number) ?? 0,
|
|
133
|
+
}));
|
|
134
|
+
|
|
135
|
+
const rawDecisions = db.prepare("SELECT * FROM decisions ORDER BY seq").all() as Record<string, unknown>[];
|
|
136
|
+
const decisions: Decision[] = rawDecisions.map((r) => ({
|
|
137
|
+
seq: r["seq"] as number,
|
|
138
|
+
id: r["id"] as string,
|
|
139
|
+
when_context: (r["when_context"] as string) ?? "",
|
|
140
|
+
scope: (r["scope"] as string) ?? "",
|
|
141
|
+
decision: (r["decision"] as string) ?? "",
|
|
142
|
+
choice: (r["choice"] as string) ?? "",
|
|
143
|
+
rationale: (r["rationale"] as string) ?? "",
|
|
144
|
+
revisable: (r["revisable"] as string) ?? "",
|
|
145
|
+
made_by: (r["made_by"] as string as Decision["made_by"]) ?? "agent",
|
|
146
|
+
superseded_by: (r["superseded_by"] as string) ?? null,
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
const rawEvidence = db.prepare("SELECT * FROM verification_evidence ORDER BY id").all() as Record<string, unknown>[];
|
|
150
|
+
const verification_evidence: VerificationEvidenceRow[] = rawEvidence.map((r) => ({
|
|
151
|
+
id: r["id"] as number,
|
|
152
|
+
task_id: r["task_id"] as string,
|
|
153
|
+
slice_id: r["slice_id"] as string,
|
|
154
|
+
milestone_id: r["milestone_id"] as string,
|
|
155
|
+
command: r["command"] as string,
|
|
156
|
+
exit_code: (r["exit_code"] as number) ?? null,
|
|
157
|
+
verdict: (r["verdict"] as string) ?? "",
|
|
158
|
+
duration_ms: (r["duration_ms"] as number) ?? null,
|
|
159
|
+
created_at: r["created_at"] as string,
|
|
160
|
+
}));
|
|
161
|
+
|
|
162
|
+
const result: StateManifest = {
|
|
163
|
+
version: 1,
|
|
164
|
+
exported_at: new Date().toISOString(),
|
|
165
|
+
milestones,
|
|
166
|
+
slices,
|
|
167
|
+
tasks,
|
|
168
|
+
decisions,
|
|
169
|
+
verification_evidence,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
db.exec("COMMIT");
|
|
173
|
+
return result;
|
|
174
|
+
} catch (err) {
|
|
175
|
+
try { db.exec("ROLLBACK"); } catch { /* ignore rollback failure */ }
|
|
176
|
+
throw err;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ─── restore ─────────────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Atomically replace all workflow state from a manifest.
|
|
184
|
+
* Runs inside a transaction — if any insert fails, no tables are modified.
|
|
185
|
+
* Only touches engine tables + decisions. Does NOT modify artifacts or memories.
|
|
186
|
+
*/
|
|
187
|
+
function restore(manifest: StateManifest): void {
|
|
188
|
+
const db = requireDb();
|
|
189
|
+
|
|
190
|
+
transaction(() => {
|
|
191
|
+
// Clear engine tables (order matters for foreign-key-like consistency)
|
|
192
|
+
db.exec("DELETE FROM verification_evidence");
|
|
193
|
+
db.exec("DELETE FROM tasks");
|
|
194
|
+
db.exec("DELETE FROM slices");
|
|
195
|
+
db.exec("DELETE FROM milestones");
|
|
196
|
+
db.exec("DELETE FROM decisions WHERE 1=1");
|
|
197
|
+
|
|
198
|
+
// Restore milestones
|
|
199
|
+
const msStmt = db.prepare(
|
|
200
|
+
`INSERT INTO milestones (id, title, status, depends_on, created_at, completed_at,
|
|
201
|
+
vision, success_criteria, key_risks, proof_strategy,
|
|
202
|
+
verification_contract, verification_integration, verification_operational, verification_uat,
|
|
203
|
+
definition_of_done, requirement_coverage, boundary_map_markdown)
|
|
204
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
205
|
+
);
|
|
206
|
+
for (const m of manifest.milestones) {
|
|
207
|
+
msStmt.run(
|
|
208
|
+
m.id, m.title, m.status,
|
|
209
|
+
JSON.stringify(m.depends_on), m.created_at, m.completed_at,
|
|
210
|
+
m.vision, JSON.stringify(m.success_criteria), JSON.stringify(m.key_risks),
|
|
211
|
+
JSON.stringify(m.proof_strategy),
|
|
212
|
+
m.verification_contract, m.verification_integration, m.verification_operational, m.verification_uat,
|
|
213
|
+
JSON.stringify(m.definition_of_done), m.requirement_coverage, m.boundary_map_markdown,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Restore slices
|
|
218
|
+
const slStmt = db.prepare(
|
|
219
|
+
`INSERT INTO slices (milestone_id, id, title, status, risk, depends, demo,
|
|
220
|
+
created_at, completed_at, full_summary_md, full_uat_md,
|
|
221
|
+
goal, success_criteria, proof_level, integration_closure, observability_impact,
|
|
222
|
+
sequence, replan_triggered_at)
|
|
223
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
224
|
+
);
|
|
225
|
+
for (const s of manifest.slices) {
|
|
226
|
+
slStmt.run(
|
|
227
|
+
s.milestone_id, s.id, s.title, s.status, s.risk,
|
|
228
|
+
JSON.stringify(s.depends), s.demo,
|
|
229
|
+
s.created_at, s.completed_at, s.full_summary_md, s.full_uat_md,
|
|
230
|
+
s.goal, s.success_criteria, s.proof_level, s.integration_closure, s.observability_impact,
|
|
231
|
+
s.sequence, s.replan_triggered_at,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Restore tasks
|
|
236
|
+
const tkStmt = db.prepare(
|
|
237
|
+
`INSERT INTO tasks (milestone_id, slice_id, id, title, status,
|
|
238
|
+
one_liner, narrative, verification_result, duration, completed_at,
|
|
239
|
+
blocker_discovered, deviations, known_issues, key_files, key_decisions,
|
|
240
|
+
full_summary_md, description, estimate, files, verify,
|
|
241
|
+
inputs, expected_output, observability_impact, sequence)
|
|
242
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
243
|
+
);
|
|
244
|
+
for (const t of manifest.tasks) {
|
|
245
|
+
tkStmt.run(
|
|
246
|
+
t.milestone_id, t.slice_id, t.id, t.title, t.status,
|
|
247
|
+
t.one_liner, t.narrative, t.verification_result, t.duration, t.completed_at,
|
|
248
|
+
t.blocker_discovered ? 1 : 0, t.deviations, t.known_issues,
|
|
249
|
+
JSON.stringify(t.key_files), JSON.stringify(t.key_decisions),
|
|
250
|
+
t.full_summary_md, t.description, t.estimate, JSON.stringify(t.files), t.verify,
|
|
251
|
+
JSON.stringify(t.inputs), JSON.stringify(t.expected_output),
|
|
252
|
+
t.observability_impact, t.sequence,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Restore decisions
|
|
257
|
+
const dcStmt = db.prepare(
|
|
258
|
+
`INSERT INTO decisions (seq, id, when_context, scope, decision, choice, rationale, revisable, made_by, superseded_by)
|
|
259
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
260
|
+
);
|
|
261
|
+
for (const d of manifest.decisions) {
|
|
262
|
+
dcStmt.run(d.seq, d.id, d.when_context, d.scope, d.decision, d.choice, d.rationale, d.revisable, d.made_by, d.superseded_by);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Restore verification evidence
|
|
266
|
+
const evStmt = db.prepare(
|
|
267
|
+
`INSERT INTO verification_evidence (task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at)
|
|
268
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
269
|
+
);
|
|
270
|
+
for (const e of manifest.verification_evidence) {
|
|
271
|
+
evStmt.run(e.task_id, e.slice_id, e.milestone_id, e.command, e.exit_code, e.verdict, e.duration_ms, e.created_at);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ─── writeManifest ───────────────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Write current DB state to .gsd/state-manifest.json via atomicWriteSync.
|
|
280
|
+
* Uses JSON.stringify with 2-space indent for git three-way merge friendliness.
|
|
281
|
+
*/
|
|
282
|
+
export function writeManifest(basePath: string): void {
|
|
283
|
+
const manifest = snapshotState();
|
|
284
|
+
const json = JSON.stringify(manifest, null, 2);
|
|
285
|
+
const dir = join(basePath, ".gsd");
|
|
286
|
+
mkdirSync(dir, { recursive: true });
|
|
287
|
+
atomicWriteSync(join(dir, "state-manifest.json"), json);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ─── readManifest ────────────────────────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Read state-manifest.json and return parsed manifest, or null if not found.
|
|
294
|
+
*/
|
|
295
|
+
export function readManifest(basePath: string): StateManifest | null {
|
|
296
|
+
const manifestPath = join(basePath, ".gsd", "state-manifest.json");
|
|
297
|
+
|
|
298
|
+
if (!existsSync(manifestPath)) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const raw = readFileSync(manifestPath, "utf-8");
|
|
303
|
+
const parsed = JSON.parse(raw) as StateManifest;
|
|
304
|
+
|
|
305
|
+
if (parsed.version !== 1) {
|
|
306
|
+
throw new Error(`Unsupported manifest version: ${parsed.version}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Validate required fields to avoid cryptic errors during restore
|
|
310
|
+
if (!Array.isArray(parsed.milestones) || !Array.isArray(parsed.slices) ||
|
|
311
|
+
!Array.isArray(parsed.tasks) || !Array.isArray(parsed.decisions) ||
|
|
312
|
+
!Array.isArray(parsed.verification_evidence)) {
|
|
313
|
+
throw new Error("Malformed manifest: missing or invalid required arrays");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return parsed;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ─── bootstrapFromManifest ──────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Read state-manifest.json and restore DB state from it.
|
|
323
|
+
* Returns true if bootstrap succeeded, false if manifest file doesn't exist.
|
|
324
|
+
*/
|
|
325
|
+
export function bootstrapFromManifest(basePath: string): boolean {
|
|
326
|
+
const manifest = readManifest(basePath);
|
|
327
|
+
|
|
328
|
+
if (!manifest) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
restore(manifest);
|
|
333
|
+
return true;
|
|
334
|
+
}
|