@codragraph/cli 2.1.4 → 2.1.5

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/README.md CHANGED
@@ -25,6 +25,13 @@ That's it. This indexes the codebase, installs agent skills, registers Claude Co
25
25
 
26
26
  The same CLI commands work in Windows PowerShell, macOS bash/zsh, and Linux shells. Use `npx @codragraph/cli ...` for no-install runs or `codragraph ...` after a global install.
27
27
 
28
+ Smart analyze rebuilds when indexed source, Markdown/MDX graph docs, language
29
+ config, schema, compression, or requested embedding settings changed. Generated
30
+ agent context, lockfile-only, and ignored asset changes reuse the existing
31
+ graph and advance metadata. Each pass refreshes `.codragraph/structure/`, a
32
+ compact what/why/how/when/where markdown pack with branch/index state, bounded
33
+ history, and SQLite seed SQL for external agent memory.
34
+
28
35
  To configure MCP for your editor, run `npx @codragraph/cli setup` once — or set it up manually below.
29
36
 
30
37
  `codragraph setup` auto-detects your editors and writes the correct global MCP config. You only need to run it once.
@@ -55,16 +62,16 @@ If you prefer to configure manually instead of using `codragraph setup`:
55
62
 
56
63
  ```bash
57
64
  # macOS / Linux
58
- claude mcp add codragraph -- npx -y @codragraph/cli@2.1.2 mcp
65
+ claude mcp add codragraph -- npx -y @codragraph/cli@2.1.5 mcp
59
66
 
60
67
  # Windows
61
- claude mcp add codragraph -- cmd /c npx -y @codragraph/cli@2.1.2 mcp
68
+ claude mcp add codragraph -- cmd /c npx -y @codragraph/cli@2.1.5 mcp
62
69
  ```
63
70
 
64
71
  ### Codex (full support — MCP + skills)
65
72
 
66
73
  ```bash
67
- codex mcp add codragraph -- npx -y @codragraph/cli@2.1.2 mcp
74
+ codex mcp add codragraph -- npx -y @codragraph/cli@2.1.5 mcp
68
75
  ```
69
76
 
70
77
  ### Cursor / Windsurf
@@ -76,7 +83,7 @@ Add to `~/.cursor/mcp.json` (global — works for all projects):
76
83
  "mcpServers": {
77
84
  "codragraph": {
78
85
  "command": "npx",
79
- "args": ["-y", "@codragraph/cli@2.1.2", "mcp"]
86
+ "args": ["-y", "@codragraph/cli@2.1.5", "mcp"]
80
87
  }
81
88
  }
82
89
  }
@@ -91,7 +98,7 @@ Add to `~/.config/opencode/config.json`:
91
98
  "mcp": {
92
99
  "codragraph": {
93
100
  "command": "npx",
94
- "args": ["-y", "@codragraph/cli@2.1.2", "mcp"]
101
+ "args": ["-y", "@codragraph/cli@2.1.5", "mcp"]
95
102
  }
96
103
  }
97
104
  }
@@ -297,9 +304,9 @@ It is fixed in **codragraph v1.6.2+**. Upgrade to the current workspace
297
304
  version, or pin the version your team has validated:
298
305
 
299
306
  ```bash
300
- npx @codragraph/cli@2.1.2 analyze # no global install
307
+ npx @codragraph/cli@2.1.5 analyze # no global install
301
308
  # or
302
- npm install -g @codragraph/cli@2.1.2 # upgrade a global install
309
+ npm install -g @codragraph/cli@2.1.5 # upgrade a global install
303
310
  ```
304
311
 
305
312
  If you still hit npm install issues after upgrading, these generic workarounds
@@ -342,6 +349,23 @@ echo "vendor/" >> .codragraphignore
342
349
  echo "dist/" >> .codragraphignore
