@azure-id/orc 1.7.1 → 1.8.1

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