@renseiai/agentfactory-cli 0.8.17 → 0.8.18
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/dist/src/code.d.ts
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* get-repo-map Get PageRank-ranked repository file map
|
|
14
14
|
* search-code <query> BM25/hybrid code search
|
|
15
15
|
* check-duplicate Check content for duplicates
|
|
16
|
+
* find-type-usages <name> Find all switch/case, mapping, and usage sites for a type
|
|
17
|
+
* validate-cross-deps Check cross-package imports have package.json entries
|
|
16
18
|
* help Show this help message
|
|
17
19
|
*
|
|
18
20
|
* Environment:
|
package/dist/src/code.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code.d.ts","sourceRoot":"","sources":["../../src/code.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"code.d.ts","sourceRoot":"","sources":["../../src/code.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;GAqBG"}
|
package/dist/src/code.js
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* get-repo-map Get PageRank-ranked repository file map
|
|
14
14
|
* search-code <query> BM25/hybrid code search
|
|
15
15
|
* check-duplicate Check content for duplicates
|
|
16
|
+
* find-type-usages <name> Find all switch/case, mapping, and usage sites for a type
|
|
17
|
+
* validate-cross-deps Check cross-package imports have package.json entries
|
|
16
18
|
* help Show this help message
|
|
17
19
|
*
|
|
18
20
|
* Environment:
|
|
@@ -55,6 +57,12 @@ Options (check-duplicate):
|
|
|
55
57
|
--content <string> Content to check (inline)
|
|
56
58
|
--content-file <path> Path to file containing content to check
|
|
57
59
|
|
|
60
|
+
Options (find-type-usages):
|
|
61
|
+
--max-results <N> Maximum results (default: 50)
|
|
62
|
+
|
|
63
|
+
Options (validate-cross-deps):
|
|
64
|
+
[path] Optional directory/file to scope the check
|
|
65
|
+
|
|
58
66
|
Index:
|
|
59
67
|
First invocation builds the index from source files (~5-10s).
|
|
60
68
|
Subsequent calls reuse the persisted index from .agentfactory/code-index/.
|
|
@@ -66,6 +74,9 @@ Examples:
|
|
|
66
74
|
af-code search-code "incremental indexer" --language typescript
|
|
67
75
|
af-code check-duplicate --content "function hello() { return 'world' }"
|
|
68
76
|
af-code check-duplicate --content-file /tmp/snippet.ts
|
|
77
|
+
af-code find-type-usages "AgentWorkType"
|
|
78
|
+
af-code validate-cross-deps
|
|
79
|
+
af-code validate-cross-deps packages/linear
|
|
69
80
|
`);
|
|
70
81
|
}
|
|
71
82
|
async function main() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-intelligence-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/code-intelligence-runner.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,CAAA;IACjD,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,OAAO,CAAA;CAChB;AAID,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAC7C,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,CAAA;IACjD,cAAc,EAAE,MAAM,EAAE,CAAA;CACzB,CAwBA;
|
|
1
|
+
{"version":3,"file":"code-intelligence-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/code-intelligence-runner.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,CAAA;IACjD,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,OAAO,CAAA;CAChB;AAID,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAC7C,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,CAAA;IACjD,cAAc,EAAE,MAAM,EAAE,CAAA;CACzB,CAwBA;AA0aD,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAAC,4BAA4B,CAAC,CAuBvC"}
|
|
@@ -171,8 +171,210 @@ async function checkDuplicate(config, engines) {
|
|
|
171
171
|
const result = await engines.dedupPipeline.check(content);
|
|
172
172
|
return result;
|
|
173
173
|
}
|
|
174
|
+
async function findTypeUsages(config) {
|
|
175
|
+
const typeName = config.positionalArgs[0];
|
|
176
|
+
if (!typeName)
|
|
177
|
+
throw new Error('Usage: af-code find-type-usages <TypeName>');
|
|
178
|
+
const maxResults = config.args['max-results'] ? Number(config.args['max-results']) : 50;
|
|
179
|
+
const files = await discoverSourceFiles(config.cwd);
|
|
180
|
+
const usages = [];
|
|
181
|
+
// Patterns that indicate exhaustive switch/case, mapping objects, or type references
|
|
182
|
+
const switchPattern = new RegExp(`switch\\s*\\(`, 'g');
|
|
183
|
+
const casePattern = new RegExp(`case\\s+['"]`, 'g');
|
|
184
|
+
const importPattern = new RegExp(`\\b${escapeRegex(typeName)}\\b`, 'g');
|
|
185
|
+
const mappingPattern = new RegExp(`(?:Record<\\s*${escapeRegex(typeName)}|:\\s*\\{\\s*\\[\\w+\\s+in\\s+${escapeRegex(typeName)}\\]|satisfies\\s+Record<\\s*${escapeRegex(typeName)})`, 'g');
|
|
186
|
+
for (const [filePath, content] of files) {
|
|
187
|
+
if (!content.includes(typeName))
|
|
188
|
+
continue;
|
|
189
|
+
const lines = content.split('\n');
|
|
190
|
+
for (let i = 0; i < lines.length; i++) {
|
|
191
|
+
const line = lines[i];
|
|
192
|
+
// Check for import of the type
|
|
193
|
+
if (line.match(/\bimport\b/) && line.includes(typeName)) {
|
|
194
|
+
usages.push({ filePath, line: i + 1, context: line.trim(), kind: 'import' });
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
// Check for switch statements — look for switch keyword near the type name usage
|
|
198
|
+
if (switchPattern.test(line)) {
|
|
199
|
+
// Scan surrounding lines for the type name
|
|
200
|
+
const windowStart = Math.max(0, i - 2);
|
|
201
|
+
const windowEnd = Math.min(lines.length - 1, i + 5);
|
|
202
|
+
const window = lines.slice(windowStart, windowEnd + 1).join('\n');
|
|
203
|
+
if (window.includes(typeName) || hasRelatedCases(lines, i, typeName)) {
|
|
204
|
+
usages.push({ filePath, line: i + 1, context: line.trim(), kind: 'switch_case' });
|
|
205
|
+
}
|
|
206
|
+
switchPattern.lastIndex = 0;
|
|
207
|
+
}
|
|
208
|
+
// Check for Record<TypeName, ...> or mapping objects
|
|
209
|
+
if (mappingPattern.test(line)) {
|
|
210
|
+
usages.push({ filePath, line: i + 1, context: line.trim(), kind: 'mapping_object' });
|
|
211
|
+
mappingPattern.lastIndex = 0;
|
|
212
|
+
}
|
|
213
|
+
// Check for exhaustive checks (assertNever, default: throw, etc.)
|
|
214
|
+
if ((line.includes('assertNever') || line.includes('exhaustive')) &&
|
|
215
|
+
content.includes(typeName)) {
|
|
216
|
+
usages.push({ filePath, line: i + 1, context: line.trim(), kind: 'exhaustive_check' });
|
|
217
|
+
}
|
|
218
|
+
// Check for type definition/reference (union type definitions, extends, etc.)
|
|
219
|
+
if ((line.includes(`type ${typeName}`) ||
|
|
220
|
+
line.includes(`interface ${typeName}`) ||
|
|
221
|
+
line.match(new RegExp(`:\\s*${escapeRegex(typeName)}\\b`))) &&
|
|
222
|
+
!line.match(/\bimport\b/)) {
|
|
223
|
+
usages.push({ filePath, line: i + 1, context: line.trim(), kind: 'type_reference' });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Deduplicate and sort: switch_case and mapping_object first (most actionable)
|
|
228
|
+
const kindPriority = {
|
|
229
|
+
switch_case: 0,
|
|
230
|
+
mapping_object: 1,
|
|
231
|
+
exhaustive_check: 2,
|
|
232
|
+
type_reference: 3,
|
|
233
|
+
import: 4,
|
|
234
|
+
};
|
|
235
|
+
usages.sort((a, b) => (kindPriority[a.kind] ?? 5) - (kindPriority[b.kind] ?? 5));
|
|
236
|
+
return {
|
|
237
|
+
typeName,
|
|
238
|
+
totalUsages: usages.length,
|
|
239
|
+
usages: usages.slice(0, maxResults),
|
|
240
|
+
switchStatements: usages.filter(u => u.kind === 'switch_case').length,
|
|
241
|
+
mappingObjects: usages.filter(u => u.kind === 'mapping_object').length,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/** Check if a switch block's case statements relate to a union type */
|
|
245
|
+
function hasRelatedCases(lines, switchLine, _typeName) {
|
|
246
|
+
// Scan forward from the switch line looking for string literal cases
|
|
247
|
+
for (let j = switchLine; j < Math.min(lines.length, switchLine + 50); j++) {
|
|
248
|
+
if (lines[j].includes('case \'') || lines[j].includes('case "')) {
|
|
249
|
+
return true; // Has string literal cases, likely a discriminated union switch
|
|
250
|
+
}
|
|
251
|
+
if (lines[j].match(/^\s*\}/))
|
|
252
|
+
break; // End of block
|
|
253
|
+
}
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
function escapeRegex(str) {
|
|
257
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
258
|
+
}
|
|
259
|
+
async function validateCrossDeps(config) {
|
|
260
|
+
const targetPath = config.positionalArgs[0]; // Optional: specific file or directory
|
|
261
|
+
const files = await discoverSourceFiles(config.cwd);
|
|
262
|
+
// 1. Build a map of workspace packages by reading all package.json files
|
|
263
|
+
const workspacePackages = new Map();
|
|
264
|
+
await discoverWorkspacePackages(config.cwd, workspacePackages);
|
|
265
|
+
// 2. Map file paths to their owning workspace package
|
|
266
|
+
function findOwningPackage(filePath) {
|
|
267
|
+
let bestMatch = null;
|
|
268
|
+
for (const [dir, pkg] of workspacePackages) {
|
|
269
|
+
if (filePath.startsWith(dir + '/') || filePath === dir) {
|
|
270
|
+
if (!bestMatch || dir.length > bestMatch.key.length) {
|
|
271
|
+
bestMatch = { key: dir, pkg };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return bestMatch?.pkg;
|
|
276
|
+
}
|
|
277
|
+
const missingDeps = [];
|
|
278
|
+
// 3. Check each file for cross-package imports
|
|
279
|
+
for (const [filePath, content] of files) {
|
|
280
|
+
if (targetPath && !filePath.startsWith(targetPath))
|
|
281
|
+
continue;
|
|
282
|
+
const owningPkg = findOwningPackage(filePath);
|
|
283
|
+
if (!owningPkg)
|
|
284
|
+
continue;
|
|
285
|
+
const lines = content.split('\n');
|
|
286
|
+
for (let i = 0; i < lines.length; i++) {
|
|
287
|
+
const line = lines[i];
|
|
288
|
+
// Match import/require of workspace packages
|
|
289
|
+
const importMatch = line.match(/(?:from\s+['"]|require\s*\(\s*['"]|import\s+['"])(@[^'"\/]+\/[^'"\/]+|[^.'"\/@][^'"\/]*)/);
|
|
290
|
+
if (!importMatch)
|
|
291
|
+
continue;
|
|
292
|
+
const importedPkg = importMatch[1];
|
|
293
|
+
// Check if this is a workspace package
|
|
294
|
+
const isWorkspacePkg = [...workspacePackages.values()].some(wp => wp.name === importedPkg);
|
|
295
|
+
if (!isWorkspacePkg)
|
|
296
|
+
continue;
|
|
297
|
+
// Check if it's declared in package.json
|
|
298
|
+
if (!owningPkg.deps.has(importedPkg)) {
|
|
299
|
+
missingDeps.push({
|
|
300
|
+
importingFile: filePath,
|
|
301
|
+
importedPackage: importedPkg,
|
|
302
|
+
packageJsonPath: join(owningPkg.dir, 'package.json'),
|
|
303
|
+
line: i + 1,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Deduplicate by (packageJsonPath, importedPackage)
|
|
309
|
+
const seen = new Set();
|
|
310
|
+
const uniqueMissing = missingDeps.filter(d => {
|
|
311
|
+
const key = `${d.packageJsonPath}:${d.importedPackage}`;
|
|
312
|
+
if (seen.has(key))
|
|
313
|
+
return false;
|
|
314
|
+
seen.add(key);
|
|
315
|
+
return true;
|
|
316
|
+
});
|
|
317
|
+
return {
|
|
318
|
+
valid: uniqueMissing.length === 0,
|
|
319
|
+
missingDeps: uniqueMissing,
|
|
320
|
+
packagesChecked: workspacePackages.size,
|
|
321
|
+
filesChecked: targetPath
|
|
322
|
+
? [...files.keys()].filter(f => f.startsWith(targetPath)).length
|
|
323
|
+
: files.size,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
async function discoverWorkspacePackages(cwd, result) {
|
|
327
|
+
// Find all package.json files in the workspace (skip node_modules, dist)
|
|
328
|
+
async function walk(dir, depth) {
|
|
329
|
+
if (depth > 5)
|
|
330
|
+
return;
|
|
331
|
+
let entries;
|
|
332
|
+
try {
|
|
333
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
for (const entry of entries) {
|
|
339
|
+
if (IGNORE_DIRS.has(entry.name))
|
|
340
|
+
continue;
|
|
341
|
+
const fullPath = join(dir, entry.name);
|
|
342
|
+
if (entry.isDirectory()) {
|
|
343
|
+
await walk(fullPath, depth + 1);
|
|
344
|
+
}
|
|
345
|
+
else if (entry.name === 'package.json') {
|
|
346
|
+
try {
|
|
347
|
+
const content = JSON.parse(await readFile(fullPath, 'utf-8'));
|
|
348
|
+
if (content.name) {
|
|
349
|
+
const allDeps = new Set([
|
|
350
|
+
...Object.keys(content.dependencies ?? {}),
|
|
351
|
+
...Object.keys(content.devDependencies ?? {}),
|
|
352
|
+
...Object.keys(content.peerDependencies ?? {}),
|
|
353
|
+
]);
|
|
354
|
+
result.set(relative(cwd, dir), {
|
|
355
|
+
name: content.name,
|
|
356
|
+
dir: relative(cwd, dir),
|
|
357
|
+
deps: allDeps,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
// Skip malformed package.json
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
await walk(cwd, 0);
|
|
368
|
+
}
|
|
174
369
|
// ── Main runner ─────────────────────────────────────────────────────────────
|
|
175
370
|
export async function runCodeIntelligence(config) {
|
|
371
|
+
// Commands that don't need the full index
|
|
372
|
+
switch (config.command) {
|
|
373
|
+
case 'find-type-usages':
|
|
374
|
+
return { output: await findTypeUsages(config) };
|
|
375
|
+
case 'validate-cross-deps':
|
|
376
|
+
return { output: await validateCrossDeps(config) };
|
|
377
|
+
}
|
|
176
378
|
const engines = await initializeIndex(config.cwd);
|
|
177
379
|
switch (config.command) {
|
|
178
380
|
case 'search-symbols':
|
|
@@ -184,6 +386,6 @@ export async function runCodeIntelligence(config) {
|
|
|
184
386
|
case 'check-duplicate':
|
|
185
387
|
return { output: await checkDuplicate(config, engines) };
|
|
186
388
|
default:
|
|
187
|
-
throw new Error(`Unknown command: ${config.command}. Available: search-symbols, get-repo-map, search-code, check-duplicate`);
|
|
389
|
+
throw new Error(`Unknown command: ${config.command}. Available: search-symbols, get-repo-map, search-code, check-duplicate, find-type-usages, validate-cross-deps`);
|
|
188
390
|
}
|
|
189
391
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@renseiai/agentfactory-cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI tools for AgentFactory — local orchestrator, remote worker, queue admin",
|
|
6
6
|
"author": "Rensei AI (https://rensei.ai)",
|
|
@@ -126,12 +126,12 @@
|
|
|
126
126
|
],
|
|
127
127
|
"dependencies": {
|
|
128
128
|
"dotenv": "^17.2.3",
|
|
129
|
-
"@renseiai/
|
|
130
|
-
"@renseiai/
|
|
131
|
-
"@renseiai/agentfactory
|
|
129
|
+
"@renseiai/plugin-linear": "0.8.18",
|
|
130
|
+
"@renseiai/agentfactory-server": "0.8.18",
|
|
131
|
+
"@renseiai/agentfactory": "0.8.18"
|
|
132
132
|
},
|
|
133
133
|
"optionalDependencies": {
|
|
134
|
-
"@renseiai/agentfactory-code-intelligence": "0.8.
|
|
134
|
+
"@renseiai/agentfactory-code-intelligence": "0.8.18"
|
|
135
135
|
},
|
|
136
136
|
"devDependencies": {
|
|
137
137
|
"@types/node": "^22.5.4",
|