@azure-id/orc 1.9.0 → 1.9.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 +202 -0
- package/README-id.md +57 -59
- package/README.md +53 -54
- package/bin/build-agents.js +109 -109
- package/bin/cli.js +262 -58
- package/bin/graph-audit.js +235 -0
- package/bin/graph-extract.js +308 -3
- package/bin/graph-gain.js +23 -3
- package/bin/graph-map.js +9 -1
- package/bin/graph-query.js +152 -10
- package/bin/graph-shard.js +6 -1
- package/bin/graph-signals.js +287 -4
- package/bin/graph.js +717 -658
- package/bin/onboarding-content.js +4 -4
- package/bin/pricing.json +207 -200
- package/bin/verify-contracts.js +117 -2
- package/bin/verify-package.js +11 -7
- package/bin/webui/css/04-motion.css +162 -152
- package/bin/webui/css/panels/knowledge.css +563 -116
- package/bin/webui/fixtures/extra.js +2036 -2036
- package/bin/webui/fixtures/hookui.js +5 -5
- package/bin/webui/fixtures/knowledge.js +21 -4
- package/bin/webui/fixtures/settings.js +305 -305
- package/bin/webui/i18n/en/knowledge.json +122 -4
- package/bin/webui/i18n/id/knowledge.json +122 -4
- package/bin/webui/js/panels/knowledge.js +573 -9
- package/bin/webui/js/panels/overview.js +13 -4
- package/mock-run/context-combiner.md +100 -100
- package/mock-run/orc-budget.md +534 -534
- package/mock-run/orc-challenge-council.md +262 -262
- package/mock-run/orc-ultra.md +103 -103
- package/package.json +1 -1
- package/templates/agents/MODEL-MAPPING.md +43 -43
- package/templates/agents/orc-advisor-opus-5-xhigh.md +56 -56
- package/templates/agents/orc-analyze-mini-opus-5-med.md +60 -60
- package/templates/agents/orc-analyze-mini-sonnet-5-high.md +58 -58
- package/templates/agents/orc-challenge-advisor-opus-5-med.md +75 -75
- package/templates/agents/orc-challenge-contrarian-opus-5-high.md +110 -110
- package/templates/agents/orc-challenge-executor-opus-5-med.md +114 -114
- package/templates/agents/orc-challenge-expansionist-opus-5-med.md +112 -112
- package/templates/agents/orc-challenge-judge-opus-5-high.md +132 -132
- package/templates/agents/orc-challenge-outsider-opus-5-low.md +109 -109
- package/templates/agents/orc-challenge-principles-opus-5-high.md +109 -109
- package/templates/agents/orc-challenge-reader-opus-5-low.md +90 -90
- package/templates/agents/orc-claude-writer-opus-5-med.md +55 -55
- package/templates/agents/orc-context-combiner-opus-5-high.md +88 -88
- package/templates/agents/orc-doc-checker-opus-5-low.md +108 -108
- package/templates/agents/orc-doc-writer-opus-5-med.md +134 -134
- package/templates/agents/orc-executor-haiku-4-5.md +2 -1
- package/templates/agents/orc-executor-opus-4-7-high.md +2 -1
- package/templates/agents/orc-executor-opus-4-7-med.md +2 -1
- package/templates/agents/orc-executor-opus-4-8-high.md +2 -1
- package/templates/agents/orc-executor-opus-5-high.md +4 -3
- package/templates/agents/orc-executor-opus-5-low.md +4 -3
- package/templates/agents/orc-executor-opus-5-med.md +4 -3
- package/templates/agents/orc-executor-sonnet-4-6-high.md +2 -1
- package/templates/agents/orc-executor-sonnet-4-6-med.md +2 -1
- package/templates/agents/orc-executor-sonnet-5-high.md +2 -1
- package/templates/agents/orc-graph-noter-sonnet-4-6-med.md +1 -1
- package/templates/agents/orc-judge-opus-5-xhigh.md +85 -85
- package/templates/agents/orc-learn-writer-opus-5-low.md +73 -73
- package/templates/agents/orc-pattern-codifier-opus-5-med.md +65 -65
- package/templates/agents/orc-planner-mini-opus-5-med.md +4 -4
- package/templates/agents/orc-planner-mini-sonnet-5-high.md +1 -1
- package/templates/agents/orc-planner-opus-5-med.md +160 -160
- package/templates/agents/orc-recon-opus-5-low.md +18 -8
- package/templates/agents/orc-recon-sonnet-4-6-med.md +15 -5
- package/templates/agents/orc-retro-opus-5-med.md +3 -3
- package/templates/agents/orc-reviewer-opus-5-med.md +60 -60
- package/templates/agents/orc-scout-opus-5-low.md +40 -40
- package/templates/agents/orc-system-analyst-opus-5-high.md +120 -120
- package/templates/agents/orc-test-author-opus-5-med.md +71 -71
- package/templates/agents/orc-test-designer-opus-5-high.md +158 -158
- package/templates/agents/orc-test-interpreter-opus-5-low.md +130 -130
- package/templates/agents/orc-verifier-opus-5-med.md +69 -69
- package/templates/agents/orc-wiki-scanner-opus-5-med.md +81 -81
- package/templates/commands/orc-ultra.md +17 -17
- package/templates/commands/orc-verify.md +11 -11
- package/templates/hooks/README.md +13 -3
- package/templates/hooks/orc-effort-guard.js +178 -178
- package/templates/hooks/orc-graph-hook.js +53 -7
- package/templates/hooks/orc-statusline.js +6 -6
- package/templates/skills/_shared/code-graph.md +57 -18
- package/templates/skills/_shared/config-precedence.md +198 -198
- package/templates/skills/_shared/extra-dispatch.md +1346 -1346
- package/templates/skills/_shared/opus5-only.md +8 -8
- package/templates/skills/_shared/phases/analyst-gates.md +136 -136
- package/templates/skills/_shared/phases/execution.md +4 -4
- package/templates/skills/_shared/phases/planning.md +3 -3
- package/templates/skills/_shared/phases/preflight.md +1 -1
- package/templates/skills/_shared/phases/review.md +4 -2
- package/templates/skills/_shared/phases/ship.md +1 -1
- package/templates/skills/_shared/phases/trace.md +3 -2
- package/templates/skills/_shared/read-ladder.md +14 -1
- package/templates/skills/_shared/return-validation.md +1 -1
- package/templates/skills/context-combiner/SKILL.md +1 -1
- package/templates/skills/context-combiner/schemas/combined-report.md +78 -78
- package/templates/skills/orc/README.md +2 -2
- package/templates/skills/orc/SKILL.md +8 -8
- package/templates/skills/orc/config.md +9 -9
- package/templates/skills/orc/examples/full-run-mock.md +73 -73
- package/templates/skills/orc/references/effort-and-mode.md +222 -222
- package/templates/skills/orc/references/preflight-report.md +2 -2
- package/templates/skills/orc/references/ultra-mode.md +3 -3
- package/templates/skills/orc/subskills/orc-planner/SKILL.md +2 -2
- package/templates/skills/orc/subskills/orc-planner-mini/SKILL.md +121 -121
- package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +76 -76
- package/templates/skills/orc/subskills/orc-testgen/SKILL.md +45 -45
- package/templates/skills/orc-analyze/SKILL.md +2 -2
- package/templates/skills/orc-analyze/examples/analyze-mock.md +42 -42
- package/templates/skills/orc-analyze/schemas/report-audit.md +83 -83
- package/templates/skills/orc-analyze/schemas/report-prose.md +63 -63
- package/templates/skills/orc-analyze/schemas/report-requirement.md +78 -78
- package/templates/skills/orc-challenge/README.md +142 -142
- package/templates/skills/orc-challenge/examples/council-full-roster.md +273 -273
- package/templates/skills/orc-challenge/references/council.md +315 -315
- package/templates/skills/orc-challenge/references/intake.md +171 -171
- package/templates/skills/orc-claude/SKILL.md +14 -14
- package/templates/skills/orc-diy/README.md +143 -143
- package/templates/skills/orc-diy/references/flow-schema.md +1 -1
- package/templates/skills/orc-doc/README.md +229 -229
- package/templates/skills/orc-doc/examples/orc-doc-prd-run.md +325 -325
- package/templates/skills/orc-doc/references/chunking.md +527 -527
- package/templates/skills/orc-fast/SKILL.md +4 -4
- package/templates/skills/orc-learn/SKILL.md +14 -14
- package/templates/skills/orc-learn/examples/learn-run-mock.md +61 -61
- package/templates/skills/orc-mini/SKILL.md +16 -20
- package/templates/skills/orc-mini/references/complexity.md +25 -11
- package/templates/skills/orc-quick/SKILL.md +12 -13
- package/templates/skills/orc-quick/references/gh-mode.md +1 -1
- package/templates/skills/orc-quick/references/look.md +5 -5
- package/templates/skills/orc-verify/SKILL.md +15 -15
- package/templates/skills/orc-verify/examples/verify-mock.md +33 -33
- package/templates/skills/orc-wiki/references/extra.md +1 -1
package/bin/graph-gain.js
CHANGED
|
@@ -254,6 +254,13 @@ function stampMs(at) {
|
|
|
254
254
|
return null;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
// M2 (v1.9.1): the reads a project COULD have made. `never_called` is
|
|
258
|
+
// `READ_SET` minus what the ledger holds, for the scope asked about. It is a
|
|
259
|
+
// fact and it carries no advice: a graph is not wrong for holding an answer
|
|
260
|
+
// nobody asked for, and this file does not tell anyone to start asking.
|
|
261
|
+
// `hint`, `read-note` and `hook-update` are the hook's rows, not reads.
|
|
262
|
+
const READ_SET = ["ctx", "for-slice", "impact", "map", "changes", "cochange", "coverage", "path"];
|
|
263
|
+
|
|
257
264
|
function gain(claudeDir, opts) {
|
|
258
265
|
const o = opts || {};
|
|
259
266
|
const all = readLedger(claudeDir);
|
|
@@ -267,11 +274,19 @@ function gain(claudeDir, opts) {
|
|
|
267
274
|
});
|
|
268
275
|
if (!rows.length) return { ok: false, state: "none", reason: o.run ? "no-rows-for-run" : "no-rows", exit: 1 };
|
|
269
276
|
|
|
270
|
-
|
|
277
|
+
// B3 (v1.9.1): `envelope` is everything a `--json` answer carried BEYOND its
|
|
278
|
+
// card — the row arrays, the scalars, the punctuation. Measured on this tree
|
|
279
|
+
// it was 4 to 8 times the card, and until now the meter called the card the
|
|
280
|
+
// whole cost. A row written before 1.9.1, and every row the hook writes, has
|
|
281
|
+
// no envelope and sums as 0.
|
|
282
|
+
const paid = { card: 0, source: 0, hints: 0, envelope: 0 };
|
|
271
283
|
const avoided = { low: 0, high: 0 };
|
|
272
284
|
const calls = { low: 0, high: 0 };
|
|
273
285
|
const byCommand = {};
|
|
274
|
-
|
|
286
|
+
// R2 (v1.9.1): `wide_unhinted` is a COUNT of whole-file reads of wide files
|
|
287
|
+
// that the `on,read` hint would have named ranges for. It put nothing into
|
|
288
|
+
// any context, so it prices at zero; it is the number DE-8 waits for.
|
|
289
|
+
const hints = { injected: 0, read_notes: 0, updates: 0, wide_unhinted: 0 };
|
|
275
290
|
// W8: rows the sharded read path could not size. Their estimate omits the
|
|
276
291
|
// search term, so a FALLING estimate can be told apart from a falling saving.
|
|
277
292
|
let unsized = 0;
|
|
@@ -283,6 +298,7 @@ function gain(claudeDir, opts) {
|
|
|
283
298
|
paid.card += Number(r.paid.card) || 0;
|
|
284
299
|
paid.source += Number(r.paid.source) || 0;
|
|
285
300
|
paid.hints += Number(r.paid.hints) || 0;
|
|
301
|
+
paid.envelope += Number(r.paid.envelope) || 0;
|
|
286
302
|
if (used) {
|
|
287
303
|
avoided.low += Number(r.avoided.low) || 0;
|
|
288
304
|
avoided.high += Number(r.avoided.high) || 0;
|
|
@@ -294,10 +310,11 @@ function gain(claudeDir, opts) {
|
|
|
294
310
|
if (r.cmd === "hint") hints.injected++;
|
|
295
311
|
if (r.cmd === "read-note") hints.read_notes++;
|
|
296
312
|
if (r.cmd === "hook-update") hints.updates++;
|
|
313
|
+
if (r.cmd === "wide-unhinted") hints.wide_unhinted++;
|
|
297
314
|
if (r.gen) gens.push(Number(r.gen));
|
|
298
315
|
if (r.run) runs.add(r.run);
|
|
299
316
|
}
|
|
300
|
-
const paidTotal = paid.card + paid.source + paid.hints;
|
|
317
|
+
const paidTotal = paid.card + paid.source + paid.hints + paid.envelope;
|
|
301
318
|
gens.sort((a, b) => a - b);
|
|
302
319
|
return {
|
|
303
320
|
ok: true,
|
|
@@ -313,6 +330,8 @@ function gain(claudeDir, opts) {
|
|
|
313
330
|
calls,
|
|
314
331
|
net: { low: avoided.low - paidTotal, high: avoided.high - paidTotal },
|
|
315
332
|
by_command: byCommand,
|
|
333
|
+
read_set: READ_SET,
|
|
334
|
+
never_called: READ_SET.filter((c) => !Object.prototype.hasOwnProperty.call(byCommand, c)),
|
|
316
335
|
hints,
|
|
317
336
|
unsized,
|
|
318
337
|
estimate: true,
|
|
@@ -373,6 +392,7 @@ function deltaPct(on, off) {
|
|
|
373
392
|
}
|
|
374
393
|
|
|
375
394
|
module.exports = {
|
|
395
|
+
READ_SET,
|
|
376
396
|
MEASURED_MIN_RUNS,
|
|
377
397
|
median,
|
|
378
398
|
summarise,
|
package/bin/graph-map.js
CHANGED
|
@@ -149,7 +149,15 @@ function multiplier(model, rel) {
|
|
|
149
149
|
let m = 1;
|
|
150
150
|
if (Q.TEST_FILE.test(rel)) m *= 0.1;
|
|
151
151
|
const syms = (model.byFile[rel].symbols || []).filter((s) => s.kind !== "module");
|
|
152
|
-
|
|
152
|
+
// A4 (v1.9.1): a file with NO symbol is as far from "a place behaviour lives"
|
|
153
|
+
// as one whose symbols are all private, and it is knocked down the same way.
|
|
154
|
+
// Measured on a real front-end project, 16 of the 26 rows a `--budget 600`
|
|
155
|
+
// map printed named a file with nothing in it — a ranking that sends a
|
|
156
|
+
// planner to a file the graph cannot answer a question about.
|
|
157
|
+
//
|
|
158
|
+
// It is a READ-TIME rank, not a stored one: `map.json` holds the edges and
|
|
159
|
+
// the base ranking and is untouched, so nothing is re-extracted for this.
|
|
160
|
+
if (!syms.length || !syms.some((s) => s.exported)) m *= 0.5;
|
|
153
161
|
return m;
|
|
154
162
|
}
|
|
155
163
|
|
package/bin/graph-query.js
CHANGED
|
@@ -340,6 +340,13 @@ function classSymbol(model, rel, name) {
|
|
|
340
340
|
const s = ((model.byFile[t.file] || {}).symbols || []).find((x) => x.kind === "class" && x.name === t.name);
|
|
341
341
|
if (s) return { sym: s, state: "IMPORT" };
|
|
342
342
|
}
|
|
343
|
+
// W5b: a DEFAULT import names the file's default export, whatever the
|
|
344
|
+
// importer calls it (`import FM from "./formMixin"`). When that file holds
|
|
345
|
+
// exactly one exported class, it is the one.
|
|
346
|
+
if (bd.orig === "default") {
|
|
347
|
+
const only = ((model.byFile[bd.files[0]] || {}).symbols || []).filter((x) => x.kind === "class" && x.exported);
|
|
348
|
+
if (only.length === 1) return { sym: only[0], state: "IMPORT" };
|
|
349
|
+
}
|
|
343
350
|
}
|
|
344
351
|
if (f.lang === "go") {
|
|
345
352
|
const s = filesInDir(model, posix.dirname(rel), ".go").flatMap((x) => ((model.byFile[x] || {}).symbols || []).filter((y) => y.kind === "class" && y.name === name));
|
|
@@ -840,6 +847,12 @@ function warmBlobs(model, queries) {
|
|
|
840
847
|
if (t.kind === "symbol") rels.add(t.sym.file);
|
|
841
848
|
else if (t.kind === "file") rels.add(t.rel);
|
|
842
849
|
}
|
|
850
|
+
warmFiles(model, rels);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// The same batch, for files the caller already knows by path (R1: the caller
|
|
854
|
+
// files a `--callers-source` card reads).
|
|
855
|
+
function warmFiles(model, rels) {
|
|
843
856
|
const onDisk = [...rels].filter((rel) => !model.blobStates.has(rel) && fs.existsSync(path.join(model.root, ...rel.split("/"))));
|
|
844
857
|
for (const rel of rels) if (!fs.existsSync(path.join(model.root, ...rel.split("/")))) model.blobStates.set(rel, "deleted");
|
|
845
858
|
if (!onDisk.length) return;
|
|
@@ -879,14 +892,15 @@ const BLOB_LABEL = {
|
|
|
879
892
|
// · `tail` is the `--source` block (D1). It is charged against the SAME
|
|
880
893
|
// budget and it goes LAST, so the rows always win: source takes what the
|
|
881
894
|
// card left, line by line, and the footer says how many lines it cut.
|
|
882
|
-
function fit(items, budget, footer, tail) {
|
|
895
|
+
function fit(items, budget, footer, tail, blocks) {
|
|
883
896
|
// The reserve is the LONGEST footer this card could print — one hidden
|
|
884
897
|
// count per section present — so the final `used` can never pass the
|
|
885
898
|
// budget, whatever ends up hidden. (W2: a card with five sections overran a
|
|
886
899
|
// 100-token budget by five tokens on the footer alone.)
|
|
887
900
|
const worst = {};
|
|
888
901
|
for (const it of items) if (it.pri > 0) worst[it.section] = 99;
|
|
889
|
-
const
|
|
902
|
+
const worstBlocks = blocks && blocks.length ? { shown: 0, want: blocks.length } : null;
|
|
903
|
+
const reserve = tok(footer(0, worst, tail ? { kept: 0, want: tail.want, cut: tail.want } : null, worstBlocks)) + 2;
|
|
890
904
|
let used = 0;
|
|
891
905
|
const keep = new Set();
|
|
892
906
|
const hidden = {};
|
|
@@ -918,9 +932,32 @@ function fit(items, budget, footer, tail) {
|
|
|
918
932
|
}
|
|
919
933
|
source = { kept: body.length, want: tail.want, cut: tail.want - body.length };
|
|
920
934
|
}
|
|
921
|
-
|
|
935
|
+
// R1 (v1.9.1): the callers' own lines. They go AFTER the target's source, and
|
|
936
|
+
// each block is kept WHOLE or not at all — six lines cut to four is a call
|
|
937
|
+
// site with its arguments missing, which is worse than no call site.
|
|
938
|
+
let callersShown = null;
|
|
939
|
+
if (blocks && blocks.length) {
|
|
940
|
+
const headOf = (n) => ` callers source ${n < blocks.length ? `${n} of ${blocks.length}` : n} shown (${CALLERS_SOURCE_LINES} lines each${n < blocks.length ? "; raise --budget" : ""})`;
|
|
941
|
+
let left = budget - used - reserve - tok(headOf(0)) - 1;
|
|
942
|
+
const out = [];
|
|
943
|
+
for (const b of blocks) {
|
|
944
|
+
const open = " ```" + (b.fence || "");
|
|
945
|
+
const block = [b.head, open, ...b.lines, " ```"];
|
|
946
|
+
const t = block.reduce((a, l) => a + tok(l) + 1, 0);
|
|
947
|
+
if (t > left) break;
|
|
948
|
+
left -= t;
|
|
949
|
+
out.push({ b, block });
|
|
950
|
+
}
|
|
951
|
+
if (out.length) {
|
|
952
|
+
const all = [headOf(out.length), ...out.flatMap((x) => x.block)];
|
|
953
|
+
used += all.reduce((a, l) => a + tok(l) + 1, 0);
|
|
954
|
+
lines.push(...all);
|
|
955
|
+
}
|
|
956
|
+
callersShown = { shown: out.length, want: blocks.length, kept: out.map((x) => x.b) };
|
|
957
|
+
}
|
|
958
|
+
const foot = footer(used, hidden, source, callersShown);
|
|
922
959
|
if (foot) lines.push(foot);
|
|
923
|
-
return { card: lines.join("\n"), kept: keep, hidden, source, used: used + tok(foot) };
|
|
960
|
+
return { card: lines.join("\n"), kept: keep, hidden, source, callers: callersShown, used: used + tok(foot) };
|
|
924
961
|
}
|
|
925
962
|
|
|
926
963
|
// ── E6: the tree format ─────────────────────────────────────────────────────
|
|
@@ -1021,6 +1058,97 @@ function sourceText(tail, kept) {
|
|
|
1021
1058
|
return tail.lines.slice(0, kept).join("\n");
|
|
1022
1059
|
}
|
|
1023
1060
|
|
|
1061
|
+
// ── R1 (v1.9.1): the call sites WITH the card ───────────────────────────────
|
|
1062
|
+
// A recon agent asked "who calls this" and then opened every caller file to
|
|
1063
|
+
// see HOW it was called. Six lines around each call site answer that in the
|
|
1064
|
+
// same call. The limits, and the reason for each:
|
|
1065
|
+
// · CONFIDENT callers only. Source printed for an AMBIGUOUS guess reads as a
|
|
1066
|
+
// fact, and that is the false sentence the R3-C eval found.
|
|
1067
|
+
// · never the target's OWN file. The agent that asks is about to edit that
|
|
1068
|
+
// file, and a file you edit is read in full with `Read` first (the read
|
|
1069
|
+
// ladder's exception 1) — a CLI print of it would be read twice.
|
|
1070
|
+
// · ≤ 5 callers, 6 lines each, charged to the SAME budget, after everything
|
|
1071
|
+
// else. The rows always win.
|
|
1072
|
+
const CALLERS_SOURCE_MAX = 5;
|
|
1073
|
+
const CALLERS_SOURCE_LINES = 6;
|
|
1074
|
+
|
|
1075
|
+
function fileLines(model, rel) {
|
|
1076
|
+
model.textCache = model.textCache || new Map();
|
|
1077
|
+
if (model.textCache.has(rel)) return model.textCache.get(rel);
|
|
1078
|
+
let all = null;
|
|
1079
|
+
try {
|
|
1080
|
+
const text = fs.readFileSync(path.join(model.root, ...rel.split("/")), "utf8");
|
|
1081
|
+
all = text.split(/\r?\n/);
|
|
1082
|
+
if (all.length > 1 && all[all.length - 1] === "") all.pop();
|
|
1083
|
+
} catch (_) {
|
|
1084
|
+
all = null;
|
|
1085
|
+
}
|
|
1086
|
+
model.textCache.set(rel, all);
|
|
1087
|
+
return all;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
function callerBlocks(model, sym, confident) {
|
|
1091
|
+
const others = confident.filter((c) => c.sym.file !== sym.file);
|
|
1092
|
+
// One git call for the files the blocks will most likely come from. A caller
|
|
1093
|
+
// past this slice (many call sites in one window) is still checked, one by one.
|
|
1094
|
+
warmFiles(model, new Set(others.slice(0, CALLERS_SOURCE_MAX * 4).map((c) => c.sym.file)));
|
|
1095
|
+
const out = [];
|
|
1096
|
+
for (const c of others) {
|
|
1097
|
+
if (out.length >= CALLERS_SOURCE_MAX) break;
|
|
1098
|
+
const rel = c.sym.file;
|
|
1099
|
+
// Two call sites a line apart are ONE window. Printing it twice is the
|
|
1100
|
+
// same six lines paid twice.
|
|
1101
|
+
if (out.some((b) => b.file === rel && b.from <= c.line && c.line <= b.to)) continue;
|
|
1102
|
+
const fresh = blobState(model, rel);
|
|
1103
|
+
if (fresh === "deleted") continue;
|
|
1104
|
+
const all = fileLines(model, rel);
|
|
1105
|
+
if (!all || !all.length) continue;
|
|
1106
|
+
const at = Math.max(1, Math.min(all.length, Number(c.line) || 1));
|
|
1107
|
+
// Two lines above, three below — shifted, not shortened, at either end of
|
|
1108
|
+
// the file, so every block is six lines when the file has six.
|
|
1109
|
+
const from = Math.max(1, Math.min(at - 2, all.length - CALLERS_SOURCE_LINES + 1));
|
|
1110
|
+
const to = Math.min(all.length, from + CALLERS_SOURCE_LINES - 1);
|
|
1111
|
+
const width = String(to).length;
|
|
1112
|
+
const lines = [];
|
|
1113
|
+
for (let i = from; i <= to; i++) lines.push(` ${String(i).padStart(width)} ${all[i - 1]}`);
|
|
1114
|
+
const caller = c.url || c.sym.qname;
|
|
1115
|
+
const changed = fresh !== "current";
|
|
1116
|
+
out.push({
|
|
1117
|
+
head: ` ${rel}:${from}-${to} ← ${caller}${changed ? " — CHANGED since index, the range may have moved" : ""}`,
|
|
1118
|
+
fence: FENCE_BY_EXT[posix.extname(rel)] || "",
|
|
1119
|
+
lines,
|
|
1120
|
+
file: rel,
|
|
1121
|
+
from,
|
|
1122
|
+
to,
|
|
1123
|
+
caller,
|
|
1124
|
+
changed,
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
return out;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function callersSourceNote(cs) {
|
|
1131
|
+
if (!cs || cs.shown >= cs.want) return "";
|
|
1132
|
+
return cs.shown ? ` · callers source ${cs.want - cs.shown} cut (raise --budget)` : ` · callers source not shown (${cs.want} callers, raise --budget)`;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// ── R4 (v1.9.1): where a language server should be asked ───────────────────
|
|
1136
|
+
// The LSP tool takes a file, a line and a 1-based character. The card knows the
|
|
1137
|
+
// line; the character is where the NAME sits on it, read from the bytes on disk.
|
|
1138
|
+
// `null` whenever that cannot be stated exactly — a guessed column sends the
|
|
1139
|
+
// language server to the wrong token, and its answer is then confidently wrong.
|
|
1140
|
+
function lspAt(model, sym, fresh) {
|
|
1141
|
+
if (fresh !== "current" || !sym || !sym.name) return null;
|
|
1142
|
+
const all = fileLines(model, sym.file);
|
|
1143
|
+
const line = sym.lines && sym.lines[0];
|
|
1144
|
+
if (!all || !line || line > all.length) return null;
|
|
1145
|
+
const text = all[line - 1];
|
|
1146
|
+
const esc = String(sym.name).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1147
|
+
const m = new RegExp(`(^|[^A-Za-z0-9_$])${esc}(?![A-Za-z0-9_$])`).exec(text);
|
|
1148
|
+
if (!m) return null;
|
|
1149
|
+
return { file: sym.file, line, character: m.index + m[1].length + 1 };
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1024
1152
|
function sourceNote(source) {
|
|
1025
1153
|
if (!source) return "";
|
|
1026
1154
|
if (!source.kept) return ` · source not shown (${source.want} lines, raise --budget)`;
|
|
@@ -1159,19 +1287,27 @@ function ctx(model, query, opts) {
|
|
|
1159
1287
|
const short = pickShort(items);
|
|
1160
1288
|
// The footer is printed ONLY when the card hid something, dropped an
|
|
1161
1289
|
// unresolved call, or cut the source block (§6).
|
|
1162
|
-
const footer = (used, hidden, source) => {
|
|
1163
|
-
const why = `${unresolved ? ` · ${unresolved} unresolved call(s) not shown` : ""}${hiddenText(hidden)}${sourceNote(source)}`;
|
|
1290
|
+
const footer = (used, hidden, source, cs) => {
|
|
1291
|
+
const why = `${unresolved ? ` · ${unresolved} unresolved call(s) not shown` : ""}${hiddenText(hidden)}${sourceNote(source)}${callersSourceNote(cs)}`;
|
|
1164
1292
|
return why ? ` budget ${used}/${budget} tokens${why}` : "";
|
|
1165
1293
|
};
|
|
1166
1294
|
const tail = opts.source === undefined ? null : sourceTail(model, sym.file, sym.lines[0], sym.lines[1], opts.source, fresh);
|
|
1167
|
-
const
|
|
1295
|
+
const blocks = opts.callersSource ? callerBlocks(model, sym, callers.confident) : null;
|
|
1296
|
+
const f = fit(items, budget, footer, tail, blocks);
|
|
1168
1297
|
const kept = items.filter((_, i) => f.kept.has(i)).map((it) => it.data).filter(Boolean);
|
|
1298
|
+
const callersSource = blocks
|
|
1299
|
+
? {
|
|
1300
|
+
callers_source: (f.callers ? f.callers.kept : []).map((b) => ({ file: b.file, from: b.from, to: b.to, caller: b.caller, text: b.lines.join("\n"), changed: b.changed })),
|
|
1301
|
+
counts: { callers_source_cut: f.callers ? f.callers.want - f.callers.shown : 0 },
|
|
1302
|
+
}
|
|
1303
|
+
: {};
|
|
1169
1304
|
return {
|
|
1170
1305
|
ok: true,
|
|
1171
1306
|
state: "found",
|
|
1172
1307
|
target: brief(sym),
|
|
1173
1308
|
blob: fresh,
|
|
1174
1309
|
exported: sym.exported,
|
|
1310
|
+
lsp_at: lspAt(model, sym, fresh),
|
|
1175
1311
|
states: short ? "short" : "long",
|
|
1176
1312
|
note: note || null,
|
|
1177
1313
|
callers: kept.filter((d) => d.item ==="caller"),
|
|
@@ -1182,6 +1318,7 @@ function ctx(model, query, opts) {
|
|
|
1182
1318
|
tests,
|
|
1183
1319
|
unresolved,
|
|
1184
1320
|
source: tail && f.source && f.source.kept ? { file: tail.file, from: tail.from, to: tail.from + f.source.kept - 1, text: sourceText(tail, f.source.kept), cut: f.source.cut } : null,
|
|
1321
|
+
...callersSource,
|
|
1185
1322
|
hidden: f.hidden,
|
|
1186
1323
|
budget: { used: f.used, max: budget },
|
|
1187
1324
|
card: f.card,
|
|
@@ -1266,8 +1403,11 @@ function fileCard(model, rel, budget, opts) {
|
|
|
1266
1403
|
if (fileTests.length) push(4, "tests", ` tests ${fileTests.join(useTree ? " " : ", ")}`, { item: "tests", files: fileTests });
|
|
1267
1404
|
const wiki = opts.wikiDocsFor ? opts.wikiDocsFor([rel]) : [];
|
|
1268
1405
|
if (wiki.length) push(5, "wiki", ` wiki ${wiki.join(useTree ? " " : ", ")}`, { item: "wiki", docs: wiki });
|
|
1406
|
+
// R1: `--callers-source` is a SYMBOL card's option. A file card names who
|
|
1407
|
+
// imports the file, not a call line, so there is no six-line window to print.
|
|
1408
|
+
const ignored = opts.callersSource ? " · callers source: symbol cards only" : "";
|
|
1269
1409
|
const footer = (used, hidden, source) => {
|
|
1270
|
-
const why = `${hiddenText(hidden)}${sourceNote(source)}`;
|
|
1410
|
+
const why = `${hiddenText(hidden)}${sourceNote(source)}${ignored}`;
|
|
1271
1411
|
return why ? ` budget ${used}/${budget} tokens${why}` : "";
|
|
1272
1412
|
};
|
|
1273
1413
|
// D1 on a FILE card is the file's head — the imports, the exports and the
|
|
@@ -1420,7 +1560,9 @@ function forSlice(model, files, opts) {
|
|
|
1420
1560
|
const each = Math.max(100, Math.floor(budget / known.length));
|
|
1421
1561
|
const blocks = known.map((rel) => sliceBlock(model, rel, each, opts));
|
|
1422
1562
|
const body = blocks.map((b) => b.card).join("\n\n");
|
|
1423
|
-
|
|
1563
|
+
// R1 / DE-7b: the executor reads its declared files whole, so the outside
|
|
1564
|
+
// view stays rows only — the flag is named as ignored, never silently dropped.
|
|
1565
|
+
const note = (missing.length ? `\n not in the graph: ${missing.join(", ")}` : "") + (opts.callersSource ? "\n callers source: symbol cards only" : "");
|
|
1424
1566
|
// The column header is named ONCE for the whole answer, not once per block —
|
|
1425
1567
|
// the tree format's own rule, applied one level up.
|
|
1426
1568
|
return {
|
|
@@ -1767,4 +1909,4 @@ function nameHits(model, name) {
|
|
|
1767
1909
|
return calls + defs;
|
|
1768
1910
|
}
|
|
1769
1911
|
|
|
1770
|
-
module.exports = { loadModel, resolveCall, resolveUrl, routeTable, urlIndex, findTarget, callersOf, nameHits, warmBlobs, ctx, forSlice, impact, graphMap, pathBetween, importsOf, TEST_FILE, SOURCE_DEFAULT, SOURCE_CAP };
|
|
1912
|
+
module.exports = { loadModel, resolveCall, resolveUrl, routeTable, urlIndex, findTarget, callersOf, nameHits, warmBlobs, ctx, forSlice, impact, graphMap, pathBetween, importsOf, TEST_FILE, SOURCE_DEFAULT, SOURCE_CAP, CALLERS_SOURCE_MAX, CALLERS_SOURCE_LINES };
|
package/bin/graph-shard.js
CHANGED
|
@@ -175,7 +175,12 @@ function forwardRow(i, res) {
|
|
|
175
175
|
const NAMES_PER_KEY = 5;
|
|
176
176
|
const norm = (p) => String(p).split("\\").join("/").replace(/^\.\//, "");
|
|
177
177
|
|
|
178
|
-
function fastModel(claudeDir, root, query) {
|
|
178
|
+
function fastModel(claudeDir, root, query, opts) {
|
|
179
|
+
// R1 (v1.9.1): `--callers-source` reads every caller FILE and asks git for
|
|
180
|
+
// each one's freshness. The fast model holds blobs, not bytes, so the full
|
|
181
|
+
// model answers — the same decline, for the same reason, as any other read
|
|
182
|
+
// the fast path cannot prove.
|
|
183
|
+
if (opts && opts.callersSource) return { model: null, reason: "callers-source" };
|
|
179
184
|
const p = G.graphPaths(claudeDir);
|
|
180
185
|
const meta = readJson(p.meta);
|
|
181
186
|
if (!meta || !meta.generation) return { model: null, reason: "no-meta" };
|