@laitszkin/apollo-toolkit 4.0.11 → 4.1.1

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 (145) hide show
  1. package/AGENTS.md +37 -27
  2. package/CHANGELOG.md +47 -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 +549 -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 +613 -5
  17. package/packages/tools/codegraph/dist/index.d.ts +3 -0
  18. package/packages/tools/codegraph/dist/index.js +343 -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 +362 -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/scripts/test.sh +39 -0
  133. package/skills/design/SKILL.md +33 -0
  134. package/skills/init-project-html/SKILL.md +66 -56
  135. package/skills/init-project-html/lib/atlas/assets/architecture.css +2 -1
  136. package/skills/init-project-html/lib/atlas/render.js +11 -1
  137. package/skills/init-project-html/lib/atlas/schema.js +44 -7
  138. package/skills/init-project-html/references/TEMPLATE_SPEC.md +20 -0
  139. package/skills/init-project-html/references/architecture.md +35 -35
  140. package/skills/init-project-html/references/definition.md +12 -17
  141. package/skills/update-project-html/README.md +16 -27
  142. package/skills/update-project-html/SKILL.md +54 -41
  143. package/skills/update-project-html/references/architecture.md +35 -35
  144. package/skills/update-project-html/references/definition.md +12 -17
  145. package/tsconfig.json +1 -0
