@azure-id/orc 2.0.2 → 2.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 (121) hide show
  1. package/CHANGELOG.md +401 -0
  2. package/README-id.md +52 -23
  3. package/README.md +50 -22
  4. package/bin/build-agents.js +24 -25
  5. package/bin/clear-logs.js +620 -0
  6. package/bin/cli.js +1155 -158
  7. package/bin/fix.js +459 -0
  8. package/bin/gotcha.js +288 -8
  9. package/bin/habit.js +1538 -1453
  10. package/bin/mockrun-catalog.js +3 -0
  11. package/bin/onboarding-content.js +2 -1
  12. package/bin/pricing.json +28 -21
  13. package/bin/trace-write.js +869 -657
  14. package/bin/verify-contracts.js +108 -27
  15. package/bin/verify-package.js +17 -6
  16. package/bin/webui/api.js +125 -6
  17. package/bin/webui/css/panels/hookui.css +79 -0
  18. package/bin/webui/fixtures/behaviour.js +30 -0
  19. package/bin/webui/fixtures/extra.js +9 -9
  20. package/bin/webui/fixtures/flow.js +81 -81
  21. package/bin/webui/fixtures/hookui.js +506 -431
  22. package/bin/webui/fixtures/index.js +32 -3
  23. package/bin/webui/fixtures/knowledge.js +2 -2
  24. package/bin/webui/fixtures/maintenance.js +123 -42
  25. package/bin/webui/fixtures/rules.js +1007 -1002
  26. package/bin/webui/fixtures/settings.js +6 -7
  27. package/bin/webui/fixtures/stats.js +9 -6
  28. package/bin/webui/fixtures/wait.js +98 -97
  29. package/bin/webui/i18n/en/behaviour.json +13 -0
  30. package/bin/webui/i18n/en/hookui.json +290 -194
  31. package/bin/webui/i18n/en/maintenance.json +71 -52
  32. package/bin/webui/i18n/en/settings.json +60 -60
  33. package/bin/webui/i18n/id/behaviour.json +13 -0
  34. package/bin/webui/i18n/id/hookui.json +290 -194
  35. package/bin/webui/i18n/id/maintenance.json +71 -52
  36. package/bin/webui/i18n/id/settings.json +60 -60
  37. package/bin/webui/js/05-banners.js +181 -172
  38. package/bin/webui/js/06-edit.js +193 -183
  39. package/bin/webui/js/panels/behaviour.js +77 -12
  40. package/bin/webui/js/panels/hookui.js +2196 -1691
  41. package/bin/webui/js/panels/maintenance.js +336 -235
  42. package/bin/webui/js/panels/settings.js +726 -719
  43. package/mock-run/INDEX.md +1 -0
  44. package/mock-run/extra-slots.md +1 -1
  45. package/mock-run/habits.md +1 -1
  46. package/mock-run/orc-aftermath.md +392 -392
  47. package/mock-run/orc-budget.md +29 -33
  48. package/mock-run/orc-cli.md +200 -200
  49. package/mock-run/orc-explain.md +86 -86
  50. package/mock-run/orc-extra.md +389 -392
  51. package/mock-run/orc-fix.md +87 -0
  52. package/mock-run/orc-pattern.md +112 -112
  53. package/mock-run/orc.md +157 -157
  54. package/package.json +1 -1
  55. package/templates/agents/MODEL-MAPPING.md +32 -29
  56. package/templates/agents/orc-analyze-mini-sonnet-5-high.md +2 -2
  57. package/templates/agents/{orc-claude-writer-opus-5-med.md → orc-claude-writer-opus-5-low.md} +4 -4
  58. package/templates/agents/orc-executor-haiku-4-5.md +1 -1
  59. package/templates/agents/orc-executor-opus-5-low.md +1 -1
  60. package/templates/agents/orc-executor-sonnet-4-6-high.md +1 -1
  61. package/templates/agents/orc-executor-sonnet-4-6-med.md +1 -1
  62. package/templates/agents/orc-executor-sonnet-5-high.md +2 -2
  63. package/templates/agents/orc-executor-sonnet-5-low.md +155 -0
  64. package/templates/agents/orc-executor-sonnet-5-med.md +155 -0
  65. package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +2 -2
  66. package/templates/agents/orc-planner-mini-sonnet-5-high.md +2 -2
  67. package/templates/agents/orc-retro-sonnet-5-high.md +2 -2
  68. package/templates/agents/{orc-reviewer-opus-5-med.md → orc-reviewer-opus-5-low.md} +4 -4
  69. package/templates/agents/{orc-wiki-scanner-opus-5-med.md → orc-wiki-scanner-opus-5-low.md} +3 -3
  70. package/templates/agents/orc-wiki-scanner-sonnet-5-high.md +2 -2
  71. package/templates/commands/orc-fix.md +18 -0
  72. package/templates/commands/orc-wiki.md +64 -64
  73. package/templates/hooks/README.md +75 -7
  74. package/templates/hooks/orc-session-hook.js +78 -3
  75. package/templates/hooks/orc-statusline-render.js +317 -23
  76. package/templates/hooks/orc-statusline.js +162 -49
  77. package/templates/hooks/orc-subagent-line.js +23 -5
  78. package/templates/hooks/orc-weather-fetch.js +123 -0
  79. package/templates/skills/_shared/extra-dispatch.md +5 -5
  80. package/templates/skills/_shared/gotchas.md +27 -7
  81. package/templates/skills/_shared/habits.md +2 -2
  82. package/templates/skills/_shared/opus5-only.md +14 -14
  83. package/templates/skills/_shared/phases/review.md +2 -2
  84. package/templates/skills/_shared/phases/trace-verbs.md +17 -3
  85. package/templates/skills/_shared/phases/trace.md +136 -136
  86. package/templates/skills/_shared/review-slice.md +28 -1
  87. package/templates/skills/_shared/wait.md +1 -0
  88. package/templates/skills/orc/README.md +2 -2
  89. package/templates/skills/orc/SKILL.md +1 -1
  90. package/templates/skills/orc/config.md +25 -27
  91. package/templates/skills/orc/examples/full-run-mock.md +2 -1
  92. package/templates/skills/orc/references/effort-and-mode.md +17 -17
  93. package/templates/skills/orc/references/preflight-report.md +3 -3
  94. package/templates/skills/orc/references/ultra-mode.md +1 -1
  95. package/templates/skills/orc/schemas/checkpoint.md +122 -122
  96. package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +3 -3
  97. package/templates/skills/orc/subskills/orc-review-verify/core.md +1 -1
  98. package/templates/skills/orc-analyze/SKILL.md +4 -2
  99. package/templates/skills/orc-claude/SKILL.md +14 -14
  100. package/templates/skills/orc-claude/examples/claude-run-mock.md +65 -65
  101. package/templates/skills/orc-diy/SKILL.md +5 -5
  102. package/templates/skills/orc-fast/SKILL.md +8 -6
  103. package/templates/skills/orc-fix/SKILL.md +110 -0
  104. package/templates/skills/orc-mini/SKILL.md +4 -4
  105. package/templates/skills/orc-mini/examples/mini-run-mock.md +4 -2
  106. package/templates/skills/orc-quick/README.md +1 -1
  107. package/templates/skills/orc-quick/SKILL.md +2 -2
  108. package/templates/skills/orc-quick/references/dispatch-gate.md +3 -2
  109. package/templates/skills/orc-quick/references/gh-mode.md +4 -2
  110. package/templates/skills/orc-retro/SKILL.md +1 -1
  111. package/templates/skills/orc-retro/examples/retro-mock.md +16 -16
  112. package/templates/skills/orc-wiki/README.md +325 -325
  113. package/templates/skills/orc-wiki/SKILL.md +195 -195
  114. package/templates/skills/orc-wiki/examples/wiki-run-mock.md +37 -37
  115. package/templates/skills/orc-wiki/references/extra.md +2 -2
  116. package/templates/skills/orc-wiki/references/partial-refresh.md +276 -271
  117. package/templates/skills/orc-wiki/references/phases/phase-0.md +1 -1
  118. package/templates/skills/orc-wiki/references/phases/phase-2.md +3 -3
  119. package/templates/skills/orc-wiki/references/phases/phase-3c.md +3 -2
  120. package/templates/agents/orc-claude-writer-opus-4-8-high.md +0 -48
  121. package/templates/agents/orc-wiki-scanner-opus-4-8-high.md +0 -74
