vigiles 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +127 -5
  2. package/dist/action-gate.d.ts +28 -0
  3. package/dist/action-gate.js +73 -0
  4. package/dist/cli.js +450 -75
  5. package/dist/community-skills.d.ts +22 -0
  6. package/dist/community-skills.js +86 -0
  7. package/dist/compile-generator.d.ts +48 -0
  8. package/dist/compile-generator.js +322 -0
  9. package/dist/compile.d.ts +3 -0
  10. package/dist/compile.js +217 -26
  11. package/dist/eval.d.ts +87 -0
  12. package/dist/eval.js +208 -0
  13. package/dist/frontmatter.d.ts +24 -6
  14. package/dist/frontmatter.js +103 -30
  15. package/dist/generate-schema.js +10 -0
  16. package/dist/harness-assert.d.ts +68 -0
  17. package/dist/harness-assert.js +127 -0
  18. package/dist/harness-test.d.ts +45 -0
  19. package/dist/harness-test.js +138 -0
  20. package/dist/inline.d.ts +22 -4
  21. package/dist/inline.js +60 -13
  22. package/dist/jest.d.ts +9 -0
  23. package/dist/jest.js +23 -0
  24. package/dist/judge.d.ts +29 -0
  25. package/dist/judge.js +88 -0
  26. package/dist/linters.js +28 -0
  27. package/dist/mock-model.d.ts +31 -0
  28. package/dist/mock-model.js +189 -0
  29. package/dist/plugin-loader.d.ts +37 -0
  30. package/dist/plugin-loader.js +195 -0
  31. package/dist/refs.d.ts +44 -0
  32. package/dist/refs.js +144 -0
  33. package/dist/run-hook.d.ts +77 -0
  34. package/dist/run-hook.js +80 -0
  35. package/dist/run-scripts.d.ts +20 -0
  36. package/dist/run-scripts.js +70 -0
  37. package/dist/skill-driver.d.ts +77 -0
  38. package/dist/skill-driver.js +76 -0
  39. package/dist/skill-runtime.d.ts +101 -0
  40. package/dist/skill-runtime.js +289 -0
  41. package/dist/skill-test.d.ts +47 -0
  42. package/dist/skill-test.js +77 -0
  43. package/dist/spec.d.ts +90 -4
  44. package/dist/spec.js +29 -0
  45. package/dist/symbols.d.ts +30 -0
  46. package/dist/symbols.js +142 -0
  47. package/dist/vitest.d.mts +9 -0
  48. package/dist/vitest.mjs +22 -0
  49. package/package.json +45 -6
package/dist/spec.d.ts CHANGED
@@ -135,7 +135,13 @@ export interface SkillRef {
135
135
  readonly _ref: "skill";
136
136
  readonly path: VerifiedRef;
137
137
  }
138
- export type Ref = FileRef | CmdRef | SkillRef;
138
+ /** A typed symbol reference the named file must define the named symbol. */
139
+ export interface SymbolRef {
140
+ readonly _ref: "symbol";
141
+ readonly file: VerifiedPath;
142
+ readonly symbol: string;
143
+ }
144
+ export type Ref = FileRef | CmdRef | SkillRef | SymbolRef;
139
145
  /**
140
146
  * Reference a file path — verified to exist at compile time.
141
147
  * When generated types are present, narrowed to known project files.
@@ -146,6 +152,13 @@ export declare function file(path: NoInfer<StrictFile>): FileRef;
146
152
  * When generated types are present, narrowed to known npm scripts.
147
153
  */
148
154
  export declare function cmd(command: NoInfer<StrictCmd>): CmdRef;
155
+ /**
156
+ * Reference a symbol defined in a file — verified at compile time that the
157
+ * named file exists AND defines the named symbol (via ast-grep, cross-language).
158
+ * Compiles to the file-qualified inline form `` `file#symbol` `` so the markdown
159
+ * `audit` / `refs-hook` re-verify the same reference.
160
+ */
161
+ export declare function symbol(file: NoInfer<StrictFile>, name: string): SymbolRef;
149
162
  /**
150
163
  * Reference another skill or instruction file — verified to exist.
151
164
  * Compiles to a markdown link: [skill name](path)
@@ -214,18 +227,91 @@ type ClaudeSpecInput = ClaudeSpecBase & ClaudeSpecSections;
214
227
  * export default claude({ commands: {...}, rules: {...} });
215
228
  */
216
229
  export declare function claude(spec: ClaudeSpecInput): ClaudeSpec;
