@laitszkin/apollo-toolkit 4.0.11 → 4.1.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.
Files changed (134) hide show
  1. package/AGENTS.md +37 -27
  2. package/CHANGELOG.md +31 -0
  3. package/CLAUDE.md +37 -27
  4. package/README.md +15 -2
  5. package/assets/spec/rg13-1780435029246/test-questions.json +1 -0
  6. package/assets/spec/rg13-1780468345132/test-questions.json +1 -0
  7. package/package.json +3 -3
  8. package/packages/cli/dist/tool-registration.js +1 -0
  9. package/packages/cli/dist/tsconfig.tsbuildinfo +1 -1
  10. package/packages/cli/tool-registration.ts +1 -0
  11. package/packages/tools/architecture/dist/index.js +539 -2
  12. package/packages/tools/architecture/dist/index.test.d.ts +1 -0
  13. package/packages/tools/architecture/dist/index.test.js +229 -0
  14. package/packages/tools/architecture/dist/tsconfig.tsbuildinfo +1 -1
  15. package/packages/tools/architecture/index.test.ts +329 -0
  16. package/packages/tools/architecture/index.ts +607 -5
  17. package/packages/tools/codegraph/dist/index.d.ts +3 -0
  18. package/packages/tools/codegraph/dist/index.js +157 -0
  19. package/packages/tools/codegraph/dist/lib/cg-instance.d.ts +29 -0
  20. package/packages/tools/codegraph/dist/lib/cg-instance.js +59 -0
  21. package/packages/tools/codegraph/dist/lib/cg-instance.test.d.ts +1 -0
  22. package/packages/tools/codegraph/dist/lib/cg-instance.test.js +27 -0
  23. package/packages/tools/codegraph/dist/lib/cmd-explore.d.ts +5 -0
  24. package/packages/tools/codegraph/dist/lib/cmd-explore.js +95 -0
  25. package/packages/tools/codegraph/dist/lib/cmd-explore.test.d.ts +1 -0
  26. package/packages/tools/codegraph/dist/lib/cmd-explore.test.js +133 -0
  27. package/packages/tools/codegraph/dist/lib/cmd-flag-splice.test.d.ts +1 -0
  28. package/packages/tools/codegraph/dist/lib/cmd-flag-splice.test.js +83 -0
  29. package/packages/tools/codegraph/dist/lib/cmd-init.d.ts +5 -0
  30. package/packages/tools/codegraph/dist/lib/cmd-init.js +50 -0
  31. package/packages/tools/codegraph/dist/lib/cmd-init.test.d.ts +1 -0
  32. package/packages/tools/codegraph/dist/lib/cmd-init.test.js +51 -0
  33. package/packages/tools/codegraph/dist/lib/cmd-list-apis.d.ts +5 -0
  34. package/packages/tools/codegraph/dist/lib/cmd-list-apis.js +64 -0
  35. package/packages/tools/codegraph/dist/lib/cmd-list-apis.test.d.ts +1 -0
  36. package/packages/tools/codegraph/dist/lib/cmd-list-apis.test.js +69 -0
  37. package/packages/tools/codegraph/dist/lib/cmd-search.d.ts +5 -0
  38. package/packages/tools/codegraph/dist/lib/cmd-search.js +21 -0
  39. package/packages/tools/codegraph/dist/lib/cmd-search.test.d.ts +1 -0
  40. package/packages/tools/codegraph/dist/lib/cmd-search.test.js +30 -0
  41. package/packages/tools/codegraph/dist/lib/cmd-status.d.ts +4 -0
  42. package/packages/tools/codegraph/dist/lib/cmd-status.js +44 -0
  43. package/packages/tools/codegraph/dist/lib/cmd-status.test.d.ts +1 -0
  44. package/packages/tools/codegraph/dist/lib/cmd-status.test.js +72 -0
  45. package/packages/tools/codegraph/dist/lib/cmd-survey.d.ts +36 -0
  46. package/packages/tools/codegraph/dist/lib/cmd-survey.js +142 -0
  47. package/packages/tools/codegraph/dist/lib/cmd-survey.test.d.ts +1 -0
  48. package/packages/tools/codegraph/dist/lib/cmd-survey.test.js +136 -0
  49. package/packages/tools/codegraph/dist/lib/cmd-sync.d.ts +4 -0
  50. package/packages/tools/codegraph/dist/lib/cmd-sync.js +51 -0
  51. package/packages/tools/codegraph/dist/lib/cmd-sync.test.d.ts +1 -0
  52. package/packages/tools/codegraph/dist/lib/cmd-sync.test.js +30 -0
  53. package/packages/tools/codegraph/dist/lib/cmd-verify.d.ts +4 -0
  54. package/packages/tools/codegraph/dist/lib/cmd-verify.js +134 -0
  55. package/packages/tools/codegraph/dist/lib/cmd-verify.test.d.ts +1 -0
  56. package/packages/tools/codegraph/dist/lib/cmd-verify.test.js +139 -0
  57. package/packages/tools/codegraph/dist/lib/formatter.d.ts +67 -0
  58. package/packages/tools/codegraph/dist/lib/formatter.js +107 -0
  59. package/packages/tools/codegraph/dist/lib/formatter.test.d.ts +1 -0
  60. package/packages/tools/codegraph/dist/lib/formatter.test.js +41 -0
  61. package/packages/tools/codegraph/dist/lib/survey/grouper.d.ts +19 -0
  62. package/packages/tools/codegraph/dist/lib/survey/grouper.js +194 -0
  63. package/packages/tools/codegraph/dist/lib/survey/grouper.test.d.ts +1 -0
  64. package/packages/tools/codegraph/dist/lib/survey/grouper.test.js +62 -0
  65. package/packages/tools/codegraph/dist/lib/survey/scanner.d.ts +31 -0
  66. package/packages/tools/codegraph/dist/lib/survey/scanner.js +50 -0
  67. package/packages/tools/codegraph/dist/lib/verify/checker.d.ts +32 -0
  68. package/packages/tools/codegraph/dist/lib/verify/checker.js +146 -0
  69. package/packages/tools/codegraph/dist/lib/verify/checker.test.d.ts +1 -0
  70. package/packages/tools/codegraph/dist/lib/verify/checker.test.js +128 -0
  71. package/packages/tools/codegraph/dist/tsconfig.tsbuildinfo +1 -0
  72. package/packages/tools/codegraph/env.d.ts +56 -0
  73. package/packages/tools/codegraph/index.ts +173 -0
  74. package/packages/tools/codegraph/lib/cg-instance.test.ts +36 -0
  75. package/packages/tools/codegraph/lib/cg-instance.ts +66 -0
  76. package/packages/tools/codegraph/lib/cmd-explore.test.ts +195 -0
  77. package/packages/tools/codegraph/lib/cmd-explore.ts +129 -0
  78. package/packages/tools/codegraph/lib/cmd-flag-splice.test.ts +94 -0
  79. package/packages/tools/codegraph/lib/cmd-init.test.ts +68 -0
  80. package/packages/tools/codegraph/lib/cmd-init.ts +60 -0
  81. package/packages/tools/codegraph/lib/cmd-list-apis.test.ts +80 -0
  82. package/packages/tools/codegraph/lib/cmd-list-apis.ts +90 -0
  83. package/packages/tools/codegraph/lib/cmd-search.test.ts +37 -0
  84. package/packages/tools/codegraph/lib/cmd-search.ts +32 -0
  85. package/packages/tools/codegraph/lib/cmd-status.test.ts +86 -0
  86. package/packages/tools/codegraph/lib/cmd-status.ts +53 -0
  87. package/packages/tools/codegraph/lib/cmd-survey.test.ts +161 -0
  88. package/packages/tools/codegraph/lib/cmd-survey.ts +199 -0
  89. package/packages/tools/codegraph/lib/cmd-sync.test.ts +41 -0
  90. package/packages/tools/codegraph/lib/cmd-sync.ts +62 -0
  91. package/packages/tools/codegraph/lib/cmd-verify.test.ts +162 -0
  92. package/packages/tools/codegraph/lib/cmd-verify.ts +145 -0
  93. package/packages/tools/codegraph/lib/formatter.test.ts +47 -0
  94. package/packages/tools/codegraph/lib/formatter.ts +130 -0
  95. package/packages/tools/codegraph/lib/survey/grouper.test.ts +72 -0
  96. package/packages/tools/codegraph/lib/survey/grouper.ts +226 -0
  97. package/packages/tools/codegraph/lib/survey/scanner.ts +89 -0
  98. package/packages/tools/codegraph/lib/verify/checker.test.ts +140 -0
  99. package/packages/tools/codegraph/lib/verify/checker.ts +172 -0
  100. package/packages/tools/codegraph/package.json +23 -0
  101. package/packages/tools/codegraph/tsconfig.json +22 -0
  102. package/resources/project-architecture/atlas/atlas.history.log +32 -0
  103. package/resources/project-architecture/atlas/atlas.history.undo.json +356 -28
  104. package/resources/project-architecture/atlas/atlas.history.undo.stack.json +14350 -0
  105. package/resources/project-architecture/atlas/atlas.index.yaml +76 -12
  106. package/resources/project-architecture/atlas/features/codegraph.yaml +95 -0
  107. package/resources/project-architecture/atlas/features/eval-ci-gate.yaml +6 -1
  108. package/resources/project-architecture/atlas/features/eval-cli.yaml +16 -1
  109. package/resources/project-architecture/atlas/features/eval-executor.yaml +12 -2
  110. package/resources/project-architecture/atlas/features/eval-isolation.yaml +6 -1
  111. package/resources/project-architecture/atlas/features/eval-optimizer.yaml +17 -2
  112. package/resources/project-architecture/atlas/features/eval-question.yaml +12 -2
  113. package/resources/project-architecture/atlas/features/eval-reporter.yaml +6 -1
  114. package/resources/project-architecture/atlas/features/eval-scorer.yaml +12 -2
  115. package/resources/project-architecture/features/codegraph/cg-discovery.html +47 -0
  116. package/resources/project-architecture/features/codegraph/cg-lifecycle.html +48 -0
  117. package/resources/project-architecture/features/codegraph/cg-validation.html +47 -0
  118. package/resources/project-architecture/features/codegraph/index.html +58 -0
  119. package/resources/project-architecture/features/eval-ci-gate/workflow-trigger.html +6 -1
  120. package/resources/project-architecture/features/eval-cli/cli-handler.html +8 -1
  121. package/resources/project-architecture/features/eval-executor/exec-api-client.html +6 -1
  122. package/resources/project-architecture/features/eval-executor/trace-recorder.html +6 -1
  123. package/resources/project-architecture/features/eval-isolation/tool-dispatcher.html +6 -1
  124. package/resources/project-architecture/features/eval-optimizer/dedup-engine.html +6 -1
  125. package/resources/project-architecture/features/eval-optimizer/issue-extractor.html +7 -1
  126. package/resources/project-architecture/features/eval-question/question-loader.html +6 -1
  127. package/resources/project-architecture/features/eval-question/variant-generator.html +6 -1
  128. package/resources/project-architecture/features/eval-reporter/report-composer.html +6 -1
  129. package/resources/project-architecture/features/eval-scorer/judge-api-client.html +6 -1
  130. package/resources/project-architecture/features/eval-scorer/judge-prompt-builder.html +6 -1
  131. package/resources/project-architecture/index.html +200 -94
  132. package/skills/design/SKILL.md +33 -0
  133. package/skills/init-project-html/SKILL.md +12 -11
  134. package/tsconfig.json +1 -0
