@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,51 @@
1
+ import { describe, it, mock } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ // Mock cg-instance before importing cmd-init so the mock takes effect
4
+ // before module resolution caches anything.
5
+ mock.module('./cg-instance.js', {
6
+ namedExports: {
7
+ createOrOpenIndex: async (_projectRoot, _options) => ({
8
+ getStats: () => ({ fileCount: 42, nodeCount: 1280, edgeCount: 5600 }),
9
+ close: () => { },
10
+ }),
11
+ closeIndex: () => { },
12
+ },
13
+ });
14
+ describe('handleInit', () => {
15
+ it('should include Duration: in TTY summary output when --index is used', async () => {
16
+ const writes = [];
17
+ const origWrite = process.stdout.write.bind(process.stdout);
18
+ process.stdout.write = (chunk, ..._rest) => {
19
+ writes.push(String(chunk));
20
+ return true;
21
+ };
22
+ try {
23
+ const { handleInit } = await import('./cmd-init.js');
24
+ const code = await handleInit('/tmp/test-project', { index: true, json: false });
25
+ assert.strictEqual(code, 0);
26
+ const output = writes.join('');
27
+ assert.ok(output.includes('Duration:'), `Expected summary output to contain "Duration:", got:\n${output}`);
28
+ }
29
+ finally {
30
+ process.stdout.write = origWrite;
31
+ }
32
+ });
33
+ it('should include durationMs in JSON output when --index is used', async () => {
34
+ const writes = [];
35
+ const origWrite = process.stdout.write.bind(process.stdout);
36
+ process.stdout.write = (chunk, ..._rest) => {
37
+ writes.push(String(chunk));
38
+ return true;
39
+ };
40
+ try {
41
+ const { handleInit } = await import('./cmd-init.js');
42
+ const code = await handleInit('/tmp/test-project', { index: true, json: true });
43
+ assert.strictEqual(code, 0);
44
+ const output = writes.join('');
45
+ assert.ok(output.includes('durationMs'), `Expected JSON output to contain "durationMs", got:\n${output}`);
46
+ }
47
+ finally {
48
+ process.stdout.write = origWrite;
49
+ }
50
+ });
51
+ });
@@ -0,0 +1,5 @@
1
+ export interface ListApisOptions {
2
+ json?: boolean;
3
+ all?: boolean;
4
+ }
5
+ export declare function handleListApis(projectRoot: string, feature?: string, options?: ListApisOptions): Promise<number>;
@@ -0,0 +1,64 @@
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
+ export async function handleListApis(projectRoot, feature, options = {}) {
6
+ const { CodeGraph } = require('@colbymchenry/codegraph');
7
+ const cg = await CodeGraph.open(projectRoot, { sync: false, readOnly: true });
8
+ let nodes = cg.getNodesByKind('function');
9
+ // Filter by feature directory if specified
10
+ if (feature) {
11
+ const featurePath = feature.replace(/^\/?/, '').replace(/\/?$/, '/');
12
+ nodes = nodes.filter((n) => n.filePath.startsWith(featurePath));
13
+ }
14
+ // Only exported functions unless --all is specified
15
+ if (!options.all) {
16
+ nodes = nodes.filter((n) => n.isExported);
17
+ }
18
+ const apis = [];
19
+ for (const node of nodes) {
20
+ const callers = cg.getCallers(node.id).map((c) => ({
21
+ name: c.node.name,
22
+ filePath: c.node.filePath,
23
+ startLine: c.node.startLine,
24
+ }));
25
+ apis.push({
26
+ name: node.name,
27
+ kind: node.kind,
28
+ filePath: node.filePath,
29
+ startLine: node.startLine,
30
+ endLine: node.endLine,
31
+ qualifiedName: node.qualifiedName,
32
+ signature: node.signature,
33
+ isExported: !!node.isExported,
34
+ callerCount: callers.length,
35
+ callers,
36
+ });
37
+ }
38
+ closeIndex(cg);
39
+ if (options.json) {
40
+ // For JSON with --all, group by directory
41
+ if (options.all) {
42
+ const grouped = {};
43
+ for (const api of apis) {
44
+ const dir = api.filePath.substring(0, api.filePath.lastIndexOf('/'));
45
+ if (!grouped[dir])
46
+ grouped[dir] = [];
47
+ grouped[dir].push(api);
48
+ }
49
+ process.stdout.write(formatOutput(grouped, { json: true }) + '\n');
50
+ }
51
+ else {
52
+ process.stdout.write(formatOutput(apis, { json: true }) + '\n');
53
+ }
54
+ }
55
+ else {
56
+ if (options.all) {
57
+ process.stdout.write(formatApiListGrouped(apis) + '\n');
58
+ }
59
+ else {
60
+ process.stdout.write(formatApiList(apis) + '\n');
61
+ }
62
+ }
63
+ return 0;
64
+ }
@@ -0,0 +1,69 @@
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('handleListApis', () => {
6
+ it('should group APIs by directory when --all is used and show ungrouped output without --all', async (ctx) => {
7
+ const nodes = [
8
+ {
9
+ id: 'n1',
10
+ name: 'funcA',
11
+ kind: 'function',
12
+ filePath: 'src/feature/a.ts',
13
+ startLine: 10,
14
+ endLine: 30,
15
+ qualifiedName: 'funcA',
16
+ signature: '(x: string): void',
17
+ isExported: true,
18
+ },
19
+ {
20
+ id: 'n2',
21
+ name: 'funcB',
22
+ kind: 'function',
23
+ filePath: 'src/lib/b.ts',
24
+ startLine: 5,
25
+ endLine: 25,
26
+ qualifiedName: 'funcB',
27
+ signature: '(y: number): string',
28
+ isExported: true,
29
+ },
30
+ ];
31
+ // Use manual require + mock.method instead of ctx.mock.module,
32
+ // because lazy CJS require() inside handlers bypasses mock.module interception.
33
+ const { CodeGraph } = require('@colbymchenry/codegraph');
34
+ const openMock = mock.method(CodeGraph, 'open', async () => ({
35
+ getNodesByKind: (_kind) => nodes,
36
+ getCallers: (_id) => [],
37
+ close: () => { },
38
+ }));
39
+ const { handleListApis } = await import('./cmd-list-apis.js');
40
+ // Test 1: --all produces directory-grouped output
41
+ {
42
+ const chunks = [];
43
+ ctx.mock.method(process.stdout, 'write', (chunk) => {
44
+ chunks.push(String(chunk));
45
+ });
46
+ await handleListApis('/fake/root', undefined, { all: true });
47
+ const output = chunks.join('');
48
+ assert.ok(output.includes('=== src/feature/ ==='), 'should group under src/feature/');
49
+ assert.ok(output.includes('=== src/lib/ ==='), 'should group under src/lib/');
50
+ assert.ok(output.includes('funcA'), 'should list funcA in output');
51
+ assert.ok(output.includes('funcB'), 'should list funcB in output');
52
+ ctx.mock.reset();
53
+ }
54
+ // Test 2: without --all, output is not grouped
55
+ {
56
+ const chunks = [];
57
+ ctx.mock.method(process.stdout, 'write', (chunk) => {
58
+ chunks.push(String(chunk));
59
+ });
60
+ await handleListApis('/fake/root', undefined, { all: false });
61
+ const output = chunks.join('');
62
+ assert.ok(!output.includes('=== src/feature/ ==='), 'should not group under src/feature/');
63
+ assert.ok(!output.includes('=== src/lib/ ==='), 'should not group under src/lib/');
64
+ assert.ok(output.includes('APIs'), 'should start with APIs count');
65
+ }
66
+ // Clean up all mocks (including CodeGraph.open)
67
+ mock.reset();
68
+ });
69
+ });
@@ -0,0 +1,5 @@
1
+ export interface SearchOptions {
2
+ limit?: number;
3
+ json?: boolean;
4
+ }
5
+ export declare function handleSearch(projectRoot: string, query: string, options?: SearchOptions): Promise<number>;
@@ -0,0 +1,21 @@
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
+ export async function handleSearch(projectRoot, query, options = {}) {
6
+ const { CodeGraph } = require('@colbymchenry/codegraph');
7
+ if (!CodeGraph.isInitialized(projectRoot)) {
8
+ process.stderr.write('CodeGraph is not initialized. Run `apltk codegraph init` first.\n');
9
+ return 1;
10
+ }
11
+ const cg = await CodeGraph.open(projectRoot, { sync: false, readOnly: true });
12
+ const results = cg.searchNodes(query, { limit: options.limit ?? 20 });
13
+ closeIndex(cg);
14
+ if (options.json) {
15
+ process.stdout.write(formatOutput(results, { json: true }) + '\n');
16
+ }
17
+ else {
18
+ process.stdout.write(formatSearchResults(results) + '\n');
19
+ }
20
+ return 0;
21
+ }
@@ -0,0 +1,30 @@
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-03: handleSearch — CodeGraph init check', () => {
6
+ it('should return exit code 1 when CodeGraph is not initialized', async () => {
7
+ // Mock CodeGraph.isInitialized to return false before importing the module under test
8
+ const { CodeGraph } = require('@colbymchenry/codegraph');
9
+ const initMock = mock.method(CodeGraph, 'isInitialized', () => false);
10
+ // Import after mock is in place
11
+ const { handleSearch } = await import('./cmd-search.js');
12
+ // Capture stderr writes
13
+ const stderrChunks = [];
14
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
15
+ process.stderr.write = (chunk) => {
16
+ stderrChunks.push(String(chunk));
17
+ return true;
18
+ };
19
+ try {
20
+ const exitCode = await handleSearch('/test/project', 'testQuery', {});
21
+ assert.strictEqual(exitCode, 1);
22
+ const stderrOutput = stderrChunks.join('');
23
+ assert.ok(stderrOutput.includes('CodeGraph is not initialized'), 'stderr should contain "CodeGraph is not initialized" message');
24
+ }
25
+ finally {
26
+ process.stderr.write = originalStderrWrite;
27
+ initMock.mock.restore();
28
+ }
29
+ });
30
+ });
@@ -0,0 +1,4 @@
1
+ export interface StatusOptions {
2
+ json?: boolean;
3
+ }
4
+ export declare function handleStatus(projectRoot: string, options?: StatusOptions): Promise<number>;
@@ -0,0 +1,44 @@
1
+ import { createRequire } from 'node:module';
2
+ const require = createRequire(import.meta.url);
3
+ import { closeIndex, getCodeGraphModule } from './cg-instance.js';
4
+ import { formatSummary, formatOutput } from './formatter.js';
5
+ export async function handleStatus(projectRoot, options = {}) {
6
+ if (!getCodeGraphModule().CodeGraph.isInitialized(projectRoot)) {
7
+ process.stderr.write('CodeGraph is not initialized. Run `apltk codegraph init` first.\n');
8
+ return 1;
9
+ }
10
+ const cg = await getCodeGraphModule().CodeGraph.open(projectRoot, { sync: false, readOnly: true });
11
+ const stats = cg.getStats();
12
+ closeIndex(cg);
13
+ if (options.json) {
14
+ process.stdout.write(formatOutput(stats, { json: true }) + '\n');
15
+ }
16
+ else {
17
+ // Summarize nodes by kind (skip zero entries)
18
+ const kindEntries = Object.entries(stats.nodesByKind).filter(([, v]) => v > 0);
19
+ const nodeKindSummary = kindEntries.map(([kind, count]) => ` ${kind.padEnd(14)} ${count}`).join('\n');
20
+ const edgeKindEntries = Object.entries(stats.edgesByKind).filter(([, v]) => v > 0);
21
+ const edgeKindSummary = edgeKindEntries.map(([kind, count]) => ` ${kind.padEnd(14)} ${count}`).join('\n');
22
+ const langEntries = Object.entries(stats.filesByLanguage || {}).filter(([, v]) => v > 0);
23
+ const langSummary = langEntries.length > 0
24
+ ? langEntries.map(([lang, count]) => ` ${lang.padEnd(14)} ${count}`).join('\n')
25
+ : ' (no files indexed)';
26
+ const summary = [
27
+ ['Project:', projectRoot],
28
+ ['Files:', stats.fileCount],
29
+ ['Nodes:', stats.nodeCount],
30
+ ['Edges:', stats.edgeCount],
31
+ ['DB size:', `${(stats.dbSizeBytes / 1024).toFixed(1)} KB`],
32
+ ['Last updated:', new Date(stats.lastUpdated).toISOString()],
33
+ ['', ''],
34
+ ['Nodes by kind:', ''],
35
+ ];
36
+ process.stdout.write(formatSummary(summary) + '\n');
37
+ process.stdout.write(nodeKindSummary + '\n');
38
+ process.stdout.write('\nEdges by kind:\n');
39
+ process.stdout.write(edgeKindSummary + '\n');
40
+ process.stdout.write('\nLanguages:\n');
41
+ process.stdout.write(langSummary + '\n');
42
+ }
43
+ return 0;
44
+ }
@@ -0,0 +1,72 @@
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-6: handleStatus — Languages section', () => {
6
+ it('should display filesByLanguage entries in human-readable output', async () => {
7
+ const testStats = {
8
+ fileCount: 100,
9
+ nodeCount: 500,
10
+ edgeCount: 200,
11
+ dbSizeBytes: 1_024_000,
12
+ lastUpdated: new Date('2026-06-01T12:00:00Z').toISOString(),
13
+ nodesByKind: { function: 50, class: 10 },
14
+ edgesByKind: { calls: 30, extends: 5 },
15
+ filesByLanguage: { typescript: 10, javascript: 5 },
16
+ };
17
+ const mockCg = {
18
+ getStats: () => testStats,
19
+ close: () => { },
20
+ };
21
+ // Load the same CodeGraph module that cmd-status.js uses (shared CJS cache),
22
+ // then mock CodeGraph.open + isInitialized so handleStatus uses our fake instance.
23
+ const { CodeGraph } = require('@colbymchenry/codegraph');
24
+ const openMock = mock.method(CodeGraph, 'open', async () => mockCg);
25
+ const initMock = mock.method(CodeGraph, 'isInitialized', () => true);
26
+ // Import the module under test AFTER the mock is in place
27
+ const { handleStatus } = await import('./cmd-status.js');
28
+ // Capture stdout writes
29
+ const chunks = [];
30
+ const originalWrite = process.stdout.write.bind(process.stdout);
31
+ process.stdout.write = (chunk) => {
32
+ chunks.push(String(chunk));
33
+ return true;
34
+ };
35
+ try {
36
+ const exitCode = await handleStatus('/test/project', {});
37
+ assert.strictEqual(exitCode, 0);
38
+ const output = chunks.join('');
39
+ assert.ok(output.includes('Languages:'), 'Output should contain "Languages:" section header');
40
+ assert.ok(output.includes('typescript'), 'Output should contain "typescript" language name');
41
+ assert.ok(output.includes('javascript'), 'Output should contain "javascript" language name');
42
+ }
43
+ finally {
44
+ process.stdout.write = originalWrite;
45
+ openMock.mock.restore();
46
+ initMock.mock.restore();
47
+ }
48
+ });
49
+ it('REGTEST-R2-02: should return exit code 1 when CodeGraph is not initialized', async () => {
50
+ const { CodeGraph } = require('@colbymchenry/codegraph');
51
+ const initMock = mock.method(CodeGraph, 'isInitialized', () => false);
52
+ // Import fresh (module cache returns same CodeGraph reference with mocked isInitialized)
53
+ const { handleStatus } = await import('./cmd-status.js');
54
+ // Capture stderr writes
55
+ const stderrChunks = [];
56
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
57
+ process.stderr.write = (chunk) => {
58
+ stderrChunks.push(String(chunk));
59
+ return true;
60
+ };
61
+ try {
62
+ const exitCode = await handleStatus('/test/project', {});
63
+ assert.strictEqual(exitCode, 1);
64
+ const stderrOutput = stderrChunks.join('');
65
+ assert.ok(stderrOutput.includes('CodeGraph is not initialized'), 'stderr should contain "CodeGraph is not initialized" message');
66
+ }
67
+ finally {
68
+ process.stderr.write = originalStderrWrite;
69
+ initMock.mock.restore();
70
+ }
71
+ });
72
+ });
@@ -0,0 +1,36 @@
1
+ export interface SurveyOptions {
2
+ feature?: string;
3
+ json?: boolean;
4
+ }
5
+ export interface SurveyReport {
6
+ directory: string;
7
+ feature?: string;
8
+ totalFiles: number;
9
+ totalSymbols: number;
10
+ files: Array<{
11
+ filePath: string;
12
+ language: string;
13
+ symbolCount: number;
14
+ }>;
15
+ entryPoints: Array<{
16
+ name: string;
17
+ kind: string;
18
+ filePath: string;
19
+ startLine: number;
20
+ isExported: boolean;
21
+ }>;
22
+ suggestedSubmodules: Array<{
23
+ slug: string;
24
+ kind: string;
25
+ role: string;
26
+ memberFunctions: string[];
27
+ memberFiles: string[];
28
+ }>;
29
+ suggestedEdges: Array<{
30
+ source: string;
31
+ target: string;
32
+ kind: string;
33
+ label: string;
34
+ }>;
35
+ }
36
+ export declare function handleSurvey(projectRoot: string, dirPath: string, options?: SurveyOptions): Promise<number>;
@@ -0,0 +1,142 @@
1
+ import { createRequire } from 'node:module';
2
+ const require = createRequire(import.meta.url);
3
+ import { existsSync } from 'node:fs';
4
+ import path from 'node:path';
5
+ import { closeIndex } from './cg-instance.js';
6
+ import { formatOutput } from './formatter.js';
7
+ import { scanDirectory } from './survey/scanner.js';
8
+ import { groupIntoSubmodules } from './survey/grouper.js';
9
+ export async function handleSurvey(projectRoot, dirPath, options = {}) {
10
+ // Check that the target directory exists
11
+ const targetPath = path.resolve(projectRoot, dirPath);
12
+ if (!existsSync(targetPath)) {
13
+ process.stderr.write(`Error: Directory not found: ${dirPath}\n`);
14
+ return 1;
15
+ }
16
+ const { CodeGraph } = require('@colbymchenry/codegraph');
17
+ const cg = await CodeGraph.open(projectRoot, { sync: false, readOnly: true });
18
+ // Scan the directory
19
+ const scan = await scanDirectory(cg, dirPath);
20
+ const fileSet = new Set(scan.files.map((f) => f.filePath));
21
+ // Group into submodule suggestions
22
+ const suggestions = groupIntoSubmodules(scan, cg);
23
+ // Build edge suggestions from cross-file call relationships
24
+ const edgeSuggestions = buildEdgeSuggestions(scan, cg, fileSet);
25
+ // Determine entry points: exported symbols called from outside the scanned directory
26
+ const entryPoints = scan.allSymbols.filter(s => {
27
+ if (!s.isExported)
28
+ return false;
29
+ const nodes = cg.getNodesByName(s.name);
30
+ for (const node of nodes) {
31
+ if (node.filePath !== s.filePath)
32
+ continue;
33
+ const callers = cg.getCallers(node.id);
34
+ for (const caller of callers) {
35
+ if (!fileSet.has(caller.node.filePath)) {
36
+ return true;
37
+ }
38
+ }
39
+ }
40
+ return false;
41
+ });
42
+ closeIndex(cg);
43
+ const report = {
44
+ directory: dirPath,
45
+ feature: options.feature,
46
+ totalFiles: scan.totalFiles,
47
+ totalSymbols: scan.totalSymbols,
48
+ files: scan.files.map((f) => ({
49
+ filePath: f.filePath,
50
+ language: f.language,
51
+ symbolCount: f.symbols.length,
52
+ })),
53
+ entryPoints,
54
+ suggestedSubmodules: suggestions.map((s) => ({
55
+ slug: s.slug,
56
+ kind: s.kind,
57
+ role: s.role,
58
+ memberFunctions: s.memberFunctions,
59
+ memberFiles: s.memberFiles,
60
+ })),
61
+ suggestedEdges: edgeSuggestions,
62
+ };
63
+ if (options.json) {
64
+ process.stdout.write(formatOutput(report, { json: true }) + '\n');
65
+ }
66
+ else {
67
+ // Human-readable output
68
+ process.stdout.write(`\n=== Survey: ${dirPath} ===\n`);
69
+ if (report.feature) {
70
+ process.stdout.write(`Feature: ${report.feature}\n`);
71
+ }
72
+ process.stdout.write('\n');
73
+ process.stdout.write(`Files: ${report.totalFiles} Symbols: ${report.totalSymbols}\n\n`);
74
+ process.stdout.write('Files:\n');
75
+ for (const f of report.files) {
76
+ process.stdout.write(` ${f.filePath} [${f.language}] (${f.symbolCount} symbols)\n`);
77
+ }
78
+ process.stdout.write('\nEntry Points:\n');
79
+ if (report.entryPoints.length === 0) {
80
+ process.stdout.write(' (none)\n');
81
+ }
82
+ else {
83
+ for (const ep of report.entryPoints) {
84
+ process.stdout.write(` ${ep.name} [${ep.kind}] ${ep.filePath}:${ep.startLine}\n`);
85
+ }
86
+ }
87
+ process.stdout.write('\nSuggested Submodules:\n');
88
+ if (report.suggestedSubmodules.length === 0) {
89
+ process.stdout.write(' (none)\n');
90
+ }
91
+ else {
92
+ for (const sub of report.suggestedSubmodules) {
93
+ process.stdout.write(` ${sub.slug} [${sub.kind}] ${sub.role}\n`);
94
+ process.stdout.write(` Functions: ${sub.memberFunctions.join(', ')}\n`);
95
+ process.stdout.write(` Files: ${sub.memberFiles.join(', ')}\n\n`);
96
+ }
97
+ }
98
+ process.stdout.write('Suggested Edges:\n');
99
+ if (report.suggestedEdges.length === 0) {
100
+ process.stdout.write(' (none)\n');
101
+ }
102
+ else {
103
+ for (const edge of report.suggestedEdges) {
104
+ process.stdout.write(` ${edge.source} --[${edge.kind}]--> ${edge.target} (${edge.label})\n`);
105
+ }
106
+ }
107
+ process.stdout.write('\n');
108
+ }
109
+ return 0;
110
+ }
111
+ /**
112
+ * Build suggested edges from cross-file call relationships in the scanned directory.
113
+ */
114
+ function buildEdgeSuggestions(scan, cg, fileSet) {
115
+ const edges = [];
116
+ const dedup = new Set();
117
+ // For each symbol in the scan, check if it calls symbols outside the scanned directory
118
+ for (const sym of scan.allSymbols) {
119
+ const nodes = cg.getNodesByName(sym.name);
120
+ for (const node of nodes) {
121
+ if (node.filePath !== sym.filePath)
122
+ continue;
123
+ const callees = cg.getCallees(node.id);
124
+ for (const callee of callees) {
125
+ // Only consider callees OUTSIDE the scanned directory (cross-boundary edges)
126
+ if (!fileSet.has(callee.node.filePath)) {
127
+ const edgeKey = `${sym.name}::${callee.node.name}`;
128
+ if (dedup.has(edgeKey))
129
+ continue;
130
+ dedup.add(edgeKey);
131
+ edges.push({
132
+ source: sym.name,
133
+ target: callee.node.name,
134
+ kind: 'call',
135
+ label: `${sym.filePath} -> ${callee.node.filePath}`,
136
+ });
137
+ }
138
+ }
139
+ }
140
+ }
141
+ return edges;
142
+ }