invar-tools 1.10.0__py3-none-any.whl → 1.12.0__py3-none-any.whl
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.
- invar/core/doc_edit.py +187 -0
- invar/core/doc_parser.py +563 -0
- invar/core/ts_sig_parser.py +6 -3
- invar/mcp/handlers.py +436 -0
- invar/mcp/server.py +351 -156
- invar/node_tools/ts-query.js +396 -0
- invar/shell/commands/doc.py +409 -0
- invar/shell/commands/guard.py +29 -0
- invar/shell/commands/init.py +72 -13
- invar/shell/commands/perception.py +302 -6
- invar/shell/doc_tools.py +459 -0
- invar/shell/fs.py +15 -14
- invar/shell/prove/crosshair.py +3 -0
- invar/shell/prove/guard_ts.py +13 -10
- invar/shell/py_refs.py +156 -0
- invar/shell/skill_manager.py +17 -15
- invar/shell/ts_compiler.py +238 -0
- invar/templates/examples/typescript/patterns.md +193 -0
- invar/templates/skills/develop/SKILL.md.jinja +46 -0
- invar/templates/skills/review/SKILL.md.jinja +205 -493
- {invar_tools-1.10.0.dist-info → invar_tools-1.12.0.dist-info}/METADATA +58 -8
- {invar_tools-1.10.0.dist-info → invar_tools-1.12.0.dist-info}/RECORD +27 -18
- {invar_tools-1.10.0.dist-info → invar_tools-1.12.0.dist-info}/WHEEL +0 -0
- {invar_tools-1.10.0.dist-info → invar_tools-1.12.0.dist-info}/entry_points.txt +0 -0
- {invar_tools-1.10.0.dist-info → invar_tools-1.12.0.dist-info}/licenses/LICENSE +0 -0
- {invar_tools-1.10.0.dist-info → invar_tools-1.12.0.dist-info}/licenses/LICENSE-GPL +0 -0
- {invar_tools-1.10.0.dist-info → invar_tools-1.12.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* TypeScript Compiler API query tool.
|
|
4
|
+
* Single-shot execution: runs query, outputs JSON, exits.
|
|
5
|
+
* No persistent process, no orphan risk.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node ts-query.js '{"command": "sig", "file": "src/auth.ts"}'
|
|
9
|
+
* node ts-query.js '{"command": "map", "path": ".", "top": 10}'
|
|
10
|
+
* node ts-query.js '{"command": "refs", "file": "src/auth.ts", "line": 10, "column": 5}'
|
|
11
|
+
*
|
|
12
|
+
* Part of DX-78: TypeScript Compiler Integration.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const ts = require('typescript');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
// Parse query from command line
|
|
20
|
+
let query;
|
|
21
|
+
try {
|
|
22
|
+
query = JSON.parse(process.argv[2] || '{}');
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.error(JSON.stringify({
|
|
25
|
+
error: 'Invalid JSON input',
|
|
26
|
+
message: e.message
|
|
27
|
+
}));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Find tsconfig.json and create a TypeScript program.
|
|
33
|
+
*/
|
|
34
|
+
function createProgram(projectPath) {
|
|
35
|
+
const configPath = ts.findConfigFile(
|
|
36
|
+
projectPath,
|
|
37
|
+
ts.sys.fileExists,
|
|
38
|
+
'tsconfig.json'
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if (!configPath) {
|
|
42
|
+
console.error(JSON.stringify({ error: 'tsconfig.json not found' }));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
47
|
+
if (configFile.error) {
|
|
48
|
+
console.error(JSON.stringify({ error: 'Failed to read tsconfig.json' }));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const parsed = ts.parseJsonConfigFileContent(
|
|
53
|
+
configFile.config,
|
|
54
|
+
ts.sys,
|
|
55
|
+
path.dirname(configPath)
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return ts.createProgram(parsed.fileNames, parsed.options);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get class/function members recursively.
|
|
63
|
+
*/
|
|
64
|
+
function getMembers(node, checker, sourceFile) {
|
|
65
|
+
const members = [];
|
|
66
|
+
|
|
67
|
+
if (ts.isClassDeclaration(node) && node.members) {
|
|
68
|
+
for (const member of node.members) {
|
|
69
|
+
if (ts.isMethodDeclaration(member) || ts.isPropertyDeclaration(member)) {
|
|
70
|
+
const name = member.name ? member.name.getText(sourceFile) : '<anonymous>';
|
|
71
|
+
const symbol = checker.getSymbolAtLocation(member.name || member);
|
|
72
|
+
let signature = '';
|
|
73
|
+
|
|
74
|
+
if (symbol) {
|
|
75
|
+
const type = checker.getTypeOfSymbolAtLocation(symbol, member);
|
|
76
|
+
signature = checker.typeToString(type);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(member.pos);
|
|
80
|
+
members.push({
|
|
81
|
+
name,
|
|
82
|
+
kind: ts.isMethodDeclaration(member) ? 'method' : 'property',
|
|
83
|
+
signature,
|
|
84
|
+
line: pos.line + 1
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return members;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Extract JSDoc @pre/@post comments.
|
|
95
|
+
*/
|
|
96
|
+
function extractContracts(node, sourceFile) {
|
|
97
|
+
const contracts = { pre: [], post: [] };
|
|
98
|
+
const jsDocs = ts.getJSDocTags(node);
|
|
99
|
+
|
|
100
|
+
for (const tag of jsDocs) {
|
|
101
|
+
const tagName = tag.tagName.getText();
|
|
102
|
+
const comment = typeof tag.comment === 'string'
|
|
103
|
+
? tag.comment
|
|
104
|
+
: tag.comment?.map(c => c.text).join('') || '';
|
|
105
|
+
|
|
106
|
+
if (tagName === 'pre') {
|
|
107
|
+
contracts.pre.push(comment);
|
|
108
|
+
} else if (tagName === 'post') {
|
|
109
|
+
contracts.post.push(comment);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return contracts;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Command: sig - Extract signatures from a file.
|
|
118
|
+
*/
|
|
119
|
+
function outputSignatures(filePath) {
|
|
120
|
+
const projectPath = path.dirname(filePath);
|
|
121
|
+
const program = createProgram(projectPath);
|
|
122
|
+
const checker = program.getTypeChecker();
|
|
123
|
+
const sourceFile = program.getSourceFile(path.resolve(filePath));
|
|
124
|
+
|
|
125
|
+
if (!sourceFile) {
|
|
126
|
+
console.log(JSON.stringify({ file: filePath, symbols: [], error: 'File not found in program' }));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const symbols = [];
|
|
131
|
+
|
|
132
|
+
function visit(node) {
|
|
133
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
134
|
+
const name = node.name.getText(sourceFile);
|
|
135
|
+
const symbol = checker.getSymbolAtLocation(node.name);
|
|
136
|
+
const type = symbol ? checker.getTypeOfSymbolAtLocation(symbol, node) : null;
|
|
137
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(node.pos);
|
|
138
|
+
const contracts = extractContracts(node, sourceFile);
|
|
139
|
+
|
|
140
|
+
symbols.push({
|
|
141
|
+
name,
|
|
142
|
+
kind: 'function',
|
|
143
|
+
signature: type ? checker.typeToString(type) : '',
|
|
144
|
+
line: pos.line + 1,
|
|
145
|
+
contracts
|
|
146
|
+
});
|
|
147
|
+
} else if (ts.isClassDeclaration(node) && node.name) {
|
|
148
|
+
const name = node.name.getText(sourceFile);
|
|
149
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(node.pos);
|
|
150
|
+
const members = getMembers(node, checker, sourceFile);
|
|
151
|
+
|
|
152
|
+
symbols.push({
|
|
153
|
+
name,
|
|
154
|
+
kind: 'class',
|
|
155
|
+
signature: `class ${name}`,
|
|
156
|
+
line: pos.line + 1,
|
|
157
|
+
members
|
|
158
|
+
});
|
|
159
|
+
} else if (ts.isInterfaceDeclaration(node) && node.name) {
|
|
160
|
+
const name = node.name.getText(sourceFile);
|
|
161
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(node.pos);
|
|
162
|
+
|
|
163
|
+
symbols.push({
|
|
164
|
+
name,
|
|
165
|
+
kind: 'interface',
|
|
166
|
+
signature: `interface ${name}`,
|
|
167
|
+
line: pos.line + 1
|
|
168
|
+
});
|
|
169
|
+
} else if (ts.isTypeAliasDeclaration(node) && node.name) {
|
|
170
|
+
const name = node.name.getText(sourceFile);
|
|
171
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(node.pos);
|
|
172
|
+
|
|
173
|
+
symbols.push({
|
|
174
|
+
name,
|
|
175
|
+
kind: 'type',
|
|
176
|
+
signature: `type ${name}`,
|
|
177
|
+
line: pos.line + 1
|
|
178
|
+
});
|
|
179
|
+
} else if (ts.isVariableStatement(node)) {
|
|
180
|
+
for (const decl of node.declarationList.declarations) {
|
|
181
|
+
if (ts.isIdentifier(decl.name)) {
|
|
182
|
+
const name = decl.name.getText(sourceFile);
|
|
183
|
+
const symbol = checker.getSymbolAtLocation(decl.name);
|
|
184
|
+
const type = symbol ? checker.getTypeOfSymbolAtLocation(symbol, decl) : null;
|
|
185
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(decl.pos);
|
|
186
|
+
|
|
187
|
+
// Only include exported or significant declarations
|
|
188
|
+
const isExported = node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
189
|
+
const isFunctionLike = decl.initializer && (
|
|
190
|
+
ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer)
|
|
191
|
+
);
|
|
192
|
+
if (isExported || isFunctionLike) {
|
|
193
|
+
symbols.push({
|
|
194
|
+
name,
|
|
195
|
+
kind: 'const',
|
|
196
|
+
signature: type ? checker.typeToString(type) : '',
|
|
197
|
+
line: pos.line + 1
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
ts.forEachChild(node, visit);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
visit(sourceFile);
|
|
208
|
+
console.log(JSON.stringify({ file: filePath, symbols }));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Command: map - Get symbol map with reference counts.
|
|
213
|
+
*/
|
|
214
|
+
function outputSymbolMap(projectPath, topN) {
|
|
215
|
+
const program = createProgram(projectPath);
|
|
216
|
+
const checker = program.getTypeChecker();
|
|
217
|
+
const allSymbols = [];
|
|
218
|
+
|
|
219
|
+
// Collect all symbols from all source files
|
|
220
|
+
for (const sourceFile of program.getSourceFiles()) {
|
|
221
|
+
// Skip node_modules and declaration files
|
|
222
|
+
if (sourceFile.isDeclarationFile || sourceFile.fileName.includes('node_modules')) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const relativePath = path.relative(projectPath, sourceFile.fileName);
|
|
227
|
+
|
|
228
|
+
function visit(node) {
|
|
229
|
+
let symbolInfo = null;
|
|
230
|
+
|
|
231
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
232
|
+
const name = node.name.getText(sourceFile);
|
|
233
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(node.pos);
|
|
234
|
+
symbolInfo = { name, kind: 'function', file: relativePath, line: pos.line + 1 };
|
|
235
|
+
} else if (ts.isClassDeclaration(node) && node.name) {
|
|
236
|
+
const name = node.name.getText(sourceFile);
|
|
237
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(node.pos);
|
|
238
|
+
symbolInfo = { name, kind: 'class', file: relativePath, line: pos.line + 1 };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (symbolInfo) {
|
|
242
|
+
allSymbols.push(symbolInfo);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
ts.forEachChild(node, visit);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
visit(sourceFile);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Sort by kind priority, then name
|
|
252
|
+
const kindOrder = { 'function': 0, 'class': 1, 'interface': 2, 'type': 3, 'const': 4 };
|
|
253
|
+
allSymbols.sort((a, b) => {
|
|
254
|
+
const orderA = kindOrder[a.kind] ?? 99;
|
|
255
|
+
const orderB = kindOrder[b.kind] ?? 99;
|
|
256
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
257
|
+
return a.name.localeCompare(b.name);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Limit to topN
|
|
261
|
+
const result = topN > 0 ? allSymbols.slice(0, topN) : allSymbols;
|
|
262
|
+
|
|
263
|
+
console.log(JSON.stringify({
|
|
264
|
+
path: projectPath,
|
|
265
|
+
total: allSymbols.length,
|
|
266
|
+
symbols: result
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Command: refs - Find all references to symbol at position.
|
|
272
|
+
*/
|
|
273
|
+
function outputReferences(filePath, line, column) {
|
|
274
|
+
const projectPath = path.dirname(filePath);
|
|
275
|
+
const configPath = ts.findConfigFile(projectPath, ts.sys.fileExists, 'tsconfig.json');
|
|
276
|
+
|
|
277
|
+
if (!configPath) {
|
|
278
|
+
console.log(JSON.stringify({ error: 'tsconfig.json not found', references: [] }));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Create language service for find references
|
|
283
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
284
|
+
const parsed = ts.parseJsonConfigFileContent(
|
|
285
|
+
configFile.config,
|
|
286
|
+
ts.sys,
|
|
287
|
+
path.dirname(configPath)
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const files = {};
|
|
291
|
+
for (const fileName of parsed.fileNames) {
|
|
292
|
+
try {
|
|
293
|
+
files[fileName] = {
|
|
294
|
+
version: 0,
|
|
295
|
+
text: fs.readFileSync(fileName, 'utf-8')
|
|
296
|
+
};
|
|
297
|
+
} catch (e) {
|
|
298
|
+
// Skip files that can't be read (may be deleted or permissions issue)
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const servicesHost = {
|
|
304
|
+
getScriptFileNames: () => parsed.fileNames,
|
|
305
|
+
getScriptVersion: (fileName) => files[fileName]?.version.toString() || '0',
|
|
306
|
+
getScriptSnapshot: (fileName) => {
|
|
307
|
+
if (!files[fileName]) {
|
|
308
|
+
try {
|
|
309
|
+
const text = fs.readFileSync(fileName, 'utf-8');
|
|
310
|
+
files[fileName] = { version: 0, text };
|
|
311
|
+
} catch (e) {
|
|
312
|
+
// File doesn't exist or can't be read
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return ts.ScriptSnapshot.fromString(files[fileName].text);
|
|
317
|
+
},
|
|
318
|
+
getCurrentDirectory: () => path.dirname(configPath),
|
|
319
|
+
getCompilationSettings: () => parsed.options,
|
|
320
|
+
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
|
|
321
|
+
fileExists: ts.sys.fileExists,
|
|
322
|
+
readFile: ts.sys.readFile,
|
|
323
|
+
readDirectory: ts.sys.readDirectory,
|
|
324
|
+
directoryExists: ts.sys.directoryExists,
|
|
325
|
+
getDirectories: ts.sys.getDirectories,
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
|
|
329
|
+
|
|
330
|
+
// Convert line/column to position
|
|
331
|
+
const absolutePath = path.resolve(filePath);
|
|
332
|
+
const sourceFile = services.getProgram()?.getSourceFile(absolutePath);
|
|
333
|
+
|
|
334
|
+
if (!sourceFile) {
|
|
335
|
+
console.log(JSON.stringify({ error: 'File not found', references: [] }));
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const position = sourceFile.getPositionOfLineAndCharacter(line - 1, column);
|
|
340
|
+
|
|
341
|
+
// Find references
|
|
342
|
+
const refs = services.findReferences(absolutePath, position);
|
|
343
|
+
const references = [];
|
|
344
|
+
|
|
345
|
+
if (refs) {
|
|
346
|
+
for (const refGroup of refs) {
|
|
347
|
+
for (const ref of refGroup.references) {
|
|
348
|
+
const refFile = services.getProgram()?.getSourceFile(ref.fileName);
|
|
349
|
+
if (refFile) {
|
|
350
|
+
const startPos = refFile.getLineAndCharacterOfPosition(ref.textSpan.start);
|
|
351
|
+
const lineText = refFile.text.split('\n')[startPos.line]?.trim() || '';
|
|
352
|
+
|
|
353
|
+
references.push({
|
|
354
|
+
file: path.relative(projectPath, ref.fileName),
|
|
355
|
+
line: startPos.line + 1,
|
|
356
|
+
column: startPos.character,
|
|
357
|
+
context: lineText,
|
|
358
|
+
isDefinition: ref.isDefinition || false
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
console.log(JSON.stringify({
|
|
366
|
+
file: filePath,
|
|
367
|
+
line,
|
|
368
|
+
column,
|
|
369
|
+
references
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Route to appropriate command
|
|
374
|
+
switch (query.command) {
|
|
375
|
+
case 'sig':
|
|
376
|
+
outputSignatures(query.file);
|
|
377
|
+
break;
|
|
378
|
+
case 'map':
|
|
379
|
+
outputSymbolMap(query.path || '.', query.top || 10);
|
|
380
|
+
break;
|
|
381
|
+
case 'refs':
|
|
382
|
+
outputReferences(query.file, query.line, query.column);
|
|
383
|
+
break;
|
|
384
|
+
default:
|
|
385
|
+
console.error(JSON.stringify({
|
|
386
|
+
error: `Unknown command: ${query.command}`,
|
|
387
|
+
usage: {
|
|
388
|
+
sig: { file: 'path/to/file.ts' },
|
|
389
|
+
map: { path: '.', top: 10 },
|
|
390
|
+
refs: { file: 'path/to/file.ts', line: 10, column: 5 }
|
|
391
|
+
}
|
|
392
|
+
}));
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
process.exit(0);
|