@azure-id/orc 1.7.0 → 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 (63) hide show
  1. package/CHANGELOG.md +3649 -3339
  2. package/README-id.md +923 -844
  3. package/README.md +836 -860
  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 -4527
  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 -113
  26. package/templates/agents/orc-executor-opus-4-7-high.md +134 -114
  27. package/templates/agents/orc-executor-opus-4-7-med.md +134 -114
  28. package/templates/agents/orc-executor-opus-4-8-high.md +134 -114
  29. package/templates/agents/orc-executor-opus-5-high.md +134 -114
  30. package/templates/agents/orc-executor-opus-5-low.md +134 -114
  31. package/templates/agents/orc-executor-opus-5-med.md +134 -114
  32. package/templates/agents/orc-executor-sonnet-4-6-high.md +134 -114
  33. package/templates/agents/orc-executor-sonnet-4-6-med.md +134 -114
  34. package/templates/agents/orc-executor-sonnet-5-high.md +134 -114
  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/extra-dispatch.md +1 -1
  43. package/templates/skills/_shared/opus5-only.md +4 -0
  44. package/templates/skills/_shared/phases/execution.md +166 -145
  45. package/templates/skills/_shared/phases/planning.md +142 -135
  46. package/templates/skills/_shared/phases/preflight.md +132 -118
  47. package/templates/skills/_shared/phases/review.md +63 -53
  48. package/templates/skills/_shared/phases/rules.md +5 -0
  49. package/templates/skills/_shared/phases/ship.md +96 -88
  50. package/templates/skills/_shared/phases/trace.md +6 -0
  51. package/templates/skills/_shared/phases/wiki-consult.md +194 -189
  52. package/templates/skills/_shared/read-ladder.md +124 -102
  53. package/templates/skills/_shared/return-validation.md +259 -250
  54. package/templates/skills/orc/SKILL.md +255 -254
  55. package/templates/skills/orc/references/preflight-report.md +6 -0
  56. package/templates/skills/orc/subskills/orc-execution/SKILL.md +2 -1
  57. package/templates/skills/orc/subskills/orc-execution/core.md +9 -1
  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 -360
  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,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 };