Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
28 changes: 20 additions & 8 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,18 @@ jobs:
- name: Run build benchmark
if: steps.existing.outputs.skip != 'true'
run: |
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
ARGS="--version ${{ steps.mode.outputs.version }}"
if [ "${{ steps.mode.outputs.source }}" = "npm" ]; then
ARGS="$ARGS --npm"
fi
node scripts/benchmark.js $ARGS 2>/dev/null > benchmark-result.json
node $STRIP_FLAG --import ./scripts/ts-resolve-loader.js scripts/benchmark.ts $ARGS 2>/dev/null > benchmark-result.json

- name: Update build report
if: steps.existing.outputs.skip != 'true'
run: node scripts/update-benchmark-report.js benchmark-result.json
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/update-benchmark-report.ts benchmark-result.json

- name: Upload build result
if: steps.existing.outputs.skip != 'true'
Expand Down Expand Up @@ -240,15 +243,18 @@ jobs:
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
ARGS="--version ${{ steps.mode.outputs.version }}"
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 $STRIP_FLAG --import ./scripts/ts-resolve-loader.js scripts/embedding-benchmark.ts $ARGS 2>/dev/null > embedding-benchmark-result.json

- name: Update embedding report
if: steps.existing.outputs.skip != 'true'
run: node scripts/update-embedding-report.js embedding-benchmark-result.json
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/update-embedding-report.ts embedding-benchmark-result.json

- name: Upload embedding result
if: steps.existing.outputs.skip != 'true'
Expand Down Expand Up @@ -377,15 +383,18 @@ jobs:
- name: Run query benchmark
if: steps.existing.outputs.skip != 'true'
run: |
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
ARGS="--version ${{ steps.mode.outputs.version }}"
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 $STRIP_FLAG --import ./scripts/ts-resolve-loader.js scripts/query-benchmark.ts $ARGS 2>/dev/null > query-benchmark-result.json

- name: Update query report
if: steps.existing.outputs.skip != 'true'
run: node scripts/update-query-report.js query-benchmark-result.json
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/update-query-report.ts query-benchmark-result.json

- name: Upload query result
if: steps.existing.outputs.skip != 'true'
Expand Down Expand Up @@ -514,15 +523,18 @@ jobs:
- name: Run incremental benchmark
if: steps.existing.outputs.skip != 'true'
run: |
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
ARGS="--version ${{ steps.mode.outputs.version }}"
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 $STRIP_FLAG --import ./scripts/ts-resolve-loader.js scripts/incremental-benchmark.ts $ARGS 2>/dev/null > incremental-benchmark-result.json

- name: Update incremental report
if: steps.existing.outputs.skip != 'true'
run: node scripts/update-incremental-report.js incremental-benchmark-result.json
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/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
6 changes: 4 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,9 @@ jobs:
node-version: 22

- name: Verify all dynamic imports resolve
run: node scripts/verify-imports.js
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

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
8 changes: 5 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,8 @@ 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
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
echo "Packaging version $VERSION"

- name: Build TypeScript
Expand Down Expand Up @@ -405,7 +406,8 @@ jobs:
run: |
git checkout -- package-lock.json
npm version "$VERSION" --no-git-tag-version --allow-same-version
node scripts/sync-native-versions.js
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
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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,4 @@ This repo uses [Greptile](https://greptile.com) for automated PR reviews. After

## Node Version

Requires Node >= 20.
Requires Node >= 22.6.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/**
* pre-commit-checks.js — Consolidated pre-commit codegraph checks.
* pre-commit-checks.ts — Consolidated pre-commit codegraph checks.
* Single Node.js process that runs all checks and returns structured JSON.
*
* Usage: node pre-commit-checks.js <WORK_ROOT> <EDITED_FILES> <STAGED_FILES>
* Usage: node pre-commit-checks.ts <WORK_ROOT> <EDITED_FILES> <STAGED_FILES>
*
* Output JSON: { action: "deny"|"allow", reason?: string, context?: string[] }
*
Expand All @@ -12,8 +12,11 @@
* 3. Diff-impact (informational) — shows blast radius of staged changes
*/

const fs = require('fs');
const path = require('path');
import fs from 'node:fs';
import path from 'node:path';
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);

const root = process.argv[2];
const editedRaw = process.argv[3] || '';
Expand Down Expand Up @@ -124,7 +127,7 @@ try {

// Scan for dynamic import() consumers
const srcDir = path.join(root, 'src');
function scanDynamic(dir) {
function scanDynamic(dir: string) {
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
if (ent.isDirectory()) { scanDynamic(path.join(dir, ent.name)); continue; }
if (!/\.(js|ts|tsx)$/.test(ent.name)) continue;
Expand Down
3 changes: 2 additions & 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,8 @@ 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
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

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"
},
"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.
46 changes: 0 additions & 46 deletions scripts/test.js

This file was deleted.

File renamed without changes.
6 changes: 3 additions & 3 deletions scripts/token-benchmark.js → scripts/token-benchmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
* ANTHROPIC_API_KEY set in environment
*
* Usage:
* node scripts/token-benchmark.js > result.json
* node scripts/token-benchmark.js --runs 1 --issues csrf-case-insensitive
* node scripts/token-benchmark.js --nextjs-dir /tmp/next.js --skip-graph
* node --experimental-strip-types --import ./scripts/ts-resolve-loader.js scripts/token-benchmark.ts > result.json
* node --experimental-strip-types --import ./scripts/ts-resolve-loader.js scripts/token-benchmark.ts --runs 1 --issues csrf-case-insensitive
* node --experimental-strip-types --import ./scripts/ts-resolve-loader.js scripts/token-benchmark.ts --nextjs-dir /tmp/next.js --skip-graph
*/

import { execFileSync, execSync } from 'node:child_process';
Expand Down
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 }> {
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));
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');
Loading
Loading