switchboard-cli 0.1.0-alpha.1
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 +122 -0
- package/bin/switchboard.mjs +49 -0
- package/calibration/engine/baseline.ts +93 -0
- package/calibration/engine/diagnosis.ts +191 -0
- package/calibration/engine/diff.ts +118 -0
- package/calibration/engine/escalation.ts +49 -0
- package/calibration/engine/ledger.ts +141 -0
- package/calibration/engine/trends.ts +141 -0
- package/calibration/external/rubric.yaml +32 -0
- package/calibration/external/scorer.ts +479 -0
- package/calibration/external/verdict-writer.ts +29 -0
- package/calibration/internal/harness.ts +697 -0
- package/calibration/internal/return-simulator.ts +270 -0
- package/calibration/internal/trace-collector.ts +78 -0
- package/calibration/internal/verdict-writer.ts +149 -0
- package/calibration/ledger/baselines/baseline-2026-04-09.yaml +23 -0
- package/calibration/ledger/history.yaml +18 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/diffs/adv-0bdc944b61d5.yaml +9 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/diffs/blind-16cdf0db1b43.yaml +9 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/diffs/blind-a6b2c8be67cc.yaml +9 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/manifest.yaml +8 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/seeds/adv-0bdc944b61d5.yaml +7 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/seeds/blind-16cdf0db1b43.yaml +8 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/seeds/blind-a6b2c8be67cc.yaml +8 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/summary.yaml +10 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/traces/adv-0bdc944b61d5.yaml +141 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/traces/blind-16cdf0db1b43.yaml +147 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/traces/blind-a6b2c8be67cc.yaml +147 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-a/adv-0bdc944b61d5.yaml +24 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-a/blind-16cdf0db1b43.yaml +24 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-a/blind-a6b2c8be67cc.yaml +25 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-b/adv-0bdc944b61d5.yaml +31 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-b/blind-16cdf0db1b43.yaml +32 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-b/blind-a6b2c8be67cc.yaml +32 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/diffs/adv-a0c9e2bfb0d6.yaml +9 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/diffs/blind-3e892f3a89ee.yaml +9 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/diffs/blind-958b2f9e6816.yaml +9 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/manifest.yaml +8 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/seeds/adv-a0c9e2bfb0d6.yaml +7 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/seeds/blind-3e892f3a89ee.yaml +8 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/seeds/blind-958b2f9e6816.yaml +8 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/summary.yaml +10 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/traces/adv-a0c9e2bfb0d6.yaml +141 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/traces/blind-3e892f3a89ee.yaml +147 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/traces/blind-958b2f9e6816.yaml +147 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-a/adv-a0c9e2bfb0d6.yaml +24 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-a/blind-3e892f3a89ee.yaml +23 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-a/blind-958b2f9e6816.yaml +25 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-b/adv-a0c9e2bfb0d6.yaml +31 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-b/blind-3e892f3a89ee.yaml +32 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-b/blind-958b2f9e6816.yaml +32 -0
- package/calibration/seeds/adversarial-generator.ts +159 -0
- package/calibration/seeds/blind-generator.ts +169 -0
- package/calibration/seeds/replay-loader.ts +117 -0
- package/calibration/skill/calibrate.ts +292 -0
- package/calibration/skill/cli-flags.ts +49 -0
- package/calibration/skill/report.ts +80 -0
- package/calibration/skill/review.ts +118 -0
- package/calibration/types.ts +292 -0
- package/package.json +46 -0
- package/src/commands/audit-codex.ts +266 -0
- package/src/commands/calibrate.ts +70 -0
- package/src/commands/compile.ts +117 -0
- package/src/commands/evaluate.ts +103 -0
- package/src/commands/ingest.ts +250 -0
- package/src/commands/init.ts +133 -0
- package/src/commands/packet.ts +408 -0
- package/src/commands/receipt.ts +305 -0
- package/src/commands/run-claude.ts +355 -0
- package/src/index.ts +43 -0
- package/src/lib/draft-return.ts +278 -0
- package/src/lib/drift-guard.ts +105 -0
- package/src/lib/errors.ts +61 -0
- package/src/lib/output.ts +43 -0
- package/src/lib/paths.ts +125 -0
- package/src/lib/proof.ts +262 -0
- package/src/lib/transport.ts +276 -0
- package/src/lib/yaml-io.ts +62 -0
- package/src/store/filesystem-store.ts +326 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem Store — local YAML-backed adapter for .switchboard/ state.
|
|
3
|
+
*
|
|
4
|
+
* Implements only the subset needed for Wave 1 (compile + packet).
|
|
5
|
+
* Does NOT replicate the full Supabase store. Each function reads/writes
|
|
6
|
+
* a single canonical file under .switchboard/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
import YAML from "yaml";
|
|
12
|
+
import {
|
|
13
|
+
projectContractSchema,
|
|
14
|
+
workingStateSchema,
|
|
15
|
+
loopContractSchema,
|
|
16
|
+
handoffRecordSchema,
|
|
17
|
+
dispatchReceiptSchema,
|
|
18
|
+
type ProjectContract,
|
|
19
|
+
type WorkingState,
|
|
20
|
+
type LoopContract,
|
|
21
|
+
type HandoffRecord,
|
|
22
|
+
type DispatchReceipt,
|
|
23
|
+
type ReceiptIssuedV2,
|
|
24
|
+
type AuditedReceipt,
|
|
25
|
+
type EvaluationResult,
|
|
26
|
+
type CanonicalDelta,
|
|
27
|
+
type NormalizedReturnEnvelope,
|
|
28
|
+
type GateState,
|
|
29
|
+
} from "@switchboard/core";
|
|
30
|
+
import {
|
|
31
|
+
contractPath,
|
|
32
|
+
currentStatePath,
|
|
33
|
+
loopPath,
|
|
34
|
+
specPath,
|
|
35
|
+
packetsDir,
|
|
36
|
+
sbDir,
|
|
37
|
+
handoffsDir,
|
|
38
|
+
receiptsDir,
|
|
39
|
+
evaluationsDir,
|
|
40
|
+
returnsDir,
|
|
41
|
+
dispatchMetaPath,
|
|
42
|
+
latestYamlIn,
|
|
43
|
+
} from "../lib/paths";
|
|
44
|
+
import { readYaml, requireYaml, writeYaml, readMarkdown, writeMarkdown } from "../lib/yaml-io";
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Read operations
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
export function loadContract(repoRoot: string): ProjectContract {
|
|
51
|
+
const raw = requireYaml(contractPath(repoRoot), "project contract");
|
|
52
|
+
return projectContractSchema.parse(raw);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function loadCurrentState(repoRoot: string): WorkingState {
|
|
56
|
+
const raw = requireYaml(currentStatePath(repoRoot), "working state");
|
|
57
|
+
return workingStateSchema.parse(raw);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function loadLoop(repoRoot: string): LoopContract | null {
|
|
61
|
+
const raw = readYaml(loopPath(repoRoot));
|
|
62
|
+
if (!raw) return null;
|
|
63
|
+
return loopContractSchema.parse(raw);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function loadSpec(repoRoot: string): string | null {
|
|
67
|
+
return readMarkdown(specPath(repoRoot));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function specExists(repoRoot: string): boolean {
|
|
71
|
+
return existsSync(specPath(repoRoot));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function switchboardExists(repoRoot: string): boolean {
|
|
75
|
+
return existsSync(sbDir(repoRoot));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Write operations
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
export function writeContract(repoRoot: string, contract: ProjectContract): void {
|
|
83
|
+
writeYaml(contractPath(repoRoot), contract);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function writeCurrentState(repoRoot: string, state: WorkingState): void {
|
|
87
|
+
writeYaml(currentStatePath(repoRoot), state);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function writeLoop(repoRoot: string, loop: LoopContract): void {
|
|
91
|
+
writeYaml(loopPath(repoRoot), loop);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function writeSpec(repoRoot: string, content: string): void {
|
|
95
|
+
writeMarkdown(specPath(repoRoot), content);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Write a dispatch packet file to .switchboard/packets/.
|
|
100
|
+
* Returns the full path of the written file.
|
|
101
|
+
*/
|
|
102
|
+
export function writePacket(repoRoot: string, filename: string, content: string): string {
|
|
103
|
+
const dir = packetsDir(repoRoot);
|
|
104
|
+
if (!existsSync(dir)) {
|
|
105
|
+
mkdirSync(dir, { recursive: true });
|
|
106
|
+
}
|
|
107
|
+
const filePath = join(dir, filename);
|
|
108
|
+
writeFileSync(filePath, content, "utf-8");
|
|
109
|
+
return filePath;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Wave 2: Handoff persistence
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
function ensureDir(dir: string): void {
|
|
117
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function saveHandoff(repoRoot: string, handoff: HandoffRecord): string {
|
|
121
|
+
ensureDir(handoffsDir(repoRoot));
|
|
122
|
+
const filePath = join(handoffsDir(repoRoot), `${handoff.handoff_id}.yaml`);
|
|
123
|
+
writeYaml(filePath, handoff);
|
|
124
|
+
return filePath;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function loadHandoff(repoRoot: string, handoffId: string): HandoffRecord | null {
|
|
128
|
+
const filePath = join(handoffsDir(repoRoot), `${handoffId}.yaml`);
|
|
129
|
+
const raw = readYaml(filePath);
|
|
130
|
+
if (!raw) return null;
|
|
131
|
+
return handoffRecordSchema.parse(raw);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function loadLatestHandoff(repoRoot: string): HandoffRecord | null {
|
|
135
|
+
const latest = latestYamlIn(handoffsDir(repoRoot));
|
|
136
|
+
if (!latest) return null;
|
|
137
|
+
const raw = readYaml(latest);
|
|
138
|
+
if (!raw) return null;
|
|
139
|
+
return handoffRecordSchema.parse(raw);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Wave 2: Dispatch metadata persistence
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
export interface DispatchMeta {
|
|
147
|
+
dispatch_id: string;
|
|
148
|
+
handoff_id: string;
|
|
149
|
+
surface: string;
|
|
150
|
+
prompt: string;
|
|
151
|
+
spec_markdown: string;
|
|
152
|
+
dispatched_at: string;
|
|
153
|
+
dispatch_receipt?: DispatchReceipt;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function saveDispatchMeta(repoRoot: string, meta: DispatchMeta): string {
|
|
157
|
+
const filePath = dispatchMetaPath(repoRoot, meta.dispatch_id);
|
|
158
|
+
writeYaml(filePath, meta);
|
|
159
|
+
return filePath;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function loadDispatchMeta(repoRoot: string, dispatchId: string): DispatchMeta | null {
|
|
163
|
+
const raw = readYaml<DispatchMeta>(dispatchMetaPath(repoRoot, dispatchId));
|
|
164
|
+
return raw ?? null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function loadLatestDispatchMeta(repoRoot: string): DispatchMeta | null {
|
|
168
|
+
// Dispatch meta files are in working/ with dispatch-<id>.yaml naming
|
|
169
|
+
const workingDir = join(sbDir(repoRoot), "working");
|
|
170
|
+
if (!existsSync(workingDir)) return null;
|
|
171
|
+
const { readdirSync, statSync } = require("fs") as typeof import("fs");
|
|
172
|
+
const files = readdirSync(workingDir)
|
|
173
|
+
.filter((f: string) => f.startsWith("dispatch-") && f.endsWith(".yaml"))
|
|
174
|
+
.map((f: string) => ({ path: join(workingDir, f), mtime: statSync(join(workingDir, f)).mtimeMs }))
|
|
175
|
+
.sort((a: { mtime: number }, b: { mtime: number }) => b.mtime - a.mtime);
|
|
176
|
+
if (files.length === 0) return null;
|
|
177
|
+
return readYaml<DispatchMeta>(files[0]!.path) ?? null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Wave 2: Return persistence
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
export function saveReturn(repoRoot: string, dispatchId: string, returnData: unknown): string {
|
|
185
|
+
ensureDir(returnsDir(repoRoot));
|
|
186
|
+
const filePath = join(returnsDir(repoRoot), `${dispatchId}-return.yaml`);
|
|
187
|
+
writeYaml(filePath, returnData);
|
|
188
|
+
return filePath;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function loadReturnYaml(repoRoot: string, path: string): string | null {
|
|
192
|
+
if (!existsSync(path)) return null;
|
|
193
|
+
return readFileSync(path, "utf-8");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Wave 2: Receipt persistence
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
export function saveReceiptV2(repoRoot: string, receipt: ReceiptIssuedV2): string {
|
|
201
|
+
ensureDir(receiptsDir(repoRoot));
|
|
202
|
+
const filePath = join(receiptsDir(repoRoot), `${receipt.receipt_id}.yaml`);
|
|
203
|
+
writeYaml(filePath, receipt);
|
|
204
|
+
return filePath;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function saveReceiptJson(repoRoot: string, receipt: ReceiptIssuedV2): string {
|
|
208
|
+
ensureDir(receiptsDir(repoRoot));
|
|
209
|
+
const filePath = join(receiptsDir(repoRoot), `${receipt.receipt_id}.json`);
|
|
210
|
+
writeFileSync(filePath, JSON.stringify(receipt, null, 2), "utf-8");
|
|
211
|
+
return filePath;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function loadLatestReceipt(repoRoot: string): ReceiptIssuedV2 | null {
|
|
215
|
+
const latest = latestYamlIn(receiptsDir(repoRoot));
|
|
216
|
+
if (!latest) return null;
|
|
217
|
+
return readYaml<ReceiptIssuedV2>(latest) ?? null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Find a receipt by its source dispatch_id.
|
|
222
|
+
* Scans receipt files and matches on the dispatch_id field inside the YAML,
|
|
223
|
+
* since receipts are stored by receipt_id, not dispatch_id.
|
|
224
|
+
*/
|
|
225
|
+
export function loadReceiptByDispatch(repoRoot: string, dispatchId: string): ReceiptIssuedV2 | null {
|
|
226
|
+
const dir = receiptsDir(repoRoot);
|
|
227
|
+
if (!existsSync(dir)) return null;
|
|
228
|
+
const files = readdirSync(dir)
|
|
229
|
+
.filter(f => f.endsWith(".yaml") && !f.endsWith("-audited.yaml"))
|
|
230
|
+
.map(f => join(dir, f));
|
|
231
|
+
|
|
232
|
+
for (const filePath of files) {
|
|
233
|
+
const receipt = readYaml<ReceiptIssuedV2>(filePath);
|
|
234
|
+
if (receipt && (receipt as any).dispatch_id === dispatchId) {
|
|
235
|
+
return receipt;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// Wave 2: Audited receipt persistence (V1 format for evaluation lane)
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
export function saveAuditedReceipt(repoRoot: string, receipt: AuditedReceipt): string {
|
|
246
|
+
ensureDir(receiptsDir(repoRoot));
|
|
247
|
+
const filePath = join(receiptsDir(repoRoot), `${receipt.receipt_id}-audited.yaml`);
|
|
248
|
+
writeYaml(filePath, receipt);
|
|
249
|
+
return filePath;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function loadLatestAuditedReceipt(repoRoot: string): AuditedReceipt | null {
|
|
253
|
+
const dir = receiptsDir(repoRoot);
|
|
254
|
+
if (!existsSync(dir)) return null;
|
|
255
|
+
const { readdirSync, statSync } = require("fs") as typeof import("fs");
|
|
256
|
+
const files = readdirSync(dir)
|
|
257
|
+
.filter((f: string) => f.endsWith("-audited.yaml"))
|
|
258
|
+
.map((f: string) => ({ path: join(dir, f), mtime: statSync(join(dir, f)).mtimeMs }))
|
|
259
|
+
.sort((a: { mtime: number }, b: { mtime: number }) => b.mtime - a.mtime);
|
|
260
|
+
if (files.length === 0) return null;
|
|
261
|
+
return readYaml<AuditedReceipt>(files[0]!.path) ?? null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Wave 2: Evaluation result persistence
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
export function saveEvaluationResult(repoRoot: string, result: EvaluationResult): string {
|
|
269
|
+
ensureDir(evaluationsDir(repoRoot));
|
|
270
|
+
const filePath = join(evaluationsDir(repoRoot), `${result.evaluation_id}.yaml`);
|
|
271
|
+
writeYaml(filePath, result);
|
|
272
|
+
return filePath;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function saveEvaluationJson(repoRoot: string, result: EvaluationResult): string {
|
|
276
|
+
ensureDir(evaluationsDir(repoRoot));
|
|
277
|
+
const filePath = join(evaluationsDir(repoRoot), `${result.evaluation_id}.json`);
|
|
278
|
+
writeFileSync(filePath, JSON.stringify(result, null, 2), "utf-8");
|
|
279
|
+
return filePath;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// Wave 2: Governed pipeline intermediate persistence
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
export function saveCanonicalDelta(repoRoot: string, delta: CanonicalDelta): string {
|
|
287
|
+
ensureDir(returnsDir(repoRoot));
|
|
288
|
+
const filePath = join(returnsDir(repoRoot), `${delta.delta_id}.yaml`);
|
|
289
|
+
writeYaml(filePath, delta);
|
|
290
|
+
return filePath;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function saveNormalizedEnvelope(repoRoot: string, envelope: NormalizedReturnEnvelope): string {
|
|
294
|
+
ensureDir(returnsDir(repoRoot));
|
|
295
|
+
const filePath = join(returnsDir(repoRoot), `${envelope.envelope_id}.yaml`);
|
|
296
|
+
writeYaml(filePath, envelope);
|
|
297
|
+
return filePath;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function saveGateState(repoRoot: string, dispatchId: string, gate: GateState): string {
|
|
301
|
+
ensureDir(returnsDir(repoRoot));
|
|
302
|
+
const filePath = join(returnsDir(repoRoot), `gate-${dispatchId}.yaml`);
|
|
303
|
+
writeYaml(filePath, gate);
|
|
304
|
+
return filePath;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ---------------------------------------------------------------------------
|
|
308
|
+
// Scaffold — create the canonical .switchboard/ directory tree
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
export function scaffoldDirectories(repoRoot: string): void {
|
|
312
|
+
// Wave 1 directories only. handoffs/, receipts/, and evaluations/
|
|
313
|
+
// are created on demand in Wave 2 when those commands land.
|
|
314
|
+
const dirs = [
|
|
315
|
+
join(sbDir(repoRoot), "project"),
|
|
316
|
+
join(sbDir(repoRoot), "working"),
|
|
317
|
+
join(sbDir(repoRoot), "artifacts"),
|
|
318
|
+
packetsDir(repoRoot),
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
for (const dir of dirs) {
|
|
322
|
+
if (!existsSync(dir)) {
|
|
323
|
+
mkdirSync(dir, { recursive: true });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|