@event4u/agent-config 14.20.0 → 14.22.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +441 -0
- package/CREDITS.md +37 -0
- package/README.md +1 -1
- package/agents/templates/.ai-council.yml.example +10 -0
- package/dist/agent-src/commands/analyze/inbox.md +381 -6
- package/dist/agent-src/commands/analyze/{reference-repo.md → repo.md} +251 -34
- package/dist/agent-src/commands/analyze/roadmap-repos.md +129 -0
- package/dist/agent-src/commands/analyze.md +12 -7
- package/dist/agent-src/commands/feature/plan.md +55 -16
- package/dist/agent-src/commands/feature/refactor.md +8 -3
- package/dist/agent-src/commands/humanize.md +48 -4
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
- package/dist/agent-src/contexts/execution/contract-decision-sheet.md +22 -1
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +4 -1
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +35 -2
- package/dist/agent-src/contexts/execution/terminal-states.md +4 -1
- package/dist/agent-src/rules/evaluator-independence.md +4 -4
- package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
- package/dist/agent-src/rules/external-reference-deep-dive.md +2 -2
- package/dist/agent-src/rules/token-efficiency.md +26 -3
- package/dist/agent-src/scripts/_cli_entry.ts +51 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
- package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
- package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/dist/agent-src/scripts/stubs_due.ts +4 -18
- package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/dist/agent-src/skills/code-intelligence/SKILL.md +25 -0
- package/dist/agent-src/skills/competitive-positioning/SKILL.md +2 -2
- package/dist/agent-src/skills/humanizer/references/anti-aiisms.md +27 -11
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +1 -1
- package/dist/agent-src/skills/markitdown/SKILL.md +1 -1
- package/dist/agent-src/skills/repomix-packer/SKILL.md +1 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
- package/dist/agent-src/skills/technical-specification/SKILL.md +4 -0
- package/dist/agent-src/templates/agent-settings.md +0 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/features.md +5 -0
- package/dist/agent-src/templates/roadmaps.md +15 -0
- package/dist/cli-delegate/_cli_entry.js +8 -0
- package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
- package/dist/cli-delegate/archival_sweep.js +8 -0
- package/dist/cli-delegate/archive_completed_roadmaps.js +494 -0
- package/dist/cli-delegate/check_update_banner.js +923 -0
- package/dist/cli-delegate/{chunk-KPBWFOPZ.js → chunk-2FTMENJL.js} +194 -37
- package/dist/cli-delegate/{chunk-PI2GU3DP.js → chunk-2TAQ2K4V.js} +4 -1
- package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
- package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
- package/dist/cli-delegate/{chunk-C6IPZRDA.js → chunk-BIXEDQPZ.js} +7 -1
- package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
- package/dist/cli-delegate/{chunk-NUMQHLS5.js → chunk-J3RFNKG4.js} +21 -2
- package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
- package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
- package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
- package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-TGIPZBCW.js} +1 -1
- package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
- package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
- package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
- package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
- package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
- package/dist/cli-delegate/cmd_conformance.js +19 -5
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +27 -3
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/cli-delegate/dashboard_mode.js +16 -0
- package/dist/cli-delegate/gate_budget.js +24 -0
- package/dist/cli-delegate/gate_execute.js +25 -0
- package/dist/cli-delegate/guarded_baseline.js +18 -0
- package/dist/cli-delegate/resume_probe.js +22 -0
- package/dist/cli-delegate/roadmap_gates.js +638 -0
- package/dist/cli-delegate/roadmap_set_step.js +173 -0
- package/dist/cli-delegate/stub_queue.js +47 -0
- package/dist/cli-delegate/stubs_due.js +38 -0
- package/dist/cli-delegate/ticket_bundles.js +59 -0
- package/dist/cli-delegate/update_roadmap_progress.js +60 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +60 -29
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +7 -6
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +6 -5
- package/dist/hooks/dispatch.js +1522 -1008
- package/dist/install/claudeLayerCarriage.js +237 -0
- package/dist/install/claudePathsPlan.js +79 -0
- package/dist/install/hostLayerFingerprint.js +1 -1
- package/dist/install/install.mjs +10 -1
- package/dist/install/partitionEligibility.js +231 -117
- package/dist/install/ruleLayerPartition.js +10 -7
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +39376 -38902
- package/dist/scripts/ai_council/content_ceiling.js +63 -0
- package/dist/scripts/ai_council/seat_policy.js +106 -0
- package/dist/scripts/ai_council/transport_resolver.js +21 -0
- package/dist/scripts/condense.js +32 -25
- package/docs/MIGRATION.md +49 -1
- package/docs/contracts/settings-classes.md +1 -1
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
- package/llms.txt +14 -14
- package/package.json +7 -4
- package/src/agent-src/scripts/_cli_entry.ts +51 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
- package/src/agent-src/scripts/roadmap_gates.ts +4 -20
- package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/src/agent-src/scripts/stubs_due.ts +4 -18
- package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/src/config/continuity-surface.json +509 -0
- package/src/config/estate-count-budget.json +1 -1
- package/src/config/gate-coverage.yml +29 -8
- package/src/config/gate-violation-baselines.json +8 -8
- package/src/config/gitignore-block.txt +19 -0
- package/src/config/host-injection-effect.json +73 -0
- package/src/config/packed-binary-manifest.json +33 -0
- package/src/config/preamble-payload-budget.json +112 -105
- package/src/config/publish-surface.json +4 -2
- package/src/config/release-gate-locality.yml +4 -3
- package/src/install/claudeLayerCarriage.ts +273 -0
- package/src/install/claudePathsPlan.ts +76 -0
- package/src/install/hostLayerFingerprint.ts +1 -1
- package/src/install/partitionEligibility.ts +261 -127
- package/src/install/ruleLayerPartition.ts +19 -23
- package/src/scripts/_dispatch.bash +77 -27
- package/src/scripts/_lib/auto_dispatch.ts +2 -2
- package/src/scripts/_lib/blocked_by_marker.ts +189 -0
- package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
- package/src/scripts/_lib/continuity_surface.ts +296 -0
- package/src/scripts/_lib/corpus_manifest.ts +26 -9
- package/src/scripts/_lib/hook_settings.ts +1 -1
- package/src/scripts/_lib/host_capability.ts +38 -1
- package/src/scripts/_lib/injection_effect.ts +151 -0
- package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
- package/src/scripts/_lib/packed_binary_predicate.ts +225 -0
- package/src/scripts/_lib/pin_resolver.ts +13 -0
- package/src/scripts/_lib/regression_neighbourhood.ts +1 -1
- package/src/scripts/_lib/release_highlights.ts +19 -211
- package/src/scripts/_lib/release_material.ts +27 -77
- package/src/scripts/_lib/retired_status.ts +74 -0
- package/src/scripts/_lib/skill_catalogue.ts +156 -21
- package/src/scripts/_lib/structured_ask.ts +117 -0
- package/src/scripts/_lib/subagent_capsule.ts +117 -8
- package/src/scripts/_lib/tty_prompt.ts +76 -0
- package/src/scripts/ai_council/api_transport.ts +168 -0
- package/src/scripts/ai_council/clients.ts +58 -63
- package/src/scripts/ai_council/config.ts +13 -0
- package/src/scripts/ai_council/content_ceiling.ts +79 -0
- package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
- package/src/scripts/ai_council/jury_aggregate.ts +178 -0
- package/src/scripts/ai_council/prompts.ts +9 -0
- package/src/scripts/ai_council/seat_policy.ts +137 -0
- package/src/scripts/ai_council/status_surface.ts +79 -0
- package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
- package/src/scripts/ai_council/transport_resolver.ts +59 -1
- package/src/scripts/ai_tells_rules.ts +446 -5
- package/src/scripts/ask_block_census.ts +499 -0
- package/src/scripts/bench_humanizer_eval.ts +264 -40
- package/src/scripts/bench_prose_tells_fp.ts +198 -0
- package/src/scripts/capture_skill_catalogue.ts +24 -11
- package/src/scripts/check_branch_work_committed.ts +234 -0
- package/src/scripts/check_candidate_lines.ts +499 -0
- package/src/scripts/check_continuity_surface.ts +475 -0
- package/src/scripts/check_dependency_floors.ts +13 -6
- package/src/scripts/check_estate_count.ts +40 -79
- package/src/scripts/check_pack_size.ts +263 -24
- package/src/scripts/check_release_highlights.ts +0 -157
- package/src/scripts/check_roadmap_trackable.ts +19 -16
- package/src/scripts/check_rule_layer_partition.ts +54 -34
- package/src/scripts/check_single_delivery.ts +97 -31
- package/src/scripts/check_update_banner.ts +13 -0
- package/src/scripts/code_graph/build.ts +138 -15
- package/src/scripts/code_graph/cli.ts +76 -8
- package/src/scripts/code_graph/extract.ts +11 -0
- package/src/scripts/code_graph/grammar_probe.ts +43 -0
- package/src/scripts/code_graph/loader.ts +88 -14
- package/src/scripts/code_graph/query.ts +121 -11
- package/src/scripts/code_graph/resolution_tiers.ts +238 -0
- package/src/scripts/code_graph/sqlite_store.ts +247 -6
- package/src/scripts/code_graph/types.ts +66 -1
- package/src/scripts/code_graph/validate.ts +23 -0
- package/src/scripts/condense.ts +34 -22
- package/src/scripts/corpus_manifest.ts +5 -5
- package/src/scripts/council_cli.ts +29 -39
- package/src/scripts/count_intent_disagreement.ts +426 -0
- package/src/scripts/detect_ai_tells.ts +142 -20
- package/src/scripts/handoff_context_hook.ts +79 -0
- package/src/scripts/harvest_reference_tokens.ts +698 -0
- package/src/scripts/hook_effect_doctor.ts +82 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +94 -12
- package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
- package/src/scripts/hooks/code_graph_context_hook.ts +212 -0
- package/src/scripts/hooks/concern_registry.ts +6 -2
- package/src/scripts/hooks/delegation_nudge_hook.ts +45 -1
- package/src/scripts/hooks/host_semantics.ts +1 -1
- package/src/scripts/hooks/one_question_per_ask_hook.ts +191 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
- package/src/scripts/hooks/rule_inject_hook.ts +1 -1
- package/src/scripts/hooks/skill_route_hook.ts +11 -4
- package/src/scripts/inbox_source_census.ts +322 -0
- package/src/scripts/install-hooks.sh +134 -0
- package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
- package/src/scripts/lint_plan_risk_register.ts +13 -17
- package/src/scripts/lint_roadmap_blockers.ts +51 -1
- package/src/scripts/mcp_server/suggest_skill.ts +72 -0
- package/src/scripts/mcp_server/tools.ts +2 -56
- package/src/scripts/media/lib/adapter-contract.md +7 -2
- package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
- package/src/scripts/migrate_command_suggestions.ts +1 -1
- package/src/scripts/probe_unblocked_ask.ts +135 -2
- package/src/scripts/rdp_candidates_delta.ts +276 -0
- package/src/scripts/rdp_quality_eval.ts +217 -19
- package/src/scripts/release.ts +26 -71
- package/src/scripts/release_drill.ts +6 -10
- package/src/scripts/release_env.ts +1 -0
- package/src/scripts/release_mix_taxonomy.json +3 -2
- package/src/scripts/release_publication.ts +34 -29
- package/src/scripts/report_host_injection_effect.ts +189 -0
- package/src/scripts/report_layer_overlap.ts +41 -0
- package/src/scripts/report_skill_menu_census.ts +341 -0
- package/src/scripts/self_review_gate.ts +409 -15
- package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
- package/src/vendor/grammars/README.md +61 -0
- package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
- package/src/scripts/_lib/carrier_status.ts +0 -118
- package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
- package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
|
@@ -16,6 +16,17 @@ import * as path from 'node:path';
|
|
|
16
16
|
|
|
17
17
|
import { write_atomic } from '../_lib/fs_atomic.js';
|
|
18
18
|
import { extractFile, type FileExtract, type RawEdge } from './extract.js';
|
|
19
|
+
import {
|
|
20
|
+
ALIAS_VIA,
|
|
21
|
+
aliasRulesFrom,
|
|
22
|
+
EMPTY_RESOLUTION_CONFIG,
|
|
23
|
+
parseJsonc,
|
|
24
|
+
PSR4_VIA,
|
|
25
|
+
psr4RulesFrom,
|
|
26
|
+
type ResolutionConfig,
|
|
27
|
+
resolveAlias,
|
|
28
|
+
resolvePsr4,
|
|
29
|
+
} from './resolution_tiers.js';
|
|
19
30
|
import { emitSqliteTwin } from './sqlite_store.js';
|
|
20
31
|
import {
|
|
21
32
|
type CodeEdge,
|
|
@@ -24,6 +35,7 @@ import {
|
|
|
24
35
|
EXPECTED_GRAMMAR_ABI,
|
|
25
36
|
EXT_LANG,
|
|
26
37
|
type Lang,
|
|
38
|
+
type ResolvedVia,
|
|
27
39
|
SCHEMA_VERSION,
|
|
28
40
|
} from './types.js';
|
|
29
41
|
|
|
@@ -145,7 +157,9 @@ function satisfies(kind: string | undefined, want: Capability): boolean {
|
|
|
145
157
|
*/
|
|
146
158
|
interface FileScope {
|
|
147
159
|
locals: Map<string, string[]>;
|
|
148
|
-
|
|
160
|
+
/** `via` records WHICH tier produced the binding, so an edge built from it
|
|
161
|
+
* reports the mechanism instead of assuming `import-specifier`. */
|
|
162
|
+
imports: Map<string, { target: string; exact: boolean; via: ResolvedVia }>;
|
|
149
163
|
}
|
|
150
164
|
|
|
151
165
|
const REL_EXTS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];
|
|
@@ -199,11 +213,40 @@ function push(map: Map<string, string[]>, k: string, v: string): void {
|
|
|
199
213
|
} else map.set(k, [v]);
|
|
200
214
|
}
|
|
201
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Read the project's declared resolution tiers from `root` (2.3).
|
|
218
|
+
*
|
|
219
|
+
* IO lives here rather than in `buildGraph`, which stays pure so identical
|
|
220
|
+
* source plus identical config yields identical bytes. A missing or
|
|
221
|
+
* unparseable config is not an error — it degrades to no tiers, which is the
|
|
222
|
+
* behaviour every build had before this existed.
|
|
223
|
+
*
|
|
224
|
+
* `tsconfig.json` is read as JSONC: it is JSONC by convention and this
|
|
225
|
+
* repository's own carries `//` comments, so `JSON.parse` on it throws.
|
|
226
|
+
*/
|
|
227
|
+
export function readResolutionConfig(root: string): ResolutionConfig {
|
|
228
|
+
const read = (name: string): unknown | null => {
|
|
229
|
+
try {
|
|
230
|
+
return parseJsonc(fs.readFileSync(path.join(root, name), 'utf-8'));
|
|
231
|
+
} catch {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
return {
|
|
236
|
+
aliases: aliasRulesFrom(read('tsconfig.json')),
|
|
237
|
+
psr4: psr4RulesFrom(read('composer.json')),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
202
241
|
/**
|
|
203
242
|
* Pure, deterministic graph build over an in-memory file set. Exposed for
|
|
204
243
|
* tests (no IO). `buildFromRepo` is the IO wrapper.
|
|
205
244
|
*/
|
|
206
|
-
export function buildGraph(
|
|
245
|
+
export function buildGraph(
|
|
246
|
+
files: readonly SourceFile[],
|
|
247
|
+
extracts: readonly FileExtract[],
|
|
248
|
+
resolution: ResolutionConfig = EMPTY_RESOLUTION_CONFIG,
|
|
249
|
+
): CodeGraph {
|
|
207
250
|
const nodes = extracts.flatMap((e) => e.nodes);
|
|
208
251
|
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
209
252
|
|
|
@@ -277,7 +320,7 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
277
320
|
for (const ex of extracts) {
|
|
278
321
|
const locals = new Map<string, string[]>();
|
|
279
322
|
for (const n of ex.nodes) if (n.kind !== 'file') push(locals, n.label, n.id);
|
|
280
|
-
const imports = new Map<string, { target: string; exact: boolean }>();
|
|
323
|
+
const imports = new Map<string, { target: string; exact: boolean; via: ResolvedVia }>();
|
|
281
324
|
for (const r of ex.rawEdges) {
|
|
282
325
|
if (r.relation !== 'imports') continue;
|
|
283
326
|
const local = r.localName ?? r.targetName;
|
|
@@ -285,29 +328,58 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
285
328
|
const spec = r.moduleSpecifier;
|
|
286
329
|
if (spec === undefined) {
|
|
287
330
|
// PHP `use A\B\C` — a fully-qualified NAME, not a module path.
|
|
288
|
-
//
|
|
289
|
-
//
|
|
331
|
+
//
|
|
332
|
+
// TIER 1 (2.3): composer PSR-4 maps the NAMESPACE to a
|
|
333
|
+
// directory, so the class file is named outright. That is a
|
|
334
|
+
// declared mapping, hence `exact: true` — and it is the only
|
|
335
|
+
// rung that can tell two same-named classes in different
|
|
336
|
+
// namespaces apart.
|
|
337
|
+
const fq = r.fqName ?? r.targetName;
|
|
338
|
+
const psr4File = resolvePsr4(fq, resolution, fileIds);
|
|
339
|
+
if (psr4File !== null) {
|
|
340
|
+
const member = `${psr4File}#${r.targetName}`;
|
|
341
|
+
imports.set(local, {
|
|
342
|
+
target: nodeKind.has(member) ? member : psr4File,
|
|
343
|
+
exact: true,
|
|
344
|
+
via: PSR4_VIA,
|
|
345
|
+
});
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
// TIER 2: base name, namespace discarded. A lookup, not a
|
|
349
|
+
// syntactic fact: `exact: false`.
|
|
290
350
|
const hit =
|
|
291
351
|
sym.classByName.get(k(ex.lang, r.targetName)) ??
|
|
292
352
|
(sym.byName.get(k(ex.lang, r.targetName)) ?? []).find((id) => id.includes('#') && !id.includes('::'));
|
|
293
|
-
if (hit) imports.set(local, { target: hit, exact: false });
|
|
353
|
+
if (hit) imports.set(local, { target: hit, exact: false, via: 'name-lookup' });
|
|
294
354
|
continue;
|
|
295
355
|
}
|
|
296
|
-
|
|
356
|
+
// TIER 0: a relative specifier — the file says where it points.
|
|
357
|
+
// TIER 1 (2.3): tsconfig `paths`. `@shared/x` IS `src/shared/x`
|
|
358
|
+
// because the project declares it, so this is a fact and not a
|
|
359
|
+
// guess. Without it the specifier is non-relative, `resolveSpecifier`
|
|
360
|
+
// returns null, and the import binds to `external:@shared/x` — a
|
|
361
|
+
// real module, correctly named, and the wrong one.
|
|
362
|
+
const relFile = resolveSpecifier(ex.file, spec, fileIds);
|
|
363
|
+
const aliasFile = relFile === null ? resolveAlias(spec, resolution, fileIds) : null;
|
|
364
|
+
const file = relFile ?? aliasFile;
|
|
297
365
|
if (file === null) {
|
|
298
366
|
// Bare (`node:path`, `zod`) or relative-but-out-of-root. Either
|
|
299
367
|
// way the module is real and NAMED, and it is not this
|
|
300
368
|
// repository's same-named symbol. `import * as path from
|
|
301
369
|
// 'node:path'` ends here, which is the whole of 1.1.
|
|
302
370
|
const named = r.importKind === 'named';
|
|
303
|
-
imports.set(local, {
|
|
371
|
+
imports.set(local, {
|
|
372
|
+
target: named ? `external:${spec}#${r.targetName}` : `external:${spec}`,
|
|
373
|
+
exact: true,
|
|
374
|
+
via: 'import-specifier',
|
|
375
|
+
});
|
|
304
376
|
continue;
|
|
305
377
|
}
|
|
306
378
|
// A namespace or default import binds the MODULE, never a
|
|
307
379
|
// same-named export inside it.
|
|
308
380
|
const member = `${file}#${r.targetName}`;
|
|
309
381
|
const target = r.importKind === 'named' && nodeKind.has(member) ? member : file;
|
|
310
|
-
imports.set(local, { target, exact: true });
|
|
382
|
+
imports.set(local, { target, exact: true, via: aliasFile === null ? 'import-specifier' : ALIAS_VIA });
|
|
311
383
|
}
|
|
312
384
|
scopes.set(ex.file, { locals, imports });
|
|
313
385
|
}
|
|
@@ -327,13 +399,23 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
327
399
|
want: Capability,
|
|
328
400
|
lang: Lang,
|
|
329
401
|
scopeOnly = false,
|
|
330
|
-
): { target: string; confidence: EdgeConfidence } | null => {
|
|
402
|
+
): { target: string; confidence: EdgeConfidence; via: ResolvedVia } | null => {
|
|
331
403
|
const scope = scopes.get(file) ?? emptyScope;
|
|
404
|
+
// The ladder's rungs ARE the mechanism taxonomy, so `via` is read off
|
|
405
|
+
// the branch that succeeded rather than inferred afterwards.
|
|
332
406
|
for (const id of scope.locals.get(name) ?? [])
|
|
333
|
-
if (satisfies(nodeKind.get(id), want))
|
|
407
|
+
if (satisfies(nodeKind.get(id), want))
|
|
408
|
+
return { target: id, confidence: 'EXTRACTED', via: 'same-file' };
|
|
334
409
|
const bound = scope.imports.get(name);
|
|
335
410
|
if (bound && satisfies(nodeKind.get(bound.target), want))
|
|
336
|
-
return {
|
|
411
|
+
return {
|
|
412
|
+
target: bound.target,
|
|
413
|
+
confidence: bound.exact ? 'EXTRACTED' : 'INFERRED',
|
|
414
|
+
// The binding knows how it was made — a tsconfig alias and a
|
|
415
|
+
// PSR-4 namespace are not import specifiers and must not be
|
|
416
|
+
// reported as ones.
|
|
417
|
+
via: bound.via,
|
|
418
|
+
};
|
|
337
419
|
if (scopeOnly) return null;
|
|
338
420
|
const wide =
|
|
339
421
|
want === 'constructible'
|
|
@@ -344,10 +426,19 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
344
426
|
// Reached only by matching a name across the whole repository, with no
|
|
345
427
|
// binding in this file to justify it. That is not a syntactic fact and
|
|
346
428
|
// no longer claims to be one.
|
|
347
|
-
if (wide) return { target: wide, confidence: 'INFERRED' };
|
|
429
|
+
if (wide) return { target: wide, confidence: 'INFERRED', via: 'name-lookup' };
|
|
348
430
|
return null;
|
|
349
431
|
};
|
|
350
432
|
|
|
433
|
+
/**
|
|
434
|
+
* The mechanism for an edge whose target did not resolve.
|
|
435
|
+
*
|
|
436
|
+
* A `symbol:` pseudo-target is the residue of a name lookup that found
|
|
437
|
+
* nothing, so `name-lookup` is what actually happened. `confidence` already
|
|
438
|
+
* carries whether it resolved; this field carries what was tried.
|
|
439
|
+
*/
|
|
440
|
+
const UNRESOLVED_VIA: ResolvedVia = 'name-lookup';
|
|
441
|
+
|
|
351
442
|
// Deduped the same way emitted edges are, so the count is comparable to
|
|
352
443
|
// `edges.length` rather than being a raw call-site tally that reads as a
|
|
353
444
|
// larger loss than the graph actually took.
|
|
@@ -372,6 +463,8 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
372
463
|
target: hit?.target ?? `symbol:${inh.parentName}`,
|
|
373
464
|
relation: 'inherits',
|
|
374
465
|
confidence: hit?.confidence ?? 'EXTRACTED',
|
|
466
|
+
resolved_via: hit?.via ?? UNRESOLVED_VIA,
|
|
467
|
+
provider: 'native',
|
|
375
468
|
});
|
|
376
469
|
}
|
|
377
470
|
for (const r of ex.rawEdges) resolveRawEdge(r, ex);
|
|
@@ -386,6 +479,11 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
386
479
|
target: nodeIds.has(target) ? target : `symbol:${r.targetName}`,
|
|
387
480
|
relation: 'member',
|
|
388
481
|
confidence: 'EXTRACTED',
|
|
482
|
+
// A member id is DERIVED from its own source node's id, so the
|
|
483
|
+
// declaration is in the same file by construction — no lookup
|
|
484
|
+
// of any kind happens here.
|
|
485
|
+
resolved_via: nodeIds.has(target) ? 'same-file' : UNRESOLVED_VIA,
|
|
486
|
+
provider: 'native',
|
|
389
487
|
});
|
|
390
488
|
return;
|
|
391
489
|
}
|
|
@@ -400,6 +498,8 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
400
498
|
target: bound?.target ?? `symbol:${r.targetName}`,
|
|
401
499
|
relation: 'imports',
|
|
402
500
|
confidence: bound ? (bound.exact ? 'EXTRACTED' : 'INFERRED') : 'EXTRACTED',
|
|
501
|
+
resolved_via: bound ? bound.via : UNRESOLVED_VIA,
|
|
502
|
+
provider: 'native',
|
|
403
503
|
});
|
|
404
504
|
return;
|
|
405
505
|
}
|
|
@@ -416,6 +516,8 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
416
516
|
target: hit?.target ?? `symbol:${r.targetName}`,
|
|
417
517
|
relation: 'uses',
|
|
418
518
|
confidence: hit?.confidence ?? 'EXTRACTED',
|
|
519
|
+
resolved_via: hit?.via ?? UNRESOLVED_VIA,
|
|
520
|
+
provider: 'native',
|
|
419
521
|
});
|
|
420
522
|
return;
|
|
421
523
|
}
|
|
@@ -433,13 +535,25 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
433
535
|
// EXTRACTED is a claim about a SPECIFIC in-repo node reached by
|
|
434
536
|
// name alone.
|
|
435
537
|
confidence: hit?.confidence ?? 'EXTRACTED',
|
|
538
|
+
resolved_via: hit?.via ?? UNRESOLVED_VIA,
|
|
539
|
+
provider: 'native',
|
|
436
540
|
});
|
|
437
541
|
return;
|
|
438
542
|
}
|
|
439
543
|
if (r.callStyle === 'this' || r.callStyle === 'self' || r.callStyle === 'static' || r.callStyle === 'parent') {
|
|
440
544
|
const hit = r.enclosingClassId ? resolveHierMethod(r.enclosingClassId, r.targetName) : null;
|
|
441
545
|
if (hit) {
|
|
442
|
-
emit({
|
|
546
|
+
emit({
|
|
547
|
+
source: r.sourceId,
|
|
548
|
+
target: hit,
|
|
549
|
+
relation: 'calls',
|
|
550
|
+
confidence: 'INFERRED',
|
|
551
|
+
// Walked the class hierarchy, which is same-file only when
|
|
552
|
+
// the declaring class is; the honest mechanism for a
|
|
553
|
+
// hierarchy walk is the repo-wide method table.
|
|
554
|
+
resolved_via: 'name-lookup',
|
|
555
|
+
provider: 'native',
|
|
556
|
+
});
|
|
443
557
|
} else {
|
|
444
558
|
// enclosing class extends an out-of-repo base → honest AMBIGUOUS
|
|
445
559
|
const cands = methodCandidates(r.targetName, ex.lang);
|
|
@@ -448,6 +562,8 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
448
562
|
target: ambiguousTarget(cands, r.targetName),
|
|
449
563
|
relation: 'calls',
|
|
450
564
|
confidence: 'AMBIGUOUS',
|
|
565
|
+
resolved_via: 'dynamic',
|
|
566
|
+
provider: 'native',
|
|
451
567
|
// The enclosing class extends a base outside this
|
|
452
568
|
// repository, so the method is not findable HERE — a
|
|
453
569
|
// different cause from a call whose receiver is unknown,
|
|
@@ -486,6 +602,8 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
486
602
|
target: ambiguousTarget(cands, r.targetName),
|
|
487
603
|
relation: 'calls',
|
|
488
604
|
confidence: 'AMBIGUOUS',
|
|
605
|
+
resolved_via: 'dynamic',
|
|
606
|
+
provider: 'native',
|
|
489
607
|
// Receiver type unknown: `$obj->m()` or an unresolved `C::m()`.
|
|
490
608
|
// Resolving this needs type inference the extractor does not do,
|
|
491
609
|
// which is exactly what 1.2 has to decide about rather than assume.
|
|
@@ -583,6 +701,11 @@ export function serializeGraph(g: CodeGraph): string {
|
|
|
583
701
|
target: e.target,
|
|
584
702
|
relation: e.relation,
|
|
585
703
|
confidence: e.confidence,
|
|
704
|
+
// Unconditional, unlike the two optional fields below: these are
|
|
705
|
+
// required on every edge, so emitting them conditionally would make
|
|
706
|
+
// an absent field mean "not set" instead of being impossible.
|
|
707
|
+
resolved_via: e.resolved_via,
|
|
708
|
+
provider: e.provider,
|
|
586
709
|
};
|
|
587
710
|
// Ordered before `candidates` deliberately: the reason is the coarser
|
|
588
711
|
// axis and a reader scanning the serialized graph sees WHY before HOW
|
|
@@ -684,7 +807,7 @@ export async function buildFromRepo(
|
|
|
684
807
|
nextSidecar.files[f.path] = { hash, extract };
|
|
685
808
|
}
|
|
686
809
|
|
|
687
|
-
const graph = buildGraph(files, extracts);
|
|
810
|
+
const graph = buildGraph(files, extracts, readResolutionConfig(root));
|
|
688
811
|
const json = serializeGraph(graph);
|
|
689
812
|
if (cachePath) {
|
|
690
813
|
write_atomic(cachePath, json);
|
|
@@ -62,6 +62,8 @@ import {
|
|
|
62
62
|
query,
|
|
63
63
|
type QueryResult,
|
|
64
64
|
} from './query.js';
|
|
65
|
+
import { openGraphIndex, sqliteTwinPath } from './sqlite_store.js';
|
|
66
|
+
import type { CodeGraph } from './types.js';
|
|
65
67
|
import { validateGraph } from './validate.js';
|
|
66
68
|
|
|
67
69
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -130,12 +132,33 @@ async function cmdBuild(argv: string[]): Promise<number> {
|
|
|
130
132
|
`✅ code-graph ${update ? 'updated' : 'built'} — ${fileCount} files · ${graph.nodes.length} nodes · ${graph.edges.length} edges\n` +
|
|
131
133
|
` languages: ${graph.languages.join(', ') || '(none)'} · grammar ABI ${graph.grammar_abi}\n` +
|
|
132
134
|
` edges: EXTRACTED ${c.EXTRACTED} · INFERRED ${c.INFERRED} · AMBIGUOUS ${c.AMBIGUOUS}\n` +
|
|
135
|
+
` resolved_via: ${resolvedViaHistogram(graph)}\n` +
|
|
133
136
|
(update ? ` incremental: ${reExtracted} re-extracted · ${reused} reused\n` : '') +
|
|
134
137
|
(out ? ` cache: ${path.relative(REPO_ROOT, out)}\n` : ''),
|
|
135
138
|
);
|
|
136
139
|
return 0;
|
|
137
140
|
}
|
|
138
141
|
|
|
142
|
+
/**
|
|
143
|
+
* The `resolved_via` histogram, descending by count.
|
|
144
|
+
*
|
|
145
|
+
* Printed on every build because it is the number that says whether the graph
|
|
146
|
+
* is getting BETTER, and the confidence split does not: 2.3's whole target is
|
|
147
|
+
* moving edges off `name-lookup` onto a real binding, and a reader who cannot
|
|
148
|
+
* see the starting distribution cannot tell whether it moved. Only non-zero
|
|
149
|
+
* buckets appear — a row of zeroes for the four mechanisms nothing emits yet
|
|
150
|
+
* would be noise on every build.
|
|
151
|
+
*/
|
|
152
|
+
function resolvedViaHistogram(graph: CodeGraph): string {
|
|
153
|
+
const counts = new Map<string, number>();
|
|
154
|
+
for (const e of graph.edges) counts.set(e.resolved_via, (counts.get(e.resolved_via) ?? 0) + 1);
|
|
155
|
+
if (counts.size === 0) return '(no edges)';
|
|
156
|
+
return [...counts.entries()]
|
|
157
|
+
.sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1))
|
|
158
|
+
.map(([via, n]) => `${via} ${n}`)
|
|
159
|
+
.join(' · ');
|
|
160
|
+
}
|
|
161
|
+
|
|
139
162
|
function cmdValidate(argv: string[]): number {
|
|
140
163
|
const p = flag(argv, '--graph') ?? DEFAULT_CACHE;
|
|
141
164
|
if (!fs.existsSync(p)) {
|
|
@@ -150,13 +173,57 @@ function cmdValidate(argv: string[]): number {
|
|
|
150
173
|
return 1;
|
|
151
174
|
}
|
|
152
175
|
const r = validateGraph(parsed);
|
|
153
|
-
if (r.ok) {
|
|
154
|
-
process.
|
|
155
|
-
|
|
176
|
+
if (!r.ok) {
|
|
177
|
+
process.stderr.write(`❌ graph schema invalid (${r.errors.length}):\n`);
|
|
178
|
+
for (const e of r.errors) process.stderr.write(` ${e}\n`);
|
|
179
|
+
return 1;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Twin ⇔ JSON, by checksum (road-to-a-graph-that-is-shipped 2.1).
|
|
183
|
+
//
|
|
184
|
+
// The schema check above says the JSON is well-formed. It says nothing
|
|
185
|
+
// about the derived store that now ANSWERS most queries, and since 2.1 the
|
|
186
|
+
// twin is read as an index rather than replayed as a blob — so a twin that
|
|
187
|
+
// disagreed with the JSON would produce different answers rather than a
|
|
188
|
+
// parse error. The comparison is `source_checksum`, which is the JSON's own
|
|
189
|
+
// content-addressed cache key, so agreement here means the twin was emitted
|
|
190
|
+
// from THIS graph and not merely from a graph.
|
|
191
|
+
const twinState = twinChecksumState(p, (parsed as CodeGraph).source_checksum);
|
|
192
|
+
const rel = path.relative(REPO_ROOT, p);
|
|
193
|
+
if (twinState.kind === 'mismatch') {
|
|
194
|
+
process.stderr.write(
|
|
195
|
+
`❌ SQLite twin disagrees with the canonical JSON: twin ${twinState.twin} ≠ json ${twinState.json}\n` +
|
|
196
|
+
' The twin is derived and disposable — delete it and re-run `code-graph build`.\n' +
|
|
197
|
+
` ${path.relative(REPO_ROOT, sqliteTwinPath(p))}\n`,
|
|
198
|
+
);
|
|
199
|
+
return 1;
|
|
200
|
+
}
|
|
201
|
+
process.stdout.write(`✅ graph schema valid: ${rel} · twin: ${twinState.kind}\n`);
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Compare the twin's stored `source_checksum` against the canonical JSON's.
|
|
207
|
+
*
|
|
208
|
+
* `absent` covers every state in which there is nothing to disagree WITH — no
|
|
209
|
+
* twin, no `node:sqlite`, a stale or corrupt twin the reader would refuse
|
|
210
|
+
* anyway. Those are not validation failures: the JSON is canonical and a
|
|
211
|
+
* missing accelerator changes no answer. Only a twin that is present, fresh,
|
|
212
|
+
* readable AND carrying a different checksum is a real contradiction.
|
|
213
|
+
*/
|
|
214
|
+
function twinChecksumState(
|
|
215
|
+
jsonPath: string,
|
|
216
|
+
jsonChecksum: string,
|
|
217
|
+
): { kind: 'absent' | 'match' } | { kind: 'mismatch'; twin: string; json: string } {
|
|
218
|
+
const index = openGraphIndex(jsonPath);
|
|
219
|
+
if (!index) return { kind: 'absent' };
|
|
220
|
+
try {
|
|
221
|
+
const twin = index.sourceChecksum;
|
|
222
|
+
if (twin && twin !== jsonChecksum) return { kind: 'mismatch', twin, json: jsonChecksum };
|
|
223
|
+
return { kind: 'match' };
|
|
224
|
+
} finally {
|
|
225
|
+
index.close();
|
|
156
226
|
}
|
|
157
|
-
process.stderr.write(`❌ graph schema invalid (${r.errors.length}):\n`);
|
|
158
|
-
for (const e of r.errors) process.stderr.write(` ${e}\n`);
|
|
159
|
-
return 1;
|
|
160
227
|
}
|
|
161
228
|
|
|
162
229
|
function resolveGraph(argv: string[]): { g: LoadedGraph; note: string } | { err: string } {
|
|
@@ -205,8 +272,9 @@ function changedNodeSeeds(g: LoadedGraph, root: string, ref: string): string[] {
|
|
|
205
272
|
} catch {
|
|
206
273
|
return [];
|
|
207
274
|
}
|
|
208
|
-
|
|
209
|
-
|
|
275
|
+
// Served by the `nodes_source_file` index on the SQLite path, so a wide
|
|
276
|
+
// diff does not force the whole node table into memory to answer it.
|
|
277
|
+
return g.idsInFiles(files);
|
|
210
278
|
}
|
|
211
279
|
|
|
212
280
|
function cmdQuery(kind: 'query' | 'explain' | 'affected' | 'path', argv: string[]): number {
|
|
@@ -67,6 +67,16 @@ export interface RawEdge {
|
|
|
67
67
|
* searched for the remote one.
|
|
68
68
|
*/
|
|
69
69
|
localName?: string;
|
|
70
|
+
/**
|
|
71
|
+
* `imports` only, PHP: the FULLY-QUALIFIED name as written (`App\\Svc\\Mailer`).
|
|
72
|
+
*
|
|
73
|
+
* `targetName` carries only the base name, because that is all the
|
|
74
|
+
* repo-wide lookup could use. Composer PSR-4 maps the NAMESPACE to a
|
|
75
|
+
* directory, so it needs the part `baseName()` discards — the same shape as
|
|
76
|
+
* `moduleSpecifier` for TS: the one piece of evidence that says where the
|
|
77
|
+
* name comes from, and it is right there in the source text.
|
|
78
|
+
*/
|
|
79
|
+
fqName?: string;
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
export interface FileExtract {
|
|
@@ -137,6 +147,7 @@ function extractPhp(root: TsNode, file: string, out: FileExtract): void {
|
|
|
137
147
|
relation: 'imports',
|
|
138
148
|
targetName: baseName(nameNode.text),
|
|
139
149
|
confidenceHint: 'EXTRACTED',
|
|
150
|
+
fqName: nameNode.text,
|
|
140
151
|
});
|
|
141
152
|
}
|
|
142
153
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Report what the PRODUCTION loader makes of each grammar file given as an
|
|
4
|
+
* argument, as one line of JSON on stdout.
|
|
5
|
+
*
|
|
6
|
+
* A separate process because `check_pack_size.main` is synchronous while
|
|
7
|
+
* `Language.load` is not, and because keeping the probe out of the gate's own
|
|
8
|
+
* process means the gate cannot accidentally hold a loaded grammar's emscripten
|
|
9
|
+
* heap for the rest of its run.
|
|
10
|
+
*
|
|
11
|
+
* Never throws for a bad file: an unreadable or invalid grammar comes back as
|
|
12
|
+
* `{ ok: false, error }`, because "the loader refused this" is the ANSWER the
|
|
13
|
+
* eligibility predicate needs, not a crash it has to interpret.
|
|
14
|
+
*/
|
|
15
|
+
import process from 'node:process';
|
|
16
|
+
|
|
17
|
+
import { probeGrammarFile } from './loader.js';
|
|
18
|
+
|
|
19
|
+
interface ProbeRow {
|
|
20
|
+
path: string;
|
|
21
|
+
ok: boolean;
|
|
22
|
+
abi?: number;
|
|
23
|
+
grammar_id?: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function main(): Promise<number> {
|
|
28
|
+
const rows: ProbeRow[] = [];
|
|
29
|
+
for (const p of process.argv.slice(2)) {
|
|
30
|
+
try {
|
|
31
|
+
const r = await probeGrammarFile(p);
|
|
32
|
+
rows.push({ path: p, ok: true, abi: r.abi, grammar_id: r.grammar_id });
|
|
33
|
+
} catch (e) {
|
|
34
|
+
rows.push({ path: p, ok: false, error: (e as Error).message });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
process.stdout.write(`${JSON.stringify(rows)}\n`);
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
void main().then((c) => {
|
|
42
|
+
process.exitCode = c;
|
|
43
|
+
});
|
|
@@ -2,19 +2,29 @@
|
|
|
2
2
|
* WASM tree-sitter loader for the code-graph engine.
|
|
3
3
|
*
|
|
4
4
|
* Pinned pair (ABI-verified 2026-07-23): `web-tree-sitter@0.24.7` +
|
|
5
|
-
*
|
|
6
|
-
* `tests/scripts/code_graph.test.ts
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* grammar ABI 14. The ABI smoke test lives in
|
|
6
|
+
* `tests/scripts/code_graph.test.ts`.
|
|
7
|
+
*
|
|
8
|
+
* SHIPPED TO CONSUMERS (ADR-259, amended 2026-09-07). `web-tree-sitter` is a
|
|
9
|
+
* runtime `dependency`, and the three loadable grammars are vendored at
|
|
10
|
+
* `src/vendor/grammars/` and listed in `package.json` `files`. So a consumer
|
|
11
|
+
* install resolves both halves with no manual step. `tree-sitter-wasms` stays a
|
|
12
|
+
* devDependency — it is the source the vendored copies are refreshed from, and
|
|
13
|
+
* the fallback below keeps this repo's own dev flow (and any consumer holding
|
|
14
|
+
* the full 36-grammar pack) working unchanged.
|
|
15
|
+
*
|
|
16
|
+
* No network, no node-gyp, and no runtime grammar download — Kill register K1:
|
|
17
|
+
* both lookups below are filesystem reads of already-installed bytes.
|
|
10
18
|
*
|
|
11
19
|
* web-tree-sitter 0.24.x is CJS with a single default export (the Parser
|
|
12
20
|
* class) and the classic `Parser.Language.load()` API; we reach it through
|
|
13
21
|
* `createRequire` so this ESM module stays portable under the repo's tsx
|
|
14
22
|
* runner.
|
|
15
23
|
*/
|
|
24
|
+
import * as fs from 'node:fs';
|
|
16
25
|
import { createRequire } from 'node:module';
|
|
17
26
|
import * as path from 'node:path';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
18
28
|
|
|
19
29
|
import { EXPECTED_GRAMMAR_ABI, GRAMMAR_WASM, type Lang } from './types.js';
|
|
20
30
|
|
|
@@ -53,22 +63,35 @@ interface ParserStatic {
|
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* `check_dependency_floors` only
|
|
66
|
+
* Reached only when BOTH grammar sources are missing — the vendored set that
|
|
67
|
+
* ships in the tarball and the devDependency pack. That is a broken or
|
|
68
|
+
* partially-extracted install, not the ordinary consumer state, so the message
|
|
69
|
+
* says how to repair rather than how to opt in.
|
|
70
|
+
*
|
|
71
|
+
* It still carries the exact pin because the pair is ABI-coupled and
|
|
72
|
+
* `check_dependency_floors` reads only version ranges, never this string.
|
|
63
73
|
*/
|
|
64
74
|
const INSTALL_HINT =
|
|
65
|
-
'the code-graph engine
|
|
66
|
-
'
|
|
75
|
+
'the code-graph engine could not find its grammars. They ship with this package at ' +
|
|
76
|
+
'`src/vendor/grammars/`, so this usually means an incomplete install — reinstall the ' +
|
|
77
|
+
'package first. To supply them manually, install the ABI-locked pack:\n' +
|
|
67
78
|
' npm i web-tree-sitter@0.24.7 tree-sitter-wasms@0.1.13\n' +
|
|
68
79
|
'Both versions are exact on purpose — the pair is ABI-coupled (grammar ABI 14) ' +
|
|
69
80
|
'and must move together.';
|
|
70
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Directory holding the grammars that ship inside the package.
|
|
84
|
+
*
|
|
85
|
+
* `../../..` from `src/scripts/code_graph/` is the package root — the same
|
|
86
|
+
* walk `_lib/agent_settings.ts` uses from `src/scripts/_lib/`. A wrong answer
|
|
87
|
+
* here is not a failure: `grammarWasmPath` only uses this path when the file is
|
|
88
|
+
* actually present, and otherwise falls through to the devDependency pack,
|
|
89
|
+
* which is exactly today's behaviour.
|
|
90
|
+
*/
|
|
91
|
+
const VENDORED_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'vendor', 'grammars');
|
|
92
|
+
|
|
71
93
|
let _wasmDir: string | null = null;
|
|
94
|
+
/** The devDependency grammar pack — the fallback source, and this repo's own. */
|
|
72
95
|
function wasmDir(): string {
|
|
73
96
|
if (_wasmDir === null) {
|
|
74
97
|
let pkgJson: string;
|
|
@@ -86,7 +109,18 @@ let _initialized = false;
|
|
|
86
109
|
const _langCache = new Map<Lang, TsLanguage>();
|
|
87
110
|
const _parserCache = new Map<Lang, TsParser>();
|
|
88
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Absolute path to a grammar, vendored set first.
|
|
114
|
+
*
|
|
115
|
+
* Order is load-bearing: the vendored set is what a consumer actually has, and
|
|
116
|
+
* it is ABI-pinned by the same change that wrote it. The devDependency pack is
|
|
117
|
+
* second so this repo keeps working when `src/vendor/grammars/` has not been
|
|
118
|
+
* refreshed, and so a consumer holding the full 36-grammar pack can reach a
|
|
119
|
+
* grammar the vendored three do not cover.
|
|
120
|
+
*/
|
|
89
121
|
export function grammarWasmPath(lang: Lang): string {
|
|
122
|
+
const vendored = path.join(VENDORED_DIR, GRAMMAR_WASM[lang]);
|
|
123
|
+
if (fs.existsSync(vendored)) return vendored;
|
|
90
124
|
return path.join(wasmDir(), GRAMMAR_WASM[lang]);
|
|
91
125
|
}
|
|
92
126
|
|
|
@@ -150,3 +184,43 @@ export async function getParser(lang: Lang): Promise<TsParser> {
|
|
|
150
184
|
export function nodeHasError(n: TsNode): boolean {
|
|
151
185
|
return typeof n.hasError === 'function' ? n.hasError() : n.hasError;
|
|
152
186
|
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Load an ARBITRARY grammar file through the production loader and report what
|
|
190
|
+
* it actually is.
|
|
191
|
+
*
|
|
192
|
+
* Exists for `check_pack_size`'s binary-eligibility predicate, and the "through
|
|
193
|
+
* the production loader" part is the whole point: the gate must not re-implement
|
|
194
|
+
* grammar loading, because a second implementation could accept bytes the engine
|
|
195
|
+
* rejects — which is precisely the gap that would let a broken grammar ship
|
|
196
|
+
* while the gate reported it valid. Same `_Parser()` instance, same
|
|
197
|
+
* `Language.load`, same ABI reading as `loadLanguage`.
|
|
198
|
+
*
|
|
199
|
+
* `grammar_id` comes from the module's exported `tree_sitter_<id>` symbol rather
|
|
200
|
+
* than from the `Language` object, because web-tree-sitter 0.24's `Language`
|
|
201
|
+
* exposes `version` and no name. `WebAssembly.Module.exports` is the runtime's
|
|
202
|
+
* own parser, so the id is read rather than guessed.
|
|
203
|
+
*
|
|
204
|
+
* Throws on anything it cannot establish — a caller that wants a verdict rather
|
|
205
|
+
* than an exception should catch. Never silently returns a partial answer.
|
|
206
|
+
*/
|
|
207
|
+
export async function probeGrammarFile(wasmPath: string): Promise<{ abi: number; grammar_id: string }> {
|
|
208
|
+
const bytes = fs.readFileSync(wasmPath);
|
|
209
|
+
const mod = new WebAssembly.Module(bytes);
|
|
210
|
+
// A grammar module exports `tree_sitter_<id>` plus, usually, a family of
|
|
211
|
+
// `tree_sitter_<id>_external_scanner_*` helpers — measured: 6 such exports
|
|
212
|
+
// on php, 7 on typescript and javascript. The language id is the one
|
|
213
|
+
// WITHOUT the scanner suffix, and filtering for it leaves exactly one.
|
|
214
|
+
const ids = WebAssembly.Module.exports(mod)
|
|
215
|
+
.map((e) => e.name)
|
|
216
|
+
.filter((n) => n.startsWith('tree_sitter_') && !n.includes('_external_scanner_'))
|
|
217
|
+
.map((n) => n.slice('tree_sitter_'.length));
|
|
218
|
+
if (ids.length !== 1) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`grammar probe: expected exactly one tree_sitter_* export in ${wasmPath}, found ${String(ids.length)}`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
await _init();
|
|
224
|
+
const language = await _Parser().Language.load(wasmPath);
|
|
225
|
+
return { abi: language.version, grammar_id: ids[0] as string };
|
|
226
|
+
}
|