@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,277 @@
1
+ "use strict";
2
+ // ── orc graph — CHEAP SIGNALS for planners and reviewers (v1.8.0 EW5) ───────
3
+ //
4
+ // Four answers that cost NO model tokens, because every one of them is already
5
+ // on disk: git's own history, and the index the parser already built.
6
+ //
7
+ // changes what this diff actually touched, who calls it, and how risky
8
+ // that is. A reviewer asking `impact` on the DECLARED files gets
9
+ // the whole file; this gets the symbols the HUNKS overlap.
10
+ // importance one number per symbol, so a card's rows are ordered by what
11
+ // matters instead of by raw fan-in. A helper called from forty
12
+ // places is not more important than the exported entry point.
13
+ // tests the test files that reach a symbol — by CALL (the graph) and by
14
+ // NAME (`x.test.js` beside `x.js`). Both, because a test that
15
+ // imports a module without naming a symbol is still its test.
16
+ // cochange the files that historically change together with this one. The
17
+ // planner's question "what else will I have to touch" is answered
18
+ // by history far better than by static edges.
19
+ //
20
+ // THE RULE THAT SHAPES ALL FOUR: a signal is a HINT with its evidence attached.
21
+ // `changes` says WHY a symbol is high risk (exported, fan-in 4, no test caller),
22
+ // never just "high". A number nobody can check is a number nobody should act on.
23
+
24
+ const fs = require("fs");
25
+ const path = require("path");
26
+ const { spawnSync } = require("child_process");
27
+ const G = require("./graph.js");
28
+
29
+ const GIT_MAX_BUFFER = 256 * 1024 * 1024;
30
+ // A merge or a sweeping rename touches hundreds of files and would make every
31
+ // pair look related. CBM's threshold, and the same reason: a commit that broad
32
+ // carries no information about which two files belong together.
33
+ const COCHANGE_MAX_FILES = 20;
34
+ const COCHANGE_MIN = 3;
35
+ const COCHANGE_SINCE = "6.months";
36
+ const COCHANGE_ROWS = 12;
37
+
38
+ function git(root, args) {
39
+ return spawnSync("git", args, { cwd: root, encoding: "utf8", maxBuffer: GIT_MAX_BUFFER });
40
+ }
41
+
42
+ const norm = (p) => String(p).split("\\").join("/").replace(/^\.\//, "");
43
+
44
+ // ── importance ──────────────────────────────────────────────────────────────
45
+ // sqrt of the confident in-edges, then knocked down by an order of magnitude for
46
+ // each thing that makes a symbol less likely to be what you are looking for.
47
+ // The square root matters: a symbol with 100 callers is not ten times more
48
+ // important than one with 10, it is a utility.
49
+ function importance(model, sym, fanIn) {
50
+ let v = Math.sqrt(Math.max(0, fanIn));
51
+ if (!sym.exported) v *= 0.1;
52
+ // A name defined in five or more files is a convention (`handler`, `index`,
53
+ // `run`), not a landmark.
54
+ const sameName = (model.byName.get(sym.name) || []).length;
55
+ if (sameName >= 5) v *= 0.1;
56
+ if (require("./graph-query.js").TEST_FILE.test(sym.file)) v *= 0.1;
57
+ return Math.round(v * 1000) / 1000;
58
+ }
59
+
60
+ // ── tests ───────────────────────────────────────────────────────────────────
61
+ // The naming half. `src/order.js` is tested by `order.test.js`, `test_order.py`,
62
+ // `order_test.go`, `OrderTest.java` — wherever they live.
63
+ function testsByName(model, rel) {
64
+ const base = path.posix.basename(rel).replace(/\.[^.]+$/, "");
65
+ if (!base) return [];
66
+ const want = new Set([
67
+ `${base}.test`,
68
+ `${base}.spec`,
69
+ `test_${base}`,
70
+ `${base}_test`,
71
+ `${base}Test`,
72
+ `${base}Tests`,
73
+ ]);
74
+ const out = [];
75
+ for (const f of model.fileSet) {
76
+ if (f === rel) continue;
77
+ const stem = path.posix.basename(f).replace(/\.[^.]+$/, "");
78
+ if (want.has(stem)) out.push(f);
79
+ }
80
+ return out.sort();
81
+ }
82
+
83
+ // ── changes ─────────────────────────────────────────────────────────────────
84
+ // exit 0 answered · 1 no graph or not a git repository
85
+ //
86
+ // `git diff -U0` gives the hunks. A symbol whose line range overlaps a hunk was
87
+ // really touched; a symbol elsewhere in the same file was not, and saying it was
88
+ // is what makes a whole-file `impact` noisy at review time.
89
+ function parseDiff(text) {
90
+ const byFile = new Map();
91
+ let cur = null;
92
+ for (const line of String(text).split("\n")) {
93
+ const f = /^\+\+\+ b\/(.*)$/.exec(line);
94
+ if (f) {
95
+ cur = f[1] === "/dev/null" ? null : norm(f[1]);
96
+ if (cur && !byFile.has(cur)) byFile.set(cur, []);
97
+ continue;
98
+ }
99
+ if (!cur) continue;
100
+ const h = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/.exec(line);
101
+ if (h) {
102
+ const start = Number(h[1]);
103
+ const count = h[2] === undefined ? 1 : Number(h[2]);
104
+ // A pure deletion has a zero-length new side; it still touched the line it
105
+ // was removed from, so record it as a one-line hunk there.
106
+ byFile.get(cur).push(count === 0 ? [start, start] : [start, start + count - 1]);
107
+ }
108
+ }
109
+ return byFile;
110
+ }
111
+
112
+ // The paths this diff touches, by name only — two cheap git calls, no parsing.
113
+ // `changes` is a REVIEW-time command, so it heals on these before it answers:
114
+ // a reviewer told "nothing depends on this" by a stale index is worse served
115
+ // than one told nothing at all.
116
+ function changedPaths(root, base) {
117
+ const out = [];
118
+ const d = git(root, base ? ["diff", "--name-only", "-z", base, "--"] : ["diff", "--name-only", "-z", "HEAD", "--"]);
119
+ if (d.status === 0) for (const f of d.stdout.split("\0")) if (f) out.push(norm(f));
120
+ const u = git(root, ["ls-files", "--others", "--exclude-standard", "-z"]);
121
+ if (u.status === 0) for (const f of u.stdout.split("\0")) if (f) out.push(norm(f));
122
+ return [...new Set(out)];
123
+ }
124
+
125
+ function graphChanges(claudeDir, root, opts) {
126
+ const Q = require("./graph-query.js");
127
+ const model = Q.loadModel(claudeDir, root);
128
+ if (!model) return { ok: false, state: "none", reason: "no-index", exit: 1 };
129
+ const base = opts.base || null;
130
+ const d = git(root, base ? ["diff", "-U0", base, "--"] : ["diff", "-U0", "HEAD", "--"]);
131
+ if (d.error || d.status !== 0) return { ok: false, state: "unavailable", reason: "git-diff-failed", exit: 1 };
132
+ const hunks = parseDiff(d.stdout);
133
+
134
+ // An untracked file is a change too, and `git diff` never mentions it. Its
135
+ // whole body is the hunk.
136
+ const un = git(root, ["ls-files", "--others", "--exclude-standard", "-z"]);
137
+ if (un.status === 0) {
138
+ for (const rel of un.stdout.split("\0")) {
139
+ if (!rel) continue;
140
+ const r = norm(rel);
141
+ if (!hunks.has(r)) hunks.set(r, [[1, Number.MAX_SAFE_INTEGER]]);
142
+ }
143
+ }
144
+
145
+ const rows = [];
146
+ const files = [...hunks.keys()].filter((f) => model.fileSet.has(f)).sort();
147
+ const indexable = new Set(Object.keys(G.LANG_BY_EXT));
148
+ const absent = [...hunks.keys()]
149
+ .filter((f) => !model.fileSet.has(f) && indexable.has(path.posix.extname(f).toLowerCase()))
150
+ .sort();
151
+ for (const rel of files) {
152
+ const ranges = hunks.get(rel);
153
+ for (const sym of (model.byFile[rel] || {}).symbols || []) {
154
+ if (sym.kind === "module") continue;
155
+ const touched = ranges.some((h) => sym.lines[0] <= h[1] && h[0] <= sym.lines[1]);
156
+ if (!touched) continue;
157
+ const c = Q.callersOf(model, sym);
158
+ const callerFiles = [...new Set(c.confident.map((x) => x.sym.file))];
159
+ const tests = [...new Set([...callerFiles.filter((f) => Q.TEST_FILE.test(f)), ...testsByName(model, rel)])];
160
+ // The reason is part of the answer. "high" with nothing behind it is a
161
+ // number nobody can check, and nobody should act on.
162
+ const why = [];
163
+ if (sym.exported) why.push("exported");
164
+ why.push(`fan-in ${c.confident.length}`);
165
+ if (!tests.length) why.push("no test reaches it");
166
+ if (c.maybe) why.push(`${c.maybe} ambiguous caller(s) not counted`);
167
+ const risk = sym.exported && c.confident.length >= 3 && !tests.length ? "high" : sym.exported || c.confident.length >= 1 ? "medium" : "low";
168
+ rows.push({
169
+ id: sym.id,
170
+ qname: sym.qname,
171
+ kind: sym.kind,
172
+ file: rel,
173
+ lines: sym.lines,
174
+ exported: !!sym.exported,
175
+ fan_in: c.confident.length,
176
+ maybe_callers: c.maybe,
177
+ caller_files: callerFiles.sort(),
178
+ tests: tests.sort(),
179
+ risk,
180
+ why,
181
+ });
182
+ }
183
+ }
184
+ const order = { high: 0, medium: 1, low: 2 };
185
+ rows.sort((a, b) => order[a.risk] - order[b.risk] || b.fan_in - a.fan_in || a.file.localeCompare(b.file) || a.lines[0] - b.lines[0]);
186
+ const counts = { high: 0, medium: 0, low: 0 };
187
+ for (const r of rows) counts[r.risk]++;
188
+ return {
189
+ ok: true,
190
+ state: "found",
191
+ base: base || "HEAD",
192
+ files,
193
+ not_in_graph: absent,
194
+ symbols: rows,
195
+ counts,
196
+ generation: model.meta.generation || 0,
197
+ exit: 0,
198
+ };
199
+ }
200
+
201
+ // ── cochange ────────────────────────────────────────────────────────────────
202
+ // exit 0 answered · 1 no graph, or git could not answer · 4 nothing reaches the
203
+ // threshold (an ANSWER: this file changes alone)
204
+ //
205
+ // Cached per HEAD. History does not move under a working-tree edit, so the cache
206
+ // is valid for exactly as long as HEAD is.
207
+ function cochangePath(claudeDir) {
208
+ return path.join(G.graphPaths(claudeDir).dir, "cochange.json");
209
+ }
210
+
211
+ function buildCochange(root) {
212
+ const r = git(root, ["log", `--since=${COCHANGE_SINCE}`, "--name-only", "--pretty=format:%x00", "--no-merges"]);
213
+ if (r.error || r.status !== 0) return null;
214
+ const pairs = Object.create(null);
215
+ const total = Object.create(null);
216
+ for (const commit of r.stdout.split("\0")) {
217
+ const files = commit.split("\n").map((x) => x.trim()).filter(Boolean).map(norm);
218
+ const uniq = [...new Set(files)];
219
+ if (uniq.length < 2 || uniq.length > COCHANGE_MAX_FILES) continue;
220
+ for (const f of uniq) total[f] = (total[f] || 0) + 1;
221
+ for (let i = 0; i < uniq.length; i++) {
222
+ for (let j = i + 1; j < uniq.length; j++) {
223
+ const k = uniq[i] < uniq[j] ? `${uniq[i]}\u0000${uniq[j]}` : `${uniq[j]}\u0000${uniq[i]}`;
224
+ pairs[k] = (pairs[k] || 0) + 1;
225
+ }
226
+ }
227
+ }
228
+ return { pairs, total };
229
+ }
230
+
231
+ function graphCochange(claudeDir, root, opts) {
232
+ const rel = norm(opts.file || "");
233
+ if (!rel) return { ok: false, reason: "missing-operand", exit: 1 };
234
+ const head = git(root, ["rev-parse", "HEAD"]);
235
+ if (head.status !== 0) return { ok: false, state: "unavailable", reason: "not-git", exit: 1 };
236
+ const at = head.stdout.trim();
237
+
238
+ let cache = null;
239
+ try {
240
+ cache = JSON.parse(fs.readFileSync(cochangePath(claudeDir), "utf8"));
241
+ } catch (_) {}
242
+ if (!cache || cache.head !== at || cache.schema !== 1) {
243
+ const built = buildCochange(root);
244
+ if (!built) return { ok: false, state: "unavailable", reason: "git-log-failed", exit: 1 };
245
+ cache = { schema: 1, head: at, since: COCHANGE_SINCE, ...built };
246
+ try {
247
+ G.atomicWrite(cochangePath(claudeDir), JSON.stringify(cache));
248
+ } catch (_) {}
249
+ }
250
+
251
+ const rows = [];
252
+ const pre = rel + "\u0000";
253
+ const suf = "\u0000" + rel;
254
+ for (const [k, n] of Object.entries(cache.pairs || {})) {
255
+ if (n < COCHANGE_MIN) continue;
256
+ let other = null;
257
+ if (k.startsWith(pre)) other = k.slice(pre.length);
258
+ else if (k.endsWith(suf)) other = k.slice(0, k.length - suf.length);
259
+ if (!other) continue;
260
+ const own = (cache.total || {})[rel] || 0;
261
+ rows.push({ file: other, together: n, of: own, share: own ? Math.round((n / own) * 100) / 100 : 0 });
262
+ }
263
+ rows.sort((a, b) => b.together - a.together || a.file.localeCompare(b.file));
264
+ return {
265
+ ok: true,
266
+ state: rows.length ? "found" : "none",
267
+ file: rel,
268
+ commits: (cache.total || {})[rel] || 0,
269
+ since: cache.since,
270
+ min: COCHANGE_MIN,
271
+ rows: rows.slice(0, COCHANGE_ROWS),
272
+ more: Math.max(0, rows.length - COCHANGE_ROWS),
273
+ exit: rows.length ? 0 : 4,
274
+ };
275
+ }
276
+
277
+ module.exports = { importance, testsByName, parseDiff, changedPaths, graphChanges, graphCochange, cochangePath, COCHANGE_MIN, COCHANGE_MAX_FILES };