@azure-id/orc 1.8.1 → 1.9.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 (69) hide show
  1. package/CHANGELOG.md +386 -0
  2. package/README-id.md +110 -73
  3. package/README.md +96 -33
  4. package/bin/cli.js +45520 -44867
  5. package/bin/graph-extract.js +2409 -120
  6. package/bin/graph-gain.js +404 -0
  7. package/bin/graph-map.js +232 -0
  8. package/bin/graph-notes.js +49 -8
  9. package/bin/graph-query.js +1770 -808
  10. package/bin/graph-resolve.js +93 -16
  11. package/bin/graph-shard.js +325 -0
  12. package/bin/graph.js +658 -605
  13. package/bin/verify-contracts.js +297 -56
  14. package/bin/verify-package.js +29 -1
  15. package/bin/webui/api.js +6 -0
  16. package/bin/webui/fixtures/index.js +6 -1
  17. package/bin/webui/fixtures/knowledge.js +41 -1
  18. package/bin/webui/fixtures/stats.js +107 -104
  19. package/bin/webui/i18n/en/knowledge.json +16 -1
  20. package/bin/webui/i18n/id/knowledge.json +16 -1
  21. package/bin/webui/js/panels/knowledge.js +68 -3
  22. package/mock-run/orc-quick.md +141 -113
  23. package/package.json +1 -1
  24. package/templates/agents/MODEL-MAPPING.md +15 -5
  25. package/templates/agents/orc-executor-haiku-4-5.md +25 -13
  26. package/templates/agents/orc-executor-opus-4-7-high.md +25 -13
  27. package/templates/agents/orc-executor-opus-4-7-med.md +25 -13
  28. package/templates/agents/orc-executor-opus-4-8-high.md +25 -13
  29. package/templates/agents/orc-executor-opus-5-high.md +25 -13
  30. package/templates/agents/orc-executor-opus-5-low.md +25 -13
  31. package/templates/agents/orc-executor-opus-5-med.md +25 -13
  32. package/templates/agents/orc-executor-sonnet-4-6-high.md +25 -13
  33. package/templates/agents/orc-executor-sonnet-4-6-med.md +25 -13
  34. package/templates/agents/orc-executor-sonnet-5-high.md +25 -13
  35. package/templates/agents/orc-graph-noter-sonnet-4-6-med.md +15 -12
  36. package/templates/agents/orc-planner-mini-opus-5-med.md +75 -69
  37. package/templates/agents/orc-planner-mini-sonnet-5-high.md +73 -67
  38. package/templates/agents/orc-recon-opus-5-low.md +99 -0
  39. package/templates/agents/orc-recon-sonnet-4-6-med.md +99 -0
  40. package/templates/commands/orc-mini.md +10 -12
  41. package/templates/commands/orc-quick.md +20 -33
  42. package/templates/hooks/README.md +13 -3
  43. package/templates/hooks/orc-graph-hook.js +148 -13
  44. package/templates/hooks/orc-trace.js +476 -471
  45. package/templates/skills/_shared/code-graph.md +148 -20
  46. package/templates/skills/_shared/phases/execution.md +13 -11
  47. package/templates/skills/_shared/phases/planning.md +8 -1
  48. package/templates/skills/_shared/phases/rules.md +172 -159
  49. package/templates/skills/_shared/phases/ship.md +5 -1
  50. package/templates/skills/_shared/phases/trace.md +4 -1
  51. package/templates/skills/_shared/phases/wiki-consult.md +10 -6
  52. package/templates/skills/_shared/read-ladder.md +10 -2
  53. package/templates/skills/_shared/return-validation.md +22 -0
  54. package/templates/skills/context-combiner/SKILL.md +13 -13
  55. package/templates/skills/orc/SKILL.md +1 -1
  56. package/templates/skills/orc/subskills/orc-execution/core.md +171 -159
  57. package/templates/skills/orc-analyze/SKILL.md +13 -13
  58. package/templates/skills/orc-diy/references/flow-schema.md +1 -1
  59. package/templates/skills/orc-mini/SKILL.md +148 -136
  60. package/templates/skills/orc-mini/examples/mini-run-mock.md +64 -50
  61. package/templates/skills/orc-mini/references/complexity.md +105 -0
  62. package/templates/skills/orc-quick/README.md +495 -423
  63. package/templates/skills/orc-quick/SKILL.md +157 -211
  64. package/templates/skills/orc-quick/references/context-doc.md +145 -114
  65. package/templates/skills/orc-quick/references/defect.md +101 -0
  66. package/templates/skills/orc-quick/references/dispatch-gate.md +55 -24
  67. package/templates/skills/orc-quick/references/gh-mode.md +148 -127
  68. package/templates/skills/orc-quick/references/look.md +107 -0
  69. package/templates/skills/orc-wiki/references/staleness.md +1 -1
