switchboard-cli 0.1.0-alpha.4 → 0.1.0-alpha.5
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/CHANGELOG.md +19 -0
- package/README.md +121 -65
- package/bin/switchboard.js +75 -0
- package/dist/index.cjs +14371 -0
- package/package.json +45 -8
- package/bin/switchboard.mjs +0 -49
- package/src/autoagent/boundary-check.spec.ts +0 -77
- package/src/autoagent/boundary-check.ts +0 -102
- package/src/autoagent/loop.ts +0 -327
- package/src/autoagent/results.spec.ts +0 -73
- package/src/autoagent/results.ts +0 -68
- package/src/autoagent/runner.spec.ts +0 -20
- package/src/autoagent/runner.ts +0 -92
- package/src/autoagent/types.ts +0 -64
- package/src/commands/audit-codex.ts +0 -266
- package/src/commands/autoagent.ts +0 -108
- package/src/commands/calibrate.ts +0 -70
- package/src/commands/compile.ts +0 -117
- package/src/commands/evaluate.ts +0 -103
- package/src/commands/ingest.ts +0 -250
- package/src/commands/init.ts +0 -133
- package/src/commands/packet.ts +0 -408
- package/src/commands/receipt.ts +0 -336
- package/src/commands/run-claude.ts +0 -355
- package/src/index.ts +0 -47
- package/src/lib/draft-return.ts +0 -278
- package/src/lib/drift-guard.ts +0 -105
- package/src/lib/errors.ts +0 -61
- package/src/lib/output.ts +0 -43
- package/src/lib/paths.ts +0 -125
- package/src/lib/proof.ts +0 -262
- package/src/lib/transport.ts +0 -276
- package/src/lib/yaml-io.ts +0 -62
- package/src/store/filesystem-store.ts +0 -326
package/src/lib/proof.ts
DELETED
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Portable proof capture — CLI-native proof persistence under .switchboard/proof/.
|
|
3
|
-
*
|
|
4
|
-
* Reuses @switchboard/core proof types (ProofArtifact, ProofManifest,
|
|
5
|
-
* buildProofManifest) but stores under .switchboard/proof/<dispatchId>/
|
|
6
|
-
* instead of .runs/ so the canonical root stays self-contained.
|
|
7
|
-
*
|
|
8
|
-
* The portable proof path captures what it can:
|
|
9
|
-
* - dispatch metadata (captured — from the governed harness)
|
|
10
|
-
* - structured return (derived — parsed from operator-provided YAML)
|
|
11
|
-
* - dispatch prompt (captured — the governed prompt)
|
|
12
|
-
*
|
|
13
|
-
* It does NOT fake an execution transcript — that requires real SDK
|
|
14
|
-
* transport. The evaluation lane is honest about this gap.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { createHash, randomBytes } from "crypto";
|
|
18
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, statSync } from "fs";
|
|
19
|
-
import { join } from "path";
|
|
20
|
-
import YAML from "yaml";
|
|
21
|
-
import {
|
|
22
|
-
buildProofManifest,
|
|
23
|
-
type ProofArtifact,
|
|
24
|
-
type ProofManifest,
|
|
25
|
-
proofArtifactSchema,
|
|
26
|
-
proofManifestSchema,
|
|
27
|
-
} from "@switchboard/core";
|
|
28
|
-
import { proofDir, proofManifestPath } from "./paths";
|
|
29
|
-
import { readYaml } from "./yaml-io";
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Content hashing
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
function hashContent(content: string): string {
|
|
36
|
-
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
// Proof folder management
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
export function ensureProofDir(repoRoot: string, dispatchId: string): string {
|
|
44
|
-
const dir = proofDir(repoRoot, dispatchId);
|
|
45
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
46
|
-
return dir;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
// Capture individual proof artifacts
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
|
|
53
|
-
function captureArtifact(
|
|
54
|
-
dir: string,
|
|
55
|
-
kind: ProofArtifact["kind"],
|
|
56
|
-
provenance: ProofArtifact["provenance"],
|
|
57
|
-
filename: string,
|
|
58
|
-
content: string,
|
|
59
|
-
description: string,
|
|
60
|
-
): ProofArtifact {
|
|
61
|
-
const filePath = join(dir, filename);
|
|
62
|
-
writeFileSync(filePath, content, "utf-8");
|
|
63
|
-
|
|
64
|
-
return proofArtifactSchema.parse({
|
|
65
|
-
artifact_id: `prf-${randomBytes(6).toString("hex")}`,
|
|
66
|
-
kind,
|
|
67
|
-
provenance,
|
|
68
|
-
filename,
|
|
69
|
-
captured_at: new Date().toISOString(),
|
|
70
|
-
size_bytes: Buffer.byteLength(content, "utf-8"),
|
|
71
|
-
content_hash: hashContent(content),
|
|
72
|
-
description,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Capture dispatch metadata as a proof artifact.
|
|
78
|
-
*/
|
|
79
|
-
export function captureDispatchProof(
|
|
80
|
-
repoRoot: string,
|
|
81
|
-
dispatchId: string,
|
|
82
|
-
meta: {
|
|
83
|
-
surface: string;
|
|
84
|
-
prompt: string;
|
|
85
|
-
packetHash: string;
|
|
86
|
-
promptHash: string;
|
|
87
|
-
specMarkdown: string;
|
|
88
|
-
},
|
|
89
|
-
): ProofArtifact {
|
|
90
|
-
const dir = ensureProofDir(repoRoot, dispatchId);
|
|
91
|
-
|
|
92
|
-
const content = YAML.stringify({
|
|
93
|
-
dispatch_id: dispatchId,
|
|
94
|
-
surface: meta.surface,
|
|
95
|
-
packet_hash: meta.packetHash,
|
|
96
|
-
prompt_hash: meta.promptHash,
|
|
97
|
-
prompt_length: meta.prompt.length,
|
|
98
|
-
spec_length: meta.specMarkdown.length,
|
|
99
|
-
captured_at: new Date().toISOString(),
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
return captureArtifact(
|
|
103
|
-
dir,
|
|
104
|
-
"dispatch-metadata",
|
|
105
|
-
"captured",
|
|
106
|
-
"dispatch-metadata.yaml",
|
|
107
|
-
content,
|
|
108
|
-
`Dispatch ${dispatchId} to ${meta.surface}`,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Capture the governed prompt as a proof artifact.
|
|
114
|
-
*/
|
|
115
|
-
export function capturePromptProof(
|
|
116
|
-
repoRoot: string,
|
|
117
|
-
dispatchId: string,
|
|
118
|
-
prompt: string,
|
|
119
|
-
): ProofArtifact {
|
|
120
|
-
const dir = ensureProofDir(repoRoot, dispatchId);
|
|
121
|
-
|
|
122
|
-
return captureArtifact(
|
|
123
|
-
dir,
|
|
124
|
-
"dispatch-metadata",
|
|
125
|
-
"captured",
|
|
126
|
-
"governed-prompt.md",
|
|
127
|
-
prompt,
|
|
128
|
-
`Governed dispatch prompt (${prompt.length} chars)`,
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Capture the structured return as a proof artifact.
|
|
134
|
-
*/
|
|
135
|
-
export function captureReturnProof(
|
|
136
|
-
repoRoot: string,
|
|
137
|
-
dispatchId: string,
|
|
138
|
-
returnYaml: string,
|
|
139
|
-
): ProofArtifact {
|
|
140
|
-
const dir = ensureProofDir(repoRoot, dispatchId);
|
|
141
|
-
|
|
142
|
-
return captureArtifact(
|
|
143
|
-
dir,
|
|
144
|
-
"return-structured",
|
|
145
|
-
"derived",
|
|
146
|
-
"structured-return.yaml",
|
|
147
|
-
returnYaml,
|
|
148
|
-
"Parsed structured return from execution output",
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Capture an execution transcript as a proof artifact.
|
|
154
|
-
* This is a real "captured" artifact — it came from actual CLI transport,
|
|
155
|
-
* not from operator-provided YAML.
|
|
156
|
-
*/
|
|
157
|
-
export function captureTranscriptProof(
|
|
158
|
-
repoRoot: string,
|
|
159
|
-
dispatchId: string,
|
|
160
|
-
transcript: string,
|
|
161
|
-
surface: string,
|
|
162
|
-
durationMs: number,
|
|
163
|
-
): ProofArtifact {
|
|
164
|
-
const dir = ensureProofDir(repoRoot, dispatchId);
|
|
165
|
-
|
|
166
|
-
const header = [
|
|
167
|
-
`# Execution Transcript`,
|
|
168
|
-
`# Surface: ${surface}`,
|
|
169
|
-
`# Dispatch: ${dispatchId}`,
|
|
170
|
-
`# Duration: ${durationMs}ms`,
|
|
171
|
-
`# Captured: ${new Date().toISOString()}`,
|
|
172
|
-
`---\n`,
|
|
173
|
-
].join("\n");
|
|
174
|
-
|
|
175
|
-
return captureArtifact(
|
|
176
|
-
dir,
|
|
177
|
-
"execution-transcript",
|
|
178
|
-
"captured",
|
|
179
|
-
"execution-transcript.md",
|
|
180
|
-
header + transcript,
|
|
181
|
-
`Captured execution transcript from ${surface} (${durationMs}ms)`,
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ---------------------------------------------------------------------------
|
|
186
|
-
// Manifest persistence
|
|
187
|
-
// ---------------------------------------------------------------------------
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Build and write a proof manifest for a dispatch.
|
|
191
|
-
* Collects any artifacts already in the proof folder + newly provided ones.
|
|
192
|
-
*/
|
|
193
|
-
export function buildAndWriteProofManifest(
|
|
194
|
-
repoRoot: string,
|
|
195
|
-
dispatchId: string,
|
|
196
|
-
additionalArtifacts: ProofArtifact[] = [],
|
|
197
|
-
): ProofManifest {
|
|
198
|
-
const dir = proofDir(repoRoot, dispatchId);
|
|
199
|
-
const existingArtifacts: ProofArtifact[] = [];
|
|
200
|
-
|
|
201
|
-
// Scan for already-captured artifacts
|
|
202
|
-
if (existsSync(dir)) {
|
|
203
|
-
const files = readdirSync(dir).filter(f => f !== "manifest.yaml");
|
|
204
|
-
for (const file of files) {
|
|
205
|
-
const filePath = join(dir, file);
|
|
206
|
-
const s = statSync(filePath);
|
|
207
|
-
if (s.isFile()) {
|
|
208
|
-
const content = readFileSync(filePath, "utf-8");
|
|
209
|
-
|
|
210
|
-
// Classify kind from filename
|
|
211
|
-
let kind: ProofArtifact["kind"] = "custom";
|
|
212
|
-
if (file.includes("dispatch-metadata")) kind = "dispatch-metadata";
|
|
213
|
-
else if (file.includes("return")) kind = "return-structured";
|
|
214
|
-
else if (file.includes("prompt")) kind = "dispatch-metadata";
|
|
215
|
-
else if (file.includes("transcript")) kind = "execution-transcript";
|
|
216
|
-
else if (file.includes("trace")) kind = "execution-trace";
|
|
217
|
-
|
|
218
|
-
existingArtifacts.push(proofArtifactSchema.parse({
|
|
219
|
-
artifact_id: `prf-${randomBytes(6).toString("hex")}`,
|
|
220
|
-
kind,
|
|
221
|
-
provenance: file.includes("return") ? "derived" : "captured",
|
|
222
|
-
filename: file,
|
|
223
|
-
captured_at: new Date(s.mtimeMs).toISOString(),
|
|
224
|
-
size_bytes: s.size,
|
|
225
|
-
content_hash: hashContent(content),
|
|
226
|
-
description: `Proof artifact: ${file}`,
|
|
227
|
-
}));
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Deduplicate by filename (prefer provided over scanned)
|
|
233
|
-
const seenFilenames = new Set(additionalArtifacts.map(a => a.filename));
|
|
234
|
-
const allArtifacts = [
|
|
235
|
-
...additionalArtifacts,
|
|
236
|
-
...existingArtifacts.filter(a => !seenFilenames.has(a.filename)),
|
|
237
|
-
];
|
|
238
|
-
|
|
239
|
-
const manifest = buildProofManifest({
|
|
240
|
-
dispatchId,
|
|
241
|
-
runFolder: dir,
|
|
242
|
-
artifacts: allArtifacts,
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
// Write manifest
|
|
246
|
-
ensureProofDir(repoRoot, dispatchId);
|
|
247
|
-
const manifestFile = proofManifestPath(repoRoot, dispatchId);
|
|
248
|
-
writeFileSync(manifestFile, YAML.stringify(manifest), "utf-8");
|
|
249
|
-
|
|
250
|
-
return manifest;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Load a proof manifest for a dispatch, if one exists.
|
|
255
|
-
*/
|
|
256
|
-
export function loadPortableProofManifest(
|
|
257
|
-
repoRoot: string,
|
|
258
|
-
dispatchId: string,
|
|
259
|
-
): ProofManifest | null {
|
|
260
|
-
const manifestFile = proofManifestPath(repoRoot, dispatchId);
|
|
261
|
-
return readYaml<ProofManifest>(manifestFile);
|
|
262
|
-
}
|
package/src/lib/transport.ts
DELETED
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Transport detection — discovers whether Claude Code or Codex CLI
|
|
3
|
-
* is available locally, with honest status reporting.
|
|
4
|
-
*
|
|
5
|
-
* Returns actionable readiness signals so commands can:
|
|
6
|
-
* 1. Attempt automatic transport when ready
|
|
7
|
-
* 2. Fall back to manual with clear guidance when not
|
|
8
|
-
* 3. Never fake or approximate transport availability
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { execFileSync, type SpawnSyncReturns } from "child_process";
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Types
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
export type TransportStatus =
|
|
18
|
-
| "ready" // CLI found + invocable (auth validated at execution time)
|
|
19
|
-
| "no-cli" // CLI binary not found on PATH
|
|
20
|
-
| "no-auth" // CLI found but auth provably missing
|
|
21
|
-
| "cli-error" // CLI found but probe command failed unexpectedly
|
|
22
|
-
| "manual-requested"; // Operator explicitly chose manual dispatch
|
|
23
|
-
|
|
24
|
-
export type TransportCapability =
|
|
25
|
-
| "review-only" // Transcript capture only — CLI cannot modify files (claude -p default)
|
|
26
|
-
| "write-capable" // CLI can modify repo files (requires --allowedTools or equivalent)
|
|
27
|
-
| "unknown"; // Cannot determine capability
|
|
28
|
-
|
|
29
|
-
export interface TransportReadiness {
|
|
30
|
-
status: TransportStatus;
|
|
31
|
-
cli_path: string | null;
|
|
32
|
-
detail: string;
|
|
33
|
-
capability: TransportCapability;
|
|
34
|
-
capability_detail: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
// Helpers
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
function whichSync(cmd: string): string | null {
|
|
42
|
-
try {
|
|
43
|
-
return execFileSync("which", [cmd], {
|
|
44
|
-
encoding: "utf-8",
|
|
45
|
-
timeout: 5000,
|
|
46
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
47
|
-
}).trim();
|
|
48
|
-
} catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
// Claude Code detection
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Detect whether the `claude` CLI is available and authenticated.
|
|
59
|
-
*
|
|
60
|
-
* Checks:
|
|
61
|
-
* 1. `claude` binary on PATH (via `which`)
|
|
62
|
-
* 2. Basic invocability (`claude --version`)
|
|
63
|
-
*
|
|
64
|
-
* Does NOT run a real prompt — that happens in the transport itself.
|
|
65
|
-
*/
|
|
66
|
-
export function detectClaudeTransport(): TransportReadiness {
|
|
67
|
-
const cliPath = whichSync("claude");
|
|
68
|
-
|
|
69
|
-
if (!cliPath) {
|
|
70
|
-
return {
|
|
71
|
-
status: "no-cli",
|
|
72
|
-
cli_path: null,
|
|
73
|
-
detail: "Claude Code CLI not found on PATH. Install: npm install -g @anthropic-ai/claude-code",
|
|
74
|
-
capability: "unknown",
|
|
75
|
-
capability_detail: "CLI not found.",
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Probe: can we invoke it?
|
|
80
|
-
try {
|
|
81
|
-
execFileSync(cliPath, ["--version"], {
|
|
82
|
-
encoding: "utf-8",
|
|
83
|
-
timeout: 10000,
|
|
84
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
85
|
-
});
|
|
86
|
-
} catch (e: any) {
|
|
87
|
-
return {
|
|
88
|
-
status: "cli-error",
|
|
89
|
-
cli_path: cliPath,
|
|
90
|
-
detail: `Claude CLI found at ${cliPath} but --version failed: ${e.message?.slice(0, 120) ?? "unknown error"}`,
|
|
91
|
-
capability: "unknown",
|
|
92
|
-
capability_detail: "CLI probe failed.",
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Capability: claude -p (print mode) is review-only by default.
|
|
97
|
-
// Write capability requires --allowedTools or similar flags.
|
|
98
|
-
return {
|
|
99
|
-
status: "ready",
|
|
100
|
-
cli_path: cliPath,
|
|
101
|
-
detail: `Claude Code CLI ready at ${cliPath} (auth validated at execution time)`,
|
|
102
|
-
capability: "review-only",
|
|
103
|
-
capability_detail: "Print mode (-p) captures transcript only. Claude cannot modify files without --allowedTools flag. To enable writes: claude -p --allowedTools Edit,Write",
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ---------------------------------------------------------------------------
|
|
108
|
-
// Codex CLI detection
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Detect whether the `codex` CLI is available and authenticated.
|
|
113
|
-
*
|
|
114
|
-
* Checks:
|
|
115
|
-
* 1. `codex` binary on PATH
|
|
116
|
-
* 2. Basic invocability (`codex --version`)
|
|
117
|
-
*
|
|
118
|
-
* Auth is validated at execution time, not detection time.
|
|
119
|
-
* Codex supports both OPENAI_API_KEY and local login — we do not
|
|
120
|
-
* hard-require either, since a `--version` probe cannot distinguish them.
|
|
121
|
-
*/
|
|
122
|
-
export function detectCodexTransport(): TransportReadiness {
|
|
123
|
-
const cliPath = whichSync("codex");
|
|
124
|
-
|
|
125
|
-
if (!cliPath) {
|
|
126
|
-
return {
|
|
127
|
-
status: "no-cli",
|
|
128
|
-
cli_path: null,
|
|
129
|
-
detail: "Codex CLI not found on PATH. Install: npm install -g @openai/codex",
|
|
130
|
-
capability: "unknown",
|
|
131
|
-
capability_detail: "CLI not found.",
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Probe: can we invoke it?
|
|
136
|
-
try {
|
|
137
|
-
execFileSync(cliPath, ["--version"], {
|
|
138
|
-
encoding: "utf-8",
|
|
139
|
-
timeout: 10000,
|
|
140
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
141
|
-
});
|
|
142
|
-
} catch (e: any) {
|
|
143
|
-
return {
|
|
144
|
-
status: "cli-error",
|
|
145
|
-
cli_path: cliPath,
|
|
146
|
-
detail: `Codex CLI found at ${cliPath} but --version failed: ${e.message?.slice(0, 120) ?? "unknown error"}`,
|
|
147
|
-
capability: "unknown",
|
|
148
|
-
capability_detail: "CLI probe failed.",
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Codex review is always read-only — that's the correct audit posture
|
|
153
|
-
return {
|
|
154
|
-
status: "ready",
|
|
155
|
-
cli_path: cliPath,
|
|
156
|
-
detail: `Codex CLI ready at ${cliPath} (auth validated at execution time)`,
|
|
157
|
-
capability: "review-only",
|
|
158
|
-
capability_detail: "Codex review mode is read-only by design (audit lane).",
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
163
|
-
// Transport execution — Claude Code
|
|
164
|
-
// ---------------------------------------------------------------------------
|
|
165
|
-
|
|
166
|
-
export interface TransportResult {
|
|
167
|
-
success: boolean;
|
|
168
|
-
transcript: string;
|
|
169
|
-
exit_code: number;
|
|
170
|
-
error?: string;
|
|
171
|
-
duration_ms: number;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Execute a governed prompt via Claude Code CLI in print mode.
|
|
176
|
-
*
|
|
177
|
-
* Uses `claude -p` (print mode) which reads the prompt, produces output,
|
|
178
|
-
* and exits. The full stdout is captured as the execution transcript.
|
|
179
|
-
*/
|
|
180
|
-
export function executeClaudeTransport(
|
|
181
|
-
cliPath: string,
|
|
182
|
-
prompt: string,
|
|
183
|
-
workingDirectory: string,
|
|
184
|
-
timeoutMs: number = 600000, // 10 minutes default
|
|
185
|
-
): TransportResult {
|
|
186
|
-
const start = Date.now();
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
const result = execFileSync(cliPath, ["-p", "--output-format", "text"], {
|
|
190
|
-
input: prompt,
|
|
191
|
-
cwd: workingDirectory,
|
|
192
|
-
encoding: "utf-8",
|
|
193
|
-
timeout: timeoutMs,
|
|
194
|
-
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
195
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
success: true,
|
|
200
|
-
transcript: result,
|
|
201
|
-
exit_code: 0,
|
|
202
|
-
duration_ms: Date.now() - start,
|
|
203
|
-
};
|
|
204
|
-
} catch (e: any) {
|
|
205
|
-
const exitCode = e.status ?? 1;
|
|
206
|
-
const stdout = e.stdout ?? "";
|
|
207
|
-
const stderr = e.stderr ?? "";
|
|
208
|
-
|
|
209
|
-
return {
|
|
210
|
-
success: false,
|
|
211
|
-
transcript: stdout || stderr || e.message || "Unknown transport error",
|
|
212
|
-
exit_code: exitCode,
|
|
213
|
-
error: stderr || e.message,
|
|
214
|
-
duration_ms: Date.now() - start,
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ---------------------------------------------------------------------------
|
|
220
|
-
// Transport execution — Codex CLI
|
|
221
|
-
// ---------------------------------------------------------------------------
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Execute an audit review via Codex CLI `review` subcommand.
|
|
225
|
-
*
|
|
226
|
-
* `codex review` is Codex's built-in read-only code review surface.
|
|
227
|
-
* It inspects the working tree and produces review output without
|
|
228
|
-
* making changes — exactly what an audit lane needs.
|
|
229
|
-
*
|
|
230
|
-
* Constraint: `--uncommitted` and `[PROMPT]` are mutually exclusive
|
|
231
|
-
* in the Codex CLI. We use `--uncommitted` for the working tree scope
|
|
232
|
-
* and `--title` to pass dispatch context.
|
|
233
|
-
*/
|
|
234
|
-
export function executeCodexTransport(
|
|
235
|
-
cliPath: string,
|
|
236
|
-
auditTitle: string,
|
|
237
|
-
workingDirectory: string,
|
|
238
|
-
timeoutMs: number = 600000,
|
|
239
|
-
): TransportResult {
|
|
240
|
-
const start = Date.now();
|
|
241
|
-
|
|
242
|
-
// --title passes dispatch context; --uncommitted scopes to working tree
|
|
243
|
-
const titleSnippet = auditTitle.slice(0, 200);
|
|
244
|
-
try {
|
|
245
|
-
const result = execFileSync(
|
|
246
|
-
cliPath,
|
|
247
|
-
["review", "--uncommitted", "--title", titleSnippet],
|
|
248
|
-
{
|
|
249
|
-
cwd: workingDirectory,
|
|
250
|
-
encoding: "utf-8",
|
|
251
|
-
timeout: timeoutMs,
|
|
252
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
253
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
254
|
-
},
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
return {
|
|
258
|
-
success: true,
|
|
259
|
-
transcript: result,
|
|
260
|
-
exit_code: 0,
|
|
261
|
-
duration_ms: Date.now() - start,
|
|
262
|
-
};
|
|
263
|
-
} catch (e: any) {
|
|
264
|
-
const exitCode = e.status ?? 1;
|
|
265
|
-
const stdout = e.stdout ?? "";
|
|
266
|
-
const stderr = e.stderr ?? "";
|
|
267
|
-
|
|
268
|
-
return {
|
|
269
|
-
success: false,
|
|
270
|
-
transcript: stdout || stderr || e.message || "Unknown transport error",
|
|
271
|
-
exit_code: exitCode,
|
|
272
|
-
error: stderr || e.message,
|
|
273
|
-
duration_ms: Date.now() - start,
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
}
|
package/src/lib/yaml-io.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* YAML read/write helpers for Switchboard CLI.
|
|
3
|
-
*
|
|
4
|
-
* Thin wrappers around the `yaml` package that handle
|
|
5
|
-
* missing files, directory creation, and consistent formatting.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
9
|
-
import { dirname } from "path";
|
|
10
|
-
import YAML from "yaml";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Read and parse a YAML file. Returns null if the file doesn't exist.
|
|
14
|
-
* Throws on parse errors (malformed YAML is a real problem, not a missing-file case).
|
|
15
|
-
*/
|
|
16
|
-
export function readYaml<T = unknown>(filePath: string): T | null {
|
|
17
|
-
if (!existsSync(filePath)) return null;
|
|
18
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
19
|
-
return YAML.parse(raw) as T;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Read a YAML file or throw if it doesn't exist.
|
|
24
|
-
*/
|
|
25
|
-
export function requireYaml<T = unknown>(filePath: string, label: string): T {
|
|
26
|
-
const result = readYaml<T>(filePath);
|
|
27
|
-
if (result === null || result === undefined) {
|
|
28
|
-
throw new Error(`Required file not found: ${filePath} (${label})`);
|
|
29
|
-
}
|
|
30
|
-
return result;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Write a value as YAML. Creates parent directories if needed.
|
|
35
|
-
*/
|
|
36
|
-
export function writeYaml(filePath: string, data: unknown): void {
|
|
37
|
-
const dir = dirname(filePath);
|
|
38
|
-
if (!existsSync(dir)) {
|
|
39
|
-
mkdirSync(dir, { recursive: true });
|
|
40
|
-
}
|
|
41
|
-
const content = YAML.stringify(data, { lineWidth: 120 });
|
|
42
|
-
writeFileSync(filePath, content, "utf-8");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Read a markdown file. Returns null if missing.
|
|
47
|
-
*/
|
|
48
|
-
export function readMarkdown(filePath: string): string | null {
|
|
49
|
-
if (!existsSync(filePath)) return null;
|
|
50
|
-
return readFileSync(filePath, "utf-8");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Write a markdown file. Creates parent directories if needed.
|
|
55
|
-
*/
|
|
56
|
-
export function writeMarkdown(filePath: string, content: string): void {
|
|
57
|
-
const dir = dirname(filePath);
|
|
58
|
-
if (!existsSync(dir)) {
|
|
59
|
-
mkdirSync(dir, { recursive: true });
|
|
60
|
-
}
|
|
61
|
-
writeFileSync(filePath, content, "utf-8");
|
|
62
|
-
}
|