agentsys 5.8.5 → 5.8.6

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agentsys",
3
3
  "description": "19 specialized plugins for AI workflow automation - task orchestration, PR workflow, slop detection, code review, drift detection, enhancement analysis, documentation sync, unified static analysis, perf investigations, topic research, agent config linting, cross-tool AI consultation, structured AI debate, workflow pattern learning, codebase onboarding, and contributor guidance",
4
- "version": "5.8.5",
4
+ "version": "5.8.6",
5
5
  "owner": {
6
6
  "name": "Avi Fenesh",
7
7
  "url": "https://github.com/avifenesh"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentsys",
3
- "version": "5.8.5",
3
+ "version": "5.8.6",
4
4
  "description": "Professional-grade slash commands for Claude Code with cross-platform support",
5
5
  "keywords": [
6
6
  "workflow",
package/CHANGELOG.md CHANGED
@@ -9,6 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [5.8.6] - 2026-04-23
13
+
14
+ ### Added
15
+ - **`@agentsys/lib`'s `repoIntel.queries` module** - typed wrappers over every `agent-analyzer repo-intel query <type>` subcommand (28 functions). Consumer plugins can now call `require('@agentsys/lib').repoIntel.queries.hotspots(cwd, { limit: 20 })` instead of constructing raw CLI argv themselves. Functions returned in JSON match the binary's output shape per query.
16
+ - **4 new graph-derived query wrappers** for the analyzer-graph crate landed in agent-analyzer v0.4.0:
17
+ - `communities(cwd)` - lists Louvain-discovered file clusters (the natural feature areas, independent of directory layout)
18
+ - `boundaries(cwd, { limit })` - files bridging multiple communities by betweenness centrality (architectural seams - highest-leverage files for refactoring)
19
+ - `areaOf(cwd, file)` - which community a file belongs to
20
+ - `communityHealth(cwd, id)` - composite per-community roll-up (size, total/recent changes, bug-fix rate, AI ratio, stale-owner count)
21
+
22
+ ### Changed
23
+ - **`ANALYZER_MIN_VERSION` bumped 0.3.0 -> 0.4.0** to match agent-analyzer v0.4.0 which adds the graph subcommands. Older binaries get auto-upgraded on first call by `lib/binary.ensureBinary()`.
24
+
12
25
  ## [5.8.5] - 2026-04-23
13
26
 
14
27
  ### Fixed
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  // Minimum binary version required by this version of agent-core
4
- const ANALYZER_MIN_VERSION = '0.3.0';
4
+ const ANALYZER_MIN_VERSION = '0.4.0';
5
5
 
6
6
  // Binary name
7
7
  const BINARY_NAME = 'agent-analyzer';
package/lib/index.js CHANGED
@@ -30,6 +30,7 @@ const perf = require('./perf');
30
30
  const collectors = require('./collectors');
31
31
  const discoveryModule = require('./discovery');
32
32
  const binary = require('./binary');
33
+ const repoIntel = require('./repo-intel');
33
34
 
34
35
  /**
35
36
  * Platform detection and verification utilities
@@ -255,6 +256,7 @@ module.exports = {
255
256
  collectors,
256
257
  discovery,
257
258
  binary,
259
+ repoIntel,
258
260
 
259
261
  // Direct module access for backward compatibility
260
262
  detectPlatform,
package/lib/package.json CHANGED
@@ -13,6 +13,8 @@
13
13
  "./discovery": "./discovery/index.js",
14
14
  "./enhance": "./enhance/index.js",
15
15
  "./perf": "./perf/index.js",
16
+ "./repo-intel": "./repo-intel/index.js",
17
+ "./repo-intel/queries": "./repo-intel/queries.js",
16
18
  "./repo-map": "./repo-map/index.js",
17
19
  "./collectors/codebase": "./collectors/codebase.js",
18
20
  "./collectors/docs-patterns": "./collectors/docs-patterns.js",
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Repo intel module - typed wrappers over agent-analyzer's repo-intel
3
+ * subcommands. Consumers can import via:
4
+ *
5
+ * const { repoIntel } = require('@agentsys/lib');
6
+ * const hot = repoIntel.queries.hotspots(cwd, { limit: 20 });
7
+ * const communities = repoIntel.queries.communities(cwd);
8
+ *
9
+ * The Rust binary is downloaded lazily on first call by lib/binary; this
10
+ * module just constructs the right argv and parses the JSON output.
11
+ *
12
+ * @module lib/repo-intel
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ const queries = require('./queries');
18
+
19
+ module.exports = {
20
+ queries,
21
+ };
@@ -0,0 +1,451 @@
1
+ /**
2
+ * Repo intel query functions
3
+ *
4
+ * Typed wrappers over `agent-analyzer repo-intel query <type>` subcommands.
5
+ * Consumer plugins can call these instead of constructing CLI args by hand:
6
+ *
7
+ * const { repoIntel } = require('@agentsys/lib');
8
+ * const hot = repoIntel.queries.hotspots(cwd, { limit: 20 });
9
+ *
10
+ * Each function resolves the cached `repo-intel.json` via the platform
11
+ * state-dir helper and shells out to the binary downloaded by lib/binary.
12
+ *
13
+ * @module lib/repo-intel/queries
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const binary = require('../binary');
21
+ const { getStateDirPath } = require('../platform/state-dir');
22
+
23
+ const MAP_FILENAME = 'repo-intel.json';
24
+
25
+ // Conservative bound on a single CLI argument length. Windows cmd-line max
26
+ // is ~32 KB total; modern Linux/macOS typically allow ~128 KB. We throw an
27
+ // actionable error rather than letting `execFileSync` fail with a cryptic
28
+ // E2BIG/ENAMETOOLONG, which would otherwise look like a binary crash to
29
+ // the caller.
30
+ const MAX_FILES_ARG_LEN = 30000;
31
+
32
+ /**
33
+ * Absolute path to the cached repo-intel artifact for `basePath`.
34
+ *
35
+ * @param {string} basePath
36
+ * @returns {string}
37
+ */
38
+ function mapFilePath(basePath) {
39
+ return path.join(getStateDirPath(basePath), MAP_FILENAME);
40
+ }
41
+
42
+ /**
43
+ * Error thrown when the cached repo-intel artifact is missing.
44
+ * Distinguished by a `.code = 'REPO_INTEL_MISSING'` field so callers can
45
+ * choose between auto-init, fallback, or surfacing the message.
46
+ */
47
+ class RepoIntelMissingError extends Error {
48
+ constructor(mapFile) {
49
+ super(
50
+ `repo-intel artifact not found at ${mapFile}. Run ` +
51
+ '`agent-analyzer repo-intel init <path>` (or `/repo-intel init` ' +
52
+ 'in a CC plugin) first to create it.'
53
+ );
54
+ this.name = 'RepoIntelMissingError';
55
+ this.code = 'REPO_INTEL_MISSING';
56
+ this.mapFile = mapFile;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Run a binary query and return the parsed JSON result.
62
+ *
63
+ * @param {string} basePath - Repository root
64
+ * @param {string[]} queryArgs - Arguments after `repo-intel query`
65
+ * @returns {Object|Array} Parsed query result
66
+ * @throws {RepoIntelMissingError} If the cached artifact is missing.
67
+ * @throws {Error} If the binary fails or returns non-JSON output (with
68
+ * the failing query and an output preview included in the message).
69
+ */
70
+ function runQuery(basePath, queryArgs) {
71
+ const mapFile = mapFilePath(basePath);
72
+
73
+ // Surface a clear "run init first" message instead of letting the binary
74
+ // exit non-zero with a low-level "no such file" error that would bubble
75
+ // up unannotated through `binary.runAnalyzer`.
76
+ if (!fs.existsSync(mapFile)) {
77
+ throw new RepoIntelMissingError(mapFile);
78
+ }
79
+
80
+ const args = ['repo-intel', 'query', ...queryArgs, '--map-file', mapFile, basePath];
81
+
82
+ let output;
83
+ try {
84
+ output = binary.runAnalyzer(args);
85
+ } catch (err) {
86
+ // Wrap so the caller learns which query failed without having to dig
87
+ // through the CLI argv. The original error stays as `cause`.
88
+ const wrapped = new Error(
89
+ `repo-intel query failed (${queryArgs.join(' ')}): ${err.message}`
90
+ );
91
+ wrapped.cause = err;
92
+ throw wrapped;
93
+ }
94
+
95
+ try {
96
+ return JSON.parse(output);
97
+ } catch (err) {
98
+ const preview = output && output.length > 0 ? output.slice(0, 200) : '<empty>';
99
+ const wrapped = new Error(
100
+ `repo-intel query returned non-JSON output (${queryArgs.join(' ')}): ${preview}`
101
+ );
102
+ wrapped.cause = err;
103
+ throw wrapped;
104
+ }
105
+ }
106
+
107
+ // ─── Activity ───────────────────────────────────────────────────────────────
108
+
109
+ /**
110
+ * Return files sorted by recency-weighted change score.
111
+ *
112
+ * @param {string} basePath
113
+ * @param {Object} [options={}]
114
+ * @param {number} [options.limit] - Maximum number of results
115
+ */
116
+ function hotspots(basePath, options = {}) {
117
+ const args = ['hotspots'];
118
+ if (options.limit) args.push('--top', String(options.limit));
119
+ return runQuery(basePath, args);
120
+ }
121
+
122
+ /**
123
+ * Return least-changed files (no recent activity).
124
+ */
125
+ function coldspots(basePath, options = {}) {
126
+ const args = ['coldspots'];
127
+ if (options.limit) args.push('--top', String(options.limit));
128
+ return runQuery(basePath, args);
129
+ }
130
+
131
+ /**
132
+ * Return change history for a specific file.
133
+ */
134
+ function fileHistory(basePath, file) {
135
+ return runQuery(basePath, ['file-history', file]);
136
+ }
137
+
138
+ // ─── Quality ────────────────────────────────────────────────────────────────
139
+
140
+ /**
141
+ * Return files with highest bug-fix density.
142
+ */
143
+ function bugspots(basePath, options = {}) {
144
+ const args = ['bugspots'];
145
+ if (options.limit) args.push('--top', String(options.limit));
146
+ return runQuery(basePath, args);
147
+ }
148
+
149
+ /**
150
+ * Return hot source files with no co-changing test file.
151
+ */
152
+ function testGaps(basePath, options = {}) {
153
+ const args = ['test-gaps'];
154
+ if (options.limit) args.push('--top', String(options.limit));
155
+ if (options.minChanges) args.push('--min-changes', String(options.minChanges));
156
+ return runQuery(basePath, args);
157
+ }
158
+
159
+ /**
160
+ * Score changed files by composite risk.
161
+ *
162
+ * Validates the joined file argument fits within the platform's argv length
163
+ * cap before shelling out (Windows is the tight constraint at ~32 KB).
164
+ * Callers with very large diffs should batch.
165
+ *
166
+ * @param {string} basePath
167
+ * @param {string[]} files - List of changed file paths
168
+ * @throws {TypeError} If `files` is not an array of strings.
169
+ * @throws {RangeError} If the joined argument exceeds {@link MAX_FILES_ARG_LEN}.
170
+ */
171
+ function diffRisk(basePath, files) {
172
+ if (!Array.isArray(files) || !files.every((f) => typeof f === 'string')) {
173
+ throw new TypeError('diffRisk: `files` must be an array of strings');
174
+ }
175
+ const joined = files.join(',');
176
+ if (joined.length > MAX_FILES_ARG_LEN) {
177
+ throw new RangeError(
178
+ `diffRisk: joined file argument is ${joined.length} chars, ` +
179
+ `exceeds platform-safe limit of ${MAX_FILES_ARG_LEN}. ` +
180
+ `Split the request into batches (~500 paths each is typically safe).`
181
+ );
182
+ }
183
+ return runQuery(basePath, ['diff-risk', '--files', joined]);
184
+ }
185
+
186
+ /**
187
+ * Files ranked by hotspot × (1 + bug_rate) × (1 + complexity/30). Requires
188
+ * Phase 2 AST data; falls back to git-only when unavailable.
189
+ */
190
+ function painspots(basePath, options = {}) {
191
+ const args = ['painspots'];
192
+ if (options.limit) args.push('--top', String(options.limit));
193
+ return runQuery(basePath, args);
194
+ }
195
+
196
+ // ─── People ─────────────────────────────────────────────────────────────────
197
+
198
+ /**
199
+ * Return ownership breakdown for a file or directory.
200
+ */
201
+ function ownership(basePath, file) {
202
+ return runQuery(basePath, ['ownership', file]);
203
+ }
204
+
205
+ /**
206
+ * Return contributors sorted by commit count.
207
+ */
208
+ function contributors(basePath, options = {}) {
209
+ const args = ['contributors'];
210
+ if (options.limit) args.push('--top', String(options.limit));
211
+ return runQuery(basePath, args);
212
+ }
213
+
214
+ /**
215
+ * Detailed bus factor with critical owners and at-risk areas.
216
+ */
217
+ function busFactor(basePath, options = {}) {
218
+ const args = ['bus-factor'];
219
+ if (options.adjustForAi) args.push('--adjust-for-ai');
220
+ return runQuery(basePath, args);
221
+ }
222
+
223
+ // ─── Coupling ───────────────────────────────────────────────────────────────
224
+
225
+ /**
226
+ * Files that frequently change together with `file`.
227
+ */
228
+ function coupling(basePath, file) {
229
+ return runQuery(basePath, ['coupling', file]);
230
+ }
231
+
232
+ // ─── Standards ──────────────────────────────────────────────────────────────
233
+
234
+ /**
235
+ * Project norms (commit conventions, etc.) detected from git history.
236
+ */
237
+ function norms(basePath) {
238
+ return runQuery(basePath, ['norms']);
239
+ }
240
+
241
+ /**
242
+ * Commit message style + prefixes + scope usage.
243
+ */
244
+ function conventions(basePath) {
245
+ return runQuery(basePath, ['conventions']);
246
+ }
247
+
248
+ // ─── Health ─────────────────────────────────────────────────────────────────
249
+
250
+ /**
251
+ * Directory-level health overview.
252
+ */
253
+ function areas(basePath) {
254
+ return runQuery(basePath, ['areas']);
255
+ }
256
+
257
+ /**
258
+ * Repository-wide health summary.
259
+ */
260
+ function health(basePath) {
261
+ return runQuery(basePath, ['health']);
262
+ }
263
+
264
+ /**
265
+ * Release cadence and tag history.
266
+ */
267
+ function releaseInfo(basePath) {
268
+ return runQuery(basePath, ['release-info']);
269
+ }
270
+
271
+ // ─── AI detection ───────────────────────────────────────────────────────────
272
+
273
+ /**
274
+ * AI vs human contribution ratio.
275
+ */
276
+ function aiRatio(basePath, options = {}) {
277
+ const args = ['ai-ratio'];
278
+ if (options.pathFilter) args.push('--path-filter', options.pathFilter);
279
+ return runQuery(basePath, args);
280
+ }
281
+
282
+ /**
283
+ * Files with recent AI-authored changes.
284
+ */
285
+ function recentAi(basePath, options = {}) {
286
+ const args = ['recent-ai'];
287
+ if (options.limit) args.push('--top', String(options.limit));
288
+ return runQuery(basePath, args);
289
+ }
290
+
291
+ // ─── Contributor guidance ───────────────────────────────────────────────────
292
+
293
+ /**
294
+ * Newcomer-oriented repo summary (tech stack, key areas, pain points).
295
+ */
296
+ function onboard(basePath) {
297
+ return runQuery(basePath, ['onboard']);
298
+ }
299
+
300
+ /**
301
+ * Contribution guidance: good-first areas, test gaps, doc drift, bugspots.
302
+ */
303
+ function canIHelp(basePath) {
304
+ return runQuery(basePath, ['can-i-help']);
305
+ }
306
+
307
+ // ─── Documentation ──────────────────────────────────────────────────────────
308
+
309
+ /**
310
+ * Doc files with low code coupling (likely stale).
311
+ */
312
+ function docDrift(basePath, options = {}) {
313
+ const args = ['doc-drift'];
314
+ if (options.limit) args.push('--top', String(options.limit));
315
+ return runQuery(basePath, args);
316
+ }
317
+
318
+ /**
319
+ * Doc files with stale references to source symbols. Requires Phase 4
320
+ * sync-check data.
321
+ */
322
+ function staleDocs(basePath, options = {}) {
323
+ const args = ['stale-docs'];
324
+ if (options.limit) args.push('--top', String(options.limit));
325
+ return runQuery(basePath, args);
326
+ }
327
+
328
+ // ─── AST symbols ────────────────────────────────────────────────────────────
329
+
330
+ /**
331
+ * AST symbols (exports, imports, definitions) for a specific file. Requires
332
+ * Phase 2 AST data.
333
+ */
334
+ function symbols(basePath, file) {
335
+ return runQuery(basePath, ['symbols', file]);
336
+ }
337
+
338
+ /**
339
+ * Files that import a given symbol (reverse dependency lookup). Requires
340
+ * Phase 2 AST data.
341
+ */
342
+ function dependents(basePath, symbol, file) {
343
+ const args = ['dependents', symbol];
344
+ if (file) args.push('--file', file);
345
+ return runQuery(basePath, args);
346
+ }
347
+
348
+ // ─── Phase 5: Graph-derived (analyzer-graph crate) ──────────────────────────
349
+
350
+ /**
351
+ * Communities discovered by Louvain modularity over the co-change graph.
352
+ * Returns clusters of files that consistently change together - the natural
353
+ * feature areas, independent of directory layout. Requires agent-analyzer
354
+ * v0.4.0+.
355
+ *
356
+ * @param {string} basePath
357
+ * @returns {Array<{id: number, size: number, files: string[]}>}
358
+ */
359
+ function communities(basePath) {
360
+ return runQuery(basePath, ['communities']);
361
+ }
362
+
363
+ /**
364
+ * Files bridging multiple communities (high betweenness centrality). These
365
+ * are the architectural seams - the highest-leverage files for refactoring
366
+ * decisions. Requires agent-analyzer v0.4.0+.
367
+ *
368
+ * @param {string} basePath
369
+ * @param {Object} [options={}]
370
+ * @param {number} [options.limit] - Maximum number of results
371
+ * @returns {Array<{path: string, betweenness: number, community: number|null}>}
372
+ */
373
+ function boundaries(basePath, options = {}) {
374
+ const args = ['boundaries'];
375
+ if (options.limit) args.push('--top', String(options.limit));
376
+ return runQuery(basePath, args);
377
+ }
378
+
379
+ /**
380
+ * Look up which community a given file belongs to. Requires agent-analyzer
381
+ * v0.4.0+.
382
+ *
383
+ * @param {string} basePath
384
+ * @param {string} file - File path (relative to repo root)
385
+ * @returns {{file: string, community: number|null, size: number|null}}
386
+ */
387
+ function areaOf(basePath, file) {
388
+ return runQuery(basePath, ['area-of', file]);
389
+ }
390
+
391
+ /**
392
+ * Composite per-community health: total/recent changes, bug-fix rate,
393
+ * AI ratio, stale-owner count. Use to identify communities under stress
394
+ * (high bug rate or stale ownership). Requires agent-analyzer v0.4.0+.
395
+ *
396
+ * @param {string} basePath
397
+ * @param {number} id - Community id (from `communities()`)
398
+ * @returns {Object|null}
399
+ */
400
+ function communityHealth(basePath, id) {
401
+ if (!Number.isInteger(id) || id < 0) {
402
+ throw new TypeError(
403
+ `communityHealth: \`id\` must be a non-negative integer, got: ${id} (${typeof id})`
404
+ );
405
+ }
406
+ return runQuery(basePath, ['community-health', String(id)]);
407
+ }
408
+
409
+ module.exports = {
410
+ // Errors
411
+ RepoIntelMissingError,
412
+ // Activity
413
+ hotspots,
414
+ coldspots,
415
+ fileHistory,
416
+ // Quality
417
+ bugspots,
418
+ testGaps,
419
+ diffRisk,
420
+ painspots,
421
+ // People
422
+ ownership,
423
+ contributors,
424
+ busFactor,
425
+ // Coupling
426
+ coupling,
427
+ // Standards
428
+ norms,
429
+ conventions,
430
+ // Health
431
+ areas,
432
+ health,
433
+ releaseInfo,
434
+ // AI detection
435
+ aiRatio,
436
+ recentAi,
437
+ // Contributor guidance
438
+ onboard,
439
+ canIHelp,
440
+ // Documentation
441
+ docDrift,
442
+ staleDocs,
443
+ // AST symbols
444
+ symbols,
445
+ dependents,
446
+ // Phase 5: graph-derived
447
+ communities,
448
+ boundaries,
449
+ areaOf,
450
+ communityHealth,
451
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentsys",
3
- "version": "5.8.5",
3
+ "version": "5.8.6",
4
4
  "description": "A modular runtime and orchestration system for AI agents - works with Claude Code, OpenCode, and Codex CLI",
5
5
  "main": "lib/platform/detect-platform.js",
6
6
  "type": "commonjs",
package/site/content.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "https://agent-sh.github.io/agentsys",
6
6
  "repo": "https://github.com/agent-sh/agentsys",
7
7
  "npm": "https://www.npmjs.com/package/agentsys",
8
- "version": "5.8.5",
8
+ "version": "5.8.6",
9
9
  "author": "Avi Fenesh",
10
10
  "author_url": "https://github.com/avifenesh"
11
11
  },