@@ -1,64 +1,64 @@
1
- ---
2
- description: Build/maintain the project knowledge base (wiki) — Opus 4.8 high, expensive, multi-session, warns before scanning
3
- ---
4
-
5
- Use the **orc-wiki** skill. Start at Phase 0: auto-detect whether this is a
6
- repair of an unregistered wiki, a fresh build, a resume of an in-progress scan,
7
- or a refresh of an existing wiki, and branch accordingly. On a fresh build, show
8
- the cost warning and scan NOTHING until the user explicitly confirms. Refreshes
9
- also sweep for coverage gaps (changed files no doc covers → propose new areas)
10
- and dead docs (covered files gone → archive/delete, user decides). Every run
11
- registers via `orc wiki sync` and ends with the integrity self-check. Follow the
12
- skill's SKILL.md exactly.
13
-
14
- **Crosslink is always on.** Plain `/orc-wiki` — every scan, resume, incremental,
15
- full regenerate, or selective refresh — publishes/updates this repo's boundary
16
- tags (`wiki/crosslink/`) in the SAME pass as the docs, and a refresh never
17
- deletes them. There is no separate "generate crosslink" step in a wiki's normal
18
- life; the only crosslink config is the graph (`orc crosslink`), needed only to
19
- resolve what this repo consumes.
20
-
21
- **`/orc-wiki crosslink`** → the CROSSLINK-ONLY branch (Phase 3c) is a LEGACY
22
- BACKFILL: publish tags + resolve consumption from the existing docs'
23
- `Contracts & shapes` rows (opening only the files they anchor), for wikis whose
24
- docs predate v0.24.0 and so have no `wiki/crosslink/` yet. No area is re-scanned,
25
- no doc rewritten. On a wiki scanned at ≥v0.24.0, missing tags are not this
26
- branch — `orc wiki sync --check` names the real fix. Never a refresh.
27
-
28
- **`/orc-wiki crosslink compile`** → the one-shot CROSSLINK COMPILE branch
29
- (v0.33.0 — references/crosslink-compile.md): resolve/consume from the graph →
30
- generate the federation atlas (`wiki/crosslink/atlas.md`) → write it into each
31
- linked repo → inject/update the CLAUDE.md pointer block locally and in each
32
- linked repo (in-place, user content byte-preserved). Requires a crosslink
33
- config with ≥1 edge; every step warn-only; never a re-scan. Peer writes are
34
- FILE writes only — never a commit or push.
35
-
36
- **`/orc-wiki update <doc-or-topic>`** → ONE doc, re-scanned. A filename goes
37
- straight into the targeted refresh. A topic ("the remittance feature") is
38
- resolved first with `orc wiki resolve` — free, no scan — and an AMBIGUOUS result
39
- is a question for you, never a guess. Branch detection and area planning are both
40
- skipped: the doc exists, so its coverage area is already in its own header.
41
-
42
- **`/orc-wiki add "<topic>"`** → ONE new doc, in a wiki that already exists, with
43
- no other area re-planned. It resolves the topic first (if something already
44
- covers it, that is an `update` and it says so), proposes a coverage scope from
45
- path names for you to correct, confirms the slug, the covers, the tier and the
46
- cost in ONE turn, and scans nothing until you say yes. It ends with
47
- `orc wiki refs`, the derived-reference sweep — adding a doc moves more derived
48
- surfaces than changing one does: the registration, the orientation page, the
49
- architecture overview, the CLAUDE.md pointer's doc count, and any crosslink tag
50
- left anchored to a file that is gone. Only the registration is repaired for you;
51
- the rest is reported with its command, because a sweep that silently regenerated
52
- prose would be spending money nobody asked it to spend.
53
-
54
- | Argument | Branch |
55
- |---|---|
56
- | *(none)* | Phase 0 auto-branch: repair / fresh / resume / refresh (delta default) |
57
- | `update <doc>` | targeted refresh of ONE doc (skips branch detection + area planning) |
58
- | `update "<topic>"` | resolve the topic to a doc, then refresh it |
59
- | `add "<topic>"` | reserve + scan + write ONE new doc, then sweep every derived reference |
60
- | `crosslink` | Phase 3c legacy backfill (publish tags from existing docs) |
61
- | `crosslink compile` | one-shot resolve + atlas + CLAUDE.md injection (local + peers) |
62
-
63
- Optional focus (`update <doc>` · `add "<topic>"` · `crosslink` ·
64
- `crosslink compile`): $ARGUMENTS
1
+ ---
2
+ description: Build/maintain the project knowledge base (wiki) — Opus 5.5 low, expensive, multi-session, warns before scanning
3
+ ---
4
+
5
+ Use the **orc-wiki** skill. Start at Phase 0: auto-detect whether this is a
6
+ repair of an unregistered wiki, a fresh build, a resume of an in-progress scan,
7
+ or a refresh of an existing wiki, and branch accordingly. On a fresh build, show
8
+ the cost warning and scan NOTHING until the user explicitly confirms. Refreshes
9
+ also sweep for coverage gaps (changed files no doc covers → propose new areas)
10
+ and dead docs (covered files gone → archive/delete, user decides). Every run
11
+ registers via `orc wiki sync` and ends with the integrity self-check. Follow the
12
+ skill's SKILL.md exactly.
13
+
14
+ **Crosslink is always on.** Plain `/orc-wiki` — every scan, resume, incremental,
15
+ full regenerate, or selective refresh — publishes/updates this repo's boundary
16
+ tags (`wiki/crosslink/`) in the SAME pass as the docs, and a refresh never
17
+ deletes them. There is no separate "generate crosslink" step in a wiki's normal
18
+ life; the only crosslink config is the graph (`orc crosslink`), needed only to
19
+ resolve what this repo consumes.
20
+
21
+ **`/orc-wiki crosslink`** → the CROSSLINK-ONLY branch (Phase 3c) is a LEGACY
22
+ BACKFILL: publish tags + resolve consumption from the existing docs'
23
+ `Contracts & shapes` rows (opening only the files they anchor), for wikis whose
24
+ docs predate v0.24.0 and so have no `wiki/crosslink/` yet. No area is re-scanned,
25
+ no doc rewritten. On a wiki scanned at ≥v0.24.0, missing tags are not this
26
+ branch — `orc wiki sync --check` names the real fix. Never a refresh.
27
+
28
+ **`/orc-wiki crosslink compile`** → the one-shot CROSSLINK COMPILE branch
29
+ (v0.33.0 — references/crosslink-compile.md): resolve/consume from the graph →
30
+ generate the federation atlas (`wiki/crosslink/atlas.md`) → write it into each
31
+ linked repo → inject/update the CLAUDE.md pointer block locally and in each
32
+ linked repo (in-place, user content byte-preserved). Requires a crosslink
33
+ config with ≥1 edge; every step warn-only; never a re-scan. Peer writes are
34
+ FILE writes only — never a commit or push.
35
+
36
+ **`/orc-wiki update <doc-or-topic>`** → ONE doc, re-scanned. A filename goes
37
+ straight into the targeted refresh. A topic ("the remittance feature") is
38
+ resolved first with `orc wiki resolve` — free, no scan — and an AMBIGUOUS result
39
+ is a question for you, never a guess. Branch detection and area planning are both
40
+ skipped: the doc exists, so its coverage area is already in its own header.
41
+
42
+ **`/orc-wiki add "<topic>"`** → ONE new doc, in a wiki that already exists, with
43
+ no other area re-planned. It resolves the topic first (if something already
44
+ covers it, that is an `update` and it says so), proposes a coverage scope from
45
+ path names for you to correct, confirms the slug, the covers, the tier and the
46
+ cost in ONE turn, and scans nothing until you say yes. It ends with
47
+ `orc wiki refs`, the derived-reference sweep — adding a doc moves more derived
48
+ surfaces than changing one does: the registration, the orientation page, the
49
+ architecture overview, the CLAUDE.md pointer's doc count, and any crosslink tag
50
+ left anchored to a file that is gone. Only the registration is repaired for you;
51
+ the rest is reported with its command, because a sweep that silently regenerated
52
+ prose would be spending money nobody asked it to spend.
53
+
54
+ | Argument | Branch |
55
+ |---|---|
56
+ | *(none)* | Phase 0 auto-branch: repair / fresh / resume / refresh (delta default) |
57
+ | `update <doc>` | targeted refresh of ONE doc (skips branch detection + area planning) |
58
+ | `update "<topic>"` | resolve the topic to a doc, then refresh it |
59
+ | `add "<topic>"` | reserve + scan + write ONE new doc, then sweep every derived reference |
60
+ | `crosslink` | Phase 3c legacy backfill (publish tags from existing docs) |
61
+ | `crosslink compile` | one-shot resolve + atlas + CLAUDE.md injection (local + peers) |
62
+
63
+ Optional focus (`update <doc>` · `add "<topic>"` · `crosslink` ·
64
+ `crosslink compile`): $ARGUMENTS
@@ -214,18 +214,76 @@ move, and it tells you why.
214
214
  Every component can be changed: its words, its colour, its shape, how wide it
