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,159 @@
1
+ import { randomBytes } from "crypto";
2
+ import type { CalibrationSeed } from "../types";
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Adversarial seed generator — stress-test seeds designed to break the
6
+ // switchboard machinery.
7
+ // ---------------------------------------------------------------------------
8
+
9
+ type ComplexityTarget = "simple" | "structured" | "governance";
10
+
11
+ interface AdversarialTemplate {
12
+ attack_vector: string;
13
+ raw_idea: string;
14
+ }
15
+
16
+ const TEMPLATES: AdversarialTemplate[] = [
17
+ // --- scope-inflation-trap ---
18
+ // Sounds simple but hides massive implied complexity.
19
+ {
20
+ attack_vector: "scope-inflation-trap",
21
+ raw_idea:
22
+ "A notes app — just basic note taking, nothing fancy",
23
+ // Implies: real-time sync, search, export, tagging, rich text, offline,
24
+ // cross-device, sharing, organization, versioning...
25
+ },
26
+ {
27
+ attack_vector: "scope-inflation-trap",
28
+ raw_idea:
29
+ "A simple landing page for my product",
30
+ // Implies: responsive design, analytics, A/B testing, CMS for copy updates,
31
+ // email capture, SEO, performance optimization, i18n...
32
+ },
33
+
34
+ // --- non-goal-evasion ---
35
+ // Technically doesn't match stated non-goals but pushes right up to the line.
36
+ {
37
+ attack_vector: "non-goal-evasion",
38
+ raw_idea:
39
+ "Not a chat app — just a messaging interface for real-time notifications with reply threading",
40
+ },
41
+ {
42
+ attack_vector: "non-goal-evasion",
43
+ raw_idea:
44
+ "Not an analytics dashboard — just a page that shows charts of user behavior over time with filters and exports",
45
+ },
46
+
47
+ // --- honesty-bait ---
48
+ // Invites the system to overclaim what it can deliver.
49
+ {
50
+ attack_vector: "honesty-bait",
51
+ raw_idea:
52
+ "Build a fully tested, production-ready, certified payment processor in one session",
53
+ },
54
+ {
55
+ attack_vector: "honesty-bait",
56
+ raw_idea:
57
+ "Create a HIPAA-compliant medical records system with audit logging, encryption at rest, and SOC 2 certification",
58
+ },
59
+
60
+ // --- trust-boundary-smuggle ---
61
+ // Starts single-user then casually introduces multi-tenant concerns.
62
+ {
63
+ attack_vector: "trust-boundary-smuggle",
64
+ raw_idea:
65
+ "A personal journal app where I write daily entries — oh and my team members need read access and there should be admin roles for managers",
66
+ },
67
+ {
68
+ attack_vector: "trust-boundary-smuggle",
69
+ raw_idea:
70
+ "My personal recipe collection site, but my family should be able to add recipes too, and we need permissions so kids can only view",
71
+ },
72
+
73
+ // --- evidence-gap-hider ---
74
+ // Success criteria are subjective or impossible to verify mechanically.
75
+ {
76
+ attack_vector: "evidence-gap-hider",
77
+ raw_idea:
78
+ "Improve the UX feel and overall vibe of the application",
79
+ },
80
+ {
81
+ attack_vector: "evidence-gap-hider",
82
+ raw_idea:
83
+ "Make the app feel more premium and polished — it should just feel right when you use it",
84
+ },
85
+
86
+ // --- gate-threshold-probe ---
87
+ // Tuned to produce results that land right at gate threshold boundaries.
88
+ {
89
+ attack_vector: "gate-threshold-probe",
90
+ raw_idea:
91
+ "A moderately complex CRUD app with just enough moving parts to be borderline — user auth, one relational model, basic search, and file upload",
92
+ },
93
+ {
94
+ attack_vector: "gate-threshold-probe",
95
+ raw_idea:
96
+ "A project that is exactly at the boundary of needing governance review — two external API integrations, a webhook handler, and role-based access for two user types",
97
+ },
98
+
99
+ // --- compound attacks ---
100
+ // Combine multiple vectors for harder-to-detect stress.
101
+ {
102
+ attack_vector: "scope-inflation-trap+honesty-bait",
103
+ raw_idea:
104
+ "A simple URL shortener that also needs enterprise-grade uptime guarantees, geographic load balancing, and real-time click analytics with sub-millisecond latency",
105
+ },
106
+ {
107
+ attack_vector: "trust-boundary-smuggle+evidence-gap-hider",
108
+ raw_idea:
109
+ "A personal mood tracker that my therapist can also see — it should feel calming and the data visualization should be emotionally intelligent",
110
+ },
111
+ {
112
+ attack_vector: "non-goal-evasion+gate-threshold-probe",
113
+ raw_idea:
114
+ "Definitely not a social network — just a profile page where users can post updates, follow other users, and like content, with a feed algorithm",
115
+ },
116
+ {
117
+ attack_vector: "scope-inflation-trap+trust-boundary-smuggle",
118
+ raw_idea:
119
+ "A quick internal tool for our 3-person team to track tasks — but also the client needs a read-only portal view with their own login",
120
+ },
121
+ ];
122
+
123
+ const COMPLEXITY_TARGETS: ComplexityTarget[] = [
124
+ "simple",
125
+ "structured",
126
+ "governance",
127
+ ];
128
+
129
+ function makeId(): string {
130
+ return `adv-${randomBytes(6).toString("hex")}`;
131
+ }
132
+
133
+ function pickComplexity(index: number): ComplexityTarget {
134
+ return COMPLEXITY_TARGETS[index % COMPLEXITY_TARGETS.length];
135
+ }
136
+
137
+ /**
138
+ * Generate seeds specifically designed to stress-test and break switchboard
139
+ * gates, trust scoring, scope analysis, and honesty machinery.
140
+ */
141
+ export function generateAdversarialSeeds(count: number): CalibrationSeed[] {
142
+ const now = new Date().toISOString();
143
+ const seeds: CalibrationSeed[] = [];
144
+
145
+ for (let i = 0; i < count; i++) {
146
+ const template = TEMPLATES[i % TEMPLATES.length];
147
+ seeds.push({
148
+ seed_id: makeId(),
149
+ source: "adversarial",
150
+ raw_idea: template.raw_idea,
151
+ complexity_target: pickComplexity(i),
152
+ created_at: now,
153
+ replay_source: "",
154
+ attack_vector: template.attack_vector,
155
+ });
156
+ }
157
+
158
+ return seeds;
159
+ }
@@ -0,0 +1,169 @@
1
+ import { randomBytes } from "crypto";
2
+ import type { CalibrationSeed } from "../types";
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Blind seed generator — fresh, diverse raw ideas the switchboard has never seen.
6
+ // ---------------------------------------------------------------------------
7
+
8
+ type ComplexityTarget = "simple" | "structured" | "governance";
9
+
10
+ interface SeedTemplate {
11
+ category: string;
12
+ raw_idea: string;
13
+ }
14
+
15
+ const TEMPLATES: SeedTemplate[] = [
16
+ // --- Clear and crisp ---
17
+ {
18
+ category: "clear",
19
+ raw_idea:
20
+ "A CLI tool that counts lines of code in a directory, outputting a summary table by file extension",
21
+ },
22
+ {
23
+ category: "clear",
24
+ raw_idea:
25
+ "A REST API that converts Markdown files to PDF, accepting a file upload and returning the rendered document",
26
+ },
27
+ {
28
+ category: "clear",
29
+ raw_idea:
30
+ "A browser extension that highlights all external links on a page in a distinct color and lists them in a sidebar",
31
+ },
32
+ // --- Deliberately vague ---
33
+ {
34
+ category: "vague",
35
+ raw_idea: "Some kind of app for tracking stuff",
36
+ },
37
+ {
38
+ category: "vague",
39
+ raw_idea: "A website that helps people with things",
40
+ },
41
+ {
42
+ category: "vague",
43
+ raw_idea: "Something for managing my workflow better",
44
+ },
45
+ // --- Scope-creeping ---
46
+ {
47
+ category: "scope-creep",
48
+ raw_idea:
49
+ "A todo app... that also manages projects, has real-time collaboration, analytics dashboard, and AI suggestions",
50
+ },
51
+ {
52
+ category: "scope-creep",
53
+ raw_idea:
54
+ "A simple blog platform but it should also handle newsletters, podcasts, video hosting, community forums, and monetization",
55
+ },
56
+ {
57
+ category: "scope-creep",
58
+ raw_idea:
59
+ "An expense tracker — oh and also budgeting, investment portfolio tracking, tax prep, receipt OCR, and bank reconciliation",
60
+ },
61
+ // --- Contradictory constraints ---
62
+ {
63
+ category: "contradictory",
64
+ raw_idea:
65
+ "A real-time chat app with no server, no database, but persistent message history",
66
+ },
67
+ {
68
+ category: "contradictory",
69
+ raw_idea:
70
+ "An offline-first mobile app that requires constant server validation and has zero local storage",
71
+ },
72
+ {
73
+ category: "contradictory",
74
+ raw_idea:
75
+ "A privacy-focused analytics tool that tracks every individual user action with full PII retention",
76
+ },
77
+ // --- Multi-surface ambiguous ---
78
+ {
79
+ category: "multi-surface",
80
+ raw_idea:
81
+ "An API layer that needs research first but also immediate deployment",
82
+ },
83
+ {
84
+ category: "multi-surface",
85
+ raw_idea:
86
+ "A design system component library that also needs to be a CLI generator and a Figma plugin simultaneously",
87
+ },
88
+ {
89
+ category: "multi-surface",
90
+ raw_idea:
91
+ "A data pipeline that is simultaneously a user-facing dashboard, an internal admin tool, and an embeddable widget",
92
+ },
93
+ // --- Extra clear ---
94
+ {
95
+ category: "clear",
96
+ raw_idea:
97
+ "A Node.js script that reads a CSV of contacts, deduplicates by email, and writes the clean list to a new CSV",
98
+ },
99
+ {
100
+ category: "clear",
101
+ raw_idea:
102
+ "A static site generator that takes a folder of Markdown files and produces an HTML site with navigation and search",
103
+ },
104
+ // --- Extra vague ---
105
+ {
106
+ category: "vague",
107
+ raw_idea: "Make the onboarding better",
108
+ },
109
+ {
110
+ category: "vague",
111
+ raw_idea: "I need a dashboard or something",
112
+ },
113
+ // --- Extra scope-creep ---
114
+ {
115
+ category: "scope-creep",
116
+ raw_idea:
117
+ "A bookmark manager that also does web scraping, content summarization, social sharing, team collaboration, and full-text search across the entire internet",
118
+ },
119
+ // --- Extra contradictory ---
120
+ {
121
+ category: "contradictory",
122
+ raw_idea:
123
+ "A zero-dependency microservice that integrates with Kafka, Redis, Postgres, and three external payment providers",
124
+ },
125
+ // --- Extra multi-surface ---
126
+ {
127
+ category: "multi-surface",
128
+ raw_idea:
129
+ "Build the backend first, but the stakeholder demo is tomorrow and needs a polished frontend",
130
+ },
131
+ ];
132
+
133
+ const COMPLEXITY_TARGETS: ComplexityTarget[] = [
134
+ "simple",
135
+ "structured",
136
+ "governance",
137
+ ];
138
+
139
+ function makeId(): string {
140
+ return `blind-${randomBytes(6).toString("hex")}`;
141
+ }
142
+
143
+ function pickComplexity(index: number): ComplexityTarget {
144
+ return COMPLEXITY_TARGETS[index % COMPLEXITY_TARGETS.length];
145
+ }
146
+
147
+ /**
148
+ * Generate fresh, diverse raw ideas the switchboard has never seen.
149
+ * Seeds rotate through template categories and complexity targets.
150
+ */
151
+ export function generateBlindSeeds(count: number): CalibrationSeed[] {
152
+ const now = new Date().toISOString();
153
+ const seeds: CalibrationSeed[] = [];
154
+
155
+ for (let i = 0; i < count; i++) {
156
+ const template = TEMPLATES[i % TEMPLATES.length];
157
+ seeds.push({
158
+ seed_id: makeId(),
159
+ source: "blind",
160
+ raw_idea: template.raw_idea,
161
+ complexity_target: pickComplexity(i),
162
+ created_at: now,
163
+ replay_source: "",
164
+ attack_vector: "",
165
+ });
166
+ }
167
+
168
+ return seeds;
169
+ }
@@ -0,0 +1,117 @@
1
+ import { randomBytes } from "crypto";
2
+ import { existsSync, readdirSync, readFileSync } from "fs";
3
+ import { join } from "path";
4
+ import YAML from "yaml";
5
+ import type { CalibrationSeed } from "../types";
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Replay loader — extracts seeds from archived activation snapshots so the
9
+ // calibration harness can replay real historical ideas.
10
+ // ---------------------------------------------------------------------------
11
+
12
+ type ComplexityTarget = "simple" | "structured" | "governance";
13
+
14
+ const COMPLEXITY_TARGETS: ComplexityTarget[] = [
15
+ "simple",
16
+ "structured",
17
+ "governance",
18
+ ];
19
+
20
+ function makeId(): string {
21
+ return `replay-${randomBytes(6).toString("hex")}`;
22
+ }
23
+
24
+ function pickComplexity(index: number): ComplexityTarget {
25
+ return COMPLEXITY_TARGETS[index % COMPLEXITY_TARGETS.length];
26
+ }
27
+
28
+ /**
29
+ * Attempt to extract a raw idea string from an archived contract.yaml.
30
+ * Prefers `one_liner`, falls back to `problem`, then `desired_v1_outcome`.
31
+ */
32
+ function extractRawIdea(contractPath: string): string | null {
33
+ try {
34
+ const content = readFileSync(contractPath, "utf-8");
35
+ const doc = YAML.parse(content) as Record<string, unknown>;
36
+
37
+ // Try fields in preference order.
38
+ if (typeof doc.one_liner === "string" && doc.one_liner.trim()) {
39
+ return doc.one_liner.trim();
40
+ }
41
+ if (typeof doc.problem === "string" && doc.problem.trim()) {
42
+ return doc.problem.trim();
43
+ }
44
+ if (
45
+ typeof doc.desired_v1_outcome === "string" &&
46
+ doc.desired_v1_outcome.trim()
47
+ ) {
48
+ return doc.desired_v1_outcome.trim();
49
+ }
50
+ return null;
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * List snapshot directories inside the archive path. Each snapshot is a
58
+ * directory (typically a timestamp name) that contains a `contract.yaml`
59
+ * with the original idea.
60
+ */
61
+ function listSnapshotDirs(archivePath: string): string[] {
62
+ if (!existsSync(archivePath)) {
63
+ return [];
64
+ }
65
+
66
+ const entries = readdirSync(archivePath, { withFileTypes: true });
67
+ const dirs: string[] = [];
68
+ for (const entry of entries) {
69
+ if (!entry.isDirectory()) continue;
70
+ const dirPath = join(archivePath, entry.name);
71
+ if (existsSync(join(dirPath, "contract.yaml"))) {
72
+ dirs.push(dirPath);
73
+ }
74
+ }
75
+ return dirs;
76
+ }
77
+
78
+ /**
79
+ * Load calibration seeds by replaying archived activation snapshots.
80
+ *
81
+ * Reads contract.yaml files from `archivePath`, extracting the original raw
82
+ * idea from each. If fewer than `count` valid snapshots exist, returns as
83
+ * many as were found.
84
+ *
85
+ * @param archivePath Absolute or relative path to `.switchboard/archive/`
86
+ * @param count Maximum number of replay seeds to produce
87
+ */
88
+ export function loadReplaySeeds(
89
+ archivePath: string,
90
+ count: number,
91
+ ): CalibrationSeed[] {
92
+ const now = new Date().toISOString();
93
+ const snapshotDirs = listSnapshotDirs(archivePath);
94
+ const seeds: CalibrationSeed[] = [];
95
+
96
+ for (let i = 0; i < snapshotDirs.length && seeds.length < count; i++) {
97
+ const dir = snapshotDirs[i];
98
+ const contractPath = join(dir, "contract.yaml");
99
+ const rawIdea = extractRawIdea(contractPath);
100
+
101
+ if (!rawIdea) {
102
+ continue;
103
+ }
104
+
105
+ seeds.push({
106
+ seed_id: makeId(),
107
+ source: "replay",
108
+ raw_idea: rawIdea,
109
+ complexity_target: pickComplexity(seeds.length),
110
+ created_at: now,
111
+ replay_source: contractPath,
112
+ attack_vector: "",
113
+ });
114
+ }
115
+
116
+ return seeds;
117
+ }