343
350
  ```
344
351
 
352
+ If the analyzer reports a worker sub-batch timeout or falls back to sequential
353
+ parsing while files are still being processed, reduce worker batch memory and
354
+ allow a longer idle window:
355
+
356
+ ```bash
357
+ # macOS/Linux bash/zsh
358
+ CODRAGRAPH_WORKER_SUB_BATCH_SIZE=100 CODRAGRAPH_WORKER_IDLE_TIMEOUT_MS=180000 npx @codragraph/cli analyze
359
+
360
+ # Windows PowerShell
361
+ $env:CODRAGRAPH_WORKER_SUB_BATCH_SIZE = "100"
362
+ $env:CODRAGRAPH_WORKER_IDLE_TIMEOUT_MS = "180000"
363
+ npx @codragraph/cli analyze
364
+ ```
365
+
366
+ The worker timer is an idle guard. Parser progress resets it, so a slow repo
367
+ should keep moving instead of paying the old 30-second wall-clock fallback.
368
+
345
369
  If you want to know **which phase** is dragging the heap up before
346
370
  deciding what to mitigate, run `codragraph profile-heap`. It writes a
347
371
  v8 heap snapshot at every phase boundary plus a JSONL timeline of
@@ -8,11 +8,14 @@
8
8
  import fs from 'fs/promises';
9
9
  import path from 'path';
10
10
  import { fileURLToPath } from 'url';
11
+ import { execFileSync } from 'node:child_process';
11
12
  // ESM equivalent of __dirname
12
13
  const __filename = fileURLToPath(import.meta.url);
13
14
  const __dirname = path.dirname(__filename);
14
15
  const CODRAGRAPH_START_MARKER = '<!-- codragraph:start -->';
15
16
  const CODRAGRAPH_END_MARKER = '<!-- codragraph:end -->';
17
+ const AGENT_STRUCTURE_DIR = 'structure';
18
+ const AGENT_HISTORY_LIMIT = 20;
16
19
  /**
17
20
  * Find the index of a section marker that occupies its own line.
18
21
  * Unlike `indexOf`, this rejects inline prose references like
@@ -113,6 +116,7 @@ This project is indexed by CodraGraph as **${projectName}**${noStats ? '' : ` ($
113
116
  | \`codragraph://repo/${projectName}/feature/{name}\` | Focused files, line ranges, flows, dependencies |
114
117
  | \`codragraph://repo/${projectName}/processes\` | All execution flows |
115
118
  | \`codragraph://repo/${projectName}/process/{name}\` | Step-by-step execution trace |
119
+ | \`.codragraph/structure/README.md\` | Local what/why/how/when/where memory, branch state, and SQLite seed |
116
120
 
117
121
  ${groupNames && groupNames.length > 0
118
122
  ? `## Cross-Repo Groups
@@ -174,6 +178,292 @@ async function upsertCodraGraphSection(filePath, content) {
174
178
  await fs.writeFile(filePath, newContent, 'utf-8');
175
179
  return 'appended';
176
180
  }
181
+ async function readJsonFile(filePath) {
182
+ try {
183
+ return JSON.parse(await fs.readFile(filePath, 'utf-8'));
184
+ }
185
+ catch {
186
+ return null;
187
+ }
188
+ }
189
+ function gitValue(repoPath, args) {
190
+ try {
191
+ return execFileSync('git', args, {
192
+ cwd: repoPath,
193
+ encoding: 'utf-8',
194
+ timeout: 3000,
195
+ stdio: ['ignore', 'pipe', 'ignore'],
196
+ }).trim();
197
+ }
198
+ catch {
199
+ return '';
200
+ }
201
+ }
202
+ function statValue(value) {
203
+ return value === undefined || value === null || value === '' ? 'unknown' : String(value);
204
+ }
205
+ function shortCommit(value) {
206
+ return value ? value.slice(0, 12) : 'unknown';
207
+ }
208
+ function sqlString(value) {
209
+ return String(value ?? '').replace(/'/g, "''");
210
+ }
211
+ async function buildAgentStructureState(repoPath, storagePath, projectName, stats) {
212
+ const meta = await readJsonFile(path.join(storagePath, 'meta.json'));
213
+ const currentGitHead = gitValue(repoPath, ['rev-parse', 'HEAD']);
214
+ const currentBranch = gitValue(repoPath, ['branch', '--show-current']) ||
215
+ gitValue(repoPath, ['rev-parse', '--abbrev-ref', 'HEAD']);
216
+ return {
217
+ projectName,
218
+ repoPath,
219
+ storagePath,
220
+ generatedAt: new Date().toISOString(),
221
+ indexedAt: meta?.indexedAt,
222
+ currentBranch: currentBranch || undefined,
223
+ currentGitHead: currentGitHead || undefined,
224
+ indexedCommit: meta?.lastCommit || currentGitHead || undefined,
225
+ remoteUrl: meta?.remoteUrl,
226
+ schemaVersion: meta?.schemaVersion,
227
+ compress: meta?.compress,
228
+ graphstoreBranch: meta?.currentBranch,
229
+ graphstoreHeadCommit: meta?.headCommit,
230
+ stats: {
231
+ files: stats.files ?? meta?.stats?.files,
232
+ nodes: stats.nodes ?? meta?.stats?.nodes,
233
+ edges: stats.edges ?? meta?.stats?.edges,
234
+ communities: stats.communities ?? meta?.stats?.communities,
235
+ clusters: stats.clusters ?? meta?.stats?.featureClusters,
236
+ processes: stats.processes ?? meta?.stats?.processes,
237
+ embeddings: meta?.stats?.embeddings,
238
+ },
239
+ };
240
+ }
241
+ function buildStructureDocs(state) {
242
+ const statsTable = `| Metric | Value |
243
+ |---|---|
244
+ | Files | ${statValue(state.stats.files)} |
245
+ | Symbols | ${statValue(state.stats.nodes)} |
246
+ | Relationships | ${statValue(state.stats.edges)} |
247
+ | Feature clusters | ${statValue(state.stats.clusters)} |
248
+ | Execution flows | ${statValue(state.stats.processes)} |
249
+ | Embeddings | ${statValue(state.stats.embeddings)} |
250
+ | Compression | ${statValue(state.compress)} |
251
+ | Schema version | ${statValue(state.schemaVersion)} |`;
252
+ const branchTable = `| Field | Value |
253
+ |---|---|
254
+ | Current git branch | ${statValue(state.currentBranch)} |
255
+ | Current git HEAD | ${statValue(state.currentGitHead)} |
256
+ | Indexed commit | ${statValue(state.indexedCommit)} |
257
+ | Indexed at | ${statValue(state.indexedAt)} |
258
+ | Graphstore branch | ${statValue(state.graphstoreBranch)} |
259
+ | Graphstore head commit | ${statValue(state.graphstoreHeadCommit)} |
260
+ | Remote | ${statValue(state.remoteUrl)} |`;
261
+ return {
262
+ 'README.md': `# CodraGraph Agent Structure
263
+
264
+ Generated: ${state.generatedAt}
265
+
266
+ This folder is the small, pre-seeded context pack for AI agents. It is rebuilt
267
+ by \`codragraph analyze\` so agents can read stable markdown instead of guessing
268
+ from stale terminal output.
269
+
270
+ Read order:
271
+
272
+ 1. [WHAT.md](WHAT.md) - what this index represents.
273
+ 2. [WHY.md](WHY.md) - why the agent should use graph context first.
274
+ 3. [HOW.md](HOW.md) - how to query, analyze, and recover safely.
275
+ 4. [WHEN.md](WHEN.md) - when to refresh, reuse, or clean the index.
276
+ 5. [WHERE.md](WHERE.md) - where local storage, MCP, HTTP, and docs live.
277
+ 6. [BRANCHES.md](BRANCHES.md) - branch, commit, and graphstore state.
278
+ 7. [SQLITE.md](SQLITE.md) - SQLite-compatible seed data for external agent memory.
279
+ `,
280
+ 'WHAT.md': `# What
281
+
282
+ CodraGraph index name: **${state.projectName}**
283
+
284
+ This index stores a local code graph for the repository at:
285
+
286
+ \`${state.repoPath}\`
287
+
288
+ The graph includes file structure, symbols, relationships, imports, execution
289
+ flows, feature clusters, Markdown graph docs, and optional embeddings. Agents
290
+ should use this pack as the first local orientation layer, then ask MCP/CLI for
291
+ live graph details.
292
+
293
+ ${statsTable}
294
+ `,
295
+ 'WHY.md': `# Why
296
+
297
+ AI agents should not rebuild project understanding from raw grep every time.
298
+ This folder gives them a compact, reusable map of the current indexed state.
299
+
300
+ Use it to avoid:
301
+
302
+ - Querying the wrong registered repo.
303
+ - Running multiple analyzers against the same .codragraph store.
304
+ - Treating stale branch or commit context as fresh.
305
+ - Enabling embeddings by default when BM25 and graph search are enough.
306
+ - Deleting index files when a targeted analyze or clean command is safer.
307
+ `,
308
+ 'HOW.md': `# How
309
+
310
+ Safe command flow for agents:
311
+
312
+ 1. Read this folder and \`AGENTS.md\` / \`CLAUDE.md\`.
313
+ 2. Run \`codragraph status\` or \`npx @codragraph/cli status\`.
314
+ 3. Use MCP \`query\`, \`context\`, \`impact\`, and \`detect_changes\` for graph work.
315
+ 4. Run \`codragraph analyze\` only when the index is missing or stale.
316
+ 5. Use \`--repo ${state.projectName}\` when running from outside this checkout.
317
+
318
+ Do not run \`analyze\`, \`detect-changes\`, \`clean\`, or DB-writing commands from
319
+ editor hooks. Hooks should stay bounded and read-only.
320
+ `,
321
+ 'WHEN.md': `# When
322
+
323
+ Refresh with \`codragraph analyze\` when:
324
+
325
+ - The repo was never indexed.
326
+ - MCP or CLI reports stale graph context.
327
+ - Source files, Markdown graph docs, language config, or path topology changed.
328
+ - Schema, compression, or embedding settings changed.
329
+
330
+ Reuse the existing graph when only generated agent context, lockfiles, or
331
+ ignored assets changed. Use \`codragraph analyze --force\` when ignore rules
332
+ changed or corruption is suspected. Ask before \`codragraph clean --force\`.
333
+ `,
334
+ 'WHERE.md': `# Where
335
+
336
+ | Item | Location |
337
+ |---|---|
338
+ | Repository | \`${state.repoPath}\` |
339
+ | Local index storage | \`${state.storagePath}\` |
340
+ | Agent structure pack | \`${path.join(state.storagePath, AGENT_STRUCTURE_DIR)}\` |
341
+ | Root agent instructions | \`AGENTS.md\`, \`CLAUDE.md\` |
342
+ | Claude skills | \`.claude/skills/@codragraph/cli/\` |
343
+ | HTTP API | \`http://127.0.0.1:4747/api/info\` after \`codragraph serve\` |
344
+ | MCP tools | \`codragraph mcp\` |
345
+ `,
346
+ 'BRANCHES.md': `# Branches And Commits
347
+
348
+ ${branchTable}
349
+
350
+ The indexed commit is the source-of-truth for whether graph answers are fresh.
351
+ If \`Current git HEAD\` and \`Indexed commit\` differ, agents should say the graph
352
+ is stale and run \`codragraph analyze\` before relying on impact, context, or
353
+ detect-changes output.
354
+ `,
355
+ 'INDEX.md': `# Index Snapshot
356
+
357
+ ${statsTable}
358
+
359
+ Storage notes:
360
+
361
+ - BM25 and graph traversal work without embeddings.
362
+ - Embeddings are optional and should be preserved with \`--embeddings\` only
363
+ when semantic/vector search is intentionally required.
364
+ - Compression mode is recorded in \`.codragraph/meta.json\`.
365
+ - This markdown pack is small and should not be embedded or vectorized.
366
+ `,
367
+ 'SQLITE.md': `# SQLite Seed
368
+
369
+ \`agent-memory.sql\` is a SQLite-compatible seed file for external agent memory
370
+ stores. CodraGraph writes it as plain SQL so installs stay light on Node 20 and
371
+ do not pull native SQLite dependencies into the CLI hot path.
372
+
373
+ Optional import:
374
+
375
+ \`\`\`sh
376
+ sqlite3 agent-memory.sqlite < agent-memory.sql
377
+ \`\`\`
378
+
379
+ The canonical live source remains \`.codragraph/meta.json\` plus the graph DB.
380
+ `,
381
+ };
382
+ }
383
+ function buildSqlSeed(state) {
384
+ const rows = [
385
+ ['project_name', state.projectName],
386
+ ['repo_path', state.repoPath],
387
+ ['storage_path', state.storagePath],
388
+ ['generated_at', state.generatedAt],
389
+ ['indexed_at', state.indexedAt],
390
+ ['current_branch', state.currentBranch],
391
+ ['current_git_head', state.currentGitHead],
392
+ ['indexed_commit', state.indexedCommit],
393
+ ['remote_url', state.remoteUrl],
394
+ ['schema_version', state.schemaVersion],
395
+ ['compress', state.compress],
396
+ ['graphstore_branch', state.graphstoreBranch],
397
+ ['graphstore_head_commit', state.graphstoreHeadCommit],
398
+ ['stats_json', JSON.stringify(state.stats)],
399
+ ];
400
+ const values = rows
401
+ .map(([key, value]) => `('${sqlString(key)}', '${sqlString(value)}', '${sqlString(state.generatedAt)}')`)
402
+ .join(',\n');
403
+ return `BEGIN;
404
+ CREATE TABLE IF NOT EXISTS codragraph_agent_context (
405
+ key TEXT PRIMARY KEY,
406
+ value TEXT NOT NULL,
407
+ updated_at TEXT NOT NULL
408
+ );
409
+ DELETE FROM codragraph_agent_context;
410
+ INSERT INTO codragraph_agent_context (key, value, updated_at) VALUES
411
+ ${values};
412
+ COMMIT;
413
+ `;
414
+ }
415
+ async function buildHistoryContent(historyPath, state) {
416
+ const entryId = (state.indexedCommit || 'no-git').replace(/[^a-zA-Z0-9._-]/g, '_');
417
+ const entry = `<!-- codragraph:history-entry:${entryId} -->
418
+ ## ${shortCommit(state.indexedCommit)} - ${state.generatedAt}
419
+
420
+ - Branch: ${statValue(state.currentBranch)}
421
+ - Indexed commit: ${statValue(state.indexedCommit)}
422
+ - Git HEAD: ${statValue(state.currentGitHead)}
423
+ - Graphstore: ${statValue(state.graphstoreBranch)} / ${statValue(state.graphstoreHeadCommit)}
424
+ - Stats: ${statValue(state.stats.nodes)} symbols, ${statValue(state.stats.edges)} relationships, ${statValue(state.stats.processes)} flows
425
+ <!-- /codragraph:history-entry -->`;
426
+ let existing = '';
427
+ try {
428
+ existing = await fs.readFile(historyPath, 'utf-8');
429
+ }
430
+ catch {
431
+ /* first run */
432
+ }
433
+ const blocks = [];
434
+ const regex = /<!-- codragraph:history-entry:([^ ]+) -->[\s\S]*?<!-- \/codragraph:history-entry -->/g;
435
+ let match;
436
+ while ((match = regex.exec(existing)) !== null) {
437
+ if (match[1] !== entryId)
438
+ blocks.push(match[0]);
439
+ }
440
+ return `# Analyze History
441
+
442
+ Most recent CodraGraph analyze or smart-reuse events. Bounded to the latest
443
+ ${AGENT_HISTORY_LIMIT} entries so this file remains small.
444
+
445
+ ${[entry, ...blocks].slice(0, AGENT_HISTORY_LIMIT).join('\n\n')}
446
+ `;
447
+ }
448
+ async function generateAgentStructurePack(repoPath, storagePath, projectName, stats) {
449
+ const structureDir = path.join(storagePath, AGENT_STRUCTURE_DIR);
450
+ await fs.mkdir(structureDir, { recursive: true });
451
+ const state = await buildAgentStructureState(repoPath, storagePath, projectName, stats);
452
+ const docs = buildStructureDocs(state);
453
+ const written = [];
454
+ for (const [fileName, content] of Object.entries(docs)) {
455
+ await fs.writeFile(path.join(structureDir, fileName), content.trim() + '\n', 'utf-8');
456
+ written.push(fileName);
457
+ }
458
+ await fs.writeFile(path.join(structureDir, 'state.json'), JSON.stringify(state, null, 2) + '\n', 'utf-8');
459
+ written.push('state.json');
460
+ await fs.writeFile(path.join(structureDir, 'agent-memory.sql'), buildSqlSeed(state), 'utf-8');
461
+ written.push('agent-memory.sql');
462
+ const historyPath = path.join(structureDir, 'HISTORY.md');
463
+ await fs.writeFile(historyPath, await buildHistoryContent(historyPath, state), 'utf-8');
464
+ written.push('HISTORY.md');
465
+ return written;
466
+ }
177
467
  /**
178
468
  * Install CodraGraph skills to .claude/skills/codragraph/
179
469
  * Works natively with Claude Code, Cursor, and GitHub Copilot
@@ -265,6 +555,13 @@ export async function generateAIContextFiles(repoPath, _storagePath, projectName
265
555
  createdFiles.push('AGENTS.md (skipped via --skip-agents-md)');
266
556
  createdFiles.push('CLAUDE.md (skipped via --skip-agents-md)');
267
557
  }
558
+ try {
559
+ const structureFiles = await generateAgentStructurePack(repoPath, _storagePath, projectName, stats);
560
+ createdFiles.push(`.codragraph/structure/ (${structureFiles.length} files)`);
561
+ }
562
+ catch (err) {
563
+ console.warn('Warning: Could not generate .codragraph/structure agent pack:', err);
564
+ }
268
565
  // Install skills to .claude/skills/codragraph/
269
566
  const installedSkills = await installSkills(repoPath);
270
567
  if (installedSkills.length > 0) {
package/dist/cli/index.js CHANGED
@@ -8,6 +8,26 @@ import { registerGroupCommands } from './group.js';
8
8
  const _require = createRequire(import.meta.url);
9
9
  const pkg = _require('../../package.json');
10
10
  const program = new Command();
11
+ const exitOneShot = (code) => {
12
+ const reallyExit = process
13
+ .reallyExit;
14
+ if (typeof reallyExit === 'function') {
15
+ reallyExit(code);
16
+ }
17
+ process.exit(code);
18
+ };
19
+ const createOneShotLazyAction = (loader, exportName) => {
20
+ const action = createLazyAction(loader, exportName);
21
+ return async (...args) => {
22
+ await action(...args);
23
+ const code = typeof process.exitCode === 'number'
24
+ ? process.exitCode
25
+ : process.exitCode
26
+ ? Number(process.exitCode) || 1
27
+ : 0;
28
+ exitOneShot(code);
29
+ };
30
+ };
11
31
  program.name('codragraph').description('CodraGraph local CLI and MCP server').version(pkg.version);
12
32
  program
13
33
  .command('setup')
@@ -114,7 +134,7 @@ program
114
134
  .option('-g, --goal <text>', 'What you want to find')
115
135
  .option('-l, --limit <n>', 'Max processes to return (default: 5)')
116
136
  .option('--content', 'Include full symbol source code')
117
- .action(createLazyAction(() => import('./tool.js'), 'queryCommand'));
137
+ .action(createOneShotLazyAction(() => import('./tool.js'), 'queryCommand'));
118
138
  program
119
139
  .command('context [name]')
120
140
  .description('360-degree view of a code symbol: callers, callees, processes')
@@ -122,7 +142,7 @@ program
122
142
  .option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
123
143
  .option('-f, --file <path>', 'File path to disambiguate common names')
124
144
  .option('--content', 'Include full symbol source code')
125
- .action(createLazyAction(() => import('./tool.js'), 'contextCommand'));
145
+ .action(createOneShotLazyAction(() => import('./tool.js'), 'contextCommand'));
126
146
  program
127
147
  .command('impact <target>')
128
148
  .description('Blast radius analysis: what breaks if you change a symbol')
@@ -130,49 +150,49 @@ program
130
150
  .option('-r, --repo <name>', 'Target repository')
131
151
  .option('--depth <n>', 'Max relationship depth (default: 3)')
132
152
  .option('--include-tests', 'Include test files in results')
133
- .action(createLazyAction(() => import('./tool.js'), 'impactCommand'));
153
+ .action(createOneShotLazyAction(() => import('./tool.js'), 'impactCommand'));
134
154
  program
135
155
  .command('cypher <query>')
136
156
  .description('Execute raw Cypher query against the knowledge graph')
137
157
  .option('-r, --repo <name>', 'Target repository')
138
- .action(createLazyAction(() => import('./tool.js'), 'cypherCommand'));
158
+ .action(createOneShotLazyAction(() => import('./tool.js'), 'cypherCommand'));
139
159
  program
140
160
  .command('feature-clusters')
141
161
  .description('List human-facing feature clusters for targeted context building')
142
162
  .option('-r, --repo <name>', 'Target repository')
143
163
  .option('-l, --limit <n>', 'Max feature clusters to return (default: 100)')
144
- .action(createLazyAction(() => import('./tool.js'), 'featureClustersCommand'));
164
+ .action(createOneShotLazyAction(() => import('./tool.js'), 'featureClustersCommand'));
145
165
  program
146
166
  .command('cluster-query [query]')
147
167
  .description('Alias for feature-clusters: list product/domain clusters')
148
168
  .option('-r, --repo <name>', 'Target repository')
149
169
  .option('-l, --limit <n>', 'Max feature clusters to return (default: 100)')
150
- .action(createLazyAction(() => import('./tool.js'), 'clusterQueryCommand'));
170
+ .action(createOneShotLazyAction(() => import('./tool.js'), 'clusterQueryCommand'));
151
171
  program
152
172
  .command('feature-context <name>')
153
173
  .description('Show members, line ranges, and dependencies for a feature cluster')
154
174
  .option('-r, --repo <name>', 'Target repository')
155
175
  .option('-l, --limit <n>', 'Max members to return (default: 100)')
156
- .action(createLazyAction(() => import('./tool.js'), 'featureContextCommand'));
176
+ .action(createOneShotLazyAction(() => import('./tool.js'), 'featureContextCommand'));
157
177
  program
158
178
  .command('cluster-context <name>')
159
179
  .description('Alias for feature-context: show a feature cluster context pack')
160
180
  .option('-r, --repo <name>', 'Target repository')
161
181
  .option('-l, --limit <n>', 'Max members to return (default: 100)')
162
- .action(createLazyAction(() => import('./tool.js'), 'clusterContextCommand'));
182
+ .action(createOneShotLazyAction(() => import('./tool.js'), 'clusterContextCommand'));
163
183
  program
164
184
  .command('context-pack <name>')
165
185
  .description('Generate the compact agent context pack for a feature cluster')
166
186
  .option('-r, --repo <name>', 'Target repository')
167
187
  .option('-l, --limit <n>', 'Max members to return (default: 100)')
168
- .action(createLazyAction(() => import('./tool.js'), 'contextPackCommand'));
188
+ .action(createOneShotLazyAction(() => import('./tool.js'), 'contextPackCommand'));
169
189
  program
170
190
  .command('cluster-impact <name>')
171
191
  .description('Feature-level blast radius analysis for a cluster')
172
192
  .option('-d, --direction <dir>', 'upstream, downstream, or both', 'upstream')
173
193
  .option('-r, --repo <name>', 'Target repository')
174
194
  .option('-l, --limit <n>', 'Max context-pack members to include (default: 100)')
175
- .action(createLazyAction(() => import('./tool.js'), 'clusterImpactCommand'));
195
+ .action(createOneShotLazyAction(() => import('./tool.js'), 'clusterImpactCommand'));
176
196
  program
177
197
  .command('detect-changes')
178
198
  .alias('detect_changes')
@@ -180,7 +200,7 @@ program
180
200
  .option('-s, --scope <scope>', 'What to analyze: unstaged, staged, all, or compare', 'unstaged')
181
201
  .option('-b, --base-ref <ref>', 'Branch/commit for compare scope (e.g. main)')
182
202
  .option('-r, --repo <name>', 'Target repository')
183
- .action(createLazyAction(() => import('./tool.js'), 'detectChangesCommand'));
203
+ .action(createOneShotLazyAction(() => import('./tool.js'), 'detectChangesCommand'));
184
204
  // ─── Eval Server (persistent daemon for SWE-bench) ─────────────────
185
205
  program
186
206
  .command('eval-server')