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.
Files changed (79) hide show
  1. package/README.md +122 -0
  2. package/bin/switchboard.mjs +49 -0
  3. package/calibration/engine/baseline.ts +93 -0
  4. package/calibration/engine/diagnosis.ts +191 -0
  5. package/calibration/engine/diff.ts +118 -0
  6. package/calibration/engine/escalation.ts +49 -0
  7. package/calibration/engine/ledger.ts +141 -0
  8. package/calibration/engine/trends.ts +141 -0
  9. package/calibration/external/rubric.yaml +32 -0
  10. package/calibration/external/scorer.ts +479 -0
  11. package/calibration/external/verdict-writer.ts +29 -0
  12. package/calibration/internal/harness.ts +697 -0
  13. package/calibration/internal/return-simulator.ts +270 -0
  14. package/calibration/internal/trace-collector.ts +78 -0
  15. package/calibration/internal/verdict-writer.ts +149 -0
  16. package/calibration/ledger/baselines/baseline-2026-04-09.yaml +23 -0
  17. package/calibration/ledger/history.yaml +18 -0
  18. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/diffs/adv-0bdc944b61d5.yaml +9 -0
  19. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/diffs/blind-16cdf0db1b43.yaml +9 -0
  20. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/diffs/blind-a6b2c8be67cc.yaml +9 -0
  21. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/manifest.yaml +8 -0
  22. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/seeds/adv-0bdc944b61d5.yaml +7 -0
  23. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/seeds/blind-16cdf0db1b43.yaml +8 -0
  24. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/seeds/blind-a6b2c8be67cc.yaml +8 -0
  25. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/summary.yaml +10 -0
  26. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/traces/adv-0bdc944b61d5.yaml +141 -0
  27. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/traces/blind-16cdf0db1b43.yaml +147 -0
  28. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/traces/blind-a6b2c8be67cc.yaml +147 -0
  29. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-a/adv-0bdc944b61d5.yaml +24 -0
  30. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-a/blind-16cdf0db1b43.yaml +24 -0
  31. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-a/blind-a6b2c8be67cc.yaml +25 -0
  32. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-b/adv-0bdc944b61d5.yaml +31 -0
  33. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-b/blind-16cdf0db1b43.yaml +32 -0
  34. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-b/blind-a6b2c8be67cc.yaml +32 -0
  35. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/diffs/adv-a0c9e2bfb0d6.yaml +9 -0
  36. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/diffs/blind-3e892f3a89ee.yaml +9 -0
  37. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/diffs/blind-958b2f9e6816.yaml +9 -0
  38. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/manifest.yaml +8 -0
  39. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/seeds/adv-a0c9e2bfb0d6.yaml +7 -0
  40. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/seeds/blind-3e892f3a89ee.yaml +8 -0
  41. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/seeds/blind-958b2f9e6816.yaml +8 -0
  42. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/summary.yaml +10 -0
  43. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/traces/adv-a0c9e2bfb0d6.yaml +141 -0
  44. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/traces/blind-3e892f3a89ee.yaml +147 -0
  45. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/traces/blind-958b2f9e6816.yaml +147 -0
  46. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-a/adv-a0c9e2bfb0d6.yaml +24 -0
  47. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-a/blind-3e892f3a89ee.yaml +23 -0
  48. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-a/blind-958b2f9e6816.yaml +25 -0
  49. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-b/adv-a0c9e2bfb0d6.yaml +31 -0
  50. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-b/blind-3e892f3a89ee.yaml +32 -0
  51. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-b/blind-958b2f9e6816.yaml +32 -0
  52. package/calibration/seeds/adversarial-generator.ts +159 -0
  53. package/calibration/seeds/blind-generator.ts +169 -0
  54. package/calibration/seeds/replay-loader.ts +117 -0
  55. package/calibration/skill/calibrate.ts +292 -0
  56. package/calibration/skill/cli-flags.ts +49 -0
  57. package/calibration/skill/report.ts +80 -0
  58. package/calibration/skill/review.ts +118 -0
  59. package/calibration/types.ts +292 -0
  60. package/package.json +46 -0
  61. package/src/commands/audit-codex.ts +266 -0
  62. package/src/commands/calibrate.ts +70 -0
  63. package/src/commands/compile.ts +117 -0
  64. package/src/commands/evaluate.ts +103 -0
  65. package/src/commands/ingest.ts +250 -0
  66. package/src/commands/init.ts +133 -0
  67. package/src/commands/packet.ts +408 -0
  68. package/src/commands/receipt.ts +305 -0
  69. package/src/commands/run-claude.ts +355 -0
  70. package/src/index.ts +43 -0
  71. package/src/lib/draft-return.ts +278 -0
  72. package/src/lib/drift-guard.ts +105 -0
  73. package/src/lib/errors.ts +61 -0
  74. package/src/lib/output.ts +43 -0
  75. package/src/lib/paths.ts +125 -0
  76. package/src/lib/proof.ts +262 -0
  77. package/src/lib/transport.ts +276 -0
  78. package/src/lib/yaml-io.ts +62 -0
  79. package/src/store/filesystem-store.ts +326 -0