230
+ /**
231
+ * A deterministic gate on a skill step or its final result. A gate is one of:
232
+ * a command (exit 0), a file (must exist), or a *project role* that resolves to
233
+ * the host project's real command at run time. cmd/file gates are verified
234
+ * against the repo at author time; role gates are portable — a skill that runs
235
+ * in other repos should prefer `project("test")` over a hard-coded `npm test`.
236
+ */
237
+ export type Gate = CmdRef | FileRef | RoleGate;
238
+ /** Project command roles, resolved per host project at run time. */
239
+ export type ProjectRole = "test" | "build" | "lint";
240
+ /** A portable gate that resolves to the host project's command for a role. */
241
+ export interface RoleGate {
242
+ readonly _ref: "role";
243
+ readonly role: ProjectRole;
244
+ }
245
+ /**
246
+ * A portable gate that resolves to the host project's command for a role
247
+ * (e.g. `project("test")` → `npm test` / `pytest` / `cargo test`). Use this
248
+ * in skills meant to run across projects, instead of hard-coding a command.
249
+ */
250
+ export declare function project(role: ProjectRole): RoleGate;
251
+ /**
252
+ * A declared skill input. Compiles to the `argument-hint` frontmatter and a
253
+ * `## Arguments` section; referenced as `$1`/`$2`/`$ARGUMENTS` in the body.
254
+ */
255
+ export interface SkillInput {
256
+ /** Argument name, e.g. "pattern". */
257
+ readonly name: string;
258
+ /** Human-readable hint shown in argument-hint and the Arguments section. */
259
+ readonly hint: string;
260
+ /** Required by default; set false to render as optional (`[<name>]`). */
261
+ readonly required?: boolean;
262
+ }
263
+ /** One step of a gated skill pipeline. */
264
+ export interface SkillStep {
265
+ /** What the model should do — prose, optionally with typed refs. */
266
+ readonly do: string | InstructionFragment[];
267
+ /** Deterministic check that must pass before advancing to the next step. */
268
+ readonly gate?: Gate;
269
+ /** Max attempts to satisfy the gate before the step fails (default 1). */
270
+ readonly retry?: number;
271
+ }
272
+ /** Declare a skill input (compiles to argument-hint + an Arguments entry). */
273
+ export declare function input(name: string, hint: string, opts?: {
274
+ required?: boolean;
275
+ }): SkillInput;
276
+ /** Declare a gated pipeline step. */
277
+ export declare function step(instr: string | InstructionFragment[], opts?: {
278
+ gate?: Gate;
279
+ retry?: number;
280
+ }): SkillStep;
217
281
  export interface SkillSpec {
218
282
  readonly _specType: "skill";
219
283
  /** Skill name (used in frontmatter). */
220
284
  readonly name: string;
221
285
  /** Short description (used in frontmatter). */
222
286
  readonly description: string;
223
- /** Hint for the argument (used in frontmatter). */
287
+ /**
288
+ * Hint for the argument (frontmatter). Ignored when `inputs` is set —
289
+ * `inputs` derive the argument-hint instead.
290
+ */
224
291
  readonly argumentHint?: string;
292
+ /** Typed inputs — compile to argument-hint + a `## Arguments` section. */
293
+ readonly inputs?: readonly SkillInput[];
225
294
  /** Whether to disable model invocation (frontmatter flag). */
226
295
  readonly disableModelInvocation?: boolean;
227
- /** Instruction body — string or tagged template with typed refs. */
228
- readonly body: string | InstructionFragment[];
296
+ /**
297
+ * Gated pipeline steps. When set, the skill compiles to a `## Steps`
298
+ * checklist with a deterministic gate per step. Use this OR `body`.
299
+ */
300
+ readonly steps?: readonly SkillStep[];
301
+ /**
302
+ * Terminal postcondition — the skill is "done" only when this gate passes.
303
+ * Compiles to a `## Result` section + a `vigiles:result` marker.
304
+ */
305
+ readonly result?: Gate;
306
+ /** Freeform instruction body (linear/unstructured skills). Use this OR `steps`. */
307
+ readonly body?: string | InstructionFragment[];
308
+ /**
309
+ * Max lines for an inline fenced code block before compilation errors,
310
+ * forcing the script into a file referenced via `file()` (default 20).
311
+ * Keeps big scripts out of the skill body (token budget + progressive
312
+ * disclosure). Set 0 to disable.
313
+ */
314
+ readonly maxInlineCodeLines?: number;
229
315
  }
