veto-leash 0.1.2 → 1.0.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.
- package/README.md +125 -203
- package/dist/ast/builtins.d.ts +28 -0
- package/dist/ast/builtins.d.ts.map +1 -0
- package/dist/ast/builtins.js +361 -0
- package/dist/ast/builtins.js.map +1 -0
- package/dist/ast/checker.d.ts +17 -0
- package/dist/ast/checker.d.ts.map +1 -0
- package/dist/ast/checker.js +97 -0
- package/dist/ast/checker.js.map +1 -0
- package/dist/ast/index.d.ts +5 -0
- package/dist/ast/index.d.ts.map +1 -0
- package/dist/ast/index.js +7 -0
- package/dist/ast/index.js.map +1 -0
- package/dist/ast/parser.d.ts +55 -0
- package/dist/ast/parser.d.ts.map +1 -0
- package/dist/ast/parser.js +210 -0
- package/dist/ast/parser.js.map +1 -0
- package/dist/ast/query.d.ts +48 -0
- package/dist/ast/query.d.ts.map +1 -0
- package/dist/ast/query.js +102 -0
- package/dist/ast/query.js.map +1 -0
- package/dist/ast/validate-cli.d.ts +21 -0
- package/dist/ast/validate-cli.d.ts.map +1 -0
- package/dist/ast/validate-cli.js +73 -0
- package/dist/ast/validate-cli.js.map +1 -0
- package/dist/cli.js +105 -21
- package/dist/cli.js.map +1 -1
- package/dist/compiler/builtins.d.ts.map +1 -1
- package/dist/compiler/builtins.js +721 -4
- package/dist/compiler/builtins.js.map +1 -1
- package/dist/compiler/commands.d.ts +40 -0
- package/dist/compiler/commands.d.ts.map +1 -0
- package/dist/compiler/commands.js +311 -0
- package/dist/compiler/commands.js.map +1 -0
- package/dist/compiler/content.d.ts +160 -0
- package/dist/compiler/content.d.ts.map +1 -0
- package/dist/compiler/content.js +461 -0
- package/dist/compiler/content.js.map +1 -0
- package/dist/compiler/index.d.ts.map +1 -1
- package/dist/compiler/index.js +34 -7
- package/dist/compiler/index.js.map +1 -1
- package/dist/compiler/llm.d.ts.map +1 -1
- package/dist/compiler/llm.js +96 -9
- package/dist/compiler/llm.js.map +1 -1
- package/dist/compiler/prompt.d.ts +1 -1
- package/dist/compiler/prompt.d.ts.map +1 -1
- package/dist/compiler/prompt.js +247 -15
- package/dist/compiler/prompt.js.map +1 -1
- package/dist/config/leash-parser.d.ts +29 -0
- package/dist/config/leash-parser.d.ts.map +1 -0
- package/dist/config/leash-parser.js +70 -0
- package/dist/config/leash-parser.js.map +1 -0
- package/dist/config/loader.d.ts +2 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +18 -8
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +8 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +19 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/config/watcher.d.ts +18 -0
- package/dist/config/watcher.d.ts.map +1 -0
- package/dist/config/watcher.js +102 -0
- package/dist/config/watcher.js.map +1 -0
- package/dist/matcher.d.ts +18 -0
- package/dist/matcher.d.ts.map +1 -1
- package/dist/matcher.js +43 -0
- package/dist/matcher.js.map +1 -1
- package/dist/native/claude-code.d.ts.map +1 -1
- package/dist/native/claude-code.js +294 -50
- package/dist/native/claude-code.js.map +1 -1
- package/dist/native/cursor.d.ts +14 -1
- package/dist/native/cursor.d.ts.map +1 -1
- package/dist/native/cursor.js +340 -34
- package/dist/native/cursor.js.map +1 -1
- package/dist/native/index.d.ts +5 -0
- package/dist/native/index.d.ts.map +1 -1
- package/dist/native/index.js +56 -10
- package/dist/native/index.js.map +1 -1
- package/dist/native/opencode.d.ts.map +1 -1
- package/dist/native/opencode.js +15 -3
- package/dist/native/opencode.js.map +1 -1
- package/dist/native/validator.d.ts +15 -0
- package/dist/native/validator.d.ts.map +1 -0
- package/dist/native/validator.js +343 -0
- package/dist/native/validator.js.map +1 -0
- package/dist/types.d.ts +114 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/wrapper/daemon.d.ts.map +1 -1
- package/dist/wrapper/daemon.js +31 -2
- package/dist/wrapper/daemon.js.map +1 -1
- package/languages/tree-sitter-javascript.wasm +0 -0
- package/languages/tree-sitter-tsx.wasm +0 -0
- package/languages/tree-sitter-typescript.wasm +0 -0
- package/package.json +12 -3
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// src/ast/parser.ts
|
|
2
|
+
import { Parser, Language } from 'web-tree-sitter';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
// Module state
|
|
7
|
+
let initialized = false;
|
|
8
|
+
let initPromise = null;
|
|
9
|
+
const languages = new Map();
|
|
10
|
+
const parsers = new Map();
|
|
11
|
+
const treeCache = new Map();
|
|
12
|
+
// Language WASM file URLs - these need to be downloaded/bundled
|
|
13
|
+
const LANGUAGE_WASM_URLS = {
|
|
14
|
+
typescript: 'https://github.com/AdeAttwood/tree-sitter-typescript-wasm/releases/download/0.23.0/tree-sitter-typescript.wasm',
|
|
15
|
+
tsx: 'https://github.com/AdeAttwood/tree-sitter-typescript-wasm/releases/download/0.23.0/tree-sitter-tsx.wasm',
|
|
16
|
+
javascript: 'https://github.com/AdeAttwood/tree-sitter-javascript-wasm/releases/download/0.21.0/tree-sitter-javascript.wasm',
|
|
17
|
+
jsx: 'https://github.com/AdeAttwood/tree-sitter-javascript-wasm/releases/download/0.21.0/tree-sitter-javascript.wasm',
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Initialize the tree-sitter WASM runtime.
|
|
21
|
+
* Must be called before any parsing operations.
|
|
22
|
+
*/
|
|
23
|
+
export async function initParser() {
|
|
24
|
+
if (initialized)
|
|
25
|
+
return;
|
|
26
|
+
if (initPromise)
|
|
27
|
+
return initPromise;
|
|
28
|
+
initPromise = (async () => {
|
|
29
|
+
try {
|
|
30
|
+
await Parser.init();
|
|
31
|
+
initialized = true;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.warn('Failed to initialize tree-sitter:', error);
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
})();
|
|
38
|
+
return initPromise;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Check if the parser is initialized
|
|
42
|
+
*/
|
|
43
|
+
export function isInitialized() {
|
|
44
|
+
return initialized;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Load a language from a WASM file
|
|
48
|
+
*/
|
|
49
|
+
export async function loadLanguage(languageType) {
|
|
50
|
+
if (languages.has(languageType)) {
|
|
51
|
+
return languages.get(languageType);
|
|
52
|
+
}
|
|
53
|
+
await initParser();
|
|
54
|
+
// Check for local WASM file first
|
|
55
|
+
const localWasmPath = getLocalWasmPath(languageType);
|
|
56
|
+
let language;
|
|
57
|
+
if (localWasmPath && fs.existsSync(localWasmPath)) {
|
|
58
|
+
language = await Language.load(localWasmPath);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Try to fetch from URL (requires network access)
|
|
62
|
+
const url = LANGUAGE_WASM_URLS[languageType];
|
|
63
|
+
try {
|
|
64
|
+
language = await Language.load(url);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
throw new Error(`Failed to load language ${languageType}. ` +
|
|
68
|
+
`Please download the WASM file from ${url} to ${localWasmPath || 'the languages directory'}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
languages.set(languageType, language);
|
|
72
|
+
return language;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get local WASM file path if it exists
|
|
76
|
+
*/
|
|
77
|
+
function getLocalWasmPath(languageType) {
|
|
78
|
+
const wasmFile = `tree-sitter-${languageType === 'jsx' ? 'javascript' : languageType}.wasm`;
|
|
79
|
+
// Try multiple locations
|
|
80
|
+
const candidates = [
|
|
81
|
+
// From process.cwd() (most reliable)
|
|
82
|
+
path.resolve(process.cwd(), 'languages', wasmFile),
|
|
83
|
+
// From import.meta.url
|
|
84
|
+
(() => {
|
|
85
|
+
try {
|
|
86
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
87
|
+
return path.resolve(__dirname, '..', '..', 'languages', wasmFile);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
})(),
|
|
93
|
+
].filter((p) => p !== null);
|
|
94
|
+
for (const candidate of candidates) {
|
|
95
|
+
if (fs.existsSync(candidate)) {
|
|
96
|
+
return candidate;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get or create a parser for the given language
|
|
103
|
+
*/
|
|
104
|
+
export async function getParser(languageType) {
|
|
105
|
+
if (parsers.has(languageType)) {
|
|
106
|
+
return parsers.get(languageType);
|
|
107
|
+
}
|
|
108
|
+
const language = await loadLanguage(languageType);
|
|
109
|
+
const parser = new Parser();
|
|
110
|
+
parser.setLanguage(language);
|
|
111
|
+
parsers.set(languageType, parser);
|
|
112
|
+
return parser;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get the language object for query creation
|
|
116
|
+
*/
|
|
117
|
+
export async function getLanguageObject(languageType) {
|
|
118
|
+
return loadLanguage(languageType);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Parse file content into AST
|
|
122
|
+
* Uses caching for performance
|
|
123
|
+
*/
|
|
124
|
+
export async function parseFile(content, filePath, languageType) {
|
|
125
|
+
const start = performance.now();
|
|
126
|
+
const parser = await getParser(languageType);
|
|
127
|
+
// Check cache
|
|
128
|
+
const hash = hashContent(content);
|
|
129
|
+
const cached = treeCache.get(filePath);
|
|
130
|
+
let tree;
|
|
131
|
+
if (cached && cached.hash === hash) {
|
|
132
|
+
tree = cached.tree;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Don't use incremental parsing when content changed - web-tree-sitter
|
|
136
|
+
// has issues with incremental updates from different source text
|
|
137
|
+
const result = parser.parse(content);
|
|
138
|
+
if (!result) {
|
|
139
|
+
throw new Error(`Failed to parse ${filePath}`);
|
|
140
|
+
}
|
|
141
|
+
tree = result;
|
|
142
|
+
treeCache.set(filePath, { tree, hash });
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
tree,
|
|
146
|
+
language: languageType,
|
|
147
|
+
parseTimeMs: performance.now() - start,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Detect language from file extension
|
|
152
|
+
*/
|
|
153
|
+
export function detectLanguage(filePath) {
|
|
154
|
+
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
155
|
+
switch (ext) {
|
|
156
|
+
case 'ts':
|
|
157
|
+
return 'typescript';
|
|
158
|
+
case 'tsx':
|
|
159
|
+
return 'tsx';
|
|
160
|
+
case 'js':
|
|
161
|
+
case 'mjs':
|
|
162
|
+
case 'cjs':
|
|
163
|
+
return 'javascript';
|
|
164
|
+
case 'jsx':
|
|
165
|
+
return 'jsx';
|
|
166
|
+
default:
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Clear the tree cache for a specific file or all files
|
|
172
|
+
*/
|
|
173
|
+
export function clearTreeCache(filePath) {
|
|
174
|
+
if (filePath) {
|
|
175
|
+
treeCache.delete(filePath);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
treeCache.clear();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Reset all parser state (for testing)
|
|
183
|
+
*/
|
|
184
|
+
export function resetParserState() {
|
|
185
|
+
treeCache.clear();
|
|
186
|
+
languages.clear();
|
|
187
|
+
parsers.clear();
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get cache statistics
|
|
191
|
+
*/
|
|
192
|
+
export function getCacheStats() {
|
|
193
|
+
return {
|
|
194
|
+
parserCount: parsers.size,
|
|
195
|
+
treeCacheSize: treeCache.size,
|
|
196
|
+
languageCount: languages.size,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Fast hash function for cache invalidation
|
|
201
|
+
*/
|
|
202
|
+
function hashContent(content) {
|
|
203
|
+
let hash = 0;
|
|
204
|
+
for (let i = 0; i < content.length; i++) {
|
|
205
|
+
hash = ((hash << 5) - hash) + content.charCodeAt(i);
|
|
206
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
207
|
+
}
|
|
208
|
+
return hash.toString(36);
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/ast/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Query } from 'web-tree-sitter';
|
|
2
|
+
import type { ASTRule } from '../types.js';
|
|
3
|
+
import { type LanguageType, type Tree } from './parser.js';
|
|
4
|
+
export interface ASTMatch {
|
|
5
|
+
/** Named captures from the query */
|
|
6
|
+
captures: Map<string, {
|
|
7
|
+
text: string;
|
|
8
|
+
line: number;
|
|
9
|
+
column: number;
|
|
10
|
+
}>;
|
|
11
|
+
/** Line number (1-indexed) */
|
|
12
|
+
line: number;
|
|
13
|
+
/** Column number (1-indexed) */
|
|
14
|
+
column: number;
|
|
15
|
+
/** The matched text (truncated for display) */
|
|
16
|
+
text: string;
|
|
17
|
+
/** The rule that matched */
|
|
18
|
+
ruleId: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get or create a compiled query
|
|
22
|
+
*/
|
|
23
|
+
export declare function getQuery(queryString: string, languageType: LanguageType): Promise<Query>;
|
|
24
|
+
/**
|
|
25
|
+
* Run an AST query against a parse tree
|
|
26
|
+
*/
|
|
27
|
+
export declare function runQuery(tree: Tree, queryString: string, languageType: LanguageType, ruleId: string): Promise<ASTMatch[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Check content against an AST rule
|
|
30
|
+
* Returns null if allowed, or match details if blocked
|
|
31
|
+
*/
|
|
32
|
+
export declare function checkASTRule(content: string, tree: Tree, languageType: LanguageType, rule: ASTRule): Promise<ASTMatch | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Check content against multiple AST rules
|
|
35
|
+
* Returns the first match found, or null if all pass
|
|
36
|
+
*/
|
|
37
|
+
export declare function checkASTRules(content: string, tree: Tree, languageType: LanguageType, rules: ASTRule[]): Promise<ASTMatch | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Clear the query cache
|
|
40
|
+
*/
|
|
41
|
+
export declare function clearQueryCache(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Get query cache statistics
|
|
44
|
+
*/
|
|
45
|
+
export declare function getQueryCacheStats(): {
|
|
46
|
+
size: number;
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/ast/query.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAqB,KAAK,YAAY,EAAE,KAAK,IAAI,EAAiB,MAAM,aAAa,CAAC;AAE7F,MAAM,WAAW,QAAQ;IACvB,oCAAoC;IACpC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;CAChB;AAYD;;GAEG;AACH,wBAAsB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,CAQ9F;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,QAAQ,EAAE,CAAC,CA8BrB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAsB1B;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,YAAY,EAC1B,KAAK,EAAE,OAAO,EAAE,GACf,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAQ1B;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAErD"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// src/ast/query.ts
|
|
2
|
+
import { Query } from 'web-tree-sitter';
|
|
3
|
+
import { getLanguageObject } from './parser.js';
|
|
4
|
+
// Query cache - parsed queries are expensive, reuse them
|
|
5
|
+
const queryCache = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Get cache key for a query + language combination
|
|
8
|
+
*/
|
|
9
|
+
function getQueryCacheKey(queryString, languageType) {
|
|
10
|
+
return `${languageType}:${queryString}`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Get or create a compiled query
|
|
14
|
+
*/
|
|
15
|
+
export async function getQuery(queryString, languageType) {
|
|
16
|
+
const cacheKey = getQueryCacheKey(queryString, languageType);
|
|
17
|
+
if (!queryCache.has(cacheKey)) {
|
|
18
|
+
const language = await getLanguageObject(languageType);
|
|
19
|
+
const query = new Query(language, queryString);
|
|
20
|
+
queryCache.set(cacheKey, query);
|
|
21
|
+
}
|
|
22
|
+
return queryCache.get(cacheKey);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Run an AST query against a parse tree
|
|
26
|
+
*/
|
|
27
|
+
export async function runQuery(tree, queryString, languageType, ruleId) {
|
|
28
|
+
const query = await getQuery(queryString, languageType);
|
|
29
|
+
const matches = [];
|
|
30
|
+
for (const match of query.matches(tree.rootNode)) {
|
|
31
|
+
const captures = new Map();
|
|
32
|
+
for (const capture of match.captures) {
|
|
33
|
+
captures.set(capture.name, {
|
|
34
|
+
text: capture.node.text,
|
|
35
|
+
line: capture.node.startPosition.row + 1,
|
|
36
|
+
column: capture.node.startPosition.column + 1,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Use first capture as primary match location
|
|
40
|
+
const primaryCapture = match.captures[0];
|
|
41
|
+
if (!primaryCapture)
|
|
42
|
+
continue;
|
|
43
|
+
const primaryNode = primaryCapture.node;
|
|
44
|
+
matches.push({
|
|
45
|
+
captures,
|
|
46
|
+
line: primaryNode.startPosition.row + 1,
|
|
47
|
+
column: primaryNode.startPosition.column + 1,
|
|
48
|
+
text: primaryNode.text.slice(0, 100), // Truncate for display
|
|
49
|
+
ruleId,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return matches;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Check content against an AST rule
|
|
56
|
+
* Returns null if allowed, or match details if blocked
|
|
57
|
+
*/
|
|
58
|
+
export async function checkASTRule(content, tree, languageType, rule) {
|
|
59
|
+
// Fast pre-filter: skip AST query if regex doesn't match
|
|
60
|
+
if (rule.regexPreFilter) {
|
|
61
|
+
if (!content.includes(rule.regexPreFilter)) {
|
|
62
|
+
return null; // Fast exit - content doesn't contain the pattern
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Check if rule applies to this language
|
|
66
|
+
const normalizedLanguage = languageType === 'tsx' ? 'typescript' : languageType === 'jsx' ? 'javascript' : languageType;
|
|
67
|
+
if (!rule.languages.includes(normalizedLanguage)) {
|
|
68
|
+
return null; // Rule doesn't apply to this language
|
|
69
|
+
}
|
|
70
|
+
// Run AST query
|
|
71
|
+
const matches = await runQuery(tree, rule.query, languageType, rule.id);
|
|
72
|
+
if (matches.length === 0) {
|
|
73
|
+
return null; // Allowed
|
|
74
|
+
}
|
|
75
|
+
return matches[0]; // Return first match
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Check content against multiple AST rules
|
|
79
|
+
* Returns the first match found, or null if all pass
|
|
80
|
+
*/
|
|
81
|
+
export async function checkASTRules(content, tree, languageType, rules) {
|
|
82
|
+
for (const rule of rules) {
|
|
83
|
+
const match = await checkASTRule(content, tree, languageType, rule);
|
|
84
|
+
if (match) {
|
|
85
|
+
return match;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Clear the query cache
|
|
92
|
+
*/
|
|
93
|
+
export function clearQueryCache() {
|
|
94
|
+
queryCache.clear();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get query cache statistics
|
|
98
|
+
*/
|
|
99
|
+
export function getQueryCacheStats() {
|
|
100
|
+
return { size: queryCache.size };
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/ast/query.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AST Validation CLI
|
|
4
|
+
*
|
|
5
|
+
* Called by native validators (Python/shell) to perform AST-based content checking.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* echo '{"file":"test.ts","content":"...","restriction":"no lodash"}' | node validate-cli.js
|
|
9
|
+
*
|
|
10
|
+
* Input (JSON on stdin):
|
|
11
|
+
* - file: File path (used for language detection)
|
|
12
|
+
* - content: File content to check
|
|
13
|
+
* - restriction: The policy description (e.g., "no lodash", "no any")
|
|
14
|
+
*
|
|
15
|
+
* Output (JSON on stdout):
|
|
16
|
+
* - allowed: boolean
|
|
17
|
+
* - match?: { line, column, text, reason, suggest, ruleId }
|
|
18
|
+
* - method: 'ast' | 'skipped'
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=validate-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-cli.d.ts","sourceRoot":"","sources":["../../src/ast/validate-cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AST Validation CLI
|
|
4
|
+
*
|
|
5
|
+
* Called by native validators (Python/shell) to perform AST-based content checking.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* echo '{"file":"test.ts","content":"...","restriction":"no lodash"}' | node validate-cli.js
|
|
9
|
+
*
|
|
10
|
+
* Input (JSON on stdin):
|
|
11
|
+
* - file: File path (used for language detection)
|
|
12
|
+
* - content: File content to check
|
|
13
|
+
* - restriction: The policy description (e.g., "no lodash", "no any")
|
|
14
|
+
*
|
|
15
|
+
* Output (JSON on stdout):
|
|
16
|
+
* - allowed: boolean
|
|
17
|
+
* - match?: { line, column, text, reason, suggest, ruleId }
|
|
18
|
+
* - method: 'ast' | 'skipped'
|
|
19
|
+
*/
|
|
20
|
+
import { checkContentAST } from './checker.js';
|
|
21
|
+
import { initParser, loadLanguage, detectLanguage } from './parser.js';
|
|
22
|
+
async function readStdin() {
|
|
23
|
+
const chunks = [];
|
|
24
|
+
for await (const chunk of process.stdin) {
|
|
25
|
+
chunks.push(chunk);
|
|
26
|
+
}
|
|
27
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
28
|
+
}
|
|
29
|
+
async function main() {
|
|
30
|
+
try {
|
|
31
|
+
// Initialize parser
|
|
32
|
+
await initParser();
|
|
33
|
+
// Read input
|
|
34
|
+
const inputText = await readStdin();
|
|
35
|
+
if (!inputText.trim()) {
|
|
36
|
+
console.log(JSON.stringify({ allowed: true, method: 'skipped', error: 'Empty input' }));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const input = JSON.parse(inputText);
|
|
40
|
+
const { file, content, restriction } = input;
|
|
41
|
+
if (!file || !content || !restriction) {
|
|
42
|
+
console.log(JSON.stringify({ allowed: true, method: 'skipped', error: 'Missing required fields' }));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Detect language and load parser
|
|
46
|
+
const language = detectLanguage(file);
|
|
47
|
+
if (!language) {
|
|
48
|
+
console.log(JSON.stringify({ allowed: true, method: 'skipped', reason: 'Unsupported file type' }));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
await loadLanguage(language);
|
|
52
|
+
// Create policy from restriction
|
|
53
|
+
const policy = {
|
|
54
|
+
action: 'modify',
|
|
55
|
+
include: ['**/*'],
|
|
56
|
+
exclude: [],
|
|
57
|
+
description: restriction,
|
|
58
|
+
};
|
|
59
|
+
// Run AST check
|
|
60
|
+
const result = await checkContentAST(content, file, policy);
|
|
61
|
+
console.log(JSON.stringify(result));
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
// On any error, allow (fail open) but log error
|
|
65
|
+
console.log(JSON.stringify({
|
|
66
|
+
allowed: true,
|
|
67
|
+
method: 'skipped',
|
|
68
|
+
error: error instanceof Error ? error.message : String(error),
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
main();
|
|
73
|
+
//# sourceMappingURL=validate-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-cli.js","sourceRoot":"","sources":["../../src/ast/validate-cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AASvE,KAAK,UAAU,SAAS;IACtB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AACjD,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,oBAAoB;QACpB,MAAM,UAAU,EAAE,CAAC;QAEnB,aAAa;QACb,MAAM,SAAS,GAAG,MAAM,SAAS,EAAE,CAAC;QACpC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;YACxF,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAkB,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACnD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC;QAE7C,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;YACtC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC,CAAC;YACpG,OAAO;QACT,CAAC;QAED,kCAAkC;QAClC,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC,CAAC,CAAC;YACnG,OAAO;QACT,CAAC;QAED,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;QAE7B,iCAAiC;QACjC,MAAM,MAAM,GAAW;YACrB,MAAM,EAAE,QAAQ;YAChB,OAAO,EAAE,CAAC,MAAM,CAAC;YACjB,OAAO,EAAE,EAAE;YACX,WAAW,EAAE,WAAW;SACzB,CAAC;QAEF,gBAAgB;QAChB,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAC5D,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,gDAAgD;QAChD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;YACzB,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,SAAS;YACjB,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC9D,CAAC,CAAC,CAAC;IACN,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -6,13 +6,13 @@ import { createWrapperDir, cleanupWrapperDir } from './wrapper/shims.js';
|
|
|
6
6
|
import { spawnAgent } from './wrapper/spawn.js';
|
|
7
7
|
import { COLORS, SYMBOLS, createSpinner } from './ui/colors.js';
|
|
8
8
|
import { clearCache } from './compiler/cache.js';
|
|
9
|
-
import { installAgent, uninstallAgent, addPolicyToAgents, listPolicies, AGENTS, } from './native/index.js';
|
|
9
|
+
import { installAgent, uninstallAgent, addPolicyToAgents, listPolicies, AGENTS, detectInstalledAgents, } from './native/index.js';
|
|
10
10
|
import { startWatchdog, stopWatchdog } from './watchdog/index.js';
|
|
11
|
-
import { findLeashConfig, loadLeashConfig, compileLeashConfig, createLeashConfig, } from './config/loader.js';
|
|
11
|
+
import { findLeashConfig, loadLeashConfig, compileLeashConfig, createLeashConfig, hasLeashConfig, } from './config/loader.js';
|
|
12
12
|
import { printAuditLog, clearAuditLog } from './audit/index.js';
|
|
13
13
|
import { login as cloudLogin, printCloudStatus } from './cloud/index.js';
|
|
14
14
|
import { getActiveSessions } from './wrapper/sessions.js';
|
|
15
|
-
const VERSION = '0.
|
|
15
|
+
const VERSION = '0.2.0';
|
|
16
16
|
async function main() {
|
|
17
17
|
const args = process.argv.slice(2);
|
|
18
18
|
// Handle flags
|
|
@@ -49,7 +49,7 @@ async function main() {
|
|
|
49
49
|
await runUninstall(args[1]);
|
|
50
50
|
break;
|
|
51
51
|
case 'init':
|
|
52
|
-
runInit();
|
|
52
|
+
await runInit();
|
|
53
53
|
break;
|
|
54
54
|
case 'sync':
|
|
55
55
|
await runSync(args[1]);
|
|
@@ -105,14 +105,37 @@ async function runWrapper(agent, restriction) {
|
|
|
105
105
|
console.log(`\n${COLORS.success}${SYMBOLS.success} veto-leash active${COLORS.reset}\n`);
|
|
106
106
|
console.log(` ${COLORS.dim}Policy:${COLORS.reset} ${policy.description}`);
|
|
107
107
|
console.log(` ${COLORS.dim}Action:${COLORS.reset} ${policy.action}\n`);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
console.log(` ${
|
|
108
|
+
// Show file patterns if present
|
|
109
|
+
if (policy.include.length > 0) {
|
|
110
|
+
console.log(` ${COLORS.dim}Protecting files:${COLORS.reset}`);
|
|
111
|
+
console.log(` ${policy.include.slice(0, 5).join(' ')}`);
|
|
112
|
+
if (policy.include.length > 5) {
|
|
113
|
+
console.log(` ${COLORS.dim}...and ${policy.include.length - 5} more${COLORS.reset}`);
|
|
114
|
+
}
|
|
115
|
+
if (policy.exclude.length > 0) {
|
|
116
|
+
console.log(`\n ${COLORS.dim}Allowing (exceptions):${COLORS.reset}`);
|
|
117
|
+
console.log(` ${policy.exclude.join(' ')}`);
|
|
118
|
+
}
|
|
112
119
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
console.log(`
|
|
120
|
+
// Show command rules if present
|
|
121
|
+
if (policy.commandRules && policy.commandRules.length > 0) {
|
|
122
|
+
console.log(` ${COLORS.dim}Blocking commands:${COLORS.reset}`);
|
|
123
|
+
for (const rule of policy.commandRules) {
|
|
124
|
+
console.log(` ${COLORS.error}${rule.block.slice(0, 3).join(', ')}${COLORS.reset}`);
|
|
125
|
+
if (rule.suggest) {
|
|
126
|
+
console.log(` ${COLORS.dim}→ Use:${COLORS.reset} ${rule.suggest}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Show content rules if present (Phase 2)
|
|
131
|
+
if (policy.contentRules && policy.contentRules.length > 0) {
|
|
132
|
+
console.log(` ${COLORS.dim}Checking content for:${COLORS.reset}`);
|
|
133
|
+
for (const rule of policy.contentRules.slice(0, 3)) {
|
|
134
|
+
console.log(` ${COLORS.error}${rule.reason}${COLORS.reset}`);
|
|
135
|
+
}
|
|
136
|
+
if (policy.contentRules.length > 3) {
|
|
137
|
+
console.log(` ${COLORS.dim}...and ${policy.contentRules.length - 3} more rules${COLORS.reset}`);
|
|
138
|
+
}
|
|
116
139
|
}
|
|
117
140
|
console.log(`\n Press Ctrl+C to exit\n`);
|
|
118
141
|
// Start daemon
|
|
@@ -163,10 +186,38 @@ async function runExplain(restriction) {
|
|
|
163
186
|
console.log('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n');
|
|
164
187
|
console.log(`Restriction: "${restriction}"\n`);
|
|
165
188
|
console.log(`Action: ${policy.action}\n`);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
189
|
+
// Show file patterns if present
|
|
190
|
+
if (policy.include.length > 0) {
|
|
191
|
+
console.log(`${COLORS.bold}File Patterns:${COLORS.reset}`);
|
|
192
|
+
console.log(` ${COLORS.dim}include:${COLORS.reset} ${policy.include.join(', ')}`);
|
|
193
|
+
console.log(` ${COLORS.dim}exclude:${COLORS.reset} ${policy.exclude.join(', ') || '(none)'}`);
|
|
194
|
+
}
|
|
195
|
+
// Show command rules if present
|
|
196
|
+
if (policy.commandRules && policy.commandRules.length > 0) {
|
|
197
|
+
console.log(`${COLORS.bold}Command Rules:${COLORS.reset}`);
|
|
198
|
+
for (const rule of policy.commandRules) {
|
|
199
|
+
console.log(` ${COLORS.error}block:${COLORS.reset} ${rule.block.join(', ')}`);
|
|
200
|
+
if (rule.suggest) {
|
|
201
|
+
console.log(` ${COLORS.success}suggest:${COLORS.reset} ${rule.suggest}`);
|
|
202
|
+
}
|
|
203
|
+
console.log(` ${COLORS.dim}reason:${COLORS.reset} ${rule.reason}`);
|
|
204
|
+
console.log('');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Show content rules if present (Phase 2)
|
|
208
|
+
if (policy.contentRules && policy.contentRules.length > 0) {
|
|
209
|
+
console.log(`${COLORS.bold}Content Rules:${COLORS.reset}`);
|
|
210
|
+
for (const rule of policy.contentRules) {
|
|
211
|
+
console.log(` ${COLORS.error}pattern:${COLORS.reset} ${rule.pattern}`);
|
|
212
|
+
console.log(` ${COLORS.dim}fileTypes:${COLORS.reset} ${rule.fileTypes.join(', ')}`);
|
|
213
|
+
if (rule.suggest) {
|
|
214
|
+
console.log(` ${COLORS.success}suggest:${COLORS.reset} ${rule.suggest}`);
|
|
215
|
+
}
|
|
216
|
+
console.log(` ${COLORS.dim}reason:${COLORS.reset} ${rule.reason}`);
|
|
217
|
+
console.log('');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
console.log(`${COLORS.dim}Description:${COLORS.reset} ${policy.description}`);
|
|
170
221
|
console.log(`\nRun 'leash <agent> "${restriction}"' to enforce.\n`);
|
|
171
222
|
}
|
|
172
223
|
async function runWatchdog(restriction) {
|
|
@@ -313,10 +364,43 @@ async function runUninstall(agent) {
|
|
|
313
364
|
process.exit(1);
|
|
314
365
|
}
|
|
315
366
|
}
|
|
316
|
-
function runInit() {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
367
|
+
async function runInit() {
|
|
368
|
+
console.log(`\n${COLORS.bold}Detecting AI coding agents...${COLORS.reset}`);
|
|
369
|
+
// Detect installed agents
|
|
370
|
+
const detected = detectInstalledAgents();
|
|
371
|
+
if (detected.length === 0) {
|
|
372
|
+
console.log(` ${COLORS.dim}No agents detected${COLORS.reset}`);
|
|
373
|
+
console.log(`\n${COLORS.dim}Tip: Install Claude Code, Cursor, OpenCode, or Windsurf first.${COLORS.reset}`);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
for (const agent of detected) {
|
|
377
|
+
console.log(` ${COLORS.success}${SYMBOLS.success} ${agent.name} found${COLORS.reset}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Create .leash file if not exists
|
|
381
|
+
if (!hasLeashConfig()) {
|
|
382
|
+
console.log(`\n${COLORS.bold}Creating .leash config...${COLORS.reset}`);
|
|
383
|
+
createLeashConfig();
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
console.log(`\n${COLORS.success}${SYMBOLS.success} .leash already exists${COLORS.reset}`);
|
|
387
|
+
}
|
|
388
|
+
// Install hooks for detected agents with native support
|
|
389
|
+
const nativeAgents = detected.filter(a => a.hasNativeHooks);
|
|
390
|
+
if (nativeAgents.length > 0) {
|
|
391
|
+
console.log(`\n${COLORS.bold}Installing enforcement hooks...${COLORS.reset}`);
|
|
392
|
+
for (const agent of nativeAgents) {
|
|
393
|
+
try {
|
|
394
|
+
await installAgent(agent.id);
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
console.log(` ${COLORS.warning}${SYMBOLS.warning} Could not install ${agent.name} hooks${COLORS.reset}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
console.log(`\n${COLORS.success}${SYMBOLS.success} Done!${COLORS.reset} Policies enforced automatically.`);
|
|
402
|
+
console.log(`\nEdit ${COLORS.bold}.leash${COLORS.reset} to customize your policies.`);
|
|
403
|
+
console.log(`Run ${COLORS.dim}leash sync${COLORS.reset} to apply changes.\n`);
|
|
320
404
|
}
|
|
321
405
|
async function runSync(agent) {
|
|
322
406
|
const configPath = findLeashConfig();
|
|
@@ -385,9 +469,9 @@ ${COLORS.bold}USAGE${COLORS.reset}
|
|
|
385
469
|
|
|
386
470
|
${COLORS.bold}AGENTS (native integration)${COLORS.reset}
|
|
387
471
|
cc, claude-code Claude Code PreToolUse hooks
|
|
388
|
-
ws, windsurf Windsurf Cascade hooks
|
|
389
472
|
oc, opencode OpenCode permission.bash rules
|
|
390
|
-
cursor Cursor
|
|
473
|
+
cursor Cursor CLI hooks.json (v1.7+)
|
|
474
|
+
ws, windsurf Windsurf Cascade hooks
|
|
391
475
|
aider Aider .aider.conf.yml read-only
|
|
392
476
|
|
|
393
477
|
${COLORS.bold}AGENTS (wrapper/watchdog)${COLORS.reset}
|