@@ -0,0 +1,697 @@
1
+ import { randomBytes } from "crypto";
2
+ import type {
3
+ ActivationInput,
4
+ AuditedReceipt,
5
+ CanonicalDelta,
6
+ DriftDiff,
7
+ GateState,
8
+ HandoffRecord,
9
+ NormalizedReturnEnvelope,
10
+ ProjectContract,
11
+ ProjectLedger,
12
+ ReconcileReport,
13
+ ReconcileV2Result,
14
+ ReturnReport,
15
+ RoutingConstraints,
16
+ SynthesizedSeed,
17
+ } from "@switchboard/core";
18
+ import {
19
+ projectContractSchema,
20
+ handoffRecordSchema,
21
+ } from "@switchboard/core";
22
+ import { activateV2 } from "@switchboard/core";
23
+ import { routeV2 } from "@switchboard/core";
24
+ import { reconcileReturn } from "@switchboard/core";
25
+ import { evaluateGates } from "@switchboard/core";
26
+ import { buildAuditedReceipt } from "@switchboard/core";
27
+ import { computeClaimSpread } from "@switchboard/core";
28
+
29
+ import type { CalibrationSeed, PipelineTrace } from "../types";
30
+ import { simulateReturn } from "./return-simulator";
31
+ import { collectTrace } from "./trace-collector";
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Deterministic seed parser — extracts structured seed from raw_idea
35
+ // ---------------------------------------------------------------------------
36
+
37
+ function parseSynthesizedSeed(rawIdea: string): SynthesizedSeed {
38
+ // Title: first sentence or first 80 chars
39
+ const firstSentence = rawIdea.match(/^[^.!?]+[.!?]?/)?.[0] ?? rawIdea.slice(0, 80);
40
+ const title = firstSentence.replace(/[.!?]+$/, "").trim();
41
+
42
+ // One-liner: short distillation
43
+ const oneLiner = title.length > 50
44
+ ? title.slice(0, 50) + "..."
45
+ : title;
46
+
47
+ // V1 outcome: second sentence if available, otherwise derived
48
+ const sentences = rawIdea.split(/[.!?]+/).map(s => s.trim()).filter(Boolean);
49
+ const v1Outcome = sentences.length > 1
50
+ ? sentences[1]!
51
+ : `A working first slice of ${title.toLowerCase()}.`;
52
+
53
+ // Detect task type
54
+ const isImpl = /\b(build|implement|create|add|set up|scaffold|code|develop)\b/i.test(rawIdea);
55
+ const taskType = isImpl ? "implementation-request" as const : "product-idea" as const;
56
+
57
+ // Confidence: more detail = higher confidence
58
+ const confidence = rawIdea.length > 200 ? "high" as const
59
+ : rawIdea.length > 80 ? "medium" as const
60
+ : "low" as const;
61
+
62
+ return {
63
+ title,
64
+ one_liner: oneLiner,
65
+ problem: rawIdea,
66
+ proposed_solution: `Build ${title.toLowerCase()} as a bounded V1 slice.`,
67
+ target_user: "Solo builders shipping software.",
68
+ v1_outcome: v1Outcome,
69
+ non_goals: ["Not a generic chat wrapper"],
70
+ missing_inputs: [],
71
+ open_questions: [],
72
+ first_build_slice: title,
73
+ confidence,
74
+ task_type: taskType,
75
+ };
76
+ }
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // Contract builder
80
+ // ---------------------------------------------------------------------------
81
+
82
+ function buildContract(seed: SynthesizedSeed): ProjectContract {
83
+ return projectContractSchema.parse({
84
+ project_id: `proj-${randomBytes(6).toString("hex")}`,
85
+ title: seed.title,
86
+ one_liner: seed.one_liner,
87
+ problem: seed.problem,
88
+ target_user: seed.target_user,
89
+ desired_v1_outcome: seed.v1_outcome,
90
+ non_goals: seed.non_goals,
91
+ repo_status: "repo-not-present",
92
+ task_clauses: {
93
+ scope_in: [seed.first_build_slice || seed.title],
94
+ scope_out: seed.non_goals,
95
+ systems_expected: [],
96
+ files_allowed: [],
97
+ must_not_widen: seed.non_goals,
98
+ build_constraints: [],
99
+ },
100
+ });
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Calibrated spec builder — 12-section SB_SPEC from seed + contract
105
+ // ---------------------------------------------------------------------------
106
+
107
+ function buildCalibratedSpec(seed: SynthesizedSeed, contract: ProjectContract): string {
108
+ const scopeIn = contract.task_clauses.scope_in.map(s => `- ${s}`).join("\n");
109
+ const scopeOut = contract.non_goals.map(s => `- ${s}`).join("\n");
110
+ const openQs = seed.open_questions.length > 0
111
+ ? seed.open_questions.map(q => `- ${q}`).join("\n")
112
+ : seed.missing_inputs.length > 0
113
+ ? seed.missing_inputs.map(m => `- ${m}`).join("\n")
114
+ : "- None identified.";
115
+ const constraints = contract.task_clauses.build_constraints.length > 0
116
+ ? contract.task_clauses.build_constraints.map(c => `- ${c}`).join("\n")
117
+ : "- No additional constraints.";
118
+ const systems = contract.task_clauses.systems_expected.length > 0
119
+ ? contract.task_clauses.systems_expected.join(", ")
120
+ : "CLI, Node.js";
121
+ const riskLine = seed.confidence === "high"
122
+ ? "Confidence is high — minimal unknowns."
123
+ : seed.confidence === "medium"
124
+ ? "Confidence is medium — some assumptions remain unconfirmed."
125
+ : "Confidence is low — significant unknowns remain.";
126
+
127
+ return `# ${contract.title}
128
+
129
+ ## What this product is
130
+
131
+ ${seed.proposed_solution}
132
+
133
+ ## Who it is for
134
+
135
+ ${seed.target_user}
136
+
137
+ ## The problem
138
+
139
+ ${seed.problem}
140
+
141
+ ## The proposed solution
142
+
143
+ ${seed.proposed_solution} This is a bounded V1 slice focused on proving the core value proposition with minimal surface area.
144
+
145
+ ## V1 promise
146
+
147
+ ${seed.v1_outcome}
148
+
149
+ ## Scope in
150
+
151
+ ${scopeIn}
152
+ - Keep the primary artifact aligned with the V1 promise.
153
+ - Touch only files necessary for the first build slice.
154
+
155
+ ## Scope out
156
+
157
+ ${scopeOut}
158
+
159
+ ## First build slice
160
+
161
+ ${seed.first_build_slice || contract.title}
162
+
163
+ Systems expected: ${systems}.
164
+
165
+ Constraints to respect:
166
+ ${constraints}
167
+
168
+ ## Acceptance criteria
169
+
170
+ - Seed is valid and the idea has been compiled into a governed contract.
171
+ - Primary artifact is execution-ready: the build slice works as described.
172
+ - Next surface is explicit with a return condition.
173
+ - Handoff is launchable as an SBX bundle.
174
+
175
+ ## Open questions
176
+
177
+ ${openQs}
178
+
179
+ ## What must not change (intent anchors)
180
+
181
+ ${scopeOut}
182
+
183
+ These are non-negotiable boundaries. Any execution that touches these areas must be flagged for review.
184
+
185
+ ## Key risks and unknowns
186
+
187
+ ${riskLine}
188
+
189
+ - Is the scope narrow enough to deliver in a single bounded execution?
190
+ - Are there hidden dependencies that could expand the build slice?
191
+ - Return condition: all done_when criteria met or blocker encountered.
192
+ `;
193
+ }
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // Routing constraints builder
197
+ // ---------------------------------------------------------------------------
198
+
199
+ function buildRoutingConstraints(
200
+ seed: SynthesizedSeed,
201
+ mode: string,
202
+ ): RoutingConstraints {
203
+ return {
204
+ capability_needed: "execute",
205
+ process_mode: (mode === "minimal" || mode === "structured" || mode === "governance")
206
+ ? mode as "minimal" | "structured" | "governance"
207
+ : "structured",
208
+ task_type: seed.task_type,
209
+ seed_confidence: seed.confidence,
210
+ repo_exists: false,
211
+ blast_radius: "low",
212
+ non_functional_flags: [],
213
+ provider_readiness: {
214
+ "internal-fallback": true,
215
+ "claude-code-sdk": true,
216
+ "openai-api": false,
217
+ "codex": false,
218
+ "chatgpt-auto": false,
219
+ "chatgpt-manual": false,
220
+ "cursor-manual": false,
221
+ },
222
+ };
223
+ }
224
+
225
+ // ---------------------------------------------------------------------------
226
+ // Handoff builder
227
+ // ---------------------------------------------------------------------------
228
+
229
+ function buildHandoff(
230
+ contract: ProjectContract,
231
+ route: RoutingDecision,
232
+ ): HandoffRecord {
233
+ // Map provider to surface label
234
+ const providerId = route.selected.provider_id;
235
+ const surfaceMap: Record<string, "chatgpt" | "claude-code" | "cursor" | "codex"> = {
236
+ "claude-code-sdk": "claude-code",
237
+ "openai-api": "chatgpt",
238
+ "chatgpt-auto": "chatgpt",
239
+ "chatgpt-manual": "chatgpt",
240
+ "cursor-manual": "cursor",
241
+ "codex": "codex",
242
+ "internal-fallback": "claude-code",
243
+ };
244
+ const surface = surfaceMap[providerId] ?? "claude-code";
245
+
246
+ return handoffRecordSchema.parse({
247
+ handoff_id: `ho-${randomBytes(6).toString("hex")}`,
248
+ surface,
249
+ objective: `Build the first slice: ${contract.title}`,
250
+ included_scope: contract.task_clauses.scope_in.length > 0
251
+ ? contract.task_clauses.scope_in
252
+ : [contract.desired_v1_outcome],
253
+ excluded_scope: contract.non_goals,
254
+ done_when: [`${contract.desired_v1_outcome} is functional and tested.`],
255
+ return_condition: "All done_when criteria met or blocker encountered.",
256
+ loop: {
257
+ objective: `Build the first slice: ${contract.title}`,
258
+ scope_in: contract.task_clauses.scope_in.length > 0
259
+ ? contract.task_clauses.scope_in
260
+ : [contract.desired_v1_outcome],
261
+ scope_out: contract.non_goals,
262
+ done_when: [`${contract.desired_v1_outcome} is functional and tested.`],
263
+ success_criteria: [`${contract.title} works as described.`],
264
+ verify_with: ["Manual testing", "Type check passes"],
265
+ return_when: "All done_when criteria met or blocker encountered.",
266
+ blocker_escalation: ["Escalate if scope grows beyond V1."],
267
+ expected_artifacts: ["src/index.ts", "src/lib/core.ts"],
268
+ fallback_surface: "claude-code",
269
+ execution_shape: "single",
270
+ },
271
+ readiness: {
272
+ status: "ready",
273
+ issues: [],
274
+ },
275
+ bundle_version: "SBX-v1",
276
+ launched_at: new Date().toISOString(),
277
+ });
278
+ }
279
+
280
+ // ---------------------------------------------------------------------------
281
+ // Minimal DriftDiff builder
282
+ // ---------------------------------------------------------------------------
283
+
284
+ function buildMinimalDriftDiff(): DriftDiff {
285
+ return {
286
+ entries: [],
287
+ has_drift: false,
288
+ summary: "No drift detected in calibration run.",
289
+ };
290
+ }
291
+
292
+ // ---------------------------------------------------------------------------
293
+ // Minimal ProjectLedger builder
294
+ // ---------------------------------------------------------------------------
295
+
296
+ function buildMinimalLedger(contract: ProjectContract): ProjectLedger {
297
+ const now = new Date().toISOString();
298
+ return {
299
+ entries: [
300
+ {
301
+ id: `goal-${randomBytes(4).toString("hex")}`,
302
+ kind: "goal",
303
+ text: contract.desired_v1_outcome,
304
+ evidence_type: "user-backed",
305
+ evidence_refs: [],
306
+ parent_ids: [],
307
+ risk_level: "low",
308
+ downstream_artifacts: [],
309
+ created_at: now,
310
+ },
311
+ {
312
+ id: `constraint-${randomBytes(4).toString("hex")}`,
313
+ kind: "constraint",
314
+ text: contract.non_goals[0] ?? "No non-goals specified.",
315
+ evidence_type: "user-backed",
316
+ evidence_refs: [],
317
+ parent_ids: [],
318
+ risk_level: "low",
319
+ downstream_artifacts: [],
320
+ created_at: now,
321
+ },
322
+ ],
323
+ scope_changes: [],
324
+ version: 1,
325
+ };
326
+ }
327
+
328
+ // ---------------------------------------------------------------------------
329
+ // Minimal ReconcileV2Result builder
330
+ // ---------------------------------------------------------------------------
331
+
332
+ function buildMinimalReconcileV2(recommendation: string): ReconcileV2Result {
333
+ return {
334
+ checks: [],
335
+ traceability: [],
336
+ passed: recommendation === "accept",
337
+ blockers: 0,
338
+ warnings: recommendation === "accept" ? 0 : 1,
339
+ recommendation: recommendation === "accept"
340
+ ? "accept"
341
+ : recommendation === "accept-with-warnings"
342
+ ? "accept-with-warnings"
343
+ : "review-required",
344
+ summary: `Reconcile result: ${recommendation}.`,
345
+ };
346
+ }
347
+
348
+ // ---------------------------------------------------------------------------
349
+ // NormalizedReturnEnvelope builder
350
+ // ---------------------------------------------------------------------------
351
+
352
+ function buildEnvelope(
353
+ report: ReturnReport,
354
+ handoff: HandoffRecord,
355
+ ): NormalizedReturnEnvelope {
356
+ const now = new Date().toISOString();
357
+ return {
358
+ envelope_id: `env-${randomBytes(6).toString("hex")}`,
359
+ envelope_version: 1 as const,
360
+ created_at: now,
361
+ source_surface: report.surface_used,
362
+ source_role: "agent",
363
+ objective_used: report.objective_attempted,
364
+ outcome_class: report.status === "done" ? "completed"
365
+ : report.status === "partial" ? "partial"
366
+ : "blocked",
367
+ changed_files: report.changed_files.map(f => ({ path: f, system: "" })),
368
+ touched_artifacts: report.artifacts_created,
369
+ commands_run: [],
370
+ evidence_items: report.evidence.map(e => ({ text: e, content_mode: "direct" as const })),
371
+ verification_runs: report.verification_run.map(v => ({ text: v, content_mode: "direct" as const })),
372
+ criteria_met: report.success_criteria_met.map(c => ({ text: c, content_mode: "direct" as const })),
373
+ decisions: report.decisions_made.map(d => ({ text: d, content_mode: "direct" as const })),
374
+ cautions: report.blockers.map(b => ({ text: b, source: "return" as const })),
375
+ residuals: report.escalations.map(e => ({ text: e, kind: "escalation" as const })),
376
+ recommended_next_step: report.recommended_next_step,
377
+ provenance_posture: "captured",
378
+ confidence_posture: "medium",
379
+ summary: report.summary,
380
+ handoff_id: report.handoff_id,
381
+ dispatch_id: `dsp-${randomBytes(6).toString("hex")}`,
382
+ return_id: `ret-${randomBytes(6).toString("hex")}`,
383
+ };
384
+ }
385
+
386
+ // ---------------------------------------------------------------------------
387
+ // CanonicalDelta builder
388
+ // ---------------------------------------------------------------------------
389
+
390
+ function buildMinimalDelta(
391
+ report: ReturnReport,
392
+ envelope: NormalizedReturnEnvelope,
393
+ ): CanonicalDelta {
394
+ const now = new Date().toISOString();
395
+ return {
396
+ delta_id: `delta-${randomBytes(6).toString("hex")}`,
397
+ project_id: "",
398
+ parent_contract_id: "",
399
+ return_id: envelope.return_id,
400
+ gate_state: "",
401
+ objective_state: report.objective_met === "yes" ? "met"
402
+ : report.objective_met === "partial" ? "partial"
403
+ : "unmet",
404
+ created_at: now,
405
+ implemented: report.changed_files,
406
+ systems_touched: [],
407
+ file_changes: report.changed_files.map(f => ({ path: f, system: "" })),
408
+ artifact_changes: report.artifacts_created,
409
+ tests_or_checks: [],
410
+ verification_runs: report.verification_run,
411
+ evidence_items: report.evidence,
412
+ boundaries_preserved: [],
413
+ boundaries_violated: [],
414
+ cautions: report.blockers,
415
+ unresolved: report.escalations,
416
+ decisions: report.decisions_made.map(d => ({ text: d, traced: false })),
417
+ state_fields_advanced: report.status === "done" ? ["objective_state"] : [],
418
+ summary: report.summary,
419
+ evidence_accounting: {
420
+ scope_check_count: 1,
421
+ scope_check_pass_count: 1,
422
+ scope_check_caution_count: 0,
423
+ scope_check_blocker_count: 0,
424
+ verification_run_count: report.verification_run.length,
425
+ verification_runs: report.verification_run,
426
+ evidence_item_count: report.evidence.length,
427
+ evidence_items: report.evidence,
428
+ file_change_count: report.changed_files.length,
429
+ artifact_change_count: report.artifacts_created.length,
430
+ criteria_met_count: report.success_criteria_met.length,
431
+ criteria_met: report.success_criteria_met,
432
+ caution_count: report.blockers.length,
433
+ unresolved_count: report.escalations.length,
434
+ boundary_preserved_count: 0,
435
+ boundary_violated_count: 0,
436
+ consequence_type: report.status === "done" ? "code-change" : "no-change",
437
+ consequence_label: report.status === "done"
438
+ ? "Code change: first slice delivered."
439
+ : "No meaningful change recorded.",
440
+ },
441
+ verified: [],
442
+ residual_resolutions: [],
443
+ };
444
+ }
445
+
446
+ // ---------------------------------------------------------------------------
447
+ // GateState builder
448
+ // ---------------------------------------------------------------------------
449
+
450
+ function buildGateState(
451
+ report: ReturnReport,
452
+ delta: CanonicalDelta,
453
+ reconcile?: ReconcileReport,
454
+ ): GateState {
455
+ const evidenceCount = report.evidence.length;
456
+ const verificationCount = report.verification_run.length;
457
+ const criteriaCount = report.success_criteria_met.length;
458
+
459
+ // --- Reconcile-aware escalation (takes priority) ---
460
+ if (reconcile) {
461
+ // Rule 1: Explicit review-required recommendation → force held-for-review
462
+ if (reconcile.recommendation === "review-required") {
463
+ const reviewFindings = reconcile.findings.filter(f => f.severity === "review-required");
464
+ return {
465
+ label: "held-for-review",
466
+ reason: `Reconcile review-required: ${reviewFindings.map(f => f.category).join(", ")}.`,
467
+ evidence_summary: {
468
+ verification_count: verificationCount,
469
+ evidence_count: evidenceCount,
470
+ criteria_met_count: criteriaCount,
471
+ caution_count: reconcile.findings.length,
472
+ blocker_count: reviewFindings.length,
473
+ },
474
+ honest: true,
475
+ };
476
+ }
477
+
478
+ // Rule 2: 3+ findings (even all warnings) → accepted-with-cautions minimum
479
+ if (reconcile.findings.length >= 3) {
480
+ return {
481
+ label: "accepted-with-cautions",
482
+ reason: `Reconcile produced ${reconcile.findings.length} findings: ${reconcile.findings.map(f => f.category).join(", ")}.`,
483
+ evidence_summary: {
484
+ verification_count: verificationCount,
485
+ evidence_count: evidenceCount,
486
+ criteria_met_count: criteriaCount,
487
+ caution_count: reconcile.findings.length,
488
+ blocker_count: 0,
489
+ },
490
+ honest: true,
491
+ };
492
+ }
493
+
494
+ // Rule 3: Drift detected with any review-required finding → held-for-review
495
+ if (reconcile.drift_detected && reconcile.findings.some(f => f.severity === "review-required")) {
496
+ return {
497
+ label: "held-for-review",
498
+ reason: `Drift detected with review-required finding.`,
499
+ evidence_summary: {
500
+ verification_count: verificationCount,
501
+ evidence_count: evidenceCount,
502
+ criteria_met_count: criteriaCount,
503
+ caution_count: reconcile.findings.length,
504
+ blocker_count: reconcile.findings.filter(f => f.severity === "review-required").length,
505
+ },
506
+ honest: true,
507
+ };
508
+ }
509
+ }
510
+ // --- End reconcile-aware escalation, fall through to evidence checks ---
511
+
512
+ // Evidence-gap returns get a weaker gate
513
+ if (verificationCount === 0 && evidenceCount === 0) {
514
+ return {
515
+ label: "held-for-review",
516
+ reason: "No verification or evidence provided despite done claim.",
517
+ evidence_summary: {
518
+ verification_count: 0,
519
+ evidence_count: 0,
520
+ criteria_met_count: criteriaCount,
521
+ caution_count: 0,
522
+ blocker_count: 0,
523
+ },
524
+ honest: true,
525
+ };
526
+ }
527
+
528
+ // Thin evidence gets cautions
529
+ if (verificationCount <= 1 && evidenceCount <= 1) {
530
+ return {
531
+ label: "accepted-with-cautions",
532
+ reason: "Minimal evidence provided. Accepted with cautions.",
533
+ evidence_summary: {
534
+ verification_count: verificationCount,
535
+ evidence_count: evidenceCount,
536
+ criteria_met_count: criteriaCount,
537
+ caution_count: 1,
538
+ blocker_count: 0,
539
+ },
540
+ honest: true,
541
+ };
542
+ }
543
+
544
+ // Good evidence
545
+ return {
546
+ label: "accepted",
547
+ reason: "Gate accepted. Evidence supports the claims.",
548
+ evidence_summary: {
549
+ verification_count: verificationCount,
550
+ evidence_count: evidenceCount,
551
+ criteria_met_count: criteriaCount,
552
+ caution_count: 0,
553
+ blocker_count: 0,
554
+ },
555
+ honest: true,
556
+ };
557
+ }
558
+
559
+ // ---------------------------------------------------------------------------
560
+ // Main pipeline runner
561
+ // ---------------------------------------------------------------------------
562
+
563
+ export function runPipeline(seed: CalibrationSeed): {
564
+ trace: PipelineTrace;
565
+ contract: ProjectContract;
566
+ handoff: HandoffRecord;
567
+ receipt: AuditedReceipt | null;
568
+ } {
569
+ const startTime = Date.now();
570
+
571
+ let contract: ProjectContract | null = null;
572
+ let handoff: HandoffRecord | null = null;
573
+ let receipt: AuditedReceipt | null = null;
574
+ let partialTrace: Partial<PipelineTrace> = {
575
+ seed_id: seed.seed_id,
576
+ return_mode: "honest",
577
+ return_mode_sealed: true,
578
+ };
579
+
580
+ try {
581
+ // Step 1: Build ActivationInput
582
+ const activationInput: ActivationInput = {
583
+ raw_idea: seed.raw_idea,
584
+ repo_exists: false,
585
+ what_do_you_want_next: "Build the first slice",
586
+ constraints: "",
587
+ };
588
+
589
+ // Step 2: Parse synthesized seed
590
+ const synthesizedSeed = parseSynthesizedSeed(seed.raw_idea);
591
+
592
+ // Step 3: Activate
593
+ const activationResult = activateV2(activationInput, synthesizedSeed);
594
+ partialTrace.complexity_score = activationResult.mode_scoring.total;
595
+ partialTrace.selected_mode = activationResult.selected_mode;
596
+ partialTrace.assumptions_count = activationResult.assumptions.length;
597
+
598
+ // Step 4: Build contract + calibrated spec
599
+ contract = buildContract(synthesizedSeed);
600
+ const compiledSpec = buildCalibratedSpec(synthesizedSeed, contract);
601
+ partialTrace.compiled_spec = compiledSpec;
602
+
603
+ // Step 5: Route
604
+ const routingConstraints = buildRoutingConstraints(
605
+ synthesizedSeed,
606
+ activationResult.selected_mode,
607
+ );
608
+ const routeDecision = routeV2(routingConstraints);
609
+ partialTrace.routed_surface = routeDecision.selected.provider_id;
610
+ partialTrace.routing_rationale = routeDecision.rationale;
611
+
612
+ // Step 6: Build handoff
613
+ handoff = buildHandoff(contract, routeDecision);
614
+
615
+ // Step 7: Decide return mode
616
+ const returnMode: "honest" | "drift" = Math.random() < 0.5 ? "honest" : "drift";
617
+ partialTrace.return_mode = returnMode;
618
+
619
+ // Step 8: Simulate return
620
+ const report = simulateReturn(returnMode, contract, handoff);
621
+
622
+ // Step 9: Reconcile (with full spec for untracked-decision context matching)
623
+ const reconcileResult = reconcileReturn({
624
+ report,
625
+ contract,
626
+ handoff,
627
+ spec_markdown: compiledSpec,
628
+ });
629
+ partialTrace.reconcile_findings_count = reconcileResult.findings.length;
630
+ partialTrace.reconcile_drift_detected = reconcileResult.drift_detected;
631
+ partialTrace.reconcile_recommendation = reconcileResult.recommendation;
632
+
633
+ // Step 10: Gate evaluation
634
+ const diff = buildMinimalDriftDiff();
635
+ const ledger = buildMinimalLedger(contract);
636
+ const reconcileV2 = buildMinimalReconcileV2(reconcileResult.recommendation);
637
+ const approvalGates = evaluateGates({
638
+ diff,
639
+ ledger,
640
+ reconcile: reconcileV2,
641
+ report,
642
+ });
643
+ partialTrace.gates_triggered = approvalGates.map(g => g.reason);
644
+ partialTrace.gate_label = approvalGates.length > 0
645
+ ? approvalGates.map(g => g.reason).join(", ")
646
+ : "no-gates";
647
+
648
+ // Step 11: Build audited receipt
649
+ const envelope = buildEnvelope(report, handoff);
650
+ const delta = buildMinimalDelta(report, envelope);
651
+ const gate = buildGateState(report, delta, reconcileResult);
652
+
653
+ receipt = buildAuditedReceipt({
654
+ envelope,
655
+ delta,
656
+ gate,
657
+ nextSlice: null,
658
+ });
659
+
660
+ partialTrace.trust_posture = receipt.trust_posture;
661
+ partialTrace.gate_label = gate.label; // Use the reconcile-aware gate label
662
+ partialTrace.receipt_verdict = receipt.gate_label;
663
+ partialTrace.honesty_violations = receipt.audit_cautions;
664
+
665
+ // Step 12: Claim spread
666
+ const spread = computeClaimSpread(receipt);
667
+ partialTrace.spread_score = spread.spread_score;
668
+ partialTrace.spread_vector = spread.vector;
669
+
670
+ // Step 13: Build final trace
671
+ partialTrace.outcome = "completed";
672
+ partialTrace.receipt_summary = receipt.summary_claimed.slice(0, 200);
673
+ partialTrace.duration_ms = Date.now() - startTime;
674
+
675
+ const trace = collectTrace(seed, partialTrace, startTime);
676
+ return { trace, contract, handoff, receipt };
677
+
678
+ } catch (error: unknown) {
679
+ // Capture error into trace
680
+ const errorMessage = error instanceof Error ? error.message : String(error);
681
+ partialTrace.outcome = "error";
682
+ partialTrace.rejection_point = "pipeline";
683
+ partialTrace.rejection_reason = errorMessage.slice(0, 200);
684
+ partialTrace.duration_ms = Date.now() - startTime;
685
+
686
+ const trace = collectTrace(seed, partialTrace, startTime);
687
+ return {
688
+ trace,
689
+ contract: contract ?? buildContract(parseSynthesizedSeed(seed.raw_idea)),
690
+ handoff: handoff ?? buildHandoff(
691
+ contract ?? buildContract(parseSynthesizedSeed(seed.raw_idea)),
692
+ { selected: { provider_id: "internal-fallback", score: 0, disqualified: false, disqualification_reason: "", verification_path: "none" }, candidates: [], rationale: "Fallback due to error.", constraints_applied: [], verification_path: "none" },
693
+ ),
694
+ receipt: null,
695
+ };
696
+ }
697
+ }