@mmnto/totem 0.44.0 → 1.1.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 (46) hide show
  1. package/dist/ast-classifier.d.ts +8 -0
  2. package/dist/ast-classifier.d.ts.map +1 -1
  3. package/dist/ast-classifier.js +2 -2
  4. package/dist/ast-classifier.js.map +1 -1
  5. package/dist/ast-query.d.ts +18 -0
  6. package/dist/ast-query.d.ts.map +1 -0
  7. package/dist/ast-query.js +177 -0
  8. package/dist/ast-query.js.map +1 -0
  9. package/dist/ast-query.test.d.ts +2 -0
  10. package/dist/ast-query.test.d.ts.map +1 -0
  11. package/dist/ast-query.test.js +79 -0
  12. package/dist/ast-query.test.js.map +1 -0
  13. package/dist/compiler.d.ts +27 -11
  14. package/dist/compiler.d.ts.map +1 -1
  15. package/dist/compiler.js +94 -5
  16. package/dist/compiler.js.map +1 -1
  17. package/dist/compiler.test.js +2 -2
  18. package/dist/compiler.test.js.map +1 -1
  19. package/dist/config-schema.d.ts +8 -0
  20. package/dist/config-schema.d.ts.map +1 -1
  21. package/dist/config-schema.js +4 -0
  22. package/dist/config-schema.js.map +1 -1
  23. package/dist/embedders/ollama-embedder.d.ts.map +1 -1
  24. package/dist/embedders/ollama-embedder.js +5 -1
  25. package/dist/embedders/ollama-embedder.js.map +1 -1
  26. package/dist/index.d.ts +6 -3
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +6 -3
  29. package/dist/index.js.map +1 -1
  30. package/dist/ingest/pipeline.d.ts +11 -0
  31. package/dist/ingest/pipeline.d.ts.map +1 -1
  32. package/dist/ingest/pipeline.js +64 -0
  33. package/dist/ingest/pipeline.js.map +1 -1
  34. package/dist/ingest/sync.d.ts +1 -1
  35. package/dist/ingest/sync.d.ts.map +1 -1
  36. package/dist/ingest/sync.js +1 -1
  37. package/dist/ingest/sync.js.map +1 -1
  38. package/dist/lock.d.ts +18 -0
  39. package/dist/lock.d.ts.map +1 -0
  40. package/dist/lock.js +192 -0
  41. package/dist/lock.js.map +1 -0
  42. package/dist/lock.test.d.ts +2 -0
  43. package/dist/lock.test.d.ts.map +1 -0
  44. package/dist/lock.test.js +94 -0
  45. package/dist/lock.test.js.map +1 -0
  46. package/package.json +1 -1
@@ -1,5 +1,13 @@
1
1
  import type { AstContext } from './compiler.js';
2
2
  export type SupportedLanguage = 'typescript' | 'tsx' | 'javascript';
3
+ /**
4
+ * Initialize web-tree-sitter WASM engine. Idempotent — safe to call multiple times.
5
+ */
6
+ export declare function ensureInit(): Promise<void>;
7
+ /**
8
+ * Load a Tree-sitter grammar WASM file for the given language.
9
+ */
10
+ export declare function loadGrammar(lang: SupportedLanguage): Promise<import('web-tree-sitter').Language>;
3
11
  /**
4
12
  * Classify specific lines of source code by their AST context.
5
13
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ast-classifier.d.ts","sourceRoot":"","sources":["../src/ast-classifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAIhD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,KAAK,GAAG,YAAY,CAAC;AAgGpE;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EAAE,EACrB,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CA+ClC;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAe9E"}
1
+ {"version":3,"file":"ast-classifier.d.ts","sourceRoot":"","sources":["../src/ast-classifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAIhD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,KAAK,GAAG,YAAY,CAAC;AASpE;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAgBhD;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,OAAO,iBAAiB,EAAE,QAAQ,CAAC,CA0B7C;AAqCD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EAAE,EACrB,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CA+ClC;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAe9E"}
@@ -6,7 +6,7 @@ const grammarCache = new Map();
6
6
  /**
7
7
  * Initialize web-tree-sitter WASM engine. Idempotent — safe to call multiple times.
8
8
  */
