@azure-id/orc 1.2.0 → 1.2.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.
- package/CHANGELOG.md +145 -0
- package/README.md +50 -66
- package/bin/cli.js +186 -1
- package/bin/verify-contracts.js +25 -0
- package/bin/verify-package.js +573 -568
- package/package.json +1 -1
- package/templates/hooks/README.md +202 -0
- package/templates/hooks/orc-statusline.js +377 -21
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,151 @@ Format: `### v<version> — <title> _(<date>)_`.
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
### v1.2.1 - the status line says what ORC is doing _(2026-09-04)_
|
|
14
|
+
|
|
15
|
+
**Still on the unscoped `orc` package?** Do this once first — your `orc upgrade`
|
|
16
|
+
is the pre-v0.56.0 one and cannot install itself. Full detail in the CAUTION at
|
|
17
|
+
the top of this file.
|
|
18
|
+
|
|
19
|
+
- **Step 1 — release the command from the old package:** `npm uninstall -g orc`
|
|
20
|
+
- **Step 2 — install the current package:** `npm i -g @azure-id/orc`
|
|
21
|
+
- **Step 3 — re-apply it to your project:** `orc update`
|
|
22
|
+
|
|
23
|
+
**Do not use `npm i -g -f`.** Full detail in v0.56.0 below.
|
|
24
|
+
|
|
25
|
+
v1.2.0 gave the status line a second row and it said which lanes had run. It
|
|
26
|
+
could not say what any of them was doing *now* — which is the one thing you look
|
|
27
|
+
down at a status bar to find out. This release puts the running phase in front
|
|
28
|
+
of that row, gives each kind of phase its own small animated mark (the `orc-cli`
|
|
29
|
+
lane-menu badge, keyed to the phase instead of the lane), and closes the row with
|
|
30
|
+
the two facts a terminal should always carry: what the session has cost and which
|
|
31
|
+
branch it is on.
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
🚀 ORC v1.2.1 - Opus 5/high · context (34%) · 5h 41% (2h13m) ↔ wk 12% · ucs 6% · wiki: fresh
|
|
35
|
+
▰ status: quick · Q3 DO · agents 7 (2 running) · orc-extra: on · Dur 48m · MTok 412K · main
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### The phase is computed by the CLI and rendered by the hook
|
|
39
|
+
|
|
40
|
+
`orc init` / `orc update` now stamps **`hooks/orc-lane-rails.json`** beside
|
|
41
|
+
`hooks/orc-version.json`, built from the same `LANE_PHASES` / `LANE_OWN_PHASES`
|
|
42
|
+
/ `LANE_TRACE` registries `orc lane phases` reads. The hook renders it and
|
|
43
|
+
derives nothing — the Flow-stepper rule (`orc ui` v0.43.7) on a second surface.
|
|
44
|
+
|
|
45
|
+
The hook cannot call the CLI: a status line re-renders on every keystroke, so a
|
|
46
|
+
subprocess per render is not on the table. The alternative was a phase table
|
|
47
|
+
hardcoded in the hook, which is a **second source of truth no lint could see**.
|
|
48
|
+
The manifest is a registered contract token instead, plus three goldens: every
|
|
49
|
+
phase has a label and a kind from the closed set, the rail names every traced
|
|
50
|
+
lane **and no lane it does not**, and its role families are asserted against the
|
|
51
|
+
trace hook's own `roleFamily()` **by source text** (the `OPUS5_BANDS` technique
|
|
52
|
+
— the two live in different files, so no import can tie them).
|
|
53
|
+
|
|
54
|
+
New read: **`orc lane rails [--json]`**, which prints the whole object.
|
|
55
|
+
|
|
56
|
+
#### A phase the disk cannot prove is HIDDEN
|
|
57
|
+
|
|
58
|
+
Four rungs, most recent line wins, and the floor is deterministic:
|
|
59
|
+
|
|
60
|
+
1. the active run comes from `.current` — not "the newest file", which is a
|
|
61
|
+
different and wrong answer during a lane suspend, when two traces are live;
|
|
62
|
+
2. a narrated trace verb **this lane's rail publishes** resolves the own-phase
|
|
63
|
+
(`Q3 DO`) — but only when it is later in the file than the edge below it, so
|
|
64
|
+
it can sharpen the answer and can never invent one;
|
|
65
|
+
3. the trace hook's `PHASE-EDGE <family>` resolves the shared phase
|
|
66
|
+
(`execution`) with **zero model cooperation**;
|
|
67
|
+
4. nothing, or a trace whose newest line is over 10 minutes old → **hidden**.
|
|
68
|
+
|
|
69
|
+
**The cost is stated rather than papered over.** A phase that dispatches nothing
|
|
70
|
+
and narrates nothing is invisible: `/orc-quick` `Q1 LOOK` and `Q2 ASK`, and every
|
|
71
|
+
ask-the-user gate in every lane. Covering those would mean asking 24 spines to
|
|
72
|
+
write a phase marker at phase open — the **remembered-not-dispatched** bet this
|
|
73
|
+
repo has already lost five times (v0.32.0 narration, v0.49.5 hand-back, v0.53.2
|
|
74
|
+
spend log, v0.54.0 journal, v1.0.0 W5 demotion). A `status:` that is right when
|
|
75
|
+
the model is diligent and silently wrong when it is not is worse than one that is
|
|
76
|
+
sometimes blank, because **a stale phase word gets believed** — the same
|
|
77
|
+
reasoning as *unknown is not low* and *unknown is not zero*.
|
|
78
|
+
|
|
79
|
+
Two more limits inherited and restated: a **continued** agent emits no
|
|
80
|
+
PreToolUse/SubagentStop pair, so the skeleton is a floor and never a census; and
|
|
81
|
+
`orc extra` runs its worker through Bash, so a foreign wave writes no `SPAWN` and
|
|
82
|
+
resolves only through its narrated `EXTRA` verb, or hides.
|
|
83
|
+
|
|
84
|
+
#### The animation is a liveness tell, not a progress bar
|
|
85
|
+
|
|
86
|
+
Eight motif kinds — `look` · `ask` · `plan` · `do` · `check` · `ship` · `wait` ·
|
|
87
|
+
`generic` — each one cell wide, each with an ASCII twin. A phase the kind table
|
|
88
|
+
does not name gets `generic`, the honest sweep, **not a guess**.
|
|
89
|
+
|
|
90
|
+
A status line is a **pull** surface: Claude Code re-renders it, ORC cannot. So
|
|
91
|
+
the frame is picked off the wall clock. It advances while you type and while
|
|
92
|
+
turns land, and it **freezes when the session is idle** — which is true, and is
|
|
93
|
+
why frame 0 is designed as a still frame.
|
|
94
|
+
|
|
95
|
+
- `ORC_STATUSLINE_MOTION=0` **removes** the motion rather than slowing it. A
|
|
96
|
+
frozen frame of a cycling animation is a bug that looks like a hang; the web
|
|
97
|
+
panel learned that at v0.44.0 and we are not learning it twice.
|
|
98
|
+
- `ORC_STATUSLINE_ASCII=1` swaps the glyph set.
|
|
99
|
+
|
|
100
|
+
#### `MTok` — MAIN token, and what it cannot see
|
|
101
|
+
|
|
102
|
+
The tokens **this session's own turns** consumed, summed from the session
|
|
103
|
+
transcript's `usage` blocks and read **incrementally** (only the bytes the file
|
|
104
|
+
has grown by — a transcript reaches tens of megabytes and this loop runs every
|
|
105
|
+
five seconds).
|
|
106
|
+
|
|
107
|
+
**Claude Code records no token usage for a dispatched subagent** — v1.2.0
|
|
108
|
+
verified that across every transcript on two machines — so an hour of Opus
|
|
109
|
+
executors adds almost nothing here. `MTok` is what your conversation costs, not
|
|
110
|
+
what a run costs; `orc usage report` and `/orc-budget` remain the truth for that.
|
|
111
|
+
An unreadable transcript renders **`—`, never `0`**, because a zero would say the
|
|
112
|
+
session was free.
|
|
113
|
+
|
|
114
|
+
All four kinds are summed. That is a deliberate exception to `/orc-budget`'s
|
|
115
|
+
*four kinds, never blended*, and it holds only because this is one cell on a
|
|
116
|
+
status bar and not a report: any subset ORC picked would be a weighting **ORC
|
|
117
|
+
invented**, which is worse. The ledger keeps all four apart regardless.
|
|
118
|
+
|
|
119
|
+
#### The rest of the line
|
|
120
|
+
|
|
121
|
+
- **The verdict word becomes the version, the ICON keeps the verdict.** The ⛔
|
|
122
|
+
branch still names every reason — a warning with no reason is an emoji. A
|
|
123
|
+
version ORC cannot read renders as plain `ORC`, never `ORC vnull`.
|
|
124
|
+
- `34% ctx` → **`context (34%)`**.
|
|
125
|
+
- `sess +6%` → **`ucs 6%`** (usage, current session), and it now **keeps its
|
|
126
|
+
slot at zero**: "nothing consumed yet" and "this build has no ucs segment" are
|
|
127
|
+
different facts. It is still a delta of an **account-wide** window.
|
|
128
|
+
- **`Dur 48m`** labels the duration.
|
|
129
|
+
- **The branch** comes from `.git/HEAD` with **no subprocess** — including the
|
|
130
|
+
`gitdir:` pointer file a worktree or submodule uses. Detached HEAD reads as
|
|
131
|
+
`@a1b2c3d`. Not a repository → the segment is absent, not empty.
|
|
132
|
+
- **`lanes:` is replaced by `status:`.** The running lane is its first word, and
|
|
133
|
+
`orc stats` / `orc run list` still hold the session's whole history.
|
|
134
|
+
|
|
135
|
+
#### The status line explains itself
|
|
136
|
+
|
|
137
|
+
**`.claude/hooks/README.md`** ships next to the hook and documents every segment
|
|
138
|
+
in Simplified Technical English (`bin/webui/i18n/TERMS.md` is the term list),
|
|
139
|
+
including what each **absent** segment means — because a user who cannot find a
|
|
140
|
+
segment needs to know whether it is broken or answering.
|
|
141
|
+
|
|
142
|
+
#### Deliberately absent
|
|
143
|
+
|
|
144
|
+
- **A config key.** A status line is display, and a hook cannot resolve a lane's
|
|
145
|
+
config anyway (it has no lane) — the caveat the `log_dir` and wiki segments
|
|
146
|
+
already carry. The two env seams exist for tests and terminals, and nothing in
|
|
147
|
+
ORC ever sets them.
|
|
148
|
+
- **A second throttle.** Everything new rides in v1.2.0's one 5-second scan;
|
|
149
|
+
`ORC_STATUSLINE_SCAN_MS` stays the only seam over it.
|
|
150
|
+
- **A `git` subprocess**, on a per-keystroke surface.
|
|
151
|
+
- **A token estimate for dispatched subagents.** Claude Code records none, and a
|
|
152
|
+
fake measurement would be worse than none.
|
|
153
|
+
- **An `orc ui` panel.** The statusline is not a panel surface; nothing in
|
|
154
|
+
`bin/webui/` moved.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
13
158
|
### v1.2.0 - a retry that cloned the agent, and a window you can watch empty _(2026-09-04)_
|
|
14
159
|
|
|
15
160
|
**Still on the unscoped `orc` package?** Do this once first — your `orc upgrade`
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|

