R-WP10 — Framework-agnostic adaptation feasibility brief
R-WP10 — Framework-agnostic adaptation feasibility brief
Section titled “R-WP10 — Framework-agnostic adaptation feasibility brief”Status: INVESTIGATION — S7 (18/05/2026) Authored by: kh-ast-S7 workflow executor Scope: Evaluate what structural changes would be needed to make the ast-dataflow tool portable beyond Next.js; assess the Vite use case specifically. Not in scope: Implementation of any abstraction or adaptor.
Why this brief exists
Section titled “Why this brief exists”The ROADMAP.md Wave 3 row for R-WP10 reads:
Investigate what would be required to adapt the tool to be framework agnostic. Immediate requirement beyond Next.js is Vite. Allows dogfooding across our other non-KH projects.
The tool currently runs against the Knowledge Hub corpus, which is a
Next.js 16 (App Router) project. Liam and Claude also build non-KH
projects — in particular Vite-based apps — and the cross-project leverage
pitch in the ROADMAP introduction (same Supabase + Next.js stack is shared across Knowledge Hub, sales-proposals, and other Liam-and-Claude projects)
motivates asking whether the ast-dataflow tool is genuinely portable or has
invisible Next.js coupling baked in.
This brief answers four questions posed by R-WP10:
- Where does the current implementation assume Next.js? (with file paths and line ranges)
- What changes when the target is a Vite project? (the delta)
- What abstraction surface would bridge the gap? (three candidate approaches compared)
- How much effort would the work cost? (banded estimate with rationale)
1. Current Next.js assumptions (file paths and line ranges)
Section titled “1. Current Next.js assumptions (file paths and line ranges)”The following survey examined every file in lib/ast-dataflow/ and
scripts/ast-dataflow-cli.ts. The conclusion is deliberately nuanced:
the query library itself has almost no Next.js coupling, but the project
bootstrap has two hard Next.js assumptions that would silently break on a
non-Next project, and there are three soft assumptions in the corpus scope
definition that Vite projects violate.
1.1 Hard assumption — tsconfig.json plugins: [{ "name": "next" }]
Section titled “1.1 Hard assumption — tsconfig.json plugins: [{ "name": "next" }]”File: tsconfig.json lines 16–20 (project root)
"plugins": [ { "name": "next" }],File: scripts/ast-dataflow-cli.ts lines 204–206
const repoRoot = process.cwd();const tsConfigFilePath = resolve(repoRoot, 'tsconfig.json');const { project } = createProject({ tsConfigFilePath, repoRoot });File: lib/ast-dataflow/index.ts lines 47–53
export function createProject(opts: CreateProjectOptions): AstProject { const project = new Project({ tsConfigFilePath: opts.tsConfigFilePath, skipAddingFilesFromTsConfig: false, }); const repoRoot = opts.repoRoot ?? resolve(opts.tsConfigFilePath, '..'); return { project, repoRoot };}The CLI hard-codes process.cwd() + /tsconfig.json as the project root
and passes it directly into ts-morph’s Project constructor without any
pre-processing. ts-morph in turn passes the plugins array to the TypeScript
compiler, which ignores unknown plugins (they only affect IDE language service),
so the Next.js plugin entry does not break ts-morph. However, the KH
tsconfig.json has a side effect of the plugin: it also includes
next-env.d.ts in its include array (line 25 of tsconfig.json).
"include": [ "next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"]The .next/types/**/*.ts and .next/dev/types/**/*.ts globs reference the
.next/ build output directory, which is present during dev/build but not in
a clean checkout. ts-morph silently tolerates missing glob expansions, so this
is not a crash risk, but it means the corpus boundary is implicitly
Next.js-aware. A Vite project would have no .next/ but might have .vite/
or dist/ instead — these are inert for the ast-dataflow tool because
generated output is typically excluded from tsconfig, but the assumption is
worth naming.
Verdict: The plugins: next entry is a non-issue for ts-morph but the
.next/types include globs embed a Next.js file system assumption. When the
CLI is run against a Vite project’s tsconfig.json, these globs simply produce
no files; the tool does not break. However, this is accidental portability, not
designed portability — a future Next.js version that requires the plugin
entries for correct type resolution would break the tool’s “discover corpus
from tsconfig” contract.
1.2 Hard assumption — @/ path alias in scripts/ast-dataflow-cli.ts
Section titled “1.2 Hard assumption — @/ path alias in scripts/ast-dataflow-cli.ts”File: scripts/ast-dataflow-cli.ts lines 14–16
import { callers, columnReads, columnWrites, deadExports, enumUses, importers, reexportChain, references, stringLiteralUses, typeEvolution, createProject,} from '@/lib/ast-dataflow';import type { ReferenceKind } from '@/lib/ast-dataflow';The CLI script uses @/lib/ast-dataflow as the import path. This resolves
correctly because the KH tsconfig.json defines "paths": { "@/*": ["./*"] }.
The CLI is executed via bun scripts/ast-dataflow-cli.ts, and Bun reads the
tsconfig path mapping for module resolution.
A Vite project’s tsconfig will almost certainly have a different path alias
convention. Vite projects typically define path aliases in vite.config.ts
(e.g. resolve: { alias: { '@': '/src' } }) and mirror them in tsconfig as
"paths": { "@/*": ["./src/*"] }. This means @/lib/ast-dataflow would
resolve to src/lib/ast-dataflow rather than lib/ast-dataflow. The CLI
script itself would fail to import the library if run from a Vite project root.
Verdict: This is the single genuine hard coupling in the codebase.
The CLI script cannot be dropped into a Vite project and executed as-is because
its @/ imports assume KH’s root-anchored path alias. The fix is either:
(a) rewrite the CLI to use relative imports (import ... from '../lib/ast-dataflow'),
or (b) document that the CLI must be run from the ast-dataflow-tooling root
rather than the target project root — which is actually already how it works
(the CLI is run from the ast-dataflow worktree, querying a different project’s
tsconfig via the --tsconfig flag or process.cwd()). Option (b) is discussed
further in §3.
1.3 Hard assumption — isTestFilePath pattern embeds KH test directory name
Section titled “1.3 Hard assumption — isTestFilePath pattern embeds KH test directory name”File: lib/ast-dataflow/resolve.ts lines 288–297
export function isTestFilePath(relPath: string): boolean { return ( relPath.startsWith('__tests__/') || relPath.includes('/test/') || relPath.endsWith('.test.ts') || relPath.endsWith('.test.tsx') || relPath.endsWith('.spec.ts') || relPath.endsWith('.spec.tsx') );}Also duplicated in:
lib/ast-dataflow/queries/column-reads.tslines 271–279 (localisTestFile)lib/ast-dataflow/queries/column-writes.tslines 280–288 (localisTestFile)
The function hard-codes __tests__/ as the primary test directory prefix, which
is the KH convention. Vite projects typically use src/__tests__/, src/tests/,
tests/, or co-located .test.ts files. The .test.ts / .spec.ts suffix
check covers the common Vitest/Jest pattern and is portable. However:
relPath.startsWith('__tests__/')— missessrc/__tests__/(common in Vite scaffolded projects usingcreate-viteor Vitest defaults)- The local copies in
column-reads.tsandcolumn-writes.tsdiverge from the sharedresolve.tsversion, creating a maintenance risk even within the KH corpus
Verdict: Soft coupling. The .test.ts / .spec.ts suffix checks cover 90%
of Vitest projects. The __tests__/ prefix is a KH-specific path assumption
that would produce false negatives (test files at src/__tests__/ would be
classified as production files) when using --exclude-tests against Vite
projects. Low practical impact unless the target Vite project uses
__tests__/-at-root.
1.4 Soft assumption — supabase-js call-chain heuristic in column-reads and column-writes
Section titled “1.4 Soft assumption — supabase-js call-chain heuristic in column-reads and column-writes”File: lib/ast-dataflow/queries/column-reads.ts lines 214–237 (findFromCalls)
File: lib/ast-dataflow/queries/column-reads.ts lines 239–268 (findRpcCalls)
File: lib/ast-dataflow/queries/column-writes.ts (parallel implementation)
The column-reads and column-writes queries are fundamentally Supabase-
specific: they scan for .from('table').select(...), .insert(...),
.update(...), .upsert(...), and .rpc(...) call chains. These are
supabase-js method names, not Next.js method names.
These queries are not less portable on Vite than on Next.js. Any project
using supabase-js — regardless of framework — will benefit from them
unchanged. The queries are correctly scoped as “Supabase queries”, not
“Next.js route queries”. The Supabase from().select() pattern is identical
whether the call site is in a Next.js route handler or a Vite React component.
Verdict: Not a portability concern for Vite. The queries work correctly
against any TypeScript corpus that uses supabase-js.
1.5 Soft assumption — jsxProp classification assumes JSX is present
Section titled “1.5 Soft assumption — jsxProp classification assumes JSX is present”File: lib/ast-dataflow/queries/string-literal-uses.ts lines 35–49
// Rule 2: JSX attribute value — `<Comp prop="value" />`if (parentKind === SyntaxKind.JsxAttribute) { return 'jsxProp';}// Check grandparent for the JsxAttribute case where the parent IS a JsxExpression wrapperif (grandParent) { const gpKind = grandParent.getKind(); if (gpKind === SyntaxKind.JsxAttribute) { return 'jsxProp'; }}The string-literal-uses query classifies string literals inside JSX attributes
as 'jsxProp'. This only fires when the project actually contains .tsx files
with JSX. Vite React projects use .tsx extensively — the classification is
more likely to be exercised on a Vite React project, not less.
The KH tsconfig.json sets "jsx": "react-jsx". A Vite project may set
"jsx": "react-jsx" (React projects) or "jsx": "preserve" (Vue or raw Vite)
or omit JSX entirely (Node.js Vite projects). If the target project omits JSX,
JsxAttribute nodes will never appear in the AST, and the jsxProp
classification will simply never fire — the query degrades gracefully rather than
breaking.
Verdict: Not a portability concern. The JSX classification is additive; its absence from non-JSX projects is benign.
1.6 Soft assumption — dead-exports test-file detection has the same __tests__/ root coupling
Section titled “1.6 Soft assumption — dead-exports test-file detection has the same __tests__/ root coupling”File: lib/ast-dataflow/resolve.ts lines 288–297 (shared isTestFilePath,
called by dead-exports, reexport-chain, walkBarrelChain)
Same observation as §1.3 — the __tests__/ prefix check in isTestFilePath
is the KH convention. For Vite projects with tests co-located at src/, the
--exclude-tests flag would be less precise but would not crash. Files at
src/__tests__/ would not be excluded as tests unless the .test.ts suffix
catches them.
1.7 Summary table
Section titled “1.7 Summary table”| Assumption | Files | Severity | Breaks on Vite? |
|---|---|---|---|
tsconfig.json plugins: next entry consumed by CLI | scripts/ast-dataflow-cli.ts:205, tsconfig.json:16–20 | Low | No (ts-morph ignores TS plugins) |
.next/types include globs in tsconfig corpus boundary | tsconfig.json:25 | Low | No (silent empty expansion) |
@/lib/ast-dataflow import in CLI script | scripts/ast-dataflow-cli.ts:14–16 | HIGH | Yes — if CLI is copied to target project root |
isTestFilePath __tests__/ root prefix | resolve.ts:289, column-reads.ts:273, column-writes.ts:282 | Low | Partial (.test.ts suffix catches most Vitest files) |
supabase-js chain heuristics | column-reads.ts, column-writes.ts | N/A | N/A (Supabase is framework-agnostic) |
jsxProp classification | string-literal-uses.ts:35–49 | Low | No (degrades gracefully without JSX) |
dead-exports test detection | resolve.ts:288–297, reexport-chain.ts:209 | Low | Partial (same as isTestFilePath) |
2. Vite delta
Section titled “2. Vite delta”This section characterises the meaningful differences between a Next.js 16 project and a Vite project from the perspective of the ast-dataflow tool.
2.1 tsconfig shape
Section titled “2.1 tsconfig shape”| Property | KH (Next.js) | Typical Vite project |
|---|---|---|
moduleResolution | "bundler" | "bundler" (Vite 5+) or "node" (older) |
jsx | "react-jsx" | "react-jsx" (Vite React) or "preserve" or absent |
paths | { "@/*": ["./*"] } | { "@/*": ["./src/*"] } or { "~/*": ["./src/*"] } or custom |
baseUrl | absent (implicit .) | sometimes "." or "./src" |
plugins | [{ "name": "next" }] | absent (Vite manages its own config) |
include | ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"] | ["src/**/*.ts", "src/**/*.tsx"] or ["**/*.ts", "**/*.tsx"] |
exclude | ["node_modules", "scripts", "supabase", "mcp-apps"] | ["node_modules", "dist"] |
Key difference for the tool: The paths mapping anchors the @/ alias at
the project root (KH) vs at ./src/ (Vite). The include globs define the
indexed corpus. Since ts-morph reads the tsconfig directly and uses the
TypeScript compiler’s own module resolver, both cases are handled correctly
by ts-morph with zero code changes, provided the correct tsconfig.json is
passed.
The tool’s createProject({ tsConfigFilePath }) will index the files
specified in any valid tsconfig. The module-resolution differences between
"bundler" and "node" are handled transparently by the TypeScript compiler
embedded in ts-morph.
2.2 File system conventions
Section titled “2.2 File system conventions”| Convention | Next.js App Router (KH) | Vite React |
|---|---|---|
| Entry point | app/layout.tsx + app/page.tsx | src/main.tsx |
| Route files | app/**/route.ts (GET, POST exports) | src/pages/ (if using React Router) or no file-based routing |
| API routes | app/api/**/route.ts | Separate Express/Hono server, or Vite plugin, or serverless function |
| Config file | next.config.ts | vite.config.ts |
| Test directory | __tests__/ (KH convention) | src/__tests__/ or co-located .test.ts |
| Type augmentation | next-env.d.ts | None (or framework-specific) |
Impact on queries:
-
callers,references,importers: These queries start from a user- supplied symbol and use ts-morph’sfindReferences(). They are entirely indifferent to the file system layout. A call tocallers('src/utils/format.ts:formatDate')works identically on a Vite project. -
column-reads,column-writes: Walk the entire corpus looking for.from('table')call chains. The file system layout is irrelevant — the queries find these calls wherever they appear. -
dead-exports: Scans the corpus for exported symbols with zero external importers. The scanned corpus is defined by the tsconfiginclude/exclude. A Vite project’s corpus lives undersrc/— ts-morph will index it correctly from the Vite tsconfig. -
string-literal-uses: Pure text + AST walk. No file system assumptions. -
importers: TheresolveTargetFilePathfunction inlib/ast-dataflow/queries/importers.tslines 28–79 uses a clever approach: it scans all import declarations across the corpus and matches specifiers against the input. The fallback strip of@/prefix at line 60:if (resolvedNormalised.endsWith('/' + normalised.replace(/^@\//, ''))) {strips the
@/prefix to match against the resolved absolute path. This works for KH’s@/alias but may fail for a Vite project using~/or a custom prefix. Thereplace(/^@\//, '')is KH-specific.
2.3 No proxy middleware
Section titled “2.3 No proxy middleware”Next.js uses proxy.ts (project root) as an auth middleware that runs on
every request via the matcher configuration. The ast-dataflow tool does not
inspect proxy.ts or the publicRoutes allowlist. It has no concept of
middleware. Vite delta: zero. The proxy is irrelevant to the AST tool.
2.4 HMR / dev server differences
Section titled “2.4 HMR / dev server differences”Next.js Dev Server and Vite’s HMR model are irrelevant — the ast-dataflow tool is a CLI that runs offline against a static file system snapshot. It does not connect to a dev server, does not watch for file changes, and does not interact with the build pipeline. Vite delta: zero.
2.5 Import resolution semantics
Section titled “2.5 Import resolution semantics”Next.js (App Router, moduleResolution: "bundler") and Vite 5 (also
moduleResolution: "bundler") use the same TypeScript module resolution mode.
Older Vite 4 projects may use "node" or "node16". ts-morph respects
whichever mode is configured in the tsconfig, so the resolution works correctly
in all three cases without tool modification.
One nuance: Vite’s runtime module resolver (its own bundler) and
TypeScript’s "bundler" mode have subtle differences with exports field in
package.json. These differences only surface when a library exports
type-only subpath packages. In practice, the ast-dataflow tool does not resolve
packages — it only resolves source files within the indexed corpus — so this
distinction is irrelevant.
2.6 Vite-specific file patterns
Section titled “2.6 Vite-specific file patterns”Vite projects introduce some file patterns the tool would encounter but handle correctly:
| Pattern | Example | Tool impact |
|---|---|---|
vite.config.ts | import { defineConfig } from 'vite' | Indexed as a normal TS file; dead-exports may flag defineConfig as external (non-importable) — but it is a package import, not a project export, so it will never appear as an “exported symbol” |
index.html | Vite entry point | Not a TS file; not in corpus |
| CSS modules | import styles from './App.module.css' | ts-morph treats .css as non-TS; the import is visible as a string literal but the module is not indexed |
.vue / .svelte files | Non-TS component formats | Not included in tsconfig include (unless a preprocessor plugin is configured); not indexed |
2.7 Vite delta summary
Section titled “2.7 Vite delta summary”| Dimension | Delta from Next.js | Tool action needed |
|---|---|---|
| tsconfig corpus | src/** instead of ** | None — createProject reads any tsconfig |
| path alias | @/src/ instead of @/ | Minor: fix importers.ts line 60 strip |
| Route conventions | No app/**/route.ts | No route-aware logic in tool; irrelevant |
| Proxy / middleware | Absent | Irrelevant |
| HMR / dev server | Different | Irrelevant |
| Test directory | src/__tests__/ common | Minor: generalise isTestFilePath |
| JSX handling | Same (react-jsx) for React Vite | None |
| Module resolution mode | "bundler" (Vite 5) or "node" (Vite 4) | None — ts-morph respects the setting |
3. Abstraction surface proposals
Section titled “3. Abstraction surface proposals”Three approaches are evaluated. They are ordered from least to most architectural work.
Approach A — Minimal: fix two concrete issues, document the rest
Section titled “Approach A — Minimal: fix two concrete issues, document the rest”Description: Repair the two identified defects (@/ alias strip in
importers.ts and duplicated/fragile isTestFilePath) and update the CLI
to accept an explicit --tsconfig <path> flag. No framework adaptor
abstraction.
Changes required:
-
scripts/ast-dataflow-cli.ts— add--tsconfigflag (lines 204–206)Replace the hard-coded
resolve(process.cwd(), 'tsconfig.json')with:const tsConfigFilePath = typeof parsed.flags.tsconfig === 'string'? resolve(process.cwd(), parsed.flags.tsconfig): resolve(process.cwd(), 'tsconfig.json');This allows invoking the tool against a remote project:
Terminal window cd /path/to/ast-dataflow-toolingbun scripts/ast-dataflow-cli.ts callers \--tsconfig /path/to/vite-project/tsconfig.json \--symbol 'src/utils/format.ts:formatDate' -
lib/ast-dataflow/queries/importers.tsline 60 — generalise the alias stripChange:
if (resolvedNormalised.endsWith('/' + normalised.replace(/^@\//, ''))) {To:
const strippedAlias = normalised.replace(/^[^/]+\//, ''); // strip any leading aliasif (resolvedNormalised.endsWith('/' + strippedAlias)) {This handles
@/,~/,#/, and other single-segment alias prefixes. -
lib/ast-dataflow/resolve.ts— generaliseisTestFilePath(lines 288–297)Promote
__tests__/to a configurable default; addsrc/__tests__/:const TEST_DIR_PREFIXES = ['__tests__/', 'src/__tests__/'];const TEST_SUFFIX_PATTERNS = ['.test.ts', '.test.tsx', '.spec.ts', '.spec.tsx'];export function isTestFilePath(relPath: string): boolean {return (TEST_DIR_PREFIXES.some(p => relPath.startsWith(p)) ||relPath.includes('/test/') ||TEST_SUFFIX_PATTERNS.some(s => relPath.endsWith(s)));}Also eliminate the two local copies in
column-reads.tsandcolumn-writes.tsthat duplicate this logic. -
scripts/ast-dataflow-cli.ts— fix@/lib/ast-dataflowimport (lines 14–16)The CLI’s own imports must use relative paths so the CLI can be executed from a different working directory without the
@/resolution breaking:import { callers, ... } from '../lib/ast-dataflow';Currently this works when Bun resolves the tsconfig from
process.cwd(). When--tsconfigpoints at a remote project,process.cwd()may still be the ast-dataflow-tooling root, so the current setup is actually safe. However, the relative import makes the invariant explicit and removes the dependency on the@/path alias being present in the active tsconfig.
Trade-offs:
| Pro | Con |
|---|---|
| Minimal change surface — 4 files touched | No formal framework contract; each new framework requires another ad-hoc audit |
| No new abstraction to learn or maintain | isTestFilePath still hard-codes common-but-not-universal directory names |
--tsconfig flag is the natural extension point; already how advanced CLI tools work (e.g. tsc --project) | Callers must supply the tsconfig path explicitly for non-KH projects |
| Vite support is achieved with zero new interfaces | Docs must explain the --tsconfig pattern rather than advertising “framework-agnostic” |
Conclusion: This approach is sufficient for 95% of the Vite use case described in R-WP10 and requires no architectural change. It is the recommended starting point.
Approach B — Moderate: FrameworkAdaptor interface
Section titled “Approach B — Moderate: FrameworkAdaptor interface”Description: Define a FrameworkAdaptor interface that encapsulates the
framework-specific behaviours the tool currently hard-codes, then ship two
concrete implementations: NextjsAdaptor (default) and ViteAdaptor.
Proposed interface shape:
export interface FrameworkAdaptor { /** * Return the corpus tsconfig.json path given a repo root. * Default: `${repoRoot}/tsconfig.json` */ resolveCorpusTsConfig(repoRoot: string): string;
/** * Return true if the given repo-relative path should be classified * as a test file (for --exclude-tests purposes). */ isTestFile(relPath: string): boolean;
/** * Strip the framework-specific path alias prefix from a module specifier * so `importers.ts` can match `@/lib/foo` against `~/lib/foo` etc. * Default: strip any single-segment alias prefix of the form `alias/`. */ stripAliasPrefix(specifier: string): string;
/** * Auto-detect from the repo root: return the adaptor that best fits * the detected framework (by probing `next.config.*`, `vite.config.*`, etc.) * Returns 'unknown' when detection is not conclusive. */ detect(repoRoot: string): 'nextjs' | 'vite' | 'unknown';}Concrete implementations:
export const NextjsAdaptor: FrameworkAdaptor = { resolveCorpusTsConfig: (root) => resolve(root, 'tsconfig.json'), isTestFile: (p) => p.startsWith('__tests__/') || ... (current logic), stripAliasPrefix: (s) => s.replace(/^@\//, ''), detect: (root) => existsSync(join(root, 'next.config.ts')) || existsSync(join(root, 'next.config.js')) ? 'nextjs' : 'unknown',};
// lib/ast-dataflow/adaptors/vite.tsexport const ViteAdaptor: FrameworkAdaptor = { resolveCorpusTsConfig: (root) => resolve(root, 'tsconfig.json'), isTestFile: (p) => p.startsWith('src/__tests__/') || p.startsWith('__tests__/') || ..., stripAliasPrefix: (s) => s.replace(/^[@~#][^/]*\//, ''), detect: (root) => existsSync(join(root, 'vite.config.ts')) || existsSync(join(root, 'vite.config.js')) ? 'vite' : 'unknown',};The createProject factory would accept an optional adaptor:
createProject({ tsConfigFilePath, repoRoot, adaptor?: FrameworkAdaptor })Auto-detection would probe next.config.* / vite.config.* at the repo
root and select the matching adaptor.
Trade-offs:
| Pro | Con |
|---|---|
| Explicit, testable contract for framework-specific behaviour | Two new files + interface; more ongoing maintenance |
Auto-detection removes the need for callers to specify --framework | Detection by config file presence is fragile (monorepos, custom project structures) |
| Each adaptor is independently testable with a fixture corpus | The interface is likely over-engineered for a two-framework world |
Clean surface for a future SvelteKitAdaptor etc. | All current tool users get a hidden NextjsAdaptor dependency even when they’re on KH and don’t need the abstraction |
Conclusion: Useful if the tool is expected to actively support multiple frameworks in parallel (a “publish as a package” future). Premature for the current dogfooding-within-Liam-projects use case.
Approach C — Configuration-driven: ast-dataflow.config.ts
Section titled “Approach C — Configuration-driven: ast-dataflow.config.ts”Description: Introduce a per-project configuration file at the repo root that overrides the default behaviours without requiring code changes to the tool. The tool reads this file at startup and applies its settings.
Proposed config shape:
// ast-dataflow.config.ts (at target project root)import type { AstDataflowConfig } from 'ast-dataflow-tooling/config';
export default { tsconfig: './tsconfig.json', // default testDirectories: ['__tests__', 'tests'], // default: ['__tests__'] testFileSuffixes: ['.test.ts', '.test.tsx', '.spec.ts', '.spec.tsx'], aliasPrefix: '@/', // default; set to '~/' for Vite projects using tilde alias} satisfies AstDataflowConfig;The CLI would discover ast-dataflow.config.ts (or .js) via a search up
from process.cwd(), similar to how ESLint, Prettier, and Vitest discover
their configs.
Trade-offs:
| Pro | Con |
|---|---|
| No new abstraction in library code; config is user-facing only | Discovery logic adds complexity and a new dependency pattern (dynamic import of a TS config file) |
| Follows modern tooling conventions (ESLint flat config, Vitest config) | Adds a new file to the target project’s repo — more friction than --tsconfig flag |
| Config file is checked in and self-documenting | The config schema must be versioned and maintained |
| Extensible beyond framework-agnostic: could configure corpus limits, alias rules, etc. | Most of the config options duplicate tsconfig semantics — two sources of truth |
Conclusion: The best approach if the tool is eventually published as an npm package for general use. Too heavy for the current single-operator, single-codebase context.
Comparison table
Section titled “Comparison table”| Criterion | A (Minimal) | B (Adaptor) | C (Config file) |
|---|---|---|---|
| Files added | 0 | ~4 | ~2 |
| Files modified | 4 | 6 | 4 + optional CLI changes |
| New interfaces | 0 | 1 | 1 (config schema) |
| Vite support achieved? | Yes | Yes | Yes |
| Future-framework support | Ad-hoc audit | Plugin a new adaptor | Add config defaults |
| Effort (total) | S | M | S–M |
| Right-sizes for current use case? | Yes | No | No |
4. Effort estimate (banded)
Section titled “4. Effort estimate (banded)”4a. Abstraction work itself
Section titled “4a. Abstraction work itself”Approach A (Recommended): S — 2–4 hours
The four changes described in §3 Approach A are each small and well-isolated:
--tsconfigCLI flag: ~30 lines diff inscripts/ast-dataflow-cli.ts.- Alias strip generalisation: 3-line diff in
lib/ast-dataflow/queries/importers.ts. isTestFilePathgeneralisation + deduplication: ~15-line diff inlib/ast-dataflow/resolve.ts+ removal of two local copies.- Relative imports in CLI: ~3-line diff.
Each change is independently testable. No new test infrastructure needed.
Existing tests (importers.test.ts, dead-exports tests) provide a regression
baseline. A smoke run against a local Vite project fixture is the verification.
Approach B (Adaptor pattern): M — 1–2 weeks
Designing a stable FrameworkAdaptor interface requires:
- Deciding which behaviours belong in the adaptor vs stay in the library core.
- Writing
NextjsAdaptorandViteAdaptorwith full test coverage. - Threading the adaptor through all query callers that use
isTestFilePathand the alias strip. - Writing auto-detection logic and deciding the fallback.
- Updating
createProjectsignature and all call sites.
The interface design itself (OQ phase) could easily consume a full session. One week is the optimistic estimate; two weeks if the interface surfaces edge cases that require iteration.
Approach C (Config file): S–M — 4–8 hours
Simpler than Approach B but requires:
- Writing a config discovery function (search up from
process.cwd()). - Defining and exporting a typed config schema.
- Integrating config loading into the CLI startup.
- Deciding what happens when no config file is present (sensible defaults).
The main risk is config file discovery interacting badly with monorepos or worktree layouts — requires at least one explicit test case.
4b. Vite adaptor on top of the abstraction work
Section titled “4b. Vite adaptor on top of the abstraction work”On top of Approach A: XS — 1–2 hours
After Approach A, Vite support requires:
- A quick smoke run against a real Vite project (e.g. a
create-vitescaffold with a few TypeScript files). - Verifying the
--tsconfigflag correctly points ts-morph at the Vite tsconfig. - Checking that
isTestFilePathcovers the Vite test layout. - Documenting the invocation pattern in the skill file (R-WP7).
Total: approximately 1–2 hours for validation plus documentation.
On top of Approach B: S — 3–5 hours
Writing ViteAdaptor, its tests, and wiring it into the auto-detection path.
The adaptor itself is small (4 methods); the work is mostly test scaffolding.
On top of Approach C: S — 2–3 hours
Writing a sample ast-dataflow.config.ts for a Vite project, verifying
discovery works, adding a test.
5. Recommendation
Section titled “5. Recommendation”Ship Approach A in S8 (or as an S7 sub-task).
Rationale:
-
The problem is smaller than it appears. The tool has almost no Next.js coupling in its query logic. The two genuine defects (the
@/alias strip inimporters.tsand theisTestFilePathduplication) are each 3–15 line fixes. The--tsconfigflag is a natural extension that most advanced CLI tools already expose. -
Approach A achieves the stated goal. R-WP10 says “immediate requirement beyond Next.js is Vite.” Approach A delivers usable Vite support. The invocation becomes:
Terminal window bun scripts/ast-dataflow-cli.ts callers \--tsconfig /path/to/vite-project/tsconfig.json \--symbol 'src/utils/format.ts:formatDate'This is identical to how
tscand other tools work and requires no new abstraction. -
Abstraction layers are premature. The tool currently serves one operator (Liam and Claude agents on KH and Liam’s other projects). Approaches B and C trade present engineering investment for a future that may never require it. The ROADMAP explicitly defers the npm-packaging question (R-WP7 is a skill file, not a published package).
-
Deduplication of
isTestFilePathis valuable independently of framework portability. The three copies (resolve.ts, column-reads.ts, column-writes.ts) are a maintenance risk on KH itself — they drift. This fix belongs in a housekeeping commit regardless of the portability goal. -
The only non-trivial risk is the
repoRoot/cwdinteraction. When the--tsconfigflag points at a remote project,repoRoot(used by all path helpers to produce repo-relative results) must match the target project root, not the ast-dataflow-tooling root. This is a one-line change (repoRoot = dirname(tsConfigFilePath)), but it must be clearly documented and tested to avoid subtly wrong result paths.
If R-WP10 is scheduled alongside R-WP6 (flow-trace) in S8, Approach A is a single short work package. If it is promoted to a standalone S7 WP, it can be delivered in a single agent session.
6. Open questions for S8 review
Section titled “6. Open questions for S8 review”| OQ | Question | Owner | When |
|---|---|---|---|
| OQ-WP10-1 | Should the --tsconfig flag accept an absolute path, a repo-relative path, or both? Current resolve(process.cwd(), flag) handles relative + absolute (via isAbsolute check), which is the standard CLI convention. | S8 executor | Before implementing |
| OQ-WP10-2 | When --tsconfig points at a remote project, should repoRoot default to dirname(tsConfigFilePath) or be separately configurable via --repo-root? The former is simpler; the latter handles monorepos where the tsconfig is not at the true project root. | S8 executor | Before implementing |
| OQ-WP10-3 | Should the isTestFilePath generalisation be a PRODUCT.md invariant (making the configurable test directory list part of the spec) or an internal implementation detail? The spec currently says nothing about test directory conventions. | Orchestrator | Before PRODUCT.md update |
| OQ-WP10-4 | Does the importers.ts alias strip change (stripping any single-segment prefix) risk matching legitimate path fragments that happen to look like aliases (e.g. api/foo)? The current regex replace(/^[^/]+\//, '') would strip api/ from api/foo. Should it be limited to known alias characters (@, ~, #)? | S8 executor | Implementation detail |
| OQ-WP10-5 | Is there a real Vite project already in Liam’s ecosystem that should be used as a smoke-test corpus? The brief assumes a create-vite scaffold; a real project would give more signal on edge cases. | Liam | Before S8 smoke test |
| OQ-WP10-6 | R-WP10 says “Immediate requirement beyond Next.js is Vite.” Is this an active need (Liam has a Vite project he wants to query today) or a strategic hedge (may be needed within the next 2-3 sessions)? The answer affects whether Approach A lands in S7 or S8. | Liam | Before S8 planning |
End of brief.