astkit is a token-efficient, reproducible structural search/patch toolkit for TypeScript/JavaScript that can be run manually, in CI, or by an agent.
It operates on AST structure, TypeScript type services, and reference graphs.
- Definition and reference navigation via the TypeScript language service
- Structural search and rewrite using AST-shaped patterns
- Reference-based symbol ranking as an impact heuristic
astkit does not:
- infer program intent or meaning
- perform whole-program flow analysis
- prove correctness or enforce architecture
- replace the TypeScript compiler or type checker
- make decisions automatically
- guarantee behavioral equivalence after rewrites
npm install --save-dev @claudiu-ceia/astkit typescriptastkit uses the project's typescript installation for type services.
You can also install individual tools:
npm install --save-dev @claudiu-ceia/sgrep
npm install --save-dev @claudiu-ceia/spatch
npm install --save-dev @claudiu-ceia/nav typescriptnpx @claudiu-ceia/astkit <command> [args]Or via the bin entry:
astkit <command> [args]From an installed npm package:
npx skills@latest add ./node_modules/@claudiu-ceia/astkit/skills/astkit-tooling -a codex -yFrom this repository:
npm run skill:installGeneral help:
astkit --help
astkit <command> --helpFull command surface:
astkit nav declarations <file>
astkit nav definition <location>
astkit nav references <location>
astkit search [--json] [--no-color] [--no-isomorphisms] [--cwd <path>] <pattern-input> [scope]
astkit patch [--interactive] [--json] [--no-color] [--dry-run] [--cwd <path>] <patch-input> [scope]
astkit code-rank [--json] [--limit <n>] [--cwd <path>] [scope]astkit nav declarations <file>
astkit nav definition <location>
astkit nav references <location><location> format:
path/to/file.ts:120:17
Line and character are 1-indexed.
Mock execution:
$ astkit nav declarations src/__tests__/fixtures/simple.ts
{
"file": "src/__tests__/fixtures/simple.ts",
"declarations": [
{
"name": "User",
"kind": "interface",
"signature": "User",
"line": 1
},
{
"name": "UserService",
"kind": "class",
"signature": "UserService",
"line": 7
}
]
}
$ astkit nav definition src/__tests__/fixtures/importer.ts:1:15
{
"symbol": "User",
"definitions": [
{
"file": "src/__tests__/fixtures/simple.ts",
"line": 1,
"character": 18,
"kind": "interface",
"containerName": "\".../simple\""
}
]
}
$ astkit nav references src/__tests__/fixtures/simple.ts:1:18
{
"symbol": "interface User",
"definition": {
"file": "src/__tests__/fixtures/simple.ts",
"line": 1,
"character": 18
},
"references": [
{
"file": "src/__tests__/fixtures/simple.ts",
"line": 1,
"character": 18,
"isDefinition": true,
"isWriteAccess": true
},
{
"file": "src/__tests__/fixtures/importer.ts",
"line": 1,
"character": 15,
"isDefinition": false,
"isWriteAccess": true
}
]
}astkit search <pattern-input> [scope] [--cwd <path>] [--no-color] [--no-isomorphisms] [--json]- Default output is compact, file-grouped text
- In interactive terminals, captures are colorized
--no-colordisables coloring- Isomorphism expansion is enabled by default (commutative binary operators, object-literal key order, redundant parentheses)
--no-isomorphismsdisables isomorphism expansion--jsonprints structured result
Examples:
# inline pattern
astkit search 'const :[name] = :[value];' src
# pattern loaded from file
astkit search rules/find-const.sgrep src --cwd /repo
# machine output
astkit search --json 'const :[name] = :[value];' srcMock execution:
$ astkit search 'const :[name] = :[value];' src
//src/example.ts
12: const foo = 42;
27: const bar = makeValue( ...
$ astkit search --json 'const :[name] = :[value];' src
{
"scope": "/repo/src",
"pattern": "const :[name] = :[value];",
"filesScanned": 24,
"filesMatched": 2,
"totalMatches": 3,
"files": [
{
"file": "src/example.ts",
"matchCount": 2,
"matches": [
{
"line": 12,
"character": 1,
"matched": "const foo = 42;",
"captures": {
"name": "foo",
"value": "42"
}
}
]
}
]
}astkit patch <patch-input> [scope] [--cwd <path>] [--dry-run] [--json] [--no-color] [--interactive]patch-inputcan be inline patch text or a file path- Patch format uses
+/-/context lines in one document - Default output is compact diff-style text
--jsonprints structured output--no-colordisables color in compact output--interactivelets you accept/reject each match
Example:
astkit patch $'-const :[name] = :[value];\n+let :[name] = :[value];' srcNotes:
- In bash/zsh,
$'...'enables\nescapes for multi-line patch documents. Alternatively, pass a patch file path as<patch-input>.
Mock execution:
$ astkit patch --dry-run $'-const :[name] = :[value];\n+let :[name] = :[value];' src
diff --git a/src/example.ts b/src/example.ts
--- a/src/example.ts
+++ b/src/example.ts
@@ -12,1 +12,1 @@
-const foo = 42;
+let foo = 42;
1 file changed, 1 replacement, (dry-run)
$ astkit patch --interactive $'-const :[name] = :[value];\n+let :[name] = :[value];' src
------------------------------------------------------------------------
Change 1/2: src/example.ts:12
@@ -12,1 +12,1 @@
-const foo = 42;
+let foo = 42;
Choice [y/n/a/q] (default: n): yastkit code-rank [scope] [--cwd <path>] [--limit <n>] [--json]- Ranks exported symbols by TypeScript reference strength
- Uses the TypeScript language service (
findReferences) - Default output is compact, one symbol per line
--jsonprints structured output
Example:
astkit code-rank src --limit 20Mock execution:
$ astkit code-rank src --limit 3
1. score=14 refs=3 ext=3 files=2 function hot src/a.ts:1:8
2. score=9 refs=2 ext=2 files=1 function warm src/a.ts:5:8
3. score=6 refs=1 ext=1 files=1 interface User src/model.ts:1:18
$ astkit code-rank src --limit 1 --json
{
"scope": "/repo/src",
"symbolsRanked": 1,
"symbols": [
{
"symbol": "hot",
"kind": "function",
"file": "src/a.ts",
"line": 1,
"character": 8,
"score": 14,
"referenceCount": 3
}
]
}Both search and patch use the same hole/metavariable syntax:
:[name]:[_]anonymous hole (ignored in captures):[name~regex]regex-constrained hole...variadic wildcard (matches balanced text and can be reused in replacement)
Repeated named holes enforce equality:
:[x] + :[x]
This matches foo + foo but not foo + bar.
Hole captures are structurally balanced (brackets/strings/comments), which helps avoid malformed partial matches.
Default compact search output:
//src/example.ts
12: const foo = 42;
34: const bar = compute( ...
JSON search output (--json) includes:
- files scanned/matched
- total matches
- byte spans
- line/character
- captures
patch --json includes structured match and replacement stats per file.
Root exports:
patchProjectfromsrc/spatchsearchProjectfromsrc/sgreprankCodefromsrc/code-rank
import { patchProject, rankCode, searchProject } from "@claudiu-ceia/astkit";See detailed internals:
src/spatch/README.mdsrc/sgrep/README.md
Run local CLI:
bun run astkit -- <command> [args]Build distributable output:
npm run buildRun tests:
bun testRun typecheck:
npm run typecheckPreview npm package contents:
npm run pack:checkInstall this repo's skill locally for Codex:
npm run skill:installsrc/nav/*: TypeScript language-service navigation commandssrc/sgrep/*: search pipeline (parse -> search -> output)src/spatch/*: patch pipeline (parse -> rewrite -> output)src/pattern/*: shared structural parser/matcher/renderersrc/common/*: shared helpers (for example inline-or-file text resolution)