@azure-id/orc 0.56.0 → 1.0.0

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 (153) hide show
  1. package/CHANGELOG.md +228 -0
  2. package/README-id.md +143 -53
  3. package/README.md +124 -103
  4. package/bin/build-agents.js +18 -11
  5. package/bin/cli.js +4139 -315
  6. package/bin/onboarding-content.js +0 -3
  7. package/bin/test-run.js +491 -0
  8. package/bin/verify-contracts.js +4256 -3277
  9. package/bin/verify-package.js +563 -498
  10. package/bin/webui/api.js +1186 -1163
  11. package/bin/webui/app.html +9 -0
  12. package/bin/webui/css/06-responsive.css +120 -108
  13. package/bin/webui/css/panels/extra.css +29 -0
  14. package/bin/webui/css/panels/lanes.css +134 -0
  15. package/bin/webui/css/panels/settings.css +79 -3
  16. package/bin/webui/fixtures/extra.js +2036 -1934
  17. package/bin/webui/fixtures/flow.js +2 -2
  18. package/bin/webui/fixtures/index.js +503 -486
  19. package/bin/webui/fixtures/lanes.js +218 -0
  20. package/bin/webui/fixtures/settings.js +261 -34
  21. package/bin/webui/fixtures/stats.js +3 -3
  22. package/bin/webui/i18n/TERMS.md +135 -134
  23. package/bin/webui/i18n/en/extra.json +345 -313
  24. package/bin/webui/i18n/en/lanes.json +29 -0
  25. package/bin/webui/i18n/en/nav.json +1 -0
  26. package/bin/webui/i18n/en/overview.json +2 -0
  27. package/bin/webui/i18n/en/settings.json +16 -5
  28. package/bin/webui/i18n/id/extra.json +345 -313
  29. package/bin/webui/i18n/id/lanes.json +29 -0
  30. package/bin/webui/i18n/id/nav.json +1 -0
  31. package/bin/webui/i18n/id/overview.json +2 -0
  32. package/bin/webui/i18n/id/settings.json +15 -4
  33. package/bin/webui/js/01-i18n.js +1 -0
  34. package/bin/webui/js/panels/extra.js +3199 -3011
  35. package/bin/webui/js/panels/lanes.js +259 -0
  36. package/bin/webui/js/panels/overview.js +10 -0
  37. package/bin/webui/js/panels/settings.js +149 -3
  38. package/package.json +3 -3
  39. package/templates/agents/MODEL-MAPPING.md +23 -35
  40. package/templates/agents/orc-executor-opus-4-7-high.md +1 -1
  41. package/templates/agents/orc-executor-opus-4-7-med.md +1 -1
  42. package/templates/agents/orc-executor-opus-4-8-high.md +1 -1
  43. package/templates/agents/orc-executor-opus-5-high.md +1 -1
  44. package/templates/agents/orc-executor-opus-5-low.md +1 -1
  45. package/templates/agents/orc-executor-opus-5-med.md +1 -1
  46. package/templates/agents/orc-retro-opus-5-med.md +73 -73
  47. package/templates/agents/orc-retro-sonnet-5-high.md +72 -72
  48. package/templates/agents/orc-trace-writer-haiku-4-5.md +107 -107
  49. package/templates/commands/orc-route.md +1 -1
  50. package/templates/commands/orc.md +1 -1
  51. package/templates/skills/_shared/README.md +7 -3
  52. package/templates/skills/_shared/config-precedence.md +198 -0
  53. package/templates/skills/_shared/drift-recovery.md +1 -1
  54. package/templates/skills/_shared/extra-dispatch.md +292 -6
  55. package/templates/skills/_shared/interview.md +1 -1
  56. package/templates/skills/_shared/opus5-only.md +9 -7
  57. package/templates/skills/_shared/phases/README.md +82 -0
  58. package/templates/skills/{orc/references → _shared/phases}/analyst-gates.md +15 -3
  59. package/templates/skills/_shared/phases/execution.md +143 -0
  60. package/templates/skills/{orc/references → _shared/phases}/house-rules.md +13 -1
  61. package/templates/skills/{orc/references → _shared/phases}/intake.md +15 -2
  62. package/templates/skills/_shared/phases/mock-example.md +56 -0
  63. package/templates/skills/{orc/references → _shared/phases}/plan-handoff.md +16 -3
  64. package/templates/skills/_shared/phases/planning.md +135 -0
  65. package/templates/skills/_shared/phases/preflight.md +76 -0
  66. package/templates/skills/_shared/phases/review.md +53 -0
  67. package/templates/skills/_shared/phases/scoring.md +65 -0
  68. package/templates/skills/{orc/references → _shared/phases}/security-checklist.md +12 -1
  69. package/templates/skills/_shared/phases/security.md +55 -0
  70. package/templates/skills/_shared/phases/ship.md +88 -0
  71. package/templates/skills/{orc/references/stop-and-resume.md → _shared/phases/stop-resume.md} +169 -160
  72. package/templates/skills/_shared/phases/summary.md +48 -0
  73. package/templates/skills/_shared/phases/testgen.md +51 -0
  74. package/templates/skills/{orc/references/trace-protocol.md → _shared/phases/trace.md} +49 -6
  75. package/templates/skills/_shared/phases/verify.md +70 -0
  76. package/templates/skills/{orc/references → _shared/phases}/wave-grouping.md +14 -2
  77. package/templates/skills/{orc/references → _shared/phases}/wiki-consult.md +13 -1
  78. package/templates/skills/_shared/read-ladder.md +48 -0
  79. package/templates/skills/context-combiner/SKILL.md +214 -187
  80. package/templates/skills/orc/README.md +148 -150
  81. package/templates/skills/orc/SKILL.md +70 -358
  82. package/templates/skills/orc/config.md +137 -389
  83. package/templates/skills/orc/examples/full-run-mock.md +1 -1
  84. package/templates/skills/orc/references/effort-and-mode.md +16 -18
  85. package/templates/skills/orc/references/phases/intake.md +47 -0
  86. package/templates/skills/orc/references/phases/integration.md +19 -0
  87. package/templates/skills/orc/references/preflight-report.md +4 -4
  88. package/templates/skills/orc/references/ultra-mode.md +117 -123
  89. package/templates/skills/orc/schemas/planning-output.md +279 -279
  90. package/templates/skills/orc/subskills/orc-planner/SKILL.md +260 -264
  91. package/templates/skills/orc/subskills/orc-pr/stack-gate.md +4 -1
  92. package/templates/skills/orc-aftermath/SKILL.md +153 -136
  93. package/templates/skills/orc-analyze/SKILL.md +247 -220
  94. package/templates/skills/orc-analyze/references/branching.md +51 -51
  95. package/templates/skills/orc-analyze/references/thin-input.md +1 -1
  96. package/templates/skills/orc-analyze-mini/SKILL.md +128 -112
  97. package/templates/skills/orc-boundary/SKILL.md +244 -208
  98. package/templates/skills/orc-brainstorm/SKILL.md +372 -339
  99. package/templates/skills/orc-budget/SKILL.md +243 -225
  100. package/templates/skills/orc-challenge/README.md +1 -1
  101. package/templates/skills/orc-challenge/SKILL.md +293 -255
  102. package/templates/skills/orc-challenge/examples/council-full-roster.md +1 -1
  103. package/templates/skills/orc-claude/SKILL.md +216 -199
  104. package/templates/skills/orc-diy/README.md +2 -1
  105. package/templates/skills/orc-diy/SKILL.md +129 -70
  106. package/templates/skills/orc-diy/references/blocks/analyze.md +1 -1
  107. package/templates/skills/orc-diy/references/compile.md +21 -7
  108. package/templates/skills/orc-diy/references/flow-schema.md +2 -2
  109. package/templates/skills/orc-doc/SKILL.md +44 -7
  110. package/templates/skills/orc-explain/SKILL.md +19 -0
  111. package/templates/skills/orc-export/SKILL.md +26 -5
  112. package/templates/skills/orc-fast/SKILL.md +215 -187
  113. package/templates/skills/orc-grill/SKILL.md +233 -207
  114. package/templates/skills/orc-handoff/SKILL.md +223 -204
  115. package/templates/skills/orc-learn/SKILL.md +176 -156
  116. package/templates/skills/orc-mini/SKILL.md +41 -23
  117. package/templates/skills/orc-pact/SKILL.md +250 -218
  118. package/templates/skills/orc-pattern/SKILL.md +149 -124
  119. package/templates/skills/orc-poly/SKILL.md +237 -216
  120. package/templates/skills/orc-poly/references/gather.md +1 -1
  121. package/templates/skills/orc-pr-driver/SKILL.md +196 -167
  122. package/templates/skills/orc-pr-driver/references/orc-run-split.md +1 -1
  123. package/templates/skills/orc-pr-setup/SKILL.md +212 -184
  124. package/templates/skills/orc-quick/README.md +2 -2
  125. package/templates/skills/orc-quick/SKILL.md +346 -313
  126. package/templates/skills/orc-quick/references/dispatch-gate.md +12 -4
  127. package/templates/skills/orc-retro/SKILL.md +244 -222
  128. package/templates/skills/orc-retro/examples/retro-mock.md +171 -171
  129. package/templates/skills/orc-route/SKILL.md +183 -165
  130. package/templates/skills/orc-verify/SKILL.md +100 -86
  131. package/templates/skills/orc-wiki/SKILL.md +55 -206
  132. package/templates/skills/orc-wiki/references/phases/phase-0.md +71 -0
  133. package/templates/skills/orc-wiki/references/phases/phase-1.md +35 -0
  134. package/templates/skills/orc-wiki/references/phases/phase-2.md +52 -0
  135. package/templates/skills/orc-wiki/references/phases/phase-3.md +57 -0
  136. package/templates/skills/orc-wiki/references/phases/phase-3c.md +36 -0
  137. package/templates/agents/orc-advisor-fable-5.md +0 -50
  138. package/templates/agents/orc-analyst-fable-5.md +0 -115
  139. package/templates/agents/orc-judge-fable-5.md +0 -79
  140. package/templates/agents/orc-planner-fable-5.md +0 -152
  141. package/templates/agents/orc-reviewer-fable-5.md +0 -57
  142. package/templates/skills/_shared/fable5-override.md +0 -56
  143. package/templates/skills/orc-diy/references/blocks/execution.md +0 -42
  144. package/templates/skills/orc-diy/references/blocks/mock-example.md +0 -22
  145. package/templates/skills/orc-diy/references/blocks/planning.md +0 -34
  146. package/templates/skills/orc-diy/references/blocks/review.md +0 -18
  147. package/templates/skills/orc-diy/references/blocks/scoring.md +0 -16
  148. package/templates/skills/orc-diy/references/blocks/security.md +0 -25
  149. package/templates/skills/orc-diy/references/blocks/ship.md +0 -25
  150. package/templates/skills/orc-diy/references/blocks/summary.md +0 -18
  151. package/templates/skills/orc-diy/references/blocks/testgen.md +0 -17
  152. package/templates/skills/orc-diy/references/blocks/trace.md +0 -28
  153. package/templates/skills/orc-diy/references/blocks/verify.md +0 -25