9
- async function ensureInit() {
9
+ export async function ensureInit() {
10
10
  if (Parser)
11
11
  return;
12
12
  if (initPromise)
@@ -25,7 +25,7 @@ async function ensureInit() {
25
25
  /**
26
26
  * Load a Tree-sitter grammar WASM file for the given language.
27
27
  */
28
- async function loadGrammar(lang) {
28
+ export async function loadGrammar(lang) {
29
29
  const cached = grammarCache.get(lang);
30
30
  if (cached)
31
31
  return cached;
@@ -1 +1 @@
1
- {"version":3,"file":"ast-classifier.js","sourceRoot":"","sources":["../src/ast-classifier.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ast-classifier.js","sourceRoot":"","sources":["../src/ast-classifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export interface AstMatch {
2
+ lineNumber: number;
3
+ lineText: string;
4
+ }
5
+ /**
6
+ * Convenience wrapper: read + parse + query in one call.
7
+ * For batch operations, use `matchAstQueriesBatch` instead.
8
+ */
9
+ export declare function matchAstQuery(filePath: string, astQuery: string, addedLineNumbers: number[], cwd: string): Promise<AstMatch[]>;
10
+ /**
11
+ * Parse a file once and run multiple AST queries against it efficiently.
12
+ * O(M + N) instead of O(M * N) — file is read and parsed exactly once.
13
+ */
14
+ export declare function matchAstQueriesBatch(filePath: string, queries: Array<{
15
+ astQuery: string;
16
+ addedLineNumbers: number[];
17
+ }>, cwd: string): Promise<Map<string, AstMatch[]>>;
18
+ //# sourceMappingURL=ast-query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast-query.d.ts","sourceRoot":"","sources":["../src/ast-query.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AA6FD;;;GAGG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,MAAM,EAAE,EAC1B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,QAAQ,EAAE,CAAC,CA0CrB;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,EAChE,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,CA6DlC"}
@@ -0,0 +1,177 @@
1
+ import { execFile } from 'node:child_process';
2
+ import * as fs from 'node:fs/promises';
3
+ import * as path from 'node:path';
4
+ import { promisify } from 'node:util';
5
+ import { ensureInit, extensionToLanguage, loadGrammar } from './ast-classifier.js';
6
+ const execFileAsync = promisify(execFile);
7
+ // ─── File reading ───────────────────────────────────
8
+ /**
9
+ * Read file content — try `git show :path` first (staged content), fall back to disk.
10
+ * Fully async — does not block the event loop.
11
+ */
12
+ async function readFileContent(filePath, cwd) {
13
+ try {
14
+ const { stdout } = await execFileAsync('git', ['show', `:${filePath}`], {
15
+ cwd,
16
+ encoding: 'utf-8',
17
+ maxBuffer: 10 * 1024 * 1024, // 10MB safety cap
18
+ });
19
+ return stdout;
20
+ }
21
+ catch {
22
+ // Fall back to disk
23
+ }
24
+ try {
25
+ const fullPath = path.resolve(cwd, filePath);
26
+ return await fs.readFile(fullPath, 'utf-8');
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ // ─── Query execution ────────────────────────────────
33
+ /**
34
+ * Run a single S-expression query against a parsed tree.
35
+ * Returns matches that overlap with added line numbers.
36
+ */
37
+ function runQuery(QueryClass, grammar, rootNode, lines, astQuery, addedLineNumbers) {
38
+ let query = null;
39
+ try {
40
+ query = new QueryClass(grammar, astQuery);
41
+ const matches = query.matches(rootNode);
42
+ const results = [];
43
+ for (const match of matches) {
44
+ // Find the @violation capture, or use the first capture
45
+ let targetNode = null;
46
+ for (const capture of match.captures) {
47
+ if (capture.name === 'violation') {
48
+ targetNode = capture.node;
49
+ break;
50
+ }
51
+ }
52
+ if (!targetNode && match.captures.length > 0) {
53
+ targetNode = match.captures[0].node;
54
+ }
55
+ if (!targetNode)
56
+ continue;
57
+ const startLine = targetNode.startPosition.row + 1;
58
+ const endLine = targetNode.endPosition.row + 1;
59
+ for (let lineNum = startLine; lineNum <= endLine; lineNum++) {
60
+ if (addedLineNumbers.has(lineNum)) {
61
+ results.push({
62
+ lineNumber: lineNum,
63
+ lineText: lines[lineNum - 1] ?? '',
64
+ });
65
+ break;
66
+ }
67
+ }
68
+ }
69
+ return results;
70
+ }
71
+ catch {
72
+ // Invalid query — fail-open
73
+ return [];
74
+ }
75
+ finally {
76
+ query?.delete();
77
+ }
78
+ }
79
+ // ─── Public API ─────────────────────────────────────
80
+ /**
81
+ * Convenience wrapper: read + parse + query in one call.
82
+ * For batch operations, use `matchAstQueriesBatch` instead.
83
+ */
84
+ export async function matchAstQuery(filePath, astQuery, addedLineNumbers, cwd) {
85
+ if (addedLineNumbers.length === 0)
86
+ return [];
87
+ const ext = path.extname(filePath);
88
+ const lang = extensionToLanguage(ext);
89
+ if (!lang)
90
+ return [];
91
+ const content = await readFileContent(filePath, cwd);
92
+ if (!content)
93
+ return [];
94
+ try {
95
+ await ensureInit();
96
+ const grammar = await loadGrammar(lang);
97
+ const TreeSitter = await import('web-tree-sitter');
98
+ const ParserClass = TreeSitter.default?.Parser ?? TreeSitter.Parser;
99
+ const QueryClass = TreeSitter.default?.Query ?? TreeSitter.Query;
100
+ const parser = new ParserClass();
101
+ try {
102
+ parser.setLanguage(grammar);
103
+ const tree = parser.parse(content);
104
+ if (!tree)
105
+ return [];
106
+ try {
107
+ return runQuery(QueryClass, grammar, tree.rootNode, content.split('\n'), astQuery, new Set(addedLineNumbers));
108
+ }
109
+ finally {
110
+ tree.delete();
111
+ }
112
+ }
113
+ finally {
114
+ parser.delete();
115
+ }
116
+ }
117
+ catch {
118
+ return [];
119
+ }
120
+ }
121
+ /**
122
+ * Parse a file once and run multiple AST queries against it efficiently.
123
+ * O(M + N) instead of O(M * N) — file is read and parsed exactly once.
124
+ */
125
+ export async function matchAstQueriesBatch(filePath, queries, cwd) {
126
+ const results = new Map();
127
+ if (queries.length === 0)
128
+ return results;
129
+ const ext = path.extname(filePath);
130
+ const lang = extensionToLanguage(ext);
131
+ if (!lang) {
132
+ for (const q of queries)
133
+ results.set(q.astQuery, []);
134
+ return results;
135
+ }
136
+ const content = await readFileContent(filePath, cwd);
137
+ if (!content) {
138
+ for (const q of queries)
139
+ results.set(q.astQuery, []);
140
+ return results;
141
+ }
142
+ try {
143
+ await ensureInit();
144
+ const grammar = await loadGrammar(lang);
145
+ const TreeSitter = await import('web-tree-sitter');
146
+ const ParserClass = TreeSitter.default?.Parser ?? TreeSitter.Parser;
147
+ const QueryClass = TreeSitter.default?.Query ?? TreeSitter.Query;
148
+ const parser = new ParserClass();
149
+ try {
150
+ parser.setLanguage(grammar);
151
+ const tree = parser.parse(content);
152
+ if (!tree) {
153
+ for (const q of queries)
154
+ results.set(q.astQuery, []);
155
+ return results;
156
+ }
157
+ const lines = content.split('\n');
158
+ try {
159
+ for (const { astQuery, addedLineNumbers } of queries) {
160
+ results.set(astQuery, runQuery(QueryClass, grammar, tree.rootNode, lines, astQuery, new Set(addedLineNumbers)));
161
+ }
162
+ }
163
+ finally {
164
+ tree.delete();
165
+ }
166
+ }
167
+ finally {
168
+ parser.delete();
169
+ }
170
+ }
171
+ catch {
172
+ for (const q of queries)
173
+ results.set(q.astQuery, []);
174
+ }
175
+ return results;
176
+ }
177
+ //# sourceMappingURL=ast-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast-query.js","sourceRoot":"","sources":["../src/ast-query.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ast-query.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast-query.test.d.ts","sourceRoot":"","sources":["../src/ast-query.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,79 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+ import { matchAstQuery } from './ast-query.js';
6
+ // ─── Helpers ────────────────────────────────────────
7
+ let tmpDir;
8
+ beforeEach(() => {
9
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-ast-query-'));
10
+ });
11
+ afterEach(() => {
12
+ fs.rmSync(tmpDir, { recursive: true, force: true });
13
+ });
14
+ function writeFile(name, content) {
15
+ const filePath = path.join(tmpDir, name);
16
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
17
+ fs.writeFileSync(filePath, content, 'utf-8');
18
+ return name;
19
+ }
20
+ // ─── matchAstQuery ──────────────────────────────────
21
+ describe('matchAstQuery', () => {
22
+ it('matches a valid S-expression query against code', async () => {
23
+ const file = writeFile('src/app.ts', ['const x = 1;', 'console.log(x);', 'const y = 2;'].join('\n'));
24
+ // Query to find console.log calls — match member_expression with console.log
25
+ const query = '(call_expression function: (member_expression object: (identifier) @obj (#eq? @obj "console"))) @violation';
26
+ const matches = await matchAstQuery(file, query, [2], tmpDir);
27
+ expect(matches).toHaveLength(1);
28
+ expect(matches[0].lineNumber).toBe(2);
29
+ expect(matches[0].lineText).toContain('console.log');
30
+ });
31
+ it('filters matches to only added lines', async () => {
32
+ const file = writeFile('src/multi.ts', ['console.log("line 1");', 'const x = 1;', 'console.log("line 3");'].join('\n'));
33
+ const query = '(call_expression function: (member_expression object: (identifier) @obj (#eq? @obj "console"))) @violation';
34
+ // Only line 3 is "added"
35
+ const matches = await matchAstQuery(file, query, [3], tmpDir);
36
+ expect(matches).toHaveLength(1);
37
+ expect(matches[0].lineNumber).toBe(3);
38
+ });
39
+ it('returns empty array for invalid S-expression (fail-open)', async () => {
40
+ const file = writeFile('src/safe.ts', 'const x = 1;\n');
41
+ const matches = await matchAstQuery(file, '(this is not valid!!!', [1], tmpDir);
42
+ expect(matches).toEqual([]);
43
+ });
44
+ it('returns empty array for non-JS/TS files', async () => {
45
+ const file = writeFile('config.py', 'import os\nprint("hello")\n');
46
+ const query = '(identifier) @violation';
47
+ const matches = await matchAstQuery(file, query, [1, 2], tmpDir);
48
+ expect(matches).toEqual([]);
49
+ });
50
+ it('returns empty array when no added lines overlap with matches', async () => {
51
+ const file = writeFile('src/no-overlap.ts', ['console.log("line 1");', 'const x = 1;', 'const y = 2;'].join('\n'));
52
+ const query = '(call_expression function: (member_expression object: (identifier) @obj (#eq? @obj "console"))) @violation';
53
+ // Lines 2 and 3 are "added" but console.log is on line 1
54
+ const matches = await matchAstQuery(file, query, [2, 3], tmpDir);
55
+ expect(matches).toEqual([]);
56
+ });
57
+ it('returns empty array for nonexistent file', async () => {
58
+ const matches = await matchAstQuery('nonexistent.ts', '(identifier) @violation', [1], tmpDir);
59
+ expect(matches).toEqual([]);
60
+ });
61
+ it('returns empty array for empty addedLineNumbers', async () => {
62
+ const file = writeFile('src/empty.ts', 'const x = 1;\n');
63
+ const matches = await matchAstQuery(file, '(identifier) @violation', [], tmpDir);
64
+ expect(matches).toEqual([]);
65
+ });
66
+ it('handles JavaScript files', async () => {
67
+ const file = writeFile('src/app.js', ['const x = 1;', 'console.log(x);'].join('\n'));
68
+ const query = '(call_expression function: (member_expression object: (identifier) @obj (#eq? @obj "console"))) @violation';
69
+ const matches = await matchAstQuery(file, query, [2], tmpDir);
70
+ expect(matches).toHaveLength(1);
71
+ });
72
+ it('handles TSX files', async () => {
73
+ const file = writeFile('src/component.tsx', ['const x = 1;', 'console.log(x);', 'const el = <div>hello</div>;'].join('\n'));
74
+ const query = '(call_expression function: (member_expression object: (identifier) @obj (#eq? @obj "console"))) @violation';
75
+ const matches = await matchAstQuery(file, query, [2], tmpDir);
76
+ expect(matches).toHaveLength(1);
77
+ });
78
+ });
79
+ //# sourceMappingURL=ast-query.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast-query.test.js","sourceRoot":"","sources":["../src/ast-query.test.ts"],"names":[],"mappings":"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"}
@@ -8,8 +8,10 @@ export declare const CompiledRuleSchema: z.ZodObject<{
8
8
  pattern: z.ZodString;
9
9
  /** Human-readable violation message shown when the pattern matches */
10
10
  message: z.ZodString;
11
- /** Engine type — only 'regex' for MVP */
12
- engine: z.ZodLiteral<"regex">;
11
+ /** Engine type — 'regex' for line-level matching, 'ast' for Tree-sitter S-expression queries */
12
+ engine: z.ZodEnum<["regex", "ast"]>;
13
+ /** Tree-sitter S-expression query (required when engine is 'ast') */
14
+ astQuery: z.ZodOptional<z.ZodString>;
13
15
  /** ISO timestamp of when this rule was compiled */
14
16
  compiledAt: z.ZodString;
15
17
  /** ISO timestamp of when this rule was first created (survives recompilation) */
@@ -25,8 +27,9 @@ export declare const CompiledRuleSchema: z.ZodObject<{
25
27
  lessonHeading: string;
26
28
  pattern: string;
27
29
  message: string;
28
- engine: "regex";
30
+ engine: "regex" | "ast";
29
31
  compiledAt: string;
32
+ astQuery?: string | undefined;
30
33
  createdAt?: string | undefined;
31
34
  fileGlobs?: string[] | undefined;
32
35
  category?: "security" | "architecture" | "style" | "performance" | undefined;
@@ -36,8 +39,9 @@ export declare const CompiledRuleSchema: z.ZodObject<{
36
39
  lessonHeading: string;
37
40
  pattern: string;
38
41
  message: string;
39
- engine: "regex";
42
+ engine: "regex" | "ast";
40
43
  compiledAt: string;
44
+ astQuery?: string | undefined;
41
45
  createdAt?: string | undefined;
42
46
  fileGlobs?: string[] | undefined;
43
47
  category?: "security" | "architecture" | "style" | "performance" | undefined;
@@ -55,8 +59,10 @@ export declare const CompiledRulesFileSchema: z.ZodObject<{
55
59
  pattern: z.ZodString;
56
60
  /** Human-readable violation message shown when the pattern matches */
57
61
  message: z.ZodString;
58
- /** Engine type — only 'regex' for MVP */
59
- engine: z.ZodLiteral<"regex">;
62
+ /** Engine type — 'regex' for line-level matching, 'ast' for Tree-sitter S-expression queries */
63
+ engine: z.ZodEnum<["regex", "ast"]>;
64
+ /** Tree-sitter S-expression query (required when engine is 'ast') */
65
+ astQuery: z.ZodOptional<z.ZodString>;
60
66
  /** ISO timestamp of when this rule was compiled */
61
67
  compiledAt: z.ZodString;
62
68
  /** ISO timestamp of when this rule was first created (survives recompilation) */
@@ -72,8 +78,9 @@ export declare const CompiledRulesFileSchema: z.ZodObject<{
72
78
  lessonHeading: string;
73
79
  pattern: string;
74
80
  message: string;
75
- engine: "regex";
81
+ engine: "regex" | "ast";
76
82
  compiledAt: string;
83
+ astQuery?: string | undefined;
77
84
  createdAt?: string | undefined;
78
85
  fileGlobs?: string[] | undefined;
79
86
  category?: "security" | "architecture" | "style" | "performance" | undefined;
@@ -83,8 +90,9 @@ export declare const CompiledRulesFileSchema: z.ZodObject<{
83
90
  lessonHeading: string;
84
91
  pattern: string;
85
92
  message: string;
86
- engine: "regex";
93
+ engine: "regex" | "ast";
87
94
  compiledAt: string;
95
+ astQuery?: string | undefined;
88
96
  createdAt?: string | undefined;
89
97
  fileGlobs?: string[] | undefined;
90
98
  category?: "security" | "architecture" | "style" | "performance" | undefined;
@@ -99,8 +107,9 @@ export declare const CompiledRulesFileSchema: z.ZodObject<{
99
107
  lessonHeading: string;
100
108
  pattern: string;
101
109
  message: string;
102
- engine: "regex";
110
+ engine: "regex" | "ast";
103
111
  compiledAt: string;
112
+ astQuery?: string | undefined;
104
113
  createdAt?: string | undefined;
105
114
  fileGlobs?: string[] | undefined;
106
115
  category?: "security" | "architecture" | "style" | "performance" | undefined;
@@ -114,8 +123,9 @@ export declare const CompiledRulesFileSchema: z.ZodObject<{
114
123
  lessonHeading: string;
115
124
  pattern: string;
116
125
  message: string;
117
- engine: "regex";
126
+ engine: "regex" | "ast";
118
127
  compiledAt: string;
128
+ astQuery?: string | undefined;
119
129
  createdAt?: string | undefined;
120
130
  fileGlobs?: string[] | undefined;
121
131
  category?: "security" | "architecture" | "style" | "performance" | undefined;
@@ -175,13 +185,19 @@ export type RuleEventCallback = (event: 'trigger' | 'suppress', lessonHash: stri
175
185
  * Optional `onRuleEvent` callback enables observability metrics collection.
176
186
  */
177
187
  export declare function applyRulesToAdditions(rules: CompiledRule[], additions: DiffAddition[], onRuleEvent?: RuleEventCallback): Violation[];
188
+ /**
189
+ * Apply AST-engine compiled rules against pre-extracted diff additions.
190
+ * Async because it reads files and runs Tree-sitter queries.
191
+ * Handles fileGlobs filtering and suppression same as regex rules.
192
+ */
193
+ export declare function applyAstRulesToAdditions(rules: CompiledRule[], additions: DiffAddition[], cwd: string, onRuleEvent?: RuleEventCallback): Promise<Violation[]>;
178
194
  /**
179
195
  * Apply compiled rules against added lines from a diff.
180
196
  * Returns all violations found.
181
197
  * @param excludeFiles — file paths to skip (e.g., compiled-rules.json to avoid self-matches)
182
198
  */
183
199
  export declare function applyRules(rules: CompiledRule[], diff: string, excludeFiles?: string[]): Violation[];
184
- /** Load compiled rules from a JSON file. Returns empty array if file missing or invalid. */
200
+ /** Load compiled rules from a JSON file. Returns empty array if file missing. */
185
201
  export declare function loadCompiledRules(rulesPath: string, onWarn?: (msg: string) => void): CompiledRule[];
186
202
  /** Load the full compiled rules file (rules + non-compilable cache). */
187
203
  export declare function loadCompiledRulesFile(rulesPath: string, onWarn?: (msg: string) => void): CompiledRulesFile;
@@ -1 +1 @@
1
- {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"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"}
package/dist/compiler.js CHANGED
@@ -1,7 +1,11 @@
1
1
  import * as crypto from 'node:crypto';
2
2
  import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
3
4
  import safeRegex from 'safe-regex2';
4
5
  import { z } from 'zod';
6
+ import { extensionToLanguage } from './ast-classifier.js';
7
+ import { matchAstQueriesBatch } from './ast-query.js';
8
+ import { TotemParseError } from './errors.js';
5
9
  // ─── Schemas ─────────────────────────────────────────
6
10
  export const CompiledRuleSchema = z.object({
7
11
  /** SHA-256 hash (first 16 hex chars) of heading + body — detects edits */
@@ -12,8 +16,10 @@ export const CompiledRuleSchema = z.object({
12
16
  pattern: z.string(),
13
17
  /** Human-readable violation message shown when the pattern matches */
14
18
  message: z.string(),
15
- /** Engine type — only 'regex' for MVP */
16
- engine: z.literal('regex'),
19
+ /** Engine type — 'regex' for line-level matching, 'ast' for Tree-sitter S-expression queries */
20
+ engine: z.enum(['regex', 'ast']),
21
+ /** Tree-sitter S-expression query (required when engine is 'ast') */
22
+ astQuery: z.string().optional(),
17
23
  /** ISO timestamp of when this rule was compiled */
18
24
  compiledAt: z.string(),
19
25
  /** ISO timestamp of when this rule was first created (survives recompilation) */
@@ -247,6 +253,81 @@ export function applyRulesToAdditions(rules, additions, onRuleEvent) {
247
253
  }
248
254
  return violations;
249
255
  }
256
+ /**
257
+ * Apply AST-engine compiled rules against pre-extracted diff additions.
258
+ * Async because it reads files and runs Tree-sitter queries.
259
+ * Handles fileGlobs filtering and suppression same as regex rules.
260
+ */
261
+ export async function applyAstRulesToAdditions(rules, additions, cwd, onRuleEvent) {
262
+ const astRules = rules.filter((r) => r.engine === 'ast' && r.astQuery);
263
+ if (astRules.length === 0 || additions.length === 0)
264
+ return [];
265
+ // Group additions by file
266
+ const byFile = new Map();
267
+ for (const a of additions) {
268
+ const existing = byFile.get(a.file);
269
+ if (existing) {
270
+ existing.push(a);
271
+ }
272
+ else {
273
+ byFile.set(a.file, [a]);
274
+ }
275
+ }
276
+ const violations = [];
277
+ // Process each file once — batch all applicable AST queries per file
278
+ for (const [file, fileAdditions] of byFile) {
279
+ // Check language support
280
+ const ext = path.extname(file);
281
+ if (!extensionToLanguage(ext))
282
+ continue;
283
+ // Collect added line numbers, filtering suppressed lines
284
+ const addedLineNumbers = [];
285
+ for (const addition of fileAdditions) {
286
+ if (addition.astContext && addition.astContext !== 'code')
287
+ continue;
288
+ if (isSuppressed(addition.line, addition.precedingLine))
289
+ continue;
290
+ addedLineNumbers.push(addition.lineNumber);
291
+ }
292
+ if (addedLineNumbers.length === 0)
293
+ continue;
294
+ // Collect all applicable rules for this file
295
+ const applicableRules = astRules.filter((rule) => {
296
+ if (rule.fileGlobs && rule.fileGlobs.length > 0) {
297
+ return fileMatchesGlobs(file, rule.fileGlobs);
298
+ }
299
+ return true;
300
+ });
301
+ if (applicableRules.length === 0)
302
+ continue;
303
+ // Batch: parse file once, run all queries against the cached tree
304
+ const queries = applicableRules.map((rule) => ({
305
+ astQuery: rule.astQuery,
306
+ addedLineNumbers,
307
+ }));
308
+ const batchResults = await matchAstQueriesBatch(file, queries, cwd);
309
+ // Map results back to violations
310
+ for (const rule of applicableRules) {
311
+ const matches = batchResults.get(rule.astQuery) ?? [];
312
+ // Check for suppressions per match
313
+ for (const match of matches) {
314
+ const addition = fileAdditions.find((a) => a.lineNumber === match.lineNumber);
315
+ if (addition && isSuppressed(addition.line, addition.precedingLine)) {
316
+ onRuleEvent?.('suppress', rule.lessonHash);
317
+ continue;
318
+ }
319
+ onRuleEvent?.('trigger', rule.lessonHash);
320
+ violations.push({
321
+ rule,
322
+ file,
323
+ line: match.lineText,
324
+ lineNumber: match.lineNumber,
325
+ });
326
+ }
327
+ }
328
+ }
329
+ return violations;
330
+ }
250
331
  /**
251
332
  * Apply compiled rules against added lines from a diff.
252
333
  * Returns all violations found.
@@ -263,18 +344,22 @@ export function applyRules(rules, diff, excludeFiles) {
263
344
  return applyRulesToAdditions(rules, additions);
264
345
  }
265
346
  // ─── File I/O ────────────────────────────────────────
266
- /** Load compiled rules from a JSON file. Returns empty array if file missing or invalid. */
347
+ /** Load compiled rules from a JSON file. Returns empty array if file missing. */
267
348
  export function loadCompiledRules(rulesPath, onWarn) {
268
349
  if (!fs.existsSync(rulesPath))
269
350
  return [];
270
351
  try {
271
352
  const raw = fs.readFileSync(rulesPath, 'utf-8');
272
- const parsed = CompiledRulesFileSchema.parse(JSON.parse(raw));
353
+ const json = JSON.parse(raw);
354
+ const parsed = CompiledRulesFileSchema.parse(json);
273
355
  return parsed.rules;
274
356
  }
275
357
  catch (err) {
276
358
  if (err instanceof Error && err.code === 'ENOENT')
277
359
  return [];
360
+ if (err instanceof z.ZodError) {
361
+ throw new TotemParseError(`Invalid compiled-rules.json: ${err.issues.map((i) => i.message).join('; ')}`, "Delete the file and run 'totem compile' to regenerate it.");
362
+ }
278
363
  onWarn?.(`Could not load compiled rules: ${err instanceof Error ? err.message : String(err)}`);
279
364
  return [];
280
365
  }
@@ -285,12 +370,16 @@ export function loadCompiledRulesFile(rulesPath, onWarn) {
285
370
  return { version: 1, rules: [], nonCompilable: [] };
286
371
  try {
287
372
  const raw = fs.readFileSync(rulesPath, 'utf-8');
288
- return CompiledRulesFileSchema.parse(JSON.parse(raw));
373
+ const json = JSON.parse(raw);
374
+ return CompiledRulesFileSchema.parse(json);
289
375
  }
290
376
  catch (err) {
291
377
  if (err instanceof Error && err.code === 'ENOENT') {
292
378
  return { version: 1, rules: [], nonCompilable: [] };
293
379
  }
380
+ if (err instanceof z.ZodError) {
381
+ throw new TotemParseError(`Invalid compiled-rules.json: ${err.issues.map((i) => i.message).join('; ')}`, "Delete the file and run 'totem compile' to regenerate it.");
382
+ }
294
383
  onWarn?.(`Could not load compiled rules: ${err instanceof Error ? err.message : String(err)}`);
295
384
  return { version: 1, rules: [], nonCompilable: [] };
296
385
  }