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