230
316
  /**
231
317
  * Define a SKILL.md specification.
package/dist/spec.js CHANGED
@@ -15,9 +15,13 @@ exports.guidance = guidance;
15
15
  exports.guard = guard;
16
16
  exports.file = file;
17
17
  exports.cmd = cmd;
18
+ exports.symbol = symbol;
18
19
  exports.ref = ref;
19
20
  exports.instructions = instructions;
20
21
  exports.claude = claude;
22
+ exports.project = project;
23
+ exports.input = input;
24
+ exports.step = step;
21
25
  exports.skill = skill;
22
26
  exports.defineConfig = defineConfig;
23
27
  // ---------------------------------------------------------------------------
@@ -81,6 +85,15 @@ function file(path) {
81
85
  function cmd(command) {
82
86
  return { _ref: "cmd", command: command };
83
87
  }
88
+ /**
89
+ * Reference a symbol defined in a file — verified at compile time that the
90
+ * named file exists AND defines the named symbol (via ast-grep, cross-language).
91
+ * Compiles to the file-qualified inline form `` `file#symbol` `` so the markdown
92
+ * `audit` / `refs-hook` re-verify the same reference.
93
+ */
94
+ function symbol(file, name) {
95
+ return { _ref: "symbol", file: file, symbol: name };
96
+ }
84
97
  /**
85
98
  * Reference another skill or instruction file — verified to exist.
86
99
  * Compiles to a markdown link: [skill name](path)
@@ -116,6 +129,22 @@ function instructions(strings, ...values) {
116
129
  function claude(spec) {
117
130
  return { _specType: "claude", ...spec };
118
131
  }
132
+ /**
133
+ * A portable gate that resolves to the host project's command for a role
134
+ * (e.g. `project("test")` → `npm test` / `pytest` / `cargo test`). Use this
135
+ * in skills meant to run across projects, instead of hard-coding a command.
136
+ */
137
+ function project(role) {
138
+ return { _ref: "role", role };
139
+ }
140
+ /** Declare a skill input (compiles to argument-hint + an Arguments entry). */
141
+ function input(name, hint, opts = {}) {
142
+ return { name, hint, required: opts.required };
143
+ }
144
+ /** Declare a gated pipeline step. */
145
+ function step(instr, opts = {}) {
146
+ return { do: instr, gate: opts.gate, retry: opts.retry };
147
+ }
119
148
  /**
120
149
  * Define a SKILL.md specification.
121
150
  *
@@ -0,0 +1,30 @@
1
+ import { Lang } from "@ast-grep/napi";
2
+ /** A language key accepted by ast-grep's `parse` (core enum or registered id). */
3
+ type LangKey = Lang | string;
4
+ /** The ast-grep language for a file, or null if unsupported (graceful skip). */
5
+ export declare function langForFile(file: string): LangKey | null;
6
+ /** A symbol definition found in a file. */
7
+ export interface SymbolDef {
8
+ /** The defined identifier, e.g. "parseConfig". */
9
+ readonly name: string;
10
+ /** The tree-sitter node kind, e.g. "function_declaration" (raw, per-grammar). */
11
+ readonly kind: string;
12
+ /** Enclosing class/module name, or "" at top level. */
13
+ readonly scope: string;
14
+ /** 1-based line of the definition. */
15
+ readonly line: number;
16
+ }
17
+ /** Extract the symbols defined in a single file's source. */
18
+ export declare function definedSymbols(code: string, lang: LangKey): SymbolDef[];
19
+ /** Defined symbols for a file on disk, or [] if unreadable/unsupported. */
20
+ export declare function definedSymbolsInFile(file: string): SymbolDef[];
21
+ /**
22
+ * Whether `file` defines a top-level (or scoped) symbol named `name`. This is
23
+ * the whole check for a file-qualified reference (`path#symbol`): we parse the
24
+ * one named file — no project-wide index, no resolution across files. As a
25
+ * fallback we also consult a co-located declaration file (`.rbi` / `.d.ts`), so
26
+ * typed dynamic symbols resolve without running Sorbet / the TS compiler.
27
+ */
28
+ export declare function fileDefinesSymbol(file: string, name: string): boolean;
29
+ export {};
30
+ //# sourceMappingURL=symbols.d.ts.map
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.langForFile = langForFile;
7
+ exports.definedSymbols = definedSymbols;
8
+ exports.definedSymbolsInFile = definedSymbolsInFile;
9
+ exports.fileDefinesSymbol = fileDefinesSymbol;
10
+ /**
11
+ * vigiles — cross-language symbol index (ast-grep / tree-sitter).
12
+ *
13
+ * The kernel of harness-pinned reference verification. Instruction files
14
+ * reference project symbols in prose (`parseConfig`, `User`, `create_docx.py`);
15
+ * authors never write a verifiable `file#symbol` form (corpus: ~0%). So instead
16
+ * of asking authors to annotate, the *harness* resolves a bare reference against
17
+ * the live code at write time and pins it. This module is the resolver: extract
18
+ * the symbols a file defines, and build a project-wide name → locations index.
19
+ *
20
+ * Boundary (see research/symbol-verification.md): we answer "does a definition
21
+ * with this name (in this scope) exist", NOT "does this reference resolve through
22
+ * imports / Zeitwerk / tsconfig". Resolution is per-language and architectural —
23
+ * delegated. Ambiguity (a name defined in several files) is reported, not guessed.
24
+ */
25
+ const node_fs_1 = require("node:fs");
26
+ const node_path_1 = require("node:path");
27
+ const napi_1 = require("@ast-grep/napi");
28
+ const lang_python_1 = __importDefault(require("@ast-grep/lang-python"));
29
+ const lang_rust_1 = __importDefault(require("@ast-grep/lang-rust"));
30
+ const lang_ruby_1 = __importDefault(require("@ast-grep/lang-ruby"));
31
+ let registered = false;
32
+ function ensureRegistered() {
33
+ if (registered)
34
+ return;
35
+ // Non-web grammars ship as separate packages registered at runtime.
36
+ (0, napi_1.registerDynamicLanguage)({ python: lang_python_1.default, rust: lang_rust_1.default, ruby: lang_ruby_1.default });
37
+ registered = true;
38
+ }
39
+ const EXT_LANG = {
40
+ ".ts": napi_1.Lang.TypeScript,
41
+ ".tsx": napi_1.Lang.Tsx,
42
+ ".mts": napi_1.Lang.TypeScript,
43
+ ".cts": napi_1.Lang.TypeScript,
44
+ ".d.ts": napi_1.Lang.TypeScript,
45
+ ".js": napi_1.Lang.JavaScript,
46
+ ".jsx": napi_1.Lang.JavaScript,
47
+ ".mjs": napi_1.Lang.JavaScript,
48
+ ".cjs": napi_1.Lang.JavaScript,
49
+ ".css": napi_1.Lang.Css,
50
+ ".py": "python",
51
+ ".pyi": "python",
52
+ ".rs": "rust",
53
+ ".rb": "ruby",
54
+ ".rbi": "ruby",
55
+ };
56
+ /** The ast-grep language for a file, or null if unsupported (graceful skip). */
57
+ function langForFile(file) {
58
+ if (file.endsWith(".d.ts"))
59
+ return napi_1.Lang.TypeScript;
60
+ return EXT_LANG[(0, node_path_1.extname)(file).toLowerCase()] ?? null;
61
+ }
62
+ const ID_KINDS = new Set(["identifier", "constant", "type_identifier"]);
63
+ const SCOPE_KINDS = new Set([
64
+ "class_declaration",
65
+ "class_definition",
66
+ "class",
67
+ "module",
68
+ "interface_declaration",
69
+ "enum_declaration",
70
+ ]);
71
+ function recordNode(node, scope, out) {
72
+ const line = node.range().start.line + 1;
73
+ const nameNode = node.field("name");
74
+ if (nameNode) {
75
+ out.push({ name: nameNode.text(), kind: node.kind(), scope, line });
76
+ }
77
+ // Assignment-style constants (Python/Ruby `X = ...`): the identifier is the
78
+ // `left` field, not `name`.
79
+ const left = node.field("left");
80
+ if (left && ID_KINDS.has(left.kind())) {
81
+ out.push({ name: left.text(), kind: node.kind(), scope, line });
82
+ }
83
+ }
84
+ /** Extract the symbols defined in a single file's source. */
85
+ function definedSymbols(code, lang) {
86
+ ensureRegistered();
87
+ const out = [];
88
+ const walk = (node, scope) => {
89
+ recordNode(node, scope, out);
90
+ const nameNode = node.field("name");
91
+ const nextScope = SCOPE_KINDS.has(node.kind()) && nameNode ? nameNode.text() : scope;
92
+ for (const child of node.children())
93
+ walk(child, nextScope);
94
+ };
95
+ walk((0, napi_1.parse)(lang, code).root(), "");
96
+ return out;
97
+ }
98
+ /** Defined symbols for a file on disk, or [] if unreadable/unsupported. */
99
+ function definedSymbolsInFile(file) {
100
+ const lang = langForFile(file);
101
+ if (!lang)
102
+ return [];
103
+ try {
104
+ return definedSymbols((0, node_fs_1.readFileSync)(file, "utf-8"), lang);
105
+ }
106
+ catch {
107
+ return [];
108
+ }
109
+ }
110
+ // A co-located declaration file that may declare symbols the source defines
111
+ // dynamically (Sorbet `.rbi`, TypeScript `.d.ts`) — checked as a fallback so a
112
+ // metaprogrammed `define_method` / ambient declaration still resolves.
113
+ const DECL_SIBLING = {
114
+ ".ts": ".d.ts",
115
+ ".tsx": ".d.ts",
116
+ ".js": ".d.ts",
117
+ ".jsx": ".d.ts",
118
+ ".mjs": ".d.ts",
119
+ ".rb": ".rbi",
120
+ };
121
+ /**
122
+ * Whether `file` defines a top-level (or scoped) symbol named `name`. This is
123
+ * the whole check for a file-qualified reference (`path#symbol`): we parse the
124
+ * one named file — no project-wide index, no resolution across files. As a
125
+ * fallback we also consult a co-located declaration file (`.rbi` / `.d.ts`), so
126
+ * typed dynamic symbols resolve without running Sorbet / the TS compiler.
127
+ */
128
+ function fileDefinesSymbol(file, name) {
129
+ if (definedSymbolsInFile(file).some((d) => d.name === name))
130
+ return true;
131
+ const ext = (0, node_path_1.extname)(file);
132
+ const decl = DECL_SIBLING[ext];
133
+ if (decl && !file.endsWith(decl)) {
134
+ const sibling = file.slice(0, -ext.length) + decl;
135
+ if ((0, node_fs_1.existsSync)(sibling) &&
136
+ definedSymbolsInFile(sibling).some((d) => d.name === name)) {
137
+ return true;
138
+ }
139
+ }
140
+ return false;
141
+ }
142
+ //# sourceMappingURL=symbols.js.map
@@ -0,0 +1,9 @@
1
+ declare module "@vitest/expect" {
2
+ interface Matchers<T = any> {
3
+ toHaveCreated(path: string): T;
4
+ toBlock(): T;
5
+ toBeatBaseline(baseline: string, arm: string, metric: string, by?: number): T;
6
+ }
7
+ }
8
+ export {};
9
+ //# sourceMappingURL=vitest.d.mts.map
@@ -0,0 +1,22 @@
1
+ /* eslint-disable max-params, @typescript-eslint/no-explicit-any --
2
+ The matcher signatures mirror the runtime vigilesMatchers (positional args),
3
+ and `Matchers<T = any>` must match @vitest/expect's generic default to merge. */
4
+ /**
5
+ * vigiles — vitest integration (opt-in). ESM, because vitest is ESM-only.
6
+ *
7
+ * Importing this entry registers the vigiles matchers AND augments vitest's
8
+ * types so `toHaveCreated` / `toBeatBaseline` type-check.
9
+ *
10
+ * // vitest.config.ts → test: { setupFiles: ["vigiles/vitest"] }
11
+ * // …or at the top of a test file:
12
+ * import "vigiles/vitest";
13
+ *
14
+ * expect(result).toHaveCreated("DONE");
15
+ * expect(report).toBeatBaseline("vanilla", "gated", "caught");
16
+ *
17
+ * vitest is an optional peer dependency — only vitest users load this entry.
18
+ */
19
+ import { expect } from "vitest";
20
+ import { vigilesMatchers } from "./harness-assert.js";
21
+ expect.extend(vigilesMatchers);
22
+ //# sourceMappingURL=vitest.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigiles",
3
- "version": "2.1.1",
3
+ "version": "2.3.0",
4
4
  "description": "Compile .spec.ts files to instruction files (CLAUDE.md, AGENTS.md) with linter cross-referencing",
