@vpxa/kb 0.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/LICENSE +21 -0
- package/README.md +1140 -0
- package/bin/kb.mjs +10 -0
- package/package.json +67 -0
- package/packages/analyzers/dist/blast-radius-analyzer.d.ts +23 -0
- package/packages/analyzers/dist/blast-radius-analyzer.js +114 -0
- package/packages/analyzers/dist/dependency-analyzer.d.ts +29 -0
- package/packages/analyzers/dist/dependency-analyzer.js +425 -0
- package/packages/analyzers/dist/diagram-generator.d.ts +13 -0
- package/packages/analyzers/dist/diagram-generator.js +86 -0
- package/packages/analyzers/dist/entry-point-analyzer.d.ts +19 -0
- package/packages/analyzers/dist/entry-point-analyzer.js +239 -0
- package/packages/analyzers/dist/index.d.ts +14 -0
- package/packages/analyzers/dist/index.js +23 -0
- package/packages/analyzers/dist/knowledge-producer.d.ts +32 -0
- package/packages/analyzers/dist/knowledge-producer.js +113 -0
- package/packages/analyzers/dist/pattern-analyzer.d.ts +12 -0
- package/packages/analyzers/dist/pattern-analyzer.js +359 -0
- package/packages/analyzers/dist/regex-call-graph.d.ts +17 -0
- package/packages/analyzers/dist/regex-call-graph.js +428 -0
- package/packages/analyzers/dist/structure-analyzer.d.ts +11 -0
- package/packages/analyzers/dist/structure-analyzer.js +258 -0
- package/packages/analyzers/dist/symbol-analyzer.d.ts +10 -0
- package/packages/analyzers/dist/symbol-analyzer.js +442 -0
- package/packages/analyzers/dist/ts-call-graph.d.ts +27 -0
- package/packages/analyzers/dist/ts-call-graph.js +160 -0
- package/packages/analyzers/dist/types.d.ts +98 -0
- package/packages/analyzers/dist/types.js +1 -0
- package/packages/chunker/dist/call-graph-extractor.d.ts +22 -0
- package/packages/chunker/dist/call-graph-extractor.js +90 -0
- package/packages/chunker/dist/chunker-factory.d.ts +7 -0
- package/packages/chunker/dist/chunker-factory.js +36 -0
- package/packages/chunker/dist/chunker.interface.d.ts +10 -0
- package/packages/chunker/dist/chunker.interface.js +1 -0
- package/packages/chunker/dist/code-chunker.d.ts +14 -0
- package/packages/chunker/dist/code-chunker.js +134 -0
- package/packages/chunker/dist/generic-chunker.d.ts +12 -0
- package/packages/chunker/dist/generic-chunker.js +72 -0
- package/packages/chunker/dist/index.d.ts +8 -0
- package/packages/chunker/dist/index.js +21 -0
- package/packages/chunker/dist/markdown-chunker.d.ts +14 -0
- package/packages/chunker/dist/markdown-chunker.js +122 -0
- package/packages/chunker/dist/treesitter-chunker.d.ts +47 -0
- package/packages/chunker/dist/treesitter-chunker.js +234 -0
- package/packages/cli/dist/commands/analyze.d.ts +3 -0
- package/packages/cli/dist/commands/analyze.js +112 -0
- package/packages/cli/dist/commands/context-cmds.d.ts +3 -0
- package/packages/cli/dist/commands/context-cmds.js +155 -0
- package/packages/cli/dist/commands/environment.d.ts +3 -0
- package/packages/cli/dist/commands/environment.js +204 -0
- package/packages/cli/dist/commands/execution.d.ts +3 -0
- package/packages/cli/dist/commands/execution.js +137 -0
- package/packages/cli/dist/commands/graph.d.ts +3 -0
- package/packages/cli/dist/commands/graph.js +81 -0
- package/packages/cli/dist/commands/init.d.ts +8 -0
- package/packages/cli/dist/commands/init.js +87 -0
- package/packages/cli/dist/commands/knowledge.d.ts +3 -0
- package/packages/cli/dist/commands/knowledge.js +139 -0
- package/packages/cli/dist/commands/search.d.ts +3 -0
- package/packages/cli/dist/commands/search.js +267 -0
- package/packages/cli/dist/commands/system.d.ts +3 -0
- package/packages/cli/dist/commands/system.js +241 -0
- package/packages/cli/dist/commands/workspace.d.ts +3 -0
- package/packages/cli/dist/commands/workspace.js +388 -0
- package/packages/cli/dist/context.d.ts +5 -0
- package/packages/cli/dist/context.js +14 -0
- package/packages/cli/dist/helpers.d.ts +52 -0
- package/packages/cli/dist/helpers.js +458 -0
- package/packages/cli/dist/index.d.ts +8 -0
- package/packages/cli/dist/index.js +69 -0
- package/packages/cli/dist/kb-init.d.ts +57 -0
- package/packages/cli/dist/kb-init.js +82 -0
- package/packages/cli/dist/types.d.ts +7 -0
- package/packages/cli/dist/types.js +1 -0
- package/packages/core/dist/constants.d.ts +49 -0
- package/packages/core/dist/constants.js +43 -0
- package/packages/core/dist/content-detector.d.ts +9 -0
- package/packages/core/dist/content-detector.js +79 -0
- package/packages/core/dist/errors.d.ts +18 -0
- package/packages/core/dist/errors.js +40 -0
- package/packages/core/dist/index.d.ts +6 -0
- package/packages/core/dist/index.js +9 -0
- package/packages/core/dist/logger.d.ts +9 -0
- package/packages/core/dist/logger.js +34 -0
- package/packages/core/dist/types.d.ts +108 -0
- package/packages/core/dist/types.js +1 -0
- package/packages/embeddings/dist/embedder.interface.d.ts +24 -0
- package/packages/embeddings/dist/embedder.interface.js +1 -0
- package/packages/embeddings/dist/index.d.ts +3 -0
- package/packages/embeddings/dist/index.js +5 -0
- package/packages/embeddings/dist/onnx-embedder.d.ts +24 -0
- package/packages/embeddings/dist/onnx-embedder.js +82 -0
- package/packages/indexer/dist/file-hasher.d.ts +11 -0
- package/packages/indexer/dist/file-hasher.js +13 -0
- package/packages/indexer/dist/filesystem-crawler.d.ts +27 -0
- package/packages/indexer/dist/filesystem-crawler.js +125 -0
- package/packages/indexer/dist/graph-extractor.d.ts +22 -0
- package/packages/indexer/dist/graph-extractor.js +111 -0
- package/packages/indexer/dist/incremental-indexer.d.ts +47 -0
- package/packages/indexer/dist/incremental-indexer.js +278 -0
- package/packages/indexer/dist/index.d.ts +5 -0
- package/packages/indexer/dist/index.js +14 -0
- package/packages/server/dist/api.d.ts +8 -0
- package/packages/server/dist/api.js +9 -0
- package/packages/server/dist/config.d.ts +3 -0
- package/packages/server/dist/config.js +75 -0
- package/packages/server/dist/curated-manager.d.ts +86 -0
- package/packages/server/dist/curated-manager.js +357 -0
- package/packages/server/dist/index.d.ts +2 -0
- package/packages/server/dist/index.js +134 -0
- package/packages/server/dist/replay-interceptor.d.ts +11 -0
- package/packages/server/dist/replay-interceptor.js +38 -0
- package/packages/server/dist/resources/resources.d.ts +4 -0
- package/packages/server/dist/resources/resources.js +40 -0
- package/packages/server/dist/server.d.ts +21 -0
- package/packages/server/dist/server.js +247 -0
- package/packages/server/dist/tools/analyze.tools.d.ts +11 -0
- package/packages/server/dist/tools/analyze.tools.js +288 -0
- package/packages/server/dist/tools/forge.tools.d.ts +12 -0
- package/packages/server/dist/tools/forge.tools.js +501 -0
- package/packages/server/dist/tools/forget.tool.d.ts +4 -0
- package/packages/server/dist/tools/forget.tool.js +43 -0
- package/packages/server/dist/tools/graph.tool.d.ts +4 -0
- package/packages/server/dist/tools/graph.tool.js +110 -0
- package/packages/server/dist/tools/list.tool.d.ts +4 -0
- package/packages/server/dist/tools/list.tool.js +56 -0
- package/packages/server/dist/tools/lookup.tool.d.ts +4 -0
- package/packages/server/dist/tools/lookup.tool.js +53 -0
- package/packages/server/dist/tools/onboard.tool.d.ts +5 -0
- package/packages/server/dist/tools/onboard.tool.js +112 -0
- package/packages/server/dist/tools/produce.tool.d.ts +3 -0
- package/packages/server/dist/tools/produce.tool.js +74 -0
- package/packages/server/dist/tools/read.tool.d.ts +4 -0
- package/packages/server/dist/tools/read.tool.js +49 -0
- package/packages/server/dist/tools/reindex.tool.d.ts +7 -0
- package/packages/server/dist/tools/reindex.tool.js +70 -0
- package/packages/server/dist/tools/remember.tool.d.ts +4 -0
- package/packages/server/dist/tools/remember.tool.js +45 -0
- package/packages/server/dist/tools/replay.tool.d.ts +3 -0
- package/packages/server/dist/tools/replay.tool.js +89 -0
- package/packages/server/dist/tools/search.tool.d.ts +5 -0
- package/packages/server/dist/tools/search.tool.js +331 -0
- package/packages/server/dist/tools/status.tool.d.ts +4 -0
- package/packages/server/dist/tools/status.tool.js +68 -0
- package/packages/server/dist/tools/toolkit.tools.d.ts +35 -0
- package/packages/server/dist/tools/toolkit.tools.js +1674 -0
- package/packages/server/dist/tools/update.tool.d.ts +4 -0
- package/packages/server/dist/tools/update.tool.js +42 -0
- package/packages/server/dist/tools/utility.tools.d.ts +15 -0
- package/packages/server/dist/tools/utility.tools.js +461 -0
- package/packages/store/dist/graph-store.interface.d.ts +104 -0
- package/packages/store/dist/graph-store.interface.js +1 -0
- package/packages/store/dist/index.d.ts +6 -0
- package/packages/store/dist/index.js +9 -0
- package/packages/store/dist/lance-store.d.ts +32 -0
- package/packages/store/dist/lance-store.js +258 -0
- package/packages/store/dist/sqlite-graph-store.d.ts +43 -0
- package/packages/store/dist/sqlite-graph-store.js +374 -0
- package/packages/store/dist/store-factory.d.ts +9 -0
- package/packages/store/dist/store-factory.js +14 -0
- package/packages/store/dist/store.interface.d.ts +48 -0
- package/packages/store/dist/store.interface.js +1 -0
- package/packages/tools/dist/batch.d.ts +21 -0
- package/packages/tools/dist/batch.js +45 -0
- package/packages/tools/dist/changelog.d.ts +34 -0
- package/packages/tools/dist/changelog.js +112 -0
- package/packages/tools/dist/check.d.ts +26 -0
- package/packages/tools/dist/check.js +59 -0
- package/packages/tools/dist/checkpoint.d.ts +17 -0
- package/packages/tools/dist/checkpoint.js +43 -0
- package/packages/tools/dist/codemod.d.ts +37 -0
- package/packages/tools/dist/codemod.js +69 -0
- package/packages/tools/dist/compact.d.ts +41 -0
- package/packages/tools/dist/compact.js +60 -0
- package/packages/tools/dist/data-transform.d.ts +10 -0
- package/packages/tools/dist/data-transform.js +124 -0
- package/packages/tools/dist/dead-symbols.d.ts +21 -0
- package/packages/tools/dist/dead-symbols.js +71 -0
- package/packages/tools/dist/delegate.d.ts +34 -0
- package/packages/tools/dist/delegate.js +130 -0
- package/packages/tools/dist/diff-parse.d.ts +26 -0
- package/packages/tools/dist/diff-parse.js +153 -0
- package/packages/tools/dist/digest.d.ts +53 -0
- package/packages/tools/dist/digest.js +242 -0
- package/packages/tools/dist/encode.d.ts +14 -0
- package/packages/tools/dist/encode.js +46 -0
- package/packages/tools/dist/env-info.d.ts +28 -0
- package/packages/tools/dist/env-info.js +58 -0
- package/packages/tools/dist/eval.d.ts +13 -0
- package/packages/tools/dist/eval.js +79 -0
- package/packages/tools/dist/evidence-map.d.ts +79 -0
- package/packages/tools/dist/evidence-map.js +203 -0
- package/packages/tools/dist/file-summary.d.ts +32 -0
- package/packages/tools/dist/file-summary.js +106 -0
- package/packages/tools/dist/file-walk.d.ts +4 -0
- package/packages/tools/dist/file-walk.js +75 -0
- package/packages/tools/dist/find-examples.d.ts +25 -0
- package/packages/tools/dist/find-examples.js +48 -0
- package/packages/tools/dist/find.d.ts +47 -0
- package/packages/tools/dist/find.js +120 -0
- package/packages/tools/dist/forge-classify.d.ts +44 -0
- package/packages/tools/dist/forge-classify.js +319 -0
- package/packages/tools/dist/forge-ground.d.ts +64 -0
- package/packages/tools/dist/forge-ground.js +184 -0
- package/packages/tools/dist/git-context.d.ts +22 -0
- package/packages/tools/dist/git-context.js +46 -0
- package/packages/tools/dist/graph-query.d.ts +89 -0
- package/packages/tools/dist/graph-query.js +194 -0
- package/packages/tools/dist/health.d.ts +14 -0
- package/packages/tools/dist/health.js +118 -0
- package/packages/tools/dist/http-request.d.ts +23 -0
- package/packages/tools/dist/http-request.js +58 -0
- package/packages/tools/dist/index.d.ts +49 -0
- package/packages/tools/dist/index.js +273 -0
- package/packages/tools/dist/lane.d.ts +39 -0
- package/packages/tools/dist/lane.js +227 -0
- package/packages/tools/dist/measure.d.ts +38 -0
- package/packages/tools/dist/measure.js +119 -0
- package/packages/tools/dist/onboard.d.ts +41 -0
- package/packages/tools/dist/onboard.js +1139 -0
- package/packages/tools/dist/parse-output.d.ts +80 -0
- package/packages/tools/dist/parse-output.js +158 -0
- package/packages/tools/dist/process-manager.d.ts +18 -0
- package/packages/tools/dist/process-manager.js +69 -0
- package/packages/tools/dist/queue.d.ts +38 -0
- package/packages/tools/dist/queue.js +126 -0
- package/packages/tools/dist/regex-test.d.ts +31 -0
- package/packages/tools/dist/regex-test.js +39 -0
- package/packages/tools/dist/rename.d.ts +29 -0
- package/packages/tools/dist/rename.js +70 -0
- package/packages/tools/dist/replay.d.ts +56 -0
- package/packages/tools/dist/replay.js +108 -0
- package/packages/tools/dist/schema-validate.d.ts +23 -0
- package/packages/tools/dist/schema-validate.js +141 -0
- package/packages/tools/dist/scope-map.d.ts +52 -0
- package/packages/tools/dist/scope-map.js +72 -0
- package/packages/tools/dist/snippet.d.ts +34 -0
- package/packages/tools/dist/snippet.js +80 -0
- package/packages/tools/dist/stash.d.ts +12 -0
- package/packages/tools/dist/stash.js +60 -0
- package/packages/tools/dist/stratum-card.d.ts +31 -0
- package/packages/tools/dist/stratum-card.js +239 -0
- package/packages/tools/dist/symbol.d.ts +28 -0
- package/packages/tools/dist/symbol.js +87 -0
- package/packages/tools/dist/test-run.d.ts +23 -0
- package/packages/tools/dist/test-run.js +55 -0
- package/packages/tools/dist/text-utils.d.ts +16 -0
- package/packages/tools/dist/text-utils.js +31 -0
- package/packages/tools/dist/time-utils.d.ts +18 -0
- package/packages/tools/dist/time-utils.js +135 -0
- package/packages/tools/dist/trace.d.ts +24 -0
- package/packages/tools/dist/trace.js +114 -0
- package/packages/tools/dist/truncation.d.ts +22 -0
- package/packages/tools/dist/truncation.js +45 -0
- package/packages/tools/dist/watch.d.ts +30 -0
- package/packages/tools/dist/watch.js +61 -0
- package/packages/tools/dist/web-fetch.d.ts +45 -0
- package/packages/tools/dist/web-fetch.js +249 -0
- package/packages/tools/dist/web-search.d.ts +23 -0
- package/packages/tools/dist/web-search.js +46 -0
- package/packages/tools/dist/workset.d.ts +45 -0
- package/packages/tools/dist/workset.js +77 -0
- package/packages/tui/dist/App.d.ts +8 -0
- package/packages/tui/dist/App.js +52659 -0
- package/packages/tui/dist/index.d.ts +19 -0
- package/packages/tui/dist/index.js +54742 -0
- package/packages/tui/dist/panels/CuratedPanel.d.ts +8 -0
- package/packages/tui/dist/panels/CuratedPanel.js +34452 -0
- package/packages/tui/dist/panels/LogPanel.d.ts +3 -0
- package/packages/tui/dist/panels/LogPanel.js +51894 -0
- package/packages/tui/dist/panels/SearchPanel.d.ts +10 -0
- package/packages/tui/dist/panels/SearchPanel.js +34985 -0
- package/packages/tui/dist/panels/StatusPanel.d.ts +8 -0
- package/packages/tui/dist/panels/StatusPanel.js +34465 -0
- package/skills/knowledge-base/SKILL.md +316 -0
|
@@ -0,0 +1,1139 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
3
|
+
import { basename, join, relative, resolve } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
DependencyAnalyzer,
|
|
6
|
+
DiagramGenerator,
|
|
7
|
+
EntryPointAnalyzer,
|
|
8
|
+
extractRegexCallGraph,
|
|
9
|
+
extractTsCallGraph,
|
|
10
|
+
PatternAnalyzer,
|
|
11
|
+
StructureAnalyzer,
|
|
12
|
+
SymbolAnalyzer
|
|
13
|
+
} from "@kb/analyzers";
|
|
14
|
+
const DISPLAY_TITLES = {
|
|
15
|
+
structure: "Project Structure",
|
|
16
|
+
dependencies: "Dependencies",
|
|
17
|
+
"entry-points": "Entry Points",
|
|
18
|
+
symbols: "Symbols",
|
|
19
|
+
patterns: "Patterns",
|
|
20
|
+
diagram: "C4 Container Diagram",
|
|
21
|
+
"code-map": "Code Map (Module Graph)",
|
|
22
|
+
"config-values": "Configuration Values",
|
|
23
|
+
"synthesis-guide": "Synthesis Guide"
|
|
24
|
+
};
|
|
25
|
+
function buildSynthesisGuide(steps, mode, projectName, dataMap) {
|
|
26
|
+
const lines = [`Analysis baselines for **${projectName}** have been generated.`];
|
|
27
|
+
if (mode === "generate") {
|
|
28
|
+
lines.push(
|
|
29
|
+
"Individual results are in the sibling `.md` and `.json` files in this directory.",
|
|
30
|
+
""
|
|
31
|
+
);
|
|
32
|
+
} else {
|
|
33
|
+
lines.push("Results are stored in the KB vector store.", "");
|
|
34
|
+
}
|
|
35
|
+
const symbolsData = dataMap.get("symbols");
|
|
36
|
+
const depsData = dataMap.get("dependencies");
|
|
37
|
+
const patternsData = dataMap.get("patterns");
|
|
38
|
+
const entryData = dataMap.get("entry-points");
|
|
39
|
+
const totalSymbols = symbolsData?.totalCount ?? 0;
|
|
40
|
+
const exportedSymbols = symbolsData?.exportedCount ?? 0;
|
|
41
|
+
const totalImports = depsData?.totalImports ?? 0;
|
|
42
|
+
const entryCount = entryData?.total ?? 0;
|
|
43
|
+
const patternList = patternsData?.patterns ?? [];
|
|
44
|
+
const patternNames = patternList.map((p) => p.pattern);
|
|
45
|
+
const isSpring = patternNames.some((p) => p.startsWith("Spring"));
|
|
46
|
+
const isCdk = patternNames.includes("AWS CDK") || patternNames.includes("CDK IaC");
|
|
47
|
+
const isMaven = patternNames.includes("Maven");
|
|
48
|
+
const isServerless = patternNames.includes("Serverless") || entryCount > 3;
|
|
49
|
+
const externalPkgs = depsData?.external ? Object.keys(depsData.external) : [];
|
|
50
|
+
const isNode = externalPkgs.some(
|
|
51
|
+
(d) => ["express", "fastify", "next", "react", "vitest", "jest"].includes(d)
|
|
52
|
+
) || totalImports > 0;
|
|
53
|
+
const isMonorepo = externalPkgs.some((d) => ["turbo", "lerna", "nx"].includes(d)) || patternNames.includes("Monorepo");
|
|
54
|
+
lines.push("### Project Profile", "");
|
|
55
|
+
lines.push(
|
|
56
|
+
`- **${totalSymbols} symbols** (${exportedSymbols} exported), **${totalImports} imports**, **${entryCount} entry ${entryCount === 1 ? "point" : "points"}**`
|
|
57
|
+
);
|
|
58
|
+
if (patternNames.length > 0) {
|
|
59
|
+
lines.push(`- **Detected**: ${patternNames.slice(0, 8).join(", ")}`);
|
|
60
|
+
}
|
|
61
|
+
lines.push("");
|
|
62
|
+
if (totalSymbols === 0 && totalImports === 0 && entryCount === 0) {
|
|
63
|
+
lines.push(
|
|
64
|
+
"> **Note:** This project appears to be empty or contains no analyzable source code.",
|
|
65
|
+
"> Run onboard again after adding source files."
|
|
66
|
+
);
|
|
67
|
+
return lines.join("\n");
|
|
68
|
+
}
|
|
69
|
+
const completed = steps.filter((s) => s.status === "success");
|
|
70
|
+
const failed = steps.filter((s) => s.status === "failed");
|
|
71
|
+
lines.push("### Completed Analyses", "");
|
|
72
|
+
for (const step of completed) {
|
|
73
|
+
const title = DISPLAY_TITLES[step.name] ?? step.name;
|
|
74
|
+
const size = step.output.length > 1e3 ? `${Math.round(step.output.length / 1024)}KB` : `${step.output.length}B`;
|
|
75
|
+
const ext = ".md";
|
|
76
|
+
if (mode === "generate") {
|
|
77
|
+
lines.push(`- \u2713 [${title}](./${step.name}${ext}) (${size})`);
|
|
78
|
+
} else {
|
|
79
|
+
lines.push(`- \u2713 ${title} (${size})`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (failed.length > 0) {
|
|
83
|
+
lines.push("", "### Failed Analyses", "");
|
|
84
|
+
for (const step of failed) {
|
|
85
|
+
lines.push(`- \u2717 ${step.name}: ${step.error}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
lines.push(
|
|
89
|
+
"",
|
|
90
|
+
"### Recommended Reading Order",
|
|
91
|
+
"",
|
|
92
|
+
"1. **Start with** `synthesis-guide.md` (this file) \u2192 `entry-points.md` \u2192 `patterns.md`",
|
|
93
|
+
"2. **Module graph** via `code-map.md` \u2014 cross-package call edges with function names",
|
|
94
|
+
"3. **Architecture** via `diagram.md` (C4 Container) \u2192 `dependencies.md`",
|
|
95
|
+
"4. **Browse structure** via `structure.md` for file layout",
|
|
96
|
+
"5. **API surface** via `symbols.md` \u2014 file paths + exported symbols (capped at 80KB)",
|
|
97
|
+
"6. **Reference**: `config-values.md` (config reference)",
|
|
98
|
+
"",
|
|
99
|
+
"> **Size guidance:** Total output is ~"
|
|
100
|
+
);
|
|
101
|
+
const totalKB = completed.reduce((sum, s) => sum + s.output.length, 0) / 1024;
|
|
102
|
+
lines[lines.length - 1] += `${Math.round(totalKB)}KB. Focus on code-map.md + entry-points.md + diagram.md (~${Math.round((completed.find((s) => s.name === "code-map")?.output.length ?? 0) / 1024 + (completed.find((s) => s.name === "entry-points")?.output.length ?? 0) / 1024 + (completed.find((s) => s.name === "diagram")?.output.length ?? 0) / 1024)}KB) for maximum signal-to-token ratio.`;
|
|
103
|
+
lines.push(
|
|
104
|
+
"",
|
|
105
|
+
"### Synthesize Knowledge",
|
|
106
|
+
"",
|
|
107
|
+
"Produce the following `kb_remember` entries:",
|
|
108
|
+
""
|
|
109
|
+
);
|
|
110
|
+
lines.push("1. **Architecture Summary** (category: `architecture`)");
|
|
111
|
+
if (isMonorepo) {
|
|
112
|
+
lines.push(" - Package boundaries, dependency graph between packages");
|
|
113
|
+
lines.push(" - Shared vs service-specific code");
|
|
114
|
+
} else if (isServerless) {
|
|
115
|
+
lines.push(" - Lambda functions, triggers, event flow");
|
|
116
|
+
lines.push(" - Infrastructure patterns (queues, tables, APIs)");
|
|
117
|
+
} else if (isSpring) {
|
|
118
|
+
lines.push(" - Controller \u2192 Service \u2192 Repository layers");
|
|
119
|
+
lines.push(" - Spring configuration and profiles");
|
|
120
|
+
} else {
|
|
121
|
+
lines.push(" - Layer structure, dependency flow");
|
|
122
|
+
lines.push(" - Key design decisions");
|
|
123
|
+
}
|
|
124
|
+
lines.push("");
|
|
125
|
+
lines.push("2. **Domain Model** (category: `architecture`)");
|
|
126
|
+
lines.push(" - Key entities/types and their relationships");
|
|
127
|
+
lines.push(" - Data flow from entry points through processing");
|
|
128
|
+
lines.push("");
|
|
129
|
+
lines.push("3. **Conventions** (category: `conventions`)");
|
|
130
|
+
lines.push(" - Naming patterns, file organization, testing approach");
|
|
131
|
+
if (isCdk) lines.push(" - CDK construct patterns and stack organization");
|
|
132
|
+
if (isNode) lines.push(" - Build tooling, package manager, module system");
|
|
133
|
+
if (isMaven) lines.push(" - Maven module structure, dependency management");
|
|
134
|
+
lines.push(
|
|
135
|
+
"",
|
|
136
|
+
"### Using KB Tools",
|
|
137
|
+
"",
|
|
138
|
+
"This project has a KB MCP server with tools for search, analysis, memory, and more.",
|
|
139
|
+
"Add the KB tools to your agent configuration so every session benefits from them.",
|
|
140
|
+
"",
|
|
141
|
+
"**Add to `.github/copilot-instructions.md`** or `AGENTS.md`:",
|
|
142
|
+
"",
|
|
143
|
+
"```markdown",
|
|
144
|
+
"## KB Tools",
|
|
145
|
+
"",
|
|
146
|
+
`Before starting any task on **${projectName}**, use these MCP tools:`,
|
|
147
|
+
"",
|
|
148
|
+
"| Action | Tool | Example |",
|
|
149
|
+
"|--------|------|---------|",
|
|
150
|
+
`| Search code & decisions | \`kb_search\` | \`kb_search({ query: "..." })\` |`,
|
|
151
|
+
'| Find symbol definition | `kb_symbol` | `kb_symbol({ name: "ClassName" })` |',
|
|
152
|
+
'| Trace call chains | `kb_trace` | `kb_trace({ symbol: "fn", file: "path" })` |',
|
|
153
|
+
'| Impact of a change | `kb_blast_radius` | `kb_blast_radius({ changed_files: ["..."] })` |',
|
|
154
|
+
'| Persist what you learn | `kb_remember` | `kb_remember({ title: "...", category: "decisions" })` |',
|
|
155
|
+
"| Typecheck + lint | `kb_check` | `kb_check({})` |",
|
|
156
|
+
"| Run tests | `kb_test_run` | `kb_test_run({})` |",
|
|
157
|
+
"```",
|
|
158
|
+
"",
|
|
159
|
+
"**Add to each `.github/agents/*.agent.md`** (Skills Reference table):",
|
|
160
|
+
"",
|
|
161
|
+
"```markdown",
|
|
162
|
+
"| Context | Skill | Details |",
|
|
163
|
+
"|---------|-------|---------|",
|
|
164
|
+
"| KB search, analysis, memory | `kb` | `path/to/skills/kb/SKILL.md` |",
|
|
165
|
+
"```",
|
|
166
|
+
"",
|
|
167
|
+
"The KB skill teaches agents the full tool set:",
|
|
168
|
+
"",
|
|
169
|
+
"| Category | Tools | Purpose |",
|
|
170
|
+
"|----------|-------|---------|",
|
|
171
|
+
"| Search & Discovery | `kb_search`, `kb_find`, `kb_symbol`, `kb_trace` | Find code, symbols, data flow |",
|
|
172
|
+
"| Code Analysis | `kb_analyze_*`, `kb_blast_radius` | Structure, deps, patterns, impact |",
|
|
173
|
+
"| Knowledge | `kb_remember`, `kb_read`, `kb_update`, `kb_forget` | Persistent cross-session memory |",
|
|
174
|
+
"| Execution | `kb_check`, `kb_test_run`, `kb_eval` | Typecheck, lint, test, run code |",
|
|
175
|
+
"| Refactoring | `kb_rename`, `kb_codemod`, `kb_dead_symbols` | Safe renames, transforms, cleanup |",
|
|
176
|
+
"| Web & API | `kb_web_fetch`, `kb_web_search`, `kb_http` | Research, API testing |",
|
|
177
|
+
"| Context | `kb_workset`, `kb_stash`, `kb_checkpoint` | Manage working sets, save progress |",
|
|
178
|
+
"",
|
|
179
|
+
"**Workflow pattern \u2014 use on every task:**",
|
|
180
|
+
"",
|
|
181
|
+
"```",
|
|
182
|
+
`kb_search({ query: "your task keywords" }) # Recall prior decisions`,
|
|
183
|
+
`kb_scope_map({ task: "what you are doing" }) # Get a reading plan`,
|
|
184
|
+
"# ... do the work ...",
|
|
185
|
+
`kb_remember({ title: "What I learned", category: "decisions" }) # Persist`,
|
|
186
|
+
"```"
|
|
187
|
+
);
|
|
188
|
+
return lines.join("\n");
|
|
189
|
+
}
|
|
190
|
+
const TEST_SEGMENTS = /* @__PURE__ */ new Set([
|
|
191
|
+
"test",
|
|
192
|
+
"tests",
|
|
193
|
+
"__tests__",
|
|
194
|
+
"spec",
|
|
195
|
+
"specs",
|
|
196
|
+
"__mocks__",
|
|
197
|
+
"__fixtures__",
|
|
198
|
+
"fixtures",
|
|
199
|
+
"test-utils"
|
|
200
|
+
]);
|
|
201
|
+
function isTestPath(filePath) {
|
|
202
|
+
const segments = filePath.replace(/\\/g, "/").split("/");
|
|
203
|
+
return segments.some((s) => TEST_SEGMENTS.has(s)) || /\.(test|spec)\.[jt]sx?$/.test(filePath) || /Test\.java$/.test(filePath);
|
|
204
|
+
}
|
|
205
|
+
function buildCodeMap(dataMap, projectName, callGraph) {
|
|
206
|
+
const depsData = dataMap.get("dependencies");
|
|
207
|
+
const symbolsData = dataMap.get("symbols");
|
|
208
|
+
const entryData = dataMap.get("entry-points");
|
|
209
|
+
const lines = [`## Code Map: ${projectName}
|
|
210
|
+
`];
|
|
211
|
+
if (!depsData && !symbolsData) {
|
|
212
|
+
lines.push("No dependency or symbol data available.");
|
|
213
|
+
return lines.join("\n");
|
|
214
|
+
}
|
|
215
|
+
const reverseGraph = depsData?.reverseGraph ?? {};
|
|
216
|
+
const symbols = symbolsData?.symbols ?? [];
|
|
217
|
+
const entryPoints = entryData?.entryPoints ?? [];
|
|
218
|
+
const exportsByFile = /* @__PURE__ */ new Map();
|
|
219
|
+
for (const sym of symbols) {
|
|
220
|
+
if (!sym.exported) continue;
|
|
221
|
+
const fp = sym.filePath.replace(/\\/g, "/");
|
|
222
|
+
if (isTestPath(fp)) continue;
|
|
223
|
+
const existing = exportsByFile.get(fp);
|
|
224
|
+
if (existing) {
|
|
225
|
+
existing.push({ name: sym.name, kind: sym.kind });
|
|
226
|
+
} else {
|
|
227
|
+
exportsByFile.set(fp, [{ name: sym.name, kind: sym.kind }]);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const normReverse = /* @__PURE__ */ new Map();
|
|
231
|
+
for (const [file, importers] of Object.entries(reverseGraph)) {
|
|
232
|
+
const nf = file.replace(/\\/g, "/");
|
|
233
|
+
const resolved = resolveExtensionlessPath(nf, exportsByFile);
|
|
234
|
+
const srcImporters = importers.map((i) => i.replace(/\\/g, "/")).filter((i) => !isTestPath(i));
|
|
235
|
+
if (srcImporters.length === 0) continue;
|
|
236
|
+
const existing = normReverse.get(resolved);
|
|
237
|
+
if (existing) {
|
|
238
|
+
for (const imp of srcImporters) existing.add(imp);
|
|
239
|
+
} else {
|
|
240
|
+
normReverse.set(resolved, new Set(srcImporters));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const entryLookup = /* @__PURE__ */ new Map();
|
|
244
|
+
for (const ep of entryPoints) {
|
|
245
|
+
entryLookup.set(ep.filePath.replace(/\\/g, "/"), { name: ep.name, trigger: ep.trigger });
|
|
246
|
+
}
|
|
247
|
+
const crossCalls = /* @__PURE__ */ new Map();
|
|
248
|
+
const crossCalledBy = /* @__PURE__ */ new Map();
|
|
249
|
+
if (callGraph) {
|
|
250
|
+
for (const [from, targets] of callGraph) {
|
|
251
|
+
if (isTestPath(from)) continue;
|
|
252
|
+
const fromPkg = getPackageKey(from);
|
|
253
|
+
for (const [to, syms] of targets) {
|
|
254
|
+
if (isTestPath(to)) continue;
|
|
255
|
+
const toPkg = getPackageKey(to);
|
|
256
|
+
if (fromPkg === toPkg) continue;
|
|
257
|
+
let outMap = crossCalls.get(from);
|
|
258
|
+
if (!outMap) {
|
|
259
|
+
outMap = /* @__PURE__ */ new Map();
|
|
260
|
+
crossCalls.set(from, outMap);
|
|
261
|
+
}
|
|
262
|
+
outMap.set(to, syms);
|
|
263
|
+
const callers = crossCalledBy.get(to);
|
|
264
|
+
const entry = { file: from, symbols: syms };
|
|
265
|
+
if (callers) {
|
|
266
|
+
callers.push(entry);
|
|
267
|
+
} else {
|
|
268
|
+
crossCalledBy.set(to, [entry]);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const allFiles = /* @__PURE__ */ new Set();
|
|
274
|
+
for (const fp of entryLookup.keys()) allFiles.add(fp);
|
|
275
|
+
for (const fp of crossCalls.keys()) allFiles.add(fp);
|
|
276
|
+
for (const fp of crossCalledBy.keys()) allFiles.add(fp);
|
|
277
|
+
if (!callGraph) {
|
|
278
|
+
for (const fp of exportsByFile.keys()) {
|
|
279
|
+
const importers = normReverse.get(fp);
|
|
280
|
+
if (importers && importers.size >= 3) allFiles.add(fp);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const groups = /* @__PURE__ */ new Map();
|
|
284
|
+
for (const fp of allFiles) {
|
|
285
|
+
const group = getPackageKey(fp);
|
|
286
|
+
const arr = groups.get(group);
|
|
287
|
+
if (arr) {
|
|
288
|
+
arr.push(fp);
|
|
289
|
+
} else {
|
|
290
|
+
groups.set(group, [fp]);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const sortedGroups = [...groups.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
294
|
+
const graphType = callGraph ? "AST call graph" : "import analysis";
|
|
295
|
+
const crossCount = callGraph ? `, ${crossCalls.size} cross-package callers` : "";
|
|
296
|
+
lines.push(`**${allFiles.size} key modules** (${graphType}${crossCount})
|
|
297
|
+
`);
|
|
298
|
+
lines.push(
|
|
299
|
+
"**Legend:** \u26A1 Entry point | \u{1F4E4} Exports | \u2192 Calls (outgoing) | \u2190 Called by (incoming) | \u27A1 Used by (import)\n"
|
|
300
|
+
);
|
|
301
|
+
for (const [group, files] of sortedGroups) {
|
|
302
|
+
files.sort();
|
|
303
|
+
lines.push(`### ${group}/
|
|
304
|
+
`);
|
|
305
|
+
for (const fp of files) {
|
|
306
|
+
const exports = exportsByFile.get(fp);
|
|
307
|
+
const entry = entryLookup.get(fp);
|
|
308
|
+
const outCalls = crossCalls.get(fp);
|
|
309
|
+
const inCalls = crossCalledBy.get(fp);
|
|
310
|
+
const importedBySet = normReverse.get(fp);
|
|
311
|
+
const shortPath = fp.startsWith(`${group}/`) ? fp.slice(group.length + 1) : fp;
|
|
312
|
+
lines.push(`**${shortPath}**`);
|
|
313
|
+
if (entry) {
|
|
314
|
+
lines.push(` \u26A1 Entry: \`${entry.name}\`${entry.trigger ? ` (${entry.trigger})` : ""}`);
|
|
315
|
+
}
|
|
316
|
+
if (exports && exports.length > 0) {
|
|
317
|
+
const compact = exports.slice(0, 8).map((e) => `${e.name}`).join(", ");
|
|
318
|
+
const more = exports.length > 8 ? ` (+${exports.length - 8})` : "";
|
|
319
|
+
lines.push(` \u{1F4E4} ${compact}${more}`);
|
|
320
|
+
}
|
|
321
|
+
if (outCalls && outCalls.size > 0) {
|
|
322
|
+
const sorted = [...outCalls.entries()].sort((a, b) => b[1].length - a[1].length);
|
|
323
|
+
for (const [target, syms] of sorted.slice(0, 4)) {
|
|
324
|
+
const shortTarget = target.startsWith(`${group}/`) ? target.slice(group.length + 1) : target;
|
|
325
|
+
lines.push(
|
|
326
|
+
` \u2192 ${shortTarget}: ${syms.slice(0, 5).join(", ")}${syms.length > 5 ? "\u2026" : ""}`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
if (sorted.length > 4) lines.push(` \u2192 +${sorted.length - 4} more targets`);
|
|
330
|
+
}
|
|
331
|
+
if (inCalls && inCalls.length > 0) {
|
|
332
|
+
for (const c of inCalls.slice(0, 4)) {
|
|
333
|
+
const shortCaller = c.file.startsWith(`${group}/`) ? c.file.slice(group.length + 1) : c.file;
|
|
334
|
+
lines.push(
|
|
335
|
+
` \u2190 ${shortCaller}: ${c.symbols.slice(0, 4).join(", ")}${c.symbols.length > 4 ? "\u2026" : ""}`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
if (inCalls.length > 4) lines.push(` \u2190 +${inCalls.length - 4} more callers`);
|
|
339
|
+
} else if (!callGraph && importedBySet && importedBySet.size > 0) {
|
|
340
|
+
const importedBy = [...importedBySet].filter((i) => !isTestPath(i));
|
|
341
|
+
if (importedBy.length <= 3) {
|
|
342
|
+
lines.push(` \u27A1 Used by: ${importedBy.join(", ")}`);
|
|
343
|
+
} else {
|
|
344
|
+
lines.push(
|
|
345
|
+
` \u27A1 Used by: ${importedBy.slice(0, 3).join(", ")} (+${importedBy.length - 3} more)`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
lines.push("");
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return lines.join("\n");
|
|
353
|
+
}
|
|
354
|
+
function getPackageKey(fp) {
|
|
355
|
+
const parts = fp.split("/");
|
|
356
|
+
if (parts.length >= 2 && ["packages", "services", "providers", "apps", "libs"].includes(parts[0])) {
|
|
357
|
+
return `${parts[0]}/${parts[1]}`;
|
|
358
|
+
}
|
|
359
|
+
const javaIdx = parts.indexOf("java");
|
|
360
|
+
const kotlinIdx = parts.indexOf("kotlin");
|
|
361
|
+
const langIdx = javaIdx >= 0 ? javaIdx : kotlinIdx;
|
|
362
|
+
if (langIdx >= 0 && langIdx + 2 < parts.length) {
|
|
363
|
+
const afterLang = parts.slice(langIdx + 1);
|
|
364
|
+
if (["com", "org", "net", "io", "dev"].includes(afterLang[0]) && afterLang.length >= 3) {
|
|
365
|
+
return afterLang.slice(0, 3).join("/");
|
|
366
|
+
}
|
|
367
|
+
return afterLang.slice(0, 2).join("/");
|
|
368
|
+
}
|
|
369
|
+
if (parts[0] === "src" && parts.length >= 3) {
|
|
370
|
+
return `${parts[0]}/${parts[1]}`;
|
|
371
|
+
}
|
|
372
|
+
return parts[0];
|
|
373
|
+
}
|
|
374
|
+
function buildDiagrams(callGraph, dataMap, projectName) {
|
|
375
|
+
const symbolsData = dataMap.get("symbols");
|
|
376
|
+
const entryData = dataMap.get("entry-points");
|
|
377
|
+
const depsData = dataMap.get("dependencies");
|
|
378
|
+
const pkgEdges = /* @__PURE__ */ new Map();
|
|
379
|
+
for (const [from, targets] of callGraph) {
|
|
380
|
+
if (isTestPath(from)) continue;
|
|
381
|
+
for (const [to, syms] of targets) {
|
|
382
|
+
if (isTestPath(to)) continue;
|
|
383
|
+
const fromPkg = getPackageKey(from);
|
|
384
|
+
const toPkg = getPackageKey(to);
|
|
385
|
+
if (fromPkg === toPkg) continue;
|
|
386
|
+
const key = `${fromPkg}|${toPkg}`;
|
|
387
|
+
pkgEdges.set(key, (pkgEdges.get(key) ?? 0) + syms.length);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (pkgEdges.size === 0)
|
|
391
|
+
return "## Architecture Diagram\n\nNo cross-package dependencies detected.";
|
|
392
|
+
const pkgNodes = /* @__PURE__ */ new Set();
|
|
393
|
+
for (const key of pkgEdges.keys()) {
|
|
394
|
+
const [from, to] = key.split("|");
|
|
395
|
+
pkgNodes.add(from);
|
|
396
|
+
pkgNodes.add(to);
|
|
397
|
+
}
|
|
398
|
+
const exportsByPkg = /* @__PURE__ */ new Map();
|
|
399
|
+
if (symbolsData?.symbols) {
|
|
400
|
+
for (const sym of symbolsData.symbols) {
|
|
401
|
+
if (!sym.exported) continue;
|
|
402
|
+
const fp = sym.filePath.replace(/\\/g, "/");
|
|
403
|
+
if (isTestPath(fp)) continue;
|
|
404
|
+
const pkg = getPackageKey(fp);
|
|
405
|
+
exportsByPkg.set(pkg, (exportsByPkg.get(pkg) ?? 0) + 1);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const entryByPkg = /* @__PURE__ */ new Map();
|
|
409
|
+
if (entryData?.entryPoints) {
|
|
410
|
+
for (const ep of entryData.entryPoints) {
|
|
411
|
+
const fp = ep.filePath.replace(/\\/g, "/");
|
|
412
|
+
const pkg = getPackageKey(fp);
|
|
413
|
+
const existing = entryByPkg.get(pkg);
|
|
414
|
+
if (existing) {
|
|
415
|
+
existing.count++;
|
|
416
|
+
if (ep.trigger) existing.triggers.add(ep.trigger);
|
|
417
|
+
} else {
|
|
418
|
+
entryByPkg.set(pkg, { count: 1, triggers: new Set(ep.trigger ? [ep.trigger] : []) });
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const extSystems = [];
|
|
423
|
+
if (depsData?.external) {
|
|
424
|
+
const external = depsData.external;
|
|
425
|
+
const awsServices = {
|
|
426
|
+
"client-dynamodb": { id: "dynamodb", name: "DynamoDB" },
|
|
427
|
+
"lib-dynamodb": { id: "dynamodb", name: "DynamoDB" },
|
|
428
|
+
"client-sqs": { id: "sqs", name: "SQS" },
|
|
429
|
+
"client-ses": { id: "ses", name: "SES" },
|
|
430
|
+
"client-sesv2": { id: "ses", name: "SES" },
|
|
431
|
+
"client-s3": { id: "s3", name: "S3" },
|
|
432
|
+
"client-eventbridge": { id: "eventbridge", name: "EventBridge" },
|
|
433
|
+
"client-sns": { id: "sns", name: "SNS" },
|
|
434
|
+
"client-secrets-manager": { id: "secrets", name: "Secrets Manager" },
|
|
435
|
+
"client-scheduler": { id: "scheduler", name: "EventBridge Scheduler" },
|
|
436
|
+
"client-apigatewaymanagementapi": { id: "apigw", name: "API Gateway" },
|
|
437
|
+
"client-cloudwatch": { id: "cloudwatch", name: "CloudWatch" }
|
|
438
|
+
};
|
|
439
|
+
const seen = /* @__PURE__ */ new Set();
|
|
440
|
+
for (const depName of Object.keys(external)) {
|
|
441
|
+
for (const [suffix, info] of Object.entries(awsServices)) {
|
|
442
|
+
if (depName.includes(suffix) && !seen.has(info.id)) {
|
|
443
|
+
seen.add(info.id);
|
|
444
|
+
extSystems.push(info);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
extSystems.sort((a, b) => a.name.localeCompare(b.name));
|
|
449
|
+
}
|
|
450
|
+
const boundaries = /* @__PURE__ */ new Map();
|
|
451
|
+
for (const pkg of [...pkgNodes].sort()) {
|
|
452
|
+
const boundary = pkg.split("/")[0];
|
|
453
|
+
const arr = boundaries.get(boundary);
|
|
454
|
+
if (arr) arr.push(pkg);
|
|
455
|
+
else boundaries.set(boundary, [pkg]);
|
|
456
|
+
}
|
|
457
|
+
const langByPkg = /* @__PURE__ */ new Map();
|
|
458
|
+
if (symbolsData?.symbols) {
|
|
459
|
+
const extCounts = /* @__PURE__ */ new Map();
|
|
460
|
+
for (const sym of symbolsData.symbols) {
|
|
461
|
+
const fp = sym.filePath.replace(/\\/g, "/");
|
|
462
|
+
const pkg = getPackageKey(fp);
|
|
463
|
+
const ext = fp.match(/\.[^./]+$/)?.[0] || "";
|
|
464
|
+
if (!extCounts.has(pkg)) extCounts.set(pkg, /* @__PURE__ */ new Map());
|
|
465
|
+
const counts = extCounts.get(pkg);
|
|
466
|
+
counts.set(ext, (counts.get(ext) ?? 0) + 1);
|
|
467
|
+
}
|
|
468
|
+
const EXT_LANG = {
|
|
469
|
+
".ts": "TypeScript",
|
|
470
|
+
".tsx": "TypeScript",
|
|
471
|
+
".js": "JavaScript",
|
|
472
|
+
".jsx": "JavaScript",
|
|
473
|
+
".java": "Java",
|
|
474
|
+
".kt": "Kotlin",
|
|
475
|
+
".scala": "Scala",
|
|
476
|
+
".py": "Python",
|
|
477
|
+
".go": "Go",
|
|
478
|
+
".rs": "Rust",
|
|
479
|
+
".cs": "C#",
|
|
480
|
+
".rb": "Ruby",
|
|
481
|
+
".php": "PHP",
|
|
482
|
+
".swift": "Swift"
|
|
483
|
+
};
|
|
484
|
+
for (const [pkg, counts] of extCounts) {
|
|
485
|
+
let maxExt = "";
|
|
486
|
+
let maxCount = 0;
|
|
487
|
+
for (const [ext, count] of counts) {
|
|
488
|
+
if (count > maxCount) {
|
|
489
|
+
maxCount = count;
|
|
490
|
+
maxExt = ext;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
langByPkg.set(pkg, EXT_LANG[maxExt] || "TypeScript");
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const nodeId = (pkg) => pkg.replace(/[^a-zA-Z0-9]/g, "_");
|
|
497
|
+
const lines = [];
|
|
498
|
+
lines.push("```mermaid");
|
|
499
|
+
lines.push("C4Container");
|
|
500
|
+
lines.push(` title C4 Container: ${projectName}`);
|
|
501
|
+
lines.push("");
|
|
502
|
+
const descFor = (pkg) => {
|
|
503
|
+
const parts = [];
|
|
504
|
+
const entry = entryByPkg.get(pkg);
|
|
505
|
+
if (entry) {
|
|
506
|
+
parts.push(`${entry.count} handlers`);
|
|
507
|
+
if (entry.triggers.size > 0) parts.push([...entry.triggers].join(", "));
|
|
508
|
+
}
|
|
509
|
+
const exports = exportsByPkg.get(pkg);
|
|
510
|
+
if (exports) parts.push(`${exports} exports`);
|
|
511
|
+
return parts.join(" \xB7 ") || "";
|
|
512
|
+
};
|
|
513
|
+
const techFor = (pkg) => {
|
|
514
|
+
const lang = langByPkg.get(pkg) || "TypeScript";
|
|
515
|
+
if (pkg.startsWith("infra")) return `CDK/${lang}`;
|
|
516
|
+
if (entryByPkg.has(pkg)) {
|
|
517
|
+
const entry = entryByPkg.get(pkg);
|
|
518
|
+
const hasSqs = entry?.triggers.has("SQS") || entry?.triggers.has("SNS");
|
|
519
|
+
if (hasSqs || entry?.triggers.has("API Gateway")) return `Lambda/${lang}`;
|
|
520
|
+
if (entry?.triggers.has("HTTP Server") || entry?.triggers.has("HTTP Endpoint"))
|
|
521
|
+
return `Spring Boot/${lang}`;
|
|
522
|
+
}
|
|
523
|
+
return lang;
|
|
524
|
+
};
|
|
525
|
+
for (const [boundary, pkgs] of [...boundaries.entries()].sort()) {
|
|
526
|
+
const genericBoundaries = /* @__PURE__ */ new Set(["com", "org", "net", "io", "dev", "src"]);
|
|
527
|
+
const boundaryLabel = genericBoundaries.has(boundary) ? projectName.charAt(0).toUpperCase() + projectName.slice(1) : boundary.charAt(0).toUpperCase() + boundary.slice(1);
|
|
528
|
+
if (pkgs.length === 1 && pkgs[0] === boundary) {
|
|
529
|
+
const pkg = pkgs[0];
|
|
530
|
+
lines.push(` Container(${nodeId(pkg)}, "${pkg}", "${techFor(pkg)}", "${descFor(pkg)}")`);
|
|
531
|
+
} else {
|
|
532
|
+
lines.push(` System_Boundary(${nodeId(boundary)}_boundary, "${boundaryLabel}") {`);
|
|
533
|
+
for (const pkg of pkgs) {
|
|
534
|
+
const shortName = pkg.split("/").slice(1).join("/") || pkg;
|
|
535
|
+
lines.push(
|
|
536
|
+
` Container(${nodeId(pkg)}, "${shortName}", "${techFor(pkg)}", "${descFor(pkg)}")`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
lines.push(" }");
|
|
540
|
+
}
|
|
541
|
+
lines.push("");
|
|
542
|
+
}
|
|
543
|
+
if (extSystems.length > 0) {
|
|
544
|
+
for (const ext of extSystems) {
|
|
545
|
+
lines.push(` System_Ext(ext_${ext.id}, "${ext.name}", "AWS")`);
|
|
546
|
+
}
|
|
547
|
+
lines.push("");
|
|
548
|
+
}
|
|
549
|
+
const sortedEdges = [...pkgEdges.entries()].sort((a, b) => b[1] - a[1]);
|
|
550
|
+
for (const [key, count] of sortedEdges.slice(0, 30)) {
|
|
551
|
+
const [from, to] = key.split("|");
|
|
552
|
+
lines.push(` Rel(${nodeId(from)}, ${nodeId(to)}, "Uses", "${count} calls")`);
|
|
553
|
+
}
|
|
554
|
+
lines.push("```");
|
|
555
|
+
const c4Diagram = `## C4 Container Diagram
|
|
556
|
+
|
|
557
|
+
${lines.join("\n")}`;
|
|
558
|
+
const flowLines = [];
|
|
559
|
+
flowLines.push("```mermaid");
|
|
560
|
+
flowLines.push("graph TB");
|
|
561
|
+
const allTriggers = /* @__PURE__ */ new Set();
|
|
562
|
+
for (const [, info] of entryByPkg) {
|
|
563
|
+
for (const t of info.triggers) allTriggers.add(t);
|
|
564
|
+
}
|
|
565
|
+
if (allTriggers.size > 0) {
|
|
566
|
+
flowLines.push(' subgraph Triggers["External Triggers"]');
|
|
567
|
+
for (const trigger of [...allTriggers].sort()) {
|
|
568
|
+
const tId = `trigger_${trigger.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
569
|
+
flowLines.push(` ${tId}(("${trigger}"))`);
|
|
570
|
+
}
|
|
571
|
+
flowLines.push(" end");
|
|
572
|
+
flowLines.push("");
|
|
573
|
+
}
|
|
574
|
+
const servicePkgs = [...pkgNodes].filter((p) => entryByPkg.has(p)).sort();
|
|
575
|
+
const libraryPkgs = [...pkgNodes].filter((p) => !entryByPkg.has(p)).sort();
|
|
576
|
+
if (servicePkgs.length > 0) {
|
|
577
|
+
flowLines.push(' subgraph Services["Service Layer"]');
|
|
578
|
+
for (const pkg of servicePkgs) {
|
|
579
|
+
const id = `flow_${nodeId(pkg)}`;
|
|
580
|
+
const shortName = pkg.includes("/") ? pkg.split("/").pop() ?? pkg : pkg;
|
|
581
|
+
const entry = entryByPkg.get(pkg);
|
|
582
|
+
flowLines.push(` ${id}["${shortName} (${entry?.count ?? 0} handlers)"]`);
|
|
583
|
+
}
|
|
584
|
+
flowLines.push(" end");
|
|
585
|
+
flowLines.push("");
|
|
586
|
+
}
|
|
587
|
+
if (libraryPkgs.length > 0) {
|
|
588
|
+
flowLines.push(' subgraph Libraries["Shared Libraries"]');
|
|
589
|
+
for (const pkg of libraryPkgs) {
|
|
590
|
+
const id = `flow_${nodeId(pkg)}`;
|
|
591
|
+
const shortName = pkg.includes("/") ? pkg.split("/").pop() ?? pkg : pkg;
|
|
592
|
+
flowLines.push(` ${id}["${shortName}"]`);
|
|
593
|
+
}
|
|
594
|
+
flowLines.push(" end");
|
|
595
|
+
flowLines.push("");
|
|
596
|
+
}
|
|
597
|
+
if (extSystems.length > 0) {
|
|
598
|
+
flowLines.push(' subgraph External["AWS Services"]');
|
|
599
|
+
for (const ext of extSystems) {
|
|
600
|
+
flowLines.push(` flow_ext_${ext.id}[("${ext.name}")]`);
|
|
601
|
+
}
|
|
602
|
+
flowLines.push(" end");
|
|
603
|
+
flowLines.push("");
|
|
604
|
+
}
|
|
605
|
+
for (const pkg of servicePkgs) {
|
|
606
|
+
const entry = entryByPkg.get(pkg);
|
|
607
|
+
if (!entry) continue;
|
|
608
|
+
const svcId = `flow_${nodeId(pkg)}`;
|
|
609
|
+
for (const trigger of entry.triggers) {
|
|
610
|
+
const tId = `trigger_${trigger.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
611
|
+
flowLines.push(` ${tId} --> ${svcId}`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
const svcToLib = sortedEdges.filter(([key]) => {
|
|
615
|
+
const [from, to] = key.split("|");
|
|
616
|
+
return entryByPkg.has(from) && !entryByPkg.has(to);
|
|
617
|
+
});
|
|
618
|
+
for (const [key, count] of svcToLib.slice(0, 15)) {
|
|
619
|
+
const [from, to] = key.split("|");
|
|
620
|
+
flowLines.push(` flow_${nodeId(from)} -->|${count}| flow_${nodeId(to)}`);
|
|
621
|
+
}
|
|
622
|
+
const libToLib = sortedEdges.filter(([key]) => {
|
|
623
|
+
const [from, to] = key.split("|");
|
|
624
|
+
return !entryByPkg.has(from) && !entryByPkg.has(to);
|
|
625
|
+
});
|
|
626
|
+
for (const [key, count] of libToLib.slice(0, 10)) {
|
|
627
|
+
const [from, to] = key.split("|");
|
|
628
|
+
flowLines.push(` flow_${nodeId(from)} -->|${count}| flow_${nodeId(to)}`);
|
|
629
|
+
}
|
|
630
|
+
flowLines.push("```");
|
|
631
|
+
const flowDiagram = `## Architectural Flow
|
|
632
|
+
|
|
633
|
+
${flowLines.join("\n")}`;
|
|
634
|
+
const sections = [`# Architecture Diagrams: ${projectName}
|
|
635
|
+
`];
|
|
636
|
+
sections.push(c4Diagram);
|
|
637
|
+
sections.push(flowDiagram);
|
|
638
|
+
return sections.join("\n\n---\n\n");
|
|
639
|
+
}
|
|
640
|
+
function resolveExtensionlessPath(nf, exportsByFile) {
|
|
641
|
+
if (exportsByFile.has(nf)) return nf;
|
|
642
|
+
for (const ext of [".ts", ".tsx", ".js", ".jsx"]) {
|
|
643
|
+
if (exportsByFile.has(`${nf}${ext}`)) return `${nf}${ext}`;
|
|
644
|
+
}
|
|
645
|
+
if (exportsByFile.has(`${nf}/index.ts`)) return `${nf}/index.ts`;
|
|
646
|
+
return nf;
|
|
647
|
+
}
|
|
648
|
+
const CONFIG_EXCLUDES = /* @__PURE__ */ new Set([
|
|
649
|
+
"node_modules",
|
|
650
|
+
".git",
|
|
651
|
+
"dist",
|
|
652
|
+
"build",
|
|
653
|
+
"coverage",
|
|
654
|
+
".turbo",
|
|
655
|
+
".cache",
|
|
656
|
+
"cdk.out",
|
|
657
|
+
"__pycache__",
|
|
658
|
+
".venv",
|
|
659
|
+
"target",
|
|
660
|
+
"obj",
|
|
661
|
+
".gradle"
|
|
662
|
+
]);
|
|
663
|
+
const CONFIG_FILE_PATTERNS = [
|
|
664
|
+
{ glob: /\.env(?:\.\w+)?$/, type: "env" },
|
|
665
|
+
{ glob: /\.env\.example$/, type: "env" },
|
|
666
|
+
{ glob: /package\.json$/, type: "package-json" },
|
|
667
|
+
{ glob: /^(?:app|config|settings|default)\.(?:json|ya?ml|toml)$/i, type: "config" },
|
|
668
|
+
{ glob: /docker-compose\.ya?ml$/, type: "docker" },
|
|
669
|
+
{ glob: /cdk\.json$/, type: "cdk" },
|
|
670
|
+
{ glob: /turbo\.json$/, type: "tooling" },
|
|
671
|
+
{ glob: /application\.(?:properties|ya?ml)$/i, type: "spring" },
|
|
672
|
+
{ glob: /settings\.py$/, type: "django" },
|
|
673
|
+
{ glob: /\.flaskenv$/, type: "env" },
|
|
674
|
+
{ glob: /appsettings\.(?:\w+\.)?json$/i, type: "dotnet" }
|
|
675
|
+
];
|
|
676
|
+
async function extractConfigValues(rootPath, projectName) {
|
|
677
|
+
const configs = [];
|
|
678
|
+
const configFiles = await collectConfigFiles(rootPath);
|
|
679
|
+
const KB_SKIP = /kb\.config\.json$/;
|
|
680
|
+
for (const filePath of configFiles) {
|
|
681
|
+
try {
|
|
682
|
+
const relPath = relative(rootPath, filePath).replace(/\\/g, "/");
|
|
683
|
+
if (KB_SKIP.test(relPath)) continue;
|
|
684
|
+
const content = await readFile(filePath, "utf-8");
|
|
685
|
+
const fileType = identifyConfigType(filePath);
|
|
686
|
+
const depth = relPath.split("/").length - 1;
|
|
687
|
+
if (depth > 1 && fileType === "tooling") continue;
|
|
688
|
+
const values = parseConfigValues(content, fileType);
|
|
689
|
+
if (values.length > 0) {
|
|
690
|
+
configs.push({ file: relPath, type: fileType, values });
|
|
691
|
+
}
|
|
692
|
+
} catch {
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return formatConfigMarkdown(configs, projectName);
|
|
696
|
+
}
|
|
697
|
+
async function collectConfigFiles(rootPath) {
|
|
698
|
+
const files = [];
|
|
699
|
+
const walk = async (dir, depth) => {
|
|
700
|
+
if (depth > 3) return;
|
|
701
|
+
try {
|
|
702
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
703
|
+
for (const entry of entries) {
|
|
704
|
+
if (CONFIG_EXCLUDES.has(entry.name)) continue;
|
|
705
|
+
const fullPath = join(dir, entry.name);
|
|
706
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
707
|
+
await walk(fullPath, depth + 1);
|
|
708
|
+
} else if (entry.isFile()) {
|
|
709
|
+
if (CONFIG_FILE_PATTERNS.some((p) => p.glob.test(entry.name))) {
|
|
710
|
+
files.push(fullPath);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
} catch {
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
await walk(rootPath, 0);
|
|
718
|
+
return files;
|
|
719
|
+
}
|
|
720
|
+
function identifyConfigType(filePath) {
|
|
721
|
+
const name = basename(filePath);
|
|
722
|
+
for (const p of CONFIG_FILE_PATTERNS) {
|
|
723
|
+
if (p.glob.test(name)) return p.type;
|
|
724
|
+
}
|
|
725
|
+
return "unknown";
|
|
726
|
+
}
|
|
727
|
+
const SENSITIVE_KEYS = /(?:secret|password|token|key|api.?key|auth|credential|private)/i;
|
|
728
|
+
function parseConfigValues(content, type) {
|
|
729
|
+
const values = [];
|
|
730
|
+
if (type === "env") {
|
|
731
|
+
for (const line of content.split("\n")) {
|
|
732
|
+
const trimmed = line.trim();
|
|
733
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
734
|
+
const eqIdx = trimmed.indexOf("=");
|
|
735
|
+
if (eqIdx === -1) continue;
|
|
736
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
737
|
+
const rawValue = trimmed.slice(eqIdx + 1).trim();
|
|
738
|
+
const sensitive = SENSITIVE_KEYS.test(key);
|
|
739
|
+
values.push({ key, value: sensitive ? "***" : rawValue, sensitive });
|
|
740
|
+
}
|
|
741
|
+
} else if (type === "package-json") {
|
|
742
|
+
try {
|
|
743
|
+
const pkg = JSON.parse(content);
|
|
744
|
+
if (pkg.scripts) {
|
|
745
|
+
for (const [key, value] of Object.entries(pkg.scripts)) {
|
|
746
|
+
values.push({ key: `scripts.${key}`, value: String(value), sensitive: false });
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (pkg.engines) {
|
|
750
|
+
for (const [key, value] of Object.entries(pkg.engines)) {
|
|
751
|
+
values.push({ key: `engines.${key}`, value: String(value), sensitive: false });
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
} catch {
|
|
755
|
+
}
|
|
756
|
+
} else if (type === "spring") {
|
|
757
|
+
for (const line of content.split("\n")) {
|
|
758
|
+
const trimmed = line.trim();
|
|
759
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("---")) continue;
|
|
760
|
+
const match = trimmed.match(/^([\w.[\]-]+)\s*[=:]\s*(.*)$/);
|
|
761
|
+
if (match) {
|
|
762
|
+
const key = match[1];
|
|
763
|
+
const rawValue = match[2].trim();
|
|
764
|
+
const sensitive = SENSITIVE_KEYS.test(key);
|
|
765
|
+
values.push({ key, value: sensitive ? "***" : rawValue, sensitive });
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
} else if (type === "json" || type === "config" || type === "cdk" || type === "tooling" || type === "dotnet") {
|
|
769
|
+
try {
|
|
770
|
+
const obj = JSON.parse(content);
|
|
771
|
+
flattenJson(obj, "", values, 0);
|
|
772
|
+
} catch {
|
|
773
|
+
}
|
|
774
|
+
} else if (type === "django") {
|
|
775
|
+
for (const line of content.split("\n")) {
|
|
776
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)\s*=\s*(.+)$/);
|
|
777
|
+
if (match) {
|
|
778
|
+
const key = match[1];
|
|
779
|
+
const rawValue = match[2].trim();
|
|
780
|
+
const sensitive = SENSITIVE_KEYS.test(key);
|
|
781
|
+
values.push({
|
|
782
|
+
key,
|
|
783
|
+
value: sensitive ? "***" : rawValue.slice(0, 100),
|
|
784
|
+
sensitive
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return values;
|
|
790
|
+
}
|
|
791
|
+
function flattenJson(obj, prefix, values, depth) {
|
|
792
|
+
if (depth > 3) return;
|
|
793
|
+
if (obj === null || obj === void 0) return;
|
|
794
|
+
if (typeof obj === "object" && !Array.isArray(obj)) {
|
|
795
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
796
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
797
|
+
if (typeof val === "object" && val !== null && !Array.isArray(val)) {
|
|
798
|
+
flattenJson(val, fullKey, values, depth + 1);
|
|
799
|
+
} else {
|
|
800
|
+
const sensitive = SENSITIVE_KEYS.test(key);
|
|
801
|
+
const strVal = Array.isArray(val) ? `[${val.length} items]` : String(val);
|
|
802
|
+
values.push({ key: fullKey, value: sensitive ? "***" : strVal.slice(0, 120), sensitive });
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
function formatConfigMarkdown(configs, projectName) {
|
|
808
|
+
const lines = [];
|
|
809
|
+
lines.push(`## Configuration Values: ${projectName}
|
|
810
|
+
`);
|
|
811
|
+
if (configs.length === 0) {
|
|
812
|
+
lines.push("No configuration files detected.");
|
|
813
|
+
return lines.join("\n");
|
|
814
|
+
}
|
|
815
|
+
lines.push(`**${configs.length} config files** found
|
|
816
|
+
`);
|
|
817
|
+
const byType = /* @__PURE__ */ new Map();
|
|
818
|
+
for (const c of configs) {
|
|
819
|
+
if (!byType.has(c.type)) byType.set(c.type, []);
|
|
820
|
+
byType.get(c.type)?.push(c);
|
|
821
|
+
}
|
|
822
|
+
for (const [type, entries] of byType) {
|
|
823
|
+
if (type === "package-json" && entries.length > 2) {
|
|
824
|
+
lines.push(`### ${type}
|
|
825
|
+
`);
|
|
826
|
+
const root = entries.find((e) => e.file === "package.json");
|
|
827
|
+
if (root) {
|
|
828
|
+
lines.push(`#### ${root.file}
|
|
829
|
+
`);
|
|
830
|
+
lines.push("| Key | Value | Sensitive |");
|
|
831
|
+
lines.push("|-----|-------|-----------|");
|
|
832
|
+
for (const v of root.values.slice(0, 50)) {
|
|
833
|
+
const escaped = v.value.replace(/\|/g, "\\|");
|
|
834
|
+
lines.push(`| ${v.key} | ${escaped} | ${v.sensitive ? "\u26A0\uFE0F yes" : "no"} |`);
|
|
835
|
+
}
|
|
836
|
+
lines.push("");
|
|
837
|
+
}
|
|
838
|
+
const subEntries = entries.filter((e) => e.file !== "package.json");
|
|
839
|
+
if (subEntries.length > 0) {
|
|
840
|
+
const scriptFreq = /* @__PURE__ */ new Map();
|
|
841
|
+
for (const entry of subEntries) {
|
|
842
|
+
for (const v of entry.values) {
|
|
843
|
+
const k = `${v.key}=${v.value}`;
|
|
844
|
+
scriptFreq.set(k, (scriptFreq.get(k) ?? 0) + 1);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
const commonThreshold = Math.max(2, Math.floor(subEntries.length * 0.5));
|
|
848
|
+
lines.push(`#### Sub-packages (${subEntries.length} packages)
|
|
849
|
+
`);
|
|
850
|
+
const commonScripts = [...scriptFreq.entries()].filter(([, count]) => count >= commonThreshold).map(([kv]) => {
|
|
851
|
+
const [key, ...rest] = kv.split("=");
|
|
852
|
+
return { key, value: rest.join("=") };
|
|
853
|
+
});
|
|
854
|
+
if (commonScripts.length > 0) {
|
|
855
|
+
lines.push("**Common scripts** (shared by most sub-packages):\n");
|
|
856
|
+
lines.push("| Key | Value |");
|
|
857
|
+
lines.push("|-----|-------|");
|
|
858
|
+
for (const s of commonScripts) {
|
|
859
|
+
lines.push(`| ${s.key} | ${s.value.replace(/\|/g, "\\|")} |`);
|
|
860
|
+
}
|
|
861
|
+
lines.push("");
|
|
862
|
+
}
|
|
863
|
+
const uniqueByPkg = /* @__PURE__ */ new Map();
|
|
864
|
+
for (const entry of subEntries) {
|
|
865
|
+
const unique = entry.values.filter((v) => {
|
|
866
|
+
const k = `${v.key}=${v.value}`;
|
|
867
|
+
return (scriptFreq.get(k) ?? 0) < commonThreshold;
|
|
868
|
+
});
|
|
869
|
+
if (unique.length === 0) continue;
|
|
870
|
+
const fingerprint = unique.map((v) => `${v.key}=${v.value}`).sort().join("||");
|
|
871
|
+
const existing = uniqueByPkg.get(fingerprint);
|
|
872
|
+
if (existing) {
|
|
873
|
+
existing.files.push(entry.file);
|
|
874
|
+
} else {
|
|
875
|
+
uniqueByPkg.set(fingerprint, {
|
|
876
|
+
files: [entry.file],
|
|
877
|
+
entries: unique.map((v) => ({ key: v.key, value: v.value }))
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
for (const [, group] of uniqueByPkg) {
|
|
882
|
+
if (group.files.length > 1) {
|
|
883
|
+
lines.push(
|
|
884
|
+
`**${group.files.length} packages** (${group.files.map((f) => f.split("/").slice(-2, -1)[0] || f).join(", ")}):`
|
|
885
|
+
);
|
|
886
|
+
} else {
|
|
887
|
+
lines.push(`**${group.files[0]}**:`);
|
|
888
|
+
}
|
|
889
|
+
lines.push("| Key | Value |");
|
|
890
|
+
lines.push("|-----|-------|");
|
|
891
|
+
for (const v of group.entries) {
|
|
892
|
+
lines.push(`| ${v.key} | ${v.value.replace(/\|/g, "\\|")} |`);
|
|
893
|
+
}
|
|
894
|
+
lines.push("");
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
if (entries.length > 3) {
|
|
900
|
+
const valueSets = entries.map(
|
|
901
|
+
(e) => e.values.map((v) => `${v.key}=${v.value}`).sort().join("||")
|
|
902
|
+
);
|
|
903
|
+
const mostCommon = valueSets.sort(
|
|
904
|
+
(a, b) => valueSets.filter((v) => v === b).length - valueSets.filter((v) => v === a).length
|
|
905
|
+
)[0];
|
|
906
|
+
const identicalCount = valueSets.filter((v) => v === mostCommon).length;
|
|
907
|
+
if (identicalCount > 2) {
|
|
908
|
+
lines.push(`### ${type}
|
|
909
|
+
`);
|
|
910
|
+
const representative = entries[valueSets.indexOf(mostCommon)];
|
|
911
|
+
const identicalFiles = entries.filter((_, i) => valueSets[i] === mostCommon).map((e) => e.file);
|
|
912
|
+
const differentEntries = entries.filter((_, i) => valueSets[i] !== mostCommon);
|
|
913
|
+
lines.push(`**${identicalFiles.length} identical files**: ${identicalFiles.join(", ")}
|
|
914
|
+
`);
|
|
915
|
+
lines.push("| Key | Value | Sensitive |");
|
|
916
|
+
lines.push("|-----|-------|-----------|");
|
|
917
|
+
for (const v of representative.values.slice(0, 30)) {
|
|
918
|
+
const escaped = v.value.replace(/\|/g, "\\|");
|
|
919
|
+
lines.push(`| ${v.key} | ${escaped} | ${v.sensitive ? "\u26A0\uFE0F yes" : "no"} |`);
|
|
920
|
+
}
|
|
921
|
+
lines.push("");
|
|
922
|
+
for (const entry of differentEntries) {
|
|
923
|
+
lines.push(`#### ${entry.file}
|
|
924
|
+
`);
|
|
925
|
+
lines.push("| Key | Value | Sensitive |");
|
|
926
|
+
lines.push("|-----|-------|-----------|");
|
|
927
|
+
for (const v of entry.values.slice(0, 30)) {
|
|
928
|
+
const escaped = v.value.replace(/\|/g, "\\|");
|
|
929
|
+
lines.push(`| ${v.key} | ${escaped} | ${v.sensitive ? "\u26A0\uFE0F yes" : "no"} |`);
|
|
930
|
+
}
|
|
931
|
+
lines.push("");
|
|
932
|
+
}
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
lines.push(`### ${type}
|
|
937
|
+
`);
|
|
938
|
+
for (const entry of entries) {
|
|
939
|
+
lines.push(`#### ${entry.file}
|
|
940
|
+
`);
|
|
941
|
+
lines.push("| Key | Value | Sensitive |");
|
|
942
|
+
lines.push("|-----|-------|-----------|");
|
|
943
|
+
for (const v of entry.values.slice(0, 50)) {
|
|
944
|
+
const escaped = v.value.replace(/\|/g, "\\|");
|
|
945
|
+
lines.push(`| ${v.key} | ${escaped} | ${v.sensitive ? "\u26A0\uFE0F yes" : "no"} |`);
|
|
946
|
+
}
|
|
947
|
+
if (entry.values.length > 50) {
|
|
948
|
+
lines.push(`
|
|
949
|
+
_...and ${entry.values.length - 50} more values._`);
|
|
950
|
+
}
|
|
951
|
+
lines.push("");
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
const sensitiveCount = configs.reduce(
|
|
955
|
+
(sum, c) => sum + c.values.filter((v) => v.sensitive).length,
|
|
956
|
+
0
|
|
957
|
+
);
|
|
958
|
+
if (sensitiveCount > 0) {
|
|
959
|
+
lines.push(`
|
|
960
|
+
**\u26A0\uFE0F ${sensitiveCount} sensitive values detected** (values masked).`);
|
|
961
|
+
}
|
|
962
|
+
return lines.join("\n");
|
|
963
|
+
}
|
|
964
|
+
async function onboard(options) {
|
|
965
|
+
const totalStart = Date.now();
|
|
966
|
+
const rootPath = resolve(options.path);
|
|
967
|
+
const projectName = basename(rootPath);
|
|
968
|
+
const mode = options.mode ?? "memory";
|
|
969
|
+
const outDir = options.outDir ?? join(rootPath, ".ai", "kb");
|
|
970
|
+
const structureAnalyzer = new StructureAnalyzer();
|
|
971
|
+
const dependencyAnalyzer = new DependencyAnalyzer();
|
|
972
|
+
const symbolAnalyzer = new SymbolAnalyzer();
|
|
973
|
+
const patternAnalyzer = new PatternAnalyzer();
|
|
974
|
+
const entryPointAnalyzer = new EntryPointAnalyzer();
|
|
975
|
+
const diagramGenerator = new DiagramGenerator();
|
|
976
|
+
const tasks = [
|
|
977
|
+
{
|
|
978
|
+
name: "structure",
|
|
979
|
+
fn: () => structureAnalyzer.analyze(rootPath, { format: "markdown", maxDepth: 3, sourceOnly: true })
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
name: "dependencies",
|
|
983
|
+
fn: () => dependencyAnalyzer.analyze(rootPath, { format: "markdown" })
|
|
984
|
+
},
|
|
985
|
+
{ name: "entry-points", fn: () => entryPointAnalyzer.analyze(rootPath) },
|
|
986
|
+
{ name: "symbols", fn: () => symbolAnalyzer.analyze(rootPath, { format: "markdown" }) },
|
|
987
|
+
{ name: "patterns", fn: () => patternAnalyzer.analyze(rootPath) },
|
|
988
|
+
{
|
|
989
|
+
name: "diagram",
|
|
990
|
+
fn: () => diagramGenerator.analyze(rootPath, { diagramType: "architecture" })
|
|
991
|
+
}
|
|
992
|
+
];
|
|
993
|
+
const settled = await Promise.allSettled(
|
|
994
|
+
tasks.map(async (task) => {
|
|
995
|
+
const taskStart = Date.now();
|
|
996
|
+
const result = await task.fn();
|
|
997
|
+
return { name: task.name, result, durationMs: Date.now() - taskStart };
|
|
998
|
+
})
|
|
999
|
+
);
|
|
1000
|
+
const steps = [];
|
|
1001
|
+
const outputs = /* @__PURE__ */ new Map();
|
|
1002
|
+
const dataMap = /* @__PURE__ */ new Map();
|
|
1003
|
+
for (const entry of settled) {
|
|
1004
|
+
if (entry.status === "fulfilled") {
|
|
1005
|
+
const { name, result, durationMs } = entry.value;
|
|
1006
|
+
const analysisResult = result;
|
|
1007
|
+
steps.push({ name, status: "success", output: analysisResult.output, durationMs });
|
|
1008
|
+
outputs.set(name, analysisResult.output);
|
|
1009
|
+
dataMap.set(name, analysisResult.data);
|
|
1010
|
+
} else {
|
|
1011
|
+
const reason = entry.reason;
|
|
1012
|
+
const idx = settled.indexOf(entry);
|
|
1013
|
+
const name = tasks[idx].name;
|
|
1014
|
+
steps.push({
|
|
1015
|
+
name,
|
|
1016
|
+
status: "failed",
|
|
1017
|
+
output: "",
|
|
1018
|
+
durationMs: 0,
|
|
1019
|
+
error: reason.message
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
const callGraphStart = Date.now();
|
|
1024
|
+
let callGraph = null;
|
|
1025
|
+
try {
|
|
1026
|
+
let result = await extractTsCallGraph(rootPath);
|
|
1027
|
+
if (!result || result.edges.length === 0) {
|
|
1028
|
+
result = await extractRegexCallGraph(rootPath);
|
|
1029
|
+
}
|
|
1030
|
+
if (result && result.edges.length > 0) {
|
|
1031
|
+
callGraph = /* @__PURE__ */ new Map();
|
|
1032
|
+
for (const edge of result.edges) {
|
|
1033
|
+
let targets = callGraph.get(edge.from);
|
|
1034
|
+
if (!targets) {
|
|
1035
|
+
targets = /* @__PURE__ */ new Map();
|
|
1036
|
+
callGraph.set(edge.from, targets);
|
|
1037
|
+
}
|
|
1038
|
+
const existing = targets.get(edge.to);
|
|
1039
|
+
if (existing) {
|
|
1040
|
+
for (const s of edge.symbols) {
|
|
1041
|
+
if (!existing.includes(s)) existing.push(s);
|
|
1042
|
+
}
|
|
1043
|
+
} else {
|
|
1044
|
+
targets.set(edge.to, [...edge.symbols]);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
} catch {
|
|
1049
|
+
}
|
|
1050
|
+
const callGraphDurationMs = Date.now() - callGraphStart;
|
|
1051
|
+
const codeMapStart = Date.now();
|
|
1052
|
+
const codeMap = buildCodeMap(dataMap, projectName, callGraph);
|
|
1053
|
+
const codeMapDurationMs = Date.now() - codeMapStart + callGraphDurationMs;
|
|
1054
|
+
steps.push({
|
|
1055
|
+
name: "code-map",
|
|
1056
|
+
status: "success",
|
|
1057
|
+
output: codeMap,
|
|
1058
|
+
durationMs: codeMapDurationMs
|
|
1059
|
+
});
|
|
1060
|
+
outputs.set("code-map", codeMap);
|
|
1061
|
+
if (callGraph && callGraph.size > 0) {
|
|
1062
|
+
const enhancedDiagram = buildDiagrams(callGraph, dataMap, projectName);
|
|
1063
|
+
const diagramStep = steps.find((s) => s.name === "diagram");
|
|
1064
|
+
if (diagramStep) {
|
|
1065
|
+
diagramStep.output = enhancedDiagram;
|
|
1066
|
+
outputs.set("diagram", enhancedDiagram);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
const configStart = Date.now();
|
|
1070
|
+
const configValues = await extractConfigValues(rootPath, projectName);
|
|
1071
|
+
const configDurationMs = Date.now() - configStart;
|
|
1072
|
+
steps.push({
|
|
1073
|
+
name: "config-values",
|
|
1074
|
+
status: "success",
|
|
1075
|
+
output: configValues,
|
|
1076
|
+
durationMs: configDurationMs
|
|
1077
|
+
});
|
|
1078
|
+
outputs.set("config-values", configValues);
|
|
1079
|
+
const synthesisGuide = buildSynthesisGuide(steps, mode, projectName, dataMap);
|
|
1080
|
+
steps.push({
|
|
1081
|
+
name: "synthesis-guide",
|
|
1082
|
+
status: "success",
|
|
1083
|
+
output: synthesisGuide,
|
|
1084
|
+
durationMs: 0
|
|
1085
|
+
});
|
|
1086
|
+
outputs.set("synthesis-guide", synthesisGuide);
|
|
1087
|
+
if (mode === "generate") {
|
|
1088
|
+
if (existsSync(outDir)) {
|
|
1089
|
+
for (const file of readdirSync(outDir)) {
|
|
1090
|
+
if (file.endsWith(".md") || file.endsWith(".json")) {
|
|
1091
|
+
rmSync(join(outDir, file), { force: true });
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
mkdirSync(outDir, { recursive: true });
|
|
1096
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1097
|
+
for (const [name, output] of outputs) {
|
|
1098
|
+
const fileName = `${name}.md`;
|
|
1099
|
+
const filePath = join(outDir, fileName);
|
|
1100
|
+
const cleaned = output.replaceAll(rootPath, projectName);
|
|
1101
|
+
const timestamp = `<!-- Generated: ${now} -->
|
|
1102
|
+
<!-- Project: ${projectName} -->
|
|
1103
|
+
<!-- Source: ${rootPath} -->
|
|
1104
|
+
|
|
1105
|
+
`;
|
|
1106
|
+
writeFileSync(filePath, timestamp + cleaned, "utf-8");
|
|
1107
|
+
}
|
|
1108
|
+
const indexLines = [
|
|
1109
|
+
`<!-- Generated: ${now} -->`,
|
|
1110
|
+
`<!-- Project: ${projectName} -->`,
|
|
1111
|
+
`<!-- Source: ${rootPath} -->`,
|
|
1112
|
+
"",
|
|
1113
|
+
`# ${projectName} \u2014 Codebase Knowledge`,
|
|
1114
|
+
"",
|
|
1115
|
+
"## Contents",
|
|
1116
|
+
""
|
|
1117
|
+
];
|
|
1118
|
+
for (const step of steps) {
|
|
1119
|
+
const fileName = `${step.name}.md`;
|
|
1120
|
+
const title = DISPLAY_TITLES[step.name] ?? step.name;
|
|
1121
|
+
const status = step.status === "success" ? "\u2713" : "\u2717";
|
|
1122
|
+
const timing = step.durationMs > 0 ? ` (${step.durationMs}ms)` : "";
|
|
1123
|
+
indexLines.push(`- ${status} [${title}](./${fileName})${timing}`);
|
|
1124
|
+
}
|
|
1125
|
+
indexLines.push("");
|
|
1126
|
+
writeFileSync(join(outDir, "README.md"), indexLines.join("\n"), "utf-8");
|
|
1127
|
+
}
|
|
1128
|
+
return {
|
|
1129
|
+
path: rootPath,
|
|
1130
|
+
mode,
|
|
1131
|
+
steps,
|
|
1132
|
+
outDir: mode === "generate" ? outDir : void 0,
|
|
1133
|
+
totalDurationMs: Date.now() - totalStart
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
export {
|
|
1137
|
+
onboard
|
|
1138
|
+
};
|
|
1139
|
+
//# sourceMappingURL=onboard.js.map
|