@azure-id/orc 1.7.1 → 1.8.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +3649 -3381
  2. package/README-id.md +923 -844
  3. package/README.md +836 -788
  4. package/bin/build-agents.js +43 -27
  5. package/bin/cli.js +701 -3
  6. package/bin/graph-extract.js +927 -0
  7. package/bin/graph-notes.js +188 -0
  8. package/bin/graph-query.js +808 -0
  9. package/bin/graph-resolve.js +178 -0
  10. package/bin/graph-signals.js +277 -0
  11. package/bin/graph.js +605 -0
  12. package/bin/verify-contracts.js +4669 -4553
  13. package/bin/verify-package.js +626 -616
  14. package/bin/webui/api.js +1419 -1414
  15. package/bin/webui/fixtures/index.js +579 -576
  16. package/bin/webui/fixtures/knowledge.js +316 -291
  17. package/bin/webui/i18n/en/knowledge.json +167 -151
  18. package/bin/webui/i18n/en/overview.json +101 -100
  19. package/bin/webui/i18n/id/knowledge.json +167 -151
  20. package/bin/webui/i18n/id/overview.json +101 -100
  21. package/bin/webui/js/panels/knowledge.js +1065 -1006
  22. package/bin/webui/js/panels/overview.js +492 -488
  23. package/package.json +39 -39
  24. package/templates/agents/MODEL-MAPPING.md +163 -158
  25. package/templates/agents/orc-executor-haiku-4-5.md +133 -121
  26. package/templates/agents/orc-executor-opus-4-7-high.md +134 -122
  27. package/templates/agents/orc-executor-opus-4-7-med.md +134 -122
  28. package/templates/agents/orc-executor-opus-4-8-high.md +134 -122
  29. package/templates/agents/orc-executor-opus-5-high.md +134 -122
  30. package/templates/agents/orc-executor-opus-5-low.md +134 -122
  31. package/templates/agents/orc-executor-opus-5-med.md +134 -122
  32. package/templates/agents/orc-executor-sonnet-4-6-high.md +134 -122
  33. package/templates/agents/orc-executor-sonnet-4-6-med.md +134 -122
  34. package/templates/agents/orc-executor-sonnet-5-high.md +134 -122
  35. package/templates/agents/orc-graph-noter-sonnet-4-6-med.md +86 -0
  36. package/templates/hooks/README.md +444 -396
  37. package/templates/hooks/orc-graph-hook.js +336 -0
  38. package/templates/hooks/orc-statusline-render.js +922 -921
  39. package/templates/hooks/orc-statusline.js +1596 -1545
  40. package/templates/skills/_shared/README.md +4 -0
  41. package/templates/skills/_shared/code-graph.md +220 -0
  42. package/templates/skills/_shared/opus5-only.md +4 -0
  43. package/templates/skills/_shared/phases/execution.md +166 -147
  44. package/templates/skills/_shared/phases/planning.md +142 -135
  45. package/templates/skills/_shared/phases/preflight.md +132 -118
  46. package/templates/skills/_shared/phases/review.md +63 -53
  47. package/templates/skills/_shared/phases/ship.md +96 -88
  48. package/templates/skills/_shared/phases/trace.md +6 -0
  49. package/templates/skills/_shared/phases/wiki-consult.md +194 -189
  50. package/templates/skills/_shared/read-ladder.md +124 -102
  51. package/templates/skills/_shared/return-validation.md +259 -250
  52. package/templates/skills/orc/SKILL.md +255 -254
  53. package/templates/skills/orc-diy/references/flow-schema.md +101 -100
  54. package/templates/skills/orc-fast/SKILL.md +236 -229
  55. package/templates/skills/orc-mini/SKILL.md +267 -259
  56. package/templates/skills/orc-quick/SKILL.md +378 -361
  57. package/templates/skills/orc-quick/references/dispatch-gate.md +6 -0
  58. package/templates/skills/orc-wiki/references/staleness.md +294 -288
