@suss/adapter-typescript 0.33.1 → 0.34.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/README.md CHANGED
@@ -2,51 +2,61 @@
2
2
 
3
3
  Part of [suss](https://github.com/nimbuscloud-ai/suss), which reads both sides of every call in a repository and says where the two disagree.
4
4
 
5
- TypeScript language adapter for suss. It extracts behavioral structure from TypeScript source using ts-morph.
5
+ The TypeScript language adapter for suss. It extracts behavioral structure from TypeScript source using ts-morph.
6
6
 
7
7
  ## What this package is
8
8
 
9
- `@suss/adapter-typescript` is the TypeScript language adapter. It walks ASTs via ts-morph, identifies code units (handlers, loaders, actions, client call sites), and emits `RawCodeStructure` objects that the extractor assembles into `BehavioralSummary` IR.
9
+ `@suss/adapter-typescript` is the TypeScript language adapter. It walks ASTs with ts-morph and identifies code units such as handlers, loaders, actions and client call sites. It emits `RawCodeStructure` objects, and the extractor assembles those into `BehavioralSummary` IR.
10
10
 
11
- It supports both provider-side extraction (handler registration, terminal discovery, contract reading, body-shape extraction) and client-side extraction (call-site discovery, enclosing-function lifting, response field tracking via `expectedInput`).
11
+ It handles both sides of a boundary. On the provider side it reads handler registration, terminals, contracts and body shapes. On the client side it finds call sites, lifts them to the enclosing function, and tracks which response fields the caller reads through `expectedInput`.
12
12
 
13
13
  ## Where it fits in suss
14
14
 
15
- It imports `@suss/behavioral-ir` for type references and `@suss/extractor` for the `RawCodeStructure` contract. Framework packs and the CLI consume it. It runs one level above the extractor in the pipeline, feeding it raw structures produced from TypeScript AST analysis.
15
+ It imports `@suss/behavioral-ir` for type references and `@suss/extractor` for the `RawCodeStructure` contract. Framework packs and the CLI consume it. In the pipeline it runs one level above the extractor, and feeds it the raw structures it builds from the TypeScript AST.
16
16
 
17
17
  ## Reading a value
18
18
 
19
- Nothing outside the facility reads the syntax to find out what a value is. `discovery/resolveValue.ts` is the entry point most callers want: `stringValueOf` folds a template and follows a name to the string it was written as, `objectLiteralOf` and `propertiesOf` read an object through whatever name it arrived under and fold a spread into it, `functionValueOf` and `writtenNodeOf` say which function or which expression a reference comes to. Under those sit the `ResolutionStore` in `facts/store.ts` (`resolveWrittenValue`, `resolveObject`, `resolveCallable`, `argumentsPassedTo`, `importedNamesOf`, `importOriginsOf`, `exportsOf`), `resolve/functionBehind.ts`, `walk/unwrap.ts` for casts and parentheses, and `discovery/importScan.ts` for imports. Packs reach all of it through this package's exports. When the facility cannot read a spelling, the case goes into the facility; `npm run check:readers` at the repo root fails on a reader written beside a call site instead. [`design/docs-internal/style.md#reading-a-value`](../../../design/docs-internal/style.md#reading-a-value) has the rule.
19
+ Code outside the facility never reads the syntax to find out what a value is. `discovery/resolveValue.ts` is the entry point most callers want. `stringValueOf` folds a template and follows a name to the string it was written as. `objectLiteralOf` and `propertiesOf` read an object through whatever name it arrived under and fold a spread into it. `functionValueOf` and `writtenNodeOf` return the function or the expression a reference comes to. Under those are the `ResolutionStore` in `facts/store.ts` (`resolveWrittenValue`, `resolveObject`, `resolveCallable`, `argumentsPassedTo`, `importedNamesOf`, `importOriginsOf`, `exportsOf`), `resolve/functionBehind.ts`, `walk/unwrap.ts` for casts and parentheses, and `discovery/importScan.ts` for imports. Packs reach all of it through this package's exports. When the facility cannot read a spelling, add the case to the facility. `npm run check:readers` at the repo root fails on a reader written beside a call site instead. [`design/docs-internal/style.md#reading-a-value`](../../../design/docs-internal/style.md#reading-a-value) has the rule.
20
20
 
21
21
  ## How a project is loaded
22
22
 
23
- ts-morph parses every file in the tsconfig include glob when a `Project` is constructed, which on a monorepo is thousands of files the extraction never touches. The bootstrap avoids that in four steps:
23
+ When a `Project` is constructed, ts-morph parses every file in the tsconfig include glob. On a monorepo that is thousands of files the extraction never touches. The bootstrap avoids that in four steps:
24
24
 
25
25
  1. Parse the tsconfig for its file list, without any AST work.
26
26
  2. Read each file concurrently and run `ts.preProcessFile`, a token scan roughly ten times cheaper than a parse.
27
27
  3. Keep the files whose imports match some active pack's `requiresImport` gate.
28
28
  4. Add only those to the `Project`.
29
29
 
30
- The closure pass loads the rest as symbol resolution reaches them. It will only lazy-load files the tsconfig already knew about, so a run never pulls in `node_modules` content nobody asked for.
30
+ The closure pass loads the rest as symbol resolution reaches them. It only lazy-loads files the tsconfig already listed, so a run never pulls in `node_modules` content nobody asked for.
31
31
 
32
32
  ### Deep re-export chains
33
33
 
34
- The compiler finds files by recursing. It reads a file, resolves that file's imports, then reads each of those the same way. If you hand it one file at the top of a long chain, it will descend the entire chain in a single pass, and a barrel chain a few hundred modules deep will overflow the call stack. A gated run makes that likely, because the bootstrap only loaded the entry file.
34
+ The compiler finds files by recursion. It reads a file, resolves that file's imports, then reads each of those the same way. Give it one file at the top of a long chain and it descends the entire chain in a single pass, so a barrel chain a few hundred modules deep overflows the call stack. A gated run makes that likely, because the bootstrap only loaded the entry file.
35
35
 
36
- Two passes get ahead of the compiler. Both walk the graph iteratively, starting from the far end, so that by the time the compiler asks about any module, the one below it has already been resolved.
36
+ Two passes do the compiler's work ahead of it. Both walk the graph iteratively, starting from the far end, so that whenever the compiler asks about a module, the module below it is already resolved.
37
37
 
38
- - **Loading.** Before a run walks any file, we load that file's import graph bottom up, so nothing is added until everything it imports is already there. We work out the graph by scanning tokens and resolving module paths, never by building a program. We only load files the project could already reach; anything that resolves under `node_modules` we leave for whoever asks for it. All the roots share a single visited set, so a file that several roots reach gets read once instead of once per root. On a gated run this pass is also what puts the entry files into the project: the walked-file list is the bootstrap's candidate list, fixed before anything loads, and the load order puts each candidate after everything it imports. That order matters to the compiler itself. The program build processes files in the order they arrived and recurses into imports it has not seen, so a chain entered from its top costs stack depth equal to its length, while the same chain entered bottom up costs one frame per file.
39
- - **Alias warming.** Once the graph is loaded, we resolve each file's import bindings and export specifiers from the bottom up, so `export { x } from "./next"` never puts a whole chain on the stack. We only do this for graphs at least `WARM_DEPTH` deep. That number is well below the depth the compiler manages on its own, and well above anything an ordinary barrel file reaches, so all it decides is where we spend time. Warming changes the order things resolve in, never what they resolve to.
38
+ - **Loading.** Before a run walks any file, the adapter loads that file's import graph bottom up, so nothing is added until everything it imports is already there. It works out the graph by scanning tokens and resolving module paths, and never builds a program to do it. It only loads files the project could already reach, and leaves anything that resolves under `node_modules` for whoever asks for it. All the roots share a single visited set, so a file that several roots reach is read once. On a gated run this pass is also what puts the entry files into the project. The walked-file list is the bootstrap's candidate list, fixed before anything loads, and the load order puts each candidate after everything it imports. The compiler itself depends on that order. The program build processes files in the order they arrived and recurses into imports it has not seen. A chain entered from its top therefore costs stack depth equal to its length, and the same chain entered bottom up costs one frame per file.
39
+ - **Alias warming.** Once the graph is loaded, the adapter resolves each file's import bindings and export specifiers from the bottom up, so `export { x } from "./next"` never puts a whole chain on the stack. It only does this for graphs at least `WARM_DEPTH` deep. That number is well below the depth the compiler manages on its own, and well above anything an ordinary barrel file reaches, so it only decides where the time goes. Warming changes the order things resolve in, and never what they resolve to.
40
40
 
41
- If the warmed compiler still cannot follow a chain, we record that rather than throwing. A provider whose exports we could not read would otherwise look exactly like one that exports nothing, so we note the file in the extraction report and let the run finish with the summaries it has.
41
+ If the warmed compiler still cannot follow a chain, the adapter records that and does not throw. A provider whose exports could not be read would otherwise look the same as one that exports nothing. So the adapter notes the file in the extraction report and lets the run finish with the summaries it has.
42
42
 
43
43
  ## Extraction cache
44
44
 
45
- This adapter uses the extraction cache shared across every language adapter, from `@suss/extractor`. It supplies the files by reading the tsconfig's include list, and supplies the tsconfig path as the config path that guards the entry alongside the file list. Choosing the cache directory is still each adapter's own job: this one keeps it beside the tsconfig, at `.suss/cache/`, and turns caching off for a caller-supplied `Project` or for `--no-cache` on one run. It also reads mount prefixes back out of each cached file's record to confirm a route's mount still resolves the same way before reusing it. See the extractor package's README for the design.
45
+ This adapter uses the extraction cache from `@suss/extractor` that every language adapter shares. It supplies the files by reading the tsconfig's include list, and gives the tsconfig path as the config path that guards the entry along with the file list. Each adapter still chooses its own cache directory. This one keeps it beside the tsconfig, at `.suss/cache/`. A caller that supplies a `Project` with no tsconfig gets no cache unless it passes a directory, and `--no-cache` turns it off for one run. Before reusing a cached route, it reads the mount prefixes back out of each cached file's record to confirm the route's mount still resolves the same way. Each file's record also lists the project files it imports, and after an edit that added and removed no file, the import graph loader takes an unchanged file's imports from there instead of resolving them again. The reachable closure keeps what each function body's scan found as a unit record, described in `src/resolve/README.md`. See the extractor package's README for the design.
46
+
47
+ ## Keeping a project between runs
48
+
49
+ The MCP server reads a project again after every edit, and it keeps one adapter per project rather than building one per run. Before each run it calls `refresh`, which stats every file the project has loaded, compares a file whose stamp moved with the text ts-morph parsed, and parses again only the files whose text changed. A file changed in the two seconds before the stat is compared whatever its stamp says, since a second write in the same clock tick can keep the stamp. The compiler builds the next program from the old one, so every other file keeps its parse and its binding. A run that misses the extraction cache refreshes once more after the cache's lookup, because the cache records the stamps that lookup took.
50
+
51
+ What was worked out from a changed file goes with it. The load walk forgets the file's imports and the chain depth of every file that reaches it, and every memo made with `createProgramMemo` is emptied, since a memo like that can rest on any file. A result that depends on one file's text alone uses `createPerFileCache`, which expires when that file is parsed again.
52
+
53
+ A file that joins or leaves the tsconfig's include set can change where an unchanged file's imports resolve, and a tsconfig edit changes the compiler options. In either case the adapter starts over with a new `Project`, and the run costs what a run with a warm disk cache costs.
54
+
55
+ A run served whole from the cache loads nothing, so `loadProgram` loads the walked files and builds the program ahead of time. The server calls it after a build while nobody is waiting.
46
56
 
47
57
  ## Status
48
58
 
49
- Stable. Public API: `createTypeScriptAdapter` returns an adapter with `extractFromFiles` and `extractAll` methods. Provider-side extraction (handlers, terminals, contracts, body shapes) and client-side extraction (call sites, response field tracking) are both supported. See [`docs/theory/extraction-algorithm.md`](../../../docs/theory/extraction-algorithm.md) for the algorithm and [`design/status.md`](../../../design/status.md) for the capability matrix.
59
+ Stable. Public API: `createTypeScriptAdapter` returns an adapter with `extractFromFiles` and `extractAll` methods, and `refresh` and `loadProgram` for a process that keeps it between runs. Provider-side extraction (handlers, terminals, contracts, body shapes) and client-side extraction (call sites, response field tracking) are both supported. See [`docs/theory/extraction-algorithm.md`](../../../docs/theory/extraction-algorithm.md) for the algorithm and [`design/status.md`](../../../design/status.md) for the capability matrix.
50
60
 
51
61
  ## More
52
62