|
|
15
15
|

|
|
16
16
|
|
|
17
|
-
**Latest: v1.2.
|
|
17
|
+
**Latest: v1.2.1** · updated 2026-09-04 · [full changelog](CHANGELOG.md)
|
|
18
18
|
|
|
19
19
|
**On npm: [`@azure-id/orc`](https://www.npmjs.com/package/@azure-id/orc)** — `npm i -g @azure-id/orc`
|
|
20
20
|
|
|
@@ -575,7 +575,7 @@ a current audit: [EVAL-REPORT.md](EVAL-REPORT.md).
|
|
|
575
575
|
**Full history: [CHANGELOG.md](CHANGELOG.md)** — or `orc changelog`, which prints
|
|
576
576
|
only what is newer than the version you have.
|
|
577
577
|
|
|
578
|
-
### v1.2.
|
|
578
|
+
### v1.2.1 - the status line says what ORC is doing _(2026-09-04)_
|
|
579
579
|
|
|
580
580
|
**Still on the unscoped `orc` package?** Do this once first — your `orc upgrade`
|
|
581
581
|
is the pre-v0.56.0 one and cannot install itself. Full detail in the CAUTION at
|
|
@@ -587,80 +587,64 @@ the top of this file.
|
|
|
587
587
|
|
|
588
588
|
**Do not use `npm i -g -f`.** Full detail in v0.56.0 below.
|
|
589
589
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
- **`orc run inflight`** — the one reader of the pending sidecar the trace hook
|
|
597
|
-
has written on every `SPAWN` since v0.34. `0` clear · `1` in-flight ·
|
|
598
|
-
`2` unknown. Every lane that re-dispatches now asks it first.
|
|
599
|
-
- **A re-dispatch is refused over a live attempt.** New registered contract
|
|
600
|
-
token, `_shared/return-validation.md` **§0**, placed above every existing rule
|
|
601
|
-
because every one of them ends in "re-dispatch". The refusal names the agent,
|
|
602
|
-
the task and its age, and always offers "dispatch anyway" — never as the
|
|
603
|
-
default.
|
|
604
|
-
- **Exit 2 refuses, and it is the one place in ORC where an absent reading
|
|
605
|
-
blocks.** `orc usage check` exit 2 never stops a run; an UNCHECKABLE pact
|
|
606
|
-
never raises an exit code. It inverts here because the two errors are not the
|
|
607
|
-
same size: a wrongly-refused dispatch costs one question, a wrongly-issued one
|
|
608
|
-
costs a second Opus agent for an hour.
|
|
609
|
-
- **An interrupted turn is UNKNOWN, never FAILED.** A usage limit, an API error
|
|
610
|
-
or a `Ctrl+C` between a dispatch and its return says nothing about the agent.
|
|
611
|
-
Classifying that as a failure is what made the incident compound: it paid
|
|
612
|
-
twice, hit the limit sooner, and retried again.
|
|
613
|
-
- **Unknown is not zero.** A missing sidecar, an unreadable one, records older
|
|
614
|
-
than six hours, or a sidecar that disagrees with the trace's own SPAWN/RETURN
|
|
615
|
-
balance all read `unknown` — never `clear`.
|
|
616
|
-
- **The honest limit is stated, not papered over.** It cannot see an *ad-hoc*
|
|
617
|
-
dispatch (`/orc-quick` recon, dispatched by model+effort rather than a pinned
|
|
618
|
-
`orc-*` agent): the hook writes no `SPAWN` for one, so no record exists. Those
|
|
619
|
-
are read-only and short, and a lane must never read `clear` as proof one
|
|
620
|
-
finished.
|
|
621
|
-
|
|
622
|
-
The other half — **you can now watch the window empty**:
|
|
623
|
-
|
|
624
|
-
- **`orc usage report`** — 5-hour, 7-day and context in one place, plus the line
|
|
625
|
-
the snapshot could never give you: **"This session has consumed 59% of the
|
|
626
|
-
5-hour window and is still counting"**, with the caveat that other sessions on
|
|
627
|
-
the same account share that window.
|
|
628
|
-
- **A window reset mid-session is not a refund.** The statusline keeps a
|
|
629
|
-
per-session ledger beside `usage.json` — raw numbers only, never a computed
|
|
630
|
-
word — and banks what was spent before a reset so the running total keeps
|
|
631
|
-
counting across the boundary. A new session re-baselines.
|
|
632
|
-
- **The statusline grew a second line, and a `sess +X%` segment on the first.**
|
|
590
|
+
The second line arrived in v1.2.0 saying which lanes had run. It could not say
|
|
591
|
+
what any of them was doing *now*. This release puts the running phase in front —
|
|
592
|
+
with a small animated mark per kind of phase, borrowed from `orc-cli`'s lane
|
|
593
|
+
menu — and finishes the line with the two facts a terminal should always carry:
|
|
594
|
+
what the session has cost, and which branch it is on.
|
|
633
595
|
|
|
634
596
|
```
|
|
635
|
-
🚀 ORC-
|
|
636
|
-
agents
|
|
597
|
+
🚀 ORC v1.2.1 - Opus 5/high · context (34%) · 5h 41% (2h13m) ↔ wk 12% · ucs 6% · wiki: fresh
|
|
598
|
+
▰ status: quick · Q3 DO · agents 7 (2 running) · orc-extra: on · Dur 48m · MTok 412K · main
|
|
637
599
|
```
|
|
638
600
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
601
|
+
- **The phase is CLI-computed and rendered, never derived.** `orc init` /
|
|
602
|
+
`orc update` stamps `hooks/orc-lane-rails.json` from the same registries
|
|
603
|
+
`orc lane phases` reads; the hook holds no idea of its own about what ORC's
|
|
604
|
+
phases are. Inspect it with the new `orc lane rails [--json]`. A second phase
|
|
605
|
+
table in a hook would be the Flow-stepper failure on a second surface, and no
|
|
606
|
+
lint could see it — so it is a registered contract token instead.
|
|
607
|
+
- **A phase the disk cannot prove is HIDDEN.** The floor is the trace hook's
|
|
608
|
+
own `PHASE-EDGE`, written with zero model cooperation; a narrated verb the
|
|
609
|
+
lane's rail publishes may only *refine* it, never invent one. The cost is
|
|
610
|
+
stated rather than papered over: a phase that dispatches nothing and narrates
|
|
611
|
+
nothing — `/orc-quick` `Q1 LOOK`, `Q2 ASK`, every ask-the-user gate — shows
|
|
612
|
+
no segment at all. **Asking 24 spines to write a phase marker would have
|
|
613
|
+
covered those, and it is the remembered-not-dispatched bet this repo has
|
|
614
|
+
already lost five times.** A stale phase word gets believed.
|
|
615
|
+
- **The animation is a liveness tell, not a progress bar.** A statusline is a
|
|
616
|
+
pull surface, so the frame comes off the wall clock: it moves while you type
|
|
617
|
+
and while ORC works, and freezes when the session is idle. `ORC_STATUSLINE_MOTION=0`
|
|
618
|
+
**removes** the motion rather than slowing it (a frozen frame of a cycling
|
|
619
|
+
animation is a bug that looks like a hang), and `ORC_STATUSLINE_ASCII=1`
|
|
620
|
+
swaps in the ASCII twin every motif ships with.
|
|
621
|
+
- **`MTok` — MAIN token — is honest about what it cannot see.** All four token
|
|
622
|
+
kinds summed, read incrementally from the session transcript. **Claude Code
|
|
623
|
+
records no token usage for a dispatched subagent**, so an hour of Opus
|
|
624
|
+
executors adds almost nothing: this is your conversation's cost, not the
|
|
625
|
+
run's. Unreadable renders `—`, **never `0`**, which would say the session was
|
|
626
|
+
free. `orc usage report` and `/orc-budget` remain the four-kind truth.
|
|
627
|
+
- **The rest of the line.** The verdict WORD becomes the installed version and
|
|
628
|
+
the ICON keeps the verdict — the ⛔ branch still names every reason, because a
|
|
629
|
+
warning with no reason is an emoji. `ctx` → `context (N%)`. `sess +X%` → `ucs
|
|
630
|
+
X%`, which now keeps its slot at zero. `Dur` labels the duration, and the
|
|
631
|
+
branch comes off `.git/HEAD` with **no subprocess** on a per-keystroke
|
|
632
|
+
surface.
|
|
633
|
+
- **`lanes:` is replaced by `status:`.** The running lane is its first word;
|
|
634
|
+
`orc stats` and `orc run list` still hold the session's whole history.
|
|
635
|
+
- **The status line now explains itself.** `.claude/hooks/README.md` documents
|
|
636
|
+
every segment in Simplified Technical English, including what each absent
|
|
637
|
+
segment means.
|
|
638
|
+
|
|
639
|
+
Everything rides in the one 5-second scan v1.2.0 already added. No new config
|
|
640
|
+
key — a status line is display, and a hook cannot resolve a lane's config anyway.
|
|
658
641
|
|
|
659
642
|
**Full entry: [CHANGELOG.md](CHANGELOG.md).**
|
|
660
643
|
|
|
661
644
|
<details>
|
|
662
645
|
<summary>Earlier releases</summary>
|
|
663
646
|
|
|
647
|
+
- **v1.2.0 - a retry that cloned the agent, and a window you can watch empty — _(2026-09-04)_**
|
|
664
648
|
- **v1.1.0 - the wait, and a window ORC can finally see — _(2026-08-31)_**
|
|
665
649
|
- **v1.0.0 - config, phases and calls stop being prose — _(2026-08-30)_**
|
|
666
650
|
|
package/bin/cli.js
CHANGED
|
@@ -220,6 +220,16 @@ function installGuards(claudeDir) {
|
|
|
220
220
|
JSON.stringify({ version: currentVersion() }) + "\n"
|
|
221
221
|
);
|
|
222
222
|
} catch (_) {}
|
|
223
|
+
// The statusline's phase rail (v1.2.1). Regenerated every install from the
|
|
224
|
+
// CLI's own registries, so the hook renders a phase list it never derives.
|
|
225
|
+
// Same fail-silent contract: a missing manifest hides the `status:` segment,
|
|
226
|
+
// it never breaks a render.
|
|
227
|
+
try {
|
|
228
|
+
fs.writeFileSync(
|
|
229
|
+
path.join(hooksDest, "orc-lane-rails.json"),
|
|
230
|
+
JSON.stringify(laneRailsManifest()) + "\n"
|
|
231
|
+
);
|
|
232
|
+
} catch (_) {}
|
|
223
233
|
|
|
224
234
|
const guardCmd = nodeCmd(path.join(hooksDest, "orc-effort-guard.js"));
|
|
225
235
|
const statusCmd = nodeCmd(path.join(hooksDest, "orc-statusline.js"));
|
|
@@ -346,6 +356,7 @@ function shippedFootprint() {
|
|
|
346
356
|
for (const rel of listSrcRel(SRC_AGENTS)) files.push("agents/" + rel);
|
|
347
357
|
for (const rel of listSrcRel(SRC_HOOKS)) files.push("hooks/" + rel);
|
|
348
358
|
files.push("hooks/orc-version.json"); // generated by installGuards, ORC-owned
|
|
359
|
+
files.push("hooks/orc-lane-rails.json"); // generated by installGuards, ORC-owned
|
|
349
360
|
return files;
|
|
350
361
|
}
|
|
351
362
|
|
|
@@ -4089,6 +4100,150 @@ function lanePhaseRows(lane) {
|
|
|
4089
4100
|
return rows;
|
|
4090
4101
|
}
|
|
4091
4102
|
|
|
4103
|
+
// ---------------------------------------------------------------------------
|
|
4104
|
+
// The STATUSLINE RAIL MANIFEST (v1.2.1)
|
|
4105
|
+
//
|
|
4106
|
+
// `templates/hooks/orc-statusline.js` shows which phase an ORC run is in. It
|
|
4107
|
+
// cannot call this CLI: a statusline re-renders on every keystroke, so a
|
|
4108
|
+
// subprocess per render is not on the table. It also must never hold its own
|
|
4109
|
+
// idea of what the phases are — that is the Flow-stepper failure (`orc ui`
|
|
4110
|
+
// v0.43.7) applied to a second surface, and a second phase table is drift no
|
|
4111
|
+
// lint could see.
|
|
4112
|
+
//
|
|
4113
|
+
// So the CLI COMPUTES and the hook RENDERS: `orc init` / `orc update` writes
|
|
4114
|
+
// `hooks/orc-lane-rails.json` from the registries below, exactly the way
|
|
4115
|
+
// `hooks/orc-version.json` is stamped at install time.
|
|
4116
|
+
//
|
|
4117
|
+
// FRAMES ARE NOT IN HERE. The glyphs live in the hook, because they are
|
|
4118
|
+
// presentation: a motif change must not need a reinstall. What ships is the
|
|
4119
|
+
// KIND — a closed word the hook maps to a frame set.
|
|
4120
|
+
// ---------------------------------------------------------------------------
|
|
4121
|
+
|
|
4122
|
+
// A phase's motif kind. The set is CLOSED, and a phase this table does not name
|
|
4123
|
+
// gets `generic` — the honest sweep, not a guess. Ported in spirit from
|
|
4124
|
+
// orc-cli's motifs.js, keyed on the PHASE rather than the lane: the lane name is
|
|
4125
|
+
// already the first word of the statusline's `status:` segment, so the glyph is
|
|
4126
|
+
// free to say what ORC is DOING.
|
|
4127
|
+
const PHASE_KINDS = ["look", "ask", "plan", "do", "check", "ship", "wait", "generic"];
|
|
4128
|
+
const PHASE_KIND = {
|
|
4129
|
+
// reading, scanning, gathering evidence
|
|
4130
|
+
preflight: "look",
|
|
4131
|
+
trace: "look",
|
|
4132
|
+
"wiki-consult": "look",
|
|
4133
|
+
"analyst-gates": "look",
|
|
4134
|
+
"house-rules": "look",
|
|
4135
|
+
analysis: "look",
|
|
4136
|
+
combine: "look",
|
|
4137
|
+
// waiting on the user
|
|
4138
|
+
intake: "ask",
|
|
4139
|
+
// deciding what to do and in what order
|
|
4140
|
+
planning: "plan",
|
|
4141
|
+
"plan-handoff": "plan",
|
|
4142
|
+
"wave-grouping": "plan",
|
|
4143
|
+
scoring: "plan",
|
|
4144
|
+
// executors running
|
|
4145
|
+
execution: "do",
|
|
4146
|
+
// grading what came back
|
|
4147
|
+
review: "check",
|
|
4148
|
+
security: "check",
|
|
4149
|
+
"security-checklist": "check",
|
|
4150
|
+
verify: "check",
|
|
4151
|
+
testgen: "check",
|
|
4152
|
+
"mock-example": "check",
|
|
4153
|
+
"ultra-gate": "check",
|
|
4154
|
+
// handing it over
|
|
4155
|
+
ship: "ship",
|
|
4156
|
+
summary: "ship",
|
|
4157
|
+
// stopped on purpose
|
|
4158
|
+
"stop-resume": "wait",
|
|
4159
|
+
};
|
|
4160
|
+
|
|
4161
|
+
// A word in an own-phase heading that decides its kind. Checked in order, so
|
|
4162
|
+
// the first match wins; no match falls through to `generic`. This is how a
|
|
4163
|
+
// lane's own pipeline (`Q1 LOOK`, `D4 DRAFT`, `C6 ADVISE`) gets a motif without
|
|
4164
|
+
// a hand-maintained row per phase per lane.
|
|
4165
|
+
const OWN_PHASE_KIND_WORDS = [
|
|
4166
|
+
[/\b(ask|question|intake|interview|frame|scope|choose|pick|gate)\b/i, "ask"],
|
|
4167
|
+
[/\b(look|preflight|read|scan|ingest|evidence|load|context|recon|harvest)\b/i, "look"],
|
|
4168
|
+
[/\b(plan|outline|cluster|shape|diverge|converge|order|batch|wave)\b/i, "plan"],
|
|
4169
|
+
[/\b(do|write|build|execute|implement|draft|generate|dispatch|compile)\b/i, "do"],
|
|
4170
|
+
[/\b(check|review|verify|judge|challenge|stress|grade|lint|audit|conserv)\b/i, "check"],
|
|
4171
|
+
[/\b(ship|finish|exit|deliver|hand|summar|report|card|record)\b/i, "ship"],
|
|
4172
|
+
[/\b(wait|stop|resume|pause|suspend)\b/i, "wait"],
|
|
4173
|
+
];
|
|
4174
|
+
|
|
4175
|
+
// The display label for one phase. 18 visible chars, because line 2 of the
|
|
4176
|
+
// statusline already carries five other segments.
|
|
4177
|
+
//
|
|
4178
|
+
// A shared phase is its own id — those ids ARE the words ORC uses everywhere
|
|
4179
|
+
// else, and renaming them for a status bar would invent a second vocabulary.
|
|
4180
|
+
// An own phase is its heading up to the first bracket or dash, which is what
|
|
4181
|
+
// turns `## Q1 — LOOK (silent — no questions here)` into `Q1 LOOK`.
|
|
4182
|
+
function railLabel(row) {
|
|
4183
|
+
if (!row.heading) return String(row.id).slice(0, 18);
|
|
4184
|
+
let s = String(row.heading).replace(/^#+\s*/, "");
|
|
4185
|
+
s = s.split(/\s+[—–-]\s+|\s*\(/)[0].trim();
|
|
4186
|
+
// A heading whose whole first clause is just the code (`## C6 — Advise`)
|
|
4187
|
+
// loses the only interesting word, so keep the second clause too when the
|
|
4188
|
+
// first is a bare code.
|
|
4189
|
+
if (/^[A-Za-z]{1,2}\d+[a-z]?$/.test(s)) {
|
|
4190
|
+
const rest = String(row.heading)
|
|
4191
|
+
.replace(/^#+\s*/, "")
|
|
4192
|
+
.slice(s.length)
|
|
4193
|
+
.replace(/^\s*[—–-]\s*/, "")
|
|
4194
|
+
.split(/\s*\(/)[0]
|
|
4195
|
+
.trim();
|
|
4196
|
+
if (rest) s = s + " " + rest;
|
|
4197
|
+
}
|
|
4198
|
+
s = s.replace(/\s+/g, " ").trim();
|
|
4199
|
+
if (s.length <= 18) return s;
|
|
4200
|
+
// Cut at a word boundary. `D2 Supporting docu` is a typo the reader has to
|
|
4201
|
+
// decode; `D2 Supporting` is a label.
|
|
4202
|
+
const cut = s.slice(0, 18);
|
|
4203
|
+
const sp = cut.lastIndexOf(" ");
|
|
4204
|
+
return (sp > 3 ? cut.slice(0, sp) : cut).trim();
|
|
4205
|
+
}
|
|
4206
|
+
|
|
4207
|
+
function railKind(row) {
|
|
4208
|
+
if (PHASE_KIND[row.id]) return PHASE_KIND[row.id];
|
|
4209
|
+
const hay = `${row.id} ${row.heading || ""}`;
|
|
4210
|
+
for (const [re, kind] of OWN_PHASE_KIND_WORDS) if (re.test(hay)) return kind;
|
|
4211
|
+
return "generic";
|
|
4212
|
+
}
|
|
4213
|
+
|
|
4214
|
+
// The role families `templates/hooks/orc-trace.js` writes as `PHASE-EDGE <fam>`.
|
|
4215
|
+
// This is a MIRROR of that hook's `roleFamily()`, and it is the one new drift
|
|
4216
|
+
// surface this feature adds — a golden test compares the two by source text,
|
|
4217
|
+
// the way the `OPUS5_BANDS` alias is asserted.
|
|
4218
|
+
const TRACE_FAMILIES = ["combine", "analysis", "planning", "execution", "review", "verify", "testgen", "ultra-gate"];
|
|
4219
|
+
|
|
4220
|
+
// The whole object, deterministic from the registries above. `--json is not a
|
|
4221
|
+
// summary`: this is what the file holds and what `orc lane rails` prints.
|
|
4222
|
+
function laneRailsManifest() {
|
|
4223
|
+
const lanes = {};
|
|
4224
|
+
for (const lane of LANE_NAMES) {
|
|
4225
|
+
const t = LANE_TRACE[lane];
|
|
4226
|
+
// A lane that opens no run writes no trace file, so no `run-<lane>-…` name
|
|
4227
|
+
// can ever name it and the resolver could never reach the row. Omitting it
|
|
4228
|
+
// is an answer, not a gap.
|
|
4229
|
+
if (!t || !t.token) continue;
|
|
4230
|
+
const rows = [];
|
|
4231
|
+
for (const p of lanePhaseRows(lane))
|
|
4232
|
+
rows.push({ id: p.id, label: railLabel(p), kind: railKind(p), verbs: [] });
|
|
4233
|
+
for (const p of LANE_OWN_PHASES[lane] || [])
|
|
4234
|
+
rows.push({
|
|
4235
|
+
id: p.id,
|
|
4236
|
+
label: railLabel(p),
|
|
4237
|
+
kind: railKind(p),
|
|
4238
|
+
verbs: Array.isArray(p.trace_verbs) ? p.trace_verbs.slice() : [],
|
|
4239
|
+
});
|
|
4240
|
+
lanes[t.token] = { lane, tier: t.tier, phases: rows };
|
|
4241
|
+
}
|
|
4242
|
+
const families = {};
|
|
4243
|
+
for (const f of TRACE_FAMILIES) families[f] = { label: f, kind: PHASE_KIND[f] || "generic" };
|
|
4244
|
+
return { version: currentVersion(), kinds: PHASE_KINDS, families, lanes };
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4092
4247
|
// \`orc lane phases <lane>\` / \`--all\`. Exit 0 in every state; 2 for an unknown
|
|
4093
4248
|
// lane or no argument — the \`orc lane calls\` contract, unchanged.
|
|
4094
4249
|
//
|
|
@@ -4284,6 +4439,33 @@ function laneConfigCmd(lane, claudeDir) {
|
|
|
4284
4439
|
}
|
|
4285
4440
|
|
|
4286
4441
|
|
|
4442
|
+
// `orc lane rails [--json]` — the statusline's phase rail, exactly as it is
|
|
4443
|
+
// written to `hooks/orc-lane-rails.json` at install. It exists so the manifest
|
|
4444
|
+
// is inspectable without opening a hook directory, and so a golden test reads
|
|
4445
|
+
// the same object the hook does. Exit 0 in every state.
|
|
4446
|
+
//
|
|
4447
|
+
// `--json is not a summary`: this prints the WHOLE object, not a count of it.
|
|
4448
|
+
function laneRailsCmd() {
|
|
4449
|
+
const m = laneRailsManifest();
|
|
4450
|
+
if (wantsJson()) {
|
|
4451
|
+
emitJson(Object.assign({ ok: true }, m));
|
|
4452
|
+
return 0;
|
|
4453
|
+
}
|
|
4454
|
+
console.log(`\nORC statusline rails ${ui.color.gray("v" + m.version)}`);
|
|
4455
|
+
console.log(ui.color.gray(` kinds ${m.kinds.join(", ")}`));
|
|
4456
|
+
console.log(ui.color.gray(` families ${Object.keys(m.families).join(", ")}`));
|
|
4457
|
+
for (const [token, row] of Object.entries(m.lanes)) {
|
|
4458
|
+
console.log(`\n ${ui.color.bold(token)} ${ui.color.gray(row.lane + " · " + row.tier)}`);
|
|
4459
|
+
for (const p of row.phases)
|
|
4460
|
+
console.log(
|
|
4461
|
+
" " + String(p.kind).padEnd(8) + " " + String(p.label).padEnd(20) +
|
|
4462
|
+
ui.color.gray(p.verbs.length ? "verbs: " + p.verbs.join(", ") : "")
|
|
4463
|
+
);
|
|
4464
|
+
}
|
|
4465
|
+
console.log("");
|
|
4466
|
+
return 0;
|
|
4467
|
+
}
|
|
4468
|
+
|
|
4287
4469
|
// `orc lane` — the noun. `list` and `config` today; `phases` lands in W12 and
|
|
4288
4470
|
// `calls` in W10.
|
|
4289
4471
|
function lane() {
|
|
@@ -4299,13 +4481,16 @@ function lane() {
|
|
|
4299
4481
|
process.exit(laneCallsCmd(pos[2], claudeDir));
|
|
4300
4482
|
case "phases":
|
|
4301
4483
|
process.exit(lanePhasesCmd(pos[2], claudeDir));
|
|
4484
|
+
case "rails":
|
|
4485
|
+
process.exit(laneRailsCmd());
|
|
4302
4486
|
default:
|
|
4303
4487
|
console.error(
|
|
4304
4488
|
`Unknown: orc lane ${pos[1]}\n` +
|
|
4305
4489
|
"Usage: orc lane list [--json]\n" +
|
|
4306
4490
|
" orc lane config <lane> [--json]\n" +
|
|
4307
4491
|
" orc lane calls <lane> [--json] | orc lane calls --all [--json]\n" +
|
|
4308
|
-
" orc lane phases <lane> [--json] | orc lane phases --all [--json]"
|
|
4492
|
+
" orc lane phases <lane> [--json] | orc lane phases --all [--json]" +
|
|
4493
|
+
"\n orc lane rails [--json]"
|
|
4309
4494
|
);
|
|
4310
4495
|
process.exit(2);
|
|
4311
4496
|
}
|
package/bin/verify-contracts.js
CHANGED
|
@@ -411,6 +411,7 @@ const CONTRACTS = [
|
|
|
411
411
|
token: "orc run inflight",
|
|
412
412
|
binFiles: ["bin/cli.js"],
|
|
413
413
|
files: [
|
|
414
|
+
"hooks/README.md",
|
|
414
415
|
"skills/_shared/return-validation.md",
|
|
415
416
|
"skills/_shared/phases/execution.md",
|
|
416
417
|
"skills/orc/SKILL.md",
|
|
@@ -842,6 +843,10 @@ const CONTRACTS = [
|
|
|
842
843
|
files: [
|
|
843
844
|
"agents/orc-trace-writer-haiku-4-5.md",
|
|
844
845
|
"hooks/orc-trace.js",
|
|
846
|
+
// v1.2.1: the statusline READS the pointer to decide which run `status:`
|
|
847
|
+
// is about. "The newest file" would be a different, wrong answer during
|
|
848
|
+
// a lane suspend, when two traces are live and only one is the run.
|
|
849
|
+
"hooks/orc-statusline.js",
|
|
845
850
|
"skills/_shared/lane-suspend.md",
|
|
846
851
|
"skills/context-combiner/SKILL.md",
|
|
847
852
|
"skills/orc-aftermath/SKILL.md",
|
|
@@ -905,6 +910,18 @@ const CONTRACTS = [
|
|
|
905
910
|
"skills/_shared/phases/trace.md",
|
|
906
911
|
],
|
|
907
912
|
},
|
|
913
|
+
{
|
|
914
|
+
// v1.2.1 — the statusline's phase rail. The CLI COMPUTES the phase list
|
|
915
|
+
// (labels, order, motif kind) and stamps it at install; the hook RENDERS it
|
|
916
|
+
// and derives nothing, the same way `orc ui` renders the Flow stepper. The
|
|
917
|
+
// two halves live in two files, so a rename of the manifest silently blanks
|
|
918
|
+
// `status:` unless it fails here.
|
|
919
|
+
name: "statusline phase rail (v1.2.1 — orc-lane-rails.json)",
|
|
920
|
+
token: "orc-lane-rails.json",
|
|
921
|
+
binFiles: ["bin/cli.js"],
|
|
922
|
+
files: [
|
|
923
|
+
"hooks/README.md","hooks/orc-statusline.js"],
|
|
924
|
+
},
|
|
908
925
|
{
|
|
909
926
|
// v0.32.0: the hook's zero-model-dependence phase segmentation. Producer
|
|
910
927
|
// (the hook) and both consumers (retro skill + its miner) must agree on the
|
|
@@ -922,6 +939,11 @@ const CONTRACTS = [
|
|
|
922
939
|
// v0.42.0: the combiner is segmented as its own PHASE-EDGE family INSIDE the
|
|
923
940
|
// analyze trace — the reason it needs no lane of its own.
|
|
924
941
|
"skills/context-combiner/SKILL.md",
|
|
942
|
+
// v1.2.1: the statusline READS these edges to render `status:`. It is the
|
|
943
|
+
// deterministic floor under that segment — the model-narrated verb can
|
|
944
|
+
// only refine it — so a rename of this verb would silently blank the one
|
|
945
|
+
// segment on line 2 that changes minute to minute.
|
|
946
|
+
"hooks/orc-statusline.js",
|
|
925
947
|
],
|
|
926
948
|
},
|
|
927
949
|
{
|
|
@@ -946,6 +968,7 @@ const CONTRACTS = [
|
|
|
946
968
|
token: "orc wiki sync",
|
|
947
969
|
binFiles: ["bin/cli.js"],
|
|
948
970
|
files: [
|
|
971
|
+
"hooks/README.md",
|
|
949
972
|
"agents/orc-wiki-scanner-opus-4-8-high.md",
|
|
950
973
|
"agents/orc-wiki-scanner-opus-5-med.md",
|
|
951
974
|
"agents/orc-wiki-scanner-sonnet-5-high.md",
|
|
@@ -985,6 +1008,7 @@ const CONTRACTS = [
|
|
|
985
1008
|
name: "wiki UNREGISTERED state (v0.18.0 — docs without a manifest ≠ no wiki; sync, never re-scan)",
|
|
986
1009
|
token: "UNREGISTERED",
|
|
987
1010
|
files: [
|
|
1011
|
+
"hooks/README.md",
|
|
988
1012
|
"hooks/orc-statusline.js",
|
|
989
1013
|
"skills/orc-wiki/README.md",
|
|
990
1014
|
"skills/orc-wiki/references/phases/phase-0.md",
|
|
@@ -1489,6 +1513,7 @@ const CONTRACTS = [
|
|
|
1489
1513
|
name: "wiki freshness tier enum (v0.11.0 — FRESH/AGING/STALE, computed on read)",
|
|
1490
1514
|
token: "AGING",
|
|
1491
1515
|
files: [
|
|
1516
|
+
"hooks/README.md",
|
|
1492
1517
|
"hooks/orc-statusline.js",
|
|
1493
1518
|
"skills/_shared/gotchas.md",
|
|
1494
1519
|
"skills/_shared/interview.md",
|