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.
@@ -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: truncate(dup.fragment || '', 100)
383
+ fragment: dup.fragment?.substring(0, 100) || ''
388
384
  });
389
385
  }
390
386
  }
@@ -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: truncate(line.trim(), 100),
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: truncate(v.files.join(', '), 100),
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: truncate(nextTrimmed, 50),
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: ${truncate(cmd, 50)}`);
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
- ? truncate(cachedLabel, maxBaseLen - 1) + '…'
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 = truncate(String(responses.project.number).replace(/[^\x20-\x7E]/g, '?'), 32);
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 = truncate(String(responses.project.owner || '').replace(/[^\x20-\x7E]/g, '?'), 64);
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
  }