ast-mcp-server 0.17.0 → 0.18.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 +20 -1
- package/README.md +5 -2
- package/cordis.patch.yml +1 -1
- package/dist/services/angular-templates.js +105 -29
- package/dist/services/context-builder.js +21 -2
- package/dist/services/explore-presentation.js +1 -0
- package/dist/services/references.js +53 -28
- package/dist/tools/explore.js +3 -0
- package/dist/tools/find_references.js +5 -21
- package/dist/tools/template-evidence-schema.js +25 -0
- package/package.json +3 -2
- package/skills/structural-code-editing/SKILL.md +7 -2
- package/skills/structural-code-editing/references/operations.md +3 -1
- package/skills/structural-code-editing/releases.json +17 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
-
## [0.
|
|
9
|
+
## [0.18.0] — not yet published (2026-11-10)
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `ast_explore` reports Angular template evidence in `evidence[].references` whenever it expands references: each item carries `origin` (`compiler`, `template_external`, `template_inline`) and the block adds `template_coverage` and `template_reference_count`, with the verdict `ast_find_references` gives for the same symbol. The fields are additive and absent when coverage is `not_applicable`, so output for non-Angular projects is unchanged (#380).
|
|
14
|
+
- New `ast_explore` omission reason `template_coverage_incomplete` (category `incomplete`, component `references`). Any coverage status other than `completed` raises it once per symbol and sets `evidence_complete: false`; the reason code is in `template_coverage`, and the omission is still counted when `max_bytes` drops the references block. A failure of the template step alone keeps the TypeScript references and reports `unfinished` with `template_unresolved` (#380).
|
|
15
|
+
- `yarn benchmark:explore-templates` measures the added latency on demand. The recorded run adds 1059 ms at p95 for `detail: "full"`, `limit: 20` on 501 components (threshold 2 s), and every request on 2001 components returns a typed result with `work_exhausted`; see `docs/spikes/0380-explore-template-latency.md` (#380).
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- In `ast_explore`, `reference_limit` now bounds the merged list of TypeScript and template items, with the same ordering as `ast_find_references`. Its input description still says "compiler references", and `reference_count` counts TypeScript items only. Page the remaining items through `ast_find_references` (#380).
|
|
20
|
+
- The `tools/list` digest changed because the `ast_explore` output schema gained the optional fields and the new omission reason. Tool descriptions and input schemas are unchanged. `ast_get_impact`, `ast_find_test_candidates` and the `ast_explore` call spines stay compiler-only (#380).
|
|
21
|
+
- The `structural-code-editing` skill is `4.9.0`: it and the README describe the `ast_explore` template evidence instead of the former TypeScript-only limit. The published npm `0.17.0` bytes (skill `4.8.0`) are recorded as a predecessor (#380).
|
|
22
|
+
- `ast_explore` reads and parses each Angular template at most once per request: the selected symbols share the component list, the template reads and the parses through a cache that lives for that request only. Items, order, per-symbol scan budgets and `template_coverage` verdicts are the same as before, and `ast_find_references` is unaffected. In the recorded run the added p50 for `detail: "full"`, `limit: 20` falls from 1040 ms to 179 ms on 501 components and from 4238 ms to 698 ms on 2001 components, where every symbol still ends with `work_exhausted`; see `docs/spikes/0380-explore-template-latency.md` (#454).
|
|
23
|
+
|
|
24
|
+
## [0.17.0] — published to `next`, verification failed, not promoted (2026-11-10)
|
|
10
25
|
|
|
11
26
|
### Added
|
|
12
27
|
|
|
@@ -25,6 +40,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
25
40
|
- With `@angular/compiler` 18 or 19, template usage scans walked every subtree twice per nesting level, so `ast_find_references` on a `@Pipe` class or a component member reported `unfinished` with `work_exhausted` on deeply nested templates, and `ast_rename_symbol` refused the rename. Each template is now walked once on every supported major (#441).
|
|
26
41
|
- Upgrading the `structural-code-editing` skill over an unmodified install from npm `0.16.0` would have failed with a conflict: its content changed after `0.16.0` (#443) while the skill version stayed `4.7.0`, and the `0.16.0` byte set had no recorded predecessor. The published `0.16.0` bytes are now recorded, and the skill version is `4.8.0` (#447).
|
|
27
42
|
|
|
43
|
+
### Release status
|
|
44
|
+
|
|
45
|
+
- npm published these immutable bytes under `next` with provenance and the expected Git commit (`e49987bd1fd36432aa76ecb27ca8359eedb613ad`, integrity `sha512-NmjdENfdfLCSqdMJyazxHfx1XuFHgQeJlAB2XIpHGklqCKkwVw43xKMlXmDfdT7m1XT/b4mokx1MefFdj/AIyw==`, publish run `42753818346`). Public-registry verification (run `42754346795`) then failed for the same reason as `0.14.3`–`0.16.0`: the consumer install resolves `minimatch@10.2.6` through `ts-morph@28.0.0`, flagged by `npm audit` for GHSA-g3hv-75wg-m249 (#361). `0.17.0` supersedes `0.16.0` on `next` and was not promoted to `latest`, tagged in Git, or released on GitHub.
|
|
46
|
+
|
|
28
47
|
## [0.16.0] — published to `next`, verification failed, not promoted (2026-11-07)
|
|
29
48
|
|
|
30
49
|
### Added
|
package/README.md
CHANGED
|
@@ -96,7 +96,10 @@ Template evidence is explicit about what it proves:
|
|
|
96
96
|
- For these members, `completed` means every component template inside the Project was read and every match resolved. `hostDirectives`, `@Directive` inputs and outputs, and non-literal scopes, aliases, metadata or pipe names stay `unfinished` with reason `cross_component`. Runtime bindings (`setInput`, router input binding, `createComponent`) and consumers outside the Project are never examined.
|
|
97
97
|
- Rename rewrites consumer bindings of a selector-bound member when coverage is `completed`, keeping the binding syntax, values and alias-keyed bindings. It returns `INCOMPLETE_EVIDENCE` when coverage is `unfinished` or `unsupported` (including `hostDirectives`, `@Directive` members and metadata-listed members), and `MUTATION_BLOCKED` for inline templates, a name collision with a template local or another binding of the element, `[(x)]` on a non-`model()` pair, `$any`, or a name in the selector. Inline templates are never rewritten.
|
|
98
98
|
- Renaming a `@Pipe` class applies TypeScript edits only and keeps the `name` literal and every `| name` usage; renaming `transform` returns `INCOMPLETE_EVIDENCE`.
|
|
99
|
-
- `ast_explore`
|
|
99
|
+
- `ast_explore` reports the same template evidence in `evidence[].references` whenever it expands references (`detail: "full"` or `include_references: true`): each item carries `origin`, and the block adds `template_coverage` and `template_reference_count` with the verdict `ast_find_references` gives for the same symbol. None of these keys appear when coverage is `not_applicable`.
|
|
100
|
+
- In `ast_explore`, `reference_limit` bounds the merged list of TypeScript and template items, even though its input description still says "compiler references". `reference_count` counts TypeScript items only, and a list cut by the limit raises the usual `reference_limit` omission. `ast_explore` has no reference offset: page the remaining items through `ast_find_references`.
|
|
101
|
+
- Any `template_coverage.status` other than `completed` adds one `template_coverage_incomplete` omission (category `incomplete`, component `references`) per symbol and sets `evidence_complete: false`. The reason code (for example `compiler_missing`) is in `template_coverage`. `omissions.details` lists the omission only when the detail limit leaves room; `omissions.counts` and `omissions.total` always count it, also when `max_bytes` drops the references block.
|
|
102
|
+
- `ast_get_impact`, `ast_find_test_candidates` and the `ast_explore` call spines stay compiler-only: they never contain a template usage.
|
|
100
103
|
- The `diagnostics` field stays TypeScript-only. When a rename edits a template, the plan also carries `template_diagnostics`: a best-effort Angular template type-check delta (`status`, `reason` when not `completed`, `added`, `removed`, `added_errors`) computed by the project's own `@angular/compiler-cli` in a separate child process that never writes to the workspace. It is absent for non-Angular projects and for renames that edit no template. `status` is `completed`, `unavailable` (`compiler_cli_missing`, `compiler_unsupported`, `typescript_incompatible`) or `unfinished` (`work_exhausted`, `deadline`, `check_failed`); the arrays are empty unless it is `completed`.
|
|
101
104
|
- Treat `unavailable` and `unfinished` as unknown, never as clean: run the project's Angular build or tests before applying. `block_on_template_errors` (default `false`) blocks the plan only when the status is `completed` and `added_errors` is not empty; `allow_new_errors` waives it. Without the input the delta is reported but never blocks.
|
|
102
105
|
- `template_diagnostics[].file` is relative to the directory of the tsconfig, while `diagnostics[].file` is relative to `project_root`; they differ when the tsconfig is not at the project root.
|
|
@@ -378,7 +381,7 @@ For a local candidate, pack and install its tarball instead:
|
|
|
378
381
|
|
|
379
382
|
```bash
|
|
380
383
|
yarn pack --out ast-mcp-server-%v.tgz
|
|
381
|
-
dsh plugin --profile web add ./ast-mcp-server-0.
|
|
384
|
+
dsh plugin --profile web add ./ast-mcp-server-0.18.0.tgz
|
|
382
385
|
```
|
|
383
386
|
|
|
384
387
|
The first supported surface is **reads + prepare + preview**. Every apply path is
|
package/cordis.patch.yml
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Mounts the packaged `ast-mcp-server` stdio command through the official
|
|
4
4
|
# `@deepseek-ai/dsh-mcp-client` bridge. Harness does not resolve MCP binaries
|
|
5
5
|
# from PATH, so the entrypoint is resolved relative to the profile directory
|
|
6
|
-
# (`baseUrl`), where `dsh plugin --profile <name> add ./ast-mcp-server-0.
|
|
6
|
+
# (`baseUrl`), where `dsh plugin --profile <name> add ./ast-mcp-server-0.18.0.tgz`
|
|
7
7
|
# installs a locally packed package as a dependency.
|
|
8
8
|
#
|
|
9
9
|
# First-slice surface: reads + prepare + preview. Every apply path is denied
|
|
@@ -175,10 +175,10 @@ export function memberOwner(node) {
|
|
|
175
175
|
? { cls: owner, name: nameNode.getText() }
|
|
176
176
|
: undefined;
|
|
177
177
|
}
|
|
178
|
-
function hasDerivedComponent(
|
|
178
|
+
function hasDerivedComponent(cls, cache) {
|
|
179
179
|
const identity = (node) => `${node.getSourceFile().getFilePath()}:${node.getPos()}`;
|
|
180
180
|
const target = identity(cls);
|
|
181
|
-
return
|
|
181
|
+
return cache.components().some((candidate) => {
|
|
182
182
|
let base = candidate.getBaseClass();
|
|
183
183
|
for (let depth = 0; base && depth < 32; depth += 1) {
|
|
184
184
|
if (identity(base) === target)
|
|
@@ -406,6 +406,73 @@ function decodeTemplate(bytes) {
|
|
|
406
406
|
return undefined;
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Template work that does not depend on the queried symbol, shared by the collections of one
|
|
411
|
+
* request: the component list, each located template, each read and each parse. It is bound to the
|
|
412
|
+
* {@link ProjectContext} it was created for, and a collection over another context refuses it. It
|
|
413
|
+
* never outlives the request, so nothing is invalidated.
|
|
414
|
+
*
|
|
415
|
+
* Only whole results are kept. An entry is stored after its step returned, so a step that threw is
|
|
416
|
+
* run again by the next collection, and a scan that stopped early leaves complete entries for the
|
|
417
|
+
* templates it reached and none for the rest. Scan budgets are not part of it: every collection
|
|
418
|
+
* charges its own budget for each template, cached or not.
|
|
419
|
+
*/
|
|
420
|
+
export class TemplateScanCache {
|
|
421
|
+
context;
|
|
422
|
+
#components;
|
|
423
|
+
#located = new Map();
|
|
424
|
+
/** External templates by path, so a file shared by two components is read once; inline by location. */
|
|
425
|
+
#reads = new Map();
|
|
426
|
+
#parsed = new WeakMap();
|
|
427
|
+
#lines = new Map();
|
|
428
|
+
constructor(context) {
|
|
429
|
+
this.context = context;
|
|
430
|
+
}
|
|
431
|
+
components() {
|
|
432
|
+
return (this.#components ??= componentClasses(this.context));
|
|
433
|
+
}
|
|
434
|
+
locate(cls) {
|
|
435
|
+
let located = this.#located.get(cls);
|
|
436
|
+
if (!located) {
|
|
437
|
+
located = locateTemplate(cls);
|
|
438
|
+
this.#located.set(cls, located);
|
|
439
|
+
}
|
|
440
|
+
return located;
|
|
441
|
+
}
|
|
442
|
+
async read(located) {
|
|
443
|
+
const key = located.container === "external" ? located.absolutePath : located;
|
|
444
|
+
let read = this.#reads.get(key);
|
|
445
|
+
if (!read) {
|
|
446
|
+
read = await readTemplate(this.context, located);
|
|
447
|
+
this.#reads.set(key, read);
|
|
448
|
+
}
|
|
449
|
+
return read;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* The lines of the file that holds a usage, for context lines. An external template that the
|
|
453
|
+
* scan read is split from that text; any other file (a source file with an inline template) is
|
|
454
|
+
* read from disk once.
|
|
455
|
+
*/
|
|
456
|
+
async lines(absolutePath) {
|
|
457
|
+
let lines = this.#lines.get(absolutePath);
|
|
458
|
+
if (!lines) {
|
|
459
|
+
const read = this.#reads.get(absolutePath);
|
|
460
|
+
const text = read && "text" in read ? read.text : await readFile(absolutePath, "utf8");
|
|
461
|
+
lines = text.split(/\r?\n/);
|
|
462
|
+
this.#lines.set(absolutePath, lines);
|
|
463
|
+
}
|
|
464
|
+
return lines;
|
|
465
|
+
}
|
|
466
|
+
/** The parse of a template returned by {@link read}, errors included. */
|
|
467
|
+
parse(compiler, read, absolutePath) {
|
|
468
|
+
let parsed = this.#parsed.get(read);
|
|
469
|
+
if (!parsed) {
|
|
470
|
+
parsed = compiler.parseTemplate(read.text, absolutePath, { preserveWhitespaces: true });
|
|
471
|
+
this.#parsed.set(read, parsed);
|
|
472
|
+
}
|
|
473
|
+
return parsed;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
409
476
|
/** Reads a located template within the per-template size cap. */
|
|
410
477
|
async function readTemplate(context, located) {
|
|
411
478
|
let text;
|
|
@@ -467,16 +534,11 @@ function bindingTarget(cls, node, name) {
|
|
|
467
534
|
return undefined;
|
|
468
535
|
return { selector, inputs: names.inputs, outputs: names.outputs };
|
|
469
536
|
}
|
|
470
|
-
/** Parses a consumer template, or returns undefined when the compiler reports errors. */
|
|
471
|
-
function parseConsumer(compiler, text, absolutePath) {
|
|
472
|
-
const parsed = compiler.parseTemplate(text, absolutePath, { preserveWhitespaces: true });
|
|
473
|
-
return parsed.errors?.length ? undefined : parsed.nodes;
|
|
474
|
-
}
|
|
475
537
|
function selectorInspector(compiler, target) {
|
|
476
|
-
return (text,
|
|
538
|
+
return (text, parse, tick) => {
|
|
477
539
|
if (!mayMatch(compiler, target.selector, text))
|
|
478
540
|
return "skip";
|
|
479
|
-
const nodes =
|
|
541
|
+
const nodes = parse();
|
|
480
542
|
if (!nodes)
|
|
481
543
|
return "template_parse_error";
|
|
482
544
|
const scan = extractConsumerUsages(compiler, nodes, text, target, tick);
|
|
@@ -489,10 +551,10 @@ function selectorInspector(compiler, target) {
|
|
|
489
551
|
};
|
|
490
552
|
}
|
|
491
553
|
function pipeInspector(compiler, name, rc) {
|
|
492
|
-
return (text,
|
|
554
|
+
return (text, parse, tick) => {
|
|
493
555
|
if (!text.includes(name))
|
|
494
556
|
return "skip";
|
|
495
|
-
const nodes =
|
|
557
|
+
const nodes = parse();
|
|
496
558
|
if (!nodes)
|
|
497
559
|
return "template_parse_error";
|
|
498
560
|
try {
|
|
@@ -511,7 +573,7 @@ function pipeInspector(compiler, name, rc) {
|
|
|
511
573
|
* Scans every component template in the Project with `inspect`. Matching comes first; scope only
|
|
512
574
|
* filters matches. Unreadable templates and unproven scopes are reported.
|
|
513
575
|
*/
|
|
514
|
-
async function scanConsumers(context, cls, inspect, rc, limits) {
|
|
576
|
+
async function scanConsumers(context, cls, compiler, inspect, rc, limits, cache) {
|
|
515
577
|
const remaining = { ...SCAN_LIMITS, ...limits };
|
|
516
578
|
const usages = [];
|
|
517
579
|
let failure;
|
|
@@ -528,25 +590,30 @@ async function scanConsumers(context, cls, inspect, rc, limits) {
|
|
|
528
590
|
rc.checkpoint();
|
|
529
591
|
};
|
|
530
592
|
try {
|
|
531
|
-
for (const consumer of
|
|
593
|
+
for (const consumer of cache.components()) {
|
|
532
594
|
rc.checkpoint();
|
|
533
595
|
if (--remaining.templates < 0)
|
|
534
596
|
throw new WorkExhausted();
|
|
535
|
-
const located =
|
|
597
|
+
const located = cache.locate(consumer);
|
|
536
598
|
if ("reason" in located) {
|
|
537
599
|
failure ??= located.reason;
|
|
538
600
|
continue;
|
|
539
601
|
}
|
|
540
|
-
const read = await
|
|
602
|
+
const read = await cache.read(located);
|
|
541
603
|
if ("reason" in read) {
|
|
542
604
|
failure ??= read.reason;
|
|
543
605
|
continue;
|
|
544
606
|
}
|
|
607
|
+
// The budget is charged for every template, whether or not the cache already held it.
|
|
545
608
|
remaining.bytes -= read.bytes.length;
|
|
546
609
|
if (remaining.bytes < 0)
|
|
547
610
|
throw new WorkExhausted();
|
|
548
611
|
examined += 1;
|
|
549
|
-
const
|
|
612
|
+
const parse = () => {
|
|
613
|
+
const parsed = cache.parse(compiler, read, located.absolutePath);
|
|
614
|
+
return parsed.errors?.length ? undefined : parsed.nodes;
|
|
615
|
+
};
|
|
616
|
+
const scan = inspect(read.text, parse, tick);
|
|
550
617
|
if (scan === "skip")
|
|
551
618
|
continue;
|
|
552
619
|
if (scan === "template_parse_error") {
|
|
@@ -583,7 +650,7 @@ function pipeName(cls) {
|
|
|
583
650
|
return literalString(Node.isPropertyAssignment(property) ? property.getInitializer() : undefined);
|
|
584
651
|
}
|
|
585
652
|
/** `| name` usages of a pipe class in the templates of components that have the class in scope. */
|
|
586
|
-
async function collectPipeUsages(cls, context, rc, limits) {
|
|
653
|
+
async function collectPipeUsages(cls, context, rc, limits, cache) {
|
|
587
654
|
const unfinished = (reason, templates = 0) => ({
|
|
588
655
|
coverage: { status: "unfinished", reason, templates },
|
|
589
656
|
usages: [],
|
|
@@ -594,15 +661,23 @@ async function collectPipeUsages(cls, context, rc, limits) {
|
|
|
594
661
|
const loaded = await loadCompiler(context.tsConfigFilePath);
|
|
595
662
|
if ("reason" in loaded)
|
|
596
663
|
return unfinished(loaded.reason);
|
|
597
|
-
const scan = await scanConsumers(context, cls, pipeInspector(loaded.compiler, name, rc), rc, limits);
|
|
664
|
+
const scan = await scanConsumers(context, cls, loaded.compiler, pipeInspector(loaded.compiler, name, rc), rc, limits, cache);
|
|
598
665
|
const coverage = scan.failure
|
|
599
666
|
? { status: "unfinished", reason: scan.failure, templates: scan.examined }
|
|
600
667
|
: { status: "completed", templates: scan.examined };
|
|
601
668
|
return { coverage, usages: scan.usages };
|
|
602
669
|
}
|
|
603
|
-
/**
|
|
604
|
-
|
|
670
|
+
/**
|
|
671
|
+
* Collects usages of a component member inside its declaring component's template.
|
|
672
|
+
*
|
|
673
|
+
* Pass one `cache` to every collection of a request to share symbol-independent work between
|
|
674
|
+
* them; the result is the same with or without it. By default each call works alone.
|
|
675
|
+
*/
|
|
676
|
+
export async function collectTemplateUsages(node, context, rc, limits = {}, cache = new TemplateScanCache(context)) {
|
|
605
677
|
rc.checkpoint();
|
|
678
|
+
if (cache.context !== context) {
|
|
679
|
+
throw new Error("The template scan cache was created for another ProjectContext.");
|
|
680
|
+
}
|
|
606
681
|
if (!hasAngularCore(context))
|
|
607
682
|
return NOT_APPLICABLE;
|
|
608
683
|
const fail = (status, reason) => ({
|
|
@@ -610,7 +685,7 @@ export async function collectTemplateUsages(node, context, rc, limits = {}) {
|
|
|
610
685
|
usages: [],
|
|
611
686
|
});
|
|
612
687
|
if (Node.isClassDeclaration(node)) {
|
|
613
|
-
return isPipeClass(node) ? collectPipeUsages(node, context, rc, limits) : NOT_APPLICABLE;
|
|
688
|
+
return isPipeClass(node) ? collectPipeUsages(node, context, rc, limits, cache) : NOT_APPLICABLE;
|
|
614
689
|
}
|
|
615
690
|
const owner = memberOwner(node);
|
|
616
691
|
if (!owner)
|
|
@@ -620,14 +695,17 @@ export async function collectTemplateUsages(node, context, rc, limits = {}) {
|
|
|
620
695
|
if (owner.name !== "transform" || componentDecorator(owner.cls)) {
|
|
621
696
|
return fail("unfinished", "cross_component");
|
|
622
697
|
}
|
|
623
|
-
return {
|
|
698
|
+
return {
|
|
699
|
+
...(await collectPipeUsages(owner.cls, context, rc, limits, cache)),
|
|
700
|
+
renameBlocked: true,
|
|
701
|
+
};
|
|
624
702
|
}
|
|
625
703
|
const isComponent = componentDecorator(owner.cls) !== undefined;
|
|
626
704
|
const selectorPublic = isSelectorPublic(node) || isMetadataPublic(owner.cls, owner.name);
|
|
627
705
|
if (!isComponent && selectorPublic && isDirectiveClass(owner.cls)) {
|
|
628
706
|
return fail("unfinished", "cross_component");
|
|
629
707
|
}
|
|
630
|
-
const derived = hasDerivedComponent(
|
|
708
|
+
const derived = hasDerivedComponent(owner.cls, cache);
|
|
631
709
|
if (!isComponent && !derived)
|
|
632
710
|
return NOT_APPLICABLE;
|
|
633
711
|
if (derived)
|
|
@@ -635,16 +713,14 @@ export async function collectTemplateUsages(node, context, rc, limits = {}) {
|
|
|
635
713
|
const loaded = await loadCompiler(context.tsConfigFilePath);
|
|
636
714
|
if ("reason" in loaded)
|
|
637
715
|
return fail("unfinished", loaded.reason);
|
|
638
|
-
const located =
|
|
716
|
+
const located = cache.locate(owner.cls);
|
|
639
717
|
if ("reason" in located)
|
|
640
718
|
return fail("unfinished", located.reason);
|
|
641
|
-
const read = await
|
|
719
|
+
const read = await cache.read(located);
|
|
642
720
|
if ("reason" in read)
|
|
643
721
|
return fail("unfinished", read.reason);
|
|
644
722
|
rc.checkpoint();
|
|
645
|
-
const parsed = loaded.compiler
|
|
646
|
-
preserveWhitespaces: true,
|
|
647
|
-
});
|
|
723
|
+
const parsed = cache.parse(loaded.compiler, read, located.absolutePath);
|
|
648
724
|
if (parsed.errors?.length)
|
|
649
725
|
return fail("unfinished", "template_parse_error");
|
|
650
726
|
let raw;
|
|
@@ -669,7 +745,7 @@ export async function collectTemplateUsages(node, context, rc, limits = {}) {
|
|
|
669
745
|
return unfinished("cross_component");
|
|
670
746
|
if (!loaded.selectors)
|
|
671
747
|
return unfinished("compiler_unsupported");
|
|
672
|
-
const scan = await scanConsumers(context, owner.cls, selectorInspector(loaded.compiler, target), rc, limits);
|
|
748
|
+
const scan = await scanConsumers(context, owner.cls, loaded.compiler, selectorInspector(loaded.compiler, target), rc, limits, cache);
|
|
673
749
|
const coverage = scan.failure
|
|
674
750
|
? { status: "unfinished", reason: scan.failure, templates: scan.examined }
|
|
675
751
|
: { status: "completed", templates: scan.examined };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { TemplateScanCache } from "./angular-templates.js";
|
|
2
3
|
import { buildFileOutline, nodeSourceWithLocation } from "./outline.js";
|
|
3
4
|
import { paginate } from "./pagination.js";
|
|
4
5
|
import { resolveImpactRoot } from "./impact.js";
|
|
5
6
|
import { planCallSpines } from "./call-spines.js";
|
|
6
7
|
import { isCooperativeInterruption, NO_REQUEST_CONTEXT, } from "./request-context.js";
|
|
7
8
|
import { findDeclarationByName, getSourceFileOrThrow, reportSymbolIndexFailure, } from "./project.js";
|
|
8
|
-
import {
|
|
9
|
+
import { collectSymbolReferencesWithTemplateVerdict, } from "./references.js";
|
|
9
10
|
import { collectCompilerCallRelationships } from "./relationships.js";
|
|
10
11
|
import { presentExploreClusters } from "./explore-presentation.js";
|
|
11
12
|
import { searchProjectSymbolsPage, searchProjectSymbolsPageWithIndex, sourceFileSymbols, symbolMatchRank, } from "./symbols.js";
|
|
@@ -151,6 +152,8 @@ export async function buildExploreContext(context, request, requestContext = NO_
|
|
|
151
152
|
const unresolved = [];
|
|
152
153
|
const evidence = [];
|
|
153
154
|
let callSpines;
|
|
155
|
+
// Lives for this request only: the selected symbols share template reads, never a scan budget.
|
|
156
|
+
const templateCache = new TemplateScanCache(effectiveContext);
|
|
154
157
|
if (request.callSpines) {
|
|
155
158
|
if (!request.filePath || !request.symbolPath || routed.route !== "symbol") {
|
|
156
159
|
throw new Error("Call spines require an exact file_path and symbol_path.");
|
|
@@ -197,7 +200,13 @@ export async function buildExploreContext(context, request, requestContext = NO_
|
|
|
197
200
|
}
|
|
198
201
|
if (expansion.references) {
|
|
199
202
|
try {
|
|
200
|
-
references =
|
|
203
|
+
references = await collectSymbolReferencesWithTemplateVerdict(node, effectiveContext, {
|
|
204
|
+
symbolPath: record.selector,
|
|
205
|
+
includeDeclaration: true,
|
|
206
|
+
detail: request.referenceDetail,
|
|
207
|
+
offset: 0,
|
|
208
|
+
limit: request.referenceLimit,
|
|
209
|
+
}, requestContext, templateCache);
|
|
201
210
|
}
|
|
202
211
|
catch (error) {
|
|
203
212
|
if (isCooperativeInterruption(error))
|
|
@@ -231,6 +240,16 @@ export async function buildExploreContext(context, request, requestContext = NO_
|
|
|
231
240
|
reason: "reference_limit",
|
|
232
241
|
});
|
|
233
242
|
}
|
|
243
|
+
// Absent coverage means not applicable; anything short of completed leaves usages unknown.
|
|
244
|
+
const coverage = item.references?.template_coverage?.status;
|
|
245
|
+
if (coverage !== undefined && coverage !== "completed" && coverage !== "not_applicable") {
|
|
246
|
+
omissions.push({
|
|
247
|
+
subject: item.selector,
|
|
248
|
+
category: "incomplete",
|
|
249
|
+
component: "references",
|
|
250
|
+
reason: "template_coverage_incomplete",
|
|
251
|
+
});
|
|
252
|
+
}
|
|
234
253
|
}
|
|
235
254
|
if (callSpines?.authority_state === "untrusted") {
|
|
236
255
|
omissions.push({
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { Node } from "ts-morph";
|
|
4
|
-
import { collectTemplateUsages, } from "./angular-templates.js";
|
|
3
|
+
import { collectTemplateUsages, TemplateScanCache, } from "./angular-templates.js";
|
|
5
4
|
import { paginate } from "./pagination.js";
|
|
6
|
-
import { NO_REQUEST_CONTEXT } from "./request-context.js";
|
|
5
|
+
import { isCooperativeInterruption, NO_REQUEST_CONTEXT, } from "./request-context.js";
|
|
7
6
|
/**
|
|
8
7
|
* Semantic references of `node` excluding the declaration itself.
|
|
9
8
|
*
|
|
@@ -79,15 +78,11 @@ export function collectSymbolReferences(node, projectRoot, symbolPath, includeDe
|
|
|
79
78
|
next_offset: page.next_offset,
|
|
80
79
|
};
|
|
81
80
|
}
|
|
82
|
-
async function templateLocation(usage, detail,
|
|
81
|
+
async function templateLocation(usage, detail, templateCache) {
|
|
83
82
|
let context;
|
|
84
83
|
if (detail === "context") {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
lines = readFile(usage.absolutePath, "utf8").then((text) => text.split(/\r?\n/));
|
|
88
|
-
lineCache.set(usage.absolutePath, lines);
|
|
89
|
-
}
|
|
90
|
-
context = ((await lines)[usage.line - 1] ?? "").trim().slice(0, 500);
|
|
84
|
+
const lines = await templateCache.lines(usage.absolutePath);
|
|
85
|
+
context = (lines[usage.line - 1] ?? "").trim().slice(0, 500);
|
|
91
86
|
}
|
|
92
87
|
return {
|
|
93
88
|
file: usage.file,
|
|
@@ -99,29 +94,28 @@ async function templateLocation(usage, detail, lineCache) {
|
|
|
99
94
|
...(context === undefined ? {} : { context }),
|
|
100
95
|
};
|
|
101
96
|
}
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
export async function collectSymbolReferencesWithTemplates(node, context, query, requestContext = NO_REQUEST_CONTEXT) {
|
|
107
|
-
const { projectRoot } = context;
|
|
108
|
-
const { coverage, usages } = await collectTemplateUsages(node, context, requestContext);
|
|
109
|
-
if (coverage.status === "not_applicable") {
|
|
110
|
-
return collectSymbolReferences(node, projectRoot, query.symbolPath, query.includeDeclaration, query.detail, query.offset, query.limit, requestContext);
|
|
111
|
-
}
|
|
112
|
-
const ts = collectTsLocations(node, projectRoot, query.symbolPath, query.includeDeclaration, query.detail, requestContext);
|
|
113
|
-
const lineCache = new Map();
|
|
114
|
-
const templateItems = [];
|
|
97
|
+
/** Template usages of `node` as reference items, with the coverage verdict that qualifies them. */
|
|
98
|
+
async function collectTemplateHalf(node, context, detail, requestContext, templateCache = new TemplateScanCache(context)) {
|
|
99
|
+
const { coverage, usages } = await collectTemplateUsages(node, context, requestContext, undefined, templateCache);
|
|
100
|
+
const items = [];
|
|
115
101
|
for (const usage of usages) {
|
|
116
102
|
requestContext.checkpoint();
|
|
117
|
-
|
|
103
|
+
items.push(await templateLocation(usage, detail, templateCache));
|
|
104
|
+
}
|
|
105
|
+
return { coverage, items };
|
|
106
|
+
}
|
|
107
|
+
/** Merges the TypeScript references of `node` with an already collected template half. */
|
|
108
|
+
function mergeWithTemplateHalf(node, projectRoot, query, template, requestContext) {
|
|
109
|
+
if (template.coverage.status === "not_applicable") {
|
|
110
|
+
return collectSymbolReferences(node, projectRoot, query.symbolPath, query.includeDeclaration, query.detail, query.offset, query.limit, requestContext);
|
|
118
111
|
}
|
|
112
|
+
const ts = collectTsLocations(node, projectRoot, query.symbolPath, query.includeDeclaration, query.detail, requestContext);
|
|
119
113
|
const merged = [
|
|
120
114
|
...ts.locations.map((item) => ({ ...item, origin: "compiler" })),
|
|
121
|
-
...
|
|
115
|
+
...template.items,
|
|
122
116
|
].sort(compareLocations);
|
|
123
117
|
const affectedFiles = [
|
|
124
|
-
...new Set([ts.declaration, ...ts.references, ...
|
|
118
|
+
...new Set([ts.declaration, ...ts.references, ...template.items].map((item) => item.file)),
|
|
125
119
|
].sort();
|
|
126
120
|
const page = paginate(merged, query.offset, query.limit);
|
|
127
121
|
requestContext.checkpoint();
|
|
@@ -137,7 +131,38 @@ export async function collectSymbolReferencesWithTemplates(node, context, query,
|
|
|
137
131
|
total: page.total,
|
|
138
132
|
has_more: page.has_more,
|
|
139
133
|
next_offset: page.next_offset,
|
|
140
|
-
template_coverage: coverage,
|
|
141
|
-
template_reference_count:
|
|
134
|
+
template_coverage: template.coverage,
|
|
135
|
+
template_reference_count: template.items.length,
|
|
142
136
|
};
|
|
143
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Collects TypeScript references and, for Angular component members, template usages.
|
|
140
|
+
* Without Angular evidence the result is identical to {@link collectSymbolReferences}.
|
|
141
|
+
*/
|
|
142
|
+
export async function collectSymbolReferencesWithTemplates(node, context, query, requestContext = NO_REQUEST_CONTEXT) {
|
|
143
|
+
const template = await collectTemplateHalf(node, context, query.detail, requestContext);
|
|
144
|
+
return mergeWithTemplateHalf(node, context.projectRoot, query, template, requestContext);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Same result as {@link collectSymbolReferencesWithTemplates}, but a failing template half is
|
|
148
|
+
* reported as an `unfinished` / `template_unresolved` verdict next to the TypeScript references
|
|
149
|
+
* instead of failing the call. TypeScript failures and cooperative interruptions still throw.
|
|
150
|
+
*
|
|
151
|
+
* A caller that collects several symbols in one request passes the same `templateCache` to each
|
|
152
|
+
* call; it shares template reads between them and does not change any result.
|
|
153
|
+
*/
|
|
154
|
+
export async function collectSymbolReferencesWithTemplateVerdict(node, context, query, requestContext = NO_REQUEST_CONTEXT, templateCache) {
|
|
155
|
+
let template;
|
|
156
|
+
try {
|
|
157
|
+
template = await collectTemplateHalf(node, context, query.detail, requestContext, templateCache);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
if (isCooperativeInterruption(error))
|
|
161
|
+
throw error;
|
|
162
|
+
template = {
|
|
163
|
+
coverage: { status: "unfinished", reason: "template_unresolved", templates: 0 },
|
|
164
|
+
items: [],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return mergeWithTemplateHalf(node, context.projectRoot, query, template, requestContext);
|
|
168
|
+
}
|
package/dist/tools/explore.js
CHANGED
|
@@ -8,6 +8,7 @@ import { createRequestContext } from "../services/request-context.js";
|
|
|
8
8
|
import { FRESHNESS_CAUSES, SNAPSHOT_STATES, TRUNCATION_REASONS, } from "../services/read-contracts.js";
|
|
9
9
|
import { createToolErrorContext, errorResult, structuredResult } from "./result.js";
|
|
10
10
|
import { RelationshipEndpointSchema } from "./relationship-schema.js";
|
|
11
|
+
import { ReferenceOriginSchema, templateEvidenceFields } from "./template-evidence-schema.js";
|
|
11
12
|
const TOOL_NAME = "ast_explore";
|
|
12
13
|
const ExploreDetailSchema = z.enum(["selectors", "summary", "context", "full"]).default("summary");
|
|
13
14
|
const ReferenceDetailSchema = z.enum(["locations", "context"]).default("locations");
|
|
@@ -100,6 +101,7 @@ const ExploreReferenceSchema = z.object({
|
|
|
100
101
|
kind: z.string(),
|
|
101
102
|
is_declaration: z.boolean(),
|
|
102
103
|
context: z.string().optional(),
|
|
104
|
+
origin: ReferenceOriginSchema.optional(),
|
|
103
105
|
});
|
|
104
106
|
const ExploreEvidenceSchema = z.object({
|
|
105
107
|
selector: z.string(),
|
|
@@ -111,6 +113,7 @@ const ExploreEvidenceSchema = z.object({
|
|
|
111
113
|
declaration_count: z.number().int().min(0),
|
|
112
114
|
reference_count: z.number().int().min(0),
|
|
113
115
|
affected_files: z.array(z.string()),
|
|
116
|
+
...templateEvidenceFields,
|
|
114
117
|
references: z.array(ExploreReferenceSchema),
|
|
115
118
|
offset: z.number().int().min(0),
|
|
116
119
|
limit: z.number().int().positive(),
|
|
@@ -4,6 +4,7 @@ import { findDeclarationByName, getSourceFileOrThrow, withProject } from "../ser
|
|
|
4
4
|
import { collectSymbolReferencesWithTemplates } from "../services/references.js";
|
|
5
5
|
import { createRequestContext } from "../services/request-context.js";
|
|
6
6
|
import { createToolErrorContext, errorResult, formattedResult, ToolOutputFormatInputSchema, } from "./result.js";
|
|
7
|
+
import { ReferenceOriginSchema, templateEvidenceFields } from "./template-evidence-schema.js";
|
|
7
8
|
const TOOL_NAME = "ast_find_references";
|
|
8
9
|
const ReferenceDetailSchema = z.enum(["locations", "context"]).default("locations");
|
|
9
10
|
const AstFindReferencesInputSchema = z.object({
|
|
@@ -26,37 +27,20 @@ const ContextReferenceSchema = z.object({
|
|
|
26
27
|
context: z.string(),
|
|
27
28
|
});
|
|
28
29
|
const ContextReferenceSchemaWithOrigin = ContextReferenceSchema.extend({
|
|
29
|
-
origin:
|
|
30
|
+
origin: ReferenceOriginSchema.optional(),
|
|
30
31
|
});
|
|
31
32
|
const LocationReferenceSchema = ContextReferenceSchemaWithOrigin.omit({ context: true });
|
|
32
|
-
const TemplateCoverageSchema = z.object({
|
|
33
|
-
status: z.enum(["completed", "unfinished", "unsupported"]),
|
|
34
|
-
reason: z
|
|
35
|
-
.enum([
|
|
36
|
-
"compiler_missing",
|
|
37
|
-
"compiler_unsupported",
|
|
38
|
-
"template_unresolved",
|
|
39
|
-
"template_unsafe",
|
|
40
|
-
"template_parse_error",
|
|
41
|
-
"size_exceeded",
|
|
42
|
-
"work_exhausted",
|
|
43
|
-
"inherited_member",
|
|
44
|
-
"cross_component",
|
|
45
|
-
])
|
|
46
|
-
.optional(),
|
|
47
|
-
templates: z.number().int().min(0).optional(),
|
|
48
|
-
});
|
|
49
33
|
const ReferenceOutputBase = {
|
|
50
34
|
symbol: z.string(),
|
|
51
35
|
include_declaration: z.boolean(),
|
|
52
36
|
declaration_count: z.number().int().min(0),
|
|
53
37
|
reference_count: z.number().int().min(0),
|
|
54
38
|
affected_files: z.array(z.string()),
|
|
55
|
-
|
|
56
|
-
template_reference_count: z.number().int().min(0).optional(),
|
|
39
|
+
...templateEvidenceFields,
|
|
57
40
|
...PaginationOutputSchema,
|
|
58
41
|
};
|
|
59
|
-
|
|
42
|
+
/** Exported so a golden test can pin the output JSON schema bytes; not a tool contract. */
|
|
43
|
+
export const ReferenceOutputSchemas = {
|
|
60
44
|
locations: z.object({
|
|
61
45
|
...ReferenceOutputBase,
|
|
62
46
|
references: z.array(LocationReferenceSchema),
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Output schemas for Angular template evidence, shared by every tool that reports references. */
|
|
3
|
+
export const ReferenceOriginSchema = z.enum(["compiler", "template_external", "template_inline"]);
|
|
4
|
+
export const TemplateCoverageSchema = z.object({
|
|
5
|
+
status: z.enum(["completed", "unfinished", "unsupported"]),
|
|
6
|
+
reason: z
|
|
7
|
+
.enum([
|
|
8
|
+
"compiler_missing",
|
|
9
|
+
"compiler_unsupported",
|
|
10
|
+
"template_unresolved",
|
|
11
|
+
"template_unsafe",
|
|
12
|
+
"template_parse_error",
|
|
13
|
+
"size_exceeded",
|
|
14
|
+
"work_exhausted",
|
|
15
|
+
"inherited_member",
|
|
16
|
+
"cross_component",
|
|
17
|
+
])
|
|
18
|
+
.optional(),
|
|
19
|
+
templates: z.number().int().min(0).optional(),
|
|
20
|
+
});
|
|
21
|
+
/** Present only when template coverage applies to the symbol. */
|
|
22
|
+
export const templateEvidenceFields = {
|
|
23
|
+
template_coverage: TemplateCoverageSchema.optional(),
|
|
24
|
+
template_reference_count: z.number().int().min(0).optional(),
|
|
25
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ast-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Structural code MCP server and batch CLI for compact TypeScript/JavaScript reads and hash-bound AST edits.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"benchmark:corpus": "yarn build && node scripts/benchmark-corpus.mjs",
|
|
92
92
|
"benchmark:batch": "yarn build && node scripts/benchmark-batch.mjs",
|
|
93
93
|
"benchmark:formats": "yarn build && node scripts/benchmark-formats.mjs",
|
|
94
|
+
"benchmark:explore-templates": "yarn build && node scripts/benchmark-explore-templates.mjs",
|
|
94
95
|
"benchmark:shapes": "yarn build && node scripts/benchmark-result-shapes.mjs",
|
|
95
96
|
"benchmark:agent-workflows": "yarn build && node scripts/benchmark-agent-workflows.mjs",
|
|
96
97
|
"benchmark:index-storage": "yarn build && node scripts/benchmark-index-storage.mjs",
|
|
@@ -140,5 +141,5 @@
|
|
|
140
141
|
"vitest": "^5.0.5",
|
|
141
142
|
"yaml": "^2.9.0"
|
|
142
143
|
},
|
|
143
|
-
"gitHead": "
|
|
144
|
+
"gitHead": "9154dc7bd17f326e04c7b7e4dbfb9fcde9a66cdf"
|
|
144
145
|
}
|
|
@@ -4,7 +4,7 @@ description: "Trigger: navegar, analizar o editar TypeScript/JavaScript con el c
|
|
|
4
4
|
license: "ISC"
|
|
5
5
|
metadata:
|
|
6
6
|
author: "yail"
|
|
7
|
-
version: "4.
|
|
7
|
+
version: "4.9.0"
|
|
8
8
|
hermes:
|
|
9
9
|
tags: ["typescript", "javascript", "ast", "refactoring", "mcp"]
|
|
10
10
|
homepage: "https://github.com/yailPeralta/ast-mcp-server"
|
|
@@ -49,6 +49,11 @@ In an Angular project (`@angular/core` resolvable from the tsconfig directory),
|
|
|
49
49
|
|
|
50
50
|
Read `template_coverage` before inferring absence. Only `completed` proves every template it covers was examined. `unfinished` (for example `compiler_missing`, `compiler_unsupported`, `template_unresolved`, `template_parse_error`, `size_exceeded`, `cross_component`, `work_exhausted`) and `unsupported` (for example an inherited member) never prove that no template usage exists. `not_applicable` means the feature is off and the response is unchanged.
|
|
51
51
|
|
|
52
|
+
`ast_explore` reports the same template evidence in `evidence[].references` whenever it expands references (`detail: "full"` or `include_references: true`): each item carries `origin`, and the block adds `template_coverage` and `template_reference_count` with the verdict `ast_find_references` gives for the same symbol. None of these keys appear when coverage is `not_applicable`. Read `template_coverage` before trusting explore references, and treat every status other than `completed` as unknown.
|
|
53
|
+
|
|
54
|
+
- In `ast_explore`, `reference_limit` bounds the merged list of TypeScript and template items, even though its input description still says "compiler references". `reference_count` counts TypeScript items only, and a list cut by the limit raises the usual `reference_limit` omission. `ast_explore` has no reference offset: page the remaining items through `ast_find_references`.
|
|
55
|
+
- Any `template_coverage.status` other than `completed` adds one `template_coverage_incomplete` omission (category `incomplete`, component `references`) per symbol and sets `evidence_complete: false`. The reason code (for example `compiler_missing`) is in `template_coverage`. `omissions.details` lists the omission only when the detail limit leaves room; `omissions.counts` and `omissions.total` always count it, also when `max_bytes` drops the references block.
|
|
56
|
+
|
|
52
57
|
Consumer templates: for a member bound through the selector (`@Input`, `@Output`, `input()`, `output()`, `model()`, a literal alias, or a literal entry of the `inputs` / `outputs` metadata), the binding keys `[x]`, `x="lit"`, `[(x)]`, `(y)` and `(xChange)` are listed with kinds `TemplateInputBinding` for `[x]`, `TemplateAttribute` for `x="lit"`, `TemplateTwoWayBinding` for `[(x)]` and `TemplateOutputBinding` for `(y)` and `(xChange)` in the templates of components whose resolved scope contains the declaring component (literal standalone `imports`, or NgModule `declarations` plus the `exports` of imported modules). For a `@Pipe` class with a literal `name`, and for its `transform` member, `| name` usages in in-scope templates are listed with kind `TemplatePipeName`. A same selector or pipe name outside that scope is ignored.
|
|
53
58
|
|
|
54
59
|
For these members, `completed` means template evidence inside the Project only: every component template of the Project (the tsconfig passed as `project_root`) was read and every match resolved. `hostDirectives`, `@Directive` inputs and outputs, and non-literal scopes, aliases, metadata or pipe names stay `unfinished` with `cross_component`. Runtime bindings (`setInput`, router input binding, `createComponent` and other dynamic component APIs) and consumers outside the Project (other tsconfigs, libraries, other applications) are never examined, so `completed` does not cover them.
|
|
@@ -59,7 +64,7 @@ Pipes: renaming a `@Pipe` class applies with TypeScript edits only; the `name` l
|
|
|
59
64
|
|
|
60
65
|
Limits to state when reporting:
|
|
61
66
|
|
|
62
|
-
- `ast_explore`
|
|
67
|
+
- `ast_get_impact`, `ast_find_test_candidates` and the `ast_explore` call spines stay compiler-only: they never contain a template usage. Rename blocks on its own evidence, independently of these tools.
|
|
63
68
|
- The `diagnostics` field stays TypeScript-only. When a rename edits a template, the plan also carries `template_diagnostics`, a best-effort template type-check delta (`status`, `reason`, `added`, `removed`, `added_errors`) from the project's own `@angular/compiler-cli`; it is absent for non-Angular projects and for renames that edit no template. `completed` is the only status with data; `unavailable` (`compiler_cli_missing`, `compiler_unsupported`, `typescript_incompatible`) and `unfinished` (`work_exhausted`, `deadline`, `check_failed`) mean unknown, so run the project's build or tests before applying. `block_on_template_errors` (default `false`) blocks only a `completed` delta with `added_errors`, and `allow_new_errors` waives it. `template_diagnostics[].file` is relative to the directory of the tsconfig, while `diagnostics[].file` is relative to `project_root`. `@angular/compiler-cli` is not a declared peer and must resolve from the project, otherwise the status is `unavailable/compiler_cli_missing`. Template-editing renames persist as plan schema v3, which is rejected on import after a downgrade.
|
|
64
69
|
- Detection uses the user's own `@angular/compiler`, never a bundled one. It is an optional peer (`>=18.0.0 <23.0.0`) that the project already installs with Angular. If it is missing or out of range, coverage is `unfinished` and rename is blocked. Yarn PnP projects without `node_modules` resolve nothing and report `unfinished`.
|
|
65
70
|
|
|
@@ -22,7 +22,9 @@ Preferir `file_path` relativo; un suffix ambiguo debe devolver candidatos. `ast_
|
|
|
22
22
|
|
|
23
23
|
### Angular templates
|
|
24
24
|
|
|
25
|
-
`ast_find_references` adds template usages (`origin: "template_external" | "template_inline"`, `template_coverage`, `template_reference_count`) only when `@angular/core` resolves; otherwise the response is unchanged. Treat `template_coverage.status` other than `completed` as not proving absence. Selector-bound members (`@Input`, `@Output`, `input()`, `output()`, `model()`, literal aliases, literal `inputs` / `outputs` metadata) also list their bindings in consumer templates (kinds `TemplateInputBinding` for `[x]`, `TemplateAttribute` for `x="lit"`, `TemplateTwoWayBinding` for `[(x)]` and `TemplateOutputBinding` for `(y)` and `(xChange)`), and `@Pipe` classes and their `transform` list `| name` usages (kind `TemplatePipeName`), for components whose resolved standalone or NgModule scope contains the declaring class. For them, `completed` covers templates inside the Project only. `hostDirectives`, `@Directive` inputs and outputs, and non-literal scopes, aliases or names stay `unfinished` with `cross_component`; runtime bindings (`setInput`, router input binding, `createComponent`) and consumers outside the Project are never examined.
|
|
25
|
+
`ast_find_references` adds template usages (`origin: "template_external" | "template_inline"`, `template_coverage`, `template_reference_count`) only when `@angular/core` resolves; otherwise the response is unchanged. Treat `template_coverage.status` other than `completed` as not proving absence. Selector-bound members (`@Input`, `@Output`, `input()`, `output()`, `model()`, literal aliases, literal `inputs` / `outputs` metadata) also list their bindings in consumer templates (kinds `TemplateInputBinding` for `[x]`, `TemplateAttribute` for `x="lit"`, `TemplateTwoWayBinding` for `[(x)]` and `TemplateOutputBinding` for `(y)` and `(xChange)`), and `@Pipe` classes and their `transform` list `| name` usages (kind `TemplatePipeName`), for components whose resolved standalone or NgModule scope contains the declaring class. For them, `completed` covers templates inside the Project only. `hostDirectives`, `@Directive` inputs and outputs, and non-literal scopes, aliases or names stay `unfinished` with `cross_component`; runtime bindings (`setInput`, router input binding, `createComponent`) and consumers outside the Project are never examined.
|
|
26
|
+
|
|
27
|
+
`ast_explore` reports the same template evidence in `evidence[].references` whenever it expands references (`detail: "full"` or `include_references: true`): each item carries `origin`, and the block adds `template_coverage` and `template_reference_count` with the verdict `ast_find_references` gives for the same symbol. None of these keys appear when coverage is `not_applicable`. Read `template_coverage` before trusting explore references. In `ast_explore`, `reference_limit` bounds the merged list of TypeScript and template items, even though its input description still says "compiler references". `reference_count` counts TypeScript items only, and a list cut by the limit raises the usual `reference_limit` omission. `ast_explore` has no reference offset: page the remaining items through `ast_find_references`. Any `template_coverage.status` other than `completed` adds one `template_coverage_incomplete` omission (category `incomplete`, component `references`) per symbol and sets `evidence_complete: false`. The reason code (for example `compiler_missing`) is in `template_coverage`. `omissions.details` lists the omission only when the detail limit leaves room; `omissions.counts` and `omissions.total` always count it, also when `max_bytes` drops the references block. `ast_get_impact`, `ast_find_test_candidates` and the `ast_explore` call spines stay compiler-only: they never contain a template usage.
|
|
26
28
|
|
|
27
29
|
The `diagnostics` field stays TypeScript-only. When a rename edits a template, the plan also carries `template_diagnostics`, a best-effort template type-check delta (`status`, `reason`, `added`, `removed`, `added_errors`) from the project's own `@angular/compiler-cli`; it is absent for non-Angular projects and for renames that edit no template. `completed` is the only status with data; `unavailable` (`compiler_cli_missing`, `compiler_unsupported`, `typescript_incompatible`) and `unfinished` (`work_exhausted`, `deadline`, `check_failed`) mean unknown, so run the project's build or tests before applying. `block_on_template_errors` (default `false`) blocks only a `completed` delta with `added_errors`, and `allow_new_errors` waives it. `template_diagnostics[].file` is relative to the directory of the tsconfig, while `diagnostics[].file` is relative to `project_root`. `@angular/compiler-cli` is not a declared peer and must resolve from the project, otherwise the status is `unavailable/compiler_cli_missing`. Template-editing renames persist as plan schema v3, which is rejected on import after a downgrade.
|
|
28
30
|
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
"schema_version": 2,
|
|
3
3
|
"algorithm": "sha256",
|
|
4
4
|
"current": {
|
|
5
|
-
"version": "4.
|
|
5
|
+
"version": "4.9.0",
|
|
6
6
|
"files": [
|
|
7
7
|
{
|
|
8
8
|
"path": "SKILL.md",
|
|
9
|
-
"sha256": "
|
|
9
|
+
"sha256": "451670dcaa0bbfc11273fe9bd507de61865dbaac80e1d586306bcd30962d90d4"
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"path": "references/operations.md",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "363ccb2332a42c68f21073a568a96d3000eff98954eaebff3d92e65d87c4f14c"
|
|
14
14
|
}
|
|
15
15
|
]
|
|
16
16
|
},
|
|
@@ -205,6 +205,20 @@
|
|
|
205
205
|
}
|
|
206
206
|
],
|
|
207
207
|
"npm_versions": ["0.16.0"]
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"version": "4.8.0",
|
|
211
|
+
"files": [
|
|
212
|
+
{
|
|
213
|
+
"path": "SKILL.md",
|
|
214
|
+
"sha256": "15e5d25553a3ebce86365ff7b43ed61e7df11dd6ad658e83ec6e43cd84118762"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"path": "references/operations.md",
|
|
218
|
+
"sha256": "dd633a24e9aabeb1d9e9d1f93e261906e7b30eb721fadfb3d317d6acc60db2ca"
|
|
219
|
+
}
|
|
220
|
+
],
|
|
221
|
+
"npm_versions": ["0.17.0"]
|
|
208
222
|
}
|
|
209
223
|
]
|
|
210
224
|
}
|