215
215
  is, and when it is allowed to disappear.
216
216
 
217
- **The shape decides what the other settings can do.** Only these shapes print a
218
- short name in front of the value: `plain`, `label-value`, `bracket`, `angle`,
219
- `badge`, `pill` and `stack`. A bar, an icon and a bare value have no room for
220
- one, so the panel switches the Name box off for them and says so. The same is
221
- true of the number settings: a bar has a width and a colour ramp, and no number
222
- format at all.
217
+ **The shape decides what the other settings can do.** The panel switches off a
218
+ setting that the shape does not use, and says so.
219
+
220
+ **Words around any shape** (v2.0.4). Every shape draws the name you type. You
221
+ choose where it goes: before, after, above or below the part. A bar can also have
222
+ text before and after it (for example `[██░░]`) and its number beside it. If you
223
+ type no name, only `plain`, `label-value`, `bracket`, `angle`, `badge`, `pill` and
224
+ `stack` print the part's built-in name.
225
+
226
+ **Captions.** A caption is a short text on a row above or below a part, at the
227
+ column of that part. `{value}` and `{label}` in the caption are replaced. A caption
228
+ makes the line one row taller.
229
+
230
+ **Small text.** A terminal has no font size. The letter styles `small` (ꜱᴍᴀʟʟ
231
+ ᴄᴀᴘꜱ), `super` (ˢᵘᵖᵉʳ) and `sub` (₆₁) use Unicode letters that look smaller.
232
+ A character with no small form stays as it is.
233
+
234
+ **Bars in your own colours and characters.** A bar can have its own fill colour,
235
+ empty colour, colour range (for example `#22c55e,#eab308,#ef4444`, blended cell by
236
+ cell on `gradient`), and its own fill and empty characters (one cell each).
237
+
238
+ **Every part** can also have brackets, space on the left and right, a background,
239
+ any `#rrggbb` colour, more than one weight, and its own colour and symbol per
240
+ state. A line can be aligned right or centre, and its separator can have a colour.
241
+ `text`, `divider`, `spacer`, `icon-static`, `fill` (pushes the rest to the right
242
+ edge) and `config` take their text in the part's settings.
243
+
244
+ **Claude Code removes the spaces at the start of every row.** So when a row of
245
+ your status line starts with a space (a caption row, a line prefix, a
246
+ right-aligned line), ORC makes that first space a braille blank (U+2800). It is one
247
+ cell wide and Claude Code keeps it, so a caption stays above or below its own part.
248
+ Under `ORC_STATUSLINE_ASCII=1` this is not done, because U+2800 is not ASCII.
249
+
250
+ **Pets that move** (the "Pets and motion" group): a pixel cat drawn in braille
251
+ dots, a mouse, a cat that chases the mouse, the text cat ᓚᘏᗢ, pac-man, a fish, a
252
+ bird and a dog. Each can `run` across its track, `bounce` right and back, or stay
253
+ `idle` and move its legs.
254
+
255
+ **Motion needs a timer.** Claude Code redraws the status line when something
256
+ happens (a message, typing), or every `refreshInterval` seconds if you set it (at
257
+ least 1). Set it with:
258
+
259
+ ```
260
+ orc statusline refresh 1
261
+ ```
262
+
263
+ and remove it with `orc statusline refresh off`. With the timer the hook runs
264
+ every second even when you do nothing, and each run starts `node`. So a pet moves
265
+ about one step per second.
266
+
267
+ **Weather** (`weather`, `weather-icon`, `weather-desc`). This is the ONLY part of
268
+ the status line that uses the network. The hook itself never waits for it: it reads
269
+ `.claude/orc/weather.json`, and when that is older than 30 minutes it starts
270
+ `orc-weather-fetch.js` in the background, which asks wttr.in and writes the file.
271
+ With an empty `location`, wttr.in finds your place from your network address. It
272
+ runs only while a weather part is on your layout. `ORC_STATUSLINE_NO_NET=1` stops
273
+ it. Every text it gets back is cleaned of control characters before it is saved.
274
+
275
+ **The panel shows your unsaved changes.** The picture above the board is drawn
276
+ from a temporary copy with your staged changes, so you see the result before you
277
+ press Apply. Nothing is written until you press Apply.
278
+
279
+ A layout may not carry a control character (for example ESC) in any text. It is
280
+ printed to your terminal, so the CLI refuses it.
223
281
 