@@ -15,10 +15,21 @@
15
15
  * that forgot its own update step cannot leave the map behind.
16
16
  * SubagentStart (E4a) one line telling a fresh subagent the graph is there.
17
17
  * PreToolUse (E4b) a Grep or Glob whose pattern contains a symbol name →
18
- * up to five rows saying where that name is defined.
18
+ * up to five rows saying where that name is defined. v1.8.2 W3
19
+ * (D3) adds a Bash SEARCH (grep, rg, git grep, findstr,
20
+ * Select-String, ag, ack) to the same hint: a shell search is
21
+ * the same question asked through a different tool.
22
+ * PreToolUse (D3, v1.8.2 W3) a whole-file Read of a WIDE file → one line
23
+ * naming its six most important symbols and their ranges, so
24
+ * the NEXT read can be a range read. The read still runs. OFF
25
+ * by default: it is armed with `code_graph_hooks: on,read`.
19
26
  * PostToolUse (E4c) a Read of a file the extractor did NOT fully see →
20
27
  * one line naming the lines it missed.
21
28
  *
29
+ * NO DOUBLE PAYMENT. Every hint dedupes against the run's own counters file,
30
+ * and `orc graph ctx --for-slice` (D2) writes the names it delivered into that
31
+ * same file — so a name the slice already carried is never injected again.
32
+ *
22
33
  * EVERY ONE OF THESE IS SILENT UNLESS ALL OF THIS IS TRUE:
23
34
  * · an ORC run is open (the trace pointer exists and is fresh)
24
35
  * · `code_graph` is on AND `code_graph_hooks` is on
@@ -44,6 +55,7 @@
44
55
  * hooks.SubagentStart[] { hooks:[{command:"node <..>/orc-graph-hook.js"}] }
45
56
  * hooks.SubagentStop[] { hooks:[{command:"node <..>/orc-graph-hook.js"}] }
46
57
  * hooks.PreToolUse[] { matcher:"Grep|Glob", hooks:[{…}] }
58
+ * hooks.PreToolUse[] { matcher:"Bash|Read", hooks:[{…}] }
47
59
  * hooks.PostToolUse[] { matcher:"Read", hooks:[{…}] }
48
60
  *
49
61
  * Wired even though it does nothing until `code_graph` is on — arming the
@@ -70,6 +82,18 @@ const MAX_TEXT = 1200;
70
82
  const EXECUTOR = /^orc-executor-/i;
71
83
  const DELIVER_TO = /^orc-(executor|planner|reviewer|verifier|system-analyst|analyze)/i;
72
84
 
85
+ // D3 — a SHELL search is the same question as a Grep. Only these seven, only at
86
+ // the head of the command, and only the first QUOTED argument is read: an
87
+ // unquoted pattern cannot be told apart from a flag or a path, and guessing
88
+ // there would hint about the wrong word.
89
+ const BASH_SEARCH = /^\s*(?:grep|rg|ag|ack|findstr|git\s+grep|select-string|sls)\b/i;
90
+ const FIRST_QUOTED = /(?:'([^']*)'|"([^"]*)")/;
91
+ const WIDE_ROWS = 6;
92
+ // One 80-line range read, at the 40-characters-a-line the ladder assumes. The
93
+ // CLI prices a range from the file's own average; the hook must not open a file
94
+ // to price a hint, so it uses the ladder's own figure.
95
+ const HINT_RANGE_TOKENS = Math.ceil((80 * 40) / 4);
96
+
73
97
  // ── config, the same tolerant scalar read every ORC hook uses ───────────────