@@ -0,0 +1,129 @@
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
+
6
+ export interface ExploreOptions {
7
+ json?: boolean;
8
+ feature?: string;
9
+ }
10
+
11
+ export async function handleExplore(
12
+ projectRoot: string,
13
+ query: string,
14
+ options: ExploreOptions = {},
15
+ ): Promise<number> {
16
+ const { CodeGraph } = require('@colbymchenry/codegraph');
17
+ const cg = await CodeGraph.open(projectRoot, { sync: false, readOnly: true });
18
+
19
+ // Step 1: Search for the query
20
+ const searchResults = cg.searchNodes(query, { limit: 10 });
21
+
22
+ if (searchResults.length === 0) {
23
+ process.stdout.write('No symbols found matching the query.\n');
24
+ closeIndex(cg);
25
+ return 0;
26
+ }
27
+
28
+ // Step 2: For each result, get callers, callees, and source code
29
+ type SymbolDetail = {
30
+ name: string;
31
+ kind: string;
32
+ filePath: string;
33
+ startLine: number;
34
+ endLine: number;
35
+ qualifiedName: string;
36
+ signature?: string;
37
+ callers: Array<{ name: string; filePath: string; startLine: number }>;
38
+ callees: Array<{ name: string; filePath: string; startLine: number }>;
39
+ code: string | null;
40
+ };
41
+
42
+ const details: SymbolDetail[] = [];
43
+ for (const result of searchResults) {
44
+ const node = result.node;
45
+ const callers = cg.getCallers(node.id).map((c: any) => ({
46
+ name: c.node.name,
47
+ filePath: c.node.filePath,
48
+ startLine: c.node.startLine,
49
+ }));
50
+ const callees = cg.getCallees(node.id).map((c: any) => ({
51
+ name: c.node.name,
52
+ filePath: c.node.filePath,
53
+ startLine: c.node.startLine,
54
+ }));
55
+ const code = await cg.getCode(node.id);
56
+
57
+ details.push({
58
+ name: node.name,
59
+ kind: node.kind,
60
+ filePath: node.filePath,
61
+ startLine: node.startLine,
62
+ endLine: node.endLine,
63
+ qualifiedName: node.qualifiedName,
64
+ signature: node.signature,
65
+ callers,
66
+ callees,
67
+ code,
68
+ });
69
+ }
70
+
71
+ closeIndex(cg);
72
+
73
+ if (options.json) {
74
+ process.stdout.write(formatOutput(details, { json: true }) + '\n');
75
+ return 0;
76
+ }
77
+
78
+ // Human-readable output — group by filePath
79
+ const grouped = new Map<string, SymbolDetail[]>();
80
+ for (const d of details) {
81
+ const group = grouped.get(d.filePath) ?? [];
82
+ group.push(d);
83
+ grouped.set(d.filePath, group);
84
+ }
85
+
86
+ if (options.feature) {
87
+ process.stdout.write(`Feature: ${options.feature}\n`);
88
+ }
89
+
90
+ for (const [filePath, symbols] of grouped) {
91
+ process.stdout.write(`\n=== ${filePath} ===\n\n`);
92
+ for (const d of symbols) {
93
+ process.stdout.write(` ${d.name} [${d.kind}] line ${d.startLine}-${d.endLine}\n`);
94
+ process.stdout.write(` QName: ${d.qualifiedName}\n`);
95
+ if (d.signature) process.stdout.write(` Signature: ${d.signature}\n`);
96
+
97
+ process.stdout.write(` Callers (${d.callers.length}):\n`);
98
+ if (d.callers.length === 0) {
99
+ process.stdout.write(' (none)\n');
100
+ } else {
101
+ for (const c of d.callers.slice(0, 20)) {
102
+ process.stdout.write(` ${c.name} ${c.filePath}:${c.startLine}\n`);
103
+ }
104
+ }
105
+
106
+ process.stdout.write(` Callees (${d.callees.length}):\n`);
107
+ if (d.callees.length === 0) {
108
+ process.stdout.write(' (none)\n');
109
+ } else {
110
+ for (const c of d.callees.slice(0, 20)) {
111
+ process.stdout.write(` ${c.name} ${c.filePath}:${c.startLine}\n`);
112
+ }
113
+ }
114
+
115
+ if (d.code) {
116
+ process.stdout.write(` Source (${d.filePath}):\n`);
117
+ const lines = d.code.split('\n');
118
+ for (let i = 0; i < Math.min(lines.length, 30); i++) {
119
+ process.stdout.write(` ${lines[i]}\n`);
120
+ }
121
+ if (lines.length > 30) {
122
+ process.stdout.write(` ... (${lines.length - 30} more lines)\n`);
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ return 0;
129
+ }
@@ -0,0 +1,94 @@
1
+ import { describe, it, mock } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createRequire } from 'node:module';
4
+
5
+ const require = createRequire(import.meta.url);
6
+
7
+ describe('REGTEST-R2-01: handleListApis --all flag splice', () => {
8
+ it('should include non-exported symbols with all=true and filter them with all=false', async (ctx) => {
9
+ // Arrange: three nodes — two exported, one non-exported
10
+ const nodes = [
11
+ {
12
+ id: 'n1',
13
+ name: 'funcA',
14
+ kind: 'function',
15
+ filePath: 'src/feature/a.ts',
16
+ startLine: 10,
17
+ endLine: 30,
18
+ qualifiedName: 'funcA',
19
+ signature: '(x: string): void',
20
+ isExported: true,
21
+ },
22
+ {
23
+ id: 'n2',
24
+ name: 'funcB',
25
+ kind: 'function',
26
+ filePath: 'src/lib/b.ts',
27
+ startLine: 5,
28
+ endLine: 25,
29
+ qualifiedName: 'funcB',
30
+ signature: '(y: number): string',
31
+ isExported: false,
32
+ },
33
+ {
34
+ id: 'n3',
35
+ name: 'funcC',
36
+ kind: 'function',
37
+ filePath: 'src/lib/c.ts',
38
+ startLine: 1,
39
+ endLine: 20,
40
+ qualifiedName: 'funcC',
41
+ signature: '(z: boolean): void',
42
+ isExported: true,
43
+ },
44
+ ];
45
+
46
+ // Mock CodeGraph.open before importing the module under test
47
+ const { CodeGraph } = require('@colbymchenry/codegraph');
48
+ const openMock = mock.method(CodeGraph, 'open', async () => ({
49
+ getNodesByKind: (_kind: string) => nodes,
50
+ getCallers: (_id: string) => [],
51
+ close: () => {},
52
+ }));
53
+
54
+ const { handleListApis } = await import('./cmd-list-apis.js');
55
+
56
+ // Test 1: all=true — all symbols appear, grouped by directory
57
+ {
58
+ const chunks: string[] = [];
59
+ ctx.mock.method(process.stdout, 'write', (chunk: string | Uint8Array) => {
60
+ chunks.push(String(chunk));
61
+ });
62
+
63
+ await handleListApis('/fake/root', undefined, { all: true });
64
+
65
+ const output = chunks.join('');
66
+ assert.ok(output.includes('funcA'), 'all=true: should include exported funcA');
67
+ assert.ok(output.includes('funcB'), 'all=true: should include non-exported funcB');
68
+ assert.ok(output.includes('funcC'), 'all=true: should include exported funcC');
69
+ assert.ok(output.includes('=== src/feature/ ==='), 'all=true: should group src/feature/');
70
+ assert.ok(output.includes('=== src/lib/ ==='), 'all=true: should group src/lib/');
71
+ ctx.mock.reset();
72
+ }
73
+
74
+ // Test 2: all=false — only exported symbols, ungrouped
75
+ {
76
+ const chunks: string[] = [];
77
+ ctx.mock.method(process.stdout, 'write', (chunk: string | Uint8Array) => {
78
+ chunks.push(String(chunk));
79
+ });
80
+
81
+ await handleListApis('/fake/root', undefined, { all: false });
82
+
83
+ const output = chunks.join('');
84
+ assert.ok(output.includes('funcA'), 'all=false: should include exported funcA');
85
+ assert.ok(!output.includes('funcB'), 'all=false: should NOT include non-exported funcB');
86
+ assert.ok(output.includes('funcC'), 'all=false: should include exported funcC');
87
+ assert.ok(!output.includes('=== src/feature/ ==='), 'all=false: should not group');
88
+ assert.ok(!output.includes('=== src/lib/ ==='), 'all=false: should not group');
89
+ }
90
+
91
+ // Clean up global mocks (CodeGraph.open)
92
+ mock.reset();
93
+ });
94
+ });
@@ -0,0 +1,68 @@
1
+ import { describe, it, mock } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ // Mock cg-instance before importing cmd-init so the mock takes effect
5
+ // before module resolution caches anything.
6
+ mock.module('./cg-instance.js', {
7
+ namedExports: {
8
+ createOrOpenIndex: async (_projectRoot: string, _options?: object) => ({
9
+ getStats: () => ({ fileCount: 42, nodeCount: 1280, edgeCount: 5600 }),
10
+ close: () => {},
11
+ }),
12
+ closeIndex: () => {},
13
+ },
14
+ });
15
+
16
+ describe('handleInit', () => {
17
+ it('should include Duration: in TTY summary output when --index is used', async () => {
18
+ const writes: string[] = [];
19
+ const origWrite = process.stdout.write.bind(process.stdout);
20
+ process.stdout.write = (
21
+ chunk: unknown,
22
+ ..._rest: unknown[]
23
+ ): boolean => {
24
+ writes.push(String(chunk));
25
+ return true;
26
+ };
27
+
28
+ try {
29
+ const { handleInit } = await import('./cmd-init.js');
30
+ const code = await handleInit('/tmp/test-project', { index: true, json: false });
31
+
32
+ assert.strictEqual(code, 0);
33
+ const output = writes.join('');
34
+ assert.ok(
35
+ output.includes('Duration:'),
36
+ `Expected summary output to contain "Duration:", got:\n${output}`,
37
+ );
38
+ } finally {
39
+ process.stdout.write = origWrite;
40
+ }
41
+ });
42
+
43
+ it('should include durationMs in JSON output when --index is used', async () => {
44
+ const writes: string[] = [];
45
+ const origWrite = process.stdout.write.bind(process.stdout);
46
+ process.stdout.write = (
47
+ chunk: unknown,
48
+ ..._rest: unknown[]
49
+ ): boolean => {
50
+ writes.push(String(chunk));
51
+ return true;
52
+ };
53
+
54
+ try {
55
+ const { handleInit } = await import('./cmd-init.js');
56
+ const code = await handleInit('/tmp/test-project', { index: true, json: true });
57
+
58
+ assert.strictEqual(code, 0);
59
+ const output = writes.join('');
60
+ assert.ok(
61
+ output.includes('durationMs'),
62
+ `Expected JSON output to contain "durationMs", got:\n${output}`,
63
+ );
64
+ } finally {
65
+ process.stdout.write = origWrite;
66
+ }
67
+ });
68
+ });
@@ -0,0 +1,60 @@
1
+ import { createOrOpenIndex, closeIndex } from './cg-instance.js';
2
+ import { formatSummary, formatOutput } from './formatter.js';
3
+
4
+ export interface InitOptions {
5
+ index?: boolean;
6
+ json?: boolean;
7
+ }
8
+
9
+ export async function handleInit(projectRoot: string, options: InitOptions = {}): Promise<number> {
10
+ const progressEvents: Array<{ phase: string; current: number; total: number }> = [];
11
+ const start = Date.now();
12
+ const cg = await createOrOpenIndex(projectRoot, {
13
+ index: options.index,
14
+ onProgress: (p) => {
15
+ progressEvents.push({ phase: p.phase, current: p.current, total: p.total });
16
+ if (process.stdout.isTTY) {
17
+ process.stdout.write(`\r Indexing: ${p.phase} ${p.current}/${p.total}${p.currentFile ? ` ${p.currentFile}` : ''}`);
18
+ }
19
+ },
20
+ });
21
+
22
+ if (options.index && process.stdout.isTTY) {
23
+ process.stdout.write('\n');
24
+ }
25
+
26
+ const stats = cg.getStats();
27
+ closeIndex(cg);
28
+ const durationMs = Date.now() - start;
29
+
30
+ const result = {
31
+ projectRoot,
32
+ initialized: true,
33
+ indexed: !!options.index,
34
+ durationMs,
35
+ stats: {
36
+ fileCount: stats.fileCount,
37
+ nodeCount: stats.nodeCount,
38
+ edgeCount: stats.edgeCount,
39
+ },
40
+ progress: progressEvents.length > 0 ? progressEvents : undefined,
41
+ };
42
+
43
+ if (options.json) {
44
+ process.stdout.write(formatOutput(result, { json: true }) + '\n');
45
+ } else {
46
+ const summary: [string, string | number][] = [
47
+ ['Project:', projectRoot],
48
+ ['Status:', 'Initialized'],
49
+ ];
50
+ if (options.index) {
51
+ summary.push(['Files:', stats.fileCount]);
52
+ summary.push(['Nodes:', stats.nodeCount]);
53
+ summary.push(['Edges:', stats.edgeCount]);
54
+ summary.push(['Duration:', `${durationMs}ms`]);
55
+ }
56
+ process.stdout.write(formatSummary(summary) + '\n');
57
+ }
58
+
59
+ return 0;
60
+ }
@@ -0,0 +1,80 @@
1
+ import { describe, it, mock } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createRequire } from 'node:module';
4
+
5
+ const require = createRequire(import.meta.url);
6
+
7
+ describe('handleListApis', () => {
8
+ it('should group APIs by directory when --all is used and show ungrouped output without --all', async (ctx) => {
9
+ const nodes = [
10
+ {
11
+ id: 'n1',
12
+ name: 'funcA',
13
+ kind: 'function',
14
+ filePath: 'src/feature/a.ts',
15
+ startLine: 10,
16
+ endLine: 30,
17
+ qualifiedName: 'funcA',
18
+ signature: '(x: string): void',
19
+ isExported: true,
20
+ },
21
+ {
22
+ id: 'n2',
23
+ name: 'funcB',
24
+ kind: 'function',
25
+ filePath: 'src/lib/b.ts',
26
+ startLine: 5,
27
+ endLine: 25,
28
+ qualifiedName: 'funcB',
29
+ signature: '(y: number): string',
30
+ isExported: true,
31
+ },
32
+ ];
33
+
34
+ // Use manual require + mock.method instead of ctx.mock.module,
35
+ // because lazy CJS require() inside handlers bypasses mock.module interception.
36
+ const { CodeGraph } = require('@colbymchenry/codegraph');
37
+ const openMock = mock.method(CodeGraph, 'open', async () => ({
38
+ getNodesByKind: (_kind: string) => nodes,
39
+ getCallers: (_id: string) => [],
40
+ close: () => {},
41
+ }));
42
+
43
+ const { handleListApis } = await import('./cmd-list-apis.js');
44
+
45
+ // Test 1: --all produces directory-grouped output
46
+ {
47
+ const chunks: string[] = [];
48
+ ctx.mock.method(process.stdout, 'write', (chunk: string | Uint8Array) => {
49
+ chunks.push(String(chunk));
50
+ });
51
+
52
+ await handleListApis('/fake/root', undefined, { all: true });
53
+
54
+ const output = chunks.join('');
55
+ assert.ok(output.includes('=== src/feature/ ==='), 'should group under src/feature/');
56
+ assert.ok(output.includes('=== src/lib/ ==='), 'should group under src/lib/');
57
+ assert.ok(output.includes('funcA'), 'should list funcA in output');
58
+ assert.ok(output.includes('funcB'), 'should list funcB in output');
59
+ ctx.mock.reset();
60
+ }
61
+
62
+ // Test 2: without --all, output is not grouped
63
+ {
64
+ const chunks: string[] = [];
65
+ ctx.mock.method(process.stdout, 'write', (chunk: string | Uint8Array) => {
66
+ chunks.push(String(chunk));
67
+ });
68
+
69
+ await handleListApis('/fake/root', undefined, { all: false });
70
+
71
+ const output = chunks.join('');
72
+ assert.ok(!output.includes('=== src/feature/ ==='), 'should not group under src/feature/');
73
+ assert.ok(!output.includes('=== src/lib/ ==='), 'should not group under src/lib/');
74
+ assert.ok(output.includes('APIs'), 'should start with APIs count');
75
+ }
76
+
77
+ // Clean up all mocks (including CodeGraph.open)
78
+ mock.reset();
79
+ });
80
+ });
@@ -0,0 +1,90 @@
1
+ import { createRequire } from 'node:module';
2
+ const require = createRequire(import.meta.url);
3
+ import { closeIndex } from './cg-instance.js';
4
+ import { formatApiList, formatApiListGrouped, formatOutput } from './formatter.js';
5
+
6
+ export interface ListApisOptions {
7
+ json?: boolean;
8
+ all?: boolean;
9
+ }
10
+
11
+ export async function handleListApis(
12
+ projectRoot: string,
13
+ feature?: string,
14
+ options: ListApisOptions = {},
15
+ ): Promise<number> {
16
+ const { CodeGraph } = require('@colbymchenry/codegraph');
17
+ const cg = await CodeGraph.open(projectRoot, { sync: false, readOnly: true });
18
+
19
+ let nodes = cg.getNodesByKind('function');
20
+
21
+ // Filter by feature directory if specified
22
+ if (feature) {
23
+ const featurePath = feature.replace(/^\/?/, '').replace(/\/?$/, '/');
24
+ nodes = nodes.filter((n: any) => n.filePath.startsWith(featurePath));
25
+ }
26
+
27
+ // Only exported functions unless --all is specified
28
+ if (!options.all) {
29
+ nodes = nodes.filter((n: any) => n.isExported);
30
+ }
31
+
32
+ type ApiEntry = {
33
+ name: string;
34
+ kind: string;
35
+ filePath: string;
36
+ startLine: number;
37
+ endLine: number;
38
+ qualifiedName: string;
39
+ signature?: string;
40
+ isExported: boolean;
41
+ callerCount: number;
42
+ callers: Array<{ name: string; filePath: string; startLine: number }>;
43
+ };
44
+
45
+ const apis: ApiEntry[] = [];
46
+ for (const node of nodes) {
47
+ const callers = cg.getCallers(node.id).map((c: any) => ({
48
+ name: c.node.name,
49
+ filePath: c.node.filePath,
50
+ startLine: c.node.startLine,
51
+ }));
52
+ apis.push({
53
+ name: node.name,
54
+ kind: node.kind,
55
+ filePath: node.filePath,
56
+ startLine: node.startLine,
57
+ endLine: node.endLine,
58
+ qualifiedName: node.qualifiedName,
59
+ signature: node.signature,
60
+ isExported: !!node.isExported,
61
+ callerCount: callers.length,
62
+ callers,
63
+ });
64
+ }
65
+
66
+ closeIndex(cg);
67
+
68
+ if (options.json) {
69
+ // For JSON with --all, group by directory
70
+ if (options.all) {
71
+ const grouped: Record<string, ApiEntry[]> = {};
72
+ for (const api of apis) {
73
+ const dir = api.filePath.substring(0, api.filePath.lastIndexOf('/'));
74
+ if (!grouped[dir]) grouped[dir] = [];
75
+ grouped[dir].push(api);
76
+ }
77
+ process.stdout.write(formatOutput(grouped, { json: true }) + '\n');
78
+ } else {
79
+ process.stdout.write(formatOutput(apis, { json: true }) + '\n');
80
+ }
81
+ } else {
82
+ if (options.all) {
83
+ process.stdout.write(formatApiListGrouped(apis) + '\n');
84
+ } else {
85
+ process.stdout.write(formatApiList(apis) + '\n');
86
+ }
87
+ }
88
+
89
+ return 0;
90
+ }
@@ -0,0 +1,37 @@
1
+ import { describe, it, mock } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createRequire } from 'node:module';
4
+
5
+ const require = createRequire(import.meta.url);
6
+
7
+ describe('REGTEST-R2-03: handleSearch — CodeGraph init check', () => {
8
+ it('should return exit code 1 when CodeGraph is not initialized', async () => {
9
+ // Mock CodeGraph.isInitialized to return false before importing the module under test
10
+ const { CodeGraph } = require('@colbymchenry/codegraph') as { CodeGraph: { isInitialized: Function; open: Function } };
11
+ const initMock = mock.method(CodeGraph, 'isInitialized', () => false);
12
+
13
+ // Import after mock is in place
14
+ const { handleSearch } = await import('./cmd-search.js');
15
+
16
+ // Capture stderr writes
17
+ const stderrChunks: string[] = [];
18
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
19
+ process.stderr.write = (chunk: unknown) => {
20
+ stderrChunks.push(String(chunk));
21
+ return true;
22
+ };
23
+
24
+ try {
25
+ const exitCode = await handleSearch('/test/project', 'testQuery', {});
26
+ assert.strictEqual(exitCode, 1);
27
+ const stderrOutput = stderrChunks.join('');
28
+ assert.ok(
29
+ stderrOutput.includes('CodeGraph is not initialized'),
30
+ 'stderr should contain "CodeGraph is not initialized" message',
31
+ );
32
+ } finally {
33
+ process.stderr.write = originalStderrWrite;
34
+ initMock.mock.restore();
35
+ }
36
+ });
37
+ });
@@ -0,0 +1,32 @@
1
+ import { createRequire } from 'node:module';
2
+ const require = createRequire(import.meta.url);
3
+ import { closeIndex } from './cg-instance.js';
4
+ import { formatSearchResults, formatOutput } from './formatter.js';
5
+
6
+ export interface SearchOptions {
7
+ limit?: number;
8
+ json?: boolean;
9
+ }
10
+
11
+ export async function handleSearch(
12
+ projectRoot: string,
13
+ query: string,
14
+ options: SearchOptions = {},
15
+ ): Promise<number> {
16
+ const { CodeGraph } = require('@colbymchenry/codegraph');
17
+ if (!CodeGraph.isInitialized(projectRoot)) {
18
+ process.stderr.write('CodeGraph is not initialized. Run `apltk codegraph init` first.\n');
19
+ return 1;
20
+ }
21
+ const cg = await CodeGraph.open(projectRoot, { sync: false, readOnly: true });
22
+ const results = cg.searchNodes(query, { limit: options.limit ?? 20 });
23
+ closeIndex(cg);
24
+
25
+ if (options.json) {
26
+ process.stdout.write(formatOutput(results, { json: true }) + '\n');
27
+ } else {
28
+ process.stdout.write(formatSearchResults(results) + '\n');
29
+ }
30
+
31
+ return 0;
32
+ }
@@ -0,0 +1,86 @@
1
+ import { describe, it, mock } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createRequire } from 'node:module';
4
+
5
+ const require = createRequire(import.meta.url);
6
+
7
+ describe('REGTEST-6: handleStatus — Languages section', () => {
8
+ it('should display filesByLanguage entries in human-readable output', async () => {
9
+ const testStats = {
10
+ fileCount: 100,
11
+ nodeCount: 500,
12
+ edgeCount: 200,
13
+ dbSizeBytes: 1_024_000,
14
+ lastUpdated: new Date('2026-06-01T12:00:00Z').toISOString(),
15
+ nodesByKind: { function: 50, class: 10 },
16
+ edgesByKind: { calls: 30, extends: 5 },
17
+ filesByLanguage: { typescript: 10, javascript: 5 },
18
+ };
19
+
20
+ const mockCg = {
21
+ getStats: () => testStats,
22
+ close: () => {},
23
+ };
24
+
25
+ // Load the same CodeGraph module that cmd-status.js uses (shared CJS cache),
26
+ // then mock CodeGraph.open + isInitialized so handleStatus uses our fake instance.
27
+ const { CodeGraph } = require('@colbymchenry/codegraph') as { CodeGraph: { open: Function; isInitialized: Function } };
28
+ const openMock = mock.method(CodeGraph, 'open', async () => mockCg);
29
+ const initMock = mock.method(CodeGraph, 'isInitialized', () => true);
30
+
31
+ // Import the module under test AFTER the mock is in place
32
+ const { handleStatus } = await import('./cmd-status.js');
33
+
34
+ // Capture stdout writes
35
+ const chunks: string[] = [];
36
+ const originalWrite = process.stdout.write.bind(process.stdout);
37
+ process.stdout.write = (chunk: unknown) => {
38
+ chunks.push(String(chunk));
39
+ return true;
40
+ };
41
+
42
+ try {
43
+ const exitCode = await handleStatus('/test/project', {});
44
+
45
+ assert.strictEqual(exitCode, 0);
46
+
47
+ const output = chunks.join('');
48
+ assert.ok(output.includes('Languages:'), 'Output should contain "Languages:" section header');
49
+ assert.ok(output.includes('typescript'), 'Output should contain "typescript" language name');
50
+ assert.ok(output.includes('javascript'), 'Output should contain "javascript" language name');
51
+ } finally {
52
+ process.stdout.write = originalWrite;
53
+ openMock.mock.restore();
54
+ initMock.mock.restore();
55
+ }
56
+ });
57
+
58
+ it('REGTEST-R2-02: should return exit code 1 when CodeGraph is not initialized', async () => {
59
+ const { CodeGraph } = require('@colbymchenry/codegraph') as { CodeGraph: { isInitialized: Function; open: Function } };
60
+ const initMock = mock.method(CodeGraph, 'isInitialized', () => false);
61
+
62
+ // Import fresh (module cache returns same CodeGraph reference with mocked isInitialized)
63
+ const { handleStatus } = await import('./cmd-status.js');
64
+
65
+ // Capture stderr writes
66
+ const stderrChunks: string[] = [];
67
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
68
+ process.stderr.write = (chunk: unknown) => {
69
+ stderrChunks.push(String(chunk));
70
+ return true;
71
+ };
72
+
73
+ try {
74
+ const exitCode = await handleStatus('/test/project', {});
75
+ assert.strictEqual(exitCode, 1);
76
+ const stderrOutput = stderrChunks.join('');
77
+ assert.ok(
78
+ stderrOutput.includes('CodeGraph is not initialized'),
79
+ 'stderr should contain "CodeGraph is not initialized" message',
80
+ );
81
+ } finally {
82
+ process.stderr.write = originalStderrWrite;
83
+ initMock.mock.restore();
84
+ }
85
+ });
86
+ });