Skip to content

refactor(cdk-explorer): rebuild the core on the assembly index - #2057

Merged
rix0rrr merged 16 commits into
mainfrom
explorer_3_explorer_refactor
Oct 26, 2026
Merged

rix0rrr merged 16 commits into
mainfrom
explorer_3_explorer_refactor

Conversation

@rix0rrr

@rix0rrr rix0rrr commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Move the LSP Explorer's loading layer onto the newly added AssemblyIndex.

Source locations and path handling

This adds an explorer-specific AssemblyIndexWithSourceLocations on top which manages source mapping on top of AssemblyIndex. It keeps track of the definition locations of constructs in source files and lines.

There is a security concern that the Explorer should only be allowed to point to file paths below the application and assembly directories. In order to avoid confusion between absolute paths and relative paths, this change introduces type brands for RelativePath and AbsolutePath, and introduces functions to map between them and to (absolute) URLs.

Other changes

  • fromCdkAppDirectory(), a Cloud Assembly Source builder that reads cdk.json and configures itself based on that.
  • Centralized the handling of waiting for a lock into a helper with a callback.
  • We load all source maps in parallel instead of on-demand.
  • Template parsing moves from cloud-assembly-api into cdk-explorer.
  • Sliiiightly update the rendering of code lenses and hovers to be a bit nicer, but didn't do a lot of work here just yet.
  • Dedupe diagnostics with the same description; normally if a line of code is executed 10 times it leads to the same diagnostic 10 times; after this change we will only show it once.
  • There was a lot of duplication around assembly loading code, thas been removed.

This leaves construct-tree.ts and template-ranges.ts in cloud-assembly-api without callers; a follow-up removes them.


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

OpenSSF Scorecard

PackageVersionScoreDetails
npm/p-limit ^3 UnknownUnknown

Scanned Files

  • packages/@aws-cdk/cdk-explorer/package.json