@@ -0,0 +1,157 @@
1
+ import { findProjectRoot } from './lib/cg-instance.js';
2
+ import { handleInit } from './lib/cmd-init.js';
3
+ import { handleSync } from './lib/cmd-sync.js';
4
+ import { handleStatus } from './lib/cmd-status.js';
5
+ import { handleSearch } from './lib/cmd-search.js';
6
+ import { handleExplore } from './lib/cmd-explore.js';
7
+ import { handleSurvey } from './lib/cmd-survey.js';
8
+ import { handleListApis } from './lib/cmd-list-apis.js';
9
+ import { handleVerify } from './lib/cmd-verify.js';
10
+ export async function codegraphHandler(args, context) {
11
+ const stdout = context.stdout || process.stdout;
12
+ const stderr = context.stderr || process.stderr;
13
+ const projectRoot = findProjectRoot(context.cwd || process.cwd());
14
+ // Parse --json flag early (can appear anywhere)
15
+ const jsonIndex = args.indexOf('--json');
16
+ const isJson = jsonIndex >= 0;
17
+ if (jsonIndex >= 0)
18
+ args.splice(jsonIndex, 1);
19
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
20
+ printHelp(stdout);
21
+ return 0;
22
+ }
23
+ const subcommand = args[0];
24
+ const rest = args.slice(1);
25
+ // Parse --spec <dir> for verify
26
+ const specIndex = rest.indexOf('--spec');
27
+ let specDir;
28
+ if (specIndex >= 0 && specIndex + 1 < rest.length) {
29
+ specDir = rest[specIndex + 1];
30
+ rest.splice(specIndex, 2);
31
+ }
32
+ // Parse --all flag for list-apis
33
+ const allIndex = rest.indexOf('--all');
34
+ const isAll = allIndex >= 0;
35
+ if (allIndex >= 0)
36
+ rest.splice(allIndex, 1);
37
+ // Parse --index flag for init
38
+ const shouldIndex = rest.includes('--index');
39
+ const indexIdx = rest.indexOf('--index');
40
+ if (indexIdx >= 0)
41
+ rest.splice(indexIdx, 1);
42
+ // Parse --feature <name> for survey
43
+ const featureIndex = rest.indexOf('--feature');
44
+ let featureName;
45
+ if (featureIndex >= 0 && featureIndex + 1 < rest.length) {
46
+ featureName = rest[featureIndex + 1];
47
+ rest.splice(featureIndex, 2);
48
+ }
49
+ // Parse limit for search
50
+ const limitIndex = rest.indexOf('--limit');
51
+ let limit;
52
+ if (limitIndex >= 0 && limitIndex + 1 < rest.length) {
53
+ limit = parseInt(rest[limitIndex + 1], 10);
54
+ rest.splice(limitIndex, 2);
55
+ }
56
+ try {
57
+ switch (subcommand) {
58
+ case 'init':
59
+ return await handleInit(projectRoot, { index: shouldIndex, json: isJson });
60
+ case 'sync':
61
+ return await handleSync(projectRoot, { json: isJson });
62
+ case 'status':
63
+ return await handleStatus(projectRoot, { json: isJson });
64
+ case 'search': {
65
+ const query = rest.join(' ');
66
+ if (!query) {
67
+ stderr.write('Usage: apltk codegraph search <query> [--limit N] [--json]\n');
68
+ return 1;
69
+ }
70
+ return await handleSearch(projectRoot, query, { limit, json: isJson });
71
+ }
72
+ case 'explore': {
73
+ const query = rest.join(' ');
74
+ if (!query) {
75
+ stderr.write('Usage: apltk codegraph explore <query> [--json]\n');
76
+ return 1;
77
+ }
78
+ return await handleExplore(projectRoot, query, { json: isJson, feature: featureName });
79
+ }
80
+ case 'survey': {
81
+ const dirPath = rest[0] || '.';
82
+ return await handleSurvey(projectRoot, dirPath, { feature: featureName, json: isJson });
83
+ }
84
+ case 'list-apis': {
85
+ const pathArg = rest[0];
86
+ const combinedPath = featureName
87
+ ? (pathArg ? `${featureName}/${pathArg.replace(/^\//, '')}` : featureName)
88
+ : pathArg;
89
+ return await handleListApis(projectRoot, combinedPath, { all: isAll, json: isJson });
90
+ }
91
+ case 'verify': {
92
+ if (!specDir) {
93
+ stderr.write('Usage: apltk codegraph verify --spec <spec-dir> [--json]\n');
94
+ return 1;
95
+ }
96
+ return await handleVerify(projectRoot, specDir, { json: isJson });
97
+ }
98
+ default:
99
+ stderr.write(`Unknown subcommand: ${subcommand}\n\n`);
100
+ printHelp(stderr);
101
+ return 1;
102
+ }
103
+ }
104
+ catch (error) {
105
+ if (error.code === 'MODULE_NOT_FOUND' || (error.message && error.message.includes('Cannot find module'))) {
106
+ stderr.write('`@colbymchenry/codegraph` is not installed. Run `npm install @colbymchenry/codegraph` in your project directory.\n');
107
+ }
108
+ else {
109
+ stderr.write(`Error running codegraph ${subcommand}: ${error.message}\n`);
110
+ }
111
+ return 1;
112
+ }
113
+ }
114
+ function printHelp(stream) {
115
+ stream.write(`Usage: apltk codegraph <subcommand> [options]
116
+
117
+ Subcommands:
118
+
119
+ lifecycle:
120
+ init Initialize CodeGraph for the project
121
+ --index Run initial indexing after init
122
+
123
+ sync Sync the index with current file state
124
+
125
+ status Show index statistics (files, nodes, edges)
126
+
127
+ discovery:
128
+ search <query> Search the code graph for symbols
129
+ --limit N Max results (default: 20)
130
+
131
+ explore <query> Deep-dive on a symbol (callers, callees, source)
132
+ --json JSON output
133
+
134
+ survey [dir] Scan a directory and suggest submodule groupings
135
+ --feature <name> Feature context
136
+ --json JSON output
137
+
138
+ list-apis [path] List public APIs in the project or a sub-path
139
+ --all Include non-exported symbols
140
+ --json JSON output
141
+
142
+ validation:
143
+ verify Verify a spec overlay against the actual code
144
+ --spec <dir> Spec directory (required)
145
+ --json JSON output
146
+
147
+ Global options:
148
+ --json Output as JSON instead of human-readable format
149
+ --help Show this help message
150
+ `);
151
+ }
152
+ export const tool = {
153
+ name: 'codegraph',
154
+ category: 'Code analysis',
155
+ description: 'CodeGraph code intelligence — init, sync, status, search, explore, survey, list-apis, verify',
156
+ handler: codegraphHandler,
157
+ };
@@ -0,0 +1,29 @@
1
+ export declare function getCodeGraphModule(): {
2
+ CodeGraph: any;
3
+ findNearestCodeGraphRoot: any;
4
+ };
5
+ /**
6
+ * Locate the project root by walking up from the given directory.
7
+ * Returns the nearest parent containing `.codegraph/`, or falls back
8
+ * to the nearest parent containing `package.json`.
9
+ */
10
+ export declare function findProjectRoot(startPath?: string): string;
11
+ /**
12
+ * Initialize a CodeGraph index for the given project root.
13
+ *
14
+ * If the project is already initialized, throws an error suggesting
15
+ * `apltk codegraph sync` instead. Otherwise, initializes a new CodeGraph
16
+ * project. When `options.index` is true, runs initial indexing after init.
17
+ *
18
+ * Note: `CodeGraph.init()` supports an `{ index: true }` shorthand that
19
+ * runs initial indexing inline -- this deviates from a two-step init-then-index
20
+ * pattern but is the supported API through the npm package.
21
+ */
22
+ export declare function createOrOpenIndex(projectRoot: string, options?: {
23
+ index?: boolean;
24
+ onProgress?: (progress: any) => void;
25
+ }): Promise<any>;
26
+ /**
27
+ * Close a CodeGraph instance and release resources.
28
+ */
29
+ export declare function closeIndex(cg: any): void;
@@ -0,0 +1,59 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ const require = createRequire(import.meta.url);
5
+ let _codeGraphModule = null;
6
+ export function getCodeGraphModule() {
7
+ if (!_codeGraphModule) {
8
+ _codeGraphModule = require('@colbymchenry/codegraph');
9
+ }
10
+ return _codeGraphModule;
11
+ }
12
+ /**
13
+ * Locate the project root by walking up from the given directory.
14
+ * Returns the nearest parent containing `.codegraph/`, or falls back
15
+ * to the nearest parent containing `package.json`.
16
+ */
17
+ export function findProjectRoot(startPath) {
18
+ const cwd = startPath || process.cwd();
19
+ const codegraphRoot = getCodeGraphModule().findNearestCodeGraphRoot(cwd);
20
+ if (codegraphRoot)
21
+ return codegraphRoot;
22
+ // Fallback: walk up looking for package.json
23
+ let dir = path.resolve(cwd);
24
+ while (true) {
25
+ if (fs.existsSync(path.join(dir, 'package.json')))
26
+ return dir;
27
+ const parent = path.dirname(dir);
28
+ if (parent === dir)
29
+ return cwd; // hit filesystem root
30
+ dir = parent;
31
+ }
32
+ }
33
+ /**
34
+ * Initialize a CodeGraph index for the given project root.
35
+ *
36
+ * If the project is already initialized, throws an error suggesting
37
+ * `apltk codegraph sync` instead. Otherwise, initializes a new CodeGraph
38
+ * project. When `options.index` is true, runs initial indexing after init.
39
+ *
40
+ * Note: `CodeGraph.init()` supports an `{ index: true }` shorthand that
41
+ * runs initial indexing inline -- this deviates from a two-step init-then-index
42
+ * pattern but is the supported API through the npm package.
43
+ */
44
+ export async function createOrOpenIndex(projectRoot, options) {
45
+ const isInit = getCodeGraphModule().CodeGraph.isInitialized(projectRoot);
46
+ if (isInit) {
47
+ throw new Error(`Project is already initialized at ${projectRoot}. Use \`apltk codegraph sync\` to update the index.`);
48
+ }
49
+ return getCodeGraphModule().CodeGraph.init(projectRoot, {
50
+ index: options?.index ?? false,
51
+ onProgress: options?.onProgress,
52
+ });
53
+ }
54
+ /**
55
+ * Close a CodeGraph instance and release resources.
56
+ */
57
+ export function closeIndex(cg) {
58
+ cg.close();
59
+ }
@@ -0,0 +1,27 @@
1
+ import { describe, it, before, after } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import { createOrOpenIndex } from './cg-instance.js';
7
+ describe('createOrOpenIndex', () => {
8
+ let tmpDir;
9
+ before(() => {
10
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-test-'));
11
+ });
12
+ after(() => {
13
+ fs.rmSync(tmpDir, { recursive: true, force: true });
14
+ });
15
+ it('should throw when project is already initialized', async () => {
16
+ // Arrange: create .codegraph/ and codegraph.db to simulate an initialized project
17
+ const codegraphDir = path.join(tmpDir, '.codegraph');
18
+ fs.mkdirSync(codegraphDir, { recursive: true });
19
+ fs.writeFileSync(path.join(codegraphDir, 'codegraph.db'), '');
20
+ // Act & Assert
21
+ await assert.rejects(() => createOrOpenIndex(tmpDir), (err) => {
22
+ assert.ok(err instanceof Error);
23
+ assert.match(err.message, /sync/);
24
+ return true;
25
+ });
26
+ });
27
+ });
@@ -0,0 +1,5 @@
1
+ export interface ExploreOptions {
2
+ json?: boolean;
3
+ feature?: string;
4
+ }
5
+ export declare function handleExplore(projectRoot: string, query: string, options?: ExploreOptions): Promise<number>;
@@ -0,0 +1,95 @@
1
+ import { createRequire } from 'node:module';
2
+ const require = createRequire(import.meta.url);
3
+ import { closeIndex } from './cg-instance.js';
4
+ import { formatOutput } from './formatter.js';
5
+ export async function handleExplore(projectRoot, query, options = {}) {
6
+ const { CodeGraph } = require('@colbymchenry/codegraph');
7
+ const cg = await CodeGraph.open(projectRoot, { sync: false, readOnly: true });
8
+ // Step 1: Search for the query
9
+ const searchResults = cg.searchNodes(query, { limit: 10 });
10
+ if (searchResults.length === 0) {
11
+ process.stdout.write('No symbols found matching the query.\n');
12
+ closeIndex(cg);
13
+ return 0;
14
+ }
15
+ const details = [];
16
+ for (const result of searchResults) {
17
+ const node = result.node;
18
+ const callers = cg.getCallers(node.id).map((c) => ({
19
+ name: c.node.name,
20
+ filePath: c.node.filePath,
21
+ startLine: c.node.startLine,
22
+ }));
23
+ const callees = cg.getCallees(node.id).map((c) => ({
24
+ name: c.node.name,
25
+ filePath: c.node.filePath,
26
+ startLine: c.node.startLine,
27
+ }));
28
+ const code = await cg.getCode(node.id);
29
+ details.push({
30
+ name: node.name,
31
+ kind: node.kind,
32
+ filePath: node.filePath,
33
+ startLine: node.startLine,
34
+ endLine: node.endLine,
35
+ qualifiedName: node.qualifiedName,
36
+ signature: node.signature,
37
+ callers,
38
+ callees,
39
+ code,
40
+ });
41
+ }
42
+ closeIndex(cg);
43
+ if (options.json) {
44
+ process.stdout.write(formatOutput(details, { json: true }) + '\n');
45
+ return 0;
46
+ }
47
+ // Human-readable output — group by filePath
48
+ const grouped = new Map();
49
+ for (const d of details) {
50
+ const group = grouped.get(d.filePath) ?? [];
51
+ group.push(d);
52
+ grouped.set(d.filePath, group);
53
+ }
54
+ if (options.feature) {
55
+ process.stdout.write(`Feature: ${options.feature}\n`);
56
+ }
57
+ for (const [filePath, symbols] of grouped) {
58
+ process.stdout.write(`\n=== ${filePath} ===\n\n`);
59
+ for (const d of symbols) {
60
+ process.stdout.write(` ${d.name} [${d.kind}] line ${d.startLine}-${d.endLine}\n`);
61
+ process.stdout.write(` QName: ${d.qualifiedName}\n`);
62
+ if (d.signature)
63
+ process.stdout.write(` Signature: ${d.signature}\n`);
64
+ process.stdout.write(` Callers (${d.callers.length}):\n`);
65
+ if (d.callers.length === 0) {
66
+ process.stdout.write(' (none)\n');
67
+ }
68
+ else {
69
+ for (const c of d.callers.slice(0, 20)) {
70
+ process.stdout.write(` ${c.name} ${c.filePath}:${c.startLine}\n`);
71
+ }
72
+ }
73
+ process.stdout.write(` Callees (${d.callees.length}):\n`);
74
+ if (d.callees.length === 0) {
75
+ process.stdout.write(' (none)\n');
76
+ }
77
+ else {
78
+ for (const c of d.callees.slice(0, 20)) {
79
+ process.stdout.write(` ${c.name} ${c.filePath}:${c.startLine}\n`);
80
+ }
81
+ }
82
+ if (d.code) {
83
+ process.stdout.write(` Source (${d.filePath}):\n`);
84
+ const lines = d.code.split('\n');
85
+ for (let i = 0; i < Math.min(lines.length, 30); i++) {
86
+ process.stdout.write(` ${lines[i]}\n`);
87
+ }
88
+ if (lines.length > 30) {
89
+ process.stdout.write(` ... (${lines.length - 30} more lines)\n`);
90
+ }
91
+ }
92
+ }
93
+ }
94
+ return 0;
95
+ }
@@ -0,0 +1,133 @@
1
+ import { describe, it, mock, before } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createRequire } from 'node:module';
4
+ const mockSearchResults = [];
5
+ // ---------------------------------------------------------------------------
6
+ // Pre-load @colbymchenry/codegraph so we can mock its methods before
7
+ // cmd-explore.js performs its own require().
8
+ // ---------------------------------------------------------------------------
9
+ const require = createRequire(import.meta.url);
10
+ const { CodeGraph } = require('@colbymchenry/codegraph');
11
+ let mockInstance;
12
+ before(() => {
13
+ mockInstance = {
14
+ searchNodes: () => mockSearchResults.map(r => ({ node: r.node, score: r.score })),
15
+ getCallers: () => [],
16
+ getCallees: () => [],
17
+ getCode: async () => null,
18
+ close: () => { },
19
+ };
20
+ mock.method(CodeGraph, 'open', async () => mockInstance);
21
+ });
22
+ // ---------------------------------------------------------------------------
23
+ // Import the module under test (same cached CodeGraph is used internally)
24
+ // ---------------------------------------------------------------------------
25
+ let handleExplore;
26
+ before(async () => {
27
+ const mod = await import('./cmd-explore.js');
28
+ handleExplore = mod.handleExplore;
29
+ });
30
+ // =========================================================================
31
+ // REGTEST-7: Explore output should group symbols by file
32
+ // =========================================================================
33
+ describe('REGTEST-7: Explore grouping by file', () => {
34
+ it('should group symbols under a single file header', async () => {
35
+ // Arrange: two symbols in the same file
36
+ mockSearchResults.length = 0;
37
+ mockSearchResults.push({
38
+ node: {
39
+ id: '1',
40
+ name: 'addUser',
41
+ kind: 'function',
42
+ filePath: 'src/utils.ts',
43
+ startLine: 10,
44
+ endLine: 25,
45
+ qualifiedName: 'utils.addUser',
46
+ signature: '(name: string, age: number): User',
47
+ },
48
+ score: 0.95,
49
+ }, {
50
+ node: {
51
+ id: '2',
52
+ name: 'deleteUser',
53
+ kind: 'function',
54
+ filePath: 'src/utils.ts',
55
+ startLine: 30,
56
+ endLine: 40,
57
+ qualifiedName: 'utils.deleteUser',
58
+ signature: '(id: number): void',
59
+ },
60
+ score: 0.85,
61
+ });
62
+ // Capture stdout
63
+ const stdoutChunks = [];
64
+ const origWrite = process.stdout.write;
65
+ process.stdout.write = ((chunk) => {
66
+ stdoutChunks.push(String(chunk));
67
+ return true;
68
+ });
69
+ try {
70
+ // Act
71
+ const exitCode = await handleExplore('/fake/project', 'utils', {});
72
+ // Assert
73
+ assert.strictEqual(exitCode, 0);
74
+ const output = stdoutChunks.join('');
75
+ // Exactly one file header for the shared filePath
76
+ const headerMatches = output.match(/=== src\/utils\.ts ===/g);
77
+ assert.strictEqual(headerMatches?.length, 1, 'Expected exactly one file header for src/utils.ts, ' +
78
+ `got ${headerMatches?.length ?? 0}`);
79
+ // Both symbols appear in the output
80
+ assert.ok(output.includes('addUser'), 'Output should contain addUser');
81
+ assert.ok(output.includes('deleteUser'), 'Output should contain deleteUser');
82
+ // File header precedes both symbols (not duplicated)
83
+ const headerIdx = output.indexOf('=== src/utils.ts ===');
84
+ assert.ok(headerIdx < output.indexOf('addUser'), 'File header should appear before addUser');
85
+ assert.ok(headerIdx < output.indexOf('deleteUser'), 'File header should appear before deleteUser');
86
+ }
87
+ finally {
88
+ process.stdout.write = origWrite;
89
+ }
90
+ });
91
+ });
92
+ // =========================================================================
93
+ // REGTEST-8: Explore --feature acceptance
94
+ // =========================================================================
95
+ describe('REGTEST-8: Explore --feature acceptance', () => {
96
+ it('should accept feature parameter without error', async () => {
97
+ // Arrange: at least one result so the feature line is emitted
98
+ mockSearchResults.length = 0;
99
+ mockSearchResults.push({
100
+ node: {
101
+ id: '3',
102
+ name: 'authLogin',
103
+ kind: 'function',
104
+ filePath: 'src/auth.ts',
105
+ startLine: 5,
106
+ endLine: 20,
107
+ qualifiedName: 'auth.login',
108
+ signature: '(credentials: Record<string, unknown>): Session',
109
+ },
110
+ score: 0.9,
111
+ });
112
+ const stdoutChunks = [];
113
+ const origWrite = process.stdout.write;
114
+ process.stdout.write = ((chunk) => {
115
+ stdoutChunks.push(String(chunk));
116
+ return true;
117
+ });
118
+ try {
119
+ // Act — pass feature without json, expect no error
120
+ const exitCode = await handleExplore('/fake/project', 'authLogin', {
121
+ feature: 'auth',
122
+ json: false,
123
+ });
124
+ // Assert
125
+ assert.strictEqual(exitCode, 0, 'Should return exit code 0');
126
+ const output = stdoutChunks.join('');
127
+ assert.ok(output.includes('Feature: auth'), 'Output should include "Feature: auth" header');
128
+ }
129
+ finally {
130
+ process.stdout.write = origWrite;
131
+ }
132
+ });
133
+ });
@@ -0,0 +1,83 @@
1
+ import { describe, it, mock } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createRequire } from 'node:module';
4
+ const require = createRequire(import.meta.url);
5
+ describe('REGTEST-R2-01: handleListApis --all flag splice', () => {
6
+ it('should include non-exported symbols with all=true and filter them with all=false', async (ctx) => {
7
+ // Arrange: three nodes — two exported, one non-exported
8
+ const nodes = [
9
+ {
10
+ id: 'n1',
11
+ name: 'funcA',
12
+ kind: 'function',
13
+ filePath: 'src/feature/a.ts',
14
+ startLine: 10,
15
+ endLine: 30,
16
+ qualifiedName: 'funcA',
17
+ signature: '(x: string): void',
18
+ isExported: true,
19
+ },
20
+ {
21
+ id: 'n2',
22
+ name: 'funcB',
23
+ kind: 'function',
24
+ filePath: 'src/lib/b.ts',
25
+ startLine: 5,
26
+ endLine: 25,
27
+ qualifiedName: 'funcB',
28
+ signature: '(y: number): string',
29
+ isExported: false,
30
+ },
31
+ {
32
+ id: 'n3',
33
+ name: 'funcC',
34
+ kind: 'function',
35
+ filePath: 'src/lib/c.ts',
36
+ startLine: 1,
37
+ endLine: 20,
38
+ qualifiedName: 'funcC',
39
+ signature: '(z: boolean): void',
40
+ isExported: true,
41
+ },
42
+ ];
43
+ // Mock CodeGraph.open before importing the module under test
44
+ const { CodeGraph } = require('@colbymchenry/codegraph');
45
+ const openMock = mock.method(CodeGraph, 'open', async () => ({
46
+ getNodesByKind: (_kind) => nodes,
47
+ getCallers: (_id) => [],
48
+ close: () => { },
49
+ }));
50
+ const { handleListApis } = await import('./cmd-list-apis.js');
51
+ // Test 1: all=true — all symbols appear, grouped by directory
52
+ {
53
+ const chunks = [];
54
+ ctx.mock.method(process.stdout, 'write', (chunk) => {
55
+ chunks.push(String(chunk));
56
+ });
57
+ await handleListApis('/fake/root', undefined, { all: true });
58
+ const output = chunks.join('');
59
+ assert.ok(output.includes('funcA'), 'all=true: should include exported funcA');
60
+ assert.ok(output.includes('funcB'), 'all=true: should include non-exported funcB');
61
+ assert.ok(output.includes('funcC'), 'all=true: should include exported funcC');
62
+ assert.ok(output.includes('=== src/feature/ ==='), 'all=true: should group src/feature/');
63
+ assert.ok(output.includes('=== src/lib/ ==='), 'all=true: should group src/lib/');
64
+ ctx.mock.reset();
65
+ }
66
+ // Test 2: all=false — only exported symbols, ungrouped
67
+ {
68
+ const chunks = [];
69
+ ctx.mock.method(process.stdout, 'write', (chunk) => {
70
+ chunks.push(String(chunk));
71
+ });
72
+ await handleListApis('/fake/root', undefined, { all: false });
73
+ const output = chunks.join('');
74
+ assert.ok(output.includes('funcA'), 'all=false: should include exported funcA');
75
+ assert.ok(!output.includes('funcB'), 'all=false: should NOT include non-exported funcB');
76
+ assert.ok(output.includes('funcC'), 'all=false: should include exported funcC');
77
+ assert.ok(!output.includes('=== src/feature/ ==='), 'all=false: should not group');
78
+ assert.ok(!output.includes('=== src/lib/ ==='), 'all=false: should not group');
79
+ }
80
+ // Clean up global mocks (CodeGraph.open)
81
+ mock.reset();
82
+ });
83
+ });
@@ -0,0 +1,5 @@
1
+ export interface InitOptions {
2
+ index?: boolean;
3
+ json?: boolean;
4
+ }
5
+ export declare function handleInit(projectRoot: string, options?: InitOptions): Promise<number>;
@@ -0,0 +1,50 @@
1
+ import { createOrOpenIndex, closeIndex } from './cg-instance.js';
2
+ import { formatSummary, formatOutput } from './formatter.js';
3
+ export async function handleInit(projectRoot, options = {}) {
4
+ const progressEvents = [];
5
+ const start = Date.now();
6
+ const cg = await createOrOpenIndex(projectRoot, {
7
+ index: options.index,
8
+ onProgress: (p) => {
9
+ progressEvents.push({ phase: p.phase, current: p.current, total: p.total });
10
+ if (process.stdout.isTTY) {
11
+ process.stdout.write(`\r Indexing: ${p.phase} ${p.current}/${p.total}${p.currentFile ? ` ${p.currentFile}` : ''}`);
12
+ }
13
+ },
14
+ });
15
+ if (options.index && process.stdout.isTTY) {
16
+ process.stdout.write('\n');
17
+ }
18
+ const stats = cg.getStats();
19
+ closeIndex(cg);
20
+ const durationMs = Date.now() - start;
21
+ const result = {
22
+ projectRoot,
23
+ initialized: true,
24
+ indexed: !!options.index,
25
+ durationMs,
26
+ stats: {
27
+ fileCount: stats.fileCount,
28
+ nodeCount: stats.nodeCount,
29
+ edgeCount: stats.edgeCount,
30
+ },
31
+ progress: progressEvents.length > 0 ? progressEvents : undefined,
32
+ };
33
+ if (options.json) {
34
+ process.stdout.write(formatOutput(result, { json: true }) + '\n');
35
+ }
36
+ else {
37
+ const summary = [
38
+ ['Project:', projectRoot],
39
+ ['Status:', 'Initialized'],
40
+ ];
41
+ if (options.index) {
42
+ summary.push(['Files:', stats.fileCount]);
43
+ summary.push(['Nodes:', stats.nodeCount]);
44
+ summary.push(['Edges:', stats.edgeCount]);
45
+ summary.push(['Duration:', `${durationMs}ms`]);
46
+ }
47
+ process.stdout.write(formatSummary(summary) + '\n');
48
+ }
49
+ return 0;
50
+ }