@azure-id/orc 1.7.1 → 1.8.2

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 (63) hide show
  1. package/CHANGELOG.md +3869 -3381
  2. package/README-id.md +942 -844
  3. package/README.md +882 -788
  4. package/bin/build-agents.js +43 -27
  5. package/bin/cli.js +45460 -44169
  6. package/bin/graph-extract.js +3216 -0
  7. package/bin/graph-gain.js +404 -0
  8. package/bin/graph-map.js +232 -0
  9. package/bin/graph-notes.js +229 -0
  10. package/bin/graph-query.js +1770 -0
  11. package/bin/graph-resolve.js +255 -0
  12. package/bin/graph-shard.js +325 -0
  13. package/bin/graph-signals.js +277 -0
  14. package/bin/graph.js +658 -0
  15. package/bin/verify-contracts.js +4809 -4553
  16. package/bin/verify-package.js +640 -616
  17. package/bin/webui/api.js +1425 -1414
  18. package/bin/webui/fixtures/index.js +584 -576
  19. package/bin/webui/fixtures/knowledge.js +356 -291
  20. package/bin/webui/fixtures/stats.js +107 -104
  21. package/bin/webui/i18n/en/knowledge.json +182 -151
  22. package/bin/webui/i18n/en/overview.json +101 -100
  23. package/bin/webui/i18n/id/knowledge.json +182 -151
  24. package/bin/webui/i18n/id/overview.json +101 -100
  25. package/bin/webui/js/panels/knowledge.js +1130 -1006
  26. package/bin/webui/js/panels/overview.js +492 -488
  27. package/package.json +39 -39
  28. package/templates/agents/MODEL-MAPPING.md +163 -158
  29. package/templates/agents/orc-executor-haiku-4-5.md +128 -121
  30. package/templates/agents/orc-executor-opus-4-7-high.md +129 -122
  31. package/templates/agents/orc-executor-opus-4-7-med.md +129 -122
  32. package/templates/agents/orc-executor-opus-4-8-high.md +129 -122
  33. package/templates/agents/orc-executor-opus-5-high.md +129 -122
  34. package/templates/agents/orc-executor-opus-5-low.md +129 -122
  35. package/templates/agents/orc-executor-opus-5-med.md +129 -122
  36. package/templates/agents/orc-executor-sonnet-4-6-high.md +129 -122
  37. package/templates/agents/orc-executor-sonnet-4-6-med.md +129 -122
  38. package/templates/agents/orc-executor-sonnet-5-high.md +129 -122
  39. package/templates/agents/orc-graph-noter-sonnet-4-6-med.md +89 -0
  40. package/templates/hooks/README.md +454 -396
  41. package/templates/hooks/orc-graph-hook.js +471 -0
  42. package/templates/hooks/orc-statusline-render.js +922 -921
  43. package/templates/hooks/orc-statusline.js +1596 -1545
  44. package/templates/skills/_shared/README.md +4 -0
  45. package/templates/skills/_shared/code-graph.md +348 -0
  46. package/templates/skills/_shared/opus5-only.md +4 -0
  47. package/templates/skills/_shared/phases/execution.md +168 -147
  48. package/templates/skills/_shared/phases/planning.md +149 -135
  49. package/templates/skills/_shared/phases/preflight.md +132 -118
  50. package/templates/skills/_shared/phases/review.md +63 -53
  51. package/templates/skills/_shared/phases/ship.md +100 -88
  52. package/templates/skills/_shared/phases/trace.md +8 -0
  53. package/templates/skills/_shared/phases/wiki-consult.md +194 -189
  54. package/templates/skills/_shared/read-ladder.md +132 -102
  55. package/templates/skills/_shared/return-validation.md +259 -250
  56. package/templates/skills/orc/SKILL.md +255 -254
  57. package/templates/skills/orc-analyze/SKILL.md +13 -13
  58. package/templates/skills/orc-diy/references/flow-schema.md +101 -100
  59. package/templates/skills/orc-fast/SKILL.md +236 -229
  60. package/templates/skills/orc-mini/SKILL.md +267 -259
  61. package/templates/skills/orc-quick/SKILL.md +378 -361
  62. package/templates/skills/orc-quick/references/dispatch-gate.md +6 -0
  63. package/templates/skills/orc-wiki/references/staleness.md +294 -288
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ // ── orc graph — NOTES (v1.8.0 W4) ───────────────────────────────────────────
3
+ //
4
+ // Layer 2: one sentence per function or method, written by
5
+ // `orc-graph-noter-sonnet-4-6-med`, stored by THIS module and nothing else.
6
+ //
7
+ // THE RULES THIS FILE HOLDS
8
+ //
9
+ // 1. A note is keyed by `(symbol, body_hash)`. It is shown as current ONLY
10
+ // while the symbol's body still hashes the same. A note about code that
11
+ // has since changed is labelled stale and never presented as a fact — the
12
+ // RepoMirage finding: agents trust provided context over the code.
13
+ // 2. The CLI is the only writer (S1). The agent pipes JSON to
14
+ // `orc graph notes apply -`; every row is validated against the CURRENT
15
+ // index, and a rejected row is named with its reason. The valid rows
16
+ // still apply.
17
+ // 3. Notes are for touched code only. `pending` needs `--files` — there is no
18
+ // repo-wide "note everything" mode, because that is the cost this layer
19
+ // must never become.
20
+ // 4. A batch below `--min` is not dispatched. The fixed start-up cost of a
21
+ // subagent (~5.7K tokens) makes a one-symbol batch the most expensive note
22
+ // there is. Nothing is lost by waiting: pending is recomputed from hashes.
23
+ // 5. `notes.jsonl` is append-only between compactions. `orc graph gc`
24
+ // compacts it to the latest note per symbol whose hash is still current.
25
+
26
+ const fs = require("fs");
27
+
28
+ const NOTE_MAX = 240;
29
+ // A two-line getter gains nothing from a sentence about it.
30
+ const MIN_LINES = 3;
31
+ // Classes are excluded: a class body hash moves whenever ANY of its methods
32
+ // moves, so a class note would go stale on every edit to the class.
33
+ // A route handler is a function with no name — it is noted like one.
34
+ const NOTE_KINDS = new Set(["function", "method", "route"]);
35
+
36
+ const G = () => require("./graph.js");
37
+
38
+ function clampInt(v, lo, hi, def) {
39
+ const n = Number(v);
40
+ if (!Number.isFinite(n) || v === true || v === undefined || v === null || v === "") return def;
41
+ return Math.max(lo, Math.min(hi, Math.round(n)));
42
+ }
43
+
44
+ function readIndex(claudeDir) {
45
+ try {
46
+ return JSON.parse(fs.readFileSync(G().graphPaths(claudeDir).index, "utf8"));
47
+ } catch (_) {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ function readNotes(file) {
53
+ const rows = [];
54
+ let text;
55
+ try {
56
+ text = fs.readFileSync(file, "utf8");
57
+ } catch (_) {
58
+ return rows;
59
+ }
60
+ for (const line of text.split("\n")) {
61
+ if (!line.trim()) continue;
62
+ try {
63
+ const r = JSON.parse(line);
64
+ if (r && typeof r.sym === "string" && typeof r.body_hash === "string" && typeof r.note === "string") rows.push(r);
65
+ } catch (_) {
66
+ // A torn last line from a crash mid-append is skipped, never fatal.
67
+ }
68
+ }
69
+ return rows;
70
+ }
71
+
72
+ // The latest note per (sym, body_hash), and the latest note per sym whatever
73
+ // its hash (used only to say "stale").
74
+ function noteIndex(claudeDir) {
75
+ const rows = readNotes(G().graphPaths(claudeDir).notes);
76
+ const byKey = new Map();
77
+ const bySym = new Map();
78
+ for (const r of rows) {
79
+ byKey.set(`${r.sym}\0${r.body_hash}`, r);
80
+ bySym.set(r.sym, r);
81
+ }
82
+ return { byKey, bySym, rows: rows.length };
83
+ }
84
+
85
+ // The ONE resolution rule, shared by every reader:
86
+ //
87
+ // 1. a CURRENT model note — somebody paid for it and it still describes
88
+ // this body
89
+ // 2. the DOC note (N1) — the author's own first sentence, re-extracted
90
+ // with the body, so it is never stale on its own
91
+ // 3. a STALE model note — reported as stale, never repeated as a fact
92
+ //
93
+ // A doc outranks a stale note because a sentence that matches the current
94
+ // bytes beats one that matched a body nobody has any more. It does NOT
95
+ // outrank a current model note: a comment can lie, and the model read the code.
96
+ // That is why the shared precedence line reads `graph notes and doc notes` as
97
+ // ONE rung, below a stale wiki — this function is the code half of it.
98
+ function noteFor(idx, sym) {
99
+ if (!sym) return null;
100
+ const cur = idx && idx.byKey.get(`${sym.id}\0${sym.body_hash}`);
101
+ if (cur) return { text: cur.note, model: cur.model, at: cur.at, current: true, source: "model" };
102
+ if (sym.doc) return { text: sym.doc, model: "parser", at: null, current: true, source: "doc" };
103
+ const any = idx && idx.bySym.get(sym.id);
104
+ if (any) return { text: null, model: any.model, at: any.at, current: false, source: "model" };
105
+ return null;
106
+ }
107
+
108
+ const normPath = (f) => String(f).replace(/\\/g, "/").replace(/^\.\//, "");
109
+
110
+ // exit 0 = rows to note · 1 = no index / no --files · 5 = none, or fewer than --min
111
+ function notesPending(claudeDir, opts, root) {
112
+ const index = readIndex(claudeDir);
113
+ if (!index || !index.by_file) return { ok: false, state: "none", reason: "no-index", exit: 1 };
114
+ const files = [...new Set((opts.files || []).map(normPath).filter(Boolean))];
115
+ if (!files.length) return { ok: false, state: "usage", reason: "missing-files", exit: 1 };
116
+ const cap = clampInt(opts.cap, 1, 200, 40);
117
+ const min = clampInt(opts.min, 1, 200, 5);
118
+ const idx = noteIndex(claudeDir);
119
+ const missing = [];
120
+ const rows = [];
121
+ let documented = 0;
122
+ for (const rel of files) {
123
+ const f = index.by_file[rel];
124
+ if (!f) {
125
+ missing.push(rel);
126
+ continue;
127
+ }
128
+ for (const s of f.symbols || []) {
129
+ if (!NOTE_KINDS.has(s.kind)) continue;
130
+ if (s.lines[1] - s.lines[0] + 1 < MIN_LINES) continue;
131
+ if (idx.byKey.has(`${s.id}\0${s.body_hash}`)) continue;
132
+ // N1 (v1.8.2 W4): the author already wrote the sentence. Paying a
133
+ // subagent to write it again is the one cost this layer must not have.
134
+ if (s.doc) {
135
+ documented++;
136
+ continue;
137
+ }
138
+ rows.push({ sym: s.id, file: rel, lines: s.lines, body_hash: s.body_hash, restale: idx.bySym.has(s.id) });
139
+ }
140
+ }
141
+ const base = { ok: true, files, missing, total: rows.length, documented, cap, min };
142
+ if (!rows.length) return { ...base, state: "none", rows: [], more: 0, exit: 5 };
143
+ if (rows.length < min) return { ...base, state: "below-min", rows: [], waiting: rows.map((r) => r.sym), more: 0, exit: 5 };
144
+ const out = rows.slice(0, cap);
145
+ // N2 (v1.8.2 W4) — `--with-source`: the ranges, in the SAME call. The noter
146
+ // used to open every symbol with a `Read`, so a 40-symbol batch was 40 round
147
+ // trips before it wrote a word. One call, `SRC_PER_ROW` lines each, and the
148
+ // noter reads nothing.
149
+ if (opts.withSource) for (const r of out) r.source = readRange(root, r.file, r.lines);
150
+ return { ...base, state: "pending", rows: out, more: rows.length - out.length, with_source: !!opts.withSource, exit: 0 };
151
+ }
152
+
153
+ const SRC_PER_ROW = 120;
154
+
155
+ function readRange(root, rel, lines) {
156
+ if (!root) return null;
157
+ try {
158
+ const all = require("fs").readFileSync(require("path").join(root, ...rel.split("/")), "utf8").split(/\r?\n/);
159
+ const from = Math.max(1, lines[0]);
160
+ const to = Math.min(all.length, Math.min(lines[1], from + SRC_PER_ROW - 1));
161
+ if (to < from) return null;
162
+ return { from, to, cut: Math.max(0, lines[1] - to), text: all.slice(from - 1, to).join("\n") };
163
+ } catch (_) {
164
+ return null; // a file that moved between the index and now: the noter reads it
165
+ }
166
+ }
167
+
168
+ // exit 0 = every row applied · 1 = no index / locked · 6 = one or more rows rejected
169
+ function notesApply(claudeDir, payload) {
170
+ const index = readIndex(claudeDir);
171
+ if (!index || !index.by_file) return { ok: false, state: "none", reason: "no-index", exit: 1 };
172
+ let data = payload;
173
+ if (typeof payload === "string") {
174
+ try {
175
+ data = JSON.parse(payload);
176
+ } catch (_) {
177
+ return { ok: false, state: "invalid", reason: "not-json", applied: 0, rejected: [], exit: 6 };
178
+ }
179
+ }
180
+ const list = Array.isArray(data) ? data : data && Array.isArray(data.notes) ? data.notes : null;
181
+ if (!list) return { ok: false, state: "invalid", reason: "no-notes-array", applied: 0, rejected: [], exit: 6 };
182
+ const model = data && !Array.isArray(data) && typeof data.model === "string" && data.model.trim() ? data.model.trim().slice(0, 60) : "unknown";
183
+
184
+ const syms = new Map();
185
+ for (const f of Object.values(index.by_file)) for (const s of f.symbols || []) syms.set(s.id, s);
186
+ const applied = [];
187
+ const rejected = [];
188
+ const at = G().stamp(new Date());
189
+ list.forEach((n, i) => {
190
+ const reject = (reason) => rejected.push({ index: i, sym: (n && n.sym) || null, reason });
191
+ if (!n || typeof n !== "object") return reject("not-an-object");
192
+ const s = syms.get(n.sym);
193
+ if (!s) return reject("unknown-symbol");
194
+ if (n.body_hash !== s.body_hash) return reject("stale-body-hash");
195
+ if (typeof n.note !== "string" || !n.note.trim()) return reject("empty-note");
196
+ const text = n.note.trim();
197
+ if (/[\r\n]/.test(text)) return reject("multi-line");
198
+ if (text.length > NOTE_MAX) return reject("too-long");
199
+ applied.push({ sym: s.id, body_hash: s.body_hash, note: text, model, at });
200
+ });
201
+
202
+ if (applied.length) {
203
+ const g = G();
204
+ const p = g.graphPaths(claudeDir);
205
+ const lock = g.acquireLock(p);
206
+ if (!lock.ok) return { ok: false, state: "unavailable", reason: "locked", holder: lock.holder, applied: 0, rejected, exit: 1 };
207
+ try {
208
+ fs.appendFileSync(p.notes, applied.map((r) => JSON.stringify(r)).join("\n") + "\n");
209
+ } finally {
210
+ g.releaseLock(p);
211
+ }
212
+ }
213
+ return { ok: true, state: rejected.length ? "partial" : "applied", applied: applied.length, rejected, model, exit: rejected.length ? 6 : 0 };
214
+ }
215
+
216
+ // Called by `graphGc` while it already holds the lock.
217
+ function compactNotes(p, index, atomicWrite) {
218
+ const rows = readNotes(p.notes);
219
+ if (!rows.length) return { kept: 0, removed: 0 };
220
+ const current = new Map();
221
+ for (const f of Object.values((index && index.by_file) || {})) for (const s of f.symbols || []) current.set(s.id, s.body_hash);
222
+ const latest = new Map();
223
+ for (const r of rows) if (current.get(r.sym) === r.body_hash) latest.set(r.sym, r);
224
+ const keep = [...latest.values()];
225
+ if (keep.length !== rows.length) atomicWrite(p.notes, keep.map((r) => JSON.stringify(r)).join("\n") + (keep.length ? "\n" : ""));
226
+ return { kept: keep.length, removed: rows.length - keep.length };
227
+ }
228
+
229
+ module.exports = { NOTE_MAX, notesPending, notesApply, noteIndex, noteFor, compactNotes };