@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,362 @@
1
+ import type { ToolDefinition, ToolContext } from '@laitszkin/tool-registry';
2
+ import { findProjectRoot } from './lib/cg-instance.js';
3
+ import { handleInit } from './lib/cmd-init.js';
4
+ import { handleSync } from './lib/cmd-sync.js';
5
+ import { handleStatus } from './lib/cmd-status.js';
6
+ import { handleSearch } from './lib/cmd-search.js';
7
+ import { handleExplore } from './lib/cmd-explore.js';
8
+ import { handleSurvey } from './lib/cmd-survey.js';
9
+ import { handleListApis } from './lib/cmd-list-apis.js';
10
+ import { handleVerify } from './lib/cmd-verify.js';
11
+
12
+ export async function codegraphHandler(args: string[], context: ToolContext): Promise<number> {
13
+ const stdout = context.stdout || process.stdout;
14
+ const stderr = context.stderr || process.stderr;
15
+ const projectRoot = findProjectRoot(context.cwd || process.cwd());
16
+
17
+ // Parse --json flag early (can appear anywhere)
18
+ const jsonIndex = args.indexOf('--json');
19
+ const isJson = jsonIndex >= 0;
20
+ if (jsonIndex >= 0) args.splice(jsonIndex, 1);
21
+
22
+ // Main help: no args, --help, -h, or "help" subcommand
23
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h' || args[0] === 'help') {
24
+ printHelp(stdout);
25
+ return 0;
26
+ }
27
+
28
+ const subcommand = args[0];
29
+
30
+ // Per-subcommand help: e.g., "apltk codegraph search --help"
31
+ // Check for --help/-h at position 1 (after the subcommand name)
32
+ const rest = args.slice(1);
33
+ if (rest.includes('--help') || rest.includes('-h')) {
34
+ printSubcommandHelp(subcommand, stdout, stderr);
35
+ return 0;
36
+ }
37
+
38
+ // Parse --spec <dir> for verify
39
+ const specIndex = rest.indexOf('--spec');
40
+ let specDir: string | undefined;
41
+ if (specIndex >= 0 && specIndex + 1 < rest.length) {
42
+ specDir = rest[specIndex + 1];
43
+ rest.splice(specIndex, 2);
44
+ }
45
+
46
+ // Parse --all flag for list-apis
47
+ const allIndex = rest.indexOf('--all');
48
+ const isAll = allIndex >= 0;
49
+ if (allIndex >= 0) rest.splice(allIndex, 1);
50
+
51
+ // Parse --index flag for init
52
+ const shouldIndex = rest.includes('--index');
53
+ const indexIdx = rest.indexOf('--index');
54
+ if (indexIdx >= 0) rest.splice(indexIdx, 1);
55
+
56
+ // Parse --feature <name> for survey
57
+ const featureIndex = rest.indexOf('--feature');
58
+ let featureName: string | undefined;
59
+ if (featureIndex >= 0 && featureIndex + 1 < rest.length) {
60
+ featureName = rest[featureIndex + 1];
61
+ rest.splice(featureIndex, 2);
62
+ }
63
+
64
+ // Parse limit for search
65
+ const limitIndex = rest.indexOf('--limit');
66
+ let limit: number | undefined;
67
+ if (limitIndex >= 0 && limitIndex + 1 < rest.length) {
68
+ limit = parseInt(rest[limitIndex + 1], 10);
69
+ rest.splice(limitIndex, 2);
70
+ }
71
+
72
+ try {
73
+ switch (subcommand) {
74
+ case 'init':
75
+ return await handleInit(projectRoot, { index: shouldIndex, json: isJson });
76
+
77
+ case 'sync':
78
+ return await handleSync(projectRoot, { json: isJson });
79
+
80
+ case 'status':
81
+ return await handleStatus(projectRoot, { json: isJson });
82
+
83
+ case 'search': {
84
+ const query = rest.join(' ');
85
+ if (!query) {
86
+ stderr.write('Usage: apltk codegraph search <query> [--limit N] [--json]\n');
87
+ return 1;
88
+ }
89
+ return await handleSearch(projectRoot, query, { limit, json: isJson });
90
+ }
91
+
92
+ case 'explore': {
93
+ const query = rest.join(' ');
94
+ if (!query) {
95
+ stderr.write('Usage: apltk codegraph explore <query> [--json]\n');
96
+ return 1;
97
+ }
98
+ return await handleExplore(projectRoot, query, { json: isJson, feature: featureName });
99
+ }
100
+
101
+ case 'survey': {
102
+ const dirPath = rest[0] || '.';
103
+ return await handleSurvey(projectRoot, dirPath, { feature: featureName, json: isJson });
104
+ }
105
+
106
+ case 'list-apis': {
107
+ const pathArg = rest[0];
108
+ const combinedPath = featureName
109
+ ? (pathArg ? `${featureName}/${pathArg.replace(/^\//, '')}` : featureName)
110
+ : pathArg;
111
+ return await handleListApis(projectRoot, combinedPath, { all: isAll, json: isJson });
112
+ }
113
+
114
+ case 'verify': {
115
+ if (!specDir) {
116
+ stderr.write('Usage: apltk codegraph verify --spec <spec-dir> [--json]\n');
117
+ return 1;
118
+ }
119
+ return await handleVerify(projectRoot, specDir, { json: isJson });
120
+ }
121
+
122
+ default:
123
+ stderr.write(`Unknown subcommand: ${subcommand}\n\n`);
124
+ printHelp(stderr);
125
+ return 1;
126
+ }
127
+ } catch (error: any) {
128
+ if (error.code === 'MODULE_NOT_FOUND' || (error.message && error.message.includes('Cannot find module'))) {
129
+ stderr.write('`@colbymchenry/codegraph` is not installed. Run `npm install @colbymchenry/codegraph` in your project directory.\n');
130
+ } else {
131
+ stderr.write(`Error running codegraph ${subcommand}: ${error.message}\n`);
132
+ }
133
+ return 1;
134
+ }
135
+ }
136
+
137
+ function printHelp(stream: NodeJS.WriteStream): void {
138
+ stream.write(`Usage: apltk codegraph <subcommand> [options]
139
+
140
+ CodeGraph code intelligence — parse source code into a knowledge graph
141
+ of symbols (functions, classes) and relationships (call edges), backed by
142
+ a local SQLite database with FTS5 full-text search.
143
+
144
+ Powered by @colbymchenry/codegraph (tree-sitter-backed code knowledge graph).
145
+
146
+ Subcommands:
147
+
148
+ lifecycle:
149
+ init Initialize CodeGraph for the project
150
+ --index Run initial indexing immediately after init
151
+ --json JSON output
152
+
153
+ sync Sync the index with current file state
154
+ --json JSON output
155
+
156
+ status Show index statistics (files, nodes, edges, languages)
157
+ --json JSON output
158
+
159
+ discovery:
160
+ search <query> Search the code graph for symbols via FTS5
161
+ --limit N Max results (default: 20, max: 100)
162
+ --json JSON output
163
+
164
+ explore <query> Deep-dive on a symbol — show callers, callees, and source
165
+ --feature <name> Only show results within this feature
166
+ --json JSON output
167
+
168
+ survey [dir] Scan a directory, suggest submodule groupings and
169
+ cross-boundary edges for atlas modelling
170
+ --feature <name> Feature context for grouping
171
+ --json JSON output
172
+
173
+ list-apis [path] List public APIs in the project or within a sub-path
174
+ --all Include non-exported (internal) symbols
175
+ --json JSON output
176
+
177
+ validation:
178
+ verify --spec <dir>
179
+ Verify a spec overlay against actual code —
180
+ checks that every declared feature, submodule,
181
+ function, and edge exists in the code graph
182
+ --json JSON output
183
+
184
+ Global options:
185
+ --json Output as JSON instead of human-readable format
186
+ --help, -h Show this help message
187
+
188
+ Use "apltk codegraph <subcommand> --help" for per-subcommand details.
189
+
190
+ Examples:
191
+ apltk codegraph init
192
+ apltk codegraph init --index
193
+ apltk codegraph status --json
194
+ apltk codegraph search getUser
195
+ apltk codegraph search getUser --limit 5 --json
196
+ apltk codegraph explore handleLogin
197
+ apltk codegraph survey src/
198
+ apltk codegraph survey src/ --feature auth --json
199
+ apltk codegraph list-apis --all
200
+ apltk codegraph verify --spec docs/plans/2026-05-11/add-2fa
201
+ `);
202
+ }
203
+
204
+ function printSubcommandHelp(subcommand: string, stream: NodeJS.WriteStream, errStream: NodeJS.WriteStream): void {
205
+ const PAD = ' ';
206
+
207
+ const helps: Record<string, string> = {
208
+ init: `Usage: apltk codegraph init [--index] [--json]
209
+
210
+ Initialize the CodeGraph knowledge graph for the project.
211
+ Creates the .codegraph/ directory and SQLite database.
212
+
213
+ Flags:
214
+ --index Run initial indexing immediately after init, so the
215
+ knowledge graph is ready for queries right away.
216
+ Without this flag, you need to run "apltk codegraph sync"
217
+ separately before searching or exploring.
218
+ --json Output confirmation as JSON.
219
+
220
+ Examples:
221
+ apltk codegraph init
222
+ apltk codegraph init --index
223
+ `,
224
+ sync: `Usage: apltk codegraph sync [--json]
225
+
226
+ Sync the code graph index with the current state of files on disk.
227
+ Parses changed files and updates the SQLite database.
228
+
229
+ This is needed after you modify source files if you want queries
230
+ to reflect the latest code. Runs incrementally — only reprocesses
231
+ files whose mtime has changed.
232
+
233
+ Flags:
234
+ --json Output sync results (files added/removed/updated) as JSON.
235
+
236
+ Examples:
237
+ apltk codegraph sync
238
+ apltk codegraph sync --json
239
+ `,
240
+ status: `Usage: apltk codegraph status [--json]
241
+
242
+ Show index statistics: file count, symbol (node) count, edge count,
243
+ languages detected, and last-sync timestamp.
244
+
245
+ Flags:
246
+ --json Output full statistics as a JSON object.
247
+
248
+ Examples:
249
+ apltk codegraph status
250
+ apltk codegraph status --json
251
+ `,
252
+ search: `Usage: apltk codegraph search <query> [--limit N] [--json]
253
+
254
+ Full-text search the code graph for symbols (functions, classes, variables).
255
+ Uses FTS5 (SQLite full-text search) under the hood.
256
+
257
+ Arguments:
258
+ query Search term (required). Matches against symbol names and
259
+ source code content.
260
+
261
+ Flags:
262
+ --limit N Max results to return (default: 20, max: 100).
263
+ --json Output results as a JSON array.
264
+
265
+ Examples:
266
+ apltk codegraph search getUser
267
+ apltk codegraph search handleLogin --limit 5
268
+ apltk codegraph search "class.*Handler" --limit 10 --json
269
+ `,
270
+ explore: `Usage: apltk codegraph explore <query> [--feature <name>] [--json]
271
+
272
+ Deep-dive on a symbol — shows who calls it, what it calls, and its
273
+ full source code. Useful for understanding how a function or class
274
+ fits into the broader codebase.
275
+
276
+ Arguments:
277
+ query Symbol name to explore (required).
278
+
279
+ Flags:
280
+ --feature <name>
281
+ Scope results to only show callers/callees within a
282
+ specific feature directory (e.g. "auth", "billing").
283
+ --json Output full exploration data as JSON (callers, callees,
284
+ source code, file path, line numbers).
285
+
286
+ Examples:
287
+ apltk codegraph explore handleLogin
288
+ apltk codegraph explore authenticate --feature auth
289
+ apltk codegraph explore sendEmail --json
290
+ `,
291
+ survey: `Usage: apltk codegraph survey [dir] [--feature <name>] [--json]
292
+
293
+ Scan a directory and produce a structured survey report with suggested
294
+ submodule groupings, cross-boundary edges, and entry points.
295
+
296
+ This is the primary input for the "init-project-html" skill's Step 1 —
297
+ it tells the LLM how to model features and submodules for the atlas.
298
+
299
+ Arguments:
300
+ dir Directory to scan (default: current directory ".").
301
+
302
+ Flags:
303
+ --feature <name>
304
+ Feature context: scope the survey to only one feature's
305
+ boundary. Helps the grouper cluster symbols more accurately.
306
+ --json Output survey results as JSON — best for LLM consumption.
307
+
308
+ Examples:
309
+ apltk codegraph survey
310
+ apltk codegraph survey src/
311
+ apltk codegraph survey src/auth --feature auth --json
312
+ `,
313
+ 'list-apis': `Usage: apltk codegraph list-apis [path] [--all] [--json]
314
+
315
+ List public (exported) symbols in the project or a specific sub-path.
316
+ Useful for understanding the public surface area of a module.
317
+
318
+ Arguments:
319
+ path Sub-path to scan (e.g. "src/auth"). When omitted, scans
320
+ the entire project.
321
+
322
+ Flags:
323
+ --all Include non-exported (internal) symbols in the listing.
324
+ --json Output API list as JSON.
325
+
326
+ Examples:
327
+ apltk codegraph list-apis
328
+ apltk codegraph list-apis src/auth
329
+ apltk codegraph list-apis --all --json
330
+ `,
331
+ verify: `Usage: apltk codegraph verify --spec <spec-dir> [--json]
332
+
333
+ Verify a spec overlay's architecture proposals against actual code.
334
+ Checks that every feature, submodule, function, and edge referenced
335
+ in the overlay's atlas state actually exists in the code graph.
336
+
337
+ Flags:
338
+ --spec <spec-dir>
339
+ Path to the spec directory containing an architecture_diff/
340
+ overlay (required). For example, "docs/plans/2026-05-11/add-2fa".
341
+ --json Output verification results as JSON (passed/failed checks).
342
+
343
+ Examples:
344
+ apltk codegraph verify --spec docs/plans/2026-05-11/add-2fa
345
+ apltk codegraph verify --spec docs/plans/2026-05-11/add-2fa --json
346
+ `,
347
+ };
348
+
349
+ const text = helps[subcommand];
350
+ if (text) {
351
+ stream.write(text);
352
+ } else {
353
+ errStream.write(`Unknown subcommand: "${subcommand}". Use "apltk codegraph --help" for the list of available subcommands.\n`);
354
+ }
355
+ }
356
+
357
+ export const tool: ToolDefinition = {
358
+ name: 'codegraph',
359
+ category: 'Code analysis',
360
+ description: 'CodeGraph code intelligence — init, sync, status, search, explore, survey, list-apis, verify',
361
+ handler: codegraphHandler,
362
+ };
@@ -0,0 +1,36 @@
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
+
7
+ import { createOrOpenIndex } from './cg-instance.js';
8
+
9
+ describe('createOrOpenIndex', () => {
10
+ let tmpDir: string;
11
+
12
+ before(() => {
13
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-test-'));
14
+ });
15
+
16
+ after(() => {
17
+ fs.rmSync(tmpDir, { recursive: true, force: true });
18
+ });
19
+
20
+ it('should throw when project is already initialized', async () => {
21
+ // Arrange: create .codegraph/ and codegraph.db to simulate an initialized project
22
+ const codegraphDir = path.join(tmpDir, '.codegraph');
23
+ fs.mkdirSync(codegraphDir, { recursive: true });
24
+ fs.writeFileSync(path.join(codegraphDir, 'codegraph.db'), '');
25
+
26
+ // Act & Assert
27
+ await assert.rejects(
28
+ () => createOrOpenIndex(tmpDir),
29
+ (err: unknown) => {
30
+ assert.ok(err instanceof Error);
31
+ assert.match(err.message, /sync/);
32
+ return true;
33
+ },
34
+ );
35
+ });
36
+ });
@@ -0,0 +1,66 @@
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
+
6
+ let _codeGraphModule: any = null;
7
+ export function getCodeGraphModule(): { CodeGraph: any; findNearestCodeGraphRoot: any } {
8
+ if (!_codeGraphModule) {
9
+ _codeGraphModule = require('@colbymchenry/codegraph');
10
+ }
11
+ return _codeGraphModule;
12
+ }
13
+
14
+ /**
15
+ * Locate the project root by walking up from the given directory.
16
+ * Returns the nearest parent containing `.codegraph/`, or falls back
17
+ * to the nearest parent containing `package.json`.
18
+ */
19
+ export function findProjectRoot(startPath?: string): string {
20
+ const cwd = startPath || process.cwd();
21
+ const codegraphRoot = getCodeGraphModule().findNearestCodeGraphRoot(cwd);
22
+ if (codegraphRoot) return codegraphRoot;
23
+
24
+ // Fallback: walk up looking for package.json
25
+ let dir = path.resolve(cwd);
26
+ while (true) {
27
+ if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
28
+ const parent = path.dirname(dir);
29
+ if (parent === dir) return cwd; // hit filesystem root
30
+ dir = parent;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Initialize a CodeGraph index for the given project root.
36
+ *
37
+ * If the project is already initialized, throws an error suggesting
38
+ * `apltk codegraph sync` instead. Otherwise, initializes a new CodeGraph
39
+ * project. When `options.index` is true, runs initial indexing after init.
40
+ *
41
+ * Note: `CodeGraph.init()` supports an `{ index: true }` shorthand that
42
+ * runs initial indexing inline -- this deviates from a two-step init-then-index
43
+ * pattern but is the supported API through the npm package.
44
+ */
45
+ export async function createOrOpenIndex(
46
+ projectRoot: string,
47
+ options?: { index?: boolean; onProgress?: (progress: any) => void },
48
+ ): Promise<any> {
49
+ const isInit = getCodeGraphModule().CodeGraph.isInitialized(projectRoot);
50
+ if (isInit) {
51
+ throw new Error(
52
+ `Project is already initialized at ${projectRoot}. Use \`apltk codegraph sync\` to update the index.`,
53
+ );
54
+ }
55
+ return getCodeGraphModule().CodeGraph.init(projectRoot, {
56
+ index: options?.index ?? false,
57
+ onProgress: options?.onProgress,
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Close a CodeGraph instance and release resources.
63
+ */
64
+ export function closeIndex(cg: any): void {
65
+ cg.close();
66
+ }
@@ -0,0 +1,195 @@
1
+ import { describe, it, mock, before } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createRequire } from 'node:module';
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Shared mutable mock state — each test sets its own data before calling the
7
+ // handler. The mock instance's searchNodes captures this array by reference,
8
+ // so mutations are reflected in every invocation.
9
+ // ---------------------------------------------------------------------------
10
+ interface MockSymbolNode {
11
+ id: string;
12
+ name: string;
13
+ kind: string;
14
+ filePath: string;
15
+ startLine: number;
16
+ endLine: number;
17
+ qualifiedName: string;
18
+ signature?: string;
19
+ }
20
+
21
+ interface MockSearchResult {
22
+ node: MockSymbolNode;
23
+ score: number;
24
+ }
25
+
26
+ const mockSearchResults: MockSearchResult[] = [];
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Pre-load @colbymchenry/codegraph so we can mock its methods before
30
+ // cmd-explore.js performs its own require().
31
+ // ---------------------------------------------------------------------------
32
+ const require = createRequire(import.meta.url);
33
+ const { CodeGraph } = require('@colbymchenry/codegraph');
34
+
35
+ let mockInstance: {
36
+ searchNodes: () => { node: MockSymbolNode; score: number }[];
37
+ getCallers: () => never[];
38
+ getCallees: () => never[];
39
+ getCode: () => Promise<null>;
40
+ close: () => void;
41
+ } | undefined;
42
+
43
+ before(() => {
44
+ mockInstance = {
45
+ searchNodes: () => mockSearchResults.map(r => ({ node: r.node, score: r.score })),
46
+ getCallers: () => [],
47
+ getCallees: () => [],
48
+ getCode: async () => null,
49
+ close: () => {},
50
+ };
51
+ mock.method(CodeGraph, 'open', async () => mockInstance);
52
+ });
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Import the module under test (same cached CodeGraph is used internally)
56
+ // ---------------------------------------------------------------------------
57
+ let handleExplore: (
58
+ projectRoot: string,
59
+ query: string,
60
+ options?: Record<string, unknown>,
61
+ ) => Promise<number>;
62
+
63
+ before(async () => {
64
+ const mod = await import('./cmd-explore.js');
65
+ handleExplore = mod.handleExplore;
66
+ });
67
+
68
+ // =========================================================================
69
+ // REGTEST-7: Explore output should group symbols by file
70
+ // =========================================================================
71
+ describe('REGTEST-7: Explore grouping by file', () => {
72
+ it('should group symbols under a single file header', async () => {
73
+ // Arrange: two symbols in the same file
74
+ mockSearchResults.length = 0;
75
+ mockSearchResults.push(
76
+ {
77
+ node: {
78
+ id: '1',
79
+ name: 'addUser',
80
+ kind: 'function',
81
+ filePath: 'src/utils.ts',
82
+ startLine: 10,
83
+ endLine: 25,
84
+ qualifiedName: 'utils.addUser',
85
+ signature: '(name: string, age: number): User',
86
+ },
87
+ score: 0.95,
88
+ },
89
+ {
90
+ node: {
91
+ id: '2',
92
+ name: 'deleteUser',
93
+ kind: 'function',
94
+ filePath: 'src/utils.ts',
95
+ startLine: 30,
96
+ endLine: 40,
97
+ qualifiedName: 'utils.deleteUser',
98
+ signature: '(id: number): void',
99
+ },
100
+ score: 0.85,
101
+ },
102
+ );
103
+
104
+ // Capture stdout
105
+ const stdoutChunks: string[] = [];
106
+ const origWrite = process.stdout.write;
107
+ process.stdout.write = ((chunk: unknown) => {
108
+ stdoutChunks.push(String(chunk));
109
+ return true;
110
+ }) as typeof process.stdout.write;
111
+
112
+ try {
113
+ // Act
114
+ const exitCode = await handleExplore('/fake/project', 'utils', {});
115
+
116
+ // Assert
117
+ assert.strictEqual(exitCode, 0);
118
+ const output = stdoutChunks.join('');
119
+
120
+ // Exactly one file header for the shared filePath
121
+ const headerMatches = output.match(/=== src\/utils\.ts ===/g);
122
+ assert.strictEqual(
123
+ headerMatches?.length,
124
+ 1,
125
+ 'Expected exactly one file header for src/utils.ts, ' +
126
+ `got ${headerMatches?.length ?? 0}`,
127
+ );
128
+
129
+ // Both symbols appear in the output
130
+ assert.ok(output.includes('addUser'), 'Output should contain addUser');
131
+ assert.ok(output.includes('deleteUser'), 'Output should contain deleteUser');
132
+
133
+ // File header precedes both symbols (not duplicated)
134
+ const headerIdx = output.indexOf('=== src/utils.ts ===');
135
+ assert.ok(
136
+ headerIdx < output.indexOf('addUser'),
137
+ 'File header should appear before addUser',
138
+ );
139
+ assert.ok(
140
+ headerIdx < output.indexOf('deleteUser'),
141
+ 'File header should appear before deleteUser',
142
+ );
143
+ } finally {
144
+ process.stdout.write = origWrite;
145
+ }
146
+ });
147
+ });
148
+
149
+ // =========================================================================
150
+ // REGTEST-8: Explore --feature acceptance
151
+ // =========================================================================
152
+ describe('REGTEST-8: Explore --feature acceptance', () => {
153
+ it('should accept feature parameter without error', async () => {
154
+ // Arrange: at least one result so the feature line is emitted
155
+ mockSearchResults.length = 0;
156
+ mockSearchResults.push({
157
+ node: {
158
+ id: '3',
159
+ name: 'authLogin',
160
+ kind: 'function',
161
+ filePath: 'src/auth.ts',
162
+ startLine: 5,
163
+ endLine: 20,
164
+ qualifiedName: 'auth.login',
165
+ signature: '(credentials: Record<string, unknown>): Session',
166
+ },
167
+ score: 0.9,
168
+ });
169
+
170
+ const stdoutChunks: string[] = [];
171
+ const origWrite = process.stdout.write;
172
+ process.stdout.write = ((chunk: unknown) => {
173
+ stdoutChunks.push(String(chunk));
174
+ return true;
175
+ }) as typeof process.stdout.write;
176
+
177
+ try {
178
+ // Act — pass feature without json, expect no error
179
+ const exitCode = await handleExplore('/fake/project', 'authLogin', {
180
+ feature: 'auth',
181
+ json: false,
182
+ });
183
+
184
+ // Assert
185
+ assert.strictEqual(exitCode, 0, 'Should return exit code 0');
186
+ const output = stdoutChunks.join('');
187
+ assert.ok(
188
+ output.includes('Feature: auth'),
189
+ 'Output should include "Feature: auth" header',
190
+ );
191
+ } finally {
192
+ process.stdout.write = origWrite;
193
+ }
194
+ });
195
+ });