5
5
  "bin": {
6
6
  "vigiles": "dist/cli.js"
@@ -11,11 +11,25 @@
11
11
  ".": "./dist/spec.js",
12
12
  "./spec": "./dist/spec.js",
13
13
  "./compile": "./dist/compile.js",
14
- "./linters": "./dist/linters.js"
14
+ "./linters": "./dist/linters.js",
15
+ "./eval": "./dist/eval.js",
16
+ "./harness-test": "./dist/harness-test.js",
17
+ "./harness-assert": "./dist/harness-assert.js",
18
+ "./run-hook": "./dist/run-hook.js",
19
+ "./plugin-loader": "./dist/plugin-loader.js",
20
+ "./judge": "./dist/judge.js",
21
+ "./mock-model": "./dist/mock-model.js",
22
+ "./vitest": {
23
+ "types": "./dist/vitest.d.mts",
24
+ "default": "./dist/vitest.mjs"
25
+ },
26
+ "./jest": "./dist/jest.js"
15
27
  },
16
28
  "files": [
17
29
  "dist/**/*.js",
30
+ "dist/**/*.mjs",
18
31
  "dist/**/*.d.ts",
32
+ "dist/**/*.d.mts",
19
33
  "!dist/**/*.test.js",
20
34
  "!dist/**/*.test.d.ts",
21
35
  "action.yml",
@@ -25,10 +39,16 @@
25
39
  ],