@@ -0,0 +1,188 @@
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
+ function noteFor(idx, sym) {
86
+ if (!idx || !sym) return null;
87
+ const cur = idx.byKey.get(`${sym.id}\0${sym.body_hash}`);
88
+ if (cur) return { text: cur.note, model: cur.model, at: cur.at, current: true };
89
+ const any = idx.bySym.get(sym.id);
90
+ if (any) return { text: null, model: any.model, at: any.at, current: false };
91
+ return null;
92
+ }
93
+
94
+ const normPath = (f) => String(f).replace(/\\/g, "/").replace(/^\.\//, "");
95
+
96
+ // exit 0 = rows to note · 1 = no index / no --files · 5 = none, or fewer than --min
97
+ function notesPending(claudeDir, opts) {
98
+ const index = readIndex(claudeDir);
99
+ if (!index || !index.by_file) return { ok: false, state: "none", reason: "no-index", exit: 1 };
100
+ const files = [...new Set((opts.files || []).map(normPath).filter(Boolean))];
101
+ if (!files.length) return { ok: false, state: "usage", reason: "missing-files", exit: 1 };
102
+ const cap = clampInt(opts.cap, 1, 200, 40);
103
+ const min = clampInt(opts.min, 1, 200, 5);
104
+ const idx = noteIndex(claudeDir);
105
+ const missing = [];
106
+ const rows = [];
107
+ for (const rel of files) {
108
+ const f = index.by_file[rel];
109
+ if (!f) {
110
+ missing.push(rel);
111
+ continue;
112
+ }
113
+ for (const s of f.symbols || []) {
114
+ if (!NOTE_KINDS.has(s.kind)) continue;
115
+ if (s.lines[1] - s.lines[0] + 1 < MIN_LINES) continue;
116
+ if (idx.byKey.has(`${s.id}\0${s.body_hash}`)) continue;
117
+ rows.push({ sym: s.id, file: rel, lines: s.lines, body_hash: s.body_hash, restale: idx.bySym.has(s.id) });
118
+ }
119
+ }
120
+ const base = { ok: true, files, missing, total: rows.length, cap, min };
121
+ if (!rows.length) return { ...base, state: "none", rows: [], more: 0, exit: 5 };
122
+ if (rows.length < min) return { ...base, state: "below-min", rows: [], waiting: rows.map((r) => r.sym), more: 0, exit: 5 };
123
+ const out = rows.slice(0, cap);
124
+ return { ...base, state: "pending", rows: out, more: rows.length - out.length, exit: 0 };
125
+ }
126
+
127
+ // exit 0 = every row applied · 1 = no index / locked · 6 = one or more rows rejected
128
+ function notesApply(claudeDir, payload) {
129
+ const index = readIndex(claudeDir);
130
+ if (!index || !index.by_file) return { ok: false, state: "none", reason: "no-index", exit: 1 };
131
+ let data = payload;
132
+ if (typeof payload === "string") {
133
+ try {
134
+ data = JSON.parse(payload);
135
+ } catch (_) {
136
+ return { ok: false, state: "invalid", reason: "not-json", applied: 0, rejected: [], exit: 6 };
137
+ }
138
+ }
139
+ const list = Array.isArray(data) ? data : data && Array.isArray(data.notes) ? data.notes : null;
140
+ if (!list) return { ok: false, state: "invalid", reason: "no-notes-array", applied: 0, rejected: [], exit: 6 };
141
+ const model = data && !Array.isArray(data) && typeof data.model === "string" && data.model.trim() ? data.model.trim().slice(0, 60) : "unknown";
142
+
143
+ const syms = new Map();
144
+ for (const f of Object.values(index.by_file)) for (const s of f.symbols || []) syms.set(s.id, s);
145
+ const applied = [];
146
+ const rejected = [];
147
+ const at = G().stamp(new Date());
148
+ list.forEach((n, i) => {
149
+ const reject = (reason) => rejected.push({ index: i, sym: (n && n.sym) || null, reason });
150
+ if (!n || typeof n !== "object") return reject("not-an-object");
151
+ const s = syms.get(n.sym);
152
+ if (!s) return reject("unknown-symbol");
153
+ if (n.body_hash !== s.body_hash) return reject("stale-body-hash");
154
+ if (typeof n.note !== "string" || !n.note.trim()) return reject("empty-note");
155
+ const text = n.note.trim();
156
+ if (/[\r\n]/.test(text)) return reject("multi-line");
157
+ if (text.length > NOTE_MAX) return reject("too-long");
158
+ applied.push({ sym: s.id, body_hash: s.body_hash, note: text, model, at });
159
+ });
160
+
161
+ if (applied.length) {
162
+ const g = G();
163
+ const p = g.graphPaths(claudeDir);
164
+ const lock = g.acquireLock(p);
165
+ if (!lock.ok) return { ok: false, state: "unavailable", reason: "locked", holder: lock.holder, applied: 0, rejected, exit: 1 };
166
+ try {
167
+ fs.appendFileSync(p.notes, applied.map((r) => JSON.stringify(r)).join("\n") + "\n");
168
+ } finally {
169
+ g.releaseLock(p);
170
+ }
171
+ }
172
+ return { ok: true, state: rejected.length ? "partial" : "applied", applied: applied.length, rejected, model, exit: rejected.length ? 6 : 0 };
173
+ }
174
+
175
+ // Called by `graphGc` while it already holds the lock.
176
+ function compactNotes(p, index, atomicWrite) {
177
+ const rows = readNotes(p.notes);
178
+ if (!rows.length) return { kept: 0, removed: 0 };
179
+ const current = new Map();
180
+ for (const f of Object.values((index && index.by_file) || {})) for (const s of f.symbols || []) current.set(s.id, s.body_hash);
181
+ const latest = new Map();
182
+ for (const r of rows) if (current.get(r.sym) === r.body_hash) latest.set(r.sym, r);
183
+ const keep = [...latest.values()];
184
+ if (keep.length !== rows.length) atomicWrite(p.notes, keep.map((r) => JSON.stringify(r)).join("\n") + (keep.length ? "\n" : ""));
185
+ return { kept: keep.length, removed: rows.length - keep.length };
186
+ }
187
+
188
+ module.exports = { NOTE_MAX, notesPending, notesApply, noteIndex, noteFor, compactNotes };