@azure-id/orc 2.1.1 → 2.2.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 (145) hide show
  1. package/CHANGELOG.md +320 -0
  2. package/README.md +48 -23
  3. package/bin/build-agents.js +207 -205
  4. package/bin/cli.js +48188 -47417
  5. package/bin/fix.js +21 -18
  6. package/bin/graph-extract.js +314 -6
  7. package/bin/graph-gain.js +83 -2
  8. package/bin/graph-map.js +23 -1
  9. package/bin/graph-notes.js +1 -1
  10. package/bin/graph-query.js +1362 -37
  11. package/bin/graph-resolve.js +1 -1
  12. package/bin/graph-shard.js +29 -6
  13. package/bin/graph-signals.js +5 -3
  14. package/bin/graph.js +7 -3
  15. package/bin/habit.js +1 -1
  16. package/bin/pricing.json +8 -1
  17. package/bin/trace-write.js +40 -11
  18. package/bin/verify-contracts.js +5661 -5552
  19. package/bin/verify-package.js +716 -716
  20. package/bin/webui/api.js +8 -0
  21. package/bin/webui/fixtures/extra.js +2036 -2036
  22. package/bin/webui/fixtures/flow.js +1 -1
  23. package/bin/webui/fixtures/index.js +2 -2
  24. package/bin/webui/fixtures/knowledge.js +8 -1
  25. package/bin/webui/fixtures/pact.js +112 -111
  26. package/bin/webui/fixtures/stats.js +2 -2
  27. package/bin/webui/i18n/en/knowledge.json +2 -0
  28. package/bin/webui/i18n/id/knowledge.json +2 -0
  29. package/bin/webui/js/panels/knowledge.js +17 -0
  30. package/guides/code-graph-benefit.md +116 -0
  31. package/guides/configuration.md +150 -0
  32. package/guides/documents.md +262 -0
  33. package/guides/extra-models.md +448 -0
  34. package/guides/habits-and-gotchas.md +152 -0
  35. package/guides/knowledge-reads.md +190 -0
  36. package/guides/model-selection.md +186 -0
  37. package/guides/rules.md +305 -0
  38. package/guides/status-line.md +344 -0
  39. package/mock-run/orc-cli.md +3 -2
  40. package/mock-run/orc-doc.md +452 -448
  41. package/mock-run/orc.md +157 -157
  42. package/package.json +2 -1
  43. package/templates/agents/MODEL-MAPPING.md +176 -176
  44. package/templates/agents/{orc-executor-haiku-4-5.md → orc-executor-haiku-5-high.md} +21 -8
  45. package/templates/agents/orc-executor-opus-4-7-high.md +17 -5
  46. package/templates/agents/orc-executor-opus-4-7-med.md +17 -5
  47. package/templates/agents/orc-executor-opus-4-8-high.md +17 -5
  48. package/templates/agents/orc-executor-opus-5-high.md +17 -5
  49. package/templates/agents/orc-executor-opus-5-low.md +17 -5
  50. package/templates/agents/orc-executor-opus-5-med.md +17 -5
  51. package/templates/agents/orc-executor-sonnet-4-6-high.md +17 -5
  52. package/templates/agents/orc-executor-sonnet-5-high.md +17 -5
  53. package/templates/agents/orc-executor-sonnet-5-low.md +17 -5
  54. package/templates/agents/orc-executor-sonnet-5-med.md +17 -5
  55. package/templates/agents/{orc-graph-noter-sonnet-4-6-med.md → orc-graph-noter-haiku-5-high.md} +4 -4
  56. package/templates/agents/orc-planner-mini-opus-5-med.md +3 -1
  57. package/templates/agents/orc-planner-mini-sonnet-5-high.md +3 -1
  58. package/templates/agents/orc-planner-opus-5-med.md +3 -1
  59. package/templates/agents/{orc-trace-writer-haiku-4-5.md → orc-trace-writer-haiku-5-high.md} +4 -3
  60. package/templates/commands/orc-doc.md +128 -128
  61. package/templates/commands/orc-export.md +51 -46
  62. package/templates/hooks/README.md +8 -3
  63. package/templates/hooks/orc-effort-guard.js +105 -27
  64. package/templates/hooks/orc-graph-hook.js +252 -4
  65. package/templates/hooks/orc-statusline.js +13 -6
  66. package/templates/hooks/orc-trace.js +1 -1
  67. package/templates/skills/_shared/README.md +2 -2
  68. package/templates/skills/_shared/code-graph.md +185 -251
  69. package/templates/skills/_shared/extra-dispatch.md +1331 -1331
  70. package/templates/skills/_shared/lane-contract.md +7 -3
  71. package/templates/skills/_shared/opus5-only.md +137 -137
  72. package/templates/skills/_shared/phases/execution.md +13 -11
  73. package/templates/skills/_shared/phases/planning.md +27 -9
  74. package/templates/skills/_shared/phases/preflight.md +3 -3
  75. package/templates/skills/_shared/phases/review.md +4 -3
  76. package/templates/skills/_shared/phases/ship.md +7 -10
  77. package/templates/skills/_shared/phases/summary.md +2 -3
  78. package/templates/skills/_shared/phases/trace-verbs.md +21 -16
  79. package/templates/skills/_shared/phases/trace.md +3 -3
  80. package/templates/skills/_shared/phases/wiki-consult.md +2 -2
  81. package/templates/skills/_shared/pr-templates.md +108 -106
  82. package/templates/skills/_shared/read-ladder.md +8 -2
  83. package/templates/skills/_shared/return-validation.md +10 -6
  84. package/templates/skills/_shared/review-slice.md +1 -1
  85. package/templates/skills/_shared/stack-plan.md +135 -135
  86. package/templates/skills/_shared/wait.md +14 -2
  87. package/templates/skills/context-combiner/SKILL.md +2 -2
  88. package/templates/skills/orc/SKILL.md +227 -227
  89. package/templates/skills/orc/config.md +2 -2
  90. package/templates/skills/orc/examples/full-run-mock.md +74 -74
  91. package/templates/skills/orc/schemas/planning-output.md +3 -0
  92. package/templates/skills/orc-aftermath/SKILL.md +15 -15
  93. package/templates/skills/orc-analyze/SKILL.md +2 -2
  94. package/templates/skills/orc-analyze-mini/SKILL.md +2 -2
  95. package/templates/skills/orc-boundary/SKILL.md +15 -15
  96. package/templates/skills/orc-boundary/references/card.md +81 -78
  97. package/templates/skills/orc-boundary/references/gate.md +113 -113
  98. package/templates/skills/orc-brainstorm/SKILL.md +15 -15
  99. package/templates/skills/orc-budget/SKILL.md +15 -15
  100. package/templates/skills/orc-challenge/README.md +1 -1
  101. package/templates/skills/orc-challenge/SKILL.md +16 -16
  102. package/templates/skills/orc-challenge/examples/council-full-roster.md +1 -1
  103. package/templates/skills/orc-claude/SKILL.md +4 -4
  104. package/templates/skills/orc-claude/examples/claude-run-mock.md +1 -1
  105. package/templates/skills/orc-diy/SKILL.md +2 -2
  106. package/templates/skills/orc-diy/references/blocks/wiki.md +26 -26
  107. package/templates/skills/orc-diy/references/flow-schema.md +2 -2
  108. package/templates/skills/orc-doc/README.md +2 -2
  109. package/templates/skills/orc-doc/SKILL.md +501 -490
  110. package/templates/skills/orc-doc/examples/orc-doc-prd-run.md +12 -1
  111. package/templates/skills/orc-doc/references/chunking.md +6 -4
  112. package/templates/skills/orc-doc/references/gates.md +311 -311
  113. package/templates/skills/orc-doc/references/resume-protocol.md +229 -228
  114. package/templates/skills/orc-explain/SKILL.md +88 -88
  115. package/templates/skills/orc-export/SKILL.md +190 -187
  116. package/templates/skills/orc-fast/SKILL.md +213 -210
  117. package/templates/skills/orc-grill/SKILL.md +245 -245
  118. package/templates/skills/orc-handoff/SKILL.md +11 -11
  119. package/templates/skills/orc-learn/SKILL.md +13 -13
  120. package/templates/skills/orc-learn/examples/learn-run-mock.md +2 -2
  121. package/templates/skills/orc-mini/SKILL.md +12 -12
  122. package/templates/skills/orc-mini/examples/mini-run-mock.md +4 -5
  123. package/templates/skills/orc-pact/SKILL.md +15 -15
  124. package/templates/skills/orc-pact/references/gate.md +70 -70
  125. package/templates/skills/orc-pattern/SKILL.md +3 -3
  126. package/templates/skills/orc-poly/SKILL.md +4 -4
  127. package/templates/skills/orc-poly/examples/poly-run-mock.md +51 -51
  128. package/templates/skills/orc-pr-driver/SKILL.md +4 -3
  129. package/templates/skills/orc-pr-driver/references/orc-run-split.md +108 -99
  130. package/templates/skills/orc-pr-setup/SKILL.md +2 -2
  131. package/templates/skills/orc-quick/README.md +535 -536
  132. package/templates/skills/orc-quick/SKILL.md +29 -32
  133. package/templates/skills/orc-quick/references/context-doc.md +145 -145
  134. package/templates/skills/orc-quick/references/dispatch-gate.md +220 -220
  135. package/templates/skills/orc-quick/references/gh-mode.md +2 -1
  136. package/templates/skills/orc-quick/references/look.md +34 -12
  137. package/templates/skills/orc-retro/SKILL.md +2 -2
  138. package/templates/skills/orc-retro/examples/retro-mock.md +2 -2
  139. package/templates/skills/orc-route/SKILL.md +2 -2
  140. package/templates/skills/orc-test/SKILL.md +6 -3
  141. package/templates/skills/orc-verify/SKILL.md +2 -2
  142. package/templates/skills/orc-wait/SKILL.md +10 -2
  143. package/templates/skills/orc-wiki/SKILL.md +4 -4
  144. package/templates/skills/orc-wiki/references/pattern-prewarm.md +19 -19
  145. package/templates/agents/orc-executor-sonnet-4-6-med.md +0 -155