224
282
  **Three things a terminal cannot do**, said plainly so you do not look for them:
225
283
 
226
284
  | You may want | What you get |
227
285
  |---|---|
228
- | A bigger font | The terminal owns the font size. Use **bold**, or make a component wider — a bar at width 12 is a big object. |
286
+ | A bigger font | The terminal owns the font size. Use **bold**, a small letter style for the words around it, or make a component wider — a bar at width 12 is a big object. |
229
287
  | A blinking part | Refused. Half of terminals turn it off, and no part of a status line needs it. |
230
288
  | Icon-font symbols | Not shipped. They need a font we cannot check for, and they draw as empty boxes without it. |
231
289
 
@@ -472,6 +530,16 @@ It never writes model text, and it blocks a stop only for the narration guard.
472
530
  authored. If the compiled file is missing, stale, or does not pass a cheap
473
531
  shape check, the hook silently renders the shipped lines instead and
474
532
  `orc doctor` names the reason.
533
+ - **Stale means the parts changed, not the version** (v2.0.3). `orc init` and
534
+ `orc update` write `catalog_hash` into `hooks/orc-version.json`, and the hook
535
+ compares it with the `catalog_hash` in `statusline.lock.json`. An install with
536
+ no hash compares the version, as before. `orc update` also recompiles every
537
+ layout that exists and validates, so an upgrade never leaves your bar on the
538
+ shipped lines. A good render deletes `orc/statusline-state.json`, so
539
+ `orc doctor` never reports a fallback that is already gone.
540
+ - `orc statusline show --json` carries `hook`: the hook the settings point to,
541
+ its version, whether it will run this layout, the last fallback, and the fix.
542
+ `orc ui` shows that on the status card.
475
543
  - The cache file is `.claude/orc/usage-session.json`. The hook reads it once and