74
98
  function cfg(key) {
75
99
  try {
@@ -121,11 +145,17 @@ function readJson(file) {
121
145
  // beside the trace and the lane copies them in at each phase close as ONE
122
146
  // GRAPH-HINT line — never one line per hint, which is the noise rule the read
123
147
  // gate already pays for.
148
+ // The pointer names the trace file WITH its extension, so drop it: the
149
+ // counters sit beside the trace, not inside its name. `orc graph ctx
150
+ // --for-slice` writes to this same file (D2).
151
+ function seenFile(run) {
152
+ return path.join(logDir(), run.replace(/\.txt$/, "") + ".graph-hook.json");
153
+ }
154
+
124
155
  function bump(run, field, token) {
125
- // The pointer names the trace file WITH its extension, so drop it: the
126
- // counters sit beside the trace, not inside its name.
127
- const file = path.join(logDir(), run.replace(/\.txt$/, "") + ".graph-hook.json");
156
+ const file = seenFile(run);
128
157
  const seen = readJson(file) || { injected: 0, subagent_start: 0, read_notes: 0, updates: 0, tokens: [] };
158
+ if (!Array.isArray(seen.tokens)) seen.tokens = [];
129
159
  if (token) {
130
160
  if (seen.tokens.includes(token)) return false; // already said, to this run
131
161
  seen.tokens.push(token);
@@ -176,6 +206,43 @@ function emit(event, text) {
176
206
  try {
177
207
  process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext: `${PREFIX} ${body}` } }));
178
208
  } catch (_) {}
209
+ return body;
210
+ }
211
+
212
+ // K1 (v1.8.2 W4b) — the gain ledger. It is appended AFTER the hint is already
213
+ // out, inside the fail-quiet wrapper, so a full disk can slow a Grep and can
214
+ // never block one. A coverage note records PAID ONLY: it adds, it saves
215
+ // nothing (K6).
216
+ function ledger(runName, cmd, body, avoided) {
217
+ try {
218
+ fs.appendFileSync(
219
+ path.join(GRAPH_DIR, "gain.jsonl"),
220
+ JSON.stringify({
221
+ at: stampNow(),
222
+ run: runName,
223
+ agent: null,
224
+ cmd,
225
+ targets: [],
226
+ gen: null,
227
+ ms: 0,
228
+ paid: { card: 0, source: 0, hints: Math.ceil((PREFIX.length + 1 + String(body || "").length) / 4) },
229
+ avoided: avoided || { low: 0, high: 0, calls_low: 0, calls_high: 0 },
230
+ basis: { files: [], grep_hits: 0 },
231
+ }) + "\n"
232
+ );
233
+ } catch (_) {}
234
+ }
235
+
236
+ // The LEDGER stamp, which is ORC's own `DD-MM-YYYY HH:MM:SS` — the same one
237
+ // the CLI writes. (The TRACE stamp above is a different format on purpose: it
238
+ // is the trace's, and it is not this.)
239
+ function stampNow() {
240
+ const d = new Date();
241
+ const q = (n) => String(n).padStart(2, "0");
242
+ return (
243
+ q(d.getDate()) + "-" + q(d.getMonth() + 1) + "-" + d.getFullYear() + " " +
244
+ q(d.getHours()) + ":" + q(d.getMinutes()) + ":" + q(d.getSeconds())
245
+ );
179
246
  }
180
247
 
181
248
  // ── main ───────────────────────────────────────────────────────────────────
@@ -201,7 +268,13 @@ function run() {
201
268
  if (!event) return;
202
269
 
203
270
  if (String(cfg("code_graph") || "off").toLowerCase() !== "on") return;
204
- if (String(cfg("code_graph_hooks") || "on").toLowerCase() !== "on") return;
271
+ // `code_graph_hooks` is `off`, `on`, or `on,read` (D3/DE-J). `on` is every
272
+ // hint that was on in 1.8.1 PLUS the Bash search (DE-C: a shell grep is a
273
+ // Grep). The whole-file READ hint is the one that changes how an agent reads,
274
+ // so it is armed on purpose and never by an upgrade.
275
+ const mode = String(cfg("code_graph_hooks") || "on").toLowerCase().split(",").map((s) => s.trim()).filter(Boolean);
276
+ if (!mode.length || mode[0] !== "on") return;
277
+ const readHint = mode.includes("read");
205
278
  const runName = currentRun();
206
279
  if (!runName) return;
207
280
 
@@ -217,7 +290,11 @@ function run() {
217
290
  if (data.agent_id == null) return;
218
291
 
219
292
  if (event === "SubagentStart") return onSubagentStart(data, runName, meta);
220
- if (event === "PreToolUse") return onSearch(data, runName, meta);
293
+ if (event === "PreToolUse") {
294
+ const tool = String(data.tool_name || "");
295
+ if (tool === "Read") return readHint ? onWideRead(data, runName, meta) : undefined;
296
+ return onSearch(data, runName, meta);
297
+ }
221
298
  if (event === "PostToolUse") return onRead(data, runName, meta);
222
299
  }
223
300
 
@@ -262,11 +339,18 @@ function onSubagentStart(data, runName, meta) {
262
339
  const agent = String(data.agent_type || data.agentType || "");
263
340
  if (agent && !DELIVER_TO.test(agent)) return;
264
341
  if (!bump(runName, "subagent_start", "start:" + (data.agent_id || agent))) return;
342
+ // D3 — the "run ctx before a Grep" sentence is dropped when this run already
343
+ // delivered a `--for-slice` block: the agent has the outside view in its
344
+ // slice, and saying it again is the double payment this wave removes.
345
+ const counters = readJson(seenFile(runName)) || {};
346
+ const advice = counters.for_slice
347
+ ? ""
348
+ : ` Before a wide Grep for a symbol, run: orc graph ctx <name|file> --json .`;
265
349
  emit(
266
350
  "SubagentStart",
267
- `a code graph of this repository is indexed (generation ${meta.generation}, ${meta.files} files). ` +
268
- `Before a wide Grep for a symbol, run: orc graph ctx <name|file> --json . ` +
269
- `A card is a LOCATOR — read the range it names before you rely on behaviour.`
351
+ `a code graph of this repository is indexed (generation ${meta.generation}, ${meta.files} files).` +
352
+ advice +
353
+ ` A card is a LOCATOR — read the range it names before you rely on behaviour.`
270
354
  );
271
355
  }
272
356
 
@@ -275,9 +359,18 @@ function onSubagentStart(data, runName, meta) {
275
359
  // is what keeps the hook off the critical path of every Grep.
276
360
  function onSearch(data, runName, meta) {
277
361
  const tool = String(data.tool_name || "");
278
- if (tool !== "Grep" && tool !== "Glob") return;
279
362
  const input = data.tool_input || {};
280
- const pattern = String(input.pattern || "");
363
+ let pattern = "";
364
+ if (tool === "Grep" || tool === "Glob") pattern = String(input.pattern || "");
365
+ else if (tool === "Bash") {
366
+ // DE-C — a shell search is a Grep. The command must START with one of the
367
+ // seven search programs, and the word comes from its first QUOTED argument.
368
+ const cmd = String(input.command || "");
369
+ if (!BASH_SEARCH.test(cmd)) return;
370
+ const q = FIRST_QUOTED.exec(cmd);
371
+ if (!q) return;
372
+ pattern = q[1] !== undefined ? q[1] : q[2];
373
+ } else return;
281
374
  if (!pattern) return;
282
375
 
283
376
  // The longest plain identifier in the pattern. Regex metacharacters are
@@ -300,11 +393,50 @@ function onSearch(data, runName, meta) {
300
393
  if (!bump(runName, "injected", "name:" + hit.word)) return;
301
394
 
302
395
  const rows = hit.rows.slice(0, MAX_ROWS).map((r) => `${clean(r[0], 80)} (${clean(r[1], 20)}) ${clean(r[2], 160)}:${Number(r[3]) || 0}-${Number(r[4]) || 0} fan-in ${Number(r[5]) || 0}`);
303
- emit(
396
+ const body = emit(
304
397
  "PreToolUse",
305
398
  `"${clean(hit.word, 80)}" is defined at ${rows.join(" · ")}. ` +
306
399
  `Read those ranges, or run orc graph ctx ${clean(hit.word, 80)} --json for callers and callees. The search below still runs.`
307
400
  );
401
+ // The search STILL RUNS, so a careful ladder saved nothing here; a careless
402
+ // one saved the range read the anchor pointed at. Low 0, high one range.
403
+ ledger(runName, "hint", body, { low: 0, high: HINT_RANGE_TOKENS, calls_low: 0, calls_high: 1 });
404
+ }
405
+
406
+ // ── D3 (v1.8.2 W3) — a whole-file Read of a WIDE file ──────────────────────
407
+ // OFF by default (`code_graph_hooks: on,read` arms it). The read STILL RUNS —
408
+ // this hook never blocks and never rewrites an input. `updatedInput` exists and
409
+ // this file deliberately never uses it: turning an agent's Read into a range
410
+ // read is the corruption the read ladder's first exception forbids, and the
411
+ // read gate stays the only layer allowed to touch a read.
412
+ //
413
+ // The point is the NEXT read. One line naming the file's six most important
414
+ // symbols and their ranges is what an agent needs to ask for a range instead of
415
+ // 2,000 lines the turn after.
416
+ function onWideRead(data, runName, meta) {
417
+ const input = data.tool_input || {};
418
+ if (input.offset !== undefined || input.limit !== undefined) return; // already a range read
419
+ const abs = String(input.file_path || "");
420
+ if (!abs) return;
421
+ let rel;
422
+ try {
423
+ rel = path.relative(PROJECT_ROOT, abs).split(path.sep).join("/");
424
+ } catch (_) {
425
+ return;
426
+ }
427
+ if (!rel || rel.startsWith("..")) return;
428
+ const wide = readJson(path.join(GRAPH_DIR, "wide.json"));
429
+ if (!wide || !wide.files || wide.generation !== meta.generation) return;
430
+ const rows = Object.prototype.hasOwnProperty.call(wide.files, rel) ? wide.files[rel] : null;
431
+ if (!Array.isArray(rows) || !rows.length) return;
432
+ if (!bump(runName, "injected", "wide:" + rel)) return;
433
+ const body = emit(
434
+ "PreToolUse",
435
+ `${clean(rel, 160)} holds many symbols; the most reached are ` +
436
+ rows.slice(0, WIDE_ROWS).map((r) => `${clean(r[0], 80)} ${Number(r[1]) || 0}-${Number(r[2]) || 0}`).join(" · ") +
437
+ `. The read below still runs; a later read of this file can name a range.`
438
+ );
439
+ ledger(runName, "hint", body, { low: 0, high: HINT_RANGE_TOKENS, calls_low: 0, calls_high: 1 });
308
440
  }
309
441
 
310
442
  // ── E4c — a Read of a file the extractor did not fully see ─────────────────
@@ -332,5 +464,8 @@ function onRead(data, runName, meta) {
332
464
  }
333
465
  if (!note) return;
334
466
  if (!bump(runName, "read_notes", "read:" + rel)) return;
335
- emit("PostToolUse", `${note}. The source you just read is ground truth; no recorded gap is not proof of completeness.`);
467
+ const body = emit("PostToolUse", `${note}. The source you just read is ground truth; no recorded gap is not proof of completeness.`);
468
+ // PAID ONLY. A coverage note tells you what the card cannot show; it replaces
469
+ // no read and it is never counted as a saving (K6).
470
+ ledger(runName, "read-note", body, { low: 0, high: 0, calls_low: 0, calls_high: 0 });
336
471
  }