agentsys 5.14.0 → 6.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/.claude-plugin/marketplace.json +1 -27
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -3
- package/AGENTS.md +4 -6
- package/CHANGELOG.md +13 -0
- package/README.md +5 -115
- package/lib/binary/index.js +8 -2
- package/lib/binary/shared-helpers.js +160 -0
- package/lib/collectors/codebase.js +7 -2
- package/lib/collectors/documentation.js +8 -2
- package/lib/enhance/agent-patterns.js +17 -4
- package/lib/enhance/auto-suppression.js +19 -7
- package/lib/enhance/cross-file-analyzer.js +11 -4
- package/lib/enhance/docs-patterns.js +6 -2
- package/lib/enhance/fixer.js +22 -5
- package/lib/enhance/skill-patterns.js +5 -5
- package/lib/index.js +2 -0
- package/lib/repo-intel/cache.js +171 -0
- package/lib/repo-intel/converter.js +130 -0
- package/lib/repo-intel/embed/binary.js +242 -0
- package/lib/repo-intel/embed/index.js +26 -0
- package/lib/repo-intel/embed/orchestrator.js +239 -0
- package/lib/repo-intel/embed/preference.js +136 -0
- package/lib/repo-intel/enrich.js +198 -0
- package/lib/repo-intel/index.js +370 -0
- package/lib/repo-intel/installer.js +78 -0
- package/lib/repo-intel/queries.js +213 -13
- package/lib/repo-intel/updater.js +104 -0
- package/lib/repo-map/index.js +19 -254
- package/package.json +1 -1
- package/scripts/generate-docs.js +2 -13
- package/scripts/plugins.txt +0 -2
- package/site/assets/js/main.js +5 -13
- package/site/content.json +7 -24
- package/site/index.html +26 -74
- package/site/ux-spec.md +6 -6
- package/.kiro/agents/web-session.json +0 -12
- package/.kiro/skills/web-auth/SKILL.md +0 -177
- package/.kiro/skills/web-browse/SKILL.md +0 -516
|
@@ -99,6 +99,19 @@ function runQuery(queryName, extraArgs, cwd) {
|
|
|
99
99
|
return parsed;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Assert a query argument is a non-empty string. Centralizes the validation
|
|
104
|
+
* that file/symbol/concept-taking queries share, so a wrong-typed arg fails
|
|
105
|
+
* with a clear message instead of being stringified into a bogus CLI argument.
|
|
106
|
+
* @param {*} val - the value to check
|
|
107
|
+
* @param {string} label - "<fn>: <arg>" for the error message
|
|
108
|
+
*/
|
|
109
|
+
function assertString(val, label) {
|
|
110
|
+
if (typeof val !== 'string' || val.length === 0) {
|
|
111
|
+
throw new TypeError(`${label} must be a non-empty string`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
102
115
|
// ---------------------------------------------------------------------------
|
|
103
116
|
// Query functions
|
|
104
117
|
// ---------------------------------------------------------------------------
|
|
@@ -123,6 +136,7 @@ function hotspots(cwd, opts = {}) {
|
|
|
123
136
|
* @returns {Array}
|
|
124
137
|
*/
|
|
125
138
|
function coupling(cwd, file, opts = {}) {
|
|
139
|
+
assertString(file, 'coupling: file');
|
|
126
140
|
const extra = [file];
|
|
127
141
|
if (opts.limit != null) extra.push('--top', String(opts.limit));
|
|
128
142
|
return runQuery('coupling', extra, cwd);
|
|
@@ -154,17 +168,6 @@ function testGaps(cwd, opts = {}) {
|
|
|
154
168
|
return runQuery('test-gaps', extra, cwd);
|
|
155
169
|
}
|
|
156
170
|
|
|
157
|
-
/**
|
|
158
|
-
* AI-authored ratio per file or project.
|
|
159
|
-
* @param {string} cwd
|
|
160
|
-
* @param {{ pathFilter?: string }} [opts]
|
|
161
|
-
* @returns {Object}
|
|
162
|
-
*/
|
|
163
|
-
function aiRatio(cwd, opts = {}) {
|
|
164
|
-
const extra = [];
|
|
165
|
-
if (opts.pathFilter != null) extra.push('--path-filter', opts.pathFilter);
|
|
166
|
-
return runQuery('ai-ratio', extra, cwd);
|
|
167
|
-
}
|
|
168
171
|
|
|
169
172
|
/**
|
|
170
173
|
* Risk score for a diff (set of changed files).
|
|
@@ -197,8 +200,12 @@ function diffRisk(cwd, files) {
|
|
|
197
200
|
* @returns {Object}
|
|
198
201
|
*/
|
|
199
202
|
function dependents(cwd, symbol, file) {
|
|
203
|
+
assertString(symbol, 'dependents: symbol');
|
|
200
204
|
const extra = [symbol];
|
|
201
|
-
if (file != null)
|
|
205
|
+
if (file != null) {
|
|
206
|
+
assertString(file, 'dependents: file');
|
|
207
|
+
extra.push('--file', file);
|
|
208
|
+
}
|
|
202
209
|
return runQuery('dependents', extra, cwd);
|
|
203
210
|
}
|
|
204
211
|
|
|
@@ -255,6 +262,7 @@ function boundaries(cwd, opts = {}) {
|
|
|
255
262
|
* @returns {Object}
|
|
256
263
|
*/
|
|
257
264
|
function areaOf(cwd, file) {
|
|
265
|
+
assertString(file, 'areaOf: file');
|
|
258
266
|
return runQuery('area-of', [file], cwd);
|
|
259
267
|
}
|
|
260
268
|
|
|
@@ -273,6 +281,171 @@ function communityHealth(cwd, id) {
|
|
|
273
281
|
return runQuery('community-health', [String(id)], cwd);
|
|
274
282
|
}
|
|
275
283
|
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
// Activity / git queries
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
/** Least-changed files (no recent activity). @param {string} cwd @param {{limit?:number}} [opts] */
|
|
289
|
+
function coldspots(cwd, opts = {}) {
|
|
290
|
+
const extra = [];
|
|
291
|
+
if (opts.limit != null) extra.push('--top', String(opts.limit));
|
|
292
|
+
return runQuery('coldspots', extra, cwd);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Ownership for a file or directory. @param {string} cwd @param {string} file */
|
|
296
|
+
function ownership(cwd, file) {
|
|
297
|
+
assertString(file, 'ownership: file');
|
|
298
|
+
return runQuery('ownership', [file], cwd);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Project norms detected from git history. @param {string} cwd */
|
|
302
|
+
function norms(cwd) {
|
|
303
|
+
return runQuery('norms', [], cwd);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Area-level health overview. @param {string} cwd */
|
|
307
|
+
function areas(cwd) {
|
|
308
|
+
return runQuery('areas', [], cwd);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Contributors sorted by commit count. @param {string} cwd @param {{limit?:number}} [opts] */
|
|
312
|
+
function contributors(cwd, opts = {}) {
|
|
313
|
+
const extra = [];
|
|
314
|
+
if (opts.limit != null) extra.push('--top', String(opts.limit));
|
|
315
|
+
return runQuery('contributors', extra, cwd);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Release cadence and tag info. @param {string} cwd */
|
|
319
|
+
function releaseInfo(cwd) {
|
|
320
|
+
return runQuery('release-info', [], cwd);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** History for a specific file. @param {string} cwd @param {string} file */
|
|
324
|
+
function fileHistory(cwd, file) {
|
|
325
|
+
assertString(file, 'fileHistory: file');
|
|
326
|
+
return runQuery('file-history', [file], cwd);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Commit message conventions. @param {string} cwd */
|
|
330
|
+
function conventions(cwd) {
|
|
331
|
+
return runQuery('conventions', [], cwd);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Doc files with low code coupling (likely stale). @param {string} cwd @param {{limit?:number}} [opts] */
|
|
335
|
+
function docDrift(cwd, opts = {}) {
|
|
336
|
+
const extra = [];
|
|
337
|
+
if (opts.limit != null) extra.push('--top', String(opts.limit));
|
|
338
|
+
return runQuery('doc-drift', extra, cwd);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
// Narrative / guidance queries
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
/** Newcomer-oriented repo summary. @param {string} cwd */
|
|
346
|
+
function onboard(cwd) {
|
|
347
|
+
return runQuery('onboard', [], cwd);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Contributor guidance matching skills to areas needing work. @param {string} cwd */
|
|
351
|
+
function canIHelp(cwd) {
|
|
352
|
+
return runQuery('can-i-help', [], cwd);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Files ranked by pain score (hotspot x complexity x bug density). @param {string} cwd @param {{limit?:number}} [opts] */
|
|
356
|
+
function painspots(cwd, opts = {}) {
|
|
357
|
+
const extra = [];
|
|
358
|
+
if (opts.limit != null) extra.push('--top', String(opts.limit));
|
|
359
|
+
return runQuery('painspots', extra, cwd);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Every place execution can start (binaries, main fns, npm scripts). @param {string} cwd @param {{files?:string[]|string}} [opts] */
|
|
363
|
+
function entryPoints(cwd, opts = {}) {
|
|
364
|
+
const extra = [];
|
|
365
|
+
if (opts.files) {
|
|
366
|
+
const list = Array.isArray(opts.files) ? opts.files.join(',') : String(opts.files);
|
|
367
|
+
extra.push('--files', list);
|
|
368
|
+
}
|
|
369
|
+
return runQuery('entry-points', extra, cwd);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Project metadata: languages, CI, license, README. @param {string} cwd */
|
|
373
|
+
function projectInfo(cwd) {
|
|
374
|
+
return runQuery('project-info', [], cwd);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ---------------------------------------------------------------------------
|
|
378
|
+
// AST / symbol queries
|
|
379
|
+
// ---------------------------------------------------------------------------
|
|
380
|
+
|
|
381
|
+
/** AST symbols (exports/imports/definitions) for a file. @param {string} cwd @param {string} file */
|
|
382
|
+
function symbols(cwd, file) {
|
|
383
|
+
assertString(file, 'symbols: file');
|
|
384
|
+
return runQuery('symbols', [file], cwd);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Doc files with stale references to source symbols. @param {string} cwd @param {{limit?:number}} [opts] */
|
|
388
|
+
function staleDocs(cwd, opts = {}) {
|
|
389
|
+
const extra = [];
|
|
390
|
+
if (opts.limit != null) extra.push('--top', String(opts.limit));
|
|
391
|
+
return runQuery('stale-docs', extra, cwd);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Concept-to-file search (ranked, replaces grep -r). @param {string} cwd @param {string} query @param {{limit?:number}} [opts] */
|
|
395
|
+
function find(cwd, query, opts = {}) {
|
|
396
|
+
assertString(query, 'find: query');
|
|
397
|
+
const extra = [query];
|
|
398
|
+
if (opts.limit != null) extra.push('--top', String(opts.limit));
|
|
399
|
+
return runQuery('find', extra, cwd);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ---------------------------------------------------------------------------
|
|
403
|
+
// Deslop-agent queries
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
|
|
406
|
+
/** Structured slop fix actions for the deslop agent. @param {string} cwd */
|
|
407
|
+
function slopFixes(cwd) {
|
|
408
|
+
return runQuery('slop-fixes', [], cwd);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Ranked slop targets (Sonnet/Opus tiers). @param {string} cwd @param {{top?:number}} [opts] */
|
|
412
|
+
function slopTargets(cwd, opts = {}) {
|
|
413
|
+
const extra = [];
|
|
414
|
+
if (opts.top != null) extra.push('--top', String(opts.top));
|
|
415
|
+
return runQuery('slop-targets', extra, cwd);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Cached 3-depth narrative summary. Returns null when not yet generated;
|
|
420
|
+
* with opts.depth returns that single depth as plain text. Bypasses the
|
|
421
|
+
* JSON-parse path because the binary emits the literal 'null' or raw text.
|
|
422
|
+
* @param {string} cwd @param {{depth?:1|3|10}} [opts]
|
|
423
|
+
*/
|
|
424
|
+
function summary(cwd, opts = {}) {
|
|
425
|
+
const mapFile = requireMapFile(cwd);
|
|
426
|
+
const extra = [];
|
|
427
|
+
if (opts.depth != null) extra.push('--depth', String(opts.depth));
|
|
428
|
+
const args = ['repo-intel', 'query', 'summary', ...extra, '--map-file', mapFile, cwd];
|
|
429
|
+
// summary bypasses runQuery (a single --depth returns plain text, not JSON),
|
|
430
|
+
// so it must wrap the analyzer call + parse itself to match runQuery's error
|
|
431
|
+
// contract instead of leaking a raw spawn / SyntaxError.
|
|
432
|
+
let raw;
|
|
433
|
+
try {
|
|
434
|
+
raw = binary.runAnalyzer(args).trim();
|
|
435
|
+
} catch (err) {
|
|
436
|
+
throw new Error(`repo-intel query failed [summary]: ${err.message}`, { cause: err });
|
|
437
|
+
}
|
|
438
|
+
if (raw === 'null') return null;
|
|
439
|
+
if (opts.depth != null) return raw; // plain-text single depth
|
|
440
|
+
try {
|
|
441
|
+
return JSON.parse(raw);
|
|
442
|
+
} catch (_parseErr) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
`repo-intel query [summary] returned non-JSON output: ${raw.slice(0, 200)}`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
276
449
|
// ---------------------------------------------------------------------------
|
|
277
450
|
// Exports
|
|
278
451
|
// ---------------------------------------------------------------------------
|
|
@@ -286,7 +459,6 @@ module.exports = {
|
|
|
286
459
|
coupling,
|
|
287
460
|
busFactor,
|
|
288
461
|
testGaps,
|
|
289
|
-
aiRatio,
|
|
290
462
|
diffRisk,
|
|
291
463
|
dependents,
|
|
292
464
|
bugspots,
|
|
@@ -297,4 +469,32 @@ module.exports = {
|
|
|
297
469
|
boundaries,
|
|
298
470
|
areaOf,
|
|
299
471
|
communityHealth,
|
|
472
|
+
|
|
473
|
+
// Activity / git queries
|
|
474
|
+
coldspots,
|
|
475
|
+
ownership,
|
|
476
|
+
norms,
|
|
477
|
+
areas,
|
|
478
|
+
contributors,
|
|
479
|
+
releaseInfo,
|
|
480
|
+
fileHistory,
|
|
481
|
+
conventions,
|
|
482
|
+
docDrift,
|
|
483
|
+
|
|
484
|
+
// Narrative / guidance queries
|
|
485
|
+
onboard,
|
|
486
|
+
canIHelp,
|
|
487
|
+
painspots,
|
|
488
|
+
entryPoints,
|
|
489
|
+
projectInfo,
|
|
490
|
+
|
|
491
|
+
// AST / symbol queries
|
|
492
|
+
symbols,
|
|
493
|
+
staleDocs,
|
|
494
|
+
find,
|
|
495
|
+
|
|
496
|
+
// Deslop-agent queries
|
|
497
|
+
slopFixes,
|
|
498
|
+
slopTargets,
|
|
499
|
+
summary,
|
|
300
500
|
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Repo map staleness checker.
|
|
5
|
+
*
|
|
6
|
+
* Determines whether a cached repo-map is stale relative to the current git HEAD.
|
|
7
|
+
* The incremental update path is handled by agent-analyzer (repo-intel update).
|
|
8
|
+
*
|
|
9
|
+
* @module lib/repo-intel/updater
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { execFileSync } = require('child_process');
|
|
13
|
+
|
|
14
|
+
const cache = require('./cache');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check whether the cached map is stale
|
|
18
|
+
* @param {string} basePath - Repository root path
|
|
19
|
+
* @param {Object} map - Cached repo map
|
|
20
|
+
* @returns {{isStale: boolean, reason: string|null, commitsBehind: number, suggestFullRebuild: boolean}}
|
|
21
|
+
*/
|
|
22
|
+
function checkStaleness(basePath, map) {
|
|
23
|
+
const result = {
|
|
24
|
+
isStale: false,
|
|
25
|
+
reason: null,
|
|
26
|
+
commitsBehind: 0,
|
|
27
|
+
suggestFullRebuild: false
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (!map?.git?.commit) {
|
|
31
|
+
result.isStale = true;
|
|
32
|
+
result.reason = 'Missing base commit in repo-map';
|
|
33
|
+
result.suggestFullRebuild = true;
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (cache.isMarkedStale(basePath)) {
|
|
38
|
+
result.isStale = true;
|
|
39
|
+
result.reason = 'Marked stale by hook';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!commitExists(basePath, map.git.commit)) {
|
|
43
|
+
result.isStale = true;
|
|
44
|
+
result.reason = 'Base commit no longer exists (rebased?)';
|
|
45
|
+
result.suggestFullRebuild = true;
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const currentBranch = getCurrentBranch(basePath);
|
|
50
|
+
if (currentBranch && map.git.branch && currentBranch !== map.git.branch) {
|
|
51
|
+
result.isStale = true;
|
|
52
|
+
result.reason = `Branch changed from ${map.git.branch} to ${currentBranch}`;
|
|
53
|
+
result.suggestFullRebuild = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const commitsBehind = getCommitsBehind(basePath, map.git.commit);
|
|
57
|
+
if (commitsBehind > 0) {
|
|
58
|
+
result.isStale = true;
|
|
59
|
+
result.commitsBehind = commitsBehind;
|
|
60
|
+
if (!result.reason) {
|
|
61
|
+
result.reason = `${commitsBehind} commits behind HEAD`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isValidCommitHash(commit) {
|
|
69
|
+
return typeof commit === 'string' && /^[0-9a-fA-F]{4,40}$/.test(commit);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function commitExists(basePath, commit) {
|
|
73
|
+
if (!isValidCommitHash(commit)) return false;
|
|
74
|
+
try {
|
|
75
|
+
execFileSync('git', ['cat-file', '-e', commit], { cwd: basePath, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
76
|
+
return true;
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getCurrentBranch(basePath) {
|
|
83
|
+
try {
|
|
84
|
+
return execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
85
|
+
cwd: basePath, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
|
|
86
|
+
}).trim();
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getCommitsBehind(basePath, commit) {
|
|
93
|
+
if (!isValidCommitHash(commit)) return 0;
|
|
94
|
+
try {
|
|
95
|
+
const out = execFileSync('git', ['rev-list', `${commit}..HEAD`, '--count'], {
|
|
96
|
+
cwd: basePath, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
|
|
97
|
+
}).trim();
|
|
98
|
+
return Number(out) || 0;
|
|
99
|
+
} catch {
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = { checkStaleness };
|
package/lib/repo-map/index.js
CHANGED
|
@@ -1,265 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* DEPRECATED COMPAT SHIM — repo-map folded into lib/repo-intel.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* repo-map was a leftover split: it produced/converted the artifact while
|
|
7
|
+
* lib/repo-intel/ only queried it. They are one pipeline over one binary, now
|
|
8
|
+
* unified under lib/repo-intel. This shim re-exports the lifecycle surface so
|
|
9
|
+
* existing `require('.../repo-map')` consumers keep working unchanged.
|
|
10
|
+
*
|
|
11
|
+
* Migrate callers to `require('.../repo-intel')` and remove this shim.
|
|
8
12
|
*
|
|
9
13
|
* @module lib/repo-map
|
|
14
|
+
* @deprecated use lib/repo-intel
|
|
10
15
|
*/
|
|
11
16
|
|
|
12
|
-
const
|
|
13
|
-
const path = require('path');
|
|
14
|
-
const { execFileSync } = require('child_process');
|
|
15
|
-
|
|
16
|
-
const installer = require('./installer');
|
|
17
|
-
const cache = require('./cache');
|
|
18
|
-
const updater = require('./updater');
|
|
19
|
-
const converter = require('./converter');
|
|
20
|
-
const binary = require('../binary');
|
|
21
|
-
const { getStateDirPath } = require('../platform/state-dir');
|
|
22
|
-
const { writeJsonAtomic } = require('../utils/atomic-write');
|
|
23
|
-
|
|
24
|
-
const REPO_INTEL_FILENAME = 'repo-intel.json';
|
|
25
|
-
|
|
26
|
-
function getIntelMapPath(basePath) {
|
|
27
|
-
return path.join(getStateDirPath(basePath), REPO_INTEL_FILENAME);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Initialize a new repo map (full scan)
|
|
32
|
-
* @param {string} basePath - Repository root path
|
|
33
|
-
* @param {Object} options - Options
|
|
34
|
-
* @param {boolean} options.force - Force rebuild even if map exists
|
|
35
|
-
* @returns {Promise<{success: boolean, map?: Object, error?: string}>}
|
|
36
|
-
*/
|
|
37
|
-
async function init(basePath, options = {}) {
|
|
38
|
-
const installed = await installer.checkInstalled();
|
|
39
|
-
if (!installed.found) {
|
|
40
|
-
return {
|
|
41
|
-
success: false,
|
|
42
|
-
error: 'agent-analyzer binary unavailable: ' + (installed.error || 'unknown error'),
|
|
43
|
-
installSuggestion: installer.getInstallInstructions()
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const existing = cache.load(basePath);
|
|
48
|
-
if (existing && !options.force) {
|
|
49
|
-
return {
|
|
50
|
-
success: false,
|
|
51
|
-
error: 'Repo map already exists. Use --force to rebuild or /repo-map update to refresh.',
|
|
52
|
-
existing: cache.getStatus(basePath)
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const startTime = Date.now();
|
|
57
|
-
|
|
58
|
-
let intelJson;
|
|
59
|
-
try {
|
|
60
|
-
intelJson = await binary.runAnalyzerAsync(['repo-intel', 'init', basePath]);
|
|
61
|
-
} catch (e) {
|
|
62
|
-
return {
|
|
63
|
-
success: false,
|
|
64
|
-
error: 'agent-analyzer repo-intel init failed: ' + e.message
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
let intel;
|
|
69
|
-
try {
|
|
70
|
-
intel = JSON.parse(intelJson);
|
|
71
|
-
} catch (e) {
|
|
72
|
-
return {
|
|
73
|
-
success: false,
|
|
74
|
-
error: 'Failed to parse repo-intel output: ' + e.message
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Persist repo-intel.json for future incremental updates
|
|
79
|
-
const intelPath = getIntelMapPath(basePath);
|
|
80
|
-
try {
|
|
81
|
-
writeJsonAtomic(intelPath, intel);
|
|
82
|
-
} catch {
|
|
83
|
-
// Non-fatal: update() will fall back to full init
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const map = converter.convertIntelToRepoMap(intel);
|
|
87
|
-
map.stats.scanDurationMs = Date.now() - startTime;
|
|
88
|
-
cache.save(basePath, map);
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
success: true,
|
|
92
|
-
map,
|
|
93
|
-
summary: {
|
|
94
|
-
files: Object.keys(map.files).length,
|
|
95
|
-
symbols: map.stats.totalSymbols,
|
|
96
|
-
languages: map.project.languages,
|
|
97
|
-
duration: map.stats.scanDurationMs
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Update an existing repo map (incremental via agent-analyzer)
|
|
104
|
-
* @param {string} basePath - Repository root path
|
|
105
|
-
* @param {Object} options - Options
|
|
106
|
-
* @param {boolean} options.full - Force full rebuild instead of incremental
|
|
107
|
-
* @returns {Promise<{success: boolean, summary?: Object, error?: string}>}
|
|
108
|
-
*/
|
|
109
|
-
async function update(basePath, options = {}) {
|
|
110
|
-
const installed = await installer.checkInstalled();
|
|
111
|
-
if (!installed.found) {
|
|
112
|
-
return {
|
|
113
|
-
success: false,
|
|
114
|
-
error: 'agent-analyzer binary unavailable: ' + (installed.error || 'unknown error'),
|
|
115
|
-
installSuggestion: installer.getInstallInstructions()
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (!cache.exists(basePath)) {
|
|
120
|
-
return {
|
|
121
|
-
success: false,
|
|
122
|
-
error: 'No repo map found. Run /repo-map init first.'
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (options.full) {
|
|
127
|
-
return init(basePath, { force: true });
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const intelPath = getIntelMapPath(basePath);
|
|
131
|
-
if (!fs.existsSync(intelPath)) {
|
|
132
|
-
return init(basePath, { force: true });
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const startTime = Date.now();
|
|
136
|
-
|
|
137
|
-
let intelJson;
|
|
138
|
-
try {
|
|
139
|
-
intelJson = await binary.runAnalyzerAsync([
|
|
140
|
-
'repo-intel', 'update',
|
|
141
|
-
'--map-file', intelPath,
|
|
142
|
-
basePath
|
|
143
|
-
]);
|
|
144
|
-
} catch (e) {
|
|
145
|
-
return {
|
|
146
|
-
success: false,
|
|
147
|
-
error: 'agent-analyzer repo-intel update failed: ' + e.message
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
let intel;
|
|
152
|
-
try {
|
|
153
|
-
intel = JSON.parse(intelJson);
|
|
154
|
-
} catch (e) {
|
|
155
|
-
return {
|
|
156
|
-
success: false,
|
|
157
|
-
error: 'Failed to parse repo-intel update output: ' + e.message
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
writeJsonAtomic(intelPath, intel);
|
|
163
|
-
} catch {
|
|
164
|
-
// Non-fatal
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const map = converter.convertIntelToRepoMap(intel);
|
|
168
|
-
map.stats.scanDurationMs = Date.now() - startTime;
|
|
169
|
-
cache.save(basePath, map);
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
success: true,
|
|
173
|
-
map,
|
|
174
|
-
summary: {
|
|
175
|
-
files: Object.keys(map.files).length,
|
|
176
|
-
symbols: map.stats.totalSymbols,
|
|
177
|
-
duration: map.stats.scanDurationMs
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Get repo map status
|
|
184
|
-
* @param {string} basePath - Repository root path
|
|
185
|
-
* @returns {{exists: boolean, status?: Object}}
|
|
186
|
-
*/
|
|
187
|
-
function status(basePath) {
|
|
188
|
-
const map = cache.load(basePath);
|
|
189
|
-
if (!map) {
|
|
190
|
-
return { exists: false };
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const staleness = updater.checkStaleness(basePath, map);
|
|
194
|
-
|
|
195
|
-
let branch;
|
|
196
|
-
try {
|
|
197
|
-
branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: basePath, encoding: 'utf8' }).trim();
|
|
198
|
-
} catch {
|
|
199
|
-
// Non-fatal
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return {
|
|
203
|
-
exists: true,
|
|
204
|
-
status: {
|
|
205
|
-
generated: map.generated,
|
|
206
|
-
updated: map.updated,
|
|
207
|
-
commit: map.git?.commit,
|
|
208
|
-
branch,
|
|
209
|
-
files: Object.keys(map.files).length,
|
|
210
|
-
symbols: map.stats?.totalSymbols || 0,
|
|
211
|
-
languages: map.project?.languages || [],
|
|
212
|
-
staleness
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Load repo map (if exists)
|
|
219
|
-
* @param {string} basePath - Repository root path
|
|
220
|
-
* @returns {Object|null}
|
|
221
|
-
*/
|
|
222
|
-
function load(basePath) {
|
|
223
|
-
return cache.load(basePath);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Check if repo map exists
|
|
228
|
-
* @param {string} basePath - Repository root path
|
|
229
|
-
* @returns {boolean}
|
|
230
|
-
*/
|
|
231
|
-
function exists(basePath) {
|
|
232
|
-
return cache.exists(basePath);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Check if agent-analyzer is available (compat alias for checkInstalled).
|
|
237
|
-
* Previously checked ast-grep; now checks agent-analyzer.
|
|
238
|
-
* @returns {Promise<{found: boolean, version?: string, tool: string}>}
|
|
239
|
-
*/
|
|
240
|
-
async function checkAstGrepInstalled() {
|
|
241
|
-
return installer.checkInstalled();
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Get install instructions (compat alias).
|
|
246
|
-
* @returns {string}
|
|
247
|
-
*/
|
|
248
|
-
function getInstallInstructions() {
|
|
249
|
-
return installer.getInstallInstructions();
|
|
250
|
-
}
|
|
17
|
+
const repoIntel = require('../repo-intel');
|
|
251
18
|
|
|
252
19
|
module.exports = {
|
|
253
|
-
init,
|
|
254
|
-
update,
|
|
255
|
-
status,
|
|
256
|
-
load,
|
|
257
|
-
exists,
|
|
258
|
-
checkAstGrepInstalled,
|
|
259
|
-
getInstallInstructions,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
cache,
|
|
264
|
-
updater
|
|
20
|
+
init: repoIntel.init,
|
|
21
|
+
update: repoIntel.update,
|
|
22
|
+
status: repoIntel.status,
|
|
23
|
+
load: repoIntel.load,
|
|
24
|
+
exists: repoIntel.exists,
|
|
25
|
+
checkAstGrepInstalled: repoIntel.checkAstGrepInstalled,
|
|
26
|
+
getInstallInstructions: repoIntel.getInstallInstructions,
|
|
27
|
+
installer: repoIntel.installer,
|
|
28
|
+
cache: repoIntel.cache,
|
|
29
|
+
updater: repoIntel.updater
|
|
265
30
|
};
|
package/package.json
CHANGED