@@ -1,486 +1,503 @@
1
- "use strict";
2
- /**
3
- * fixtures.js — canned API responses for `orc ui --fixtures`.
4
- *
5
- * This exists because you cannot DESIGN a state you cannot reach. On a healthy
6
- * install with a fresh wiki and no paused runs, the STALE chip, the `waiting`
7
- * run card, the shadowed-setting lock and the unhealthy doctor panel are
8
- * unreachable — so they get built once, blind, and never looked at again.
9
- *
10
- * The rule for this file: carry ONE OF EVERY STATE, including the ugly ones.
11
- * It also means the UI can be worked on with no ORC project at all.
12
- *
13
- * Shapes MUST match what `bin/cli.js --json` really emits. A fixture that has
14
- * drifted from the CLI is worse than no fixture, so test/webui.test.js asserts
15
- * the fixture key set against the live CLI output for the shared routes.
16
- */
17
-
18
-
19
- // v0.48.1 — the data lives one file per panel; this file is the ROUTER and
20
- // nothing else. Adding a fixture is a new key in the panel's own file plus a
21
- // case here; it is never an edit to a 1 700-line module.
22
- const { PROJECT, doctor, where } = require("./shell.js");
23
- const { config } = require("./settings.js");
24
- const { wiki, wikiDocs, wikiShow, wikiCoverage, wikiCoverageFull, wikiUnregistered, wikiPlan, wikiDebt, wikiUsage, patterns, patternShow, gotchas, gotchasArchived, gotchaPrunePreview, wikiImpact } = require("./knowledge.js");
25
- const { runs, runDetail, runDetailClosed, aftermath } = require("./runs.js");
26
- const { stats, budgetForecast, budgetRates } = require("./stats.js");
27
- const { pact } = require("./pact.js");
28
- const { boundary } = require("./boundary.js");
29
- const { handoff } = require("./handoff.js");
30
- const { exportState, mocks } = require("./maintenance.js");
31
- const { chGoals, chDims, challengeRoles, challengeCouncil, challengeCycles, challengeList, challengeShow, challengeDiff, challengeDiffMissing, challengeLint } = require("./challenge.js");
32
- const { docList, docParts, docStatuses, docMapSections, docMap, docLint, docPlan, docShow, docSection, docShipped, docShippedDrifted, docNext, docAudit, docJournalRich, docJournalEmpty, docContext, docRules, docRulesFrozen, docForecast, docCost } = require("./docs.js");
33
- const { diy } = require("./flow.js");
34
- const { crosslink } = require("./crosslink.js");
35
- const { mockDetail } = require("./mockrun.js");
36
- const { extraProviders, extraList, extraListNoConnection, extraListNeverTested, extraTools, extraKeyhelp, extraModels, extraDoctor, extraRoute, extraRole, extraLanes, extraStats, extraRates, extraPingOk, extraPingBad, extraPingSaveOffer, extraJournal, extraReconcile, extraJournalPrune, extraPingLive, extraPingDeadModel, extraPingNotInstalled, extraInstall } = require("./extra.js");
37
-
38
- module.exports.get = function get(route, q) {
39
- switch (route) {
40
- case "/api/doc":
41
- return docList;
42
- case "/api/doc/one":
43
- // The two SHIPPED states overlay the plain ones, so `shipped`,
44
- // `shipped-drifted` and a FORCED ship are all reachable in the panel.
45
- return (
46
- { [docShipped.slug]: docShipped, [docShippedDrifted.slug]: docShippedDrifted }[q && q.slug] ||
47
- (q && docStatuses[q.slug]) ||
48
- docStatuses["prd-checkout-refund-130826"]
49
- );
50
- case "/api/doc/show":
51
- // The memory fields ride on whichever slug was asked for, so the header
52
- // strip, the brief and the journal are all designable per state.
53
- return {
54
- ...docShow,
55
- slug: (q && q.slug) || docShow.slug,
56
- created_at: "13-08-2026 09:02:11",
57
- last_touched_at: "15-08-2026 17:05:00",
58
- sessions: 3,
59
- context: (docContext[q && q.slug] || docContext["prd-checkout-refund-130826"]).context,
60
- journal: (q && q.slug === "collab-risk-and-payments-130826" ? docJournalEmpty : docJournalRich).journal,
61
- shipped: (docStatuses[q && q.slug] || {}).shipped || null,
62
- ship_history: [],
63
- };
64
- case "/api/doc/map":
65
- return docMap;
66
- case "/api/doc/lint":
67
- return docLint;
68
- case "/api/doc/plan":
69
- return docPlan;
70
- case "/api/doc/section":
71
- return docSection;
72
- // v0.48.1. Each of these has ONE OF EVERY STATE behind it: a free next, a
73
- // paid next, a next that is blocked on a human; a clean audit and a dirty
74
- // one; a rich journal and one with nothing recorded at all.
75
- case "/api/doc/parts":
76
- return docParts[(q && q.slug) || ""] || docParts["prd-checkout-refund-130826"];
77
- case "/api/doc/next":
78
- return docNext[(q && q.slug) || ""] || docNext["prd-checkout-refund-130826"];
79
- case "/api/doc/audit":
80
- return docAudit[(q && q.slug) || ""] || docAudit["prd-checkout-refund-130826"];
81
- case "/api/doc/journal":
82
- return (q && q.slug) === "collab-risk-and-payments-130826" ? docJournalEmpty : docJournalRich;
83
- // v0.52.0 (D9) — BOTH shapes: a document routed off Claude, and one that is
84
- // not. `off` is a state with its own card, never a missing card.
85
- case "/api/doc/extra":
86
- return q && String(q.slug || "").indexOf("prd-") === 0
87
- ? {
88
- ok: true,
89
- slug: String(q.slug),
90
- extra: "writer",
91
- stored: "writer",
92
- source: "doc.json",
93
- options: ["off", "writer", "checker", "both"],
94
- resolve_order: ["doc.json (this document)", "config.extra_roles (the project)", "off"],
95
- config_roles: ["executor", "doc-writer"],
96
- shadowed_by_config: [],
97
- why: "writer goes off Claude for this document; every other role stays on Claude.",
98
- edges: { band: "[40,80)", edges: [40, 79], agree: true, resolved: { profile: "cheap", model: "deepseek-chat" } },
99
- default: "off",
100
- }
101
- : {
102
- ok: true,
103
- slug: String(q && q.slug),
104
- extra: "off",
105
- stored: null,
106
- source: "off",
107
- options: ["off", "writer", "checker", "both"],
108
- resolve_order: ["doc.json (this document)", "config.extra_roles (the project)", "off"],
109
- config_roles: [],
110
- shadowed_by_config: [],
111
- why: "nothing in this document goes off Claude.",
112
- edges: null,
113
- default: "off",
114
- };
115
- case "/api/doc/context":
116
- return docContext[(q && q.slug) || ""] || docContext["prd-checkout-refund-130826"];
117
- // v0.49.2. One of every state behind each: a populated ledger AND an empty
118
- // one, frozen-clean AND frozen-drifted, a real forecast AND a refusal AND a
119
- // naive floor, a cost report joined AND one with no trace at all.
120
- case "/api/doc/rules":
121
- return docRules;
122
- case "/api/doc/rules/one":
123
- return docRulesFrozen[(q && q.slug) || ""] || docRulesFrozen["prd-checkout-refund-130826"];
124
- case "/api/doc/forecast":
125
- return docForecast[(q && q.slug) || ""] || docForecast["prd-checkout-refund-130826"];
126
- case "/api/doc/cost":
127
- return docCost[(q && q.slug) || ""] || docCost["prd-checkout-refund-130826"];
128
- case "/api/challenge":
129
- return challengeList;
130
- case "/api/challenge/one":
131
- return (q && challengeCycles[q.slug]) || challengeCycles["tsd-payments"];
132
- case "/api/challenge/show":
133
- // Only the rich cycle carries findings. Every other slug returns its own
134
- // identity with an empty iteration list, which is the honest shape for a
135
- // cycle nobody has judged yet and stops the panel drawing one cycle's
136
- // findings under another cycle's name.
137
- return q && q.slug && q.slug !== challengeShow.slug
138
- ? { ...challengeShow, slug: q.slug, iterations: [], accepted: {}, rebuttals: {}, events: [] }
139
- : challengeShow;
140
- case "/api/challenge/diff":
141
- return q && q.slug === "billing-webhooks" ? challengeDiffMissing : challengeDiff;
142
- case "/api/challenge/lint":
143
- return challengeLint;
144
- case "/api/challenge/roles":
145
- return challengeRoles;
146
- case "/api/challenge/council":
147
- // UNSET is an ANSWER, not an error — the panel has to be designable
148
- // against a cycle nobody has answered the roster for.
149
- return q && q.slug === "billing-webhooks"
150
- ? { ok: false, slug: "billing-webhooks", council: null, council_version: 1, suggested: challengeRoles.council.slice(0, 3), rows: challengeCycles["billing-webhooks"].council, lens_counts: {}, reason: "council-unset", hint: 'the roster is a decision, not a default; run `orc challenge council billing-webhooks --set <csv|all|none> --reason "…"`' }
151
- : challengeCouncil;
152
- case "/api/meta":
153
- return undefined; // served for real even in fixture mode
154
- case "/api/version":
155
- // An update IS available here on purpose. "Up to date" is the state that
156
- // needs no design; you cannot lay out the update chip, the rail dot or
157
- // the upgrade row against a version that matches.
158
- return { version: "0.43.2", latest: "0.44.0", update_available: true, install_spec: "github:azure-id/orc", checked_source: "https://raw.githubusercontent.com/azure-id/orc/main/package.json", checked_ref: "azure-id/orc@main", check_disabled: false };
159
- case "/api/where":
160
- return where;
161
- case "/api/doctor":
162
- return doctor;
163
- case "/api/config":
164
- return config;
165
- case "/api/config/profiles":
166
- return {
167
- profiles: [
168
- { name: "solo-fast", desc: "One person, moving fast, reads their own diffs. Fewer gates, bigger waves.", keys: { max_wave_tasks: 4, batch_pause_every: 3 }, changes: [{ key: "batch_pause_every", from: 2, to: 3 }] },
169
- { name: "balanced", desc: "Today's defaults. Change nothing unless you know why.", keys: { max_wave_tasks: 3 }, changes: [{ key: "max_wave_tasks", from: 4, to: 3 }] },
170
- { name: "paranoid", desc: "Shared codebase, real users. Every gate on, small waves, pause often.", keys: { max_wave_tasks: 2, security_review: "on" }, changes: [{ key: "max_wave_tasks", from: 4, to: 2 }, { key: "security_review", from: "off", to: "on" }] },
171
- { name: "token-lean", desc: "Big repo, tight budget. Narrow scans, shallow analysis.", keys: { max_scouts: 1 }, changes: [{ key: "max_scouts", from: 3, to: 1 }] },
172
- ],
173
- };
174
- case "/api/config/recommend":
175
- return {
176
- recommended: "paranoid",
177
- desc: "Shared codebase, real users. Every gate on, small waves, pause often.",
178
- reasons: ["a real `npm test` script exists — gates have something to check", "CI is configured — this repo is shared, not a scratchpad", "7 contributors in history — coordination cost is real", "a project wiki exists — grounding is already cheap"],
179
- scores: { "solo-fast": 0, paranoid: 3, "token-lean": 0 },
180
- };
181
- case "/api/overview":
182
- return { where, doctor, wiki, patterns: patterns, runs_total: runs.total, waiting: runs.runs.filter((r) => r.status === "waiting").map((r) => ({ slug: r.slug, updated_ms: r.updated_ms, lane: r.lane })), diy, pact, boundary, wiki_debt: wikiDebt, extra_journal: extraJournal };
183
- case "/api/pact":
184
- return pact;
185
- case "/api/boundary":
186
- return boundary;
187
- case "/api/handoff":
188
- return handoff;
189
- case "/api/wiki/plan":
190
- return wikiPlan;
191
- case "/api/wiki/debt":
192
- return wikiDebt;
193
- case "/api/wiki/usage":
194
- return wikiUsage;
195
- case "/api/budget/forecast":
196
- // No plan path the exit-3 "no forecast possible" state, which is what a
197
- // first-time user sees and therefore has to be designed too.
198
- return q && q.plan
199
- ? budgetForecast
200
- : { ok: false, reason: "no-plan", hint: "pick a plan file — a forecast from a sentence is a guess that looks computed" };
201
- case "/api/budget/rates":
202
- return budgetRates;
203
- case "/api/budget/actual":
204
- return { ok: true, run: "store-credit", lane: "orc", trace: "run-orc-store-credit-100826-093012.txt", rows: [{ band: "[40,55)", dispatches: 3, forecast_weighted: 96000, actual_weighted: 138000, diff_pct: 44, tokens: { input: 9000, cache_write: 61000, cache_read: 121000, output: 11000 } }, { band: "[70,80)", dispatches: 1, forecast_weighted: 121000, actual_weighted: 304000, diff_pct: 151, tokens: { input: 12000, cache_write: 98000, cache_read: 240000, output: 24000 } }], actual: { tokens: { input: 21000, cache_write: 159000, cache_read: 361000, output: 35000 }, raw: 576000, weighted: 251100, usd: 7.02 }, cache_read_share: 0.71, unattributed: { blocks: 12, tokens: { input: 900, cache_write: 12000, cache_read: 24000, output: 1100 } }, joined: 17, dispatches: 19 };
205
- case "/api/aftermath":
206
- return aftermath;
207
- case "/api/export":
208
- return exportState;
209
- case "/api/runs":
210
- return runs;
211
- case "/api/run": {
212
- if (!q || !q.slug || q.slug === runDetail.slug) return runDetail;
213
- // A closed run is its OWN detail shape Reopen instead of Mark as done,
214
- // and the recorded reason. Without it the closed state exists in the list
215
- // and nowhere you can click.
216
- if (q.slug === runDetailClosed.slug) return runDetailClosed;
217
- const row = runs.runs.find((r) => r.slug === q.slug);
218
- return {
219
- ...runDetail,
220
- slug: q.slug,
221
- status: (row && row.status) || "done",
222
- resume: row && row.status === "waiting" ? runDetail.resume : null,
223
- closed: (row && row.closed) || null,
224
- stands: { lane: (row && row.lane) || "/orc-mini", phase: (row && row.phase) || "", wave: (row && row.wave) || "" },
225
- };
226
- }
227
- case "/api/wiki":
228
- return wiki;
229
- case "/api/wiki/impact":
230
- return wikiImpact;
231
- case "/api/patterns":
232
- return patterns;
233
- case "/api/gotchas":
234
- return gotchas;
235
- // v0.49.1 — the wiki's CONTENTS, the pattern's CONTENTS, and the two gotcha
236
- // reads the panel needs to preview an eviction and reach the archive.
237
- case "/api/wiki/docs":
238
- return wikiDocs;
239
- case "/api/wiki/show":
240
- // `--body` is opt-in: the default carries no prose at all.
241
- return q && q.body ? wikiShow : (({ body, ...rest }) => rest)(wikiShow);
242
- case "/api/wiki/coverage":
243
- // Two of them, so 61% AND 100% are both designable. `?full=1` picks the
244
- // happy one; a state with no fixture is a state nobody has looked at.
245
- return q && q.full ? wikiCoverageFull : wikiCoverage;
246
- case "/api/pattern/show": {
247
- const one = patternShow[(q && q.lang) || "react"] || patternShow.react;
248
- return q && q.body ? one : (({ body, ...rest }) => rest)(one);
249
- }
250
- case "/api/gotchas/archived":
251
- return gotchasArchived;
252
- case "/api/gotcha/prune/preview":
253
- return gotchaPrunePreview;
254
- case "/api/stats":
255
- return stats;
256
- case "/api/diy":
257
- return diy;
258
- case "/api/crosslink":
259
- return crosslink;
260
- case "/api/mocks":
261
- return mocks;
262
- case "/api/mock":
263
- return mockDetail;
264
- case "/api/stack":
265
- return { slugs: ["billing-split"], slug: "billing-split", ambiguous: false, plan: { slug: "billing-split", ready: false, exists: true, plan_path: PROJECT + "/stacked-pr/billing-split/stack-plan.md", layers: 4, ticket: "PAY-2214", problems: ["3 unfilled placeholders (e.g. <risk> <owner> <base>)"] } };
266
- case "/api/changelog":
267
- // Two entries, so the modal has to handle a LIST rather than one release —
268
- // skipping a version is the normal case, not the exotic one.
269
- return {
270
- version: "0.43.2",
271
- latest: "0.44.0",
272
- update_available: true,
273
- source: "https://raw.githubusercontent.com/azure-id/orc/main/CHANGELOG.md",
274
- check_disabled: false,
275
- fetched: true,
276
- entries: [
277
- {
278
- version: "0.44.0",
279
- date: "2026-08-09",
280
- title: "`orc ui`: the guided tour, and an upgrade you can read first",
281
- body:
282
- "**A version number is not a reason to upgrade.** The banner now fetches the\n" +
283
- "changelog from the same branch `orc upgrade` installs from, so what you read\n" +
284
- "and what you get can never be different releases.\n\n" +
285
- "- First-run tour over the key surfaces, skippable per project\n" +
286
- "- The upgrade spotlight clears when you actually reach the preview",
287
- },
288
- {
289
- version: "0.43.3",
290
- date: "2026-08-08",
291
- title: "settings stop being a wall",
292
- body: "Collapsible tiers and a filter across all of them at once.",
293
- },
294
- ],
295
- };
296
- case "/api/crosslink/kinds":
297
- // A short slice of the real catalog enough to design the picker with,
298
- // including the nested `auth/oidc` whose directory is sanitised to
299
- // `auth-oidc` on disk. A picker that never sees one cannot be trusted.
300
- return { kinds: ["grpc", "rest-endpoint", "graphql", "websocket", "message-queue", "webhook", "shared-db", "auth/oidc"] };
301
- case "/api/experiment":
302
- // can_launch:false is the fixture-mode state on purpose a disabled
303
- // launch button with a reason is a thing that needs designing.
304
- return {
305
- lanes: [
306
- { id: "orc", cmd: "/orc", what: "Full pipeline: intake → plan → scored parallel waves → review → verify → ship." },
307
- { id: "orc-quick", cmd: "/orc-quick", what: "Ask for anything. Look → ask once → do, and it always asks which agent." },
308
- { id: "orc-mini", cmd: "/orc-mini", what: "One executor, smoke gate, ship. No full review or verify phase." },
309
- { id: "orc-wiki", cmd: "/orc-wiki", what: "Build or refresh the project wiki. Expensive; always asks first." },
310
- ],
311
- project_root: "/example/project",
312
- platform: "linux",
313
- can_launch: false,
314
- };
315
- case "/api/learn":
316
- return { sections: require("../../onboarding-content.js").SECTIONS };
317
- // The mocked runs are package content, identical on every machine and
318
- // needing no project — so fixture mode serves the REAL catalogue. A canned
319
- // copy here could only ever be a worse version of a file sitting next to
320
- // it, and it would be the one thing on this panel that could go stale.
321
- case "/api/mockruns":
322
- return require("../../mockrun-catalog.js").catalogue();
323
- case "/api/mockrun": {
324
- const doc = require("../../mockrun-catalog.js").get(String((q && q.slug) || ""));
325
- return doc ? { ...doc, found: true } : { slug: String((q && q.slug) || ""), found: false };
326
- }
327
- case "/api/fs/list":
328
- // The folder picker on canned data. It carries the states that are hard
329
- // to reach on a tidy machine: a plain folder, a git repo WITHOUT a wiki
330
- // (the case that saves an inert edge), a repo with one, and the project
331
- // itself — which the picker must refuse.
332
- return {
333
- path: "/example",
334
- parent: "/",
335
- sep: "/",
336
- home: "/home/dev",
337
- project_root: PROJECT,
338
- is_project_root: false,
339
- relative: "..",
340
- truncated: false,
341
- dirs: [
342
- { name: "payments-core", path: "/example/payments-core", is_repo: true, has_wiki: true },
343
- { name: "storefront-web", path: "/example/storefront-web", is_repo: true, has_wiki: false },
344
- { name: "ledger-batch", path: "/example/ledger-batch", is_repo: true, has_wiki: false },
345
- { name: "project", path: PROJECT, is_repo: true, has_wiki: true },
346
- { name: "scratch", path: "/example/scratch", is_repo: false, has_wiki: false },
347
- ],
348
- };
349
- case "/api/maintenance":
350
- return {
351
- actions: [
352
- { id: "update", label: "Re-copy this package's payload over the installed one", command: "orc update", network: false, names_files: false },
353
- { id: "prune", label: "Update AND delete ORC-named orphans from a pre-manifest install", command: "orc update --prune", network: false, names_files: true },
354
- { id: "fix", label: "Apply every fix orc doctor found (= update + prune + settings re-merge)", command: "orc doctor --fix", network: false, names_files: false },
355
- { id: "upgrade", label: "Fetch the LATEST package from the network, then apply it", command: "orc upgrade", network: true, names_files: false },
356
- { id: "update-global", label: "Re-copy this package's payload over the GLOBAL install in ~/.claude", command: "orc update --global", network: false, names_files: false, advanced: true },
357
- ],
358
- };
359
- case "/api/maintenance/preview":
360
- return {
361
- action: q.action,
362
- label: "Preview (fixtures)",
363
- command:
364
- "orc " +
365
- (q.action === "prune"
366
- ? "update --prune"
367
- : q.action === "fix"
368
- ? "doctor --fix"
369
- : q.action === "upgrade"
370
- ? "upgrade"
371
- : q.action === "update-global"
372
- ? "update --global"
373
- : "update"),
374
- network: q.action === "upgrade",
375
- names_files: q.action === "prune",
376
- advanced: q.action === "update-global",
377
- preview_command: q.action === "update-global" ? "orc doctor --global" : "orc doctor",
378
- preview: doctor,
379
- // Derived from the SAME list the Runs panel renders, so a run closed in
380
- // the fixture set stops blocking the preview here too — which is the
381
- // whole point of the v0.49.2 fix.
382
- waiting_runs: runs.runs.filter((r) => r.status === "waiting").map((r) => r.slug),
383
- dirty_tree: true,
384
- };
385
- // v0.50.0 — `orc extra`. One of every state, including the ugly ones: a
386
- // stale catalog, a vault part-way through its countdown, a vault that
387
- // deleted itself, a missing environment key, overlapping route rows and a
388
- // routed model the provider no longer lists.
389
- // v0.51.0 — the SETUP GATE has three states and each is reachable here:
390
- // `?gate=none` is nothing connected, `?gate=untested` is a connection that
391
- // has never answered, and the default is the gate open. Without all three
392
- // you cannot design the panel a first-time user actually sees.
393
- case "/api/extra": {
394
- // The panel sends no query on this read, so a query-only switch would put
395
- // the two shut-gate states somewhere nobody could ever look at them —
396
- // which is the exact failure --fixtures exists to prevent. `ORC_UI_FIXTURE_GATE`
397
- // makes both reachable in the running panel:
398
- // ORC_UI_FIXTURE_GATE=none nothing connected at all
399
- // ORC_UI_FIXTURE_GATE=untested connected, never answered
400
- const g = (q && q.gate) || process.env.ORC_UI_FIXTURE_GATE || "";
401
- return g === "none" ? extraListNoConnection : g === "untested" ? extraListNeverTested : extraList;
402
- }
403
- // v0.51.0 the four LOCAL TOOL states, the three credential routes, and
404
- // both model-entry shapes.
405
- case "/api/extra/tools":
406
- return extraTools;
407
- case "/api/extra/keyhelp":
408
- return q && q.profile === "local" ? extraKeyhelp.env : extraKeyhelp.login;
409
- case "/api/extra/models":
410
- return q && q.profile === "custom" ? extraModels.freeText : extraModels.list;
411
- case "/api/extra/providers":
412
- return extraProviders;
413
- case "/api/extra/doctor":
414
- return extraDoctor;
415
- case "/api/extra/route":
416
- return extraRoute;
417
- case "/api/extra/role":
418
- return extraRole;
419
- case "/api/extra/lanes":
420
- return extraLanes;
421
- case "/api/extra/stats":
422
- return extraStats;
423
- case "/api/extra/rates":
424
- return extraRates;
425
- // v0.54.0 RECOVERY. One reconcile fixture per state, including the ugly
426
- // ones: an `in-flight` REFUSAL, a `reverted` block, a `streamed-opaque`
427
- // journal with no per-turn attribution to render, and a `no-journal` answer
428
- // for a dispatch that predates the feature.
429
- case "/api/extra/journal":
430
- return extraJournal;
431
- case "/api/extra/reconcile":
432
- return extraReconcile[String((q && q.task) || "")] || extraReconcile["T-12"];
433
- case "/api/extra/journal/prune/preview":
434
- return extraJournalPrune;
435
- case "/api/extra/show":
436
- return {
437
- ok: true,
438
- profile: extraList.profiles.find((p) => p.name === (q && q.profile)) || extraList.profiles[0],
439
- catalog: null,
440
- history: [],
441
- };
442
- case "/api/job":
443
- return { id: null, running: false };
444
- default:
445
- return undefined;
446
- }
447
- };
448
-
449
- // ── the states below are referenced above; declared after for readability ───
450
-
451
- // The POST half (v0.50.0). Almost every mutation in fixture mode answers "nothing
452
- // ran" and that is the honest answer — but the CONNECTION TEST'S OUTCOME is a
453
- // state this panel is largely about, and a state you cannot design if the wire
454
- // never lands. So exactly one route has canned answers, chosen deterministically
455
- // from the profile so they agree with what the list already claims.
456
- //
457
- // A route with no entry here still gets the ordinary "nothing ran" reply.
458
- module.exports.post = function post(route, body) {
459
- // `add` answers OK so the CONNECT FLOW can be walked end to end in fixture
460
- // mode the add is only the step before the test, and the test's outcome is
461
- // the state worth designing. Every other write still answers "nothing ran".
462
- if (route === "/api/extra/add") return { exit_code: 0, data: { ok: true, next: "orc extra ping" } };
463
- // v0.51.0 — a launch that could NOT happen and one that did. Both are exit 0,
464
- // because "no terminal here" is an ANSWER carrying the command to paste.
465
- if (route === "/api/extra/install")
466
- return {
467
- exit_code: 0,
468
- data: (body && body.provider) === "codex" ? extraInstall.refused : extraInstall.launched,
469
- };
470
- if (route === "/api/extra/models/test")
471
- return { exit_code: 0, data: { ok: true, test: extraPingLive } };
472
- if (route !== "/api/extra/ping") return undefined;
473
- const profile = String((body && body.profile) || "");
474
- if (body && body.key && !body.passphrase) return { exit_code: 0, data: { ...extraPingSaveOffer, profile } };
475
- // v0.51.0 the PAID rung has THREE outcomes worth designing and each is
476
- // reachable: it worked and still cannot say which model answered, the model
477
- // was LISTED and is DEAD upstream, and the tool is not installed at all.
478
- if (route === "/api/extra/ping" && body && body.live) {
479
- if (profile === "toolc") return { exit_code: 1, data: { ...extraPingNotInstalled, profile } };
480
- if (String(body.model || "").indexOf("free") !== -1)
481
- return { exit_code: 1, data: { ...extraPingDeadModel, profile } };
482
- return { exit_code: 0, data: { ...extraPingLive, profile: profile || extraPingLive.profile } };
483
- }
484
- if (profile === "router" || profile === "burned") return { exit_code: 1, data: { ...extraPingBad, profile } };
485
- return { exit_code: 0, data: { ...extraPingOk, profile: profile || extraPingOk.profile } };
486
- };
1
+ "use strict";
2
+ /**
3
+ * fixtures.js — canned API responses for `orc ui --fixtures`.
4
+ *
5
+ * This exists because you cannot DESIGN a state you cannot reach. On a healthy
6
+ * install with a fresh wiki and no paused runs, the STALE chip, the `waiting`
7
+ * run card, the shadowed-setting lock and the unhealthy doctor panel are
8
+ * unreachable — so they get built once, blind, and never looked at again.
9
+ *
10
+ * The rule for this file: carry ONE OF EVERY STATE, including the ugly ones.
11
+ * It also means the UI can be worked on with no ORC project at all.
12
+ *
13
+ * Shapes MUST match what `bin/cli.js --json` really emits. A fixture that has
14
+ * drifted from the CLI is worse than no fixture, so test/webui.test.js asserts
15
+ * the fixture key set against the live CLI output for the shared routes.
16
+ */
17
+
18
+
19
+ // v0.48.1 — the data lives one file per panel; this file is the ROUTER and
20
+ // nothing else. Adding a fixture is a new key in the panel's own file plus a
21
+ // case here; it is never an edit to a 1 700-line module.
22
+ const { PROJECT, doctor, where } = require("./shell.js");
23
+ const { config } = require("./settings.js");
24
+ const { wiki, wikiDocs, wikiShow, wikiCoverage, wikiCoverageFull, wikiUnregistered, wikiPlan, wikiDebt, wikiUsage, patterns, patternShow, gotchas, gotchasArchived, gotchaPrunePreview, wikiImpact } = require("./knowledge.js");
25
+ const { runs, runDetail, runDetailClosed, aftermath } = require("./runs.js");
26
+ const { stats, budgetForecast, budgetRates } = require("./stats.js");
27
+ const { pact } = require("./pact.js");
28
+ const { boundary } = require("./boundary.js");
29
+ const { handoff } = require("./handoff.js");
30
+ const { exportState, mocks } = require("./maintenance.js");
31
+ const { chGoals, chDims, challengeRoles, challengeCouncil, challengeCycles, challengeList, challengeShow, challengeDiff, challengeDiffMissing, challengeLint } = require("./challenge.js");
32
+ const { docList, docParts, docStatuses, docMapSections, docMap, docLint, docPlan, docShow, docSection, docShipped, docShippedDrifted, docNext, docAudit, docJournalRich, docJournalEmpty, docContext, docRules, docRulesFrozen, docForecast, docCost } = require("./docs.js");
33
+ const { laneList, lanePhases, laneCalls } = require("./lanes.js");
34
+ const { diy } = require("./flow.js");
35
+ const { crosslink } = require("./crosslink.js");
36
+ const { mockDetail } = require("./mockrun.js");
37
+ const { extraProviders, extraList, extraListNoConnection, extraListNeverTested, extraTools, extraKeyhelp, extraModels, extraDoctor, extraRoute, extraRole, extraLanes, extraStats, extraRates, extraPingOk, extraPingBad, extraPingSaveOffer, extraJournal, extraReconcile, extraJournalPrune, extraPingLive, extraPingDeadModel, extraPingNotInstalled, extraInstall, extraDemotion } = require("./extra.js");
38
+
39
+ module.exports.get = function get(route, q) {
40
+ switch (route) {
41
+ // v1.0.0 W16 — the `orc lane` noun. `phases` is keyed by lane so the
42
+ // picker reaches BOTH designable states: a lane with a full pipeline and
43
+ // one that keeps its pipeline in its own spine. An unknown lane falls to
44
+ // the `orc` entry rather than returning nothing, because a fixture that
45
+ // 404s teaches the panel a state the CLI does not have (it exits 2).
46
+ // The run demotion. `demoted: true` on purpose with it false the Promote
47
+ // button correctly does not render, and the modal behind it (the one place
48
+ // a required reason is collected) could never be designed.
49
+ case "/api/extra/demotion":
50
+ return extraDemotion;
51
+ case "/api/lanes":
52
+ return laneList;
53
+ case "/api/lane/phases":
54
+ return lanePhases[(q && q.lane) || "orc"] || lanePhases.orc;
55
+ case "/api/lane/calls":
56
+ return laneCalls;
57
+ case "/api/doc":
58
+ return docList;
59
+ case "/api/doc/one":
60
+ // The two SHIPPED states overlay the plain ones, so `shipped`,
61
+ // `shipped-drifted` and a FORCED ship are all reachable in the panel.
62
+ return (
63
+ { [docShipped.slug]: docShipped, [docShippedDrifted.slug]: docShippedDrifted }[q && q.slug] ||
64
+ (q && docStatuses[q.slug]) ||
65
+ docStatuses["prd-checkout-refund-130826"]
66
+ );
67
+ case "/api/doc/show":
68
+ // The memory fields ride on whichever slug was asked for, so the header
69
+ // strip, the brief and the journal are all designable per state.
70
+ return {
71
+ ...docShow,
72
+ slug: (q && q.slug) || docShow.slug,
73
+ created_at: "13-08-2026 09:02:11",
74
+ last_touched_at: "15-08-2026 17:05:00",
75
+ sessions: 3,
76
+ context: (docContext[q && q.slug] || docContext["prd-checkout-refund-130826"]).context,
77
+ journal: (q && q.slug === "collab-risk-and-payments-130826" ? docJournalEmpty : docJournalRich).journal,
78
+ shipped: (docStatuses[q && q.slug] || {}).shipped || null,
79
+ ship_history: [],
80
+ };
81
+ case "/api/doc/map":
82
+ return docMap;
83
+ case "/api/doc/lint":
84
+ return docLint;
85
+ case "/api/doc/plan":
86
+ return docPlan;
87
+ case "/api/doc/section":
88
+ return docSection;
89
+ // v0.48.1. Each of these has ONE OF EVERY STATE behind it: a free next, a
90
+ // paid next, a next that is blocked on a human; a clean audit and a dirty
91
+ // one; a rich journal and one with nothing recorded at all.
92
+ case "/api/doc/parts":
93
+ return docParts[(q && q.slug) || ""] || docParts["prd-checkout-refund-130826"];
94
+ case "/api/doc/next":
95
+ return docNext[(q && q.slug) || ""] || docNext["prd-checkout-refund-130826"];
96
+ case "/api/doc/audit":
97
+ return docAudit[(q && q.slug) || ""] || docAudit["prd-checkout-refund-130826"];
98
+ case "/api/doc/journal":
99
+ return (q && q.slug) === "collab-risk-and-payments-130826" ? docJournalEmpty : docJournalRich;
100
+ // v0.52.0 (D9) — BOTH shapes: a document routed off Claude, and one that is
101
+ // not. `off` is a state with its own card, never a missing card.
102
+ case "/api/doc/extra":
103
+ return q && String(q.slug || "").indexOf("prd-") === 0
104
+ ? {
105
+ ok: true,
106
+ slug: String(q.slug),
107
+ extra: "writer",
108
+ stored: "writer",
109
+ source: "doc.json",
110
+ options: ["off", "writer", "checker", "both"],
111
+ resolve_order: ["doc.json (this document)", "config.extra_roles (the project)", "off"],
112
+ config_roles: ["executor", "doc-writer"],
113
+ shadowed_by_config: [],
114
+ why: "writer goes off Claude for this document; every other role stays on Claude.",
115
+ edges: { band: "[40,80)", edges: [40, 79], agree: true, resolved: { profile: "cheap", model: "deepseek-chat" } },
116
+ default: "off",
117
+ }
118
+ : {
119
+ ok: true,
120
+ slug: String(q && q.slug),
121
+ extra: "off",
122
+ stored: null,
123
+ source: "off",
124
+ options: ["off", "writer", "checker", "both"],
125
+ resolve_order: ["doc.json (this document)", "config.extra_roles (the project)", "off"],
126
+ config_roles: [],
127
+ shadowed_by_config: [],
128
+ why: "nothing in this document goes off Claude.",
129
+ edges: null,
130
+ default: "off",
131
+ };
132
+ case "/api/doc/context":
133
+ return docContext[(q && q.slug) || ""] || docContext["prd-checkout-refund-130826"];
134
+ // v0.49.2. One of every state behind each: a populated ledger AND an empty
135
+ // one, frozen-clean AND frozen-drifted, a real forecast AND a refusal AND a
136
+ // naive floor, a cost report joined AND one with no trace at all.
137
+ case "/api/doc/rules":
138
+ return docRules;
139
+ case "/api/doc/rules/one":
140
+ return docRulesFrozen[(q && q.slug) || ""] || docRulesFrozen["prd-checkout-refund-130826"];
141
+ case "/api/doc/forecast":
142
+ return docForecast[(q && q.slug) || ""] || docForecast["prd-checkout-refund-130826"];
143
+ case "/api/doc/cost":
144
+ return docCost[(q && q.slug) || ""] || docCost["prd-checkout-refund-130826"];
145
+ case "/api/challenge":
146
+ return challengeList;
147
+ case "/api/challenge/one":
148
+ return (q && challengeCycles[q.slug]) || challengeCycles["tsd-payments"];
149
+ case "/api/challenge/show":
150
+ // Only the rich cycle carries findings. Every other slug returns its own
151
+ // identity with an empty iteration list, which is the honest shape for a
152
+ // cycle nobody has judged yet — and stops the panel drawing one cycle's
153
+ // findings under another cycle's name.
154
+ return q && q.slug && q.slug !== challengeShow.slug
155
+ ? { ...challengeShow, slug: q.slug, iterations: [], accepted: {}, rebuttals: {}, events: [] }
156
+ : challengeShow;
157
+ case "/api/challenge/diff":
158
+ return q && q.slug === "billing-webhooks" ? challengeDiffMissing : challengeDiff;
159
+ case "/api/challenge/lint":
160
+ return challengeLint;
161
+ case "/api/challenge/roles":
162
+ return challengeRoles;
163
+ case "/api/challenge/council":
164
+ // UNSET is an ANSWER, not an error — the panel has to be designable
165
+ // against a cycle nobody has answered the roster for.
166
+ return q && q.slug === "billing-webhooks"
167
+ ? { ok: false, slug: "billing-webhooks", council: null, council_version: 1, suggested: challengeRoles.council.slice(0, 3), rows: challengeCycles["billing-webhooks"].council, lens_counts: {}, reason: "council-unset", hint: 'the roster is a decision, not a default; run `orc challenge council billing-webhooks --set <csv|all|none> --reason "…"`' }
168
+ : challengeCouncil;
169
+ case "/api/meta":
170
+ return undefined; // served for real even in fixture mode
171
+ case "/api/version":
172
+ // An update IS available here on purpose. "Up to date" is the state that
173
+ // needs no design; you cannot lay out the update chip, the rail dot or
174
+ // the upgrade row against a version that matches.
175
+ return { version: "0.43.2", latest: "0.44.0", update_available: true, install_spec: "github:azure-id/orc", checked_source: "https://raw.githubusercontent.com/azure-id/orc/main/package.json", checked_ref: "azure-id/orc@main", check_disabled: false };
176
+ case "/api/where":
177
+ return where;
178
+ case "/api/doctor":
179
+ return doctor;
180
+ case "/api/config":
181
+ return config;
182
+ case "/api/config/profiles":
183
+ return {
184
+ profiles: [
185
+ { name: "solo-fast", desc: "One person, moving fast, reads their own diffs. Fewer gates, bigger waves.", keys: { max_wave_tasks: 4, batch_pause_every: 3 }, changes: [{ key: "batch_pause_every", from: 2, to: 3 }] },
186
+ { name: "balanced", desc: "Today's defaults. Change nothing unless you know why.", keys: { max_wave_tasks: 3 }, changes: [{ key: "max_wave_tasks", from: 4, to: 3 }] },
187
+ { name: "paranoid", desc: "Shared codebase, real users. Every gate on, small waves, pause often.", keys: { max_wave_tasks: 2, security_review: "on" }, changes: [{ key: "max_wave_tasks", from: 4, to: 2 }, { key: "security_review", from: "off", to: "on" }] },
188
+ { name: "token-lean", desc: "Big repo, tight budget. Narrow scans, shallow analysis.", keys: { max_scouts: 1 }, changes: [{ key: "max_scouts", from: 3, to: 1 }] },
189
+ ],
190
+ };
191
+ case "/api/config/recommend":
192
+ return {
193
+ recommended: "paranoid",
194
+ desc: "Shared codebase, real users. Every gate on, small waves, pause often.",
195
+ reasons: ["a real `npm test` script exists — gates have something to check", "CI is configured — this repo is shared, not a scratchpad", "7 contributors in history — coordination cost is real", "a project wiki exists — grounding is already cheap"],
196
+ scores: { "solo-fast": 0, paranoid: 3, "token-lean": 0 },
197
+ };
198
+ case "/api/overview":
199
+ return { where, doctor, wiki, patterns: patterns, runs_total: runs.total, waiting: runs.runs.filter((r) => r.status === "waiting").map((r) => ({ slug: r.slug, updated_ms: r.updated_ms, lane: r.lane })), diy, pact, boundary, wiki_debt: wikiDebt, extra_journal: extraJournal };
200
+ case "/api/pact":
201
+ return pact;
202
+ case "/api/boundary":
203
+ return boundary;
204
+ case "/api/handoff":
205
+ return handoff;
206
+ case "/api/wiki/plan":
207
+ return wikiPlan;
208
+ case "/api/wiki/debt":
209
+ return wikiDebt;
210
+ case "/api/wiki/usage":
211
+ return wikiUsage;
212
+ case "/api/budget/forecast":
213
+ // No plan path the exit-3 "no forecast possible" state, which is what a
214
+ // first-time user sees and therefore has to be designed too.
215
+ return q && q.plan
216
+ ? budgetForecast
217
+ : { ok: false, reason: "no-plan", hint: "pick a plan file — a forecast from a sentence is a guess that looks computed" };
218
+ case "/api/budget/rates":
219
+ return budgetRates;
220
+ case "/api/budget/actual":
221
+ return { ok: true, run: "store-credit", lane: "orc", trace: "run-orc-store-credit-100826-093012.txt", rows: [{ band: "[40,55)", dispatches: 3, forecast_weighted: 96000, actual_weighted: 138000, diff_pct: 44, tokens: { input: 9000, cache_write: 61000, cache_read: 121000, output: 11000 } }, { band: "[70,80)", dispatches: 1, forecast_weighted: 121000, actual_weighted: 304000, diff_pct: 151, tokens: { input: 12000, cache_write: 98000, cache_read: 240000, output: 24000 } }], actual: { tokens: { input: 21000, cache_write: 159000, cache_read: 361000, output: 35000 }, raw: 576000, weighted: 251100, usd: 7.02 }, cache_read_share: 0.71, unattributed: { blocks: 12, tokens: { input: 900, cache_write: 12000, cache_read: 24000, output: 1100 } }, joined: 17, dispatches: 19 };
222
+ case "/api/aftermath":
223
+ return aftermath;
224
+ case "/api/export":
225
+ return exportState;
226
+ case "/api/runs":
227
+ return runs;
228
+ case "/api/run": {
229
+ if (!q || !q.slug || q.slug === runDetail.slug) return runDetail;
230
+ // A closed run is its OWN detail shape — Reopen instead of Mark as done,
231
+ // and the recorded reason. Without it the closed state exists in the list
232
+ // and nowhere you can click.
233
+ if (q.slug === runDetailClosed.slug) return runDetailClosed;
234
+ const row = runs.runs.find((r) => r.slug === q.slug);
235
+ return {
236
+ ...runDetail,
237
+ slug: q.slug,
238
+ status: (row && row.status) || "done",
239
+ resume: row && row.status === "waiting" ? runDetail.resume : null,
240
+ closed: (row && row.closed) || null,
241
+ stands: { lane: (row && row.lane) || "/orc-mini", phase: (row && row.phase) || "", wave: (row && row.wave) || "" },
242
+ };
243
+ }
244
+ case "/api/wiki":
245
+ return wiki;
246
+ case "/api/wiki/impact":
247
+ return wikiImpact;
248
+ case "/api/patterns":
249
+ return patterns;
250
+ case "/api/gotchas":
251
+ return gotchas;
252
+ // v0.49.1 — the wiki's CONTENTS, the pattern's CONTENTS, and the two gotcha
253
+ // reads the panel needs to preview an eviction and reach the archive.
254
+ case "/api/wiki/docs":
255
+ return wikiDocs;
256
+ case "/api/wiki/show":
257
+ // `--body` is opt-in: the default carries no prose at all.
258
+ return q && q.body ? wikiShow : (({ body, ...rest }) => rest)(wikiShow);
259
+ case "/api/wiki/coverage":
260
+ // Two of them, so 61% AND 100% are both designable. `?full=1` picks the
261
+ // happy one; a state with no fixture is a state nobody has looked at.
262
+ return q && q.full ? wikiCoverageFull : wikiCoverage;
263
+ case "/api/pattern/show": {
264
+ const one = patternShow[(q && q.lang) || "react"] || patternShow.react;
265
+ return q && q.body ? one : (({ body, ...rest }) => rest)(one);
266
+ }
267
+ case "/api/gotchas/archived":
268
+ return gotchasArchived;
269
+ case "/api/gotcha/prune/preview":
270
+ return gotchaPrunePreview;
271
+ case "/api/stats":
272
+ return stats;
273
+ case "/api/diy":
274
+ return diy;
275
+ case "/api/crosslink":
276
+ return crosslink;
277
+ case "/api/mocks":
278
+ return mocks;
279
+ case "/api/mock":
280
+ return mockDetail;
281
+ case "/api/stack":
282
+ return { slugs: ["billing-split"], slug: "billing-split", ambiguous: false, plan: { slug: "billing-split", ready: false, exists: true, plan_path: PROJECT + "/stacked-pr/billing-split/stack-plan.md", layers: 4, ticket: "PAY-2214", problems: ["3 unfilled placeholders (e.g. <risk> <owner> <base>)"] } };
283
+ case "/api/changelog":
284
+ // Two entries, so the modal has to handle a LIST rather than one release —
285
+ // skipping a version is the normal case, not the exotic one.
286
+ return {
287
+ version: "0.43.2",
288
+ latest: "0.44.0",
289
+ update_available: true,
290
+ source: "https://raw.githubusercontent.com/azure-id/orc/main/CHANGELOG.md",
291
+ check_disabled: false,
292
+ fetched: true,
293
+ entries: [
294
+ {
295
+ version: "0.44.0",
296
+ date: "2026-08-09",
297
+ title: "`orc ui`: the guided tour, and an upgrade you can read first",
298
+ body:
299
+ "**A version number is not a reason to upgrade.** The banner now fetches the\n" +
300
+ "changelog from the same branch `orc upgrade` installs from, so what you read\n" +
301
+ "and what you get can never be different releases.\n\n" +
302
+ "- First-run tour over the key surfaces, skippable per project\n" +
303
+ "- The upgrade spotlight clears when you actually reach the preview",
304
+ },
305
+ {
306
+ version: "0.43.3",
307
+ date: "2026-08-08",
308
+ title: "settings stop being a wall",
309
+ body: "Collapsible tiers and a filter across all of them at once.",
310
+ },
311
+ ],
312
+ };
313
+ case "/api/crosslink/kinds":
314
+ // A short slice of the real catalog — enough to design the picker with,
315
+ // including the nested `auth/oidc` whose directory is sanitised to
316
+ // `auth-oidc` on disk. A picker that never sees one cannot be trusted.
317
+ return { kinds: ["grpc", "rest-endpoint", "graphql", "websocket", "message-queue", "webhook", "shared-db", "auth/oidc"] };
318
+ case "/api/experiment":
319
+ // can_launch:false is the fixture-mode state on purpose a disabled
320
+ // launch button with a reason is a thing that needs designing.
321
+ return {
322
+ lanes: [
323
+ { id: "orc", cmd: "/orc", what: "Full pipeline: intake → plan → scored parallel waves → review → verify → ship." },
324
+ { id: "orc-quick", cmd: "/orc-quick", what: "Ask for anything. Look → ask once → do, and it always asks which agent." },
325
+ { id: "orc-mini", cmd: "/orc-mini", what: "One executor, smoke gate, ship. No full review or verify phase." },
326
+ { id: "orc-wiki", cmd: "/orc-wiki", what: "Build or refresh the project wiki. Expensive; always asks first." },
327
+ ],
328
+ project_root: "/example/project",
329
+ platform: "linux",
330
+ can_launch: false,
331
+ };
332
+ case "/api/learn":
333
+ return { sections: require("../../onboarding-content.js").SECTIONS };
334
+ // The mocked runs are package content, identical on every machine and
335
+ // needing no project — so fixture mode serves the REAL catalogue. A canned
336
+ // copy here could only ever be a worse version of a file sitting next to
337
+ // it, and it would be the one thing on this panel that could go stale.
338
+ case "/api/mockruns":
339
+ return require("../../mockrun-catalog.js").catalogue();
340
+ case "/api/mockrun": {
341
+ const doc = require("../../mockrun-catalog.js").get(String((q && q.slug) || ""));
342
+ return doc ? { ...doc, found: true } : { slug: String((q && q.slug) || ""), found: false };
343
+ }
344
+ case "/api/fs/list":
345
+ // The folder picker on canned data. It carries the states that are hard
346
+ // to reach on a tidy machine: a plain folder, a git repo WITHOUT a wiki
347
+ // (the case that saves an inert edge), a repo with one, and the project
348
+ // itself — which the picker must refuse.
349
+ return {
350
+ path: "/example",
351
+ parent: "/",
352
+ sep: "/",
353
+ home: "/home/dev",
354
+ project_root: PROJECT,
355
+ is_project_root: false,
356
+ relative: "..",
357
+ truncated: false,
358
+ dirs: [
359
+ { name: "payments-core", path: "/example/payments-core", is_repo: true, has_wiki: true },
360
+ { name: "storefront-web", path: "/example/storefront-web", is_repo: true, has_wiki: false },
361
+ { name: "ledger-batch", path: "/example/ledger-batch", is_repo: true, has_wiki: false },
362
+ { name: "project", path: PROJECT, is_repo: true, has_wiki: true },
363
+ { name: "scratch", path: "/example/scratch", is_repo: false, has_wiki: false },
364
+ ],
365
+ };
366
+ case "/api/maintenance":
367
+ return {
368
+ actions: [
369
+ { id: "update", label: "Re-copy this package's payload over the installed one", command: "orc update", network: false, names_files: false },
370
+ { id: "prune", label: "Update AND delete ORC-named orphans from a pre-manifest install", command: "orc update --prune", network: false, names_files: true },
371
+ { id: "fix", label: "Apply every fix orc doctor found (= update + prune + settings re-merge)", command: "orc doctor --fix", network: false, names_files: false },
372
+ { id: "upgrade", label: "Fetch the LATEST package from the network, then apply it", command: "orc upgrade", network: true, names_files: false },
373
+ { id: "update-global", label: "Re-copy this package's payload over the GLOBAL install in ~/.claude", command: "orc update --global", network: false, names_files: false, advanced: true },
374
+ ],
375
+ };
376
+ case "/api/maintenance/preview":
377
+ return {
378
+ action: q.action,
379
+ label: "Preview (fixtures)",
380
+ command:
381
+ "orc " +
382
+ (q.action === "prune"
383
+ ? "update --prune"
384
+ : q.action === "fix"
385
+ ? "doctor --fix"
386
+ : q.action === "upgrade"
387
+ ? "upgrade"
388
+ : q.action === "update-global"
389
+ ? "update --global"
390
+ : "update"),
391
+ network: q.action === "upgrade",
392
+ names_files: q.action === "prune",
393
+ advanced: q.action === "update-global",
394
+ preview_command: q.action === "update-global" ? "orc doctor --global" : "orc doctor",
395
+ preview: doctor,
396
+ // Derived from the SAME list the Runs panel renders, so a run closed in
397
+ // the fixture set stops blocking the preview here too — which is the
398
+ // whole point of the v0.49.2 fix.
399
+ waiting_runs: runs.runs.filter((r) => r.status === "waiting").map((r) => r.slug),
400
+ dirty_tree: true,
401
+ };
402
+ // v0.50.0 — `orc extra`. One of every state, including the ugly ones: a
403
+ // stale catalog, a vault part-way through its countdown, a vault that
404
+ // deleted itself, a missing environment key, overlapping route rows and a
405
+ // routed model the provider no longer lists.
406
+ // v0.51.0 — the SETUP GATE has three states and each is reachable here:
407
+ // `?gate=none` is nothing connected, `?gate=untested` is a connection that
408
+ // has never answered, and the default is the gate open. Without all three
409
+ // you cannot design the panel a first-time user actually sees.
410
+ case "/api/extra": {
411
+ // The panel sends no query on this read, so a query-only switch would put
412
+ // the two shut-gate states somewhere nobody could ever look at them —
413
+ // which is the exact failure --fixtures exists to prevent. `ORC_UI_FIXTURE_GATE`
414
+ // makes both reachable in the running panel:
415
+ // ORC_UI_FIXTURE_GATE=none nothing connected at all
416
+ // ORC_UI_FIXTURE_GATE=untested connected, never answered
417
+ const g = (q && q.gate) || process.env.ORC_UI_FIXTURE_GATE || "";
418
+ return g === "none" ? extraListNoConnection : g === "untested" ? extraListNeverTested : extraList;
419
+ }
420
+ // v0.51.0 — the four LOCAL TOOL states, the three credential routes, and
421
+ // both model-entry shapes.
422
+ case "/api/extra/tools":
423
+ return extraTools;
424
+ case "/api/extra/keyhelp":
425
+ return q && q.profile === "local" ? extraKeyhelp.env : extraKeyhelp.login;
426
+ case "/api/extra/models":
427
+ return q && q.profile === "custom" ? extraModels.freeText : extraModels.list;
428
+ case "/api/extra/providers":
429
+ return extraProviders;
430
+ case "/api/extra/doctor":
431
+ return extraDoctor;
432
+ case "/api/extra/route":
433
+ return extraRoute;
434
+ case "/api/extra/role":
435
+ return extraRole;
436
+ case "/api/extra/lanes":
437
+ return extraLanes;
438
+ case "/api/extra/stats":
439
+ return extraStats;
440
+ case "/api/extra/rates":
441
+ return extraRates;
442
+ // v0.54.0 — RECOVERY. One reconcile fixture per state, including the ugly
443
+ // ones: an `in-flight` REFUSAL, a `reverted` block, a `streamed-opaque`
444
+ // journal with no per-turn attribution to render, and a `no-journal` answer
445
+ // for a dispatch that predates the feature.
446
+ case "/api/extra/journal":
447
+ return extraJournal;
448
+ case "/api/extra/reconcile":
449
+ return extraReconcile[String((q && q.task) || "")] || extraReconcile["T-12"];
450
+ case "/api/extra/journal/prune/preview":
451
+ return extraJournalPrune;
452
+ case "/api/extra/show":
453
+ return {
454
+ ok: true,
455
+ profile: extraList.profiles.find((p) => p.name === (q && q.profile)) || extraList.profiles[0],
456
+ catalog: null,
457
+ history: [],
458
+ };
459
+ case "/api/job":
460
+ return { id: null, running: false };
461
+ default:
462
+ return undefined;
463
+ }
464
+ };
465
+
466
+ // ── the states below are referenced above; declared after for readability ───
467
+
468
+ // The POST half (v0.50.0). Almost every mutation in fixture mode answers "nothing
469
+ // ran" and that is the honest answer — but the CONNECTION TEST'S OUTCOME is a
470
+ // state this panel is largely about, and a state you cannot design if the wire
471
+ // never lands. So exactly one route has canned answers, chosen deterministically
472
+ // from the profile so they agree with what the list already claims.
473
+ //
474
+ // A route with no entry here still gets the ordinary "nothing ran" reply.
475
+ module.exports.post = function post(route, body) {
476
+ // `add` answers OK so the CONNECT FLOW can be walked end to end in fixture
477
+ // mode the add is only the step before the test, and the test's outcome is
478
+ // the state worth designing. Every other write still answers "nothing ran".
479
+ if (route === "/api/extra/add") return { exit_code: 0, data: { ok: true, next: "orc extra ping" } };
480
+ // v0.51.0 a launch that could NOT happen and one that did. Both are exit 0,
481
+ // because "no terminal here" is an ANSWER carrying the command to paste.
482
+ if (route === "/api/extra/install")
483
+ return {
484
+ exit_code: 0,
485
+ data: (body && body.provider) === "codex" ? extraInstall.refused : extraInstall.launched,
486
+ };
487
+ if (route === "/api/extra/models/test")
488
+ return { exit_code: 0, data: { ok: true, test: extraPingLive } };
489
+ if (route !== "/api/extra/ping") return undefined;
490
+ const profile = String((body && body.profile) || "");
491
+ if (body && body.key && !body.passphrase) return { exit_code: 0, data: { ...extraPingSaveOffer, profile } };
492
+ // v0.51.0 — the PAID rung has THREE outcomes worth designing and each is
493
+ // reachable: it worked and still cannot say which model answered, the model
494
+ // was LISTED and is DEAD upstream, and the tool is not installed at all.
495
+ if (route === "/api/extra/ping" && body && body.live) {
496
+ if (profile === "toolc") return { exit_code: 1, data: { ...extraPingNotInstalled, profile } };
497
+ if (String(body.model || "").indexOf("free") !== -1)
498
+ return { exit_code: 1, data: { ...extraPingDeadModel, profile } };
499
+ return { exit_code: 0, data: { ...extraPingLive, profile: profile || extraPingLive.profile } };
500
+ }
501
+ if (profile === "router" || profile === "burned") return { exit_code: 1, data: { ...extraPingBad, profile } };
502
+ return { exit_code: 0, data: { ...extraPingOk, profile: profile || extraPingOk.profile } };
503
+ };