@azure-id/orc 2.1.1 → 2.2.0
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 +320 -0
- package/README.md +48 -23
- package/bin/build-agents.js +207 -205
- package/bin/cli.js +48188 -47417
- package/bin/fix.js +21 -18
- package/bin/graph-extract.js +314 -6
- package/bin/graph-gain.js +83 -2
- package/bin/graph-map.js +23 -1
- package/bin/graph-notes.js +1 -1
- package/bin/graph-query.js +1362 -37
- package/bin/graph-resolve.js +1 -1
- package/bin/graph-shard.js +29 -6
- package/bin/graph-signals.js +5 -3
- package/bin/graph.js +7 -3
- package/bin/habit.js +1 -1
- package/bin/pricing.json +8 -1
- package/bin/trace-write.js +40 -11
- package/bin/verify-contracts.js +5661 -5552
- package/bin/verify-package.js +716 -716
- package/bin/webui/api.js +8 -0
- package/bin/webui/fixtures/extra.js +2036 -2036
- package/bin/webui/fixtures/flow.js +1 -1
- package/bin/webui/fixtures/index.js +2 -2
- package/bin/webui/fixtures/knowledge.js +8 -1
- package/bin/webui/fixtures/pact.js +112 -111
- package/bin/webui/fixtures/stats.js +2 -2
- package/bin/webui/i18n/en/knowledge.json +2 -0
- package/bin/webui/i18n/id/knowledge.json +2 -0
- package/bin/webui/js/panels/knowledge.js +17 -0
- package/guides/code-graph-benefit.md +116 -0
- package/guides/configuration.md +150 -0
- package/guides/documents.md +262 -0
- package/guides/extra-models.md +448 -0
- package/guides/habits-and-gotchas.md +152 -0
- package/guides/knowledge-reads.md +190 -0
- package/guides/model-selection.md +186 -0
- package/guides/rules.md +305 -0
- package/guides/status-line.md +344 -0
- package/mock-run/orc-cli.md +3 -2
- package/mock-run/orc-doc.md +452 -448
- package/mock-run/orc.md +157 -157
- package/package.json +2 -1
- package/templates/agents/MODEL-MAPPING.md +176 -176
- package/templates/agents/{orc-executor-haiku-4-5.md → orc-executor-haiku-5-high.md} +21 -8
- package/templates/agents/orc-executor-opus-4-7-high.md +17 -5
- package/templates/agents/orc-executor-opus-4-7-med.md +17 -5
- package/templates/agents/orc-executor-opus-4-8-high.md +17 -5
- package/templates/agents/orc-executor-opus-5-high.md +17 -5
- package/templates/agents/orc-executor-opus-5-low.md +17 -5
- package/templates/agents/orc-executor-opus-5-med.md +17 -5
- package/templates/agents/orc-executor-sonnet-4-6-high.md +17 -5
- package/templates/agents/orc-executor-sonnet-5-high.md +17 -5
- package/templates/agents/orc-executor-sonnet-5-low.md +17 -5
- package/templates/agents/orc-executor-sonnet-5-med.md +17 -5
- package/templates/agents/{orc-graph-noter-sonnet-4-6-med.md → orc-graph-noter-haiku-5-high.md} +4 -4
- package/templates/agents/orc-planner-mini-opus-5-med.md +3 -1
- package/templates/agents/orc-planner-mini-sonnet-5-high.md +3 -1
- package/templates/agents/orc-planner-opus-5-med.md +3 -1
- package/templates/agents/{orc-trace-writer-haiku-4-5.md → orc-trace-writer-haiku-5-high.md} +4 -3
- package/templates/commands/orc-doc.md +128 -128
- package/templates/commands/orc-export.md +51 -46
- package/templates/hooks/README.md +8 -3
- package/templates/hooks/orc-effort-guard.js +105 -27
- package/templates/hooks/orc-graph-hook.js +252 -4
- package/templates/hooks/orc-statusline.js +13 -6
- package/templates/hooks/orc-trace.js +1 -1
- package/templates/skills/_shared/README.md +2 -2
- package/templates/skills/_shared/code-graph.md +185 -251
- package/templates/skills/_shared/extra-dispatch.md +1331 -1331
- package/templates/skills/_shared/lane-contract.md +7 -3
- package/templates/skills/_shared/opus5-only.md +137 -137
- package/templates/skills/_shared/phases/execution.md +13 -11
- package/templates/skills/_shared/phases/planning.md +27 -9
- package/templates/skills/_shared/phases/preflight.md +3 -3
- package/templates/skills/_shared/phases/review.md +4 -3
- package/templates/skills/_shared/phases/ship.md +7 -10
- package/templates/skills/_shared/phases/summary.md +2 -3
- package/templates/skills/_shared/phases/trace-verbs.md +21 -16
- package/templates/skills/_shared/phases/trace.md +3 -3
- package/templates/skills/_shared/phases/wiki-consult.md +2 -2
- package/templates/skills/_shared/pr-templates.md +108 -106
- package/templates/skills/_shared/read-ladder.md +8 -2
- package/templates/skills/_shared/return-validation.md +10 -6
- package/templates/skills/_shared/review-slice.md +1 -1
- package/templates/skills/_shared/stack-plan.md +135 -135
- package/templates/skills/_shared/wait.md +14 -2
- package/templates/skills/context-combiner/SKILL.md +2 -2
- package/templates/skills/orc/SKILL.md +227 -227
- package/templates/skills/orc/config.md +2 -2
- package/templates/skills/orc/examples/full-run-mock.md +74 -74
- package/templates/skills/orc/schemas/planning-output.md +3 -0
- package/templates/skills/orc-aftermath/SKILL.md +15 -15
- package/templates/skills/orc-analyze/SKILL.md +2 -2
- package/templates/skills/orc-analyze-mini/SKILL.md +2 -2
- package/templates/skills/orc-boundary/SKILL.md +15 -15
- package/templates/skills/orc-boundary/references/card.md +81 -78
- package/templates/skills/orc-boundary/references/gate.md +113 -113
- package/templates/skills/orc-brainstorm/SKILL.md +15 -15
- package/templates/skills/orc-budget/SKILL.md +15 -15
- package/templates/skills/orc-challenge/README.md +1 -1
- package/templates/skills/orc-challenge/SKILL.md +16 -16
- package/templates/skills/orc-challenge/examples/council-full-roster.md +1 -1
- package/templates/skills/orc-claude/SKILL.md +4 -4
- package/templates/skills/orc-claude/examples/claude-run-mock.md +1 -1
- package/templates/skills/orc-diy/SKILL.md +2 -2
- package/templates/skills/orc-diy/references/blocks/wiki.md +26 -26
- package/templates/skills/orc-diy/references/flow-schema.md +2 -2
- package/templates/skills/orc-doc/README.md +2 -2
- package/templates/skills/orc-doc/SKILL.md +501 -490
- package/templates/skills/orc-doc/examples/orc-doc-prd-run.md +12 -1
- package/templates/skills/orc-doc/references/chunking.md +6 -4
- package/templates/skills/orc-doc/references/gates.md +311 -311
- package/templates/skills/orc-doc/references/resume-protocol.md +229 -228
- package/templates/skills/orc-explain/SKILL.md +88 -88
- package/templates/skills/orc-export/SKILL.md +190 -187
- package/templates/skills/orc-fast/SKILL.md +213 -210
- package/templates/skills/orc-grill/SKILL.md +245 -245
- package/templates/skills/orc-handoff/SKILL.md +11 -11
- package/templates/skills/orc-learn/SKILL.md +13 -13
- package/templates/skills/orc-learn/examples/learn-run-mock.md +2 -2
- package/templates/skills/orc-mini/SKILL.md +12 -12
- package/templates/skills/orc-mini/examples/mini-run-mock.md +4 -5
- package/templates/skills/orc-pact/SKILL.md +15 -15
- package/templates/skills/orc-pact/references/gate.md +70 -70
- package/templates/skills/orc-pattern/SKILL.md +3 -3
- package/templates/skills/orc-poly/SKILL.md +4 -4
- package/templates/skills/orc-poly/examples/poly-run-mock.md +51 -51
- package/templates/skills/orc-pr-driver/SKILL.md +4 -3
- package/templates/skills/orc-pr-driver/references/orc-run-split.md +108 -99
- package/templates/skills/orc-pr-setup/SKILL.md +2 -2
- package/templates/skills/orc-quick/README.md +535 -536
- package/templates/skills/orc-quick/SKILL.md +29 -32
- package/templates/skills/orc-quick/references/context-doc.md +145 -145
- package/templates/skills/orc-quick/references/dispatch-gate.md +220 -220
- package/templates/skills/orc-quick/references/gh-mode.md +2 -1
- package/templates/skills/orc-quick/references/look.md +34 -12
- package/templates/skills/orc-retro/SKILL.md +2 -2
- package/templates/skills/orc-retro/examples/retro-mock.md +2 -2
- package/templates/skills/orc-route/SKILL.md +2 -2
- package/templates/skills/orc-test/SKILL.md +6 -3
- package/templates/skills/orc-verify/SKILL.md +2 -2
- package/templates/skills/orc-wait/SKILL.md +10 -2
- package/templates/skills/orc-wiki/SKILL.md +4 -4
- package/templates/skills/orc-wiki/references/pattern-prewarm.md +19 -19
- package/templates/agents/orc-executor-sonnet-4-6-med.md +0 -155
package/bin/fix.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
// `/orc-fix` is a RIDER lane, like `/orc-wait` and `/orc-explain`: it opens no
|
|
9
9
|
// run of its own. So this module NEVER writes `.current` — it only READS it to
|
|
10
10
|
// find the host run, and appends ONE `FIX` line to that run's trace (DE-14).
|
|
11
|
+
// A host is an OPEN run (6 h) that a lane registered; the hook's bootstrap
|
|
12
|
+
// pointer is not one (v2.1.2, F14).
|
|
11
13
|
//
|
|
12
14
|
// The class has two axes (04 §5.2): `source` (what found it) and
|
|
13
15
|
// `introduced_by` (who wrote the bad lines). Both are kept: a Sonar issue can
|
|
@@ -57,17 +59,17 @@ function git(cwd, args) {
|
|
|
57
59
|
|
|
58
60
|
// The run in flight, READ from `.current` (never written here), and whether a
|
|
59
61
|
// dispatch is still open — the same sidecar `orc run inflight` reads.
|
|
62
|
+
// v2.1.2 (F14): the pointer goes through `deps.openRunPointer`, the hooks' one
|
|
63
|
+
// idea of an open run (6 hours). A HOST is an open run a LANE registered. The
|
|
64
|
+
// trace hook's bootstrap name (written when /orc-fix's own may-fix executor is
|
|
65
|
+
// dispatched) names no lane, so no lane is there to make the fix. Its open
|
|
66
|
+
// dispatches still block a record.
|
|
60
67
|
function hostRun(deps, claudeDir) {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (!cur) return { host: null, inflight: "clear", pending: 0 };
|
|
67
|
-
const tracePath = path.join(dir, cur);
|
|
68
|
-
if (!fs.existsSync(tracePath)) return { host: null, inflight: "clear", pending: 0 };
|
|
69
|
-
const m = deps.TRACE_NAME.exec(cur);
|
|
70
|
-
const host = { trace: cur, run: cur.replace(/\.txt$/, ""), lane: m ? m[1] : null, slug: m ? m[2] : null, path: tracePath };
|
|
68
|
+
const ptr = deps.openRunPointer(claudeDir);
|
|
69
|
+
if (!ptr || !ptr.exists) return { host: null, open: null, inflight: "clear", pending: 0 };
|
|
70
|
+
const tracePath = ptr.path;
|
|
71
|
+
const ref = { trace: ptr.name, run: ptr.run, lane: ptr.lane, slug: ptr.slug, path: tracePath };
|
|
72
|
+
const host = ptr.lane ? ref : null;
|
|
71
73
|
let pend = null;
|
|
72
74
|
try {
|
|
73
75
|
const raw = JSON.parse(fs.readFileSync(tracePath + ".pending.json", "utf8"));
|
|
@@ -75,16 +77,16 @@ function hostRun(deps, claudeDir) {
|
|
|
75
77
|
} catch (_) {}
|
|
76
78
|
const now = Date.now();
|
|
77
79
|
if (pend) {
|
|
78
|
-
const
|
|
79
|
-
return { host, inflight:
|
|
80
|
+
const n = pend.filter((r) => !(typeof r.ts === "number" && now - r.ts > deps.inflightStaleMs)).length;
|
|
81
|
+
return { host, open: ref, inflight: n ? "in-flight" : "clear", pending: n };
|
|
80
82
|
}
|
|
81
83
|
// No sidecar: trust the trace's own SPAWN/RETURN balance.
|
|
82
84
|
let balance = 0;
|
|
83
85
|
try {
|
|
84
86
|
const text = fs.readFileSync(tracePath, "utf8");
|
|
85
|
-
balance = (text.match(
|
|
87
|
+
balance = (text.match(/] hooks+SPAWN /g) || []).length - (text.match(/] hooks+RETURN /g) || []).length + (text.match(/] hooks+RETURN ~agent :: unattributed/g) || []).length;
|
|
86
88
|
} catch (_) {}
|
|
87
|
-
return { host, inflight: balance > 0 ? "in-flight" : "clear", pending: Math.max(0, balance) };
|
|
89
|
+
return { host, open: ref, inflight: balance > 0 ? "in-flight" : "clear", pending: Math.max(0, balance) };
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
// `git blame` on the named lines → the commit that wrote most of them.
|
|
@@ -278,6 +280,7 @@ function classify(deps, claudeDir, text) {
|
|
|
278
280
|
commit,
|
|
279
281
|
evidence,
|
|
280
282
|
host: run.host ? { trace: run.host.trace, run: run.host.run, lane: run.host.lane, slug: run.host.slug } : null,
|
|
283
|
+
open_run: run.open ? run.open.run : null,
|
|
281
284
|
inflight: run.inflight,
|
|
282
285
|
pending: run.pending,
|
|
283
286
|
// DE-12 (c): a run is open → record only, the host lane makes the fix.
|
|
@@ -385,7 +388,7 @@ function fixCmd(deps) {
|
|
|
385
388
|
if (!text) return fail(2, "usage", "fix classify: --text \"<what the user said>\" is required\n" + USAGE);
|
|
386
389
|
const c = classify(deps, claudeDir, text);
|
|
387
390
|
const code = c.inflight === "in-flight" ? 1 : 0;
|
|
388
|
-
if (code) c.message = `a dispatch is in flight in ${c.
|
|
391
|
+
if (code) c.message = `a dispatch is in flight in ${c.open_run} (${c.pending} open) — /orc-fix waits for its return`;
|
|
389
392
|
return out(c, code, () => {
|
|
390
393
|
if (code) console.log(`⏸ ${c.message}`);
|
|
391
394
|
console.log(`proposed: source ${c.source} · introduced by ${c.introduced_by}${c.run ? ` (${c.run})` : ""}`);
|
|
@@ -399,7 +402,7 @@ function fixCmd(deps) {
|
|
|
399
402
|
if (inp.err) return fail(2, "malformed", `fix record: the input ${inp.err}`, { field: "body" });
|
|
400
403
|
const run = hostRun(deps, claudeDir);
|
|
401
404
|
if (run.inflight === "in-flight")
|
|
402
|
-
return fail(1, "in-flight", `fix record: a dispatch is in flight in ${run.
|
|
405
|
+
return fail(1, "in-flight", `fix record: a dispatch is in flight in ${run.open.run} (${run.pending} open) — nothing was written. Record after its return.`, { host: run.host ? run.host.run : null, open_run: run.open.run, pending: run.pending });
|
|
403
406
|
const b = Object.assign({}, inp.value);
|
|
404
407
|
if (!b.fix_run && run.host) b.fix_run = run.host.run;
|
|
405
408
|
const t = toObservation(b, now);
|
|
@@ -425,7 +428,7 @@ function fixCmd(deps) {
|
|
|
425
428
|
// takes the record down with it.
|
|
426
429
|
let traceLine = null;
|
|
427
430
|
if (run.host) {
|
|
428
|
-
const head = `FIX source=${
|
|
431
|
+
const head = `FIX source=${rec.source} introduced_by=${rec.introduced_by} by=${rec.class_by || "evidence"} obs=${rec.obs.slice(0, 8)}${rec.miss ? ` missed_by=${rec.missed_by}` : ""}`;
|
|
429
432
|
const line = `[${stamp(new Date(now))}] ${"cli".padEnd(8)} ${head} :: ${where}${what ? " " + oneLine(what).slice(0, 80) : ""}`;
|
|
430
433
|
try {
|
|
431
434
|
fs.appendFileSync(run.host.path, line + "\n");
|
|
@@ -433,7 +436,7 @@ function fixCmd(deps) {
|
|
|
433
436
|
} catch (_) {}
|
|
434
437
|
}
|
|
435
438
|
const who = rec.introduced_by === "orc" ? `ORC${rec.run ? ` (${rec.run})` : ""}` : rec.introduced_by === "ai" ? "AI-written code" : rec.introduced_by;
|
|
436
|
-
const lineOut = `recorded ${id} · ${
|
|
439
|
+
const lineOut = `recorded ${id} · ${rec.source}${what ? " " + oneLine(what).slice(0, 60) : ""} · introduced by ${who}${rec.miss ? ` · missed by review ${rec.missed_by}` : ""} · the next review sees it`;
|
|
437
440
|
return out(
|
|
438
441
|
{ ok: true, id, obs: rec.obs, observation: rec, miss: !!rec.miss, missed_by: rec.missed_by || null, host: run.host ? run.host.run : null, trace_line: traceLine, promoted: changes.promoted, bumped: changes.bumped, line: lineOut },
|
|
439
442
|
0,
|
package/bin/graph-extract.js
CHANGED
|
@@ -56,7 +56,7 @@ const { spawnSync } = require("child_process");
|
|
|
56
56
|
// @3 (W9): route handlers are symbols, and a function passed BY NAME is a `ref`.
|
|
57
57
|
// @4 (EW1): every record says how much of the file the extractor actually saw.
|
|
58
58
|
// @5 (v1.8.2 W1): urls, mounts, decorator routes, `handler` on a route alias.
|
|
59
|
-
const HEURISTIC = "heuristic@
|
|
59
|
+
const HEURISTIC = "heuristic@7";
|
|
60
60
|
const MAX_CALLS_PER_SYMBOL = 200;
|
|
61
61
|
const MAX_EFFECTS_PER_SYMBOL = 12;
|
|
62
62
|
const MAX_URLS_PER_SYMBOL = 200;
|
|
@@ -1258,6 +1258,8 @@ function objectDefs(c, have, sfc) {
|
|
|
1258
1258
|
const top = close > open ? objectMembers(m, open, close).map(([a, b]) => ({ a, b, mem: memberOf(m, a, b) })).filter((x) => x.mem) : [];
|
|
1259
1259
|
const shaped = top.some(
|
|
1260
1260
|
(x) => x.mem.type === "fn" || (OBJ_SECTIONS.has(x.mem.name) && (x.mem.type === "obj" || x.mem.type === "short")) || x.mem.name === "mixins" || x.mem.name === "extends"
|
|
1261
|
+
// v2.2.0 T3: inside an SFC, a props-only component is still a component.
|
|
1262
|
+
|| (sfc && /^(props|emits|inject|components)$/.test(x.mem.name))
|
|
1261
1263
|
);
|
|
1262
1264
|
if (shaped) {
|
|
1263
1265
|
const nameMem = top.find((x) => x.mem.type === "val" && x.mem.name === "name");
|
|
@@ -1273,6 +1275,27 @@ function objectDefs(c, have, sfc) {
|
|
|
1273
1275
|
}
|
|
1274
1276
|
}
|
|
1275
1277
|
owner = add({ name: oname, qname: oname, kind: "class", from: dx.index, to: close, exported: true, ...(bases.length ? { bases: bases.slice(0, 8) } : {}) });
|
|
1278
|
+
// v2.2.0 T3: the component CONTRACT — `props`, `emits`, `inject`. Each
|
|
1279
|
+
// member is a name-only symbol; like a `const`, it owns no call.
|
|
1280
|
+
for (const x of top) {
|
|
1281
|
+
const kind = CONTRACT_OF[x.mem.name];
|
|
1282
|
+
if (!kind || x.mem.close == null || x.mem.close <= x.mem.open) continue;
|
|
1283
|
+
if (x.mem.type === "arr") {
|
|
1284
|
+
const STR = /(['"`])([A-Za-z_$][\w$-]*)\1/g;
|
|
1285
|
+
const body = src.slice(x.mem.open, x.mem.close);
|
|
1286
|
+
let q;
|
|
1287
|
+
while ((q = STR.exec(body))) {
|
|
1288
|
+
const at = x.mem.open + q.index;
|
|
1289
|
+
if (m[at] !== src[at]) continue;
|
|
1290
|
+
add({ name: q[2], qname: `${oname}.${q[2]}`, kind, from: at, to: at + q[0].length - 1, exported: false });
|
|
1291
|
+
}
|
|
1292
|
+
} else if (x.mem.type === "obj") {
|
|
1293
|
+
for (const [a, b] of objectMembers(m, x.mem.open, x.mem.close)) {
|
|
1294
|
+
const mem = memberOf(m, a, b);
|
|
1295
|
+
if (mem && mem.name) add({ name: mem.name, qname: `${oname}.${mem.name}`, kind, from: a, to: trim(b), exported: false });
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1276
1299
|
const method = (name, from, to, paren) => {
|
|
1277
1300
|
if (paren != null) defParens.add(paren);
|
|
1278
1301
|
add({ name, qname: `${oname}.${name}`, kind: "method", from, to, exported: false });
|
|
@@ -1340,7 +1363,271 @@ function objectDefs(c, have, sfc) {
|
|
|
1340
1363
|
// visible to every function below it, exactly as it was before W5b.
|
|
1341
1364
|
if (!taken.has(name)) add({ name, qname: name, kind: "class", from, to, exported: true, nameOnly: true });
|
|
1342
1365
|
}
|
|
1343
|
-
|
|
1366
|
+
const ownerName = owner ? owner.name : sfc ? ownerStem(rel) : null;
|
|
1367
|
+
return { defs, defParens, ownerName };
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// ── v2.2.0 T3: what a Vue / SPA codebase says outside a function body ───────
|
|
1371
|
+
// Runs on BOTH rungs, right after the W5b object pass: the template (its
|
|
1372
|
+
// handlers, bindings and child components), lazy `import()`, SPA route
|
|
1373
|
+
// records, router navigation by route NAME, and the string values of an
|
|
1374
|
+
// exported constant object. It edits `defs` and `calls` in place and returns
|
|
1375
|
+
// the import list with the lazy imports appended.
|
|
1376
|
+
const CONTRACT_OF = { props: "prop", emits: "emit", inject: "inject" };
|
|
1377
|
+
const CONTRACT_KINDS = new Set(["prop", "emit", "inject"]);
|
|
1378
|
+
const TPL_SKIP_TAGS = new Set(["router-link", "router-view", "transition", "transition-group", "keep-alive", "component", "slot", "template", "RouterLink", "RouterView", "Transition", "TransitionGroup", "KeepAlive", "Component", "Slot", "Template"]);
|
|
1379
|
+
const TPL_WORDS = new Set(["true", "false", "null", "undefined", "in", "of", "typeof", "instanceof", "new", "void", "this", "NaN", "Infinity"]);
|
|
1380
|
+
const STR_LIT = /^\s*(['"`])([^'"`\n]*)\1/;
|
|
1381
|
+
|
|
1382
|
+
// Blank the inside of every string literal of a template expression, so an
|
|
1383
|
+
// identifier scan never reads a word inside quotes. Length is kept.
|
|
1384
|
+
function blankStrings(e) {
|
|
1385
|
+
return e.replace(/(['"`])((?:\\.|(?!\1)[^\\])*)\1/g, (s, q, b) => q + " ".repeat(b.length) + q);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
function vuePass(c, raw, sfc, defs, calls, imports, ownerName) {
|
|
1389
|
+
const { m, starts } = c;
|
|
1390
|
+
const src = c.src;
|
|
1391
|
+
const lineAt = (p) => lineOf(starts, p);
|
|
1392
|
+
const imps = (imports || []).slice();
|
|
1393
|
+
const code = (p) => m[p] === src[p];
|
|
1394
|
+
|
|
1395
|
+
// ── shape 5: lazy imports ──
|
|
1396
|
+
const lazyAt = new Set();
|
|
1397
|
+
const lazyNames = new Map();
|
|
1398
|
+
const LAZY = /\b(?:const|let|var)[ \t]+([A-Za-z_$][\w$]*)[ \t]*(?::[^=\n]+)?=[ \t]*(?:async[ \t]*)?(?:\([ \t]*\)|_)[ \t]*=>[ \t\r\n]*\(?[ \t\r\n]*import[ \t]*\([ \t]*(['"])([^'"\n]+)\2[ \t]*\)/g;
|
|
1399
|
+
let x;
|
|
1400
|
+
while ((x = LAZY.exec(src))) {
|
|
1401
|
+
if (!code(x.index)) continue;
|
|
1402
|
+
const ip = x.index + x[0].lastIndexOf("import");
|
|
1403
|
+
lazyAt.add(ip);
|
|
1404
|
+
const line = lineAt(x.index);
|
|
1405
|
+
lazyNames.set(x[1], line);
|
|
1406
|
+
imps.push({ from: x[3], line, bindings: [{ local: x[1], orig: "default", kind: "default" }] });
|
|
1407
|
+
}
|
|
1408
|
+
const DYN = /\bimport[ \t]*\([ \t]*(['"])([^'"\n]+)\1[ \t]*\)/g;
|
|
1409
|
+
while ((x = DYN.exec(src))) {
|
|
1410
|
+
if (!code(x.index) || lazyAt.has(x.index)) continue;
|
|
1411
|
+
imps.push({ from: x[2], line: lineAt(x.index), bindings: [] });
|
|
1412
|
+
}
|
|
1413
|
+
for (let i = defs.length - 1; i >= 0; i--) {
|
|
1414
|
+
const d = defs[i];
|
|
1415
|
+
if (lazyNames.get(d.name) === d.s && d.kind !== "route" && d.kind !== "class") defs.splice(i, 1);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// `const N = 'literal'` in THIS file, for a route name given by constant.
|
|
1419
|
+
const constStr = (name) => {
|
|
1420
|
+
const re = new RegExp(`\\b(?:const|let|var)[ \\t]+${name.replace(/\$/g, "\\$")}[ \\t]*(?::[^=\\n]+)?=[ \\t]*(['"\`])([^'"\`\\n]*)\\1`);
|
|
1421
|
+
const q = re.exec(src);
|
|
1422
|
+
return q && code(q.index) ? q[2] : null;
|
|
1423
|
+
};
|
|
1424
|
+
// A route NAME expression → the call record shape 7 writes.
|
|
1425
|
+
const navCall = (expr, line) => {
|
|
1426
|
+
const e = String(expr).trim();
|
|
1427
|
+
const lit = /^(['"`])([^'"`\n]*)\1$/.exec(e);
|
|
1428
|
+
if (lit) return lit[2] ? { name: lit[2], line, route: true } : null;
|
|
1429
|
+
if (/^[A-Za-z_$][\w$]*$/.test(e)) {
|
|
1430
|
+
const v = constStr(e);
|
|
1431
|
+
return v ? { name: v, line, route: true } : null;
|
|
1432
|
+
}
|
|
1433
|
+
if (/^[A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*$/.test(e)) return { name: e, line, route: true, via: true };
|
|
1434
|
+
return null;
|
|
1435
|
+
};
|
|
1436
|
+
const NAME_IN = /(?:^|[{,\s])name[ \t]*:[ \t]*((['"`])[^'"`\n]*\2|[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)?)/;
|
|
1437
|
+
|
|
1438
|
+
// ── shape 7: `router.push({ name: … })` / `this.$router.replace(…)` ──
|
|
1439
|
+
const NAV = /(?:^|[^\w$])((?:[A-Za-z_$][\w$]*[ \t]*\.[ \t]*)*\$?router)[ \t]*\.[ \t]*(?:push|replace)[ \t]*\([ \t\r\n]*\{/g;
|
|
1440
|
+
while ((x = NAV.exec(src))) {
|
|
1441
|
+
const open = x.index + x[0].length - 1;
|
|
1442
|
+
if (!code(open)) continue;
|
|
1443
|
+
const close = matchPairQuiet(m, open, "{", "}");
|
|
1444
|
+
if (close < 0) continue;
|
|
1445
|
+
const n = NAME_IN.exec(src.slice(open + 1, close));
|
|
1446
|
+
if (!n) continue;
|
|
1447
|
+
const rec = navCall(n[1], lineAt(open));
|
|
1448
|
+
if (rec) calls.push(rec);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// ── shape 6: SPA route records (js/ts files) ──
|
|
1452
|
+
if (!sfc) {
|
|
1453
|
+
const seenObj = new Set();
|
|
1454
|
+
const PATH = /\bpath[ \t]*:[ \t]*['"`]/g;
|
|
1455
|
+
while ((x = PATH.exec(m))) {
|
|
1456
|
+
// the enclosing `{`
|
|
1457
|
+
let d = 0;
|
|
1458
|
+
let open = -1;
|
|
1459
|
+
for (let k = x.index - 1; k >= 0; k--) {
|
|
1460
|
+
const ch = m[k];
|
|
1461
|
+
if (ch === "}" || ch === "]" || ch === ")") d++;
|
|
1462
|
+
else if (ch === "{" || ch === "[" || ch === "(") {
|
|
1463
|
+
if (d === 0) {
|
|
1464
|
+
open = ch === "{" ? k : -1;
|
|
1465
|
+
break;
|
|
1466
|
+
}
|
|
1467
|
+
d--;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
if (open < 0 || seenObj.has(open)) continue;
|
|
1471
|
+
seenObj.add(open);
|
|
1472
|
+
const close = matchPairQuiet(m, open, "{", "}");
|
|
1473
|
+
if (close < 0) continue;
|
|
1474
|
+
const mems = objectMembers(m, open, close)
|
|
1475
|
+
.map(([a, b]) => ({ a, b, mem: memberOf(m, a, b) }))
|
|
1476
|
+
.filter((y) => y.mem);
|
|
1477
|
+
const get = (k) => mems.find((y) => y.mem.name === k);
|
|
1478
|
+
const pm = get("path");
|
|
1479
|
+
if (!pm || pm.mem.type !== "val") continue;
|
|
1480
|
+
const pl = STR_LIT.exec(src.slice(pm.mem.v, pm.mem.v + 300));
|
|
1481
|
+
if (!pl) continue;
|
|
1482
|
+
const comp = get("component");
|
|
1483
|
+
const compId = comp && comp.mem.type === "val" ? m.slice(comp.mem.v, comp.b).trim() : "";
|
|
1484
|
+
const hasComp = IDENT_RE.test(compId);
|
|
1485
|
+
const nm = get("name");
|
|
1486
|
+
const nl = nm && nm.mem.type === "val" ? STR_LIT.exec(src.slice(nm.mem.v, nm.mem.v + 200)) : null;
|
|
1487
|
+
const ok = hasComp || (get("components") && get("components").mem.type === "obj") || (nl && (get("meta") || get("children") || get("redirect")));
|
|
1488
|
+
if (!ok) continue;
|
|
1489
|
+
let to = close;
|
|
1490
|
+
const def = { kind: "route", method: "VIEW", path: pl[2], name: "route", from: open, to, exported: false, s: lineAt(open), e: lineAt(to) };
|
|
1491
|
+
if (nl) def.route_name = nl[2];
|
|
1492
|
+
defs.push(def);
|
|
1493
|
+
if (hasComp) calls.push({ name: compId, line: lineAt(comp.mem.v), ref: true });
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
// ── shape 8: the string values of an exported constant object ──
|
|
1498
|
+
for (const d of defs) {
|
|
1499
|
+
if (d.kind !== "const" || !d.exported || d.values) continue;
|
|
1500
|
+
const re = new RegExp(`^[ \\t]*(?:export[ \\t]+)?(?:const|let|var)[ \\t]+${d.name.replace(/\$/g, "\\$")}[ \\t]*(?::[^=\\n]+)?=[ \\t]*\\{`, "m");
|
|
1501
|
+
const q = re.exec(m);
|
|
1502
|
+
if (!q) continue;
|
|
1503
|
+
const open = q.index + q[0].length - 1;
|
|
1504
|
+
const close = matchPairQuiet(m, open, "{", "}");
|
|
1505
|
+
if (close < 0) continue;
|
|
1506
|
+
const values = {};
|
|
1507
|
+
let n = 0;
|
|
1508
|
+
for (const [a, b] of objectMembers(m, open, close)) {
|
|
1509
|
+
if (n >= 64) break;
|
|
1510
|
+
const mem = memberOf(m, a, b);
|
|
1511
|
+
if (!mem || mem.type !== "val") continue;
|
|
1512
|
+
const s = /^(['"`])([^'"`\n]*)\1\s*$/.exec(src.slice(mem.v, b).trim());
|
|
1513
|
+
if (!s) continue;
|
|
1514
|
+
values[mem.name] = s[2].slice(0, 80);
|
|
1515
|
+
n++;
|
|
1516
|
+
}
|
|
1517
|
+
if (n) d.values = values;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// ── shapes 1 + 2: the template of a `.vue` file ──
|
|
1521
|
+
if (sfc && !sfc.svelte && raw) {
|
|
1522
|
+
// the outer <template>, outside every <script>
|
|
1523
|
+
const inScript = (p) => sfc.blocks.some(([a, b]) => p >= a && p < b);
|
|
1524
|
+
const TAG = /<(\/?)template\b[^>]*?(\/?)>/gi;
|
|
1525
|
+
let tOpen = -1;
|
|
1526
|
+
let tEnd = -1;
|
|
1527
|
+
let depth = 0;
|
|
1528
|
+
while ((x = TAG.exec(raw))) {
|
|
1529
|
+
if (inScript(x.index) || x[2] === "/") continue;
|
|
1530
|
+
if (!x[1]) {
|
|
1531
|
+
if (depth === 0) tOpen = x.index;
|
|
1532
|
+
depth++;
|
|
1533
|
+
} else if (depth > 0 && --depth === 0) {
|
|
1534
|
+
tEnd = x.index + x[0].length - 1;
|
|
1535
|
+
break;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
if (tOpen >= 0 && tEnd > tOpen && ownerName) {
|
|
1539
|
+
const tdef = { name: "template", qname: `${ownerName}.template`, kind: "template", from: tOpen, to: tEnd, exported: false, s: lineAt(tOpen), e: lineAt(tEnd), _raw: raw.slice(starts[lineAt(tOpen) - 1] || 0, tEnd + 1) };
|
|
1540
|
+
defs.push(tdef);
|
|
1541
|
+
const body = raw.slice(tOpen, tEnd + 1);
|
|
1542
|
+
const base = tOpen;
|
|
1543
|
+
const seen = new Set();
|
|
1544
|
+
const push = (rec) => {
|
|
1545
|
+
const k = `${rec.name}|${rec.line}|${rec.self ? 1 : 0}|${rec.ref ? 1 : 0}|${rec.route ? 1 : 0}`;
|
|
1546
|
+
if (seen.has(k)) return;
|
|
1547
|
+
seen.add(k);
|
|
1548
|
+
calls.push(rec);
|
|
1549
|
+
};
|
|
1550
|
+
const bad = (w) => !w || w[0] === "$" || KW.has(w) || TPL_WORDS.has(w) || REF_WORDS.has(w) || /^\d/.test(w);
|
|
1551
|
+
// v-for aliases are template locals, never a member of the component.
|
|
1552
|
+
const locals = new Set();
|
|
1553
|
+
const VFOR = /\sv-for[ \t]*=[ \t]*(["'])([^"']*?)\s+(?:in|of)\s/g;
|
|
1554
|
+
while ((x = VFOR.exec(body))) for (const w of x[2].replace(/[()]/g, " ").split(/[\s,]+/)) if (w) locals.add(w);
|
|
1555
|
+
const refsOf = (expr, at) => {
|
|
1556
|
+
const e = blankStrings(expr);
|
|
1557
|
+
const ID = /[A-Za-z_$][\w$]*/g;
|
|
1558
|
+
let q;
|
|
1559
|
+
while ((q = ID.exec(e))) {
|
|
1560
|
+
const w = q[0];
|
|
1561
|
+
let p = q.index - 1;
|
|
1562
|
+
while (p >= 0 && /\s/.test(e[p])) p--;
|
|
1563
|
+
if (p >= 0 && (e[p] === "." || /[\w$]/.test(e[p]))) continue;
|
|
1564
|
+
let n = q.index + w.length;
|
|
1565
|
+
while (n < e.length && /\s/.test(e[n])) n++;
|
|
1566
|
+
// an object key: `{ name: … }`
|
|
1567
|
+
if (e[n] === ":" && (p < 0 || e[p] === "{" || e[p] === ",")) continue;
|
|
1568
|
+
if (bad(w) || locals.has(w)) continue;
|
|
1569
|
+
push({ name: w, line: lineAt(at + q.index), self: true, ref: true });
|
|
1570
|
+
}
|
|
1571
|
+
};
|
|
1572
|
+
const ATTR = /\s(@[\w.:\-[\]]+|v-on:[\w.:\-[\]]+|:[\w.\-[\]]+|v-bind:[\w.\-[\]]+|v-(?:if|else-if|show|model|html|text|for)(?::[\w-]+)?(?:\.[\w-]+)*)[ \t]*=[ \t]*(?:"([^"]*)"|'([^']*)')/g;
|
|
1573
|
+
while ((x = ATTR.exec(body))) {
|
|
1574
|
+
const attr = x[1];
|
|
1575
|
+
const val = x[2] != null ? x[2] : x[3];
|
|
1576
|
+
const at = base + x.index + x[0].length - 1 - val.length;
|
|
1577
|
+
if (attr[0] === "@" || attr.startsWith("v-on:")) {
|
|
1578
|
+
const t = val.trim();
|
|
1579
|
+
if (/^[A-Za-z_$][\w$]*$/.test(t)) {
|
|
1580
|
+
if (!bad(t)) push({ name: t, line: lineAt(at + val.indexOf(t)), self: true });
|
|
1581
|
+
continue;
|
|
1582
|
+
}
|
|
1583
|
+
const e = blankStrings(val);
|
|
1584
|
+
const CALLRE = /[A-Za-z_$][\w$]*(?=[ \t]*\()/g;
|
|
1585
|
+
let q;
|
|
1586
|
+
while ((q = CALLRE.exec(e))) {
|
|
1587
|
+
let p = q.index - 1;
|
|
1588
|
+
while (p >= 0 && /\s/.test(e[p])) p--;
|
|
1589
|
+
if (p >= 0 && (e[p] === "." || /[\w$]/.test(e[p]))) continue;
|
|
1590
|
+
if (bad(q[0])) continue;
|
|
1591
|
+
push({ name: q[0], line: lineAt(at + q.index), self: true });
|
|
1592
|
+
}
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1595
|
+
if (attr.startsWith("v-for")) {
|
|
1596
|
+
const f = /^([\s\S]*?\s)(?:in|of)\s([\s\S]*)$/.exec(val);
|
|
1597
|
+
if (f) refsOf(f[2], at + f[1].length + 3);
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
refsOf(val, at);
|
|
1601
|
+
}
|
|
1602
|
+
const MUS = /\{\{([\s\S]*?)\}\}/g;
|
|
1603
|
+
while ((x = MUS.exec(body))) refsOf(x[1], base + x.index + 2);
|
|
1604
|
+
// child components, and <router-link :to="{ name: … }">
|
|
1605
|
+
const local = new Set();
|
|
1606
|
+
for (const imp of imps) for (const b of imp.bindings || []) local.add(b.local);
|
|
1607
|
+
const OPEN = /<([A-Za-z][\w-]*)\b([^>]*)>/g;
|
|
1608
|
+
while ((x = OPEN.exec(body))) {
|
|
1609
|
+
const tag = x[1];
|
|
1610
|
+
const line = lineAt(base + x.index);
|
|
1611
|
+
if (tag === "router-link" || tag === "RouterLink") {
|
|
1612
|
+
const to = /\s(?::to|v-bind:to)[ \t]*=[ \t]*(?:"([^"]*)"|'([^']*)')/.exec(x[2]);
|
|
1613
|
+
const v = to ? (to[1] != null ? to[1] : to[2]) : null;
|
|
1614
|
+
const n = v ? NAME_IN.exec(v) : null;
|
|
1615
|
+
const rec = n ? navCall(n[1], line) : null;
|
|
1616
|
+
if (rec) push(rec);
|
|
1617
|
+
continue;
|
|
1618
|
+
}
|
|
1619
|
+
if (TPL_SKIP_TAGS.has(tag)) continue;
|
|
1620
|
+
if (!/^[A-Z]/.test(tag) && !tag.includes("-")) continue;
|
|
1621
|
+
const pascal = tag
|
|
1622
|
+
.split("-")
|
|
1623
|
+
.filter(Boolean)
|
|
1624
|
+
.map((w) => w[0].toUpperCase() + w.slice(1))
|
|
1625
|
+
.join("");
|
|
1626
|
+
if (local.has(pascal)) push({ name: pascal, line, ref: true });
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
return imps;
|
|
1344
1631
|
}
|
|
1345
1632
|
|
|
1346
1633
|
// ── declarations: Python (indentation) ──────────────────────────────────────
|
|
@@ -1633,6 +1920,10 @@ function normCall(name) {
|
|
|
1633
1920
|
sup = true;
|
|
1634
1921
|
}
|
|
1635
1922
|
}
|
|
1923
|
+
// v2.2.0 T3: `this.$t(…)`, `this.$router.push(…)`, `this.$emit(…)` — a
|
|
1924
|
+
// framework built-in hung on the instance. Stripping the `$` made `this.$t`
|
|
1925
|
+
// a call to `t`, which then resolved into whatever file defined a `t`.
|
|
1926
|
+
if (self && /^\$[A-Za-z_]/.test(n)) return null;
|
|
1636
1927
|
n = n.replace(/^\$/, "").replace(/\.\$/g, ".");
|
|
1637
1928
|
const last = n.split(".").pop();
|
|
1638
1929
|
// A keyword after a dot is a METHOD name — `router.delete(`, `promise.catch(` —
|
|
@@ -2301,7 +2592,7 @@ function finalize(c, lang, defs, calls, imports, exportsSet, extractor) {
|
|
|
2301
2592
|
// stays with whatever holds the constant — the module, most often — so a
|
|
2302
2593
|
// constant symbol always carries `calls: []`.
|
|
2303
2594
|
for (const i of order) {
|
|
2304
|
-
if (defs[i].kind === "const" || defs[i].nameOnly) continue;
|
|
2595
|
+
if (defs[i].kind === "const" || CONTRACT_KINDS.has(defs[i].kind) || defs[i].nameOnly) continue;
|
|
2305
2596
|
for (let L = defs[i].s; L <= defs[i].e && L <= nLines; L++) owner[L] = i;
|
|
2306
2597
|
}
|
|
2307
2598
|
|
|
@@ -2409,6 +2700,8 @@ function finalize(c, lang, defs, calls, imports, exportsSet, extractor) {
|
|
|
2409
2700
|
effects: [],
|
|
2410
2701
|
...(doc ? { doc } : {}),
|
|
2411
2702
|
...(d.route ? { route: d.route } : {}),
|
|
2703
|
+
...(d.route_name ? { route_name: d.route_name } : {}),
|
|
2704
|
+
...(d.values ? { values: d.values } : {}),
|
|
2412
2705
|
...(d.bases && d.bases.length ? { bases: d.bases } : {}),
|
|
2413
2706
|
};
|
|
2414
2707
|
});
|
|
@@ -2441,6 +2734,8 @@ function finalize(c, lang, defs, calls, imports, exportsSet, extractor) {
|
|
|
2441
2734
|
...(cc.sup ? { sup: true } : {}),
|
|
2442
2735
|
...(cc.ref ? { ref: true } : {}),
|
|
2443
2736
|
...(cc.alias ? { alias: cc.alias } : {}),
|
|
2737
|
+
...(cc.route ? { route: true } : {}),
|
|
2738
|
+
...(cc.via ? { via: true } : {}),
|
|
2444
2739
|
});
|
|
2445
2740
|
else cut(t, cc.line);
|
|
2446
2741
|
if (cc.ref || !cc.args) continue;
|
|
@@ -2491,6 +2786,8 @@ function finalize(c, lang, defs, calls, imports, exportsSet, extractor) {
|
|
|
2491
2786
|
s.body_hash = hash(src.slice(a, b));
|
|
2492
2787
|
assignId(s);
|
|
2493
2788
|
}
|
|
2789
|
+
// v2.2.0 T3: a template is blanked in `src`; its hash comes from the markup.
|
|
2790
|
+
for (let i = 0; i < defs.length; i++) if (defs[i]._raw != null && syms[i]) syms[i].body_hash = hash(defs[i]._raw);
|
|
2494
2791
|
// W1: the alias ROUTE symbol for a decorated handler. Same lines and the same
|
|
2495
2792
|
// body hash as the handler; it CALLS the handler (one LOCAL edge on read), so
|
|
2496
2793
|
// `impact` and `ctx` walk test → route → handler with no special case.
|
|
@@ -2576,6 +2873,7 @@ function sfcSource(src) {
|
|
|
2576
2873
|
|
|
2577
2874
|
function extractOne(item, borrowed) {
|
|
2578
2875
|
let { lang, src } = item;
|
|
2876
|
+
const raw = src;
|
|
2579
2877
|
resetCoverage();
|
|
2580
2878
|
let sfc = null;
|
|
2581
2879
|
if (lang === "vue" || lang === "svelte") {
|
|
@@ -2609,14 +2907,19 @@ function extractOne(item, borrowed) {
|
|
|
2609
2907
|
// W5b: the walker records a method only inside a class. An object's
|
|
2610
2908
|
// members, the constants and an SFC's component come from the same pass
|
|
2611
2909
|
// the heuristic rung runs, so both rungs name the same symbols.
|
|
2612
|
-
|
|
2910
|
+
let imps = borrowed.imports;
|
|
2911
|
+
if (lang === "js" || lang === "ts") {
|
|
2912
|
+
const o = objectDefs(c, defs, sfc);
|
|
2913
|
+
defs.push(...o.defs);
|
|
2914
|
+
imps = vuePass(c, raw, sfc, defs, calls, imps, o.ownerName);
|
|
2915
|
+
}
|
|
2613
2916
|
for (const p of regParens) reg.add(lineOf(c.starts, p));
|
|
2614
2917
|
for (const cc of calls) if (reg.has(cc.line) && ARGS_WORTH.test(cc.name)) cc.reg = true;
|
|
2615
2918
|
defs.sort((a, b) => a.from - b.from);
|
|
2616
2919
|
// `module.exports = { … }` is not in the AST walk; the heuristic half
|
|
2617
2920
|
// that just ran read it out of the source, so the two sets are merged.
|
|
2618
2921
|
const exp = new Set([...(borrowed.exportsSet || []), ...(hexports || [])]);
|
|
2619
|
-
return finalize(c, lang, defs, calls,
|
|
2922
|
+
return finalize(c, lang, defs, calls, imps, exp.size ? exp : null, borrowed.extractor);
|
|
2620
2923
|
}
|
|
2621
2924
|
// Django routes are module-level calls the ast visitor reports with their
|
|
2622
2925
|
// argument pieces; the heuristic finds them by regex. Same shape either way.
|
|
@@ -2636,15 +2939,20 @@ function extractOne(item, borrowed) {
|
|
|
2636
2939
|
return finalize(c, lang, defs, scanCalls(c, defParens, regParens), importsFor(lang, c.src, c.m, c.starts), null, HEURISTIC);
|
|
2637
2940
|
}
|
|
2638
2941
|
const { defs, defParens, regParens, exportsSet } = braceDefs(c, lang);
|
|
2942
|
+
let ownerNameOf = null;
|
|
2639
2943
|
if (lang === "js" || lang === "ts") {
|
|
2640
2944
|
const o = objectDefs(c, defs, sfc);
|
|
2945
|
+
ownerNameOf = o.ownerName;
|
|
2641
2946
|
defs.push(...o.defs);
|
|
2642
2947
|
// APPENDED, never sorted in: a file with no object keeps every symbol in
|
|
2643
2948
|
// the order 1.9.0 wrote it, so its record is the same bytes.
|
|
2644
2949
|
for (const p of o.defParens) defParens.add(p);
|
|
2645
2950
|
}
|
|
2646
2951
|
braceDecorators(c, lang, defs);
|
|
2647
|
-
|
|
2952
|
+
const calls = scanCalls(c, defParens, regParens);
|
|
2953
|
+
let imps = importsFor(lang, c.src, c.m, c.starts);
|
|
2954
|
+
if (lang === "js" || lang === "ts") imps = vuePass(c, raw, sfc, defs, calls, imps, ownerNameOf);
|
|
2955
|
+
return finalize(c, lang, defs, calls, imps, exportsSet, HEURISTIC);
|
|
2648
2956
|
}
|
|
2649
2957
|
|
|
2650
2958
|
// ── the borrowed Python parser ──────────────────────────────────────────────
|
package/bin/graph-gain.js
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
// executor-window delta, with N and the spread beside it)
|
|
28
28
|
// · never count a hook coverage note as avoided anything — it adds, it saves
|
|
29
29
|
// nothing, and it is listed as paid only
|
|
30
|
-
// ·
|
|
30
|
+
// · NOT DONE YET: a card the agent's return marked `graph_used: none` is not
|
|
31
|
+
// a saving, but the meter cannot see a return, so `avoided` still counts it
|
|
31
32
|
// · never block a read: the ledger is appended AFTER the answer is out, and a
|
|
32
33
|
// failed append is silent
|
|
33
34
|
|
|
@@ -261,13 +262,83 @@ function stampMs(at) {
|
|
|
261
262
|
// `hint`, `read-note` and `hook-update` are the hook's rows, not reads.
|
|
262
263
|
const READ_SET = ["ctx", "for-slice", "impact", "map", "changes", "cochange", "coverage", "path"];
|
|
263
264
|
|
|
265
|
+
// v2.2.0 (G-07): a run is named three ways — `run-quick-x-091026-134917.txt`
|
|
266
|
+
// (the trace file), without `.txt`, and without `run-` (the slug a lane
|
|
267
|
+
// prints). All three are the same run. Both sides are normalised; when they
|
|
268
|
+
// still differ, the same `-DDMMYY-HHMMSS` stamp at the end is the same run.
|
|
269
|
+
function runKey(name) {
|
|
270
|
+
return String(name || "")
|
|
271
|
+
.split(/[\\/]/)
|
|
272
|
+
.pop()
|
|
273
|
+
.replace(/\.txt$/, "")
|
|
274
|
+
.replace(/^run-/, "");
|
|
275
|
+
}
|
|
276
|
+
function sameRun(a, b) {
|
|
277
|
+
if (!a || !b) return false;
|
|
278
|
+
const x = runKey(a);
|
|
279
|
+
const y = runKey(b);
|
|
280
|
+
if (x === y) return true;
|
|
281
|
+
const sx = /-(\d{6}-\d{6})$/.exec(x);
|
|
282
|
+
const sy = /-(\d{6}-\d{6})$/.exec(y);
|
|
283
|
+
return !!(sx && sy && sx[1] === sy[1]);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// v2.2.0 (G-08): the identity of one `changes` answer — its sorted symbol ids
|
|
287
|
+
// with their body hashes. The same key twice in one run is the same diff read
|
|
288
|
+
// twice, and the second read avoided nothing.
|
|
289
|
+
function changesKey(symbols) {
|
|
290
|
+
return (symbols || [])
|
|
291
|
+
.map((x) => `${x.id}@${x.body_hash || ""}`)
|
|
292
|
+
.sort()
|
|
293
|
+
.join("|");
|
|
294
|
+
}
|
|
295
|
+
function changesSeen(claudeDir, run, key) {
|
|
296
|
+
if (!run || !key) return false;
|
|
297
|
+
const rows = readLedger(claudeDir) || [];
|
|
298
|
+
return rows.some((r) => r.cmd === "changes" && r.key === key && sameRun(r.run, run));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// G-08: `changes` prices ONLY the symbols with a confident caller OUTSIDE the
|
|
302
|
+
// changed files, and only those callers. A symbol nobody outside reaches adds 0.
|
|
303
|
+
function changesPriced(model, symbols, changedFiles, nameHits) {
|
|
304
|
+
const changed = new Set(changedFiles || []);
|
|
305
|
+
let hits = 0;
|
|
306
|
+
let n = 0;
|
|
307
|
+
const callerFiles = [];
|
|
308
|
+
for (const x of symbols || []) {
|
|
309
|
+
const outside = (x.caller_files || []).filter((f) => !changed.has(f));
|
|
310
|
+
if (!outside.length) continue;
|
|
311
|
+
n++;
|
|
312
|
+
hits += Number(nameHits(x)) || 0;
|
|
313
|
+
callerFiles.push(...outside);
|
|
314
|
+
}
|
|
315
|
+
if (!n) return { avoided: { low: 0, high: 0, calls_low: 0, calls_high: 0 }, basis: { files: [], grep_hits: 0 }, priced: 0 };
|
|
316
|
+
return { ...changesCost(model, n, hits, callerFiles), priced: n };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// v2.2.0 (T6): the executor PACK. Done well, the executor would have read
|
|
320
|
+
// nothing it did not need (low 0); done badly — what the Vue eval measured —
|
|
321
|
+
// it read every reference file and every big declared file whole. The pack's
|
|
322
|
+
// own tokens come off that, never below 0.
|
|
323
|
+
function packCost(model, refFiles, bigDeclared, packTokens) {
|
|
324
|
+
const cs = [...new Set([...(refFiles || []), ...(bigDeclared || [])])].map((rel) => fileCost(model, rel));
|
|
325
|
+
const whole = cs.reduce((a, c) => a + c.tokens, 0);
|
|
326
|
+
return {
|
|
327
|
+
avoided: { low: 0, high: Math.max(0, whole - (Number(packTokens) || 0)), calls_low: 0, calls_high: cs.length },
|
|
328
|
+
basis: { files: (refFiles || []).map((rel) => strip(fileCost(model, rel))), grep_hits: 0 },
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// The outcome of each read (G-19). A row written before 2.2.0 has none.
|
|
333
|
+
const OUTCOMES = ["ok", "empty", "declined", "error"];
|
|
334
|
+
|
|
264
335
|
function gain(claudeDir, opts) {
|
|
265
336
|
const o = opts || {};
|
|
266
337
|
const all = readLedger(claudeDir);
|
|
267
338
|
if (!all) return { ok: false, state: "none", reason: "no-ledger", exit: 1 };
|
|
268
339
|
const sinceMs = parseSince(o.since);
|
|
269
340
|
let rows = all;
|
|
270
|
-
if (o.run) rows = rows.filter((r) => r.run
|
|
341
|
+
if (o.run) rows = rows.filter((r) => sameRun(r.run, o.run));
|
|
271
342
|
if (sinceMs != null) rows = rows.filter((r) => {
|
|
272
343
|
const t = stampMs(r.at);
|
|
273
344
|
return t == null || t >= sinceMs;
|
|
@@ -290,6 +361,7 @@ function gain(claudeDir, opts) {
|
|
|
290
361
|
// W8: rows the sharded read path could not size. Their estimate omits the
|
|
291
362
|
// search term, so a FALLING estimate can be told apart from a falling saving.
|
|
292
363
|
let unsized = 0;
|
|
364
|
+
const outcomes = { ok: 0, empty: 0, declined: 0, error: 0 };
|
|
293
365
|
const gens = [];
|
|
294
366
|
const runs = new Set();
|
|
295
367
|
for (const r of rows) {
|
|
@@ -306,6 +378,7 @@ function gain(claudeDir, opts) {
|
|
|
306
378
|
calls.high += Number(r.avoided.calls_high) || 0;
|
|
307
379
|
}
|
|
308
380
|
if (r.basis && r.basis.grep_hits === null) unsized++;
|
|
381
|
+
if (OUTCOMES.includes(r.outcome)) outcomes[r.outcome]++;
|
|
309
382
|
byCommand[r.cmd] = (byCommand[r.cmd] || 0) + 1;
|
|
310
383
|
if (r.cmd === "hint") hints.injected++;
|
|
311
384
|
if (r.cmd === "read-note") hints.read_notes++;
|
|
@@ -333,6 +406,7 @@ function gain(claudeDir, opts) {
|
|
|
333
406
|
read_set: READ_SET,
|
|
334
407
|
never_called: READ_SET.filter((c) => !Object.prototype.hasOwnProperty.call(byCommand, c)),
|
|
335
408
|
hints,
|
|
409
|
+
outcomes,
|
|
336
410
|
unsized,
|
|
337
411
|
estimate: true,
|
|
338
412
|
carry_note: "a card is re-sent on every later turn of the agent that received it — and so is the read it replaced. Both halves carry the same multiplier, so these are FIRST-ENTRY tokens on both sides (eval/graph-replay.js).",
|
|
@@ -421,4 +495,11 @@ module.exports = {
|
|
|
421
495
|
reset,
|
|
422
496
|
stampMs,
|
|
423
497
|
parseSince,
|
|
498
|
+
runKey,
|
|
499
|
+
sameRun,
|
|
500
|
+
changesKey,
|
|
501
|
+
changesSeen,
|
|
502
|
+
changesPriced,
|
|
503
|
+
packCost,
|
|
504
|
+
OUTCOMES,
|
|
424
505
|
};
|