@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.
- package/CHANGELOG.md +3869 -3381
- package/README-id.md +942 -844
- package/README.md +882 -788
- package/bin/build-agents.js +43 -27
- package/bin/cli.js +45460 -44169
- package/bin/graph-extract.js +3216 -0
- package/bin/graph-gain.js +404 -0
- package/bin/graph-map.js +232 -0
- package/bin/graph-notes.js +229 -0
- package/bin/graph-query.js +1770 -0
- package/bin/graph-resolve.js +255 -0
- package/bin/graph-shard.js +325 -0
- package/bin/graph-signals.js +277 -0
- package/bin/graph.js +658 -0
- package/bin/verify-contracts.js +4809 -4553
- package/bin/verify-package.js +640 -616
- package/bin/webui/api.js +1425 -1414
- package/bin/webui/fixtures/index.js +584 -576
- package/bin/webui/fixtures/knowledge.js +356 -291
- package/bin/webui/fixtures/stats.js +107 -104
- package/bin/webui/i18n/en/knowledge.json +182 -151
- package/bin/webui/i18n/en/overview.json +101 -100
- package/bin/webui/i18n/id/knowledge.json +182 -151
- package/bin/webui/i18n/id/overview.json +101 -100
- package/bin/webui/js/panels/knowledge.js +1130 -1006
- package/bin/webui/js/panels/overview.js +492 -488
- package/package.json +39 -39
- package/templates/agents/MODEL-MAPPING.md +163 -158
- package/templates/agents/orc-executor-haiku-4-5.md +128 -121
- package/templates/agents/orc-executor-opus-4-7-high.md +129 -122
- package/templates/agents/orc-executor-opus-4-7-med.md +129 -122
- package/templates/agents/orc-executor-opus-4-8-high.md +129 -122
- package/templates/agents/orc-executor-opus-5-high.md +129 -122
- package/templates/agents/orc-executor-opus-5-low.md +129 -122
- package/templates/agents/orc-executor-opus-5-med.md +129 -122
- package/templates/agents/orc-executor-sonnet-4-6-high.md +129 -122
- package/templates/agents/orc-executor-sonnet-4-6-med.md +129 -122
- package/templates/agents/orc-executor-sonnet-5-high.md +129 -122
- package/templates/agents/orc-graph-noter-sonnet-4-6-med.md +89 -0
- package/templates/hooks/README.md +454 -396
- package/templates/hooks/orc-graph-hook.js +471 -0
- package/templates/hooks/orc-statusline-render.js +922 -921
- package/templates/hooks/orc-statusline.js +1596 -1545
- package/templates/skills/_shared/README.md +4 -0
- package/templates/skills/_shared/code-graph.md +348 -0
- package/templates/skills/_shared/opus5-only.md +4 -0
- package/templates/skills/_shared/phases/execution.md +168 -147
- package/templates/skills/_shared/phases/planning.md +149 -135
- package/templates/skills/_shared/phases/preflight.md +132 -118
- package/templates/skills/_shared/phases/review.md +63 -53
- package/templates/skills/_shared/phases/ship.md +100 -88
- package/templates/skills/_shared/phases/trace.md +8 -0
- package/templates/skills/_shared/phases/wiki-consult.md +194 -189
- package/templates/skills/_shared/read-ladder.md +132 -102
- package/templates/skills/_shared/return-validation.md +259 -250
- package/templates/skills/orc/SKILL.md +255 -254
- package/templates/skills/orc-analyze/SKILL.md +13 -13
- package/templates/skills/orc-diy/references/flow-schema.md +101 -100
- package/templates/skills/orc-fast/SKILL.md +236 -229
- package/templates/skills/orc-mini/SKILL.md +267 -259
- package/templates/skills/orc-quick/SKILL.md +378 -361
- package/templates/skills/orc-quick/references/dispatch-gate.md +6 -0
- 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 };
|