@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.
Files changed (134) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/README-id.md +57 -59
  3. package/README.md +53 -54
  4. package/bin/build-agents.js +109 -109
  5. package/bin/cli.js +262 -58
  6. package/bin/graph-audit.js +235 -0
  7. package/bin/graph-extract.js +308 -3
  8. package/bin/graph-gain.js +23 -3
  9. package/bin/graph-map.js +9 -1
  10. package/bin/graph-query.js +152 -10
  11. package/bin/graph-shard.js +6 -1
  12. package/bin/graph-signals.js +287 -4
  13. package/bin/graph.js +717 -658
  14. package/bin/onboarding-content.js +4 -4
  15. package/bin/pricing.json +207 -200
  16. package/bin/verify-contracts.js +117 -2
  17. package/bin/verify-package.js +11 -7
  18. package/bin/webui/css/04-motion.css +162 -152
  19. package/bin/webui/css/panels/knowledge.css +563 -116
  20. package/bin/webui/fixtures/extra.js +2036 -2036
  21. package/bin/webui/fixtures/hookui.js +5 -5
  22. package/bin/webui/fixtures/knowledge.js +21 -4
  23. package/bin/webui/fixtures/settings.js +305 -305
  24. package/bin/webui/i18n/en/knowledge.json +122 -4
  25. package/bin/webui/i18n/id/knowledge.json +122 -4
  26. package/bin/webui/js/panels/knowledge.js +573 -9
  27. package/bin/webui/js/panels/overview.js +13 -4
  28. package/mock-run/context-combiner.md +100 -100
  29. package/mock-run/orc-budget.md +534 -534
  30. package/mock-run/orc-challenge-council.md +262 -262
  31. package/mock-run/orc-ultra.md +103 -103
  32. package/package.json +1 -1
  33. package/templates/agents/MODEL-MAPPING.md +43 -43
  34. package/templates/agents/orc-advisor-opus-5-xhigh.md +56 -56
  35. package/templates/agents/orc-analyze-mini-opus-5-med.md +60 -60
  36. package/templates/agents/orc-analyze-mini-sonnet-5-high.md +58 -58
  37. package/templates/agents/orc-challenge-advisor-opus-5-med.md +75 -75
  38. package/templates/agents/orc-challenge-contrarian-opus-5-high.md +110 -110
  39. package/templates/agents/orc-challenge-executor-opus-5-med.md +114 -114
  40. package/templates/agents/orc-challenge-expansionist-opus-5-med.md +112 -112
  41. package/templates/agents/orc-challenge-judge-opus-5-high.md +132 -132
  42. package/templates/agents/orc-challenge-outsider-opus-5-low.md +109 -109
  43. package/templates/agents/orc-challenge-principles-opus-5-high.md +109 -109
  44. package/templates/agents/orc-challenge-reader-opus-5-low.md +90 -90
  45. package/templates/agents/orc-claude-writer-opus-5-med.md +55 -55
  46. package/templates/agents/orc-context-combiner-opus-5-high.md +88 -88
  47. package/templates/agents/orc-doc-checker-opus-5-low.md +108 -108
  48. package/templates/agents/orc-doc-writer-opus-5-med.md +134 -134
  49. package/templates/agents/orc-executor-haiku-4-5.md +2 -1
  50. package/templates/agents/orc-executor-opus-4-7-high.md +2 -1
  51. package/templates/agents/orc-executor-opus-4-7-med.md +2 -1
  52. package/templates/agents/orc-executor-opus-4-8-high.md +2 -1
  53. package/templates/agents/orc-executor-opus-5-high.md +4 -3
  54. package/templates/agents/orc-executor-opus-5-low.md +4 -3
  55. package/templates/agents/orc-executor-opus-5-med.md +4 -3
  56. package/templates/agents/orc-executor-sonnet-4-6-high.md +2 -1
  57. package/templates/agents/orc-executor-sonnet-4-6-med.md +2 -1
  58. package/templates/agents/orc-executor-sonnet-5-high.md +2 -1
  59. package/templates/agents/orc-graph-noter-sonnet-4-6-med.md +1 -1
  60. package/templates/agents/orc-judge-opus-5-xhigh.md +85 -85
  61. package/templates/agents/orc-learn-writer-opus-5-low.md +73 -73
  62. package/templates/agents/orc-pattern-codifier-opus-5-med.md +65 -65
  63. package/templates/agents/orc-planner-mini-opus-5-med.md +4 -4
  64. package/templates/agents/orc-planner-mini-sonnet-5-high.md +1 -1
  65. package/templates/agents/orc-planner-opus-5-med.md +160 -160
  66. package/templates/agents/orc-recon-opus-5-low.md +18 -8
  67. package/templates/agents/orc-recon-sonnet-4-6-med.md +15 -5
  68. package/templates/agents/orc-retro-opus-5-med.md +3 -3
  69. package/templates/agents/orc-reviewer-opus-5-med.md +60 -60
  70. package/templates/agents/orc-scout-opus-5-low.md +40 -40
  71. package/templates/agents/orc-system-analyst-opus-5-high.md +120 -120
  72. package/templates/agents/orc-test-author-opus-5-med.md +71 -71
  73. package/templates/agents/orc-test-designer-opus-5-high.md +158 -158
  74. package/templates/agents/orc-test-interpreter-opus-5-low.md +130 -130
  75. package/templates/agents/orc-verifier-opus-5-med.md +69 -69
  76. package/templates/agents/orc-wiki-scanner-opus-5-med.md +81 -81
  77. package/templates/commands/orc-ultra.md +17 -17
  78. package/templates/commands/orc-verify.md +11 -11
  79. package/templates/hooks/README.md +13 -3
  80. package/templates/hooks/orc-effort-guard.js +178 -178
  81. package/templates/hooks/orc-graph-hook.js +53 -7
  82. package/templates/hooks/orc-statusline.js +6 -6
  83. package/templates/skills/_shared/code-graph.md +57 -18
  84. package/templates/skills/_shared/config-precedence.md +198 -198
  85. package/templates/skills/_shared/extra-dispatch.md +1346 -1346
  86. package/templates/skills/_shared/opus5-only.md +8 -8
  87. package/templates/skills/_shared/phases/analyst-gates.md +136 -136
  88. package/templates/skills/_shared/phases/execution.md +4 -4
  89. package/templates/skills/_shared/phases/planning.md +3 -3
  90. package/templates/skills/_shared/phases/preflight.md +1 -1
  91. package/templates/skills/_shared/phases/review.md +4 -2
  92. package/templates/skills/_shared/phases/ship.md +1 -1
  93. package/templates/skills/_shared/phases/trace.md +3 -2
  94. package/templates/skills/_shared/read-ladder.md +14 -1
  95. package/templates/skills/_shared/return-validation.md +1 -1
  96. package/templates/skills/context-combiner/SKILL.md +1 -1
  97. package/templates/skills/context-combiner/schemas/combined-report.md +78 -78
  98. package/templates/skills/orc/README.md +2 -2
  99. package/templates/skills/orc/SKILL.md +8 -8
  100. package/templates/skills/orc/config.md +9 -9
  101. package/templates/skills/orc/examples/full-run-mock.md +73 -73
  102. package/templates/skills/orc/references/effort-and-mode.md +222 -222
  103. package/templates/skills/orc/references/preflight-report.md +2 -2
  104. package/templates/skills/orc/references/ultra-mode.md +3 -3
  105. package/templates/skills/orc/subskills/orc-planner/SKILL.md +2 -2
  106. package/templates/skills/orc/subskills/orc-planner-mini/SKILL.md +121 -121
  107. package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +76 -76
  108. package/templates/skills/orc/subskills/orc-testgen/SKILL.md +45 -45
  109. package/templates/skills/orc-analyze/SKILL.md +2 -2
  110. package/templates/skills/orc-analyze/examples/analyze-mock.md +42 -42
  111. package/templates/skills/orc-analyze/schemas/report-audit.md +83 -83
  112. package/templates/skills/orc-analyze/schemas/report-prose.md +63 -63
  113. package/templates/skills/orc-analyze/schemas/report-requirement.md +78 -78
  114. package/templates/skills/orc-challenge/README.md +142 -142
  115. package/templates/skills/orc-challenge/examples/council-full-roster.md +273 -273
  116. package/templates/skills/orc-challenge/references/council.md +315 -315
  117. package/templates/skills/orc-challenge/references/intake.md +171 -171
  118. package/templates/skills/orc-claude/SKILL.md +14 -14
  119. package/templates/skills/orc-diy/README.md +143 -143
  120. package/templates/skills/orc-diy/references/flow-schema.md +1 -1
  121. package/templates/skills/orc-doc/README.md +229 -229
  122. package/templates/skills/orc-doc/examples/orc-doc-prd-run.md +325 -325
  123. package/templates/skills/orc-doc/references/chunking.md +527 -527
  124. package/templates/skills/orc-fast/SKILL.md +4 -4
  125. package/templates/skills/orc-learn/SKILL.md +14 -14
  126. package/templates/skills/orc-learn/examples/learn-run-mock.md +61 -61
  127. package/templates/skills/orc-mini/SKILL.md +16 -20
  128. package/templates/skills/orc-mini/references/complexity.md +25 -11
  129. package/templates/skills/orc-quick/SKILL.md +12 -13
  130. package/templates/skills/orc-quick/references/gh-mode.md +1 -1
  131. package/templates/skills/orc-quick/references/look.md +5 -5
  132. package/templates/skills/orc-verify/SKILL.md +15 -15
  133. package/templates/skills/orc-verify/examples/verify-mock.md +33 -33
  134. package/templates/skills/orc-wiki/references/extra.md +1 -1
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ // ── orc graph audit (v1.9.1 A2) — WHY the map is thin ───────────────────────
3
+ //
4
+ // `orc graph status` can now say THIN. This says why, and it is the only
5
+ // answer in the family whose subject is the PARSER rather than the code.
6
+ //
7
+ // The question it exists for is a real one, measured on a real project: a graph
8
+ // with 1.9 symbols per file answers `exit 4` to most questions, and until now
9
+ // nothing told anyone whether the code has no named functions or the parser did
10
+ // not read them. Those are opposite problems with opposite fixes, and the only
11
+ // evidence that tells them apart is what the empty files actually LOOK like.
12
+ //
13
+ // THE RULES THIS FILE HOLDS
14
+ //
15
+ // 1. It READS. It never writes a file, never takes the lock, never runs the
16
+ // extractor, and it costs zero model tokens.
17
+ // 2. A zero-symbol file is NOT a defect by itself. A component with no named
18
+ // function is a file the parser read correctly. The last line says so, and
19
+ // the shape reading is a HINT that never becomes a verdict.
20
+ // 3. It reads at most `SHAPE_LINES` lines of a file, and only of a file it is
21
+ // about to name. A repository of ten thousand empty files is still one
22
+ // bounded read per LISTED file.
23
+ // 4. It is a USER command. `LANE_CALLS["graph-audit"].lanes` is `[]` and a
24
+ // test holds it there: nothing a lane does is made better by an audit.
25
+
26
+ const fs = require("fs");
27
+ const path = require("path");
28
+ const G = require("./graph.js");
29
+
30
+ // How far into a file the sampler looks for its first real declaration. A file
31
+ // with a 40-line licence header and then `export default {` is common; a file
32
+ // whose first declaration is below line 40 is rare enough to read as `other`.
33
+ const SHAPE_LINES = 40;
34
+ const TOP_DEFAULT = 20;
35
+ const TOP_CAP = 200;
36
+
37
+ // The languages whose empty files are worth sampling. Sampling a `.json`
38
+ // fixture would produce a shape nobody can act on.
39
+ const SAMPLED = new Set(["js", "ts", "vue", "svelte"]);
40
+
41
+ // Each shape, the plain reading that goes beside it, and whether that reading
42
+ // is a PARSER GAP — a file the parser should have indexed and did not. Only
43
+ // those three are worth an issue; the rest are files that really are data.
44
+ const SHAPES = [
45
+ { id: "script-setup", test: /^<script\s+setup/, reading: "a component with no named function — the parser is right, there is nothing to name", gap: false },
46
+ { id: "script", test: /^<script[\s>]/, reading: "a component whose script block declared nothing the parser names", gap: true },
47
+ // graph@6 (W5b) names the members of a component, a mixin and a store
48
+ // module, and the plain keys of a data object. An object that is STILL empty
49
+ // has neither — quoted keys, or nothing at all.
50
+ { id: "export-default-object", test: /^export default \{/, reading: "an object literal with no member the parser names (quoted keys, or empty) — data, not code", gap: false },
51
+ { id: "export-default-component", test: /^export default (defineComponent|Vue\.extend)\s*\(/, reading: "a component defined as an object whose members the parser did not find — a parser gap; report it with one file path", gap: true },
52
+ { id: "export-default-class", test: /^export default class\b/, reading: "a class the parser should index — report it with one file path", gap: true },
53
+ { id: "export-const-fn", test: /^export const \w+\s*=\s*(\(|async\b)/, reading: "an arrow constant the parser SHOULD index — a parser gap; report it with one file path", gap: true },
54
+ { id: "export-function", test: /^export (async )?function\b/, reading: "a function the parser should index — report it with one file path", gap: true },
55
+ { id: "module-exports-object", test: /^module\.exports\s*=\s*\{/, reading: "data", gap: false },
56
+ { id: "class", test: /^class \w+/, reading: "a class the parser should index — report it with one file path", gap: true },
57
+ { id: "function", test: /^(async )?function \w+/, reading: "a function the parser should index — report it with one file path", gap: true },
58
+ ];
59
+ const OTHER = { id: "other", reading: "no declaration in the first lines — data, a type file, or a re-export", gap: false };
60
+
61
+ const readJson = (f) => {
62
+ try {
63
+ return JSON.parse(fs.readFileSync(f, "utf8"));
64
+ } catch (_) {
65
+ return null;
66
+ }
67
+ };
68
+
69
+ // The first line of a file that is not blank, not a comment and not an import.
70
+ // The import skip matters: a Vue SFC opens with twenty imports and its shape is
71
+ // whatever follows them.
72
+ function firstDeclaration(abs) {
73
+ let text;
74
+ try {
75
+ text = fs.readFileSync(abs, "utf8");
76
+ } catch (_) {
77
+ return null;
78
+ }
79
+ const lines = text.split(/\r?\n/).slice(0, SHAPE_LINES);
80
+ let inBlock = false;
81
+ for (const raw of lines) {
82
+ const line = raw.trim();
83
+ if (!line) continue;
84
+ if (inBlock) {
85
+ if (line.includes("*/")) inBlock = false;
86
+ continue;
87
+ }
88
+ if (line.startsWith("/*")) {
89
+ if (!line.includes("*/")) inBlock = true;
90
+ continue;
91
+ }
92
+ if (line.startsWith("//") || line.startsWith("*") || line.startsWith("<!--")) continue;
93
+ // A directive prologue is not a declaration, and neither is an import. Skip
94
+ // both, or every CommonJS file in the repository reads as `other`.
95
+ if (/^["']use (strict|client|server)["'];?$/.test(line)) continue;
96
+ if (/^(import|export \{|export \*|const .* = require\()/.test(line)) continue;
97
+ return line;
98
+ }
99
+ return null;
100
+ }
101
+
102
+ function shapeOf(abs) {
103
+ const line = firstDeclaration(abs);
104
+ if (line == null) return OTHER;
105
+ for (const s of SHAPES) if (s.test.test(line)) return s;
106
+ return OTHER;
107
+ }
108
+
109
+ function audit(claudeDir, root, opts) {
110
+ const o = opts || {};
111
+ const p = G.graphPaths(claudeDir);
112
+ const index = readJson(p.index);
113
+ const files = readJson(p.files);
114
+ const meta = readJson(p.meta);
115
+ if (!index || !files || !meta) return { ok: false, state: "none", reason: "no-index", exit: 1 };
116
+
117
+ const SIG = require("./graph-signals.js");
118
+ const density = meta.density || G.densityOf(index, files);
119
+ const thin = SIG.isThin(density, meta.files || Object.keys(index.by_file || {}).length);
120
+ const top = Math.max(1, Math.min(TOP_CAP, Number(o.top) || TOP_DEFAULT));
121
+
122
+ // Per language: the density field, plus which extractor actually read each
123
+ // file. A language read by the heuristic where a real parser was expected is
124
+ // half the answer on its own.
125
+ const byLang = {};
126
+ for (const [lang, v] of Object.entries(density.by_lang || {})) byLang[lang] = { ...v, per_file: v.files ? Math.round((10 * v.symbols) / v.files) / 10 : 0, extractor: {} };
127
+ for (const [rel, v] of Object.entries(files)) {
128
+ const lang = (index.by_file[rel] && index.by_file[rel].lang) || G.LANG_BY_EXT[path.extname(rel).toLowerCase()] || "other";
129
+ const row = byLang[lang];
130
+ if (!row || !v || !v.extractor) continue;
131
+ row.extractor[v.extractor] = (row.extractor[v.extractor] || 0) + 1;
132
+ }
133
+
134
+ // The empty files, longest first: the longest file the parser read as empty
135
+ // is the one that says most about what it cannot read.
136
+ const zero = [];
137
+ const partial = [];
138
+ for (const [rel, v] of Object.entries(index.by_file)) {
139
+ const n = (v.symbols || []).filter((s) => s.kind !== "module").length;
140
+ if (v.coverage === "partial") partial.push({ path: rel, ranges: v.partial || [] });
141
+ if (n > 0) continue;
142
+ zero.push({ path: rel, lines: Number(v.lines) || 0, lang: v.lang || G.LANG_BY_EXT[path.extname(rel).toLowerCase()] || "other" });
143
+ }
144
+ zero.sort((a, b) => b.lines - a.lines || a.path.localeCompare(b.path));
145
+
146
+ // The sampler reads only the files it lists, and only the languages whose
147
+ // shape means anything.
148
+ const shapeCount = new Map();
149
+ for (const row of zero) {
150
+ if (!SAMPLED.has(row.lang)) continue;
151
+ const s = shapeOf(path.join(root, ...row.path.split("/")));
152
+ row.shape = s.id;
153
+ shapeCount.set(s.id, (shapeCount.get(s.id) || 0) + 1);
154
+ }
155
+ const shapes = [...shapeCount.entries()]
156
+ .map(([id, count]) => {
157
+ const def = SHAPES.find((x) => x.id === id) || OTHER;
158
+ return { shape: id, count, reading: def.reading, gap: def.gap };
159
+ })
160
+ .sort((a, b) => b.count - a.count || a.shape.localeCompare(b.shape));
161
+
162
+ const skipped = Object.entries(files)
163
+ .filter(([, v]) => v && v.skipped)
164
+ .map(([rel, v]) => ({ path: rel, reason: v.skipped, bytes: Number(v.bytes) || 0 }))
165
+ .sort((a, b) => a.path.localeCompare(b.path));
166
+
167
+ const gen = meta.generation || 0;
168
+ const num = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
169
+ const pad = (v, w) => String(v).padStart(w);
170
+ const head =
171
+ `graph audit (gen ${gen}) — ${num(meta.files || 0)} files · ${num(meta.symbols || 0)} symbols · ` +
172
+ `${density.symbols_per_file} symbols/file${thin ? " · THIN" : ""}`;
173
+ const table = [
174
+ " lang files symbols per-file zero partial skipped extractor",
175
+ ...Object.entries(byLang)
176
+ .sort((a, b) => b[1].files - a[1].files || a[0].localeCompare(b[0]))
177
+ .map(([lang, v]) => {
178
+ const ex = Object.entries(v.extractor)
179
+ .sort((a, b) => b[1] - a[1])
180
+ .map(([k, c]) => `${k} (${c})`)
181
+ .join(" · ");
182
+ return ` ${lang.padEnd(6)} ${pad(num(v.files), 5)} ${pad(num(v.symbols), 8)} ${pad(v.per_file, 9)} ${pad(num(v.zero), 5)} ${pad(num(v.partial), 9)} ${pad(num(v.skipped), 8)} ${ex}`;
183
+ }),
184
+ ];
185
+ const zeroList = zero.length
186
+ ? [
187
+ ` files the parser read as EMPTY, longest first (${Math.min(top, zero.length)} of ${num(zero.length)}${zero.length > top ? " · --top=N for more" : ""})`,
188
+ ...zero.slice(0, top).map((z) => ` ${pad(num(z.lines), 6)} ${z.path}${z.shape ? ` ${z.shape}` : ""}`),
189
+ ]
190
+ : [" no file was read as empty"];
191
+ const shapeList = shapes.length
192
+ ? [
193
+ ` what the first declaration in an empty file looks like (${num(zero.filter((z) => SAMPLED.has(z.lang)).length)} files sampled)`,
194
+ ...shapes.map((s) => ` ${s.shape.padEnd(26)} ${pad(num(s.count), 5)} ${s.reading}`),
195
+ ]
196
+ : [];
197
+ const skippedList = skipped.length
198
+ ? [` skipped (${skipped.length})`, ...skipped.slice(0, top).map((x) => ` ${x.reason.padEnd(11)} ${x.path}${x.bytes ? ` (${Math.round(x.bytes / 1024)} KB)` : ""}`)]
199
+ : [];
200
+ const partialList = partial.length
201
+ ? [` partial (${partial.length}) ` + partial.slice(0, 6).map((x) => `${x.path} ${(x.ranges[0] || []).join("-")}`).join(" · ") + (partial.length > 6 ? " · … (--json lists all)" : "")]
202
+ : [];
203
+
204
+ const line = [
205
+ head,
206
+ ...table,
207
+ ...zeroList,
208
+ ...shapeList,
209
+ ...skippedList,
210
+ ...partialList,
211
+ " an empty file is not a defect by itself — the graph names functions, methods, classes and routes;",
212
+ ' a shape marked "parser gap" is worth one issue with the file path',
213
+ ].join("\n");
214
+
215
+ return {
216
+ ok: true,
217
+ state: "found",
218
+ generation: gen,
219
+ gen_id: meta.gen_id || null,
220
+ density,
221
+ thin,
222
+ top,
223
+ by_lang: byLang,
224
+ zero_files: zero.slice(0, top),
225
+ total_zero: zero.length,
226
+ shapes,
227
+ skipped,
228
+ partial,
229
+ line,
230
+ trace: `GRAPH-AUDIT ${thin ? "thin" : "ok"} :: files=${meta.files || 0} symbols=${meta.symbols || 0} per_file=${density.symbols_per_file} zero=${zero.length} skipped=${skipped.length} gen=${gen}`,
231
+ exit: 0,
232
+ };
233
+ }
234
+
235
+ module.exports = { audit, shapeOf, firstDeclaration, SHAPES, OTHER, SHAPE_LINES, TOP_DEFAULT, TOP_CAP };
@@ -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@5";
59
+ const HEURISTIC = "heuristic@6";
60
60
  const MAX_CALLS_PER_SYMBOL = 200;
61
61
  const MAX_EFFECTS_PER_SYMBOL = 12;
62
62
  const MAX_URLS_PER_SYMBOL = 200;
@@ -1061,6 +1061,288 @@ function basesOf(tail) {
1061
1061
  return [...new Set(out)].slice(0, 8);
1062
1062
  }
1063
1063
 
1064
+ // ── W5b (v1.9.1, DE-15): an OBJECT is a declaration too ─────────────────────
1065
+ // An Options API component, a mixin and a Vuex module define every function
1066
+ // they have as a MEMBER of one default-exported object. The rungs above read
1067
+ // declarations at a line start, so on a Vue project 416 of 429 `.vue` files
1068
+ // had no symbol at all. This pass runs on the masked source for js/ts (and an
1069
+ // SFC's `<script>`), on the heuristic rung AND after the borrowed TypeScript
1070
+ // walk — the walker only records a method inside a class, so both were blind in
1071
+ // the same place.
1072
+ //
1073
+ // It adds no new idea to the resolver: the object is a `class`, a member is a
1074
+ // `method` named `<Owner>.<name>`, `mixins`/`extends` are its `bases`. So
1075
+ // `this.submit()` resolves LOCAL through `classOf`, and a mixin member is
1076
+ // reached through `inheritedMember`, exactly as a class method is.
1077
+ const OBJ_SECTIONS = new Set(["methods", "computed", "watch", "getters", "mutations", "actions", "filters", "provide"]);
1078
+ const DEFAULT_OBJECT = /\bexport[ \t]+default[ \t]+(?:(?:defineComponent|Vue[ \t]*\.[ \t]*extend)[ \t]*\([ \t\r\n]*)?\{/g;
1079
+ const IDENT_RE = /^[A-Za-z_$][\w$]*$/;
1080
+ // A value that is a function: `function (…)`, `async function`, `(…) =>`,
1081
+ // `x =>`. The arrow's parameter list may span lines, so it is matched by pair.
1082
+ function fnValueAt(m, v) {
1083
+ const rest = m.slice(v, v + 200);
1084
+ if (/^(?:async[ \t]+)?function\b/.test(rest)) {
1085
+ const p = m.indexOf("(", v);
1086
+ return { paren: p };
1087
+ }
1088
+ if (/^(?:async[ \t]+)?[A-Za-z_$][\w$]*[ \t]*=>/.test(rest) && !/^(?:async[ \t]+)?(?:function|class|new)\b/.test(rest)) return { paren: null };
1089
+ const a = /^(?:async[ \t]*)?\(/.exec(rest);
1090
+ if (a) {
1091
+ const p = v + a[0].length - 1;
1092
+ const pc = matchPairQuiet(m, p, "(", ")");
1093
+ if (pc > 0 && /^\s*(?::[^=\n]+)?=>/.test(m.slice(pc + 1, pc + 200))) return { paren: null };
1094
+ }
1095
+ return null;
1096
+ }
1097
+
1098
+ // `matchPair` records an unclosed `{` as a coverage gap. A speculative probe
1099
+ // for an arrow's parameter list must never do that.
1100
+ function matchPairQuiet(s, open, a, b) {
1101
+ let d = 0;
1102
+ for (let k = open; k < s.length; k++) {
1103
+ if (s[k] === a) d++;
1104
+ else if (s[k] === b && --d === 0) return k;
1105
+ }
1106
+ return -1;
1107
+ }
1108
+
1109
+ // The depth-0 members of an object literal, as [start, end) with the leading
1110
+ // whitespace dropped. The mask keeps quotes and blanks what is inside them, so
1111
+ // a comma in a string can never split a member.
1112
+ function objectMembers(m, open, close) {
1113
+ const out = [];
1114
+ let d = 0;
1115
+ let a = open + 1;
1116
+ for (let k = open + 1; k < close; k++) {
1117
+ const ch = m[k];
1118
+ if (ch === "(" || ch === "[" || ch === "{") d++;
1119
+ else if (ch === ")" || ch === "]" || ch === "}") d--;
1120
+ else if (ch === "," && d === 0) {
1121
+ out.push([a, k]);
1122
+ a = k + 1;
1123
+ }
1124
+ }
1125
+ out.push([a, close]);
1126
+ const kept = [];
1127
+ for (let [x, y] of out) {
1128
+ while (x < y && /\s/.test(m[x])) x++;
1129
+ if (x < y) kept.push([x, y]);
1130
+ }
1131
+ return kept;
1132
+ }
1133
+
1134
+ const KEY = "([A-Za-z_$][\\w$]*|\\[[ \\t]*[A-Za-z_$][\\w$.]*[ \\t]*\\])";
1135
+ const MEM_ACCESSOR = /^(?:get|set)[ \t]+([A-Za-z_$][\w$]*)[ \t]*\(/;
1136
+ const MEM_METHOD = new RegExp(`^(?:async[ \\t]+)?\\*?[ \\t]*${KEY}[ \\t]*\\(`);
1137
+ const MEM_PAIR = new RegExp(`^${KEY}[ \\t]*:[ \\t\\r\\n]*`);
1138
+ const keyName = (k) => (k.startsWith("[") ? k.replace(/[[\]\s]/g, "").split(".").pop() : k);
1139
+
1140
+ // What one member IS. `fn` carries the paren of a method shorthand (so the
1141
+ // call scanner never reads `submit() {` as a call to `submit`).
1142
+ function memberOf(m, a, b) {
1143
+ const seg = m.slice(a, b);
1144
+ if (seg.startsWith("...")) return null;
1145
+ let x = MEM_ACCESSOR.exec(seg);
1146
+ if (x) return { type: "fn", name: x[1], paren: a + x[0].length - 1 };
1147
+ x = MEM_METHOD.exec(seg);
1148
+ if (x && !/^(?:function|async)$/.test(x[1])) return { type: "fn", name: keyName(x[1]), paren: a + x[0].length - 1 };
1149
+ x = MEM_PAIR.exec(seg);
1150
+ if (x) {
1151
+ const name = keyName(x[1]);
1152
+ const v = a + x[0].length;
1153
+ const f = fnValueAt(m, v);
1154
+ // `submit: function submit() {}` — the paren is a declaration's, never a call.
1155
+ if (f) return { type: "fn", name, paren: f.paren, v };
1156
+ if (m[v] === "{") return { type: "obj", name, open: v, close: matchPairQuiet(m, v, "{", "}"), v };
1157
+ if (m[v] === "[") return { type: "arr", name, open: v, close: matchPairQuiet(m, v, "[", "]"), v };
1158
+ return { type: "val", name, v };
1159
+ }
1160
+ if (/^[A-Za-z_$][\w$]*\s*$/.test(seg)) return { type: "short", name: seg.trim() };
1161
+ return null;
1162
+ }
1163
+
1164
+ // The owner's name when the object has no `name:` — the file stem, or the
1165
+ // folder for an `index` file. A kebab-case stem becomes PascalCase, the way a
1166
+ // component is named where it is used.
1167
+ function ownerStem(rel) {
1168
+ const parts = String(rel).split("/");
1169
+ let base = parts.pop().replace(/\.[^.]*$/, "");
1170
+ if (/^index$/i.test(base) && parts.length) base = parts.pop();
1171
+ if (IDENT_RE.test(base)) return base;
1172
+ let n = base
1173
+ .split(/[^A-Za-z0-9_$]+/)
1174
+ .filter(Boolean)
1175
+ .map((w) => w[0].toUpperCase() + w.slice(1))
1176
+ .join("");
1177
+ if (!n) n = "Component";
1178
+ return /^\d/.test(n) ? "_" + n : n;
1179
+ }
1180
+
1181
+ // The end of a `const X = …` value: the `;` or the line break at depth 0 that
1182
+ // ends the statement. A line that ends on an operator, or a next line that
1183
+ // starts with one, continues it.
1184
+ function valueEnd(m, v) {
1185
+ let d = 0;
1186
+ for (let k = v; k < m.length; k++) {
1187
+ const ch = m[k];
1188
+ if (ch === "(" || ch === "[" || ch === "{") d++;
1189
+ else if (ch === ")" || ch === "]" || ch === "}") {
1190
+ if (d === 0) return k - 1;
1191
+ d--;
1192
+ } else if (d === 0 && ch === ";") return k - 1;
1193
+ else if (d === 0 && ch === "\n") {
1194
+ let p = k - 1;
1195
+ while (p > v && (m[p] === " " || m[p] === "\t" || m[p] === "\r")) p--;
1196
+ let n = k + 1;
1197
+ while (n < m.length && /\s/.test(m[n])) n++;
1198
+ if (!/[=+\-*/,(?:&|]/.test(m[p]) && !/[.?:+\-*/&|]/.test(m[n] || "")) return p;
1199
+ }
1200
+ }
1201
+ return m.length - 1;
1202
+ }
1203
+
1204
+ function objectDefs(c, have, sfc) {
1205
+ const { src, m, starts, rel } = c;
1206
+ const defs = [];
1207
+ const defParens = new Set();
1208
+ const taken = new Set(have.map((d) => d.name));
1209
+ const trim = (b) => {
1210
+ let k = b - 1;
1211
+ while (k > 0 && /\s/.test(m[k])) k--;
1212
+ return k;
1213
+ };
1214
+ const add = (d) => {
1215
+ if (!d.name || MEMBER_KW.has(d.name)) return null;
1216
+ d.s = lineOf(starts, d.from);
1217
+ d.e = lineOf(starts, Math.max(d.from, d.to));
1218
+ defs.push(d);
1219
+ return d;
1220
+ };
1221
+ // A top-level `const NAME = …`: where a shorthand key's value is declared.
1222
+ const declOf = (name) => {
1223
+ const re = new RegExp(`^[ \\t]*(?:export[ \\t]+)?(?:const|let|var)[ \\t]+${name.replace(/\$/g, "\\$")}[ \\t]*(?::[^=\\n]+)?=[ \\t]*`, "m");
1224
+ const x = re.exec(m);
1225
+ return x ? { from: x.index, v: x.index + x[0].length } : null;
1226
+ };
1227
+ // A value that is a `require(…)` / `import(…)` is a RE-EXPORT: the name is
1228
+ // defined in the other file, and a constant here would make this barrel look
1229
+ // like its definition (`exports.x = require("./store").x`).
1230
+ const reexport = (v) => /^(?:await[ \t]+)?(?:require|import)[ \t]*\(/.test(m.slice(v, v + 40));
1231
+ const constant = (name, from, to, v) => {
1232
+ if (!IDENT_RE.test(name) || taken.has(name) || (v != null && reexport(v))) return;
1233
+ taken.add(name);
1234
+ add({ name, qname: name, kind: "const", from, to, exported: true });
1235
+ };
1236
+ // E2 for an object whose members are data: each key is a constant. A
1237
+ // function-valued member is a function, not a constant, and is left alone.
1238
+ const constantsOf = (open, close) => {
1239
+ for (const [a, b] of objectMembers(m, open, close)) {
1240
+ const mem = memberOf(m, a, b);
1241
+ if (!mem || mem.type === "fn") continue;
1242
+ if (mem.type === "short") {
1243
+ const d = declOf(mem.name);
1244
+ if (d && !fnValueAt(m, d.v)) constant(mem.name, d.from, valueEnd(m, d.v), d.v);
1245
+ continue;
1246
+ }
1247
+ constant(mem.name, a, trim(b), mem.v);
1248
+ }
1249
+ };
1250
+
1251
+ // ── E1: the default-exported object ──
1252
+ let owner = null;
1253
+ DEFAULT_OBJECT.lastIndex = 0;
1254
+ const dx = DEFAULT_OBJECT.exec(m);
1255
+ if (dx) {
1256
+ const open = dx.index + dx[0].length - 1;
1257
+ const close = matchPairQuiet(m, open, "{", "}");
1258
+ const top = close > open ? objectMembers(m, open, close).map(([a, b]) => ({ a, b, mem: memberOf(m, a, b) })).filter((x) => x.mem) : [];
1259
+ const shaped = top.some(
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
+ );
1262
+ if (shaped) {
1263
+ const nameMem = top.find((x) => x.mem.type === "val" && x.mem.name === "name");
1264
+ const named = nameMem ? /^\s*(['"`])([A-Za-z_$][\w$]*)\1/.exec(src.slice(nameMem.mem.v, nameMem.mem.v + 120)) : null;
1265
+ const oname = named ? named[2] : ownerStem(rel);
1266
+ const bases = [];
1267
+ for (const x of top) {
1268
+ if (x.mem.name === "mixins" && x.mem.type === "arr")
1269
+ for (const w of m.slice(x.mem.open + 1, x.mem.close).split(",")) if (IDENT_RE.test(w.trim())) bases.push(w.trim());
1270
+ if (x.mem.name === "extends" && x.mem.type === "val") {
1271
+ const w = m.slice(x.mem.v, x.b).trim();
1272
+ if (IDENT_RE.test(w)) bases.push(w);
1273
+ }
1274
+ }
1275
+ owner = add({ name: oname, qname: oname, kind: "class", from: dx.index, to: close, exported: true, ...(bases.length ? { bases: bases.slice(0, 8) } : {}) });
1276
+ const method = (name, from, to, paren) => {
1277
+ if (paren != null) defParens.add(paren);
1278
+ add({ name, qname: `${oname}.${name}`, kind: "method", from, to, exported: false });
1279
+ };
1280
+ const section = (open2, close2) => {
1281
+ for (const [a, b] of objectMembers(m, open2, close2)) {
1282
+ const mem = memberOf(m, a, b);
1283
+ if (!mem) continue;
1284
+ if (mem.type === "fn") method(mem.name, a, trim(b), mem.paren);
1285
+ else if (mem.type === "obj" && mem.close > mem.open) {
1286
+ // `label: { get() {…}, set(v) {…} }` and a watcher's `{ handler() {…} }`
1287
+ // are ONE member whose behaviour sits in its inner functions.
1288
+ const inner = objectMembers(m, mem.open, mem.close).map(([p, q]) => memberOf(m, p, q)).filter(Boolean);
1289
+ if (inner.some((i) => i.type === "fn" && /^(get|set|handler)$/.test(i.name))) {
1290
+ for (const i of inner) if (i.type === "fn" && i.paren != null) defParens.add(i.paren);
1291
+ method(mem.name, a, trim(b), null);
1292
+ }
1293
+ }
1294
+ }
1295
+ };
1296
+ for (const x of top) {
1297
+ if (x.mem.type === "fn") method(x.mem.name, x.a, trim(x.b), x.mem.paren);
1298
+ else if (OBJ_SECTIONS.has(x.mem.name) && x.mem.type === "obj" && x.mem.close > x.mem.open) section(x.mem.open, x.mem.close);
1299
+ else if (OBJ_SECTIONS.has(x.mem.name) && x.mem.type === "short") {
1300
+ // `const mutations = { … }; export default { mutations }` — the section
1301
+ // is declared above and named here.
1302
+ const d = declOf(x.mem.name);
1303
+ if (d && m[d.v] === "{") section(d.v, matchPairQuiet(m, d.v, "{", "}"));
1304
+ }
1305
+ }
1306
+ } else if (close > open) constantsOf(open, close);
1307
+ }
1308
+
1309
+ // ── E2: exported constants ──
1310
+ const EXPORT_CONST = /^[ \t]*export[ \t]+(?:const|let|var)[ \t]+([A-Za-z_$][\w$]*)[ \t]*(?::[^=\n]+)?=[ \t]*/gm;
1311
+ let x;
1312
+ // A Svelte `export let` is a PROP the parent passes in, not a constant.
1313
+ while (!(sfc && sfc.svelte) && (x = EXPORT_CONST.exec(m))) {
1314
+ const v = x.index + x[0].length;
1315
+ if (fnValueAt(m, v)) continue;
1316
+ constant(x[1], x.index, valueEnd(m, v), v);
1317
+ }
1318
+ const EXPORTS_DOT = /^[ \t]*(?:module[ \t]*\.[ \t]*)?exports[ \t]*\.[ \t]*([A-Za-z_$][\w$]*)[ \t]*=[ \t]*/gm;
1319
+ while ((x = EXPORTS_DOT.exec(m))) {
1320
+ const v = x.index + x[0].length;
1321
+ if (fnValueAt(m, v) || /^[A-Za-z_$][\w$]*[ \t]*[;\n]/.test(m.slice(v, v + 80))) continue;
1322
+ constant(x[1], x.index, valueEnd(m, v), v);
1323
+ }
1324
+ const MODULE_OBJECT = /^[ \t]*module[ \t]*\.[ \t]*exports[ \t]*=[ \t]*\{/gm;
1325
+ while ((x = MODULE_OBJECT.exec(m))) {
1326
+ const open = x.index + x[0].length - 1;
1327
+ const close = matchPairQuiet(m, open, "{", "}");
1328
+ if (close > open) constantsOf(open, close);
1329
+ }
1330
+
1331
+ // ── E3: a component with no object to name ──
1332
+ // `<script setup>`, a Svelte instance script, or no script at all: ONE class
1333
+ // symbol, so the component exists in the map, the names and the hints.
1334
+ if (sfc && !owner && (sfc.setup || sfc.svelte || !sfc.blocks.length)) {
1335
+ const from = sfc.blocks.length ? sfc.blocks[0][0] : 0;
1336
+ const to = sfc.blocks.length ? Math.max(from, sfc.blocks[sfc.blocks.length - 1][1] - 1) : Math.max(0, src.length - 1);
1337
+ const name = ownerStem(rel);
1338
+ // NAME-ONLY: it owns no line. `<script setup>` code is module scope, and
1339
+ // an alias declared there (`const store = new OrderStore()`) must stay
1340
+ // visible to every function below it, exactly as it was before W5b.
1341
+ if (!taken.has(name)) add({ name, qname: name, kind: "class", from, to, exported: true, nameOnly: true });
1342
+ }
1343
+ return { defs, defParens };
1344
+ }
1345
+
1064
1346
  // ── declarations: Python (indentation) ──────────────────────────────────────
1065
1347
  function pyDefs(c) {
1066
1348
  const { src, m, starts, strAt } = c;
@@ -2015,7 +2297,13 @@ function finalize(c, lang, defs, calls, imports, exportsSet, extractor) {
2015
2297
 
2016
2298
  const owner = new Int32Array(nLines + 2).fill(-1);
2017
2299
  const order = defs.map((_, i) => i).sort((a, b) => defs[b].e - defs[b].s - (defs[a].e - defs[a].s));
2018
- for (const i of order) for (let L = defs[i].s; L <= defs[i].e && L <= nLines; L++) owner[L] = i;
2300
+ // W5b (E2): a `const` is a NAME, not a body. A call inside its value (`axios.create(…)`)
2301
+ // stays with whatever holds the constant — the module, most often — so a
2302
+ // constant symbol always carries `calls: []`.
2303
+ for (const i of order) {
2304
+ if (defs[i].kind === "const" || defs[i].nameOnly) continue;
2305
+ for (let L = defs[i].s; L <= defs[i].e && L <= nLines; L++) owner[L] = i;
2306
+ }
2019
2307
 
2020
2308
  // W2 (G2): instance aliases, scoped. `classAt(line)` is the innermost class
2021
2309
  // holding a line; `ownerOf(line)` the innermost symbol (−1 = the module).
@@ -2258,6 +2546,7 @@ function context(item) {
2258
2546
  function sfcSource(src) {
2259
2547
  const out = src.split("");
2260
2548
  let lang = "js";
2549
+ let setup = false;
2261
2550
  const keep = [];
2262
2551
  const lower = src.toLowerCase();
2263
2552
  const RE = /<script\b([^>]*)>/gi;
@@ -2267,6 +2556,9 @@ function sfcSource(src) {
2267
2556
  const close = lower.indexOf("</script>", open);
2268
2557
  const end = close < 0 ? src.length : close;
2269
2558
  if (/lang[ \t]*=[ \t]*["']?(ts|typescript)/i.test(x[1])) lang = "ts";
2559
+ // W5b (E3): `<script setup>` has no object to name, so the component is
2560
+ // named from its file instead.
2561
+ if (/(^|\s)setup(\s|=|$)/i.test(x[1])) setup = true;
2270
2562
  keep.push([open, end]);
2271
2563
  RE.lastIndex = end;
2272
2564
  }
@@ -2279,14 +2571,16 @@ function sfcSource(src) {
2279
2571
  at = b;
2280
2572
  }
2281
2573
  blank(at, src.length);
2282
- return { src: out.join(""), lang };
2574
+ return { src: out.join(""), lang, blocks: keep, setup };
2283
2575
  }
2284
2576
 
2285
2577
  function extractOne(item, borrowed) {
2286
2578
  let { lang, src } = item;
2287
2579
  resetCoverage();
2580
+ let sfc = null;
2288
2581
  if (lang === "vue" || lang === "svelte") {
2289
2582
  const s = sfcSource(src);
2583
+ sfc = { blocks: s.blocks, setup: s.setup, svelte: lang === "svelte" };
2290
2584
  src = s.src;
2291
2585
  lang = s.lang;
2292
2586
  item = { ...item, src, lang };
@@ -2312,6 +2606,10 @@ function extractOne(item, borrowed) {
2312
2606
  const { defs: rdefs, regParens, exportsSet: hexports } = braceDefs(c, lang);
2313
2607
  resetCoverage();
2314
2608
  for (const d of rdefs) if (d.kind === "route") defs.push({ ...d, s: lineOf(c.starts, d.from), e: lineOf(c.starts, Math.max(d.from, d.to)) });
2609
+ // W5b: the walker records a method only inside a class. An object's
2610
+ // members, the constants and an SFC's component come from the same pass
2611
+ // the heuristic rung runs, so both rungs name the same symbols.
2612
+ if (lang === "js" || lang === "ts") defs.push(...objectDefs(c, defs, sfc).defs);
2315
2613
  for (const p of regParens) reg.add(lineOf(c.starts, p));
2316
2614
  for (const cc of calls) if (reg.has(cc.line) && ARGS_WORTH.test(cc.name)) cc.reg = true;
2317
2615
  defs.sort((a, b) => a.from - b.from);
@@ -2338,6 +2636,13 @@ function extractOne(item, borrowed) {
2338
2636
  return finalize(c, lang, defs, scanCalls(c, defParens, regParens), importsFor(lang, c.src, c.m, c.starts), null, HEURISTIC);
2339
2637
  }
2340
2638
  const { defs, defParens, regParens, exportsSet } = braceDefs(c, lang);
2639
+ if (lang === "js" || lang === "ts") {
2640
+ const o = objectDefs(c, defs, sfc);
2641
+ defs.push(...o.defs);
2642
+ // APPENDED, never sorted in: a file with no object keeps every symbol in
2643
+ // the order 1.9.0 wrote it, so its record is the same bytes.
2644
+ for (const p of o.defParens) defParens.add(p);
2645
+ }
2341
2646
  braceDecorators(c, lang, defs);
2342
2647
  return finalize(c, lang, defs, scanCalls(c, defParens, regParens), importsFor(lang, c.src, c.m, c.starts), exportsSet, HEURISTIC);
2343
2648
  }