Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
8b2ad0a
feat(types): migrate test suite and config files to TypeScript (Phase 6)
carlos-alm Mar 24, 2026
7d77811
feat(types): migrate scripts and docs examples to TypeScript
carlos-alm Mar 24, 2026
476e642
feat(types): drop Node 20, require Node >= 22
carlos-alm Mar 24, 2026
8cfb084
fix(ci): update embedding regression workflow to reference .ts test f…
carlos-alm Mar 24, 2026
5c50b2a
Merge remote-tracking branch 'origin/main' into review-588
carlos-alm Mar 24, 2026
a6b8f2e
fix: tighten Node engines to >=22.6, restore strip-types guard, use r…
carlos-alm Mar 24, 2026
04a2e50
fix(docs): use version-aware strip-types flag in pre-commit example (…
carlos-alm Mar 25, 2026
57bcfaf
fix(types): add TypeScript parameter types to test helpers (#588)
carlos-alm Mar 25, 2026
a21a419
fix(hooks): convert pre-commit-checks.ts from CJS require() to ESM im…
carlos-alm Mar 25, 2026
563de54
fix(ci): add --import loader to benchmark workflow for Node 22 compat…
carlos-alm Mar 25, 2026
9b8eef1
fix: version-aware strip-types flag in benchmark.yml, update docs (#588)
carlos-alm Mar 25, 2026
966ec3e
fix: remove unused .ts loader duplicates (#588)
carlos-alm Mar 25, 2026
f19aec9
Revert "fix: remove unused .ts loader duplicates (#588)"
carlos-alm Mar 25, 2026
39d07f1
fix(ci): use version-aware strip-types flag in verify-imports step (#…
carlos-alm Mar 25, 2026
d5f30c7
fix(ci): use dynamic strip-types flag in publish workflow (#588)
carlos-alm Mar 25, 2026
83f2633
fix: remove dead scripts/test.js (#588)
carlos-alm Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ jobs:
if [ "${{ steps.mode.outputs.source }}" = "npm" ]; then
ARGS="$ARGS --npm"
fi
node scripts/benchmark.js $ARGS 2>/dev/null > benchmark-result.json
node --experimental-strip-types scripts/benchmark.ts $ARGS 2>/dev/null > benchmark-result.json

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.

P1 Missing --import loader — benchmark scripts will fail on Node 22

scripts/benchmark.ts (and embedding-benchmark.ts, query-benchmark.ts, incremental-benchmark.ts) contain cross-script imports using .js specifiers:

import { resolveBenchmarkSource, srcImport } from './lib/bench-config.js';
import { isWorker, workerEngine, workerTargets, forkEngines } from './lib/fork-engine.js';

Both scripts/lib/bench-config.js and scripts/lib/fork-engine.js do not exist on disk — only their .ts counterparts do. Node 22's --experimental-strip-types strips type annotations but does not rewrite .js specifiers to .ts (that resolution was only added in Node 23+). Without the --import ESM loader hook, these scripts will fail immediately with ERR_MODULE_NOT_FOUND.

The fix is to add --import ./scripts/ts-resolve-loader.js (the .js version is still present and fully functional) before the script path. The same fix applies at lines 247, 384, and 521:

Suggested change
node --experimental-strip-types scripts/benchmark.ts $ARGS 2>/dev/null > benchmark-result.json
node --experimental-strip-types --import ./scripts/ts-resolve-loader.js scripts/benchmark.ts $ARGS 2>/dev/null > benchmark-result.json

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — added --import ./scripts/ts-resolve-loader.js to the benchmark.ts invocation so .js specifiers resolve to their .ts counterparts on Node 22.


- name: Update build report
if: steps.existing.outputs.skip != 'true'
run: node scripts/update-benchmark-report.js benchmark-result.json
run: node --experimental-strip-types scripts/update-benchmark-report.ts benchmark-result.json

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.

P2 Hardcoded --experimental-strip-types in update-report invocations

The four update-*-report.ts invocations (benchmark, embedding, query, incremental) use node --experimental-strip-types without --strip-types on Node 23+. The benchmark.yml workflow isn't pinned to Node 22 — if it's run on a Node 23+ runner (or when the matrix is eventually updated), these will emit deprecation warnings about the obsolete flag.

The main benchmark runners (lines 96, 247, 384, 521) received the same fix in this PR and correctly keep --experimental-strip-types, but the equivalent vitest.config.ts dynamically selects between --experimental-strip-types and --strip-types based on the major version. Consider applying the same dynamic selection here, or at minimum noting this as a forward-compatibility concern.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — all eight node invocations in benchmark.yml (four benchmark runners + four update-report scripts) now use a dynamic STRIP_FLAG variable that selects --strip-types on Node 23+ and --experimental-strip-types on Node 22.x, matching the approach already used in vitest.config.ts and pre-commit.sh.


- name: Upload build result
if: steps.existing.outputs.skip != 'true'
Expand Down Expand Up @@ -244,11 +244,11 @@ jobs:
if [ "${{ steps.mode.outputs.source }}" = "npm" ]; then
ARGS="$ARGS --npm"
fi
node scripts/embedding-benchmark.js $ARGS 2>/dev/null > embedding-benchmark-result.json
node --experimental-strip-types scripts/embedding-benchmark.ts $ARGS 2>/dev/null > embedding-benchmark-result.json

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.

P1 Missing --import loader — same resolution failure as line 96

scripts/embedding-benchmark.ts also imports ./lib/bench-config.js and ./lib/fork-engine.js (which exist only as .ts files). Same fix applies:

Suggested change
node --experimental-strip-types scripts/embedding-benchmark.ts $ARGS 2>/dev/null > embedding-benchmark-result.json
node --experimental-strip-types --import ./scripts/ts-resolve-loader.js scripts/embedding-benchmark.ts $ARGS 2>/dev/null > embedding-benchmark-result.json

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — same --import ./scripts/ts-resolve-loader.js flag added to the embedding-benchmark.ts invocation.


- name: Update embedding report
if: steps.existing.outputs.skip != 'true'
run: node scripts/update-embedding-report.js embedding-benchmark-result.json
run: node --experimental-strip-types scripts/update-embedding-report.ts embedding-benchmark-result.json

- name: Upload embedding result
if: steps.existing.outputs.skip != 'true'
Expand Down Expand Up @@ -381,11 +381,11 @@ jobs:
if [ "${{ steps.mode.outputs.source }}" = "npm" ]; then
ARGS="$ARGS --npm"
fi
node scripts/query-benchmark.js $ARGS 2>/dev/null > query-benchmark-result.json
node --experimental-strip-types scripts/query-benchmark.ts $ARGS 2>/dev/null > query-benchmark-result.json

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.

P1 Missing --import loader — same resolution failure as line 96

scripts/query-benchmark.ts also imports ./lib/bench-config.js and ./lib/fork-engine.js (which exist only as .ts files).

Suggested change
node --experimental-strip-types scripts/query-benchmark.ts $ARGS 2>/dev/null > query-benchmark-result.json
node --experimental-strip-types --import ./scripts/ts-resolve-loader.js scripts/query-benchmark.ts $ARGS 2>/dev/null > query-benchmark-result.json

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — same --import ./scripts/ts-resolve-loader.js flag added to the query-benchmark.ts invocation.


- name: Update query report
if: steps.existing.outputs.skip != 'true'
run: node scripts/update-query-report.js query-benchmark-result.json
run: node --experimental-strip-types scripts/update-query-report.ts query-benchmark-result.json

- name: Upload query result
if: steps.existing.outputs.skip != 'true'
Expand Down Expand Up @@ -518,11 +518,11 @@ jobs:
if [ "${{ steps.mode.outputs.source }}" = "npm" ]; then
ARGS="$ARGS --npm"
fi
node scripts/incremental-benchmark.js $ARGS 2>/dev/null > incremental-benchmark-result.json
node --experimental-strip-types scripts/incremental-benchmark.ts $ARGS 2>/dev/null > incremental-benchmark-result.json

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.

P1 Missing --import loader — same resolution failure as line 96

scripts/incremental-benchmark.ts also imports ./lib/bench-config.js and ./lib/fork-engine.js (which exist only as .ts files).

Suggested change
node --experimental-strip-types scripts/incremental-benchmark.ts $ARGS 2>/dev/null > incremental-benchmark-result.json
node --experimental-strip-types --import ./scripts/ts-resolve-loader.js scripts/incremental-benchmark.ts $ARGS 2>/dev/null > incremental-benchmark-result.json

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — same --import ./scripts/ts-resolve-loader.js flag added to the incremental-benchmark.ts invocation.


- name: Update incremental report
if: steps.existing.outputs.skip != 'true'
run: node scripts/update-incremental-report.js incremental-benchmark-result.json
run: node --experimental-strip-types scripts/update-incremental-report.ts incremental-benchmark-result.json

- name: Upload incremental result
if: steps.existing.outputs.skip != 'true'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-native.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 20
node-version: 22

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: [20, 22]
node-version: [22]

runs-on: ${{ matrix.os }}
name: Test Node ${{ matrix.node-version }} (${{ matrix.os }})
Expand Down Expand Up @@ -113,7 +113,7 @@ jobs:
node-version: 22

- name: Verify all dynamic imports resolve
run: node scripts/verify-imports.js
run: node --experimental-strip-types scripts/verify-imports.ts

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.

P2 Hardcoded --experimental-strip-types in verify-imports step

This step runs node --experimental-strip-types scripts/verify-imports.ts with a hardcoded flag. The CI matrix is currently pinned to Node 22, so this works today, but if Node 23+ is ever added to the matrix (or the runner's default Node version shifts), users will see deprecation warnings.

benchmark.yml was updated to use a dynamic STRIP_FLAG variable for all eight invocations. Applying the same pattern here keeps the workflows consistent:

Suggested change
- name: Verify all dynamic imports resolve
run: node scripts/verify-imports.js
run: node --experimental-strip-types scripts/verify-imports.ts
run: |
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
node $STRIP_FLAG scripts/verify-imports.ts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — applied the same dynamic STRIP_FLAG pattern used in benchmark.yml to the verify-imports step in ci.yml.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — applied the dynamic STRIP_FLAG pattern to both sync-native-versions.ts invocations in publish.yml (lines 227 and 409), matching the approach already used in benchmark.yml.


rust-check:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/embedding-regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ jobs:
key: hf-models-minilm-v1

- name: Run embedding regression tests
run: npx vitest run tests/search/embedding-regression.test.js
run: npx vitest run tests/search/embedding-regression.test.ts
6 changes: 3 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 20
node-version: 22

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
Expand Down Expand Up @@ -224,7 +224,7 @@ jobs:
VERSION: ${{ needs.compute-version.outputs.version }}
run: |
npm version "$VERSION" --no-git-tag-version --allow-same-version
node scripts/sync-native-versions.js --strip
node --experimental-strip-types scripts/sync-native-versions.ts --strip

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.

P2 Hardcoded --experimental-strip-types in publish workflow

Both sync-native-versions.ts invocations in this workflow still hardcode --experimental-strip-types (also at line 408). The same pattern was flagged and fixed in benchmark.yml (all eight invocations) and ci.yml (the verify-imports step) with a dynamic STRIP_FLAG variable. The jobs are currently pinned to Node 22, so this is functional today, but it creates an inconsistency and will silently emit deprecation warnings if the node-version is ever bumped to 23+.

Applying the same dynamic selection used elsewhere:

Suggested change
node --experimental-strip-types scripts/sync-native-versions.ts --strip
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
node $STRIP_FLAG scripts/sync-native-versions.ts --strip

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — both sync-native-versions.ts invocations in publish.yml now use the dynamic STRIP_FLAG pattern, pushed in commit d5f30c7.

echo "Packaging version $VERSION"

- name: Build TypeScript
Expand Down Expand Up @@ -405,7 +405,7 @@ jobs:
run: |
git checkout -- package-lock.json
npm version "$VERSION" --no-git-tag-version --allow-same-version
node scripts/sync-native-versions.js
node --experimental-strip-types scripts/sync-native-versions.ts

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.

P2 Same hardcoded --experimental-strip-types issue as line 227

Same forward-compatibility concern applies here. Apply the same dynamic STRIP_FLAG pattern:

Suggested change
node --experimental-strip-types scripts/sync-native-versions.ts
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
node $STRIP_FLAG scripts/sync-native-versions.ts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — same dynamic STRIP_FLAG applied at this invocation too.

echo "Publishing version $VERSION"

- name: Build TypeScript
Expand Down
2 changes: 1 addition & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ docs/
*.db
.codegraphrc.json
.versionrc.json
commitlint.config.js
commitlint.config.ts
codegraph-improvements.md
File renamed without changes.
2 changes: 1 addition & 1 deletion docs/examples/claude-code-hooks/pre-commit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fi

# Run all checks in a single Node.js process
HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
RESULT=$(node "$HOOK_DIR/pre-commit-checks.js" "$WORK_ROOT" "$EDITED_FILES" "$STAGED" 2>/dev/null) || true
RESULT=$(node --experimental-strip-types "$HOOK_DIR/pre-commit-checks.ts" "$WORK_ROOT" "$EDITED_FILES" "$STAGED" 2>/dev/null) || true

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.

P2 Hardcoded --experimental-strip-types is deprecated on Node 23+

This shell script hardcodes --experimental-strip-types, but vitest.config.ts correctly selects between --experimental-strip-types (Node 22.x) and --strip-types (Node >=23) at runtime. Users running Node 23+ who follow this documentation example will get deprecation warnings, even though they're suppressed here via 2>/dev/null.

Since the package now requires Node >=22.6, consider a version-aware invocation:

Suggested change
RESULT=$(node --experimental-strip-types "$HOOK_DIR/pre-commit-checks.ts" "$WORK_ROOT" "$EDITED_FILES" "$STAGED" 2>/dev/null) || true
STRIP_FLAG=$(node -e "const [M,m]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
RESULT=$(node $STRIP_FLAG "$HOOK_DIR/pre-commit-checks.ts" "$WORK_ROOT" "$EDITED_FILES" "$STAGED" 2>/dev/null) || true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — the pre-commit.sh example now dynamically selects between --strip-types (Node >=23) and --experimental-strip-types (Node 22.x) at runtime, matching the approach already used in vitest.config.ts.


if [ -z "$RESULT" ]; then
exit 0
Expand Down
18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,26 @@
"README.md"
],
"engines": {
"node": ">=20"
"node": ">=22.6"
},
Comment on lines 28 to 30

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.

P1 engines constraint too broad for --experimental-strip-types

--experimental-strip-types was only introduced in Node 22.6.0. Declaring >=22 here means users on Node 22.0–22.5 will hit errors when running any script that uses node --experimental-strip-types (build:wasm, deps:tree, verify-imports, version, etc.) — even though the engines field technically allows those versions.

The same issue affects vitest.config.ts, which injects --experimental-strip-types into NODE_OPTIONS unconditionally for any Node 22.x (see below).

Suggested change
"engines": {
"node": ">=20"
"node": ">=22"
},
"engines": {
"node": ">=22.6"
},

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — tightened engines constraint to >=22.6 since --experimental-strip-types requires Node 22.6+.

"scripts": {
"build": "tsc",
"build:wasm": "node scripts/build-wasm.js",
"build": "tsc && node -e \"require('fs').writeFileSync('dist/index.cjs',require('fs').readFileSync('src/index.cjs','utf8').replaceAll('./index.ts','./index.js'))\"",
"build:wasm": "node --experimental-strip-types scripts/build-wasm.ts",
"typecheck": "tsc --noEmit",
"verify-imports": "node scripts/verify-imports.js",
"test": "node scripts/test.js run",
"test:watch": "node scripts/test.js",
"test:coverage": "node scripts/test.js run --coverage",
"verify-imports": "node --experimental-strip-types scripts/verify-imports.ts",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"lint": "biome check src/ tests/",
"lint:fix": "biome check --write src/ tests/",
"format": "biome format --write src/ tests/",
"prepack": "npm run build",
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('.tsbuildinfo',{force:true})\"",
"prepare": "npm run build:wasm && npm run build && husky && npm run deps:tree",
"deps:tree": "node scripts/gen-deps.cjs",
"deps:tree": "node --experimental-strip-types scripts/gen-deps.ts",
"release": "commit-and-tag-version",
"release:dry-run": "commit-and-tag-version --dry-run",
"version": "node scripts/sync-native-versions.js && git add package.json crates/codegraph-core/Cargo.toml"
"version": "node --experimental-strip-types scripts/sync-native-versions.ts && git add package.json crates/codegraph-core/Cargo.toml"
Comment on lines +33 to +48

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.

P2 Hardcoded --experimental-strip-types across all npm scripts

Every user-facing npm script that invokes a .ts file still hardcodes --experimental-strip-types, which is deprecated on Node 23+. The same pattern was flagged and fixed in benchmark.yml, pre-commit.sh, and vitest.config.ts, but package.json was left behind.

Affected scripts:

  • build:wasm (line 33) — runs via prepare, i.e. on every npm install
  • verify-imports (line 35)
  • deps:tree (line 45)
  • version (line 48)

Users on Node 23+ will see a deprecation warning for every invocation. Because build:wasm is part of the prepare lifecycle, the warning appears on npm install, which is the noisiest possible place.

package.json scripts can't use shell conditionals inline, but a tiny wrapper (similar to the now-unused scripts/test.js, which already did version-aware flag selection) or an env-var trick works:

Suggested change
"build:wasm": "node --experimental-strip-types scripts/build-wasm.ts",
"typecheck": "tsc --noEmit",
"verify-imports": "node scripts/verify-imports.js",
"test": "node scripts/test.js run",
"test:watch": "node scripts/test.js",
"test:coverage": "node scripts/test.js run --coverage",
"verify-imports": "node --experimental-strip-types scripts/verify-imports.ts",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"lint": "biome check src/ tests/",
"lint:fix": "biome check --write src/ tests/",
"format": "biome format --write src/ tests/",
"prepack": "npm run build",
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('.tsbuildinfo',{force:true})\"",
"prepare": "npm run build:wasm && npm run build && husky && npm run deps:tree",
"deps:tree": "node scripts/gen-deps.cjs",
"deps:tree": "node --experimental-strip-types scripts/gen-deps.ts",
"release": "commit-and-tag-version",
"release:dry-run": "commit-and-tag-version --dry-run",
"version": "node scripts/sync-native-versions.js && git add package.json crates/codegraph-core/Cargo.toml"
"version": "node --experimental-strip-types scripts/sync-native-versions.ts && git add package.json crates/codegraph-core/Cargo.toml"
"build:wasm": "node -e \"const v=process.versions.node.split('.').map(Number);const f=v[0]>=23?'--strip-types':'--experimental-strip-types';require('child_process').execFileSync(process.execPath,[f,'scripts/build-wasm.ts'],{stdio:'inherit'})\"",
"typecheck": "tsc --noEmit",
"verify-imports": "node -e \"const v=process.versions.node.split('.').map(Number);const f=v[0]>=23?'--strip-types':'--experimental-strip-types';require('child_process').execFileSync(process.execPath,[f,'scripts/verify-imports.ts'],{stdio:'inherit'})\"",

Alternatively, since scripts/test.js already contains version-aware flag selection logic and is now dead code (package.json replaced node scripts/test.js run with vitest run directly), that pattern could be extracted into a small scripts/node-ts.js launcher that accepts a script path as its first argument and applies the correct flag.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed this is a real forward-compatibility concern. However, fixing this in package.json requires creating a new launcher script and changing all four script invocations, which is a meaningful refactor beyond this PR's scope.

Created #590 to track this. The engines constraint (>=22.6) means this will surface as deprecation warnings (not errors) when users adopt Node 23+, and build:wasm during npm install is indeed the noisiest place for it.

},
"keywords": [
"codegraph",
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
17 changes: 9 additions & 8 deletions scripts/gen-deps.cjs → scripts/gen-deps.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
#!/usr/bin/env node
import { execSync } from 'node:child_process';
import { mkdirSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';

const outFile = path.join('generated', 'DEPENDENCIES.md');
fs.mkdirSync(path.dirname(outFile), { recursive: true });
const outFile = join('generated', 'DEPENDENCIES.md');
mkdirSync(dirname(outFile), { recursive: true });

try {
const tree = execSync('npm ls --all --omit=dev', { encoding: 'utf8' });
fs.writeFileSync(outFile, '# Dependencies\n\n```\n' + tree + '```\n');
} catch (err) {
writeFileSync(outFile, '# Dependencies\n\n```\n' + tree + '```\n');
} catch (err: any) {
// npm ls exits non-zero on ELSPROBLEMS (version mismatches in optional deps).
// If stdout still has content, write it; otherwise skip silently.
if (err.stdout) {
fs.writeFileSync(
writeFileSync(
outFile,
'# Dependencies\n\n```\n' + err.stdout + '```\n',
);
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
28 changes: 28 additions & 0 deletions scripts/ts-resolve-hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* ESM resolve hook — rewrites .js specifiers to .ts when the .js file
* does not exist on disk. Needed because TypeScript's moduleResolution
* "nodenext" convention uses .js extensions in imports, but at runtime
* the source files are .ts.
*
* Loaded via module.register() from ts-resolve-loader.ts.
*/

import { existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';

export async function resolve(
specifier: string,
context: { parentURL?: string; conditions: string[] },
nextResolve: (specifier: string, context?: { parentURL?: string; conditions: string[] }) => Promise<{ url: string; shortCircuit?: boolean }>,
): Promise<{ url: string; shortCircuit?: boolean }> {
Comment on lines +14 to +17

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.

P2 Avoid the Function type — use a specific callback signature

Function is explicitly banned by most TypeScript style guides (equivalent to any for callables). The Node ESM nextResolve hook has a known shape:

Suggested change
specifier: string,
context: { parentURL?: string; conditions: string[] },
nextResolve: Function,
): Promise<{ url: string; shortCircuit?: boolean }> {
export async function resolve(
specifier: string,
context: { parentURL?: string; conditions: string[] },
nextResolve: (specifier: string, context?: { parentURL?: string; conditions: string[] }) => Promise<{ url: string; shortCircuit?: boolean }>,
): Promise<{ url: string; shortCircuit?: boolean }> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — replaced bare Function type with a specific callback signature: (specifier: string, context?: { parentURL?: string; conditions: string[] }) => Promise<{ url: string; shortCircuit?: boolean }>.

try {
return await nextResolve(specifier, context);
} catch (err: any) {
// Only attempt .js → .ts fallback for file-relative specifiers
if (err?.code === 'ERR_MODULE_NOT_FOUND' && specifier.endsWith('.js')) {
const tsSpecifier = specifier.slice(0, -3) + '.ts';
return nextResolve(tsSpecifier, context);
}
throw err;
}
}
9 changes: 9 additions & 0 deletions scripts/ts-resolve-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Registers the .js → .ts ESM resolve hook.
*
* Usage: node --experimental-strip-types --import ./scripts/ts-resolve-loader.ts src/cli.ts
*/

import { register } from 'node:module';

register(new URL('./ts-resolve-hooks.ts', import.meta.url));
Comment on lines +1 to +9

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.

P2 New .ts loader duplicates existing .js loader but is unused

scripts/ts-resolve-loader.ts and scripts/ts-resolve-hooks.ts are newly added files, but neither is referenced anywhere in CI workflows, package.json scripts, or vitest.config.ts. Every callsite that needs the resolver (benchmark.yml lines 96, 247, 384, 521) still explicitly uses --import ./scripts/ts-resolve-loader.js.

The PR description notes that ESM loader hooks must stay .js for bootstrap reasons, which is consistent — but it's unclear what purpose these new .ts duplicates serve. If they're intended for documentation or future use, a comment explaining that would help. If they're not needed, they introduce confusion about which version to use and create an extra maintenance burden.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — removed both scripts/ts-resolve-loader.ts and scripts/ts-resolve-hooks.ts. All callsites correctly use the .js versions (ESM loader hooks must stay .js for bootstrap reasons), so the .ts duplicates were dead code adding maintenance confusion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correction on my earlier reply: I initially removed these files, but CI revealed they ARE used by the test suite. Three test files (batch.test.ts, cli.test.ts, registry.test.ts) reference scripts/ts-resolve-loader.ts via new URL('../../scripts/ts-resolve-loader.ts', import.meta.url) when spawning child node processes that need the loader.

The .ts versions serve a different purpose than the .js versions: the .js files are used by CI workflows and package.json scripts (where Node runs the loader directly), while the .ts files are used by the test suite's child process spawning (where the test resolver handles .ts imports).

Reverted the deletion. These files are correctly part of the PR.

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion src/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
// Note: if import() rejects (e.g. missing dependency), the rejected Promise is cached
// by the CJS module system and every subsequent require() call will re-surface the same
// rejection without re-attempting the load.
module.exports = import('./index.js');
module.exports = import('./index.ts');
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,10 @@ describe('Call Resolution Precision/Recall', () => {

for (const lang of languages) {
describe(lang, () => {
let fixtureDir;
let resolvedEdges;
let expectedEdges;
let metrics;
let fixtureDir: string;
let resolvedEdges: any[];
let expectedEdges: any[];
let metrics: any;

beforeAll(async () => {
fixtureDir = copyFixture(lang);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { PipelineContext } from '../../src/domain/graph/builder/context.js';
import { collectFiles } from '../../src/domain/graph/builder/stages/collect-files.js';

let tmpDir;
let tmpDir: string;

beforeAll(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-stage-collect-'));
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { PipelineContext } from '../../src/domain/graph/builder/context.js';
import { detectChanges } from '../../src/domain/graph/builder/stages/detect-changes.js';
import { writeJournalHeader } from '../../src/domain/graph/journal.js';

let tmpDir;
let tmpDir: string;

beforeAll(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-stage-detect-'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { buildGraph } from '../../src/domain/graph/builder/pipeline.js';

const FIXTURE_DIR = path.join(import.meta.dirname, '..', 'fixtures', 'sample-project');
let tmpDir;
let tmpDir: string;

beforeAll(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-pipeline-'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import { createParsers, getParser } from '../../src/domain/parser.js';
import { extractDataflow } from '../../src/features/dataflow.js';
import { isNativeAvailable } from '../../src/infrastructure/native.js';

let native;
let parsers;
let native: any;
let parsers: any;
let nativeHasDataflow = false;

/**
Expand Down
4 changes: 2 additions & 2 deletions tests/engines/parity.test.js → tests/engines/parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import {
} from '../../src/domain/parser.js';
import { isNativeAvailable } from '../../src/infrastructure/native.js';

let native;
let parsers;
let native: any;
let parsers: any;

function wasmExtract(code, filePath) {
const parser = getParser(parsers, filePath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { beforeAll, describe, expect, it } from 'vitest';
import { createParsers, getParser, parseFileAuto } from '../../src/domain/parser.js';
import { extractSymbols } from '../../src/extractors/javascript.js';

let parsers;
let parsers: any;

beforeAll(async () => {
parsers = await createParsers();
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
6 changes: 0 additions & 6 deletions tests/helpers/node-version.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { isNativeAvailable, loadNative } from '../../src/infrastructure/native.j
const hasNative = isNativeAvailable();

describe.skipIf(!hasNative)('ParseTreeCache', () => {
let cache;
let cache: any;

beforeEach(() => {
const native = loadNative();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import { isNativeAvailable } from '../../src/infrastructure/native.js';
const hasNative = isNativeAvailable();

describe.skipIf(!hasNative)('Watcher incremental flow', () => {
let tmpDir;
let cache;
let tmpDir: string;
let cache: any;

beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-inc-'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function insertAstNode(db, file, line, kind, name, text, receiver, parentNodeId)

// ─── Fixture DB ────────────────────────────────────────────────────────

let tmpDir, dbPath;
let tmpDir: string, dbPath: string;

beforeAll(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-ast-'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function insertComplexity(db, nodeId, opts = {}) {

// ─── Fixture DB ────────────────────────────────────────────────────────

let tmpDir, dbPath;
let tmpDir: string, dbPath: string;

beforeAll(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-audit-'));
Expand Down
Loading
Loading