@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.
- package/AGENTS.md +37 -27
- package/CHANGELOG.md +47 -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 +549 -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 +613 -5
- package/packages/tools/codegraph/dist/index.d.ts +3 -0
- package/packages/tools/codegraph/dist/index.js +343 -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 +362 -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/scripts/test.sh +39 -0
- package/skills/design/SKILL.md +33 -0
- package/skills/init-project-html/SKILL.md +66 -56
- package/skills/init-project-html/lib/atlas/assets/architecture.css +2 -1
- package/skills/init-project-html/lib/atlas/render.js +11 -1
- package/skills/init-project-html/lib/atlas/schema.js +44 -7
- package/skills/init-project-html/references/TEMPLATE_SPEC.md +20 -0
- package/skills/init-project-html/references/architecture.md +35 -35
- package/skills/init-project-html/references/definition.md +12 -17
- package/skills/update-project-html/README.md +16 -27
- package/skills/update-project-html/SKILL.md +54 -41
- package/skills/update-project-html/references/architecture.md +35 -35
- package/skills/update-project-html/references/definition.md +12 -17
- package/tsconfig.json +1 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, it } 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 yaml from 'js-yaml';
|
|
7
|
+
/**
|
|
8
|
+
* REGTEST-1: Verify that the js-yaml based parser (used in `loadOverlay`)
|
|
9
|
+
* correctly parses object-format YAML function declarations.
|
|
10
|
+
*
|
|
11
|
+
* The fix replaced a custom YAML parser with `js-yaml`. The old parser
|
|
12
|
+
* incorrectly produced raw string fragments like `"name: init"` when
|
|
13
|
+
* encountering object-format functions:
|
|
14
|
+
*
|
|
15
|
+
* functions:
|
|
16
|
+
* - name: init
|
|
17
|
+
* in: string
|
|
18
|
+
* out: void
|
|
19
|
+
*
|
|
20
|
+
* With `js-yaml`, the parser produces actual objects:
|
|
21
|
+
* `{ name: "init", in: "string", out: "void" }`
|
|
22
|
+
*
|
|
23
|
+
* This test exercises the same parsing path as `loadOverlay`: reading a YAML
|
|
24
|
+
* file from disk with `fs.readFileSync`, then parsing with `yaml.load`.
|
|
25
|
+
*/
|
|
26
|
+
describe('REGTEST-1: loadOverlay YAML parser', () => {
|
|
27
|
+
it('should parse object-format functions and correctly extract .name', () => {
|
|
28
|
+
const yamlStr = [
|
|
29
|
+
'slug: test-feature',
|
|
30
|
+
'submodules:',
|
|
31
|
+
' - slug: module-a',
|
|
32
|
+
' functions:',
|
|
33
|
+
' - name: init',
|
|
34
|
+
' in: string',
|
|
35
|
+
' out: void',
|
|
36
|
+
'edges:',
|
|
37
|
+
' - from: funcA',
|
|
38
|
+
' to: funcB',
|
|
39
|
+
' kind: call',
|
|
40
|
+
].join('\n');
|
|
41
|
+
// Parse with js-yaml (same library used by loadOverlay)
|
|
42
|
+
const data = yaml.load(yamlStr);
|
|
43
|
+
// Verify object-format function parsing
|
|
44
|
+
const fn = data.submodules[0].functions[0];
|
|
45
|
+
assert.strictEqual(typeof fn, 'object', 'object-format function should be parsed as object, not string');
|
|
46
|
+
assert.strictEqual(fn.name, 'init', 'parsed function .name should be "init"');
|
|
47
|
+
// Verify the old bug is fixed: no raw "name: init" string
|
|
48
|
+
assert.notStrictEqual(fn, 'name: init', 'function should NOT be a raw string fragment');
|
|
49
|
+
assert.notStrictEqual(typeof fn, 'string', 'object-format function should not be a string');
|
|
50
|
+
// Verify additional fields are preserved
|
|
51
|
+
assert.strictEqual(fn.in, 'string', 'function "in" field should be preserved');
|
|
52
|
+
assert.strictEqual(fn.out, 'void', 'function "out" field should be preserved');
|
|
53
|
+
// Verify string-format functions still work
|
|
54
|
+
// Note: the YAML above only has object-format functions; string format is tested separately
|
|
55
|
+
});
|
|
56
|
+
it('should parse string-format functions correctly', () => {
|
|
57
|
+
const yamlStr = [
|
|
58
|
+
'slug: test-feature',
|
|
59
|
+
'submodules:',
|
|
60
|
+
' - slug: module-a',
|
|
61
|
+
' functions:',
|
|
62
|
+
' - init',
|
|
63
|
+
' - process',
|
|
64
|
+
].join('\n');
|
|
65
|
+
const data = yaml.load(yamlStr);
|
|
66
|
+
const functions = data.submodules[0].functions;
|
|
67
|
+
assert.strictEqual(functions.length, 2, 'should have 2 string-format functions');
|
|
68
|
+
assert.strictEqual(typeof functions[0], 'string', 'string-format function should be a string');
|
|
69
|
+
assert.strictEqual(functions[0], 'init', 'string function name should be "init"');
|
|
70
|
+
assert.strictEqual(functions[1], 'process', 'string function name should be "process"');
|
|
71
|
+
});
|
|
72
|
+
it('should parse mixed object and string format functions', () => {
|
|
73
|
+
const yamlStr = [
|
|
74
|
+
'slug: test-feature',
|
|
75
|
+
'submodules:',
|
|
76
|
+
' - slug: module-a',
|
|
77
|
+
' functions:',
|
|
78
|
+
' - name: init',
|
|
79
|
+
' in: string',
|
|
80
|
+
' out: void',
|
|
81
|
+
' - process',
|
|
82
|
+
' - name: render',
|
|
83
|
+
' in: string',
|
|
84
|
+
' out: void',
|
|
85
|
+
' - cleanup',
|
|
86
|
+
].join('\n');
|
|
87
|
+
const data = yaml.load(yamlStr);
|
|
88
|
+
const functions = data.submodules[0].functions;
|
|
89
|
+
assert.strictEqual(functions.length, 4, 'should have 4 functions total');
|
|
90
|
+
// Index 0: object-format
|
|
91
|
+
assert.strictEqual(typeof functions[0], 'object', 'functions[0] should be object');
|
|
92
|
+
assert.strictEqual(functions[0].name, 'init');
|
|
93
|
+
// Index 1: string-format
|
|
94
|
+
assert.strictEqual(typeof functions[1], 'string', 'functions[1] should be string');
|
|
95
|
+
assert.strictEqual(functions[1], 'process');
|
|
96
|
+
// Index 2: object-format
|
|
97
|
+
assert.strictEqual(typeof functions[2], 'object', 'functions[2] should be object');
|
|
98
|
+
assert.strictEqual(functions[2].name, 'render');
|
|
99
|
+
// Index 3: string-format
|
|
100
|
+
assert.strictEqual(typeof functions[3], 'string', 'functions[3] should be string');
|
|
101
|
+
assert.strictEqual(functions[3], 'cleanup');
|
|
102
|
+
});
|
|
103
|
+
it('should parse feature YAML from disk (same path as loadOverlay)', async () => {
|
|
104
|
+
// Create a temp directory matching the loadOverlay structure
|
|
105
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-verify-test-'));
|
|
106
|
+
try {
|
|
107
|
+
const featuresDir = path.join(tmpDir, 'architecture_diff', 'atlas', 'features');
|
|
108
|
+
fs.mkdirSync(featuresDir, { recursive: true });
|
|
109
|
+
const yamlPath = path.join(featuresDir, 'test-feature.yaml');
|
|
110
|
+
fs.writeFileSync(yamlPath, [
|
|
111
|
+
'slug: test-feature',
|
|
112
|
+
'submodules:',
|
|
113
|
+
' - slug: module-a',
|
|
114
|
+
' functions:',
|
|
115
|
+
' - name: init',
|
|
116
|
+
' in: string',
|
|
117
|
+
' out: void',
|
|
118
|
+
' edges:',
|
|
119
|
+
' - from: funcA',
|
|
120
|
+
' to: funcB',
|
|
121
|
+
' kind: call',
|
|
122
|
+
].join('\n') + '\n');
|
|
123
|
+
// Read file and parse (same as loadOverlay)
|
|
124
|
+
const raw = fs.readFileSync(yamlPath, 'utf8');
|
|
125
|
+
const data = yaml.load(raw);
|
|
126
|
+
assert.ok(data, 'parsed data should be truthy');
|
|
127
|
+
assert.strictEqual(typeof data, 'object', 'parsed data should be an object');
|
|
128
|
+
assert.strictEqual(data.slug, 'test-feature', 'feature slug should be preserved');
|
|
129
|
+
const fn = data.submodules[0].functions[0];
|
|
130
|
+
assert.strictEqual(typeof fn, 'object', 'object-format function parsed from file should be object');
|
|
131
|
+
assert.strictEqual(fn.name, 'init', 'function .name should be "init"');
|
|
132
|
+
assert.strictEqual(fn.in, 'string', 'function .in should be "string"');
|
|
133
|
+
assert.strictEqual(fn.out, 'void', 'function .out should be "void"');
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format structured data for CLI output.
|
|
3
|
+
*
|
|
4
|
+
* - TTY mode produces human-readable tables and headings.
|
|
5
|
+
* - Non-TTY or `--json` produces JSON.stringify with 2-space indent.
|
|
6
|
+
* - Auto-detects TTY via `process.stdout.isTTY`.
|
|
7
|
+
*/
|
|
8
|
+
export interface FormatOptions {
|
|
9
|
+
json?: boolean;
|
|
10
|
+
tty?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Format generic data for output.
|
|
14
|
+
*/
|
|
15
|
+
export declare function formatOutput(data: unknown, options?: FormatOptions): string;
|
|
16
|
+
/**
|
|
17
|
+
* Format a key-value summary block for human-readable output.
|
|
18
|
+
* Example:
|
|
19
|
+
* Files: 42
|
|
20
|
+
* Nodes: 1280
|
|
21
|
+
* Edges: 5600
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatSummary(rows: [string, string | number][]): string;
|
|
24
|
+
/**
|
|
25
|
+
* Format a list of search results as a human-readable table.
|
|
26
|
+
*/
|
|
27
|
+
export declare function formatSearchResults(results: Array<{
|
|
28
|
+
node: {
|
|
29
|
+
name: string;
|
|
30
|
+
kind: string;
|
|
31
|
+
filePath: string;
|
|
32
|
+
startLine: number;
|
|
33
|
+
};
|
|
34
|
+
score: number;
|
|
35
|
+
}>): string;
|
|
36
|
+
/**
|
|
37
|
+
* Format an API directory listing for human-readable output.
|
|
38
|
+
*/
|
|
39
|
+
export declare function formatApiList(apis: Array<{
|
|
40
|
+
name: string;
|
|
41
|
+
kind: string;
|
|
42
|
+
filePath: string;
|
|
43
|
+
startLine: number;
|
|
44
|
+
signature?: string;
|
|
45
|
+
callerCount: number;
|
|
46
|
+
callers?: Array<{
|
|
47
|
+
name: string;
|
|
48
|
+
filePath: string;
|
|
49
|
+
startLine: number;
|
|
50
|
+
}>;
|
|
51
|
+
}>): string;
|
|
52
|
+
/**
|
|
53
|
+
* Format an API listing grouped by directory.
|
|
54
|
+
*/
|
|
55
|
+
export declare function formatApiListGrouped(apis: Array<{
|
|
56
|
+
name: string;
|
|
57
|
+
kind: string;
|
|
58
|
+
filePath: string;
|
|
59
|
+
startLine: number;
|
|
60
|
+
signature?: string;
|
|
61
|
+
callerCount: number;
|
|
62
|
+
callers?: Array<{
|
|
63
|
+
name: string;
|
|
64
|
+
filePath: string;
|
|
65
|
+
startLine: number;
|
|
66
|
+
}>;
|
|
67
|
+
}>): string;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format structured data for CLI output.
|
|
3
|
+
*
|
|
4
|
+
* - TTY mode produces human-readable tables and headings.
|
|
5
|
+
* - Non-TTY or `--json` produces JSON.stringify with 2-space indent.
|
|
6
|
+
* - Auto-detects TTY via `process.stdout.isTTY`.
|
|
7
|
+
*/
|
|
8
|
+
function isTTY(options) {
|
|
9
|
+
if (options.json)
|
|
10
|
+
return false;
|
|
11
|
+
if (options.tty !== undefined)
|
|
12
|
+
return options.tty;
|
|
13
|
+
return !!process.stdout.isTTY;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Format generic data for output.
|
|
17
|
+
*/
|
|
18
|
+
export function formatOutput(data, options = {}) {
|
|
19
|
+
if (!isTTY(options)) {
|
|
20
|
+
return JSON.stringify(data, null, 2);
|
|
21
|
+
}
|
|
22
|
+
// Fallback: JSON for complex objects in TTY too
|
|
23
|
+
if (typeof data === 'string')
|
|
24
|
+
return data;
|
|
25
|
+
if (data === null || data === undefined)
|
|
26
|
+
return '';
|
|
27
|
+
return JSON.stringify(data, null, 2);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Format a key-value summary block for human-readable output.
|
|
31
|
+
* Example:
|
|
32
|
+
* Files: 42
|
|
33
|
+
* Nodes: 1280
|
|
34
|
+
* Edges: 5600
|
|
35
|
+
*/
|
|
36
|
+
export function formatSummary(rows) {
|
|
37
|
+
const width = rows.reduce((max, [key]) => Math.max(max, key.length + 1), 0);
|
|
38
|
+
return rows
|
|
39
|
+
.map(([key, val]) => `${key.padEnd(width, ' ')} ${val}`)
|
|
40
|
+
.join('\n');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Format a list of search results as a human-readable table.
|
|
44
|
+
*/
|
|
45
|
+
export function formatSearchResults(results) {
|
|
46
|
+
if (results.length === 0)
|
|
47
|
+
return 'No results found.';
|
|
48
|
+
const lines = results.map((r, i) => {
|
|
49
|
+
const score = (r.score * 100).toFixed(0);
|
|
50
|
+
return ` ${i + 1}. ${r.node.name} [${r.node.kind}] ${r.node.filePath}:${r.node.startLine} (${score}%)`;
|
|
51
|
+
});
|
|
52
|
+
return `Results (${results.length}):\n${lines.join('\n')}`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Format an API directory listing for human-readable output.
|
|
56
|
+
*/
|
|
57
|
+
export function formatApiList(apis) {
|
|
58
|
+
if (apis.length === 0)
|
|
59
|
+
return 'No public APIs found.';
|
|
60
|
+
const lines = [];
|
|
61
|
+
for (const a of apis) {
|
|
62
|
+
const sig = a.signature ? ` ${a.signature}` : '';
|
|
63
|
+
lines.push(` ${a.name} [${a.kind}] ${a.filePath}:${a.startLine} (${a.callerCount} callers)${sig}`);
|
|
64
|
+
if (a.callerCount > 0 && a.callers) {
|
|
65
|
+
const callerLines = a.callers.slice(0, 5).map(c => ` Called by: ${c.name} ${c.filePath}:${c.startLine}`);
|
|
66
|
+
lines.push(...callerLines);
|
|
67
|
+
if (a.callerCount > 5)
|
|
68
|
+
lines.push(` ... and ${a.callerCount - 5} more`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return `APIs (${apis.length}):\n${lines.join('\n')}`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Format an API listing grouped by directory.
|
|
75
|
+
*/
|
|
76
|
+
export function formatApiListGrouped(apis) {
|
|
77
|
+
if (apis.length === 0)
|
|
78
|
+
return 'No public APIs found.';
|
|
79
|
+
// Group by directory
|
|
80
|
+
const groups = new Map();
|
|
81
|
+
for (const a of apis) {
|
|
82
|
+
const dir = a.filePath.substring(0, a.filePath.lastIndexOf('/'));
|
|
83
|
+
if (!groups.has(dir))
|
|
84
|
+
groups.set(dir, []);
|
|
85
|
+
groups.get(dir).push(a);
|
|
86
|
+
}
|
|
87
|
+
// Render with directory headers
|
|
88
|
+
const lines = [];
|
|
89
|
+
const sortedDirs = [...groups.keys()].sort();
|
|
90
|
+
for (const dir of sortedDirs) {
|
|
91
|
+
const entries = groups.get(dir);
|
|
92
|
+
lines.push('');
|
|
93
|
+
lines.push(`=== ${dir}/ ===`);
|
|
94
|
+
for (const a of entries) {
|
|
95
|
+
const sig = a.signature ? ` ${a.signature}` : '';
|
|
96
|
+
lines.push(` ${a.name} [${a.kind}] ${a.filePath}:${a.startLine} (${a.callerCount} callers)${sig}`);
|
|
97
|
+
if (a.callerCount > 0 && a.callers) {
|
|
98
|
+
const callerLines = a.callers.slice(0, 5).map(c => ` Called by: ${c.name} ${c.filePath}:${c.startLine}`);
|
|
99
|
+
lines.push(...callerLines);
|
|
100
|
+
if (a.callerCount > 5)
|
|
101
|
+
lines.push(` ... and ${a.callerCount - 5} more`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
lines.push('');
|
|
106
|
+
return lines.join('\n').trimStart();
|
|
107
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { formatApiList } from './formatter.js';
|
|
4
|
+
describe('formatApiList', () => {
|
|
5
|
+
it('should show caller names in output when callers exist', () => {
|
|
6
|
+
const apis = [
|
|
7
|
+
{
|
|
8
|
+
name: 'myFunc',
|
|
9
|
+
kind: 'function',
|
|
10
|
+
filePath: 'src/a.ts',
|
|
11
|
+
startLine: 10,
|
|
12
|
+
callerCount: 3,
|
|
13
|
+
callers: [
|
|
14
|
+
{ name: 'callerOne', filePath: 'src/b.ts', startLine: 5 },
|
|
15
|
+
{ name: 'callerTwo', filePath: 'src/c.ts', startLine: 15 },
|
|
16
|
+
{ name: 'callerThree', filePath: 'src/d.ts', startLine: 25 },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
const output = formatApiList(apis);
|
|
21
|
+
assert.ok(output.includes('callerOne'), 'output should contain callerOne');
|
|
22
|
+
assert.ok(output.includes('callerTwo'), 'output should contain callerTwo');
|
|
23
|
+
assert.ok(output.includes('callerThree'), 'output should contain callerThree');
|
|
24
|
+
assert.ok(output.includes('(3 callers)'), 'output should show caller count');
|
|
25
|
+
assert.ok(output.includes('myFunc'), 'output should contain function name');
|
|
26
|
+
});
|
|
27
|
+
it('should show "(0 callers)" when there are no callers', () => {
|
|
28
|
+
const apis = [
|
|
29
|
+
{
|
|
30
|
+
name: 'noCallers',
|
|
31
|
+
kind: 'function',
|
|
32
|
+
filePath: 'src/a.ts',
|
|
33
|
+
startLine: 1,
|
|
34
|
+
callerCount: 0,
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
const output = formatApiList(apis);
|
|
38
|
+
assert.ok(output.includes('(0 callers)'), 'output should show zero callers');
|
|
39
|
+
assert.ok(!output.includes('Called by:'), 'output should not contain caller lines');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ScanResult } from './scanner.js';
|
|
2
|
+
export interface SubmoduleSuggestion {
|
|
3
|
+
slug: string;
|
|
4
|
+
kind: 'api' | 'service' | 'db' | 'ui' | 'pure-fn' | 'queue';
|
|
5
|
+
role: string;
|
|
6
|
+
memberFunctions: string[];
|
|
7
|
+
memberFiles: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Group scanned symbols into suggested submodule groupings.
|
|
11
|
+
*
|
|
12
|
+
* Algorithm (hybrid):
|
|
13
|
+
* 1. Build adjacency map from call graph: for each symbol, find callees within the scan
|
|
14
|
+
* 2. BFS on undirected graph to find connected components
|
|
15
|
+
* 3. Components with >=2 symbols create connectivity-based submodule suggestions
|
|
16
|
+
* 4. Remaining (isolated) symbols fall back to per-file grouping
|
|
17
|
+
* 5. Apply mergeByDirectoryPrefix as final step
|
|
18
|
+
*/
|
|
19
|
+
export declare function groupIntoSubmodules(scan: ScanResult, cg: any): SubmoduleSuggestion[];
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group scanned symbols into suggested submodule groupings.
|
|
3
|
+
*
|
|
4
|
+
* Algorithm (hybrid):
|
|
5
|
+
* 1. Build adjacency map from call graph: for each symbol, find callees within the scan
|
|
6
|
+
* 2. BFS on undirected graph to find connected components
|
|
7
|
+
* 3. Components with >=2 symbols create connectivity-based submodule suggestions
|
|
8
|
+
* 4. Remaining (isolated) symbols fall back to per-file grouping
|
|
9
|
+
* 5. Apply mergeByDirectoryPrefix as final step
|
|
10
|
+
*/
|
|
11
|
+
export function groupIntoSubmodules(scan, cg) {
|
|
12
|
+
if (scan.allSymbols.length === 0)
|
|
13
|
+
return [];
|
|
14
|
+
// --- Phase 1: Build adjacency map from call graph connectivity ---
|
|
15
|
+
const allNameSet = new Set(scan.allSymbols.map(s => s.name));
|
|
16
|
+
const adj = new Map();
|
|
17
|
+
const nodeKeyMap = new Map();
|
|
18
|
+
for (const sym of scan.allSymbols) {
|
|
19
|
+
const key = `${sym.filePath}::${sym.name}`;
|
|
20
|
+
const calleeSet = new Set();
|
|
21
|
+
adj.set(key, calleeSet);
|
|
22
|
+
nodeKeyMap.set(key, sym);
|
|
23
|
+
const nodes = cg.getNodesByName(sym.name);
|
|
24
|
+
for (const node of nodes) {
|
|
25
|
+
if (node.filePath !== sym.filePath)
|
|
26
|
+
continue;
|
|
27
|
+
const callees = cg.getCallees(node.id);
|
|
28
|
+
for (const callee of callees) {
|
|
29
|
+
if (allNameSet.has(callee.node.name)) {
|
|
30
|
+
calleeSet.add(callee.node.name);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Build reverse index: name -> list of symbol keys (for efficient BFS lookup)
|
|
36
|
+
const nameToKeys = new Map();
|
|
37
|
+
for (const [key, sym] of nodeKeyMap.entries()) {
|
|
38
|
+
const arr = nameToKeys.get(sym.name) || [];
|
|
39
|
+
arr.push(key);
|
|
40
|
+
nameToKeys.set(sym.name, arr);
|
|
41
|
+
}
|
|
42
|
+
// --- Phase 2: BFS to find connected components (undirected graph) ---
|
|
43
|
+
const visited = new Set();
|
|
44
|
+
const components = [];
|
|
45
|
+
for (const key of adj.keys()) {
|
|
46
|
+
if (visited.has(key))
|
|
47
|
+
continue;
|
|
48
|
+
const component = [];
|
|
49
|
+
const queue = [key];
|
|
50
|
+
visited.add(key);
|
|
51
|
+
while (queue.length > 0) {
|
|
52
|
+
const currentKey = queue.shift();
|
|
53
|
+
const sym = nodeKeyMap.get(currentKey);
|
|
54
|
+
component.push(sym);
|
|
55
|
+
const calleeNames = adj.get(currentKey) || new Set();
|
|
56
|
+
for (const calleeName of calleeNames) {
|
|
57
|
+
const targetKeys = nameToKeys.get(calleeName) || [];
|
|
58
|
+
for (const tk of targetKeys) {
|
|
59
|
+
if (!visited.has(tk)) {
|
|
60
|
+
visited.add(tk);
|
|
61
|
+
queue.push(tk);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (component.length > 0) {
|
|
67
|
+
components.push(component);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// --- Phase 3: Build suggestions from connected components ---
|
|
71
|
+
const suggestions = [];
|
|
72
|
+
const processed = new Set();
|
|
73
|
+
// Components with >=2 symbols become connectivity-based submodules
|
|
74
|
+
for (const component of components) {
|
|
75
|
+
if (component.length < 2)
|
|
76
|
+
continue;
|
|
77
|
+
const files = [...new Set(component.map(s => s.filePath))];
|
|
78
|
+
const representativePath = files[0];
|
|
79
|
+
const fileName = representativePath.split('/').pop() || '';
|
|
80
|
+
const slug = fileName.replace(/\.\w+$/, '').replace(/[_ ]/g, '-').toLowerCase();
|
|
81
|
+
const kind = inferKind(representativePath, component);
|
|
82
|
+
const role = inferRole(kind, slug, component.filter(s => s.isExported));
|
|
83
|
+
for (const s of component) {
|
|
84
|
+
processed.add(`${s.filePath}::${s.name}`);
|
|
85
|
+
}
|
|
86
|
+
suggestions.push({
|
|
87
|
+
slug,
|
|
88
|
+
kind,
|
|
89
|
+
role,
|
|
90
|
+
memberFunctions: [...new Set(component.map(s => s.name))],
|
|
91
|
+
memberFiles: files,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// --- Phase 4: Remaining (isolated) symbols fall back to per-file grouping ---
|
|
95
|
+
for (const file of scan.files) {
|
|
96
|
+
const unprocessedSymbols = file.symbols.filter(s => !processed.has(`${file.filePath}::${s.name}`));
|
|
97
|
+
if (unprocessedSymbols.length === 0 && file.symbols.length === 0)
|
|
98
|
+
continue;
|
|
99
|
+
const fileName = file.filePath.split('/').pop() || '';
|
|
100
|
+
const slug = fileName.replace(/\.\w+$/, '').replace(/[_ ]/g, '-').toLowerCase();
|
|
101
|
+
const kind = inferKind(file.filePath, file.symbols);
|
|
102
|
+
const symbols = unprocessedSymbols.map(s => s.name);
|
|
103
|
+
if (symbols.length === 0)
|
|
104
|
+
continue;
|
|
105
|
+
for (const s of unprocessedSymbols)
|
|
106
|
+
processed.add(`${file.filePath}::${s.name}`);
|
|
107
|
+
const role = inferRole(kind, slug, file.symbols.filter(s => s.isExported));
|
|
108
|
+
suggestions.push({
|
|
109
|
+
slug,
|
|
110
|
+
kind,
|
|
111
|
+
role,
|
|
112
|
+
memberFunctions: symbols,
|
|
113
|
+
memberFiles: [file.filePath],
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// --- Phase 5: Merge small files sharing a directory prefix ---
|
|
117
|
+
const merged = mergeByDirectoryPrefix(suggestions, scan.directory);
|
|
118
|
+
return merged;
|
|
119
|
+
}
|
|
120
|
+
function inferKind(filePath, symbols) {
|
|
121
|
+
const lower = filePath.toLowerCase();
|
|
122
|
+
// Detect by path patterns
|
|
123
|
+
if (lower.includes('/api/') || lower.includes('/routes/') || lower.includes('/controller'))
|
|
124
|
+
return 'api';
|
|
125
|
+
if (lower.includes('/db/') || lower.includes('/model/') || lower.includes('/repository') || lower.includes('/schema'))
|
|
126
|
+
return 'db';
|
|
127
|
+
if (lower.includes('/ui/') || lower.includes('/component/') || lower.includes('/page/') || lower.includes('/view'))
|
|
128
|
+
return 'ui';
|
|
129
|
+
if (lower.includes('/queue/') || lower.includes('/job/') || lower.includes('/worker'))
|
|
130
|
+
return 'queue';
|
|
131
|
+
// Detect by symbol kinds
|
|
132
|
+
const hasHandler = symbols.some((s) => s.kind === 'route' || s.kind === 'component');
|
|
133
|
+
if (hasHandler)
|
|
134
|
+
return 'api';
|
|
135
|
+
const hasModel = symbols.some((s) => s.kind === 'interface' || s.kind === 'struct');
|
|
136
|
+
if (hasModel)
|
|
137
|
+
return 'db';
|
|
138
|
+
return 'service';
|
|
139
|
+
}
|
|
140
|
+
function inferRole(kind, slug, exportedSymbols) {
|
|
141
|
+
const name = slug.replace(/-/g, ' ');
|
|
142
|
+
switch (kind) {
|
|
143
|
+
case 'api':
|
|
144
|
+
return `Handles API requests for ${name}`;
|
|
145
|
+
case 'db':
|
|
146
|
+
return `Manages data access and persistence for ${name}`;
|
|
147
|
+
case 'service':
|
|
148
|
+
return `Contains business logic for ${name}`;
|
|
149
|
+
case 'ui':
|
|
150
|
+
return `Renders UI components for ${name}`;
|
|
151
|
+
case 'queue':
|
|
152
|
+
return `Processes background jobs for ${name}`;
|
|
153
|
+
case 'pure-fn':
|
|
154
|
+
return `Provides pure utility functions for ${name}`;
|
|
155
|
+
default:
|
|
156
|
+
return `Supports ${name} functionality`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function mergeByDirectoryPrefix(suggestions, _directory) {
|
|
160
|
+
// When there are many single-file suggestions, merge those
|
|
161
|
+
// that share a common 2-segment directory prefix
|
|
162
|
+
const prefixMap = new Map();
|
|
163
|
+
for (const s of suggestions) {
|
|
164
|
+
if (s.memberFiles.length !== 1) {
|
|
165
|
+
// Already contains multiple files, keep as-is
|
|
166
|
+
const key = s.slug;
|
|
167
|
+
prefixMap.set(key, s);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const filePath = s.memberFiles[0];
|
|
171
|
+
const parts = filePath.split('/');
|
|
172
|
+
// Use the parent directory as merge key for files in subdirs
|
|
173
|
+
const mergeKey = parts.length >= 3 ? parts.slice(0, -1).join('/') : s.slug;
|
|
174
|
+
if (prefixMap.has(mergeKey)) {
|
|
175
|
+
const existing = prefixMap.get(mergeKey);
|
|
176
|
+
existing.memberFunctions.push(...s.memberFunctions);
|
|
177
|
+
existing.memberFiles.push(...s.memberFiles);
|
|
178
|
+
// Keep more specific kind
|
|
179
|
+
if (existing.kind === 'service' && s.kind !== 'service') {
|
|
180
|
+
existing.kind = s.kind;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
prefixMap.set(mergeKey, {
|
|
185
|
+
slug: mergeKey.replace(/\//g, '-'),
|
|
186
|
+
kind: s.kind,
|
|
187
|
+
role: s.role,
|
|
188
|
+
memberFunctions: [...s.memberFunctions],
|
|
189
|
+
memberFiles: [...s.memberFiles],
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return Array.from(prefixMap.values());
|
|
194
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { groupIntoSubmodules } from './grouper.js';
|
|
4
|
+
describe('groupIntoSubmodules', () => {
|
|
5
|
+
it('groups connected call graph symbols into one submodule (hybrid connectivity grouping)', () => {
|
|
6
|
+
// -----------------------------------------------------------------------
|
|
7
|
+
// GIVEN a scan result with functions A, B, C where A calls B and B calls C
|
|
8
|
+
// (all in the same file, forming a connected chain)
|
|
9
|
+
// -----------------------------------------------------------------------
|
|
10
|
+
const filePath = 'src/module.ts';
|
|
11
|
+
const scan = {
|
|
12
|
+
directory: 'src',
|
|
13
|
+
files: [
|
|
14
|
+
{
|
|
15
|
+
filePath,
|
|
16
|
+
language: 'typescript',
|
|
17
|
+
symbols: [
|
|
18
|
+
{ name: 'A', kind: 'function', qualifiedName: 'A', startLine: 1, endLine: 5, isExported: true },
|
|
19
|
+
{ name: 'B', kind: 'function', qualifiedName: 'B', startLine: 6, endLine: 10, isExported: true },
|
|
20
|
+
{ name: 'C', kind: 'function', qualifiedName: 'C', startLine: 11, endLine: 15, isExported: false },
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
allSymbols: [
|
|
25
|
+
{ name: 'A', kind: 'function', filePath, qualifiedName: 'A', startLine: 1, isExported: true },
|
|
26
|
+
{ name: 'B', kind: 'function', filePath, qualifiedName: 'B', startLine: 6, isExported: true },
|
|
27
|
+
{ name: 'C', kind: 'function', filePath, qualifiedName: 'C', startLine: 11, isExported: false },
|
|
28
|
+
],
|
|
29
|
+
totalFiles: 1,
|
|
30
|
+
totalSymbols: 3,
|
|
31
|
+
};
|
|
32
|
+
// Mock call graph: A -> B -> C
|
|
33
|
+
const mockCg = {
|
|
34
|
+
getNodesByName(name) {
|
|
35
|
+
const nodeId = `node-${name}`;
|
|
36
|
+
return [{ id: nodeId, filePath }];
|
|
37
|
+
},
|
|
38
|
+
getCallees(nodeId) {
|
|
39
|
+
const calleeMap = {
|
|
40
|
+
'node-A': ['B'],
|
|
41
|
+
'node-B': ['C'],
|
|
42
|
+
'node-C': [],
|
|
43
|
+
};
|
|
44
|
+
return (calleeMap[nodeId] || []).map((calleeName) => ({
|
|
45
|
+
node: { name: calleeName },
|
|
46
|
+
}));
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
// -----------------------------------------------------------------------
|
|
50
|
+
// WHEN groupIntoSubmodules(scan, mockCg) runs
|
|
51
|
+
// -----------------------------------------------------------------------
|
|
52
|
+
const result = groupIntoSubmodules(scan, mockCg);
|
|
53
|
+
// -----------------------------------------------------------------------
|
|
54
|
+
// THEN A, B, C are grouped into one submodule
|
|
55
|
+
// (connectivity-based grouping takes priority over per-file isolation)
|
|
56
|
+
// -----------------------------------------------------------------------
|
|
57
|
+
assert.equal(result.length, 1, 'should produce exactly one submodule');
|
|
58
|
+
const sortedMembers = [...result[0].memberFunctions].sort();
|
|
59
|
+
assert.deepEqual(sortedMembers, ['A', 'B', 'C'], 'should contain A, B, C');
|
|
60
|
+
assert.ok(result[0].memberFiles.includes(filePath), 'should include the source file in memberFiles');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface FileScan {
|
|
2
|
+
filePath: string;
|
|
3
|
+
language: string;
|
|
4
|
+
symbols: Array<{
|
|
5
|
+
name: string;
|
|
6
|
+
kind: string;
|
|
7
|
+
qualifiedName: string;
|
|
8
|
+
startLine: number;
|
|
9
|
+
endLine: number;
|
|
10
|
+
isExported: boolean;
|
|
11
|
+
signature?: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export interface ScanResult {
|
|
15
|
+
directory: string;
|
|
16
|
+
files: FileScan[];
|
|
17
|
+
allSymbols: Array<{
|
|
18
|
+
name: string;
|
|
19
|
+
kind: string;
|
|
20
|
+
filePath: string;
|
|
21
|
+
qualifiedName: string;
|
|
22
|
+
startLine: number;
|
|
23
|
+
isExported: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
totalFiles: number;
|
|
26
|
+
totalSymbols: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Scan a directory for all files and symbols tracked by CodeGraph.
|
|
30
|
+
*/
|
|
31
|
+
export declare function scanDirectory(cg: any, dirPath: string): Promise<ScanResult>;
|