26
40
  "scripts": {
27
41
  "build": "tsc",
28
- "test": "npm run build && node --test dist/spec.test.js dist/validate.test.js dist/cli.test.js dist/proofs.test.js dist/inline.test.js dist/sidecar.test.js dist/coverage.test.js dist/session.test.js dist/orphans.test.js dist/cedar.test.js dist/doc-refs.test.js dist/frontmatter.test.js",
42
+ "test": "npm run build && node --test dist/spec.test.js dist/validate.test.js dist/cli.test.js dist/proofs.test.js dist/inline.test.js dist/sidecar.test.js dist/coverage.test.js dist/session.test.js dist/orphans.test.js dist/cedar.test.js dist/doc-refs.test.js dist/frontmatter.test.js dist/skill-pipeline.test.js dist/skill-runtime.test.js dist/skill-driver.test.js dist/skill-test.test.js dist/compile-generator.test.js dist/community-skills.test.js dist/action-gate.test.js dist/symbols.test.js dist/refs.test.js dist/harness-test.test.js dist/eval.test.js dist/run-scripts.test.js dist/plugin-loader.test.js dist/harness-assert.test.js dist/judge.test.js dist/run-hook.test.js",
29
43
  "lint": "eslint src/",
30
44
  "fmt": "prettier --write .",
31
- "fmt:check": "prettier --check ."
45
+ "fmt:check": "prettier --check .",
46
+ "test:e2e": "bash test/e2e/run.sh",
47
+ "test:harness": "npm run build && node dist/cli.js test",
48
+ "test:eval": "npm run build && node dist/cli.js eval",
49
+ "test:vitest": "npm run build && vitest run",
50
+ "test:jest": "npm run build && jest",
51
+ "test:types": "npm run build && tsc --noEmit -p test/types/tsconfig.json"
32
52
  },
