veto-leash 0.1.3 → 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.
Files changed (96) hide show
  1. package/README.md +110 -195
  2. package/dist/ast/builtins.d.ts +28 -0
  3. package/dist/ast/builtins.d.ts.map +1 -0
  4. package/dist/ast/builtins.js +361 -0
  5. package/dist/ast/builtins.js.map +1 -0
  6. package/dist/ast/checker.d.ts +17 -0
  7. package/dist/ast/checker.d.ts.map +1 -0
  8. package/dist/ast/checker.js +97 -0
  9. package/dist/ast/checker.js.map +1 -0
  10. package/dist/ast/index.d.ts +5 -0
  11. package/dist/ast/index.d.ts.map +1 -0
  12. package/dist/ast/index.js +7 -0
  13. package/dist/ast/index.js.map +1 -0
  14. package/dist/ast/parser.d.ts +55 -0
  15. package/dist/ast/parser.d.ts.map +1 -0
  16. package/dist/ast/parser.js +210 -0
  17. package/dist/ast/parser.js.map +1 -0
  18. package/dist/ast/query.d.ts +48 -0
  19. package/dist/ast/query.d.ts.map +1 -0
  20. package/dist/ast/query.js +102 -0
  21. package/dist/ast/query.js.map +1 -0
  22. package/dist/ast/validate-cli.d.ts +21 -0
  23. package/dist/ast/validate-cli.d.ts.map +1 -0
  24. package/dist/ast/validate-cli.js +73 -0
  25. package/dist/ast/validate-cli.js.map +1 -0
  26. package/dist/cli.js +105 -21
  27. package/dist/cli.js.map +1 -1
  28. package/dist/compiler/builtins.d.ts.map +1 -1
  29. package/dist/compiler/builtins.js +721 -4
  30. package/dist/compiler/builtins.js.map +1 -1
  31. package/dist/compiler/commands.d.ts +40 -0
  32. package/dist/compiler/commands.d.ts.map +1 -0
  33. package/dist/compiler/commands.js +311 -0
  34. package/dist/compiler/commands.js.map +1 -0
  35. package/dist/compiler/content.d.ts +160 -0
  36. package/dist/compiler/content.d.ts.map +1 -0
  37. package/dist/compiler/content.js +461 -0
  38. package/dist/compiler/content.js.map +1 -0
  39. package/dist/compiler/index.d.ts.map +1 -1
  40. package/dist/compiler/index.js +34 -7
  41. package/dist/compiler/index.js.map +1 -1
  42. package/dist/compiler/llm.d.ts.map +1 -1
  43. package/dist/compiler/llm.js +96 -9
  44. package/dist/compiler/llm.js.map +1 -1
  45. package/dist/compiler/prompt.d.ts +1 -1
  46. package/dist/compiler/prompt.d.ts.map +1 -1
  47. package/dist/compiler/prompt.js +247 -15
  48. package/dist/compiler/prompt.js.map +1 -1
  49. package/dist/config/leash-parser.d.ts +29 -0
  50. package/dist/config/leash-parser.d.ts.map +1 -0
  51. package/dist/config/leash-parser.js +70 -0
  52. package/dist/config/leash-parser.js.map +1 -0
  53. package/dist/config/loader.d.ts +2 -1
  54. package/dist/config/loader.d.ts.map +1 -1
  55. package/dist/config/loader.js +18 -8
  56. package/dist/config/loader.js.map +1 -1
  57. package/dist/config/schema.d.ts +8 -0
  58. package/dist/config/schema.d.ts.map +1 -1
  59. package/dist/config/schema.js +19 -0
  60. package/dist/config/schema.js.map +1 -1
  61. package/dist/config/watcher.d.ts +18 -0
  62. package/dist/config/watcher.d.ts.map +1 -0
  63. package/dist/config/watcher.js +102 -0
  64. package/dist/config/watcher.js.map +1 -0
  65. package/dist/matcher.d.ts +18 -0
  66. package/dist/matcher.d.ts.map +1 -1
  67. package/dist/matcher.js +43 -0
  68. package/dist/matcher.js.map +1 -1
  69. package/dist/native/claude-code.d.ts.map +1 -1
  70. package/dist/native/claude-code.js +294 -50
  71. package/dist/native/claude-code.js.map +1 -1
  72. package/dist/native/cursor.d.ts +14 -1
  73. package/dist/native/cursor.d.ts.map +1 -1
  74. package/dist/native/cursor.js +340 -34
  75. package/dist/native/cursor.js.map +1 -1
  76. package/dist/native/index.d.ts +5 -0
  77. package/dist/native/index.d.ts.map +1 -1
  78. package/dist/native/index.js +56 -10
  79. package/dist/native/index.js.map +1 -1
  80. package/dist/native/opencode.d.ts.map +1 -1
  81. package/dist/native/opencode.js +15 -3
  82. package/dist/native/opencode.js.map +1 -1
  83. package/dist/native/validator.d.ts +15 -0
  84. package/dist/native/validator.d.ts.map +1 -0
  85. package/dist/native/validator.js +343 -0
  86. package/dist/native/validator.js.map +1 -0
  87. package/dist/types.d.ts +114 -0
  88. package/dist/types.d.ts.map +1 -1
  89. package/dist/types.js.map +1 -1
  90. package/dist/wrapper/daemon.d.ts.map +1 -1
  91. package/dist/wrapper/daemon.js +31 -2
  92. package/dist/wrapper/daemon.js.map +1 -1
  93. package/languages/tree-sitter-javascript.wasm +0 -0
  94. package/languages/tree-sitter-tsx.wasm +0 -0
  95. package/languages/tree-sitter-typescript.wasm +0 -0
  96. package/package.json +5 -2
@@ -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.1.2';
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
- console.log(` ${COLORS.dim}Protecting:${COLORS.reset}`);
109
- console.log(` ${policy.include.slice(0, 5).join(' ')}`);
110
- if (policy.include.length > 5) {
111
- console.log(` ${COLORS.dim}...and ${policy.include.length - 5} more${COLORS.reset}`);
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
- if (policy.exclude.length > 0) {
114
- console.log(`\n ${COLORS.dim}Allowing (exceptions):${COLORS.reset}`);
115
- console.log(` ${policy.exclude.join(' ')}`);
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
- console.log(`Patterns:`);
167
- console.log(` ${COLORS.dim}include:${COLORS.reset} ${policy.include.join(', ')}`);
168
- console.log(` ${COLORS.dim}exclude:${COLORS.reset} ${policy.exclude.join(', ') || '(none)'}`);
169
- console.log(`\nDescription: ${policy.description}`);
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
- createLeashConfig();
318
- console.log(`\nEdit .leash to customize your policies.`);
319
- console.log(`Then run: ${COLORS.dim}leash sync${COLORS.reset}\n`);
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 .cursorrules (guidance only)
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}