codebase-onboarder 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +275 -0
- package/bin/onboarder.js +14 -0
- package/bin/postinstall.js +6 -0
- package/cli/commands.js +436 -0
- package/cli/main.js +129 -0
- package/cli/prompt.js +101 -0
- package/cli/ui.js +55 -0
- package/cli/wizard.js +325 -0
- package/package.json +48 -0
- package/public/app.js +1339 -0
- package/public/index.html +455 -0
- package/public/js/about.js +284 -0
- package/public/js/aiDraft.js +163 -0
- package/public/js/analysisPanel.js +270 -0
- package/public/js/analysisReport.js +154 -0
- package/public/js/api.js +264 -0
- package/public/js/atlas.js +98 -0
- package/public/js/blameView.js +41 -0
- package/public/js/codeTab.js +256 -0
- package/public/js/codeViewer.js +184 -0
- package/public/js/components/FileChip.js +102 -0
- package/public/js/components/MetricSparkline.js +89 -0
- package/public/js/components/RiskBadge.js +105 -0
- package/public/js/deepAnalysisView.js +496 -0
- package/public/js/diagramPane.js +154 -0
- package/public/js/diffView.js +352 -0
- package/public/js/docsView.js +566 -0
- package/public/js/fileSourceBrowser.js +47 -0
- package/public/js/flameGraph.js +75 -0
- package/public/js/forceGraph.js +592 -0
- package/public/js/heatmap.js +185 -0
- package/public/js/highlight.js +122 -0
- package/public/js/html.js +50 -0
- package/public/js/insightsView.js +236 -0
- package/public/js/inspector.js +535 -0
- package/public/js/llm.js +345 -0
- package/public/js/mapView.js +159 -0
- package/public/js/markdown.js +119 -0
- package/public/js/mindmap.js +289 -0
- package/public/js/repoFiles.js +44 -0
- package/public/js/sbomView.js +142 -0
- package/public/js/scanCache.js +76 -0
- package/public/js/search.js +874 -0
- package/public/js/serverSettings.js +169 -0
- package/public/js/state.js +192 -0
- package/public/js/tour.js +17 -0
- package/public/js/transitions.js +15 -0
- package/public/js/tree.js +200 -0
- package/public/js/workflowsView.js +95 -0
- package/public/styles.css +2767 -0
- package/public/vendor/mermaid.min.js +3587 -0
- package/public/vendor/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
- package/public/vendor/monaco/vs/base/worker/workerMain.js +31 -0
- package/public/vendor/monaco/vs/basic-languages/abap/abap.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/apex/apex.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/azcli/azcli.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/bat/bat.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/bicep/bicep.js +11 -0
- package/public/vendor/monaco/vs/basic-languages/cameligo/cameligo.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/clojure/clojure.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/coffee/coffee.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/cpp/cpp.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/csharp/csharp.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/csp/csp.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/css/css.js +12 -0
- package/public/vendor/monaco/vs/basic-languages/cypher/cypher.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/dart/dart.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/dockerfile/dockerfile.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/ecl/ecl.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/elixir/elixir.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/flow9/flow9.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/freemarker2/freemarker2.js +12 -0
- package/public/vendor/monaco/vs/basic-languages/fsharp/fsharp.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/go/go.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/graphql/graphql.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/handlebars/handlebars.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/hcl/hcl.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/html/html.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/ini/ini.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/java/java.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/javascript/javascript.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/julia/julia.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/kotlin/kotlin.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/less/less.js +11 -0
- package/public/vendor/monaco/vs/basic-languages/lexon/lexon.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/liquid/liquid.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/lua/lua.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/m3/m3.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/markdown/markdown.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/mdx/mdx.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/mips/mips.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/msdax/msdax.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/mysql/mysql.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/objective-c/objective-c.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/pascal/pascal.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/pascaligo/pascaligo.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/perl/perl.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/pgsql/pgsql.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/php/php.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/pla/pla.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/postiats/postiats.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/powerquery/powerquery.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/powershell/powershell.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/protobuf/protobuf.js +11 -0
- package/public/vendor/monaco/vs/basic-languages/pug/pug.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/python/python.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/qsharp/qsharp.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/r/r.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/razor/razor.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/redis/redis.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/redshift/redshift.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/ruby/ruby.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/rust/rust.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/sb/sb.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/scala/scala.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/scheme/scheme.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/scss/scss.js +12 -0
- package/public/vendor/monaco/vs/basic-languages/shell/shell.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/solidity/solidity.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/sophia/sophia.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/sparql/sparql.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/sql/sql.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/st/st.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/swift/swift.js +13 -0
- package/public/vendor/monaco/vs/basic-languages/systemverilog/systemverilog.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/tcl/tcl.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/twig/twig.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/typescript/typescript.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/typespec/typespec.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/vb/vb.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/wgsl/wgsl.js +307 -0
- package/public/vendor/monaco/vs/basic-languages/xml/xml.js +10 -0
- package/public/vendor/monaco/vs/basic-languages/yaml/yaml.js +10 -0
- package/public/vendor/monaco/vs/editor/editor.main.css +8 -0
- package/public/vendor/monaco/vs/editor/editor.main.js +798 -0
- package/public/vendor/monaco/vs/language/css/cssMode.js +13 -0
- package/public/vendor/monaco/vs/language/css/cssWorker.js +77 -0
- package/public/vendor/monaco/vs/language/html/htmlMode.js +13 -0
- package/public/vendor/monaco/vs/language/html/htmlWorker.js +454 -0
- package/public/vendor/monaco/vs/language/json/jsonMode.js +19 -0
- package/public/vendor/monaco/vs/language/json/jsonWorker.js +42 -0
- package/public/vendor/monaco/vs/language/typescript/tsMode.js +20 -0
- package/public/vendor/monaco/vs/language/typescript/tsWorker.js +51328 -0
- package/public/vendor/monaco/vs/loader.js +11 -0
- package/public/vendor/monaco/worker-boot.js +9 -0
- package/server/.fuse_hidden0000000800000001 +36 -0
- package/server/apiDiff.js +25 -0
- package/server/apiDocs.js +67 -0
- package/server/apiFile.js +36 -0
- package/server/apiGitBlame.js +63 -0
- package/server/apiMcp.js +110 -0
- package/server/apiScan.js +119 -0
- package/server/apiSearch.js +253 -0
- package/server/apiSettings.js +118 -0
- package/server/apiTools.js +90 -0
- package/server/config.js +227 -0
- package/server/fileSourceNode.js +44 -0
- package/server/gitClone.js +95 -0
- package/server/gitDiff.js +184 -0
- package/server/gitHistory.js +110 -0
- package/server/htmlText.js +44 -0
- package/server/http.js +55 -0
- package/server/httpGuards.js +87 -0
- package/server/index.js +156 -0
- package/server/llmProxy.js +162 -0
- package/server/logger.js +29 -0
- package/server/mcp/analysis.js +209 -0
- package/server/mcp/http.js +213 -0
- package/server/mcp/runner.js +278 -0
- package/server/mcp/server.js +241 -0
- package/server/mcp/standalone.js +42 -0
- package/server/mcp/tools.js +683 -0
- package/server/paths.js +39 -0
- package/server/router.js +218 -0
- package/server/searchIndex.js +118 -0
- package/server/sessions.js +163 -0
- package/server/static.js +59 -0
- package/server/tools/install.js +246 -0
- package/server/tools/parse.js +170 -0
- package/server/tools/platform.js +91 -0
- package/server/tools/registry.js +212 -0
- package/server/tools/scan.js +136 -0
- package/server/tools.js +212 -0
- package/server/tunnel.js +92 -0
- package/shared/analyzer/docs.js +135 -0
- package/shared/analyzer/explainLocal.js +163 -0
- package/shared/analyzer/graph.js +461 -0
- package/shared/analyzer/health.js +215 -0
- package/shared/analyzer/history.js +146 -0
- package/shared/analyzer/languages/csharp.js +39 -0
- package/shared/analyzer/languages/generic.js +131 -0
- package/shared/analyzer/languages/go.js +70 -0
- package/shared/analyzer/languages/index.js +43 -0
- package/shared/analyzer/languages/java.js +39 -0
- package/shared/analyzer/languages/javascript.js +240 -0
- package/shared/analyzer/languages/python.js +120 -0
- package/shared/analyzer/languages/rust.js +42 -0
- package/shared/analyzer/languages/typescript.js +138 -0
- package/shared/analyzer/licenses.js +151 -0
- package/shared/analyzer/metrics.js +120 -0
- package/shared/analyzer/pathUtil.js +69 -0
- package/shared/analyzer/patterns.js +272 -0
- package/shared/analyzer/scan.js +473 -0
- package/shared/analyzer/security.js +187 -0
- package/shared/analyzer/services.js +157 -0
- package/shared/analyzer/stack.js +173 -0
- package/shared/analyzer/tour.js +69 -0
- package/shared/analyzer/util.js +123 -0
- package/shared/analyzer/workflows.js +143 -0
- package/shared/diagram/aiFacts.js +145 -0
- package/shared/diagram/aiMermaid.js +59 -0
- package/shared/diagram/atlas.js +93 -0
- package/shared/diagram/mermaid.js +459 -0
- package/shared/search/query.js +518 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
// The Map view: the whole repository as a left-to-right tree of cells.
|
|
2
|
+
// Folders expand into their contents; files expand into the cells they are
|
|
3
|
+
// connected to (what they pull in, who leans on them); click again to
|
|
4
|
+
// shrink. Every cell carries a one-line description. Layout is a small
|
|
5
|
+
// tidy-tree: leaves get rows in order, parents center on their children.
|
|
6
|
+
// Pure logic up front so it can be unit-tested without a DOM.
|
|
7
|
+
|
|
8
|
+
export const GEOMETRY = {
|
|
9
|
+
colW: 250,
|
|
10
|
+
rowH: 58,
|
|
11
|
+
cellW: 204,
|
|
12
|
+
cellH: 46,
|
|
13
|
+
pad: 10,
|
|
14
|
+
maxFolderChildren: 30,
|
|
15
|
+
maxNeighbors: 6,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Builds the root spec from the file tree. The app supplies the prose:
|
|
19
|
+
// folderInfo(node) -> { summary, cls }
|
|
20
|
+
// fileInfo(path) -> { summary, cls, parsed, neighbors: [{path, relation}] }
|
|
21
|
+
// `expanded` is a Set of spec ids ('dir:…', 'file:…') currently open.
|
|
22
|
+
export function buildSpecs({ root, expanded, folderInfo, fileInfo, rootSummary }) {
|
|
23
|
+
const folderSpec = (node) => ({
|
|
24
|
+
id: 'dir:' + node.path,
|
|
25
|
+
kind: node.path ? 'folder' : 'root',
|
|
26
|
+
name: node.path ? node.name + '/' : rootSummary.name,
|
|
27
|
+
summary: node.path ? folderInfo(node).summary : rootSummary.summary,
|
|
28
|
+
cls: node.path ? folderInfo(node).cls || '' : 'is-root',
|
|
29
|
+
expandable: true,
|
|
30
|
+
nav: node.path || null,
|
|
31
|
+
kids: () => folderKids(node),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const folderKids = (node) => {
|
|
35
|
+
const dirs = [...node.dirs.values()]
|
|
36
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
37
|
+
.map(folderSpec);
|
|
38
|
+
const files = node.files.slice().sort().map(fileSpec);
|
|
39
|
+
const shown = files.slice(0, GEOMETRY.maxFolderChildren);
|
|
40
|
+
const hidden = files.length - shown.length;
|
|
41
|
+
const more = hidden > 0
|
|
42
|
+
? [{
|
|
43
|
+
id: 'more:' + node.path,
|
|
44
|
+
kind: 'more',
|
|
45
|
+
name: '+ ' + hidden + ' more',
|
|
46
|
+
summary: 'see them in Files view',
|
|
47
|
+
cls: 'is-ghost',
|
|
48
|
+
expandable: false,
|
|
49
|
+
navFolder: node.path,
|
|
50
|
+
kids: null,
|
|
51
|
+
}]
|
|
52
|
+
: [];
|
|
53
|
+
return [...dirs, ...shown, ...more];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const fileSpec = (path) => {
|
|
57
|
+
const info = fileInfo(path);
|
|
58
|
+
const name = path.split('/').pop();
|
|
59
|
+
if (!info.parsed) {
|
|
60
|
+
return {
|
|
61
|
+
id: 'file:' + path,
|
|
62
|
+
kind: 'file',
|
|
63
|
+
name,
|
|
64
|
+
summary: info.summary,
|
|
65
|
+
cls: 'is-plain',
|
|
66
|
+
expandable: false,
|
|
67
|
+
nav: path,
|
|
68
|
+
kids: null,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const neighbors = info.neighbors || [];
|
|
72
|
+
return {
|
|
73
|
+
id: 'file:' + path,
|
|
74
|
+
kind: 'file',
|
|
75
|
+
name,
|
|
76
|
+
summary: info.summary,
|
|
77
|
+
cls: info.cls || '',
|
|
78
|
+
expandable: neighbors.length > 0,
|
|
79
|
+
nav: path,
|
|
80
|
+
kids: () => {
|
|
81
|
+
const shown = neighbors.slice(0, GEOMETRY.maxNeighbors);
|
|
82
|
+
const hidden = neighbors.length - shown.length;
|
|
83
|
+
const specs = shown.map((n) => ({
|
|
84
|
+
id: 'g:' + path + ':' + n.path,
|
|
85
|
+
kind: 'ghost',
|
|
86
|
+
name: n.path.split('/').pop(),
|
|
87
|
+
summary: `${n.relation} · ${dirPart(n.path)}`,
|
|
88
|
+
cls: 'is-ghost',
|
|
89
|
+
expandable: false,
|
|
90
|
+
nav: n.path,
|
|
91
|
+
kids: null,
|
|
92
|
+
}));
|
|
93
|
+
if (hidden > 0) {
|
|
94
|
+
specs.push({
|
|
95
|
+
id: 'gmore:' + path,
|
|
96
|
+
kind: 'more',
|
|
97
|
+
name: '+ ' + hidden + ' more',
|
|
98
|
+
summary: 'open the deep-dive',
|
|
99
|
+
cls: 'is-ghost',
|
|
100
|
+
expandable: false,
|
|
101
|
+
nav: path,
|
|
102
|
+
kids: null,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return specs;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return folderSpec(root);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Lays out the visible cells. Returns flat cell + edge lists with absolute
|
|
114
|
+
// coordinates, plus the total size of the drawing.
|
|
115
|
+
export function layoutMindMap(rootSpec, expanded) {
|
|
116
|
+
const cells = [];
|
|
117
|
+
const edges = [];
|
|
118
|
+
let nextRow = 0;
|
|
119
|
+
let maxDepth = 0;
|
|
120
|
+
const G = GEOMETRY;
|
|
121
|
+
|
|
122
|
+
const walk = (spec, depth) => {
|
|
123
|
+
const cell = {
|
|
124
|
+
id: spec.id,
|
|
125
|
+
kind: spec.kind,
|
|
126
|
+
name: spec.name,
|
|
127
|
+
summary: spec.summary,
|
|
128
|
+
cls: spec.cls,
|
|
129
|
+
expandable: spec.expandable,
|
|
130
|
+
nav: spec.nav ?? null,
|
|
131
|
+
navFolder: spec.navFolder ?? null,
|
|
132
|
+
depth,
|
|
133
|
+
x: G.pad + depth * G.colW,
|
|
134
|
+
w: G.cellW,
|
|
135
|
+
h: G.cellH,
|
|
136
|
+
};
|
|
137
|
+
cells.push(cell);
|
|
138
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
139
|
+
|
|
140
|
+
const open = spec.expandable && expanded.has(spec.id);
|
|
141
|
+
const kidSpecs = open && spec.kids ? spec.kids() : [];
|
|
142
|
+
const kidCells = kidSpecs.map((k) => {
|
|
143
|
+
const kc = walk(k, depth + 1);
|
|
144
|
+
edges.push({ from: cell, to: kc, dashed: k.kind === 'ghost' || k.kind === 'more' });
|
|
145
|
+
return kc;
|
|
146
|
+
});
|
|
147
|
+
cell.row = kidCells.length
|
|
148
|
+
? (kidCells[0].row + kidCells[kidCells.length - 1].row) / 2
|
|
149
|
+
: nextRow++;
|
|
150
|
+
cell.y = G.pad + cell.row * G.rowH;
|
|
151
|
+
return cell;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
walk(rootSpec, 0);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
cells,
|
|
158
|
+
edges,
|
|
159
|
+
width: G.pad * 2 + (maxDepth + 1) * G.colW,
|
|
160
|
+
height: G.pad * 2 + nextRow * G.rowH,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// Draws the layout into the canvas: SVG elbow edges underneath, HTML cells
|
|
164
|
+
// on top (better text handling and click targets than SVG text).
|
|
165
|
+
// handlers: onToggle(cell) for real cells, onNavigate(cell) for ghosts.
|
|
166
|
+
export function renderMindMap(container, layout, expanded, handlers) {
|
|
167
|
+
container.innerHTML = '';
|
|
168
|
+
|
|
169
|
+
const wrap = document.createElement('div');
|
|
170
|
+
wrap.className = 'mm';
|
|
171
|
+
wrap.style.width = layout.width + 'px';
|
|
172
|
+
wrap.style.height = layout.height + 'px';
|
|
173
|
+
|
|
174
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
175
|
+
svg.setAttribute('class', 'mm-svg');
|
|
176
|
+
svg.setAttribute('width', layout.width);
|
|
177
|
+
svg.setAttribute('height', layout.height);
|
|
178
|
+
|
|
179
|
+
const edgePaths = new Map(); // edge -> svgPath
|
|
180
|
+
const cellElements = new Map(); // cellId -> domElement
|
|
181
|
+
const parentMap = new Map(); // cell -> { parentCell, edge }
|
|
182
|
+
const childMap = new Map(); // cell -> Array<{ childCell, edge }>
|
|
183
|
+
|
|
184
|
+
for (const edge of layout.edges) {
|
|
185
|
+
const x1 = edge.from.x + edge.from.w;
|
|
186
|
+
const y1 = edge.from.y + edge.from.h / 2;
|
|
187
|
+
const x2 = edge.to.x;
|
|
188
|
+
const y2 = edge.to.y + edge.to.h / 2;
|
|
189
|
+
const bend = Math.max(30, (x2 - x1) / 2);
|
|
190
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
191
|
+
path.setAttribute('d', `M ${x1} ${y1} C ${x1 + bend} ${y1}, ${x2 - bend} ${y2}, ${x2} ${y2}`);
|
|
192
|
+
path.setAttribute('class', 'mm-edge' + (edge.dashed ? ' is-dashed' : ''));
|
|
193
|
+
svg.appendChild(path);
|
|
194
|
+
edgePaths.set(edge, path);
|
|
195
|
+
|
|
196
|
+
parentMap.set(edge.to, { parentCell: edge.from, edge });
|
|
197
|
+
if (!childMap.has(edge.from)) childMap.set(edge.from, []);
|
|
198
|
+
childMap.get(edge.from).push({ childCell: edge.to, edge });
|
|
199
|
+
}
|
|
200
|
+
wrap.appendChild(svg);
|
|
201
|
+
|
|
202
|
+
for (const cell of layout.cells) {
|
|
203
|
+
const el = document.createElement('div');
|
|
204
|
+
el.className = 'mm-cell ' + (cell.cls || '') + ' kind-' + cell.kind;
|
|
205
|
+
el.style.left = cell.x + 'px';
|
|
206
|
+
el.style.top = cell.y + 'px';
|
|
207
|
+
el.style.width = cell.w + 'px';
|
|
208
|
+
el.style.minHeight = cell.h + 'px';
|
|
209
|
+
cellElements.set(cell.id, el);
|
|
210
|
+
|
|
211
|
+
const head = document.createElement('div');
|
|
212
|
+
head.className = 'mm-head';
|
|
213
|
+
if (cell.expandable) {
|
|
214
|
+
const caret = document.createElement('span');
|
|
215
|
+
caret.className = 'mm-caret';
|
|
216
|
+
caret.textContent = expanded.has(cell.id) ? '▾' : '▸';
|
|
217
|
+
head.appendChild(caret);
|
|
218
|
+
}
|
|
219
|
+
const name = document.createElement('span');
|
|
220
|
+
name.className = 'mm-name';
|
|
221
|
+
name.textContent = cell.name;
|
|
222
|
+
name.title = cell.nav || cell.name;
|
|
223
|
+
head.appendChild(name);
|
|
224
|
+
el.appendChild(head);
|
|
225
|
+
|
|
226
|
+
if (cell.summary) {
|
|
227
|
+
const sum = document.createElement('div');
|
|
228
|
+
sum.className = 'mm-sum';
|
|
229
|
+
sum.textContent = cell.summary;
|
|
230
|
+
el.appendChild(sum);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Branch hover highlighting
|
|
234
|
+
el.addEventListener('mouseenter', () => {
|
|
235
|
+
wrap.classList.add('has-hover');
|
|
236
|
+
el.classList.add('is-active-branch');
|
|
237
|
+
|
|
238
|
+
// Trace ancestors up to root
|
|
239
|
+
let cur = cell;
|
|
240
|
+
while (parentMap.has(cur)) {
|
|
241
|
+
const { parentCell, edge } = parentMap.get(cur);
|
|
242
|
+
const edgePath = edgePaths.get(edge);
|
|
243
|
+
if (edgePath) edgePath.classList.add('is-active');
|
|
244
|
+
const pEl = cellElements.get(parentCell.id);
|
|
245
|
+
if (pEl) pEl.classList.add('is-active-branch');
|
|
246
|
+
cur = parentCell;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Trace direct children
|
|
250
|
+
const kids = childMap.get(cell) || [];
|
|
251
|
+
for (const { childCell, edge } of kids) {
|
|
252
|
+
const edgePath = edgePaths.get(edge);
|
|
253
|
+
if (edgePath) edgePath.classList.add('is-active');
|
|
254
|
+
const cEl = cellElements.get(childCell.id);
|
|
255
|
+
if (cEl) cEl.classList.add('is-active-branch');
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
el.addEventListener('mouseleave', () => {
|
|
260
|
+
wrap.classList.remove('has-hover');
|
|
261
|
+
for (const edgePath of edgePaths.values()) {
|
|
262
|
+
edgePath.classList.remove('is-active');
|
|
263
|
+
}
|
|
264
|
+
for (const cellEl of cellElements.values()) {
|
|
265
|
+
cellEl.classList.remove('is-active-branch');
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
el.addEventListener('click', (event) => {
|
|
270
|
+
event.stopPropagation();
|
|
271
|
+
if (cell.kind === 'ghost' || cell.kind === 'more') {
|
|
272
|
+
handlers.onNavigate(cell);
|
|
273
|
+
} else if (cell.expandable) {
|
|
274
|
+
handlers.onToggle(cell);
|
|
275
|
+
} else {
|
|
276
|
+
handlers.onNavigate(cell);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
wrap.appendChild(el);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
container.appendChild(wrap);
|
|
283
|
+
return wrap;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function dirPart(path) {
|
|
287
|
+
const i = path.lastIndexOf('/');
|
|
288
|
+
return i === -1 ? '(root)' : path.slice(0, i);
|
|
289
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Reading a file out of whichever repo is loaded, and packing up what the
|
|
2
|
+
// prompts need to know about it.
|
|
3
|
+
//
|
|
4
|
+
// Two sources, one signature: a server-side scan fetches over HTTP, a
|
|
5
|
+
// browser-picked folder reads from the File System Access handle it kept. Every
|
|
6
|
+
// caller that wants file text goes through here, so neither the docs view nor
|
|
7
|
+
// the code tab has to know which kind of repo it is looking at.
|
|
8
|
+
|
|
9
|
+
import { roleOf, scanIndex } from '/shared/analyzer/graph.js';
|
|
10
|
+
import { state } from './state.js';
|
|
11
|
+
import { fetchFileText } from './api.js';
|
|
12
|
+
|
|
13
|
+
export async function readRepoFile(path) {
|
|
14
|
+
if (state.browserSource) return state.browserSource.read(path);
|
|
15
|
+
if (state.scanId) return fetchFileText(state.scanId, path);
|
|
16
|
+
throw new Error('no file source for this repo');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Everything the AI prompts want about one file: its place in the graph, plus
|
|
20
|
+
// its source if we can get it. A failed read is not an error — the prompts are
|
|
21
|
+
// written to work from the graph alone, and say so.
|
|
22
|
+
export async function gatherFileContext(path) {
|
|
23
|
+
const file = scanIndex(state.scan).fileAt(path);
|
|
24
|
+
let source = '';
|
|
25
|
+
try {
|
|
26
|
+
source = await readRepoFile(path);
|
|
27
|
+
} catch {
|
|
28
|
+
source = '';
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
path,
|
|
32
|
+
// Read from the graph, not from whatever the inspector happens to be
|
|
33
|
+
// showing. This used to be `document.querySelector('#inspRole').textContent`,
|
|
34
|
+
// which is why one caller overwrote the field immediately afterwards.
|
|
35
|
+
role: roleOf(path, state.facts),
|
|
36
|
+
fanIn: state.facts.fanIn[path] || 0,
|
|
37
|
+
fanOut: state.facts.fanOut[path] || 0,
|
|
38
|
+
importers: state.facts.importers[path] || [],
|
|
39
|
+
imports: state.facts.importsOf[path] || [],
|
|
40
|
+
functions: file?.functions.map((f) => f.name),
|
|
41
|
+
exports_: file?.exports.map((e) => e.name),
|
|
42
|
+
source,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Software Bill of Materials (SBOM) & License Compliance View.
|
|
2
|
+
// Renders dependency inventory, license classification, and copyleft compliance audit.
|
|
3
|
+
|
|
4
|
+
import { escapeHtml } from './html.js';
|
|
5
|
+
|
|
6
|
+
export function renderSbom(container, { scan }) {
|
|
7
|
+
if (!container) return;
|
|
8
|
+
|
|
9
|
+
const report = scan?.licenseReport || {
|
|
10
|
+
projectLicense: scan?.license || { id: 'UNKNOWN', name: 'Unknown', type: 'unknown' },
|
|
11
|
+
sbom: scan?.sbom || [],
|
|
12
|
+
counts: { permissive: 0, copyleft: 0, weakCopyleft: 0, unknown: 0, total: 0 },
|
|
13
|
+
complianceStatus: 'clean',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const sbom = report.sbom || [];
|
|
17
|
+
const counts = report.counts || { permissive: 0, copyleft: 0, weakCopyleft: 0, unknown: 0, total: sbom.length };
|
|
18
|
+
const projectLicense = report.projectLicense || { id: 'UNKNOWN', name: 'Unknown' };
|
|
19
|
+
|
|
20
|
+
// The filter bar is pointless when there is nothing to filter.
|
|
21
|
+
// Built as a separate template — nesting backticks inline breaks the
|
|
22
|
+
// dangling-call lint (nested template literals defeat its string blanking).
|
|
23
|
+
const filterBar = sbom.length ? `
|
|
24
|
+
<div class="sbom-filter-bar">
|
|
25
|
+
<input type="text" class="text-input sbom-search-input" id="sbomSearchInput" placeholder="Filter dependencies or licenses…">
|
|
26
|
+
<div class="sbom-filter-chips">
|
|
27
|
+
<button class="btn btn-ghost btn-sm is-active" data-filter="all">All (${sbom.length})</button>
|
|
28
|
+
<button class="btn btn-ghost btn-sm" data-filter="prod">Production</button>
|
|
29
|
+
<button class="btn btn-ghost btn-sm" data-filter="dev">Development</button>
|
|
30
|
+
<button class="btn btn-ghost btn-sm" data-filter="copyleft">Copyleft (${counts.copyleft})</button>
|
|
31
|
+
</div>
|
|
32
|
+
</div>` : '';
|
|
33
|
+
|
|
34
|
+
container.innerHTML = `
|
|
35
|
+
<div class="sbom-layout">
|
|
36
|
+
<!-- License Header & Summary -->
|
|
37
|
+
<header class="sbom-header">
|
|
38
|
+
<div class="sbom-lic-card">
|
|
39
|
+
<span class="sbom-lic-label">Project License</span>
|
|
40
|
+
<div class="sbom-lic-val">
|
|
41
|
+
<span class="sbom-badge is-${projectLicense.type || 'permissive'}">${escapeHtml(projectLicense.id)}</span>
|
|
42
|
+
<span class="sbom-lic-name">${escapeHtml(projectLicense.name)}</span>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="sbom-stat-cards">
|
|
47
|
+
<div class="sbom-stat-chip">
|
|
48
|
+
<b>${counts.total}</b> Total Packages
|
|
49
|
+
</div>
|
|
50
|
+
<div class="sbom-stat-chip is-permissive">
|
|
51
|
+
<b>${counts.permissive}</b> Permissive
|
|
52
|
+
</div>
|
|
53
|
+
<div class="sbom-stat-chip is-copyleft">
|
|
54
|
+
<b>${counts.copyleft}</b> Copyleft
|
|
55
|
+
</div>
|
|
56
|
+
<div class="sbom-stat-chip is-weak">
|
|
57
|
+
<b>${counts.weakCopyleft}</b> Weak Copyleft
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</header>
|
|
61
|
+
|
|
62
|
+
<!-- Search & Filter Controls (only useful with packages to filter) -->
|
|
63
|
+
${filterBar}
|
|
64
|
+
|
|
65
|
+
<!-- Dependencies Inventory Table -->
|
|
66
|
+
<div class="sbom-table-wrap">
|
|
67
|
+
<table class="sbom-table" id="sbomTable">
|
|
68
|
+
<thead>
|
|
69
|
+
<tr>
|
|
70
|
+
<th>Package Name</th>
|
|
71
|
+
<th>Version</th>
|
|
72
|
+
<th>Ecosystem</th>
|
|
73
|
+
<th>Type</th>
|
|
74
|
+
<th>License</th>
|
|
75
|
+
</tr>
|
|
76
|
+
</thead>
|
|
77
|
+
<tbody id="sbomTableBody">
|
|
78
|
+
${renderSbomRows(sbom)}
|
|
79
|
+
</tbody>
|
|
80
|
+
</table>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
// Wire search & filtering
|
|
86
|
+
const searchInput = container.querySelector('#sbomSearchInput');
|
|
87
|
+
const tableBody = container.querySelector('#sbomTableBody');
|
|
88
|
+
const filterBtns = container.querySelectorAll('.sbom-filter-chips button');
|
|
89
|
+
|
|
90
|
+
let activeFilter = 'all';
|
|
91
|
+
|
|
92
|
+
function applyFilter() {
|
|
93
|
+
const q = (searchInput?.value || '').toLowerCase().trim();
|
|
94
|
+
const filtered = sbom.filter((item) => {
|
|
95
|
+
if (activeFilter === 'prod' && item.isDev) return false;
|
|
96
|
+
if (activeFilter === 'dev' && !item.isDev) return false;
|
|
97
|
+
if (activeFilter === 'copyleft' && item.license?.type !== 'copyleft') return false;
|
|
98
|
+
|
|
99
|
+
if (!q) return true;
|
|
100
|
+
return item.name.toLowerCase().includes(q)
|
|
101
|
+
|| item.ecosystem.toLowerCase().includes(q)
|
|
102
|
+
|| String(item.license?.id || '').toLowerCase().includes(q);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (tableBody) tableBody.innerHTML = renderSbomRows(filtered, { filtered: true });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
searchInput?.addEventListener('input', applyFilter);
|
|
109
|
+
|
|
110
|
+
filterBtns.forEach((btn) => {
|
|
111
|
+
btn.addEventListener('click', () => {
|
|
112
|
+
filterBtns.forEach((b) => b.classList.remove('is-active'));
|
|
113
|
+
btn.classList.add('is-active');
|
|
114
|
+
activeFilter = btn.dataset.filter;
|
|
115
|
+
applyFilter();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function renderSbomRows(items, { filtered } = {}) {
|
|
121
|
+
if (!items.length) {
|
|
122
|
+
// Distinguish "nothing matched your filter" from "this project simply
|
|
123
|
+
// declares no dependencies" — the latter is a finding, not a dead end.
|
|
124
|
+
if (!filtered) {
|
|
125
|
+
return '<tr><td colspan="5" class="sbom-no-results">No dependency manifests found — this project declares no npm, pip, Go, or Cargo dependencies.</td></tr>';
|
|
126
|
+
}
|
|
127
|
+
return '<tr><td colspan="5" class="sbom-no-results">No packages match the current filter.</td></tr>';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return items.map((item) => {
|
|
131
|
+
const lic = item.license || { id: 'Unknown', type: 'unknown' };
|
|
132
|
+
return `
|
|
133
|
+
<tr>
|
|
134
|
+
<td class="sbom-cell-pkg"><b>${escapeHtml(item.name)}</b></td>
|
|
135
|
+
<td class="sbom-cell-ver"><code>${escapeHtml(item.version)}</code></td>
|
|
136
|
+
<td class="sbom-cell-eco"><span class="eco-tag eco-${item.ecosystem}">${escapeHtml(item.ecosystem)}</span></td>
|
|
137
|
+
<td class="sbom-cell-dev"><span class="dep-type-chip ${item.isDev ? 'is-dev' : 'is-prod'}">${item.isDev ? 'dev' : 'prod'}</span></td>
|
|
138
|
+
<td class="sbom-cell-lic"><span class="sbom-badge is-${lic.type}">${escapeHtml(lic.id)}</span></td>
|
|
139
|
+
</tr>
|
|
140
|
+
`;
|
|
141
|
+
}).join('');
|
|
142
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Scan caching & trend analysis. Pure logic so it is unit-tested without a browser.
|
|
2
|
+
|
|
3
|
+
export function hashString(str) {
|
|
4
|
+
let h = 2166136261 >>> 0;
|
|
5
|
+
for (let i = 0; i < str.length; i++) {
|
|
6
|
+
h ^= str.charCodeAt(i);
|
|
7
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
8
|
+
}
|
|
9
|
+
return h.toString(16);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function repoCacheKey(scan) {
|
|
13
|
+
if (!scan) return '';
|
|
14
|
+
const id = scan.gitUrl || scan.root || scan.name || '';
|
|
15
|
+
const summary = (scan.files || [])
|
|
16
|
+
.slice(0, 100)
|
|
17
|
+
.map((f) => `${f.path}:${f.size}:${f.loc}`)
|
|
18
|
+
.join('|');
|
|
19
|
+
return `onboarder.cache.${hashString(id)}.${hashString(summary)}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function compareScans(prev, curr) {
|
|
23
|
+
if (!prev || !curr) return null;
|
|
24
|
+
const prevScore = prev.health?.score ?? prev.score ?? 100;
|
|
25
|
+
const currScore = curr.health?.score ?? curr.score ?? 100;
|
|
26
|
+
const scoreDiff = currScore - prevScore;
|
|
27
|
+
const prevFiles = prev.scan?.files?.length ?? prev.files?.length ?? 0;
|
|
28
|
+
const currFiles = curr.scan?.files?.length ?? curr.files?.length ?? 0;
|
|
29
|
+
const filesDiff = currFiles - prevFiles;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
scoreDiff,
|
|
33
|
+
filesDiff,
|
|
34
|
+
trend: scoreDiff > 0 ? 'improving' : scoreDiff < 0 ? 'degrading' : 'stable',
|
|
35
|
+
summary: scoreDiff > 0
|
|
36
|
+
? `Health improved by +${scoreDiff} pts since last scan.`
|
|
37
|
+
: scoreDiff < 0
|
|
38
|
+
? `Health decreased by ${scoreDiff} pts since last scan.`
|
|
39
|
+
: 'Health score matches previous scan.',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class ScanCache {
|
|
44
|
+
constructor(storage = typeof localStorage !== 'undefined' ? localStorage : null) {
|
|
45
|
+
this.storage = storage;
|
|
46
|
+
this.memory = new Map();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get(key) {
|
|
50
|
+
if (this.memory.has(key)) return this.memory.get(key);
|
|
51
|
+
if (!this.storage) return null;
|
|
52
|
+
try {
|
|
53
|
+
const raw = this.storage.getItem(key);
|
|
54
|
+
if (!raw) return null;
|
|
55
|
+
const parsed = JSON.parse(raw);
|
|
56
|
+
this.memory.set(key, parsed);
|
|
57
|
+
return parsed;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
set(key, val) {
|
|
64
|
+
this.memory.set(key, val);
|
|
65
|
+
if (!this.storage) return;
|
|
66
|
+
try {
|
|
67
|
+
this.storage.setItem(key, JSON.stringify(val));
|
|
68
|
+
} catch {
|
|
69
|
+
// Quota exceeded or disabled
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
clear() {
|
|
74
|
+
this.memory.clear();
|
|
75
|
+
}
|
|
76
|
+
}
|