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.
Files changed (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +275 -0
  3. package/bin/onboarder.js +14 -0
  4. package/bin/postinstall.js +6 -0
  5. package/cli/commands.js +436 -0
  6. package/cli/main.js +129 -0
  7. package/cli/prompt.js +101 -0
  8. package/cli/ui.js +55 -0
  9. package/cli/wizard.js +325 -0
  10. package/package.json +48 -0
  11. package/public/app.js +1339 -0
  12. package/public/index.html +455 -0
  13. package/public/js/about.js +284 -0
  14. package/public/js/aiDraft.js +163 -0
  15. package/public/js/analysisPanel.js +270 -0
  16. package/public/js/analysisReport.js +154 -0
  17. package/public/js/api.js +264 -0
  18. package/public/js/atlas.js +98 -0
  19. package/public/js/blameView.js +41 -0
  20. package/public/js/codeTab.js +256 -0
  21. package/public/js/codeViewer.js +184 -0
  22. package/public/js/components/FileChip.js +102 -0
  23. package/public/js/components/MetricSparkline.js +89 -0
  24. package/public/js/components/RiskBadge.js +105 -0
  25. package/public/js/deepAnalysisView.js +496 -0
  26. package/public/js/diagramPane.js +154 -0
  27. package/public/js/diffView.js +352 -0
  28. package/public/js/docsView.js +566 -0
  29. package/public/js/fileSourceBrowser.js +47 -0
  30. package/public/js/flameGraph.js +75 -0
  31. package/public/js/forceGraph.js +592 -0
  32. package/public/js/heatmap.js +185 -0
  33. package/public/js/highlight.js +122 -0
  34. package/public/js/html.js +50 -0
  35. package/public/js/insightsView.js +236 -0
  36. package/public/js/inspector.js +535 -0
  37. package/public/js/llm.js +345 -0
  38. package/public/js/mapView.js +159 -0
  39. package/public/js/markdown.js +119 -0
  40. package/public/js/mindmap.js +289 -0
  41. package/public/js/repoFiles.js +44 -0
  42. package/public/js/sbomView.js +142 -0
  43. package/public/js/scanCache.js +76 -0
  44. package/public/js/search.js +874 -0
  45. package/public/js/serverSettings.js +169 -0
  46. package/public/js/state.js +192 -0
  47. package/public/js/tour.js +17 -0
  48. package/public/js/transitions.js +15 -0
  49. package/public/js/tree.js +200 -0
  50. package/public/js/workflowsView.js +95 -0
  51. package/public/styles.css +2767 -0
  52. package/public/vendor/mermaid.min.js +3587 -0
  53. package/public/vendor/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  54. package/public/vendor/monaco/vs/base/worker/workerMain.js +31 -0
  55. package/public/vendor/monaco/vs/basic-languages/abap/abap.js +10 -0
  56. package/public/vendor/monaco/vs/basic-languages/apex/apex.js +10 -0
  57. package/public/vendor/monaco/vs/basic-languages/azcli/azcli.js +10 -0
  58. package/public/vendor/monaco/vs/basic-languages/bat/bat.js +10 -0
  59. package/public/vendor/monaco/vs/basic-languages/bicep/bicep.js +11 -0
  60. package/public/vendor/monaco/vs/basic-languages/cameligo/cameligo.js +10 -0
  61. package/public/vendor/monaco/vs/basic-languages/clojure/clojure.js +10 -0
  62. package/public/vendor/monaco/vs/basic-languages/coffee/coffee.js +10 -0
  63. package/public/vendor/monaco/vs/basic-languages/cpp/cpp.js +10 -0
  64. package/public/vendor/monaco/vs/basic-languages/csharp/csharp.js +10 -0
  65. package/public/vendor/monaco/vs/basic-languages/csp/csp.js +10 -0
  66. package/public/vendor/monaco/vs/basic-languages/css/css.js +12 -0
  67. package/public/vendor/monaco/vs/basic-languages/cypher/cypher.js +10 -0
  68. package/public/vendor/monaco/vs/basic-languages/dart/dart.js +10 -0
  69. package/public/vendor/monaco/vs/basic-languages/dockerfile/dockerfile.js +10 -0
  70. package/public/vendor/monaco/vs/basic-languages/ecl/ecl.js +10 -0
  71. package/public/vendor/monaco/vs/basic-languages/elixir/elixir.js +10 -0
  72. package/public/vendor/monaco/vs/basic-languages/flow9/flow9.js +10 -0
  73. package/public/vendor/monaco/vs/basic-languages/freemarker2/freemarker2.js +12 -0
  74. package/public/vendor/monaco/vs/basic-languages/fsharp/fsharp.js +10 -0
  75. package/public/vendor/monaco/vs/basic-languages/go/go.js +10 -0
  76. package/public/vendor/monaco/vs/basic-languages/graphql/graphql.js +10 -0
  77. package/public/vendor/monaco/vs/basic-languages/handlebars/handlebars.js +10 -0
  78. package/public/vendor/monaco/vs/basic-languages/hcl/hcl.js +10 -0
  79. package/public/vendor/monaco/vs/basic-languages/html/html.js +10 -0
  80. package/public/vendor/monaco/vs/basic-languages/ini/ini.js +10 -0
  81. package/public/vendor/monaco/vs/basic-languages/java/java.js +10 -0
  82. package/public/vendor/monaco/vs/basic-languages/javascript/javascript.js +10 -0
  83. package/public/vendor/monaco/vs/basic-languages/julia/julia.js +10 -0
  84. package/public/vendor/monaco/vs/basic-languages/kotlin/kotlin.js +10 -0
  85. package/public/vendor/monaco/vs/basic-languages/less/less.js +11 -0
  86. package/public/vendor/monaco/vs/basic-languages/lexon/lexon.js +10 -0
  87. package/public/vendor/monaco/vs/basic-languages/liquid/liquid.js +10 -0
  88. package/public/vendor/monaco/vs/basic-languages/lua/lua.js +10 -0
  89. package/public/vendor/monaco/vs/basic-languages/m3/m3.js +10 -0
  90. package/public/vendor/monaco/vs/basic-languages/markdown/markdown.js +10 -0
  91. package/public/vendor/monaco/vs/basic-languages/mdx/mdx.js +10 -0
  92. package/public/vendor/monaco/vs/basic-languages/mips/mips.js +10 -0
  93. package/public/vendor/monaco/vs/basic-languages/msdax/msdax.js +10 -0
  94. package/public/vendor/monaco/vs/basic-languages/mysql/mysql.js +10 -0
  95. package/public/vendor/monaco/vs/basic-languages/objective-c/objective-c.js +10 -0
  96. package/public/vendor/monaco/vs/basic-languages/pascal/pascal.js +10 -0
  97. package/public/vendor/monaco/vs/basic-languages/pascaligo/pascaligo.js +10 -0
  98. package/public/vendor/monaco/vs/basic-languages/perl/perl.js +10 -0
  99. package/public/vendor/monaco/vs/basic-languages/pgsql/pgsql.js +10 -0
  100. package/public/vendor/monaco/vs/basic-languages/php/php.js +10 -0
  101. package/public/vendor/monaco/vs/basic-languages/pla/pla.js +10 -0
  102. package/public/vendor/monaco/vs/basic-languages/postiats/postiats.js +10 -0
  103. package/public/vendor/monaco/vs/basic-languages/powerquery/powerquery.js +10 -0
  104. package/public/vendor/monaco/vs/basic-languages/powershell/powershell.js +10 -0
  105. package/public/vendor/monaco/vs/basic-languages/protobuf/protobuf.js +11 -0
  106. package/public/vendor/monaco/vs/basic-languages/pug/pug.js +10 -0
  107. package/public/vendor/monaco/vs/basic-languages/python/python.js +10 -0
  108. package/public/vendor/monaco/vs/basic-languages/qsharp/qsharp.js +10 -0
  109. package/public/vendor/monaco/vs/basic-languages/r/r.js +10 -0
  110. package/public/vendor/monaco/vs/basic-languages/razor/razor.js +10 -0
  111. package/public/vendor/monaco/vs/basic-languages/redis/redis.js +10 -0
  112. package/public/vendor/monaco/vs/basic-languages/redshift/redshift.js +10 -0
  113. package/public/vendor/monaco/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
  114. package/public/vendor/monaco/vs/basic-languages/ruby/ruby.js +10 -0
  115. package/public/vendor/monaco/vs/basic-languages/rust/rust.js +10 -0
  116. package/public/vendor/monaco/vs/basic-languages/sb/sb.js +10 -0
  117. package/public/vendor/monaco/vs/basic-languages/scala/scala.js +10 -0
  118. package/public/vendor/monaco/vs/basic-languages/scheme/scheme.js +10 -0
  119. package/public/vendor/monaco/vs/basic-languages/scss/scss.js +12 -0
  120. package/public/vendor/monaco/vs/basic-languages/shell/shell.js +10 -0
  121. package/public/vendor/monaco/vs/basic-languages/solidity/solidity.js +10 -0
  122. package/public/vendor/monaco/vs/basic-languages/sophia/sophia.js +10 -0
  123. package/public/vendor/monaco/vs/basic-languages/sparql/sparql.js +10 -0
  124. package/public/vendor/monaco/vs/basic-languages/sql/sql.js +10 -0
  125. package/public/vendor/monaco/vs/basic-languages/st/st.js +10 -0
  126. package/public/vendor/monaco/vs/basic-languages/swift/swift.js +13 -0
  127. package/public/vendor/monaco/vs/basic-languages/systemverilog/systemverilog.js +10 -0
  128. package/public/vendor/monaco/vs/basic-languages/tcl/tcl.js +10 -0
  129. package/public/vendor/monaco/vs/basic-languages/twig/twig.js +10 -0
  130. package/public/vendor/monaco/vs/basic-languages/typescript/typescript.js +10 -0
  131. package/public/vendor/monaco/vs/basic-languages/typespec/typespec.js +10 -0
  132. package/public/vendor/monaco/vs/basic-languages/vb/vb.js +10 -0
  133. package/public/vendor/monaco/vs/basic-languages/wgsl/wgsl.js +307 -0
  134. package/public/vendor/monaco/vs/basic-languages/xml/xml.js +10 -0
  135. package/public/vendor/monaco/vs/basic-languages/yaml/yaml.js +10 -0
  136. package/public/vendor/monaco/vs/editor/editor.main.css +8 -0
  137. package/public/vendor/monaco/vs/editor/editor.main.js +798 -0
  138. package/public/vendor/monaco/vs/language/css/cssMode.js +13 -0
  139. package/public/vendor/monaco/vs/language/css/cssWorker.js +77 -0
  140. package/public/vendor/monaco/vs/language/html/htmlMode.js +13 -0
  141. package/public/vendor/monaco/vs/language/html/htmlWorker.js +454 -0
  142. package/public/vendor/monaco/vs/language/json/jsonMode.js +19 -0
  143. package/public/vendor/monaco/vs/language/json/jsonWorker.js +42 -0
  144. package/public/vendor/monaco/vs/language/typescript/tsMode.js +20 -0
  145. package/public/vendor/monaco/vs/language/typescript/tsWorker.js +51328 -0
  146. package/public/vendor/monaco/vs/loader.js +11 -0
  147. package/public/vendor/monaco/worker-boot.js +9 -0
  148. package/server/.fuse_hidden0000000800000001 +36 -0
  149. package/server/apiDiff.js +25 -0
  150. package/server/apiDocs.js +67 -0
  151. package/server/apiFile.js +36 -0
  152. package/server/apiGitBlame.js +63 -0
  153. package/server/apiMcp.js +110 -0
  154. package/server/apiScan.js +119 -0
  155. package/server/apiSearch.js +253 -0
  156. package/server/apiSettings.js +118 -0
  157. package/server/apiTools.js +90 -0
  158. package/server/config.js +227 -0
  159. package/server/fileSourceNode.js +44 -0
  160. package/server/gitClone.js +95 -0
  161. package/server/gitDiff.js +184 -0
  162. package/server/gitHistory.js +110 -0
  163. package/server/htmlText.js +44 -0
  164. package/server/http.js +55 -0
  165. package/server/httpGuards.js +87 -0
  166. package/server/index.js +156 -0
  167. package/server/llmProxy.js +162 -0
  168. package/server/logger.js +29 -0
  169. package/server/mcp/analysis.js +209 -0
  170. package/server/mcp/http.js +213 -0
  171. package/server/mcp/runner.js +278 -0
  172. package/server/mcp/server.js +241 -0
  173. package/server/mcp/standalone.js +42 -0
  174. package/server/mcp/tools.js +683 -0
  175. package/server/paths.js +39 -0
  176. package/server/router.js +218 -0
  177. package/server/searchIndex.js +118 -0
  178. package/server/sessions.js +163 -0
  179. package/server/static.js +59 -0
  180. package/server/tools/install.js +246 -0
  181. package/server/tools/parse.js +170 -0
  182. package/server/tools/platform.js +91 -0
  183. package/server/tools/registry.js +212 -0
  184. package/server/tools/scan.js +136 -0
  185. package/server/tools.js +212 -0
  186. package/server/tunnel.js +92 -0
  187. package/shared/analyzer/docs.js +135 -0
  188. package/shared/analyzer/explainLocal.js +163 -0
  189. package/shared/analyzer/graph.js +461 -0
  190. package/shared/analyzer/health.js +215 -0
  191. package/shared/analyzer/history.js +146 -0
  192. package/shared/analyzer/languages/csharp.js +39 -0
  193. package/shared/analyzer/languages/generic.js +131 -0
  194. package/shared/analyzer/languages/go.js +70 -0
  195. package/shared/analyzer/languages/index.js +43 -0
  196. package/shared/analyzer/languages/java.js +39 -0
  197. package/shared/analyzer/languages/javascript.js +240 -0
  198. package/shared/analyzer/languages/python.js +120 -0
  199. package/shared/analyzer/languages/rust.js +42 -0
  200. package/shared/analyzer/languages/typescript.js +138 -0
  201. package/shared/analyzer/licenses.js +151 -0
  202. package/shared/analyzer/metrics.js +120 -0
  203. package/shared/analyzer/pathUtil.js +69 -0
  204. package/shared/analyzer/patterns.js +272 -0
  205. package/shared/analyzer/scan.js +473 -0
  206. package/shared/analyzer/security.js +187 -0
  207. package/shared/analyzer/services.js +157 -0
  208. package/shared/analyzer/stack.js +173 -0
  209. package/shared/analyzer/tour.js +69 -0
  210. package/shared/analyzer/util.js +123 -0
  211. package/shared/analyzer/workflows.js +143 -0
  212. package/shared/diagram/aiFacts.js +145 -0
  213. package/shared/diagram/aiMermaid.js +59 -0
  214. package/shared/diagram/atlas.js +93 -0
  215. package/shared/diagram/mermaid.js +459 -0
  216. 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
+ }