476
544
  writes it once, after the text is ready. It stores raw numbers only — never a
477
545
  word like `fresh` or `STALE`, which is computed each time it is shown.
@@ -113,10 +113,45 @@ function narrationGuard(data) {
113
113
  } catch (_) {
114
114
  return false;
115
115
  }
116
- const lines = text.split(/\r?\n/).filter((l) => /^\[/.test(l));
116
+ const all = text.split(/\r?\n/).filter((l) => l.trim());
117
+ const lines = all.filter((l) => /^\[/.test(l));
117
118
  if (!lines.some((l) => /\]\s+hook\s+SPAWN /.test(l))) return false; // nothing was dispatched yet
118
- if (lines.some((l) => !/\]\s+hook\s+/.test(l))) return false; // a packet already landed
119
119
  const statePath = path.join(CLAUDE_DIR, "orc", "narration-guard.json");
120
+ // v2.1.0 (E21 D7) — narration written BY HAND. A live /orc-quick appended its
121
+ // lines itself (no `.mmm` stamp, or no stamp at all), so `orc trace write` never
122
+ // saw them: no grammar check, and none of the FINISH reminders (observe, which
123
+ // review, config, phases) ran — the lane then claimed the findings were
124
+ // "logged". The CLI writes every line to the .txt AND its .jsonl twin, so more
125
+ // narration lines than jsonl rows, or a line without the CLI's stamp, is proof.
126
+ const narr = all.filter((l) => !/^\[[^\]]*\]\s+hook\s+/.test(l));
127
+ if (narr.length) {
128
+ let rows = 0;
129
+ try {
130
+ rows = fs.readFileSync(path.join(dir, best.f + ".jsonl"), "utf8").split(/\r?\n/).filter((l) => l.trim()).length;
131
+ } catch (_) {}
132
+ // NOT the `.mmm` stamp: `orc trace write` also accepts HH:MM:SS (a live run
133
+ // proved it, and an early guard blocked a correct run on it).
134
+ const hand = narr.some((l) => !/^\[\d{6} \d{2}:\d{2}:\d{2}(?:\.\d{1,3})?\] /.test(l)) || narr.length > rows;
135
+ if (!hand) return reviewGuard(best, text, statePath); // a CLI packet already landed
136
+ try {
137
+ const st = JSON.parse(fs.readFileSync(statePath, "utf8"));
138
+ if (st.run === best.f && st.kind === "hand") return false;
139
+ } catch (_) {}
140
+ try {
141
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
142
+ fs.writeFileSync(statePath, JSON.stringify({ run: best.f, kind: "hand", at: Date.now() }) + "\n");
143
+ } catch (_) {}
144
+ process.stdout.write(
145
+ JSON.stringify({
146
+ decision: "block",
147
+ reason:
148
+ `ORC: the trace ${best.f} has lines written by hand — \`orc trace write\` never saw them, so no check and no FINISH reminder ran. ` +
149
+ `Never append to a trace yourself. Send the FINISH packet now through \`orc trace write --packet -\` (\`run: ${best.f.replace(/\.txt$/, "")}\` if .current is gone) ` +
150
+ `and do what its reply says (for a review: one \`orc gotcha observe\` per finding), then finish.`,
151
+ })
152
+ );
153
+ return true;
154
+ }
120
155
  try {
121
156
  if (JSON.parse(fs.readFileSync(statePath, "utf8")).run === best.f) return false;
122
157
  } catch (_) {}