@@ -53,7 +53,7 @@ const G = require("./graph.js");
53
53
 
54
54
  // 3 (v1.8.2 W1): ROUTE rows. A 1.8.1 cache is refused by the schema check and
55
55
  // recomputed on read until the next `update` rewrites it — no answer changes.
56
- const RESOLVE_SCHEMA = 3;
56
+ const RESOLVE_SCHEMA = 4;
57
57
  const CONFIDENT = new Set(["LOCAL", "IMPORT", "UNIQUE", "ROUTE"]);
58
58
  const NAMES_PER_KEY = 5;
59
59
 
@@ -279,13 +279,36 @@ function fastModel(claudeDir, root, query, opts) {
279
279
  };
280
280
 
281
281
  // The target's own shard holds its callers AND its ambiguous-caller counts.
282
- // `ctx` asks for callers of the TARGET and of nothing else, so one shard is
283
- // the whole reverse half of this card.
284
282
  const s0 = shardFor(rel);
285
- const callersCache = Object.create(null);
286
- const maybeCache = Object.create(null);
287
- for (const [k, v] of Object.entries(s0.callers)) callersCache[k] = v;
288
- for (const [k, v] of Object.entries(s0.maybe || {})) maybeCache[k] = v;
283
+ const callersRows = Object.create(null);
284
+ const maybeRows = Object.create(null);
285
+ const merged = new Set();
286
+ const merge = (r, sh) => {
287
+ merged.add(r);
288
+ for (const [k, v] of Object.entries(sh.callers || {})) callersRows[k] = v;
289
+ for (const [k, v] of Object.entries(sh.maybe || {})) maybeRows[k] = v;
290
+ };
291
+ merge(rel, s0);
292
+ // R11 (v2.2.0): the card also asks for the callers of the target's CALLERS
293
+ // (the tests that reach it through one). Those rows live in the shard of the
294
+ // caller's file, so that shard is read on first ask. A shard that will not
295
+ // read sends the whole card back to the full model, never a shorter card.
296
+ const ensure = (k) => {
297
+ if (typeof k !== "string" || !k.includes("#")) return;
298
+ const r = k.split("#")[0];
299
+ if (merged.has(r) || !files[r]) return;
300
+ const sh = shardFor(r);
301
+ if (!sh) throw new FastUnavailable("shard-stale:" + r);
302
+ merge(r, sh);
303
+ };
304
+ const lazy = (t) =>
305
+ new Proxy(t, {
306
+ get: (o, k) => (ensure(k), o[k]),
307
+ has: (o, k) => (ensure(k), k in o),
308
+ getOwnPropertyDescriptor: (o, k) => (ensure(k), Reflect.getOwnPropertyDescriptor(o, k)),
309
+ });
310
+ const callersCache = lazy(callersRows);
311
+ const maybeCache = lazy(maybeRows);
289
312
 
