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