@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.
- package/CHANGELOG.md +320 -0
- package/README.md +48 -23
- package/bin/build-agents.js +207 -205
- package/bin/cli.js +48188 -47417
- package/bin/fix.js +21 -18
- package/bin/graph-extract.js +314 -6
- package/bin/graph-gain.js +83 -2
- package/bin/graph-map.js +23 -1
- package/bin/graph-notes.js +1 -1
- package/bin/graph-query.js +1362 -37
- package/bin/graph-resolve.js +1 -1
- package/bin/graph-shard.js +29 -6
- package/bin/graph-signals.js +5 -3
- package/bin/graph.js +7 -3
- package/bin/habit.js +1 -1
- package/bin/pricing.json +8 -1
- package/bin/trace-write.js +40 -11
- package/bin/verify-contracts.js +5661 -5552
- package/bin/verify-package.js +716 -716
- package/bin/webui/api.js +8 -0
- package/bin/webui/fixtures/extra.js +2036 -2036
- package/bin/webui/fixtures/flow.js +1 -1
- package/bin/webui/fixtures/index.js +2 -2
- package/bin/webui/fixtures/knowledge.js +8 -1
- package/bin/webui/fixtures/pact.js +112 -111
- package/bin/webui/fixtures/stats.js +2 -2
- package/bin/webui/i18n/en/knowledge.json +2 -0
- package/bin/webui/i18n/id/knowledge.json +2 -0
- package/bin/webui/js/panels/knowledge.js +17 -0
- package/guides/code-graph-benefit.md +116 -0
- package/guides/configuration.md +150 -0
- package/guides/documents.md +262 -0
- package/guides/extra-models.md +448 -0
- package/guides/habits-and-gotchas.md +152 -0
- package/guides/knowledge-reads.md +190 -0
- package/guides/model-selection.md +186 -0
- package/guides/rules.md +305 -0
- package/guides/status-line.md +344 -0
- package/mock-run/orc-cli.md +3 -2
- package/mock-run/orc-doc.md +452 -448
- package/mock-run/orc.md +157 -157
- package/package.json +2 -1
- package/templates/agents/MODEL-MAPPING.md +176 -176
- package/templates/agents/{orc-executor-haiku-4-5.md → orc-executor-haiku-5-high.md} +21 -8
- package/templates/agents/orc-executor-opus-4-7-high.md +17 -5
- package/templates/agents/orc-executor-opus-4-7-med.md +17 -5
- package/templates/agents/orc-executor-opus-4-8-high.md +17 -5
- package/templates/agents/orc-executor-opus-5-high.md +17 -5
- package/templates/agents/orc-executor-opus-5-low.md +17 -5
- package/templates/agents/orc-executor-opus-5-med.md +17 -5
- package/templates/agents/orc-executor-sonnet-4-6-high.md +17 -5
- package/templates/agents/orc-executor-sonnet-5-high.md +17 -5
- package/templates/agents/orc-executor-sonnet-5-low.md +17 -5
- package/templates/agents/orc-executor-sonnet-5-med.md +17 -5
- package/templates/agents/{orc-graph-noter-sonnet-4-6-med.md → orc-graph-noter-haiku-5-high.md} +4 -4
- package/templates/agents/orc-planner-mini-opus-5-med.md +3 -1
- package/templates/agents/orc-planner-mini-sonnet-5-high.md +3 -1
- package/templates/agents/orc-planner-opus-5-med.md +3 -1
- package/templates/agents/{orc-trace-writer-haiku-4-5.md → orc-trace-writer-haiku-5-high.md} +4 -3
- package/templates/commands/orc-doc.md +128 -128
- package/templates/commands/orc-export.md +51 -46
- package/templates/hooks/README.md +8 -3
- package/templates/hooks/orc-effort-guard.js +105 -27
- package/templates/hooks/orc-graph-hook.js +252 -4
- package/templates/hooks/orc-statusline.js +13 -6
- package/templates/hooks/orc-trace.js +1 -1
- package/templates/skills/_shared/README.md +2 -2
- package/templates/skills/_shared/code-graph.md +185 -251
- package/templates/skills/_shared/extra-dispatch.md +1331 -1331
- package/templates/skills/_shared/lane-contract.md +7 -3
- package/templates/skills/_shared/opus5-only.md +137 -137
- package/templates/skills/_shared/phases/execution.md +13 -11
- package/templates/skills/_shared/phases/planning.md +27 -9
- package/templates/skills/_shared/phases/preflight.md +3 -3
- package/templates/skills/_shared/phases/review.md +4 -3
- package/templates/skills/_shared/phases/ship.md +7 -10
- package/templates/skills/_shared/phases/summary.md +2 -3
- package/templates/skills/_shared/phases/trace-verbs.md +21 -16
- package/templates/skills/_shared/phases/trace.md +3 -3
- package/templates/skills/_shared/phases/wiki-consult.md +2 -2
- package/templates/skills/_shared/pr-templates.md +108 -106
- package/templates/skills/_shared/read-ladder.md +8 -2
- package/templates/skills/_shared/return-validation.md +10 -6
- package/templates/skills/_shared/review-slice.md +1 -1
- package/templates/skills/_shared/stack-plan.md +135 -135
- package/templates/skills/_shared/wait.md +14 -2
- package/templates/skills/context-combiner/SKILL.md +2 -2
- package/templates/skills/orc/SKILL.md +227 -227
- package/templates/skills/orc/config.md +2 -2
- package/templates/skills/orc/examples/full-run-mock.md +74 -74
- package/templates/skills/orc/schemas/planning-output.md +3 -0
- package/templates/skills/orc-aftermath/SKILL.md +15 -15
- package/templates/skills/orc-analyze/SKILL.md +2 -2
- package/templates/skills/orc-analyze-mini/SKILL.md +2 -2
- package/templates/skills/orc-boundary/SKILL.md +15 -15
- package/templates/skills/orc-boundary/references/card.md +81 -78
- package/templates/skills/orc-boundary/references/gate.md +113 -113
- package/templates/skills/orc-brainstorm/SKILL.md +15 -15
- package/templates/skills/orc-budget/SKILL.md +15 -15
- package/templates/skills/orc-challenge/README.md +1 -1
- package/templates/skills/orc-challenge/SKILL.md +16 -16
- package/templates/skills/orc-challenge/examples/council-full-roster.md +1 -1
- package/templates/skills/orc-claude/SKILL.md +4 -4
- package/templates/skills/orc-claude/examples/claude-run-mock.md +1 -1
- package/templates/skills/orc-diy/SKILL.md +2 -2
- package/templates/skills/orc-diy/references/blocks/wiki.md +26 -26
- package/templates/skills/orc-diy/references/flow-schema.md +2 -2
- package/templates/skills/orc-doc/README.md +2 -2
- package/templates/skills/orc-doc/SKILL.md +501 -490
- package/templates/skills/orc-doc/examples/orc-doc-prd-run.md +12 -1
- package/templates/skills/orc-doc/references/chunking.md +6 -4
- package/templates/skills/orc-doc/references/gates.md +311 -311
- package/templates/skills/orc-doc/references/resume-protocol.md +229 -228
- package/templates/skills/orc-explain/SKILL.md +88 -88
- package/templates/skills/orc-export/SKILL.md +190 -187
- package/templates/skills/orc-fast/SKILL.md +213 -210
- package/templates/skills/orc-grill/SKILL.md +245 -245
- package/templates/skills/orc-handoff/SKILL.md +11 -11
- package/templates/skills/orc-learn/SKILL.md +13 -13
- package/templates/skills/orc-learn/examples/learn-run-mock.md +2 -2
- package/templates/skills/orc-mini/SKILL.md +12 -12
- package/templates/skills/orc-mini/examples/mini-run-mock.md +4 -5
- package/templates/skills/orc-pact/SKILL.md +15 -15
- package/templates/skills/orc-pact/references/gate.md +70 -70
- package/templates/skills/orc-pattern/SKILL.md +3 -3
- package/templates/skills/orc-poly/SKILL.md +4 -4
- package/templates/skills/orc-poly/examples/poly-run-mock.md +51 -51
- package/templates/skills/orc-pr-driver/SKILL.md +4 -3
- package/templates/skills/orc-pr-driver/references/orc-run-split.md +108 -99
- package/templates/skills/orc-pr-setup/SKILL.md +2 -2
- package/templates/skills/orc-quick/README.md +535 -536
- package/templates/skills/orc-quick/SKILL.md +29 -32
- package/templates/skills/orc-quick/references/context-doc.md +145 -145
- package/templates/skills/orc-quick/references/dispatch-gate.md +220 -220
- package/templates/skills/orc-quick/references/gh-mode.md +2 -1
- package/templates/skills/orc-quick/references/look.md +34 -12
- package/templates/skills/orc-retro/SKILL.md +2 -2
- package/templates/skills/orc-retro/examples/retro-mock.md +2 -2
- package/templates/skills/orc-route/SKILL.md +2 -2
- package/templates/skills/orc-test/SKILL.md +6 -3
- package/templates/skills/orc-verify/SKILL.md +2 -2
- package/templates/skills/orc-wait/SKILL.md +10 -2
- package/templates/skills/orc-wiki/SKILL.md +4 -4
- package/templates/skills/orc-wiki/references/pattern-prewarm.md +19 -19
- package/templates/agents/orc-executor-sonnet-4-6-med.md +0 -155
|
@@ -39,7 +39,11 @@
|
|
|
39
39
|
* · a graph exists on disk
|
|
40
40
|
* · (the three delivery events) a SUBAGENT is acting — `agent_id` is present.
|
|
41
41
|
* The orchestrator gets cards through the lane; a second copy in the main
|
|
42
|
-
* session is the same tokens spent twice.
|
|
42
|
+
* session is the same tokens spent twice. ONE exception (R12, v2.2.0): a
|
|
43
|
+
* MAIN-session Grep / Glob / shell search of a name the graph knows gets
|
|
44
|
+
* the brief ctx card (callers, tests, coverage, the --anchors line), ONCE
|
|
45
|
+
* per name and at most 3 per run, under `code_graph: on` only. Live runs
|
|
46
|
+
* showed the main session ignore the graph text and hunt the tests by hand.
|
|
43
47
|
*
|
|
44
48
|
* FAIL-OPEN, ALWAYS, AND FAIL-QUIET. Exit 0 on every path, output nothing on
|
|
45
49
|
* any error, and never write to stderr — a PreToolUse hook that exits non-zero
|
|
@@ -60,6 +64,7 @@
|
|
|
60
64
|
* hooks.PreToolUse[] { matcher:"Grep|Glob", hooks:[{…}] }
|
|
61
65
|
* hooks.PreToolUse[] { matcher:"Bash|Read", hooks:[{…}] }
|
|
62
66
|
* hooks.PostToolUse[] { matcher:"Read", hooks:[{…}] }
|
|
67
|
+
* hooks.PostToolUse[] { matcher:"Task|Agent", hooks:[{…}] } (R13, main session)
|
|
63
68
|
*
|
|
64
69
|
* Wired even though it does nothing until `code_graph` is on — arming the
|
|
65
70
|
* feature is a config edit, never an install step the user has to discover.
|
|
@@ -275,7 +280,11 @@ function run() {
|
|
|
275
280
|
const event = String(data.hook_event_name || data.hookEventName || "");
|
|
276
281
|
if (!event) return;
|
|
277
282
|
|
|
278
|
-
|
|
283
|
+
// `semi` (v2.2.0) keeps the graph CURRENT but no lane reads it: only the
|
|
284
|
+
// SubagentStop update gets through. No hint, no additionalContext, no rows.
|
|
285
|
+
const graphMode = String(cfg("code_graph") || "off").toLowerCase();
|
|
286
|
+
if (graphMode !== "on" && graphMode !== "semi") return;
|
|
287
|
+
if (graphMode === "semi" && event !== "SubagentStop") return;
|
|
279
288
|
// `code_graph_hooks` is `off`, `on`, or `on,read` (D3/DE-J). `on` is every
|
|
280
289
|
// hint that was on in 1.8.1 PLUS the Bash search (DE-C: a shell grep is a
|
|
281
290
|
// Grep). The whole-file READ hint is the one that changes how an agent reads,
|
|
@@ -295,7 +304,13 @@ function run() {
|
|
|
295
304
|
// a subagent payload and absent in a main-session one — the same positive
|
|
296
305
|
// test orc-read-gate.js measured in W1. Never test for the absence of some
|
|
297
306
|
// other key, which asserts nothing.
|
|
298
|
-
if (data.agent_id == null)
|
|
307
|
+
if (data.agent_id == null) {
|
|
308
|
+
// R12 — the one main-session delivery: a search of a known name.
|
|
309
|
+
if (event === "PreToolUse" && graphMode === "on" && String(data.tool_name || "") !== "Read") return onMainSearch(data, runName, meta);
|
|
310
|
+
// R13 — an executor came back: hand the main session the touchpoint-4 card.
|
|
311
|
+
if (event === "PostToolUse" && graphMode === "on" && /^(Agent|Task)$/.test(String(data.tool_name || ""))) return onExecutorReturn(data, runName, meta);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
299
314
|
|
|
300
315
|
if (event === "SubagentStart") return onSubagentStart(data, runName, meta);
|
|
301
316
|
if (event === "PreToolUse") {
|
|
@@ -377,7 +392,7 @@ function onSubagentStart(data, runName, meta) {
|
|
|
377
392
|
// ── E4b — a search for a name the graph already knows ──────────────────────
|
|
378
393
|
// It reads names.json ONLY: a name lookup, no index parse, no resolution. This
|
|
379
394
|
// is what keeps the hook off the critical path of every Grep.
|
|
380
|
-
function
|
|
395
|
+
function searchHit(data, meta) {
|
|
381
396
|
const tool = String(data.tool_name || "");
|
|
382
397
|
const input = data.tool_input || {};
|
|
383
398
|
let pattern = "";
|
|
@@ -409,6 +424,12 @@ function onSearch(data, runName, meta) {
|
|
|
409
424
|
}
|
|
410
425
|
}
|
|
411
426
|
if (!hit || !Array.isArray(hit.rows) || !hit.rows.length) return;
|
|
427
|
+
return hit;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function onSearch(data, runName, meta) {
|
|
431
|
+
const hit = searchHit(data, meta);
|
|
432
|
+
if (!hit) return;
|
|
412
433
|
// Once per run per name. A wave of greps for the same symbol pays once.
|
|
413
434
|
if (!bump(runName, "injected", "name:" + hit.word)) return;
|
|
414
435
|
|
|
@@ -423,6 +444,233 @@ function onSearch(data, runName, meta) {
|
|
|
423
444
|
ledger(runName, "hint", body, { low: 0, high: HINT_RANGE_TOKENS, calls_low: 0, calls_high: 1 });
|
|
424
445
|
}
|
|
425
446
|
|
|
447
|
+
// ── R12 (v2.2.0) — the MAIN session searched a name the graph knows ────────
|
|
448
|
+
// In /orc-quick the main session does the dig, and live runs showed it ignore
|
|
449
|
+
// the graph text and spend calls hunting the tests by hand. So the hook answers
|
|
450
|
+
// the search itself with the ctx card: callers, tests (also via a caller),
|
|
451
|
+
// coverage and the one-call anchors line. Once per name, at most 3 per run, so
|
|
452
|
+
// the main context cannot grow without a bound. A CLI that is missing, slow or
|
|
453
|
+
// broken falls back to the names.json one-liner. The search still runs.
|
|
454
|
+
const MAIN_CARDS = 3;
|
|
455
|
+
const MAIN_CALLERS = 8;
|
|
456
|
+
const MAIN_TEXT = 1800;
|
|
457
|
+
|
|
458
|
+
// R12b — the main session chains: `cd x && cat y; grep -rn "name" src`. Any
|
|
459
|
+
// segment (split on && || ; | and newlines) that starts with a search program
|
|
460
|
+
// counts, after `cd`, env assignments and `rtk` are dropped. The pattern is the
|
|
461
|
+
// first quoted argument of THAT segment, else its first bare non-flag word.
|
|
462
|
+
// MAIN PATH ONLY: the subagent path keeps BASH_SEARCH and FIRST_QUOTED as-is.
|
|
463
|
+
const SEARCH_HEAD = /^(?:grep|rg|ag|ack|findstr|git\s+grep|select-string|sls)\b/i;
|
|
464
|
+
// Split on && || ; | and newlines OUTSIDE quotes, so a grep pattern like
|
|
465
|
+
// "a\|b" stays one argument.
|
|
466
|
+
function shellSegments(cmd) {
|
|
467
|
+
const segs = [];
|
|
468
|
+
let cur = "";
|
|
469
|
+
let quote = null;
|
|
470
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
471
|
+
const c = cmd[i];
|
|
472
|
+
if (quote) {
|
|
473
|
+
if (c === quote) quote = null;
|
|
474
|
+
cur += c;
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (c === "'" || c === '"') {
|
|
478
|
+
quote = c;
|
|
479
|
+
cur += c;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (c === ";" || c === "|" || c === "&" || c === "\n" || c === "\r") {
|
|
483
|
+
segs.push(cur);
|
|
484
|
+
cur = "";
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
cur += c;
|
|
488
|
+
}
|
|
489
|
+
segs.push(cur);
|
|
490
|
+
return segs;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function mainPattern(data) {
|
|
494
|
+
const tool = String(data.tool_name || "");
|
|
495
|
+
const input = data.tool_input || {};
|
|
496
|
+
if (tool === "Grep" || tool === "Glob") return String(input.pattern || "");
|
|
497
|
+
if (tool !== "Bash") return "";
|
|
498
|
+
for (let seg of shellSegments(String(input.command || ""))) {
|
|
499
|
+
seg = seg.trim().replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)+/, "").replace(/^rtk\s+/i, "");
|
|
500
|
+
const m = SEARCH_HEAD.exec(seg);
|
|
501
|
+
if (!m) continue;
|
|
502
|
+
const rest = seg.slice(m[0].length);
|
|
503
|
+
const q = FIRST_QUOTED.exec(rest);
|
|
504
|
+
if (q) return q[1] !== undefined ? q[1] : q[2];
|
|
505
|
+
const bare = rest.split(/\s+/).find((t) => t && !t.startsWith("-"));
|
|
506
|
+
if (bare) return bare;
|
|
507
|
+
}
|
|
508
|
+
return "";
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// R12b — the word with the HIGHEST fan-in wins; a word with fan-in 0 never
|
|
512
|
+
// spends a slot (a card that says "callers: none found" is noise).
|
|
513
|
+
function mainHit(data, meta) {
|
|
514
|
+
const pattern = mainPattern(data);
|
|
515
|
+
if (!pattern) return null;
|
|
516
|
+
const words = (pattern.match(/[A-Za-z_][A-Za-z0-9_]*/g) || []).filter((w) => w.length >= MIN_TOKEN);
|
|
517
|
+
if (!words.length) return null;
|
|
518
|
+
const names = readJson(path.join(GRAPH_DIR, "names.json"));
|
|
519
|
+
if (!names || !names.names || names.generation !== meta.generation) return null;
|
|
520
|
+
let best = null;
|
|
521
|
+
for (const w of words) {
|
|
522
|
+
if (!Object.prototype.hasOwnProperty.call(names.names, w)) continue;
|
|
523
|
+
const rows = names.names[w];
|
|
524
|
+
if (!Array.isArray(rows) || !rows.length) continue;
|
|
525
|
+
const fan = Math.max(...rows.map((x) => Number(x[5]) || 0));
|
|
526
|
+
if (fan > 0 && (!best || fan > best.fan)) best = { word: w, rows, fan };
|
|
527
|
+
}
|
|
528
|
+
return best;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function onMainSearch(data, runName, meta) {
|
|
532
|
+
const hit = mainHit(data, meta);
|
|
533
|
+
if (!hit) return;
|
|
534
|
+
const counters = readJson(seenFile(runName)) || {};
|
|
535
|
+
if ((Number(counters.main_cards) || 0) >= MAIN_CARDS) return;
|
|
536
|
+
if (!bump(runName, "injected", "main:" + hit.word)) return;
|
|
537
|
+
bump(runName, "main_cards");
|
|
538
|
+
const word = clean(hit.word, 80);
|
|
539
|
+
|
|
540
|
+
let out = null;
|
|
541
|
+
try {
|
|
542
|
+
const stamp = readJson(path.join(CLAUDE_DIR, "hooks", "orc-version.json"));
|
|
543
|
+
const cli = stamp && stamp.cli;
|
|
544
|
+
if (cli && fs.existsSync(cli)) {
|
|
545
|
+
const deadline = Math.max(1500, Number(cfg("code_graph_heal_ms")) || DEFAULT_DEADLINE_MS);
|
|
546
|
+
// Full --json, not --brief: --brief drops the callers[] rows this card
|
|
547
|
+
// names. The answer stays inside the hook; only the card text goes out.
|
|
548
|
+
const r = spawnSync(process.execPath, [cli, "graph", "ctx", hit.word, "--if-enabled", "--json", "--dir", PROJECT_ROOT], {
|
|
549
|
+
encoding: "utf8",
|
|
550
|
+
timeout: deadline,
|
|
551
|
+
windowsHide: true,
|
|
552
|
+
});
|
|
553
|
+
if (!r.error) out = JSON.parse(r.stdout || "{}");
|
|
554
|
+
}
|
|
555
|
+
} catch (_) {
|
|
556
|
+
out = null;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
let text;
|
|
560
|
+
if (out && out.ok && out.target && out.target.file) {
|
|
561
|
+
const t = out.target;
|
|
562
|
+
const lines = Array.isArray(t.lines) ? t.lines : [];
|
|
563
|
+
const parts = [`graph: ${word} — ${clean(t.file, 160)}:${Number(lines[0]) || 0}-${Number(lines[1]) || 0}`];
|
|
564
|
+
const callers = Array.isArray(out.callers) ? out.callers.slice(0, MAIN_CALLERS) : [];
|
|
565
|
+
parts.push("callers: " + (callers.length ? callers.map((c) => `${clean(c.qname, 80)} ${clean(c.file, 160)}:${Number(c.line) || 0}`).join(", ") : "none found"));
|
|
566
|
+
const tests = new Set((Array.isArray(out.tests) ? out.tests : []).map((x) => clean(x, 160)));
|
|
567
|
+
for (const v of Array.isArray(out.tests_via) ? out.tests_via : []) if (v && v.file) tests.add(clean(v.file, 160));
|
|
568
|
+
parts.push("tests: " + (tests.size ? [...tests].slice(0, 8).join(", ") : "none found"));
|
|
569
|
+
const cov = out.coverage && typeof out.coverage === "object" ? out.coverage.verdict : out.coverage;
|
|
570
|
+
parts.push("coverage: " + (clean(cov, 40) || "unknown"));
|
|
571
|
+
parts.push(`slice anchors in one call: orc graph ctx ${word} --anchors --focus=${word} --if-enabled --json --brief`);
|
|
572
|
+
text = parts.join(" · ") + ". The search below still runs.";
|
|
573
|
+
} else {
|
|
574
|
+
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}`);
|
|
575
|
+
text = `"${word}" is defined at ${rows.join(" · ")}. Read those ranges, or run orc graph ctx ${word} --json for callers and callees. The search below still runs.`;
|
|
576
|
+
}
|
|
577
|
+
const body = clean(text, MAIN_TEXT);
|
|
578
|
+
if (!body) return;
|
|
579
|
+
try {
|
|
580
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: `${PREFIX} ${body}` } }));
|
|
581
|
+
} catch (_) {}
|
|
582
|
+
const tokens = Math.ceil((PREFIX.length + 1 + body.length) / 4);
|
|
583
|
+
ledger(runName, "main-hint", body, { low: 0, high: HINT_RANGE_TOKENS, calls_low: 0, calls_high: 1 }, {
|
|
584
|
+
targets: [word],
|
|
585
|
+
gen: meta.generation,
|
|
586
|
+
paid: { card: tokens, source: 0, hints: 0, envelope: 0 },
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// ── R13 (v2.2.0) — an executor returned to the MAIN session ──────────────
|
|
591
|
+
// The SubagentStop update already ran, so the graph is current. The hook asks
|
|
592
|
+
// `orc graph changes` for the files git sees as changed and injects ONE card:
|
|
593
|
+
// the line, the tests to run first, and the risky symbols. Once per dispatch
|
|
594
|
+
// (tool_use_id). Fail quiet: no card, and the lane runs `changes` itself.
|
|
595
|
+
const CHANGES_TEXT = 1500;
|
|
596
|
+
const CHANGES_MAX_FILES = 30;
|
|
597
|
+
const CHANGES_SKIP = /^(?:\.claude\/|orc-quick\/|coverage\/|node_modules\/)|\/node_modules\//;
|
|
598
|
+
|
|
599
|
+
function changedFiles() {
|
|
600
|
+
const r = spawnSync("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: PROJECT_ROOT, encoding: "utf8", timeout: 3000, windowsHide: true });
|
|
601
|
+
if (r.error || r.status !== 0) return [];
|
|
602
|
+
const out = [];
|
|
603
|
+
for (const line of String(r.stdout || "").split(/\r?\n/)) {
|
|
604
|
+
if (line.length < 4) continue;
|
|
605
|
+
const xy = line.slice(0, 2);
|
|
606
|
+
if (xy.includes("D")) continue; // no deletions
|
|
607
|
+
let p = line.slice(3);
|
|
608
|
+
const arrow = p.indexOf(" -> ");
|
|
609
|
+
if (arrow >= 0) p = p.slice(arrow + 4);
|
|
610
|
+
p = p.replace(/^"|"$/g, "").replace(/\\/g, "/");
|
|
611
|
+
if (!p || p.endsWith("/") || CHANGES_SKIP.test(p)) continue;
|
|
612
|
+
out.push(p);
|
|
613
|
+
if (out.length >= CHANGES_MAX_FILES) break;
|
|
614
|
+
}
|
|
615
|
+
return out;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function onExecutorReturn(data, runName, meta) {
|
|
619
|
+
const input = data.tool_input || {};
|
|
620
|
+
const agent = String(input.subagent_type || "");
|
|
621
|
+
if (!agent || !EXECUTOR.test(agent)) return;
|
|
622
|
+
const id = String(data.tool_use_id || "");
|
|
623
|
+
if (!id) return;
|
|
624
|
+
const counters = readJson(seenFile(runName)) || {};
|
|
625
|
+
if (Array.isArray(counters.tokens) && counters.tokens.includes("changes:" + id)) return;
|
|
626
|
+
|
|
627
|
+
const stamp = readJson(path.join(CLAUDE_DIR, "hooks", "orc-version.json"));
|
|
628
|
+
const cli = stamp && stamp.cli;
|
|
629
|
+
if (!cli || !fs.existsSync(cli)) return;
|
|
630
|
+
const files = changedFiles();
|
|
631
|
+
if (!files.length) return;
|
|
632
|
+
|
|
633
|
+
let out = null;
|
|
634
|
+
try {
|
|
635
|
+
const deadline = Math.max(1500, Number(cfg("code_graph_heal_ms")) || DEFAULT_DEADLINE_MS);
|
|
636
|
+
// Full --json, not --brief: --brief drops the symbols[] rows the risk list
|
|
637
|
+
// needs. The answer stays inside the hook; only the card text goes out.
|
|
638
|
+
const r = spawnSync(process.execPath, [cli, "graph", "changes", "--files=" + files.join(","), "--if-enabled", "--json", "--dir", PROJECT_ROOT], {
|
|
639
|
+
encoding: "utf8",
|
|
640
|
+
timeout: deadline,
|
|
641
|
+
windowsHide: true,
|
|
642
|
+
});
|
|
643
|
+
if (!r.error) out = JSON.parse(r.stdout || "{}");
|
|
644
|
+
} catch (_) {
|
|
645
|
+
out = null;
|
|
646
|
+
}
|
|
647
|
+
if (!out || !out.ok || typeof out.line !== "string") return;
|
|
648
|
+
if (!bump(runName, "changes_cards", "changes:" + id)) return;
|
|
649
|
+
|
|
650
|
+
const parts = [clean(String(out.line).split(/\r?\n/)[0], 300)];
|
|
651
|
+
const tests = (Array.isArray(out.tests) ? out.tests : []).map((x) => clean(x, 160)).filter(Boolean);
|
|
652
|
+
parts.push("tests to run first: " + (tests.length ? tests.slice(0, 12).join(", ") : "none"));
|
|
653
|
+
const risky = (Array.isArray(out.symbols) ? out.symbols : []).filter((x) => x && (x.risk === "high" || x.risk === "medium")).slice(0, 5);
|
|
654
|
+
if (risky.length) {
|
|
655
|
+
parts.push(
|
|
656
|
+
"risk: " +
|
|
657
|
+
risky.map((x) => `${clean(x.risk, 8)} ${clean(x.qname, 80)} ${clean(x.file, 160)}:${Number((x.lines || [])[0]) || 0} (${(Array.isArray(x.why) ? x.why : []).map((w) => clean(w, 40)).join(", ")})`).join("; ")
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
const tail = " This is the touchpoint-4 changes answer — do not run orc graph changes for this dispatch.";
|
|
661
|
+
const body = clean(parts.join(" · "), CHANGES_TEXT - PREFIX.length - tail.length - 2) + tail;
|
|
662
|
+
try {
|
|
663
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: `${PREFIX} ${body}` } }));
|
|
664
|
+
} catch (_) {}
|
|
665
|
+
const tokens = Math.ceil((PREFIX.length + 1 + body.length) / 4);
|
|
666
|
+
ledger(runName, "changes-hint", body, null, {
|
|
667
|
+
agent: clean(agent, 60),
|
|
668
|
+
targets: files.slice(0, 10).map((x) => clean(x, 160)),
|
|
669
|
+
gen: out.generation || meta.generation,
|
|
670
|
+
paid: { card: tokens, source: 0, hints: 0, envelope: 0 },
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
|
|
426
674
|
// ── D3 (v1.8.2 W3) — a whole-file Read of a WIDE file ──────────────────────
|
|
427
675
|
// OFF by default (`code_graph_hooks: on,read` arms it). The read STILL RUNS —
|
|
428
676
|
// this hook never blocks and never rewrites an input. `updatedInput` exists and
|
|
@@ -51,9 +51,11 @@
|
|
|
51
51
|
*
|
|
52
52
|
* This is the ONLY place Claude Code exposes the live model id, so it also
|
|
53
53
|
* writes a fail-silent session-model bridge (.claude/orc/session-model.json)
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* reaches it
|
|
54
|
+
* = {model_id, effort, session_id, written_at}. The effort guard reads it on
|
|
55
|
+
* the Skill call (the guard can't see the model id on its own, so the bridge is
|
|
56
|
+
* how the Opus 5.5 / Fable 5 medium-effort allowance reaches it) and on a TYPED
|
|
57
|
+
* /orc (the effort itself); the session id stops one session's reading gating
|
|
58
|
+
* another.
|
|
57
59
|
*
|
|
58
60
|
* Also appends a "newer orc version available" hint from the 24h update cache
|
|
59
61
|
* (cache-only here — never a network call in the statusline hot path; the
|
|
@@ -542,8 +544,10 @@ process.stdin.on("end", () => {
|
|
|
542
544
|
|
|
543
545
|
// ── Session-model bridge (fail-silent) ─────────────────────────────────────
|
|
544
546
|
// The PreToolUse effort guard cannot see the model id; it can only read
|
|
545
|
-
// effort. Persist {model_id, effort, written_at} here
|
|
546
|
-
// the Opus 5.5 / Fable 5 medium
|
|
547
|
+
// effort. Persist {model_id, effort, session_id, written_at} here: the guard
|
|
548
|
+
// reads it on the Skill call (the Opus 5.5 / Fable 5 medium allowance) and on
|
|
549
|
+
// a TYPED /orc (the effort itself); the session id stops one session's
|
|
550
|
+
// reading gating another (v2.1.2, F13). The statusline re-renders constantly
|
|
547
551
|
// while a session is active, so written_at stays fresh; the guard treats a
|
|
548
552
|
// stale file (older than its freshness window) as absent and never blocks on
|
|
549
553
|
// it. Any error (no dir, read-only fs) is swallowed — this is a nicety, not a
|
|
@@ -558,7 +562,7 @@ process.stdin.on("end", () => {
|
|
|
558
562
|
fs.mkdirSync(orcDir, { recursive: true });
|
|
559
563
|
fs.writeFileSync(
|
|
560
564
|
path.join(orcDir, "session-model.json"),
|
|
561
|
-
JSON.stringify({ model_id: model, effort, written_at: Date.now() }) + "\n"
|
|
565
|
+
JSON.stringify({ model_id: model, effort, session_id: String(d.session_id || ""), written_at: Date.now() }) + "\n"
|
|
562
566
|
);
|
|
563
567
|
}
|
|
564
568
|
} catch (_) {}
|
|
@@ -1474,6 +1478,9 @@ function extendedScan(d, wants, wantsProvider) {
|
|
|
1474
1478
|
try {
|
|
1475
1479
|
raw = fs.readFileSync(path.join(projectDir, ".claude", "orc.config.yaml"), "utf8");
|
|
1476
1480
|
} catch (_) {}
|
|
1481
|
+
// `semi` (v2.2.0): kept current, never read by a lane. Still a floor: it
|
|
1482
|
+
// says only `semi`, never fresh, behind or drifted.
|
|
1483
|
+
if (/^[ \t]*code_graph:[ \t]*["']?semi["']?[ \t]*\r?$/m.test(raw)) return { state: "semi" };
|
|
1477
1484
|
if (!/^[ \t]*code_graph:[ \t]*["']?on["']?[ \t]*\r?$/m.test(raw)) return { state: "off" };
|
|
1478
1485
|
const meta = readJson(path.join(orc, "graph", "meta.json"));
|
|
1479
1486
|
if (!meta) return { state: "none" };
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
* Rich run filenames (v0.32.0): the canonical trace name is
|
|
92
92
|
* `run-<lane>-<slug>-<DDMMYY>-<HHMMSS>.txt`, written by the orchestrating lane at
|
|
93
93
|
* run start. The hook still bootstraps its own generic `run-<DDMMYY>-<HHMMSS>.txt`
|
|
94
|
-
* when the pointer is missing; the first `orc-trace-writer-haiku-
|
|
94
|
+
* when the pointer is missing; the first `orc-trace-writer-haiku-5-high` dispatch
|
|
95
95
|
* RENAMES that bootstrap file (+ sidecars) and rewrites `.current`. The hook is
|
|
96
96
|
* name-agnostic (rotation/STALE logic is pointer-based) and holds no open
|
|
97
97
|
* handles, so a rename between two hook events is safe.
|
|
@@ -23,8 +23,8 @@ loaded on demand when the step fires.
|
|
|
23
23
|
- `detecting-artifacts.md` — the deterministic wiki/pattern existence probes.
|
|
24
24
|
- `read-ladder.md` — the escalating read discipline (locate → outline → range →
|
|
25
25
|
full) for every role that reads code it is not about to edit.
|
|
26
|
-
- `code-graph.md` — the local code graph (`orc graph`):
|
|
27
|
-
lane
|
|
26
|
+
- `code-graph.md` — the local code graph (`orc graph`): the four touchpoints every
|
|
27
|
+
code lane shares (consult, orient, pack, affected tests), the `update_step` rule, the notes dispatch rule, and the rule
|
|
28
28
|
that keeps it safe — the graph is a LOCATOR, never the truth. No lane reads
|
|
29
29
|
its keys; every call carries `--if-enabled`.
|
|
30
30
|
- `_shared/interview.md` — the interview mechanic (design tree → frontier rounds
|