290
313
  return {
291
314
  model: {
@@ -489,12 +489,14 @@ function complexityOf(claudeDir, root, model, rels, opts) {
489
489
  callerFiles.size >= COMPLEXITY_CALLER_FILES || callersOutside >= COMPLEXITY_CALLERS || risk.length > 0 || cochange.length > 0 ? "recommend-orc" : "mini-ok";
490
490
 
491
491
  const riskText = risk.length ? risk.map((x) => `${x.class}${x.at ? ` (${x.at})` : ""}`).join(", ") : riskGiven ? "none" : "not given";
492
- const cochangeText = cochange.length ? cochange.map((x) => `${x.path} x${x.count} not in plan`).join(", ") : "none";
492
+ // v2.2.0 (G-15): no co-change row is no segment — `cochange none` was a
493
+ // line about nothing.
494
+ const cochangeText = cochange.length ? ` · cochange ${cochange.map((x) => `${x.path} x${x.count} not in plan`).join(", ")}` : "";
493
495
  const maybeText = maybe ? ` · maybe ${maybe}` : "";
494
496
  const line =
495
497
  verdict === "mini-ok"
496
- ? `complexity: mini-ok — ${rels.length} files · confident callers ${callersOutside} in ${callerFiles.size} files${maybeText} · tests reach ${testsReaching} · risk ${riskText} · cochange ${cochangeText}`
497
- : `complexity: recommend /orc — ${rels.length} files · callers ${callersOutside} in ${callerFiles.size} files${maybeText} · tests reach ${testsReaching} · risk ${riskText} · cochange ${cochangeText}`;
498
+ ? `complexity: mini-ok — ${rels.length} files · confident callers ${callersOutside} in ${callerFiles.size} files${maybeText} · tests reach ${testsReaching} · risk ${riskText}${cochangeText}`
499
+ : `complexity: recommend /orc — ${rels.length} files · callers ${callersOutside} in ${callerFiles.size} files${maybeText} · tests reach ${testsReaching} · risk ${riskText}${cochangeText}`;
498
500
 
499
501
  return {
500
502
  verdict,
package/bin/graph.js CHANGED
@@ -45,7 +45,7 @@ const SCHEMA = 1;
45
45
  // symbols, and a `<script setup>` component is one `class`. No record FIELD
46
46
  // changed: a 1.9.0 store reads DRIFTED and the next `status --heal`
47
47
  // re-extracts once, the same path 1.8.1 → 1.8.2 took.
48
- const ENGINE = "graph@6";
48
+ const ENGINE = "graph@7";
49
49
  const GIT_MAX_BUFFER = 256 * 1024 * 1024;
50
50
  const MAX_BYTES = 512 * 1024;
51
51
  const LOCK_STALE_MS = 10 * 60 * 1000;
@@ -514,7 +514,10 @@ function graphUpdate(claudeDir, root, opts) {
514
514
  // EW3: how long the last real update took, end to end. A read that may
515
515
  // heal uses it as the estimate for the next one — the only honest
516
516
  // estimate available, because an update cannot be stopped half way.
517
- update_ms: unchanged && prevMeta ? prevMeta.update_ms || 0 : 0,
517
+ // v2.2.0 (G-13): a COLD build is not an estimate for an incremental
518
+ // update, so it writes `build_ms` and leaves `update_ms` absent.
519
+ ...(first ? {} : { update_ms: unchanged && prevMeta ? prevMeta.update_ms || 0 : 0 }),
520
+ ...(prevMeta && prevMeta.build_ms ? { build_ms: prevMeta.build_ms } : {}),
518
521
  // A1: computed from the index this update just built, so it costs one
519
522
  // pass over what is already in memory and no file is opened for it.
520
523
  density: unchanged && prevMeta && prevMeta.density ? prevMeta.density : densityOf(index, filesOut),
@@ -540,7 +543,8 @@ function graphUpdate(claudeDir, root, opts) {
540
543
  // so a crash between the two leaves a valid meta that only lacks an
541
544
  // estimate, and a missing estimate simply means "heal, and find out".
542
545
  if (!unchanged) {
543
- meta.update_ms = Date.now() - t0;
546
+ if (first) meta.build_ms = Date.now() - t0;
547
+ else meta.update_ms = Date.now() - t0;
544
548
  atomicWrite(p.meta, JSON.stringify(meta, null, 2) + "\n");
545
549
  }
546
550
  return {
package/bin/habit.js CHANGED
@@ -140,7 +140,7 @@ const QUESTION_RE = /(?:^|\s)QUESTION\s+count=(\d+)/;
140
140
  const AGENT_RE = /^[a-z0-9][a-z0-9.:-]*$/;
141
141
 
142
142
  // v2.1.0 (A1) — ONE agent, ONE spelling. A live gate wrote the same executor
143
- // three ways (`orc-executor-sonnet-4-6-med`, `sonnet-4-6-med`, `sonnet`), so a
143
+ // three ways (`orc-executor-haiku-5-high`, `haiku-5-high`, `haiku`), so a
144
144
  // 15-of-15 choice read as 5/7 and was never proposed. A short form is mapped to
145
145
  // the full SHIPPED agent name only when it can mean exactly one agent of the
146
146
  // gate's role (a family word like `sonnet` is settled by the `offered=` list);
package/bin/pricing.json CHANGED
@@ -28,7 +28,7 @@
28
28
  "it has no rate for. A pair with no rate reports `usd: null` forever, never a",
29
29
  "guess and never a family fallback."
30
30
  ],
31
- "as_of": "2026-10-01",
31
+ "as_of": "2026-10-09",
32
32
  "currency": "USD",
33
33
  "unit": "per_million_tokens",
34
34
  "server_tools": {
@@ -84,6 +84,12 @@
84
84
  "cache_read": 0.3,
85
85
  "output": 15
86
86
  },
87
+ "claude-haiku-5-5": {
88
+ "input": 0.1,
89
+ "cache_write": 0.125,
90
+ "cache_read": 0.01,
91
+ "output": 0.5
92
+ },
87
93
  "claude-haiku-4-5": {
88
94
  "input": 1,
89
95
  "cache_write": 1.25,
@@ -151,6 +157,7 @@
151
157
  "claude-sonnet-5-5": 200000,
152
158
  "claude-sonnet-5": 200000,
153
159
  "claude-sonnet-4-6": 200000,
160
+ "claude-haiku-5-5": 1000000,
154
161
  "claude-haiku-4-5": 200000
155
162
  },
156
163
  "providers": {
@@ -6,10 +6,10 @@
6
6
  // halves of the trace pair — the `.txt` line and its `.jsonl` row — so the two
7
7
  // can never disagree about an event. W0 measured the Haiku writer at 92.4 %
8
8
  // (whole phase blocks missing from the `.jsonl`); here parity is by
9
- // construction. The writer agent `orc-trace-writer-haiku-4-5` stays as the
9
+ // construction. The writer agent `orc-trace-writer-haiku-5-high` stays as the
10
10
  // FALLBACK: a lane dispatches it only when this command exits ≠ 0.
11
11
  //
12
- // The formats are the writer's, not new ones (`agents/orc-trace-writer-haiku-4-5.md`):
12
+ // The formats are the writer's, not new ones (`agents/orc-trace-writer-haiku-5-high.md`):
13
13
  // .txt [<ts>] <actor padded to 8> <VERB> :: <tail>
14
14
  // .jsonl {"ts","actor","phase","verb","tail", …verbatim extra fields}
15
15
  // NOTE one line from `decisions`, actor `writer`, the last event's ts
@@ -179,6 +179,10 @@ function blockScalar(lines, i, style, parentIndent) {
179
179
  return [text, j - 1];
180
180
  }
181
181
 
182
+ // v2.2.0 T13 — a parse error names the line it could not read (trimmed, ≤ 80 chars).
183
+ const quoteLine = (l) => { const t = String(l).trim(); return JSON.stringify(t.length > 80 ? t.slice(0, 79) + "…" : t); };
184
+ const PACKET_SHAPE_HINT = ' — a packet is YAML or JSON, e.g. {"phase":"entry 1","events":[{"ts":"DDMMYY HH:MM:SS.mmm","verb":"NOTE","tail":"…"}]}';
185
+
182
186
  function parseYaml(text) {
183
187
  const lines = text.split("\n");
184
188
  const out = {};
@@ -187,7 +191,7 @@ function parseYaml(text) {
187
191
  if (line.trim() === "" || line.trim()[0] === "#") continue;
188
192
  if (indentOf(line) !== 0) throw new PacketError(`line ${i + 1}: unexpected indentation`);
189
193
  const m = KEY_RE.exec(line);
190
- if (!m) throw new PacketError(`line ${i + 1}: expected "key: value"`);
194
+ if (!m) throw new PacketError(`line ${i + 1}: expected "key: value", got ${quoteLine(line)}`);
191
195
  const key = m[1];
192
196
  const rest = stripComment(m[2] || "").trim();
193
197
  if (/^[>|][+-]?$/.test(rest)) {
@@ -250,7 +254,7 @@ function parseYaml(text) {
250
254
  }
251
255
  if (indentOf(n) <= itemIndent) break;
252
256
  const nm = KEY_RE.exec(n.trim());
253
- if (!nm) throw new PacketError(`line ${k + 2}: expected "key: value" inside an item`);
257
+ if (!nm) throw new PacketError(`line ${k + 2}: expected "key: value" inside an item, got ${quoteLine(n)}`);
254
258
  k++;
255
259
  put(nm[1], nm[2], k);
256
260
  }
@@ -267,7 +271,7 @@ function parseYaml(text) {
267
271
  if (l.trim() === "" || l.trim()[0] === "#") continue;
268
272
  if (indentOf(l) === 0) break;
269
273
  const nm = KEY_RE.exec(l.trim());
270
- if (!nm) throw new PacketError(`line ${k + 1}: expected "key: value" under ${key}`);
274
+ if (!nm) throw new PacketError(`line ${k + 1}: expected "key: value" under ${key}, got ${quoteLine(l)}`);
271
275
  map[nm[1]] = scalar(nm[2] || "");
272
276
  }
273
277
  out[key] = map;
@@ -381,7 +385,22 @@ function findingOutcomeError(verb, tail) {
381
385
  }
382
386
 
383
387
  // → { phase, run_meta, events: [{ts, actor, verb, tail, extra}], note } or throws.
384
- function validatePacket(pk, traceVerbs) {
388
+ // v2.2.0 T13 — `laneVerbs(tokenOrLane)` → this lane's verb heads, or null; `laneHint`
389
+ // is the lane token from the trace file name when the packet has no run_meta.
390
+ function unknownVerbFix(pk, traceVerbs, laneVerbs, laneHint) {
391
+ const rm = pk && pk.run_meta && typeof pk.run_meta === "object" ? pk.run_meta : null;
392
+ const lane = (rm && rm.lane) || laneHint || null;
393
+ const own = lane && typeof laneVerbs === "function" ? laneVerbs(String(lane)) : null;
394
+ // T13b — PHASE and ASK are packet verbs for every lane, even when the grammar omits them.
395
+ if (own) {
396
+ if (!own.includes("PHASE")) own.push("PHASE");
397
+ if (!own.includes("ASK")) { const ln = String(lane); const name = /^orc(-|$)/.test(ln) ? ln : ln === "ultra" ? "orc" : `orc-${ln}`; own.push(`ASK <qid> (the ids: orc lane phases ${name} --json → ask_ids)`); }
398
+ }
399
+ const heads = own || Object.keys(traceVerbs).filter((v) => traceVerbs[v].emitter !== "hook" && traceVerbs[v].emitter !== "cli");
400
+ return ` — use "PHASE <name> start|end" for a phase, "NOTE" for free text; ${own ? "this lane's verbs" : "the packet verbs"}: ${heads.join(", ")}`;
401
+ }
402
+
403
+ function validatePacket(pk, traceVerbs, laneVerbs, laneHint) {
385
404
  if (!pk || typeof pk !== "object" || Array.isArray(pk)) throw new PacketError("the packet is not a map");
386
405
  const errs = [];
387
406
  const events = Array.isArray(pk.events) ? pk.events : [];
@@ -395,10 +414,11 @@ function validatePacket(pk, traceVerbs) {
395
414
  const word = verb.split(/\s+/)[0];
396
415
  if (!word) return errs.push(`${at} has no verb`);
397
416
  const d = Object.prototype.hasOwnProperty.call(traceVerbs, word) ? traceVerbs[word] : null;
398
- if (!d) return errs.push(`${at}: unknown verb ${word} — not in the CLOSED set (trace-verbs.md)`);
417
+ if (!d) return errs.push(`${at}: unknown verb ${word} — not in the CLOSED set (trace-verbs.md)${unknownVerbFix(pk, traceVerbs, laneVerbs, laneHint)}`);
399
418
  if (d.emitter === "hook") return errs.push(`${at}: ${word} is written by the hook, never by a packet`);
400
- // v2.1.0 W6 — `FIX` is written by `orc fix record` itself.
401
- if (d.emitter === "cli") return errs.push(`${at}: ${word} is written by the CLI (\`orc fix record\`), never by a packet`);
419
+ // v2.1.0 W6 — `FIX` is written by `orc fix record` itself; v2.1.2 F11 —
420
+ // `WAIT` by `orc wait`. The verb's owner file says which command.
421
+ if (d.emitter === "cli") return errs.push(`${at}: ${word} is written by the CLI itself (see ${d.owner}), never by a packet`);
402
422
  const ts = e.ts == null ? "" : String(e.ts).trim();
403
423
  if (!TS_RE.test(ts)) errs.push(`${at} (${word}): ts "${ts}" is not DDMMYY HH:MM:SS.mmm — the event's REAL time, never "now"`);
404
424
  const actor = e.actor == null || e.actor === "" ? "orc" : String(e.actor).trim();
@@ -727,11 +747,20 @@ function traceCmd(deps) {
727
747
  return fail(1, "unreadable", `cannot read the packet (${e.code || e.message})`);
728
748
  }
729
749
  let v;
750
+ let laneHint = null;
751
+ try {
752
+ const ld0 = deps.resolveLogDir(deps.resolveClaudeDir());
753
+ const cur0 = fs.readFileSync(path.join(ld0, ".current"), "utf8").trim();
754
+ laneHint = (/(?:^|[\/])run-([a-z0-9]+)-/.exec(cur0) || [])[1] || null;
755
+ } catch (_) {}
730
756
  try {
731
- v = validatePacket(parsePacket(text), deps.TRACE_VERBS);
757
+ const pk0 = parsePacket(text);
758
+ if (!laneHint && pk0 && typeof pk0.run === "string") laneHint = (/(?:^|[\/])run-([a-z0-9]+)-/.exec(pk0.run) || [])[1] || null;
759
+ v = validatePacket(pk0, deps.TRACE_VERBS, deps.laneVerbsFor, laneHint);
732
760
  } catch (e) {
733
761
  if (!(e instanceof PacketError)) throw e;
734
- return fail(2, "invalid", `${e.message} — nothing written`, { errors: e.errors || [e.message] });
762
+ const hint = /expected "key: value"|not valid JSON|unexpected indentation/.test(e.message) ? PACKET_SHAPE_HINT : "";
763
+ return fail(2, "invalid", `${e.message} — nothing written${hint}`, { errors: e.errors || [e.message] });
735
764
  }
736
765
  // An ASK is checked against the habit registry AS IT ARRIVES (eval E2): a live lane
737
766
  // wrote its own option names and extra text, and the habit engine then counted