agentsys 5.8.5 → 5.9.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 +13 -2
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +19 -0
- package/README.md +2 -2
- package/lib/collectors/analyzer-queries.js +341 -0
- package/lib/collectors/documentation.js +1 -2
- package/lib/collectors/github.js +3 -5
- package/lib/collectors/index.js +12 -1
- package/lib/cross-platform/index.js +6 -5
- package/lib/enhance/cross-file-analyzer.js +2 -3
- package/lib/enhance/cross-file-patterns.js +2 -3
- package/lib/enhance/docs-patterns.js +4 -6
- package/lib/package.json +0 -32
- package/lib/patterns/cli-enhancers.js +2 -6
- package/lib/patterns/pipeline.js +3 -7
- package/lib/patterns/slop-analyzers.js +1 -3
- package/lib/platform/detect-platform.js +1 -2
- package/lib/repo-intel/queries.js +300 -0
- package/lib/sources/policy-questions.js +3 -4
- package/lib/state/workflow-state.js +106 -173
- package/package.json +1 -1
- package/scripts/generate-docs.js +23 -11
- package/scripts/plugins.txt +1 -0
- package/site/content.json +3 -3
- package/site/index.html +5 -5
- package/site/ux-spec.md +7 -7
|
@@ -6,11 +6,7 @@
|
|
|
6
6
|
* Functions gracefully degrade when tools are not available.
|
|
7
7
|
*
|
|
8
8
|
* Supported languages: javascript, typescript, python, rust, go
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const { truncate } = require('../cross-platform');
|
|
12
|
-
|
|
13
|
-
/**
|
|
9
|
+
*
|
|
14
10
|
* @module patterns/cli-enhancers
|
|
15
11
|
* @author Avi Fenesh
|
|
16
12
|
* @license MIT
|
|
@@ -384,7 +380,7 @@ function runDuplicateDetection(repoPath, options = {}) {
|
|
|
384
380
|
secondLine: dup.secondFile?.start || 0,
|
|
385
381
|
lines: dup.lines || 0,
|
|
386
382
|
tokens: dup.tokens || 0,
|
|
387
|
-
fragment:
|
|
383
|
+
fragment: dup.fragment?.substring(0, 100) || ''
|
|
388
384
|
});
|
|
389
385
|
}
|
|
390
386
|
}
|
package/lib/patterns/pipeline.js
CHANGED
|
@@ -7,11 +7,7 @@
|
|
|
7
7
|
* - Phase 3 (LLM handoff): certainty-tagged findings for agent review
|
|
8
8
|
*
|
|
9
9
|
* Inherits modes from deslop: report (analyze only) vs apply (fix issues)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const { truncate } = require('../cross-platform');
|
|
13
|
-
|
|
14
|
-
/**
|
|
10
|
+
*
|
|
15
11
|
* @module patterns/pipeline
|
|
16
12
|
* @author Avi Fenesh
|
|
17
13
|
* @license MIT
|
|
@@ -360,7 +356,7 @@ function runPhase1(repoPath, targetFiles, language, fileContents) {
|
|
|
360
356
|
certainty: CERTAINTY.HIGH,
|
|
361
357
|
description: pattern.description,
|
|
362
358
|
autoFix: pattern.autoFix,
|
|
363
|
-
content:
|
|
359
|
+
content: line.trim().substring(0, 100),
|
|
364
360
|
phase: 1
|
|
365
361
|
});
|
|
366
362
|
}
|
|
@@ -661,7 +657,7 @@ async function runMultiPassAnalyzers(repoPath, targetFiles, fileContents) {
|
|
|
661
657
|
certainty: CERTAINTY.MEDIUM,
|
|
662
658
|
description: `${shotgunPattern.description}: ${v.files.length} files change together ${v.count} times`,
|
|
663
659
|
autoFix: shotgunPattern.autoFix,
|
|
664
|
-
content:
|
|
660
|
+
content: v.files.join(', ').substring(0, 100),
|
|
665
661
|
phase: 1,
|
|
666
662
|
details: { files: v.files, changeCount: v.count }
|
|
667
663
|
});
|
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
* @license MIT
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
const { truncate } = require('../cross-platform');
|
|
12
|
-
|
|
13
11
|
/**
|
|
14
12
|
* Analyze JSDoc-to-function ratio to detect excessive documentation
|
|
15
13
|
*
|
|
@@ -1881,7 +1879,7 @@ function analyzeDeadCode(content, options = {}) {
|
|
|
1881
1879
|
line: j + 1, // 1-indexed
|
|
1882
1880
|
terminationType: terminationType,
|
|
1883
1881
|
terminationLine: i + 1,
|
|
1884
|
-
content:
|
|
1882
|
+
content: nextTrimmed.substring(0, 50) + (nextTrimmed.length > 50 ? '...' : ''),
|
|
1885
1883
|
severity: 'high'
|
|
1886
1884
|
});
|
|
1887
1885
|
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const fs = require('fs');
|
|
14
|
-
const { truncate } = require('../cross-platform');
|
|
15
14
|
const path = require('path');
|
|
16
15
|
const { exec } = require('child_process');
|
|
17
16
|
const { promisify } = require('util');
|
|
@@ -85,7 +84,7 @@ function withTimeout(promise, timeoutMs = DEFAULT_ASYNC_TIMEOUT_MS, operation =
|
|
|
85
84
|
* @returns {Promise<{stdout: string, stderr: string}>}
|
|
86
85
|
*/
|
|
87
86
|
async function execWithTimeout(cmd, options = {}, timeoutMs = DEFAULT_ASYNC_TIMEOUT_MS) {
|
|
88
|
-
return withTimeout(execAsync(cmd, options), timeoutMs, `exec: ${
|
|
87
|
+
return withTimeout(execAsync(cmd, options), timeoutMs, `exec: ${cmd.substring(0, 50)}`);
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
// Maximum cached file size constant
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Typed wrappers over `agent-analyzer repo-intel query <type>` subcommands.
|
|
5
|
+
*
|
|
6
|
+
* Each function constructs the exact argv expected by the binary, delegates to
|
|
7
|
+
* `binary.runAnalyzer`, parses the JSON output, and returns the result.
|
|
8
|
+
*
|
|
9
|
+
* All functions throw `RepoIntelMissingError` when the repo-intel map file
|
|
10
|
+
* has not been generated yet (run `agentsys repo-intel update` first).
|
|
11
|
+
*
|
|
12
|
+
* @module lib/repo-intel/queries
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { getStateDir } = require('../platform/state-dir');
|
|
18
|
+
const binary = require('../binary');
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Error types
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Thrown when the repo-intel map file is absent.
|
|
26
|
+
*/
|
|
27
|
+
class RepoIntelMissingError extends Error {
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} mapFile - Expected path to the map file
|
|
30
|
+
*/
|
|
31
|
+
constructor(mapFile) {
|
|
32
|
+
super(
|
|
33
|
+
`repo-intel map not found at ${mapFile}. ` +
|
|
34
|
+
'Run `agentsys repo-intel update` to generate it first.'
|
|
35
|
+
);
|
|
36
|
+
this.name = 'RepoIntelMissingError';
|
|
37
|
+
this.code = 'REPO_INTEL_MISSING';
|
|
38
|
+
this.mapFile = mapFile;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Internal helpers
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
const MAP_FILE_NAME = 'repo-intel.json';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the map file path for a given project root.
|
|
50
|
+
* @param {string} cwd
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
function resolveMapFile(cwd) {
|
|
54
|
+
const stateDir = getStateDir(cwd);
|
|
55
|
+
return path.join(cwd, stateDir, MAP_FILE_NAME);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Assert the map file exists, throwing `RepoIntelMissingError` if not.
|
|
60
|
+
* @param {string} cwd
|
|
61
|
+
* @returns {string} resolved map file path
|
|
62
|
+
*/
|
|
63
|
+
function requireMapFile(cwd) {
|
|
64
|
+
const mapFile = resolveMapFile(cwd);
|
|
65
|
+
if (!fs.existsSync(mapFile)) {
|
|
66
|
+
throw new RepoIntelMissingError(mapFile);
|
|
67
|
+
}
|
|
68
|
+
return mapFile;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Run a repo-intel query and parse JSON output.
|
|
73
|
+
* @param {string} queryName - The subcommand name (e.g. 'hotspots')
|
|
74
|
+
* @param {string[]} extraArgs - Additional argv elements
|
|
75
|
+
* @param {string} cwd - Project root
|
|
76
|
+
* @returns {*} Parsed JSON result
|
|
77
|
+
*/
|
|
78
|
+
function runQuery(queryName, extraArgs, cwd) {
|
|
79
|
+
const mapFile = requireMapFile(cwd);
|
|
80
|
+
const args = ['repo-intel', 'query', queryName, ...extraArgs, '--map-file', mapFile, cwd];
|
|
81
|
+
let raw;
|
|
82
|
+
try {
|
|
83
|
+
raw = binary.runAnalyzer(args);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`repo-intel query failed [${queryName}]: ${err.message}`,
|
|
87
|
+
{ cause: err }
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
let parsed;
|
|
91
|
+
try {
|
|
92
|
+
parsed = JSON.parse(raw);
|
|
93
|
+
} catch (_parseErr) {
|
|
94
|
+
const preview = raw.slice(0, 200);
|
|
95
|
+
throw new Error(
|
|
96
|
+
`repo-intel query [${queryName}] returned non-JSON output: ${preview}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return parsed;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Query functions
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Hotspot files by change frequency.
|
|
108
|
+
* @param {string} cwd
|
|
109
|
+
* @param {{ limit?: number }} [opts]
|
|
110
|
+
* @returns {Array}
|
|
111
|
+
*/
|
|
112
|
+
function hotspots(cwd, opts = {}) {
|
|
113
|
+
const extra = [];
|
|
114
|
+
if (opts.limit != null) extra.push('--top', String(opts.limit));
|
|
115
|
+
return runQuery('hotspots', extra, cwd);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Co-change coupling for a file.
|
|
120
|
+
* @param {string} cwd
|
|
121
|
+
* @param {string} file
|
|
122
|
+
* @param {{ limit?: number }} [opts]
|
|
123
|
+
* @returns {Array}
|
|
124
|
+
*/
|
|
125
|
+
function coupling(cwd, file, opts = {}) {
|
|
126
|
+
const extra = [file];
|
|
127
|
+
if (opts.limit != null) extra.push('--top', String(opts.limit));
|
|
128
|
+
return runQuery('coupling', extra, cwd);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Bus factor report.
|
|
133
|
+
* @param {string} cwd
|
|
134
|
+
* @param {{ adjustForAi?: boolean, limit?: number }} [opts]
|
|
135
|
+
* @returns {Object}
|
|
136
|
+
*/
|
|
137
|
+
function busFactor(cwd, opts = {}) {
|
|
138
|
+
const extra = [];
|
|
139
|
+
if (opts.adjustForAi) extra.push('--adjust-for-ai');
|
|
140
|
+
if (opts.limit != null) extra.push('--top', String(opts.limit));
|
|
141
|
+
return runQuery('bus-factor', extra, cwd);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Files with low test coverage relative to change frequency.
|
|
146
|
+
* @param {string} cwd
|
|
147
|
+
* @param {{ limit?: number, minChanges?: number }} [opts]
|
|
148
|
+
* @returns {Array}
|
|
149
|
+
*/
|
|
150
|
+
function testGaps(cwd, opts = {}) {
|
|
151
|
+
const extra = [];
|
|
152
|
+
if (opts.limit != null) extra.push('--top', String(opts.limit));
|
|
153
|
+
if (opts.minChanges != null) extra.push('--min-changes', String(opts.minChanges));
|
|
154
|
+
return runQuery('test-gaps', extra, cwd);
|
|
155
|
+
}
|
|
156
|
+
|
|
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
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Risk score for a diff (set of changed files).
|
|
171
|
+
* @param {string} cwd
|
|
172
|
+
* @param {string[]} files
|
|
173
|
+
* @returns {Array}
|
|
174
|
+
*/
|
|
175
|
+
function diffRisk(cwd, files) {
|
|
176
|
+
if (!Array.isArray(files)) {
|
|
177
|
+
throw new TypeError('diffRisk: files must be an array of strings');
|
|
178
|
+
}
|
|
179
|
+
if (!files.every(f => typeof f === 'string')) {
|
|
180
|
+
throw new TypeError('diffRisk: all entries in files must be strings');
|
|
181
|
+
}
|
|
182
|
+
const joined = files.join(',');
|
|
183
|
+
if (joined.length > 30000) {
|
|
184
|
+
throw new RangeError(
|
|
185
|
+
`diffRisk: files argument exceeds 30000 character limit (got ${joined.length})`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
const extra = ['--files', joined];
|
|
189
|
+
return runQuery('diff-risk', extra, cwd);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Call-graph dependents of a symbol.
|
|
194
|
+
* @param {string} cwd
|
|
195
|
+
* @param {string} symbol
|
|
196
|
+
* @param {string} [file] - Scope to a specific file
|
|
197
|
+
* @returns {Object}
|
|
198
|
+
*/
|
|
199
|
+
function dependents(cwd, symbol, file) {
|
|
200
|
+
const extra = [symbol];
|
|
201
|
+
if (file != null) extra.push('--file', file);
|
|
202
|
+
return runQuery('dependents', extra, cwd);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Bugspot risk scores.
|
|
207
|
+
* @param {string} cwd
|
|
208
|
+
* @param {{ limit?: number }} [opts]
|
|
209
|
+
* @returns {Array}
|
|
210
|
+
*/
|
|
211
|
+
function bugspots(cwd, opts = {}) {
|
|
212
|
+
const extra = [];
|
|
213
|
+
if (opts.limit != null) extra.push('--top', String(opts.limit));
|
|
214
|
+
return runQuery('bugspots', extra, cwd);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Project health summary.
|
|
219
|
+
* @param {string} cwd
|
|
220
|
+
* @returns {Object}
|
|
221
|
+
*/
|
|
222
|
+
function health(cwd) {
|
|
223
|
+
return runQuery('health', [], cwd);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
// Graph queries (Phase 5.1 - agent-analyzer v0.4.0+)
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Louvain-discovered file communities.
|
|
232
|
+
* @param {string} cwd
|
|
233
|
+
* @returns {Array}
|
|
234
|
+
*/
|
|
235
|
+
function communities(cwd) {
|
|
236
|
+
return runQuery('communities', [], cwd);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Files bridging multiple communities (architectural seams).
|
|
241
|
+
* @param {string} cwd
|
|
242
|
+
* @param {{ limit?: number }} [opts]
|
|
243
|
+
* @returns {Array}
|
|
244
|
+
*/
|
|
245
|
+
function boundaries(cwd, opts = {}) {
|
|
246
|
+
const extra = [];
|
|
247
|
+
if (opts.limit != null) extra.push('--top', String(opts.limit));
|
|
248
|
+
return runQuery('boundaries', extra, cwd);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Which community a file belongs to.
|
|
253
|
+
* @param {string} cwd
|
|
254
|
+
* @param {string} file
|
|
255
|
+
* @returns {Object}
|
|
256
|
+
*/
|
|
257
|
+
function areaOf(cwd, file) {
|
|
258
|
+
return runQuery('area-of', [file], cwd);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Composite health roll-up for a community.
|
|
263
|
+
* @param {string} cwd
|
|
264
|
+
* @param {number} id - Community id (non-negative integer)
|
|
265
|
+
* @returns {Object}
|
|
266
|
+
*/
|
|
267
|
+
function communityHealth(cwd, id) {
|
|
268
|
+
if (typeof id !== 'number' || !Number.isInteger(id) || id < 0) {
|
|
269
|
+
throw new TypeError(
|
|
270
|
+
'communityHealth: id must be a non-negative integer'
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
return runQuery('community-health', [String(id)], cwd);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// Exports
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
module.exports = {
|
|
281
|
+
// Error class
|
|
282
|
+
RepoIntelMissingError,
|
|
283
|
+
|
|
284
|
+
// Core queries
|
|
285
|
+
hotspots,
|
|
286
|
+
coupling,
|
|
287
|
+
busFactor,
|
|
288
|
+
testGaps,
|
|
289
|
+
aiRatio,
|
|
290
|
+
diffRisk,
|
|
291
|
+
dependents,
|
|
292
|
+
bugspots,
|
|
293
|
+
health,
|
|
294
|
+
|
|
295
|
+
// Graph queries
|
|
296
|
+
communities,
|
|
297
|
+
boundaries,
|
|
298
|
+
areaOf,
|
|
299
|
+
communityHealth,
|
|
300
|
+
};
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const sourceCache = require('./source-cache');
|
|
9
|
-
const { truncate } = require('../cross-platform');
|
|
10
9
|
const customHandler = require('./custom-handler');
|
|
11
10
|
|
|
12
11
|
/**
|
|
@@ -43,7 +42,7 @@ function getPolicyQuestions() {
|
|
|
43
42
|
// Truncate to fit within 30 chars: "X (last used)" where X can be max 17 chars
|
|
44
43
|
const maxBaseLen = 30 - ' (last used)'.length; // 18 chars for base
|
|
45
44
|
const truncatedLabel = cachedLabel.length > maxBaseLen
|
|
46
|
-
?
|
|
45
|
+
? cachedLabel.substring(0, maxBaseLen - 1) + '…'
|
|
47
46
|
: cachedLabel;
|
|
48
47
|
|
|
49
48
|
sourceOptions.push({
|
|
@@ -156,12 +155,12 @@ function parseAndCachePolicy(responses) {
|
|
|
156
155
|
// Validate and merge follow-up responses
|
|
157
156
|
const rawNum = String(responses.project.number).trim();
|
|
158
157
|
if (!/^[1-9][0-9]*$/.test(rawNum)) {
|
|
159
|
-
const safeNum =
|
|
158
|
+
const safeNum = String(responses.project.number).replace(/[^\x20-\x7E]/g, '?').substring(0, 32);
|
|
160
159
|
throw new Error(`Invalid project number: "${safeNum}". Must be a positive integer.`);
|
|
161
160
|
}
|
|
162
161
|
const num = Number(rawNum);
|
|
163
162
|
const owner = String(responses.project.owner || '').trim();
|
|
164
|
-
const safeOwner =
|
|
163
|
+
const safeOwner = String(responses.project.owner || '').replace(/[^\x20-\x7E]/g, '?').substring(0, 64);
|
|
165
164
|
if (!owner || !/^(@me|[a-zA-Z0-9][a-zA-Z0-9_-]*)$/.test(owner)) {
|
|
166
165
|
throw new Error(`Invalid project owner: "${safeOwner}" (use @me or an org/user name)`);
|
|
167
166
|
}
|