for (const vc of violation.violatingConstructs ?? []) {
const traced = index.traceViolation(vc);
for (const loc of traced.sourceLocations) {
yield loc.file;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

Comment thread packages/@aws-cdk/cdk-explorer/lib/core/from-cdk-app-directory.ts Outdated

@mrgrain mrgrain left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor stuff, mostly good

@maintainer-for-aws

maintainer-for-aws Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Automated review

A maintainer will still review this — treat the notes below as a starting point.

📌 Commit 120c1be

This PR is a large internal refactor of the cdk-explorer LSP server, rebuilding its loading and source-mapping core on top of the shared AssemblyIndex (merged separately): it replaces the hand-rolled construct tree and bespoke read lock with a thin AssemblyIndexWithSourceLocations wrapper, centralizes locking behind a retryOnLock backoff helper, adds branded RelativePath/AbsolutePath path-safety types, moves template and JSON parsing into the package, and rewrites the synth concurrency latch. The path-safety brands, the lock-retry helper, the queue/latch design, and the new strict JSON parser hold up well under review.

The one blocking concern is the synth debug-flag change: swapping the explicit JSII_HOST_STACK_TRACES=1 env for synthOptions: { debug: true } does not set that env var in the toolkit-lib path (the debug key only enables the debug env block once promoted to debugApp, which happens only in the CLI layer), so the creation-stack-trace forwarding the entire source-mapping feature depends on appears to break — and the test that guarded it was weakened to a misleading name. Several secondary behavior changes also go unmentioned in the PR body: untraceable policy violations now drop with no diagnostic and no warning, and the assembly directory read no longer tolerates missing context. Please confirm the debug-env behavior against a real Toolkit before merge, along with the dropped-context and host-language changes.

🔴 1 blocking · 🟡 6 recommended · ⚪ 4 optional

Files with findings (8)
File Findings
packages/@aws-cdk/cdk-explorer/lib/core/synth-runner.ts 🔴 1 🟡 1
packages/@aws-cdk/cdk-explorer/lib/core/assembly-reader.ts 🟡 1
packages/@aws-cdk/cdk-explorer/lib/core/read-cdk-assembly.ts 🟡 1
packages/@aws-cdk/cdk-explorer/lib/lsp/diagnostics.ts 🟡 1 ⚪ 1
packages/@aws-cdk/cdk-explorer/lib/lsp/server.ts 🟡 1
packages/@aws-cdk/cdk-explorer/test/core/synth-runner.test.ts 🟡 1
packages/@aws-cdk/cdk-explorer/lib/core/source-resolver.ts ⚪ 2
packages/@aws-cdk/toolkit-lib/lib/api/rwlock.ts ⚪ 1

Generated automatically. React 👍 or 👎 to tell us whether this review helped, so we can improve these reviews.

@maintainer-for-aws maintainer-for-aws Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review summary comment for the overview; the notes below are inline.

Comment thread packages/@aws-cdk/cdk-explorer/lib/core/assembly-reader.ts
Comment thread packages/@aws-cdk/cdk-explorer/lib/core/source-resolver.ts

@maintainer-for-aws maintainer-for-aws Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review summary comment for the overview; the notes below are inline.

Comment thread packages/@aws-cdk/cdk-explorer/lib/lsp/server.ts Outdated

@maintainer-for-aws maintainer-for-aws Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review summary comment for the overview; the notes below are inline.

Comment on lines 78 to +86
/** Resolve a violation to a presentable anchor, or a reason it can't be shown. */
function anchorViolation(
constructPath: string | undefined,
index: ConstructIndex<ConstructNode>,
): { readonly uri: string; readonly range: Range } | { readonly reason: string } {
const loc = resolveLocation(constructPath, index);
if ('reason' in loc) return loc;
violation: ViolatingConstructJson,
index: AssemblyIndexWithSourceLocations,
): Result<{ readonly uri: string; readonly range: Range }[]> {
const traced = index.traceViolation(violation);
return traced.sourceLocations.map((loc) => {
return { uri: pathToFileURL(index.appDir.absolute(loc.file)).toString(), range: toRange(loc) };
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Recommended — A violation whose traced sourceLocations is empty is silently discarded — it produces neither a diagnostic nor a dropped entry. anchorViolation returns traced.sourceLocations.map(...), so an empty list yields []; isFailure([]) is false, so the located branch runs with nothing to append and the dropped path is never taken. The doc comment above mapViolationsToDiagnostics still asserts such violations "are dropped (with a reason) rather than thrown," which is now inaccurate, and server.ts logs every dropped entry via log.warn, so a framework-only or unresolved violation now vanishes without any trace to the user.

Suggested change: Treat an empty sourceLocations as a drop with a reason — e.g. have anchorViolation return { reason: 'no source location (framework-only trace or unresolved source)' } when traced.sourceLocations.length === 0, so the existing dropped/log.warn path still surfaces it; or, if the silence is intentional, update the comment to match.

*/
export async function retryOnLock(abort: AbortSignal, fn: () => Promise<IReadableCloudAssembly>) {
let delayMs = 500;
const deadline = AbortSignal.any([abort, AbortSignal.timeout(60 * 60 * 1000)]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Recommended — retryOnLock calls AbortSignal.any([...]), which is only available from Node.js 18.17.0 (and 20.3.0), but this package declares engines.node as >= 18.0.0. On a Node 18.0–18.16 runtime the first lock-retry throws TypeError: AbortSignal.any is not a function, and retryOnLock sits on both the primary read path (readAssemblyDirectory) and the synth path, so the explorer would fail to read or synth an assembly there. The declared engine contract and the code's actual requirement disagree.

Suggested change: Confirm the intended minimum Node version. If Node 20 is the real target, raise engines.node accordingly via .projenrc.ts; if 18.0 must be supported, compose the deadline without AbortSignal.any (e.g. a manual setTimeout-driven AbortController plus an abort listener).

@@ -33,7 +25,7 @@ export class SourceMapResolver {
* the source maps they point at), which are attacker-influenceable if
* cdk.out is tampered with, so anything escaping the root is dropped

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Optional — The SourceMapResolver constructor's JSDoc documents @param projectRoot - Absolute path to the project..., but the constructor's only parameter is index: AssemblyIndexWithSourceLocations. The projectRoot parameter no longer exists on this constructor, so the comment describes a signature that isn't there and misleads the next reader about what the resolver takes.

Suggested change: Replace the @param projectRoot block with one describing index; the attacker-influence rationale is still valuable — re-key it to the actual parameter or move it to the class-level doc.

@maintainer-for-aws maintainer-for-aws Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review summary comment for the overview; the notes below are inline.

Comment on lines +99 to 105
return result;
}

/**
* LSP range for a source location. Anchors at the resolved line/column, or the
* top of the file when line/column aren't known. LSP positions are 0-based;
* sourceLocation is 1-based. The end spans the rest of the line.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Optional — The toRange doc comment still describes column handling ("Anchors at the resolved line/column … LSP positions are 0-based; sourceLocation is 1-based") and a hasLineCol variable, but this change removed column entirely: toRange(loc: { line: number }) always sets character: 0. The comment now describes behavior the function no longer has, which will mislead the next reader into thinking a column is honored.

Suggested change: Drop the "column" language from the comment and state that the anchor is always at column 0 by design, reflecting the deliberate column drop.

@maintainer-for-aws maintainer-for-aws Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review summary comment for the overview; the notes below are inline.

Comment on lines +41 to +50
}
const controller = new AbortController();
for (const signal of signals) {
// If we start aborted, no need to add the event listener.
if (signal.aborted) {
controller.abort();
break;
}
signal.addEventListener('abort', () => controller.abort());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Recommended — The anyAbortSignal polyfill registers signal.addEventListener('abort', …) on each input signal but never removes the listeners. It is called by retryOnLock on every synth and every assembly refresh, always against the single process-lifetime shutdown signal, so over an active editing session (save → synth, cdk.out change → refresh) these listeners accumulate without bound on one AbortSignal, eventually tripping Node's MaxListenersExceededWarning and holding references for the life of the server. The fresh one-hour AbortSignal.timeout created per retryOnLock call likewise accumulates. This polyfill branch is the real runtime path on Node 18.0–18.16, since AbortSignal.any only landed in 18.17.0 while the repo supports Node >= 18.0.0.

Suggested change: Register the listeners with { once: true } and, when the combined controller aborts, remove any still-attached listeners (track them and clean up in a finally/abort handler); consider creating the one-hour deadline once per server rather than per retryOnLock call.

this.pidString = `${process.pid}`;

this.writerFile = path.resolve(this.directory, 'synth.lock');
this.writerFile = path.resolve(this.directory, SYNTH_LOCK_FILE);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Optional — This change only extracts the literal 'synth.lock' into a module-private const SYNTH_LOCK_FILE used solely by the existing constructor. It is not exported and nothing in cdk-explorer imports it, so within this PR it is a pure no-op rename in a different package from the one the change is scoped to. It is harmless, but it is an unrelated hunk (AGENTS.md#pr-conventions, "one concern per PR"). Worth confirming it is intended groundwork for a later change in the stack rather than a stray edit; if it has no consumer here, consider dropping it or splitting it out.

Suggested change: Confirm this constant extraction is intended groundwork for a later change in the stack; if it has no consumer in this PR, drop it or split it into its own change.

@maintainer-for-aws maintainer-for-aws Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review summary comment for the overview; the notes below are inline.

Comment on lines 50 to +57
export async function runSynth(options: SynthRunnerOptions): Promise<SynthRunResult> {
const app = readCdkConfig(options.projectDir).app;
if (app === undefined) return { status: 'unavailable' };

let cached;
let asm;
try {
const cx = await options.toolkit.fromCdkApp(app, {
workingDirectory: options.projectDir,
const cx = await options.toolkit.fromCdkAppDirectory(options.projectDir, {
lookups: false,
// Make jsii forward host-language (Python/Java) source frames; TS resolves via source maps.
env: { JSII_HOST_STACK_TRACES: '1' },
synthOptions: {
debug: true,
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocking — The refactor replaces fromCdkApp(app, { env: { JSII_HOST_STACK_TRACES: '1' } }) with fromCdkAppDirectory(projectDir, { synthOptions: { debug: true } }), relying on debug: true to make the synthesized app emit construct creation-stack traces. Those traces are the sole input the assembly index parses into source locations — the basis of every code lens, hover, go-to-definition, and policy diagnostic this package produces. In toolkit-lib, synthParametersFromSettings gates both CDK_DEBUG and JSII_HOST_STACK_TRACES behind the debugApp setting, never debug; the debug→debugApp expansion lives only in the CLI package. So synthOptions: { debug: true } appears to set neither env var, leaving creationStack empty for every node and the Explorer with nothing to anchor. The accompanying unit test was changed to assert nothing about the environment and runs against a fake toolkit, so it cannot catch this.

Suggested change: Verify against a real Toolkit (not the fake) that debug: true actually yields creation stacks; if it does not, pass the setting synthParametersFromSettings reads (debugApp) or set CDK_DEBUG/JSII_HOST_STACK_TRACES through a path toolkit-lib honors, and restore a test asserting the env the app process receives.

Comment on lines 50 to +57
export async function runSynth(options: SynthRunnerOptions): Promise<SynthRunResult> {
const app = readCdkConfig(options.projectDir).app;
if (app === undefined) return { status: 'unavailable' };

let cached;
let asm;
try {
const cx = await options.toolkit.fromCdkApp(app, {
workingDirectory: options.projectDir,
const cx = await options.toolkit.fromCdkAppDirectory(options.projectDir, {
lookups: false,
// Make jsii forward host-language (Python/Java) source frames; TS resolves via source maps.
env: { JSII_HOST_STACK_TRACES: '1' },
synthOptions: {
debug: true,
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Recommended — This hunk drops the explicit JSII_HOST_STACK_TRACES: '1' env var (commented "Make jsii forward host-language (Python/Java) source frames"), and the companion host-language branch in source-resolver.ts is removed too. Even if TypeScript source mapping is restored, JSII_HOST_STACK_TRACES is only set by toolkit-lib's synthParametersFromSettings under debugApp, so a Python/Java user who previously got go-to-source silently loses it. The PR is titled as a feature-preserving refactor, yet this is an observable behavior change with no stated justification.

Suggested change: Confirm whether host-language support is intentionally dropped. If so, state it in the PR body; if not, keep forwarding host frames so Python/Java source anchoring continues to work.

@maintainer-for-aws maintainer-for-aws Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review summary comment for the overview; the notes below are inline.

Comment on lines +28 to +31

export async function readAssemblyDirectory(toolkit: Toolkit, abort: AbortSignal, assemblyDir: string) {
const asmSource = await toolkit.fromAssemblyDirectory(assemblyDir);
// NOTE: Purposely using asmSource.produce() here: toolkit.synth() would throw on validation failures and we don't necessarily want that.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Recommended — readAssemblyDirectory calls toolkit.fromAssemblyDirectory(assemblyDir) with no options, but the code this replaces passed { failOnMissingContext: false }. In toolkit-lib AssemblyDirectoryProps.failOnMissingContext defaults to true, so produce() now throws when the assembly's manifest.missing is non-empty — a common state for any cdk.out synthesized with unresolved context lookups. The server catches that rejection into its error branch, which only logs and returns with no cached index, so the Explorer silently shows no constructs, lenses, or diagnostics for that project. The PR body does not mention this behavior change.

Suggested change: Pass { failOnMissingContext: false } to toolkit.fromAssemblyDirectory(...) in readAssemblyDirectory, restoring the prior intent — or, if failing on missing context is deliberate for the Explorer, say so in the PR body.

@@ -23,17 +15,10 @@ export interface SourceLocation {
*/
export class SourceMapResolver {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Optional — The comment reads // Parsed source maps keyed by absolute .js path; null = known to have no map., but the cache is written by loadTraceMap(jsFile: RelativePath) and read by reverseMapJs via this.cache.get(loc.file) — both using the assembly-relative path, not an absolute one — and the "no map" sentinel is undefined, not null. The comment misleads the next reader about both the key space and the sentinel value.

Suggested change: Update the comment to: "Parsed source maps keyed by assembly-relative .js path; undefined = known to have no map."

@maintainer-for-aws maintainer-for-aws Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review summary comment for the overview; the notes below are inline.

Comment on lines 65 to +73
describe('runSynth', () => {
test('reads the app from cdk.json, returns success, and disposes the cached assembly', async () => {
test('reads the app from cdk.json, sets the right environment variables, returns success, and disposes the cached assembly', async () => {
const { toolkit, cached } = makeToolkit({});
const projectDir = makeProjectDir({ app: APP });

const result = await run(toolkit, projectDir);
// WHEN
await run(toolkit, projectDir);

expect(result).toEqual({ status: 'success' });
expect(toolkit.fromCdkApp).toHaveBeenCalledWith(APP, { workingDirectory: projectDir, lookups: false, env: { JSII_HOST_STACK_TRACES: '1' } });
// THEN

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Optional — The first test is titled "reads the app from cdk.json, sets the right environment variables, returns success, and disposes the cached assembly", but after the rewrite it no longer asserts any environment variable — the old JSII_HOST_STACK_TRACES assertion was removed and nothing replaced it, and the fake fromCdkAppDirectory does not inspect synthOptions. The title now describes a check the test does not perform, hiding the untested env-var regression behind an inaccurate name.

Suggested change: Either assert the env/synthOptions the runner now passes, or drop the "sets the right environment variables" clause from the title so it matches what the test checks.

Comment on lines 221 to +260
}
}

// One synth, retrying the write lock with exponential backoff + full jitter.
// The latch guarantees we never contend with our own synth, so a lock-conflict
// here is an external writer (a terminal `cdk synth`/`cdk watch`) holding
// cdk.out; retry until it releases rather than dropping the synth.
async function runSynthWithRetry(): Promise<SynthRunResult> {
let delayMs = SYNTH_LOCK_RETRY_BASE_MS;
const deadline = Date.now() + 3_600_000; // 1 hour
while (Date.now() < deadline) {
const result = await (synthRunner ? synthRunner(currentProjectDir()) : Promise.resolve({ status: 'error', message: 'No synth runner configured' } as const));
if (result.status !== 'lock-conflict') {
publishSynthDiagnostics(result);
return result;
}
await sleep(Math.random() * delayMs);
delayMs = Math.min(delayMs * 2, SYNTH_LOCK_RETRY_MAX_MS);
}
const timeoutResult: SynthRunResult = { status: 'error', message: 'Auto-synth timed out waiting for external writer to release cdk.out' };
return timeoutResult;
}
// Concurrency latch, mirroring `cdk watch` (toolkit.ts): a synth requested
// while one runs is batched into a single follow-up, so synths run one at a
// time and the assembly catches up to the latest save.
let synthLatch: SynthLatch = 'open';
const synthListeners: Array<() => void> = [];

/**
* Queue a new synth
*
* Start it immediately if we are not currently synthing; otherwise, another instance
* of this async function is already running. Mark the latch as
* queued and the existing instance will check it in a loop.
*
* Returns a Promise that resolves once the queued synth has completed.
*/
function queueSynth() {
const ret = new Promise<void>((resolve) => {
synthListeners.push(resolve);
});

// Shared by the manual CodeLens command and auto-synth-on-save. A call while a
// synth runs is batched (latch 'queued') and returns lock-conflict now, so the
// caller still sees "in progress" while the assembly catches up to the latest
// save. Any number of overlapping calls collapse into that one follow-up.
async function guardedSynth(): Promise<SynthRunResult> {
if (synthLatch !== 'open') {
if (synthLatch === 'open') {
void runSynthLoop();
} else {
synthLatch = 'queued';
return { status: 'lock-conflict' };
}
synthLatch = 'synthing';
try {
// Drain the queued slot with a loop (not recursion) so the stack stays
// flat. There is no await between reading the latch as not-'queued' and the
// finally resetting it to 'open', so under the single-threaded event loop
// any request that set 'queued' did so during an await and is observed
// here -- no wakeup is lost.
let result = await runSynthWithRetry();
while ((synthLatch as SynthLatch) === 'queued') {
synthLatch = 'synthing';
result = await runSynthWithRetry();

return ret;

async function runSynthLoop() {
try {
do {
synthLatch = 'synthing' as SynthLatch;

// Get the current listeners and notify them once we are done, successfully
// or otherwise.
const currentListeners = synthListeners.splice(0, synthListeners.length);
try {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Recommended — When a synth is queued during a running synth, its resolver is pushed onto synthListeners and the loop drains and resolves listeners each iteration. But the loop's continuation guard is while (synthLatch === 'queued' && !shutdown.signal.aborted). If shutdown.abort() fires while a synth is queued but before the loop takes its next iteration, the loop exits without resolving the already-queued listener, so that queueSynth() promise never resolves. The manual COMMAND_SYNTH_NOW path awaits this promise through notify.withProgress(...), so on shutdown-during-queued-synth the progress indicator can hang unresolved.

Suggested change: In the finally that sets synthLatch = 'open', also drain and resolve any remaining synthListeners so queued callers are released on abort.

rix0rrr and others added 16 commits October 23, 2026 14:44
Move the explorer's core layer onto `AssemblyIndex` and reshape it so
the LSP (and a future web UI) share one read model of a synthesized app.

- `AssemblyIndexWithSourceLocations` wraps the index with source-location
  resolution, source-map reverse mapping and a line-to-constructs lookup
  per source file
- paths are branded (`RelativePath` + `PathTranslator`), so app-relative,
  assembly-relative and absolute paths can no longer be mixed up, and
  `safe-url.ts` is the only way to turn one into a URL (enforced by a
  `no-restricted-imports` rule)
- `source-watcher.ts` watches authored sources; `from-cdk-app-directory.ts`
  centralizes opening an app directory
- `template-ranges.ts` is copied in from `cloud-assembly-api`, which has
  no other caller for it
- `assembly-lock.ts` is gone: toolkit-lib now exports `SYNTH_LOCK_FILE`,
  which is all the watcher needed
- LSP features (hover, codelens, diagnostics, template locator, server)
  are rewritten against the new core, with construct summaries split out
  into `summarize.ts` and logging into `log-sink.ts`
- test fixtures replaced by `_helpers/assembly.ts` + `_helpers/fake-toolkit.ts`

This leaves `construct-tree.ts` and `template-ranges.ts` in
`cloud-assembly-api` without callers; a follow-up removes them.
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

Total lines changed 3136 is greater than 1000. Please consider breaking this PR down.

This branch had an error being deployed

1 failed (outdated), 1 active, and 1 inactive deployments
run-tests — 120c1be6 Deployed Oct 23, 2026 by rix0rrr via integ_cli (cli-integ-tests, 24.19, 4) #7439
no-approval — 120c1be6 Deployed Oct 23, 2026 by rix0rrr via prepare #7439
automation — 1f656885 Deployed Oct 23, 2026 by rix0rrr via Set AutoQueue on PR #2057 #3852
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants