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,185 @@
1
+ export function initHeatmap(canvas, files = []) {
2
+ const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
3
+ if (!gl) {
4
+ console.warn('WebGL not supported, falling back to Canvas 2D (not implemented)');
5
+ return { update: () => {}, destroy: () => {} };
6
+ }
7
+
8
+ const vsSource = `
9
+ attribute vec2 aVertexPosition;
10
+ attribute vec3 aColor;
11
+ varying vec3 vColor;
12
+ void main() {
13
+ gl_Position = vec4(aVertexPosition, 0.0, 1.0);
14
+ vColor = aColor;
15
+ }
16
+ `;
17
+
18
+ const fsSource = `
19
+ precision mediump float;
20
+ varying vec3 vColor;
21
+ void main() {
22
+ gl_FragColor = vec4(vColor, 1.0);
23
+ }
24
+ `;
25
+
26
+ function createShader(gl, type, source) {
27
+ const shader = gl.createShader(type);
28
+ gl.shaderSource(shader, source);
29
+ gl.compileShader(shader);
30
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
31
+ console.error(gl.getShaderInfoLog(shader));
32
+ gl.deleteShader(shader);
33
+ return null;
34
+ }
35
+ return shader;
36
+ }
37
+
38
+ const vs = createShader(gl, gl.VERTEX_SHADER, vsSource);
39
+ const fs = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
40
+ const program = gl.createProgram();
41
+ gl.attachShader(program, vs);
42
+ gl.attachShader(program, fs);
43
+ gl.linkProgram(program);
44
+
45
+ const positionLocation = gl.getAttribLocation(program, 'aVertexPosition');
46
+ const colorLocation = gl.getAttribLocation(program, 'aColor');
47
+
48
+ const positionBuffer = gl.createBuffer();
49
+ const colorBuffer = gl.createBuffer();
50
+
51
+ let fileRects = [];
52
+ let cols, rows;
53
+
54
+ function resize() {
55
+ const rect = canvas.parentElement.getBoundingClientRect();
56
+ canvas.width = rect.width;
57
+ canvas.height = rect.height;
58
+ gl.viewport(0, 0, canvas.width, canvas.height);
59
+ if (files.length) update(files);
60
+ }
61
+
62
+ function getRiskColor(risk) {
63
+ if (risk <= 33) return [76/255, 175/255, 80/255]; // Green
64
+ if (risk <= 66) return [255/255, 193/255, 7/255]; // Yellow
65
+ return [244/255, 67/255, 54/255]; // Red
66
+ }
67
+
68
+ function update(newFiles) {
69
+ files = newFiles.sort((a, b) => a.path.localeCompare(b.path));
70
+ const count = files.length;
71
+ if (count === 0) return;
72
+
73
+ cols = Math.ceil(Math.sqrt(count * (canvas.width / canvas.height)));
74
+ rows = Math.ceil(count / cols);
75
+
76
+ const tileW = 2.0 / cols;
77
+ const tileH = 2.0 / rows;
78
+
79
+ const positions = new Float32Array(count * 12);
80
+ const colors = new Float32Array(count * 18);
81
+
82
+ fileRects = [];
83
+
84
+ for (let i = 0; i < count; i++) {
85
+ const c = i % cols;
86
+ const r = Math.floor(i / cols);
87
+
88
+ const x = -1.0 + c * tileW;
89
+ const y = 1.0 - r * tileH;
90
+
91
+ const padX = tileW * 0.05;
92
+ const padY = tileH * 0.05;
93
+
94
+ const px1 = x + padX; const py1 = y - padY;
95
+ const px2 = x + tileW - padX; const py2 = y - tileH + padY;
96
+
97
+ fileRects.push({ file: files[i], x: px1, y: py1, w: px2-px1, h: py2-py1 });
98
+
99
+ const pIdx = i * 12;
100
+ positions.set([
101
+ px1, py1, px2, py1, px1, py2,
102
+ px1, py2, px2, py1, px2, py2
103
+ ], pIdx);
104
+
105
+ const color = getRiskColor(files[i].risk || 0);
106
+ const cIdx = i * 18;
107
+ for (let j = 0; j < 6; j++) {
108
+ colors.set(color, cIdx + j * 3);
109
+ }
110
+ }
111
+
112
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
113
+ gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
114
+
115
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
116
+ gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
117
+
118
+ render(count);
119
+ }
120
+
121
+ function render(count) {
122
+ gl.clearColor(0.1, 0.1, 0.1, 1.0);
123
+ gl.clear(gl.COLOR_BUFFER_BIT);
124
+
125
+ gl.useProgram(program);
126
+
127
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
128
+ gl.enableVertexAttribArray(positionLocation);
129
+ gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
130
+
131
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
132
+ gl.enableVertexAttribArray(colorLocation);
133
+ gl.vertexAttribPointer(colorLocation, 3, gl.FLOAT, false, 0, 0);
134
+
135
+ gl.drawArrays(gl.TRIANGLES, 0, count * 6);
136
+ }
137
+
138
+ canvas.addEventListener('mousemove', e => {
139
+ if (fileRects.length === 0) return;
140
+ const rect = canvas.getBoundingClientRect();
141
+ const nx = ((e.clientX - rect.left) / canvas.width) * 2 - 1;
142
+ const ny = -(((e.clientY - rect.top) / canvas.height) * 2 - 1);
143
+
144
+ let hovered = null;
145
+ for (const fr of fileRects) {
146
+ if (nx >= fr.x && nx <= fr.x + fr.w && ny <= fr.y && ny >= fr.y - fr.h) {
147
+ hovered = fr.file;
148
+ break;
149
+ }
150
+ }
151
+
152
+ const tooltip = document.getElementById('heatmapTooltip');
153
+ if (tooltip) {
154
+ if (hovered) {
155
+ tooltip.textContent = `${hovered.path} (Risk: ${hovered.risk || 0})`;
156
+ tooltip.style.left = e.clientX + 10 + 'px';
157
+ tooltip.style.top = e.clientY + 10 + 'px';
158
+ tooltip.hidden = false;
159
+ } else {
160
+ tooltip.hidden = true;
161
+ }
162
+ }
163
+ });
164
+
165
+ canvas.addEventListener('click', () => {
166
+ const tooltip = document.getElementById('heatmapTooltip');
167
+ if (tooltip && !tooltip.hidden && fileRects.length) {
168
+ // Find currently hovered file
169
+ const rect = canvas.getBoundingClientRect();
170
+ // Dispatch click for the hovered file if available
171
+ canvas.dispatchEvent(new CustomEvent('heatmap-node-click', {
172
+ detail: { path: tooltip.textContent.split(' ')[0] },
173
+ bubbles: true
174
+ }));
175
+ }
176
+ });
177
+
178
+ window.addEventListener('resize', resize);
179
+ resize();
180
+
181
+ return {
182
+ update,
183
+ destroy: () => window.removeEventListener('resize', resize)
184
+ };
185
+ }
@@ -0,0 +1,122 @@
1
+ // Hand-rolled syntax highlighting — a single left-to-right scanner, no
2
+ // regex tokenizing, so highlighting is linear-time even on pathological
3
+ // input (an earlier master-regex version could stall on real files).
4
+ // Runs in Node (tests) and the browser.
5
+
6
+ import { escapeHtml } from './html.js';
7
+
8
+ const KEYWORDS = {
9
+ js: 'const let var function return import export from default if else for while do class extends new await async try catch finally throw typeof instanceof of in switch case break continue yield static get set super this null undefined true false void delete',
10
+ py: 'def class return import from as if elif else for while in not and or is None True False try except finally raise with lambda pass break continue yield global nonlocal assert async await del',
11
+ go: 'func package import return if else for range var const type struct interface map chan go defer select switch case break continue fallthrough default nil true false iota',
12
+ c: 'int void char float double long short unsigned signed const static return if else for while do switch case break continue struct union enum typedef sizeof include define extern volatile register goto',
13
+ };
14
+
15
+ export function langOf(path) {
16
+ const ext = (path.match(/\.([A-Za-z0-9]+)$/) || [, ''])[1].toLowerCase();
17
+ if (['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs', 'json'].includes(ext)) return 'js';
18
+ if (['py', 'pyi'].includes(ext)) return 'py';
19
+ if (ext === 'go') return 'go';
20
+ if (['c', 'h', 'cpp', 'cc', 'java', 'rs', 'rb', 'php', 'cs', 'swift', 'kt', 'css'].includes(ext)) return 'c';
21
+ return null; // markdown, yaml, toml, plain text — shown unadorned
22
+ }
23
+
24
+ const isWordStart = (c) => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c === '_' || c === '$';
25
+ const isWordPart = (c) => isWordStart(c) || (c >= '0' && c <= '9');
26
+ const isDigit = (c) => c >= '0' && c <= '9';
27
+
28
+ export function highlightCode(source, lang) {
29
+ if (!lang || !KEYWORDS[lang]) return escapeHtml(source);
30
+ const kw = new Set(KEYWORDS[lang].split(' '));
31
+ const hashComment = lang === 'py';
32
+ const out = [];
33
+ let plain = '';
34
+
35
+ const flushPlain = () => {
36
+ if (plain) { out.push(escapeHtml(plain)); plain = ''; }
37
+ };
38
+ const emit = (cls, text) => {
39
+ flushPlain();
40
+ out.push(`<span class="${cls}">${escapeHtml(text)}</span>`);
41
+ };
42
+
43
+ const n = source.length;
44
+ let i = 0;
45
+ while (i < n) {
46
+ const c = source[i];
47
+
48
+ // Line comments: # for python, // elsewhere.
49
+ if ((hashComment && c === '#') || (!hashComment && c === '/' && source[i + 1] === '/')) {
50
+ let j = source.indexOf('\n', i);
51
+ if (j === -1) j = n;
52
+ emit('tok-com', source.slice(i, j));
53
+ i = j;
54
+ continue;
55
+ }
56
+
57
+ // Block comments (and preprocessor lines for the C family).
58
+ if (!hashComment && c === '/' && source[i + 1] === '*') {
59
+ const end = source.indexOf('*/', i + 2);
60
+ const j = end === -1 ? n : end + 2;
61
+ emit('tok-com', source.slice(i, j));
62
+ i = j;
63
+ continue;
64
+ }
65
+ if (lang === 'c' && c === '#' && (i === 0 || source[i - 1] === '\n')) {
66
+ let j = source.indexOf('\n', i);
67
+ if (j === -1) j = n;
68
+ emit('tok-com', source.slice(i, j));
69
+ i = j;
70
+ continue;
71
+ }
72
+
73
+ // Strings. Python triple quotes first; backticks are multiline in js/go.
74
+ if (lang === 'py' && (source.startsWith("'''", i) || source.startsWith('"""', i))) {
75
+ const q = source.slice(i, i + 3);
76
+ const end = source.indexOf(q, i + 3);
77
+ const j = end === -1 ? n : end + 3;
78
+ emit('tok-str', source.slice(i, j));
79
+ i = j;
80
+ continue;
81
+ }
82
+ if (c === "'" || c === '"' || (c === '`' && lang !== 'py' && lang !== 'c')) {
83
+ const multiline = c === '`' || lang === 'py';
84
+ let j = i + 1;
85
+ while (j < n) {
86
+ const d = source[j];
87
+ if (d === '\\') { j += 2; continue; }
88
+ if (d === c) { j++; break; }
89
+ if (d === '\n' && !multiline) break; // unterminated: stop at the line end
90
+ j++;
91
+ }
92
+ emit('tok-str', source.slice(i, j));
93
+ i = j;
94
+ continue;
95
+ }
96
+
97
+ // Words → keywords when the language claims them.
98
+ if (isWordStart(c)) {
99
+ let j = i + 1;
100
+ while (j < n && isWordPart(source[j])) j++;
101
+ const w = source.slice(i, j);
102
+ if (kw.has(w)) emit('tok-kw', w);
103
+ else plain += w;
104
+ i = j;
105
+ continue;
106
+ }
107
+
108
+ // Numbers.
109
+ if (isDigit(c)) {
110
+ let j = i + 1;
111
+ while (j < n && (isDigit(source[j]) || source[j] === '.' || source[j] === '_')) j++;
112
+ emit('tok-num', source.slice(i, j));
113
+ i = j;
114
+ continue;
115
+ }
116
+
117
+ plain += c;
118
+ i++;
119
+ }
120
+ flushPlain();
121
+ return out.join('');
122
+ }
@@ -0,0 +1,50 @@
1
+ // Escaping at the boundary — one implementation, used everywhere.
2
+ //
3
+ // Onboarder renders content it does not control: file and folder names from a
4
+ // repository that may have been cloned from an arbitrary git URL, READMEs,
5
+ // GitHub API fields, and model output. All of it reaches the DOM as HTML
6
+ // strings, and a good deal of it lands inside attributes
7
+ // (`title="…"`, `data-path="…"`). So quotes matter as much as angle brackets:
8
+ // a file named `x" onmouseover="…` closes the attribute and opens a handler,
9
+ // and double quotes are legal in filenames on macOS and Linux.
10
+ //
11
+ // This used to be five near-copies — four of which escaped only & < > — with
12
+ // the attribute call sites quietly relying on the one that didn't. Now there
13
+ // is one function and it escapes all five characters.
14
+ //
15
+ // Single pass, one regex, one lookup per match: strictly cheaper than the
16
+ // chain of four .replace() calls it replaces, which matters because the
17
+ // syntax highlighter runs this over every token of every file.
18
+
19
+ const ESCAPES = {
20
+ '&': '&amp;',
21
+ '<': '&lt;',
22
+ '>': '&gt;',
23
+ '"': '&quot;',
24
+ "'": '&#39;',
25
+ };
26
+
27
+ const NEEDS_ESCAPE = /[&<>"']/g;
28
+
29
+ /**
30
+ * Make a string safe to interpolate into HTML — as text, or inside a
31
+ * double- or single-quoted attribute value.
32
+ */
33
+ export function escapeHtml(s) {
34
+ return String(s ?? '').replace(NEEDS_ESCAPE, (c) => ESCAPES[c]);
35
+ }
36
+
37
+ // Whitespace and C0/C1 control characters, which browsers ignore when they
38
+ // parse a URL scheme — so `java\tscript:alert(1)` is a live javascript: URL.
39
+ const URL_NOISE = /[\s\u0000-\u001F\u007F-\u009F]/g;
40
+
41
+ /**
42
+ * A URL safe to put in an href. Anything that isn't plainly http(s) comes back
43
+ * empty, which keeps `javascript:`, `data:` and `vbscript:` out of the DOM
44
+ * even when the surrounding markup is assembled by hand.
45
+ */
46
+ export function safeUrl(url) {
47
+ const raw = String(url ?? '').trim();
48
+ if (!/^https?:\/\//i.test(raw.replace(URL_NOISE, ''))) return '';
49
+ return escapeHtml(raw);
50
+ }
@@ -0,0 +1,236 @@
1
+ // GitHub Insights & Pulse View.
2
+ // Renders 52-week contribution heatmap, contributor leaderboard, punchcard, and release timeline.
3
+
4
+ import { escapeHtml } from './html.js';
5
+
6
+ export function renderInsights(container, { scan, facts, history }) {
7
+ if (!container) return;
8
+
9
+ const commits = history?.commits || [];
10
+ const authorMap = new Map();
11
+ const dayHourMatrix = Array.from({ length: 7 }, () => Array(24).fill(0));
12
+ const dayCounts = new Map();
13
+
14
+ // Process commit dates & authors
15
+ for (const c of commits) {
16
+ const { key, name } = authorIdentity(c.author);
17
+ if (!authorMap.has(key)) {
18
+ authorMap.set(key, { name, commits: 0, additions: 0, deletions: 0, firstDate: c.date, lastDate: c.date, files: new Set() });
19
+ }
20
+ const a = authorMap.get(key);
21
+ a.commits++;
22
+ if (c.date < a.firstDate) a.firstDate = c.date;
23
+ if (c.date > a.lastDate) a.lastDate = c.date;
24
+
25
+ const d = new Date(c.date || Date.now());
26
+ if (!isNaN(d.getTime())) {
27
+ const day = d.getDay(); // 0 = Sun, 6 = Sat
28
+ const hour = d.getHours();
29
+ dayHourMatrix[day][hour]++;
30
+
31
+ const ymd = d.toISOString().slice(0, 10);
32
+ dayCounts.set(ymd, (dayCounts.get(ymd) || 0) + 1);
33
+ }
34
+ }
35
+
36
+ const sortedAuthors = [...authorMap.values()].sort((a, b) => b.commits - a.commits);
37
+ const totalCommits = commits.length || 1;
38
+
39
+ container.innerHTML = `
40
+ <div class="insights-layout">
41
+ <section class="insights-hero">
42
+ <div class="insights-stat-card">
43
+ <span class="isc-num">${commits.length}</span>
44
+ <span class="isc-lbl">Total Commits</span>
45
+ </div>
46
+ <div class="insights-stat-card">
47
+ <span class="isc-num">${sortedAuthors.length}</span>
48
+ <span class="isc-lbl">Contributors</span>
49
+ </div>
50
+ <div class="insights-stat-card">
51
+ <span class="isc-num">${scan.files?.length || 0}</span>
52
+ <span class="isc-lbl">Tracked Files</span>
53
+ </div>
54
+ <div class="insights-stat-card">
55
+ <span class="isc-num">${scan.stats?.edgeCount || 0}</span>
56
+ <span class="isc-lbl">Graph Dependencies</span>
57
+ </div>
58
+ </section>
59
+
60
+ <!-- 52-Week Contribution Calendar Heatmap -->
61
+ <section class="insights-section">
62
+ <div class="section-head">
63
+ <h3>Contribution Activity</h3>
64
+ <span class="section-sub">Commit frequency over the past year</span>
65
+ </div>
66
+ <div class="heatmap-card">
67
+ ${renderCalendarHeatmap(dayCounts)}
68
+ </div>
69
+ </section>
70
+
71
+ <div class="insights-grid-2">
72
+ <!-- Contributor Leaderboard -->
73
+ <section class="insights-section">
74
+ <div class="section-head">
75
+ <h3>Top Contributors</h3>
76
+ <span class="section-sub">Ranked by total commit contributions</span>
77
+ </div>
78
+ <div class="contrib-list">
79
+ ${sortedAuthors.slice(0, 10).map((a, idx) => `
80
+ <div class="contrib-item">
81
+ <div class="contrib-rank">#${idx + 1}</div>
82
+ <div class="contrib-avatar">${escapeHtml(a.name.slice(0, 2).toUpperCase())}</div>
83
+ <div class="contrib-info">
84
+ <div class="contrib-name">${escapeHtml(a.name)}</div>
85
+ <div class="contrib-meta">${a.commits} commit${a.commits === 1 ? '' : 's'} (${Math.round((a.commits / totalCommits) * 100)}%)</div>
86
+ </div>
87
+ <div class="contrib-bar-wrap">
88
+ <div class="contrib-bar" style="width: ${Math.round((a.commits / totalCommits) * 100)}%"></div>
89
+ </div>
90
+ </div>
91
+ `).join('') || '<p class="insights-empty">No git history available</p>'}
92
+ </div>
93
+ </section>
94
+
95
+ <!-- Commit Punchcard -->
96
+ <section class="insights-section">
97
+ <div class="section-head">
98
+ <h3>Commit Punch Card</h3>
99
+ <span class="section-sub">Activity by day of week &amp; time of day</span>
100
+ </div>
101
+ <div class="punchcard-card">
102
+ ${renderPunchcardSvg(dayHourMatrix)}
103
+ </div>
104
+ </section>
105
+ </div>
106
+ </div>
107
+ `;
108
+ }
109
+
110
+ // gitHistory shapes commit.author as { name, email }; a bare string is also
111
+ // tolerated so older cached payloads don't break the view. Commits are grouped
112
+ // by email when present — the same person with two name spellings is one
113
+ // contributor, like shared/analyzer/history.js does server-side.
114
+ function authorIdentity(author) {
115
+ if (author && typeof author === 'object') {
116
+ const name = author.name || author.email || 'Anonymous';
117
+ return { key: author.email || name, name };
118
+ }
119
+ const name = author || 'Anonymous';
120
+ return { key: name, name };
121
+ }
122
+
123
+
124
+ function renderCalendarHeatmap(dayCounts) {
125
+ const weeks = 52;
126
+ const daysPerWeek = 7;
127
+ const cellSize = 12;
128
+ const cellGap = 3;
129
+
130
+ const now = new Date();
131
+ const cells = [];
132
+ const oneDayMs = 24 * 60 * 60 * 1000;
133
+
134
+ // Align to end on current day
135
+ const endDay = new Date(now);
136
+ const startDay = new Date(endDay.getTime() - (weeks * 7) * oneDayMs);
137
+
138
+ let maxCount = 1;
139
+ for (const c of dayCounts.values()) if (c > maxCount) maxCount = c;
140
+
141
+ const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
142
+
143
+ for (let w = 0; w < weeks; w++) {
144
+ for (let d = 0; d < 7; d++) {
145
+ const cur = new Date(startDay.getTime() + (w * 7 + d) * oneDayMs);
146
+ const ymd = cur.toISOString().slice(0, 10);
147
+ const count = dayCounts.get(ymd) || 0;
148
+ let level = 0;
149
+ if (count > 0) level = Math.min(4, Math.ceil((count / maxCount) * 4));
150
+
151
+ const x = w * (cellSize + cellGap) + 30;
152
+ const y = d * (cellSize + cellGap) + 20;
153
+
154
+ cells.push(`
155
+ <rect class="cal-cell lvl-${level}" x="${x}" y="${y}" width="${cellSize}" height="${cellSize}" rx="2">
156
+ <title>${ymd}: ${count} commit${count === 1 ? '' : 's'}</title>
157
+ </rect>
158
+ `);
159
+ }
160
+ }
161
+
162
+ const svgWidth = weeks * (cellSize + cellGap) + 40;
163
+ const svgHeight = 7 * (cellSize + cellGap) + 30;
164
+
165
+ return `
166
+ <div class="cal-scroll-wrap">
167
+ <svg class="cal-heatmap-svg" viewBox="0 0 ${svgWidth} ${svgHeight}" style="max-width: 100%; height: auto;">
168
+ <text class="cal-label" x="5" y="42">Mon</text>
169
+ <text class="cal-label" x="5" y="72">Wed</text>
170
+ <text class="cal-label" x="5" y="102">Fri</text>
171
+ ${cells.join('')}
172
+ </svg>
173
+ <div class="cal-legend">
174
+ <span>Less</span>
175
+ <span class="cal-cell-legend lvl-0"></span>
176
+ <span class="cal-cell-legend lvl-1"></span>
177
+ <span class="cal-cell-legend lvl-2"></span>
178
+ <span class="cal-cell-legend lvl-3"></span>
179
+ <span class="cal-cell-legend lvl-4"></span>
180
+ <span>More</span>
181
+ </div>
182
+ </div>
183
+ `;
184
+ }
185
+
186
+ function renderPunchcardSvg(matrix) {
187
+ const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
188
+ const cellW = 20;
189
+ const cellH = 20;
190
+ const padLeft = 40;
191
+ const padTop = 20;
192
+ let max = 1;
193
+
194
+ for (let d = 0; d < 7; d++) {
195
+ for (let h = 0; h < 24; h++) {
196
+ if (matrix[d][h] > max) max = matrix[d][h];
197
+ }
198
+ }
199
+
200
+ const dots = [];
201
+ for (let d = 0; d < 7; d++) {
202
+ for (let h = 0; h < 24; h++) {
203
+ const val = matrix[d][h];
204
+ if (val === 0) continue;
205
+ const r = Math.max(2, Math.min(8, (val / max) * 8));
206
+ const cx = padLeft + h * cellW + cellW / 2;
207
+ const cy = padTop + d * cellH + cellH / 2;
208
+ dots.push(`
209
+ <circle cx="${cx}" cy="${cy}" r="${r}" class="punch-dot">
210
+ <title>${days[d]} ${h}:00 - ${val} commit${val === 1 ? '' : 's'}</title>
211
+ </circle>
212
+ `);
213
+ }
214
+ }
215
+
216
+ const hoursHeader = [0, 4, 8, 12, 16, 20].map((h) => `
217
+ <text class="punch-lbl" x="${padLeft + h * cellW + cellW / 2}" y="12" text-anchor="middle">${h}h</text>
218
+ `).join('');
219
+
220
+ const daysHeader = days.map((day, idx) => `
221
+ <text class="punch-lbl" x="5" y="${padTop + idx * cellH + cellH / 2 + 4}">${day}</text>
222
+ `).join('');
223
+
224
+ const svgW = padLeft + 24 * cellW + 10;
225
+ const svgH = padTop + 7 * cellH + 10;
226
+
227
+ return `
228
+ <div class="cal-scroll-wrap">
229
+ <svg class="punch-svg" viewBox="0 0 ${svgW} ${svgH}">
230
+ ${hoursHeader}
231
+ ${daysHeader}
232
+ ${dots.join('')}
233
+ </svg>
234
+ </div>
235
+ `;
236
+ }