33
53
  "devDependencies": {
34
54
  "@eslint/js": "^10.0.1",
@@ -40,15 +60,34 @@
40
60
  "eslint": "^10.1.0",
41
61
  "eslint-plugin-sonarjs": "^4.0.2",
42
62
  "globals": "^17.4.0",
63
+ "jest": "^30.4.2",
43
64
  "prettier": "^3.8.1",
44
65
  "tsx": "^4.21.0",
45
- "typescript": "^5.9.3"
66
+ "typescript": "^5.9.3",
67
+ "vitest": "^4.1.8"
68
+ },
69
+ "peerDependencies": {
70
+ "jest": ">=28",
71
+ "vitest": ">=1"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "jest": {
75
+ "optional": true
76
+ },
77
+ "vitest": {
78
+ "optional": true
79
+ }
46
80
  },
47
81
  "dependencies": {
82
+ "@ast-grep/lang-python": "^0.0.6",
83
+ "@ast-grep/lang-ruby": "^0.0.7",
84
+ "@ast-grep/lang-rust": "^0.0.7",
85
+ "@ast-grep/napi": "^0.43.0",
48
86
  "@jackchuka/mdschema": "^0.12.8",
49
87
  "cosmiconfig": "^9.0.1",
50
88
  "glob": "^13.0.6",
51
89
  "js-yaml": "^4.1.0",
52
- "minimatch": "^10.0.1"
90
+ "minimatch": "^10.0.1",
91
+ "typescript": "^5.9.3"
53
92
  }
54
93
  }