@@ -136,8 +171,48 @@ function narrationGuard(data) {
136
171
  return true;
137
172
  }
138
173
 
174
+ // v2.1.0 (E21 D8) — a FINISHED run that reviewed and recorded nothing. A live
175
+ // /orc ignored the FINISH observe reminder after a review with findings; Review
176
+ // Quality then counts the review but learns no acceptance from it. ONCE per run,
177
+ // in the main session: the trace has a reviewer RETURN and a FINISH line, the
178
+ // review was not clean, and no observation names this run → block one stop.
179
+ function reviewGuard(best, text, statePath) {
180
+ if (!/\]\s+hook\s+RETURN\s+orc-reviewer-/.test(text) || !/\]\s+\S+\s+FINISH\b/.test(text)) return false;
181
+ const fo = /FINDING-OUTCOME addressed=(\d+) disputed=(\d+) wontfix=(\d+) open=(\d+)/.exec(text);
182
+ const f = /\]\s+\S+\s+FINDING p0=(\d+) p1=(\d+) p2=(\d+) p3=(\d+)/.exec(text);
183
+ if ((fo && fo.slice(1, 5).every((n) => n === "0")) || (!fo && f && f.slice(1, 5).every((n) => n === "0"))) return false; // clean
184
+ const run = best.f.replace(/\.txt$/, "");
185
+ try {
186
+ for (const line of fs.readFileSync(path.join(CLAUDE_DIR, "orc", "observations.jsonl"), "utf8").split(/\r?\n/)) {
187
+ if (!line.trim()) continue;
188
+ try {
189
+ const o = JSON.parse(line);
190
+ if (o && o.author === "orc" && o.run === run) return false;
191
+ } catch (_) {}
192
+ }
193
+ } catch (_) {}
194
+ try {
195
+ const st = JSON.parse(fs.readFileSync(statePath, "utf8"));
196
+ if (st.run === best.f && st.kind === "review") return false;
197
+ } catch (_) {}
198
+ try {
199
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
200
+ fs.writeFileSync(statePath, JSON.stringify({ run: best.f, kind: "review", at: Date.now() }) + "\n");
201
+ } catch (_) {}
202
+ process.stdout.write(
203
+ JSON.stringify({
204
+ decision: "block",
205
+ reason:
206
+ `ORC: the run ${run} ran a review with findings and recorded no outcome. Pipe ONE object per finding to \`orc gotcha observe - --json\` ` +
207
+ `(source review · author orc · run ${run} · ref "${run} · F<n>" · outcome addressed|disputed|wontfix|open · category · rule or sig), ` +
208
+ `and write the FINDING-OUTCOME line if it is missing (\`orc trace write --packet -\`, run: ${run}). Then finish.`,
209
+ })
210
+ );
211
+ return true;
212
+ }
213
+
139
214
  // ── The review card at SubagentStart (v2.0.2, eval D13) ─────────────────────
140
- // A live /orc-quick review dispatched `orc-reviewer-opus-5-med` with NO gotcha
215
+ // A live /orc-quick review dispatched `orc-reviewer-opus-5-low` with NO gotcha
141
216
  // card in its prompt in 5 of 5 runs, although review learning is always on. The
142
217
  // card is a CLI answer, so the hook hands it over itself: for a reviewer,
143
218
  // verifier or judge, the files git sees as changed → `orc gotcha card` → one