headlesscode 1.0.2

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 (232) hide show
  1. package/ATTRIBUTION.md +53 -0
  2. package/CODE_OF_CONDUCT.md +130 -0
  3. package/CONTRIBUTING.md +107 -0
  4. package/LICENSE +202 -0
  5. package/README.md +486 -0
  6. package/SECURITY.md +211 -0
  7. package/bin/headlesscode.mjs +83 -0
  8. package/package.json +63 -0
  9. package/shared/prompts/review-mode-prompt-short.md +93 -0
  10. package/shared/prompts/review-mode-prompt.md +281 -0
  11. package/shared/rules-code/rules.md +22 -0
  12. package/shared/stacks/cpp/rules.md +30 -0
  13. package/shared/stacks/fastapi/rules.md +30 -0
  14. package/shared/stacks/javascript/rules.md +37 -0
  15. package/shared/stacks/postgresql/rules.md +31 -0
  16. package/shared/stacks/python/rules.md +35 -0
  17. package/shared/stacks/react/rules.md +11 -0
  18. package/shared/stacks/typescript/rules.md +10 -0
  19. package/src/budget/budget.ts +221 -0
  20. package/src/budget/concurrency.ts +126 -0
  21. package/src/budget/cost.ts +309 -0
  22. package/src/budget/index.ts +8 -0
  23. package/src/checkpoints/cli.ts +256 -0
  24. package/src/checkpoints/service.ts +227 -0
  25. package/src/cli.ts +1535 -0
  26. package/src/cloud/docker-provider.ts +334 -0
  27. package/src/cloud/provider.ts +300 -0
  28. package/src/codeintel/call-graph.ts +78 -0
  29. package/src/codeintel/find-references.ts +123 -0
  30. package/src/codeintel/go-to-definition.ts +193 -0
  31. package/src/codeintel/handlers.ts +190 -0
  32. package/src/codeintel/import-graph.ts +173 -0
  33. package/src/codeintel/outline.ts +180 -0
  34. package/src/codeintel/position.ts +77 -0
  35. package/src/codeintel/program.ts +350 -0
  36. package/src/codeintel/rename-symbol.ts +213 -0
  37. package/src/codeintel/tools.ts +280 -0
  38. package/src/codemap/build.ts +135 -0
  39. package/src/codemap/cli.ts +190 -0
  40. package/src/codemap/extract.ts +339 -0
  41. package/src/codemap/files.ts +236 -0
  42. package/src/codemap/fingerprint.ts +65 -0
  43. package/src/codemap/flows.ts +62 -0
  44. package/src/codemap/html.ts +451 -0
  45. package/src/codemap/lock.ts +80 -0
  46. package/src/codemap/types.ts +101 -0
  47. package/src/codesearch/airunner-embedder.ts +185 -0
  48. package/src/codesearch/chunk.ts +339 -0
  49. package/src/codesearch/cli.ts +223 -0
  50. package/src/codesearch/embedder.ts +332 -0
  51. package/src/codesearch/files.ts +280 -0
  52. package/src/codesearch/index.ts +469 -0
  53. package/src/codesearch/ollama-embedder.ts +205 -0
  54. package/src/codesearch/search.ts +141 -0
  55. package/src/codesearch/types.ts +100 -0
  56. package/src/config/mode-models.ts +218 -0
  57. package/src/dashboard/aggregate.ts +364 -0
  58. package/src/dashboard/chat-thread.ts +141 -0
  59. package/src/dashboard/checkpoints.ts +124 -0
  60. package/src/dashboard/cli.ts +193 -0
  61. package/src/dashboard/codemap.ts +44 -0
  62. package/src/dashboard/files.ts +121 -0
  63. package/src/dashboard/page.ts +2803 -0
  64. package/src/dashboard/self-improvement-metrics.ts +282 -0
  65. package/src/dashboard/server.ts +1103 -0
  66. package/src/dashboard/session-launch.ts +310 -0
  67. package/src/dashboard/timeline.ts +273 -0
  68. package/src/dashboard/tool-exec.ts +107 -0
  69. package/src/dashboard/trend-cli.ts +141 -0
  70. package/src/dashboard/trend.ts +413 -0
  71. package/src/decision-proxy/cli.ts +261 -0
  72. package/src/decision-proxy/proxy.ts +569 -0
  73. package/src/deploy/gate-cli.ts +147 -0
  74. package/src/deploy/gate.ts +254 -0
  75. package/src/engine/condense.ts +512 -0
  76. package/src/engine/events.ts +428 -0
  77. package/src/engine/handoff.ts +71 -0
  78. package/src/engine/lazy-tools.ts +160 -0
  79. package/src/engine/local-explore.ts +653 -0
  80. package/src/engine/logger.ts +96 -0
  81. package/src/engine/loop.ts +5517 -0
  82. package/src/engine/parser.ts +347 -0
  83. package/src/engine/prompt.ts +860 -0
  84. package/src/engine/reports.ts +47 -0
  85. package/src/engine/stacks.ts +448 -0
  86. package/src/engine/types.ts +291 -0
  87. package/src/engine/usage.ts +186 -0
  88. package/src/github/app-auth.ts +161 -0
  89. package/src/github/cli.ts +448 -0
  90. package/src/github/installations.ts +133 -0
  91. package/src/github/pr.ts +321 -0
  92. package/src/github/provision.ts +118 -0
  93. package/src/github/push.ts +122 -0
  94. package/src/index-util.ts +50 -0
  95. package/src/index.ts +81 -0
  96. package/src/init/cli.ts +248 -0
  97. package/src/init/gitignore.ts +74 -0
  98. package/src/llm/ollama.ts +308 -0
  99. package/src/llm/openrouter.ts +868 -0
  100. package/src/llm/preflight.ts +367 -0
  101. package/src/llm/transcript-capture.ts +84 -0
  102. package/src/memory/embed.ts +110 -0
  103. package/src/memory/index.ts +22 -0
  104. package/src/memory/local.ts +259 -0
  105. package/src/memory/summarizer.ts +283 -0
  106. package/src/memory/types.ts +153 -0
  107. package/src/memory/uwuchat.ts +157 -0
  108. package/src/migrate/cli.ts +115 -0
  109. package/src/orchestrator/analyze-cli.ts +104 -0
  110. package/src/orchestrator/auto-split.ts +206 -0
  111. package/src/orchestrator/cleanup.ts +1003 -0
  112. package/src/orchestrator/cli.ts +3571 -0
  113. package/src/orchestrator/cost-estimate.ts +564 -0
  114. package/src/orchestrator/cost-history-cli.ts +242 -0
  115. package/src/orchestrator/cost-history.ts +397 -0
  116. package/src/orchestrator/git-sync.ts +250 -0
  117. package/src/orchestrator/index.ts +153 -0
  118. package/src/orchestrator/log-analysis.ts +0 -0
  119. package/src/orchestrator/merge-check.ts +108 -0
  120. package/src/orchestrator/pipeline.ts +411 -0
  121. package/src/orchestrator/resume.ts +1940 -0
  122. package/src/orchestrator/reviewer.ts +503 -0
  123. package/src/orchestrator/split.ts +296 -0
  124. package/src/orchestrator/state.ts +542 -0
  125. package/src/orchestrator/status.ts +697 -0
  126. package/src/orchestrator/verification-gate.ts +134 -0
  127. package/src/orchestrator/watch.ts +898 -0
  128. package/src/permissions/commands.ts +1083 -0
  129. package/src/permissions/config.ts +241 -0
  130. package/src/permissions/index.ts +12 -0
  131. package/src/permissions/protected-files.ts +96 -0
  132. package/src/permissions/store-protection.ts +272 -0
  133. package/src/project-store.ts +648 -0
  134. package/src/projects/cli.ts +382 -0
  135. package/src/qa/qa.ts +487 -0
  136. package/src/tools/browser/handler.ts +346 -0
  137. package/src/tools/browser/service.ts +406 -0
  138. package/src/tools/browser/smoke.ts +78 -0
  139. package/src/tools/browser/tool.ts +99 -0
  140. package/src/tools/executor.ts +2575 -0
  141. package/src/tools/language-detect.ts +183 -0
  142. package/src/tools/output-summarizer.ts +369 -0
  143. package/src/tools/run-tests.ts +302 -0
  144. package/src/tools/set-indentation-tool.ts +49 -0
  145. package/src/tools/test-selection.ts +160 -0
  146. package/src/vendor/tests/smoke.ts +103 -0
  147. package/src/vendor/zoo-code/VENDOR-NOTES.md +213 -0
  148. package/src/vendor/zoo-code/shim/anthropic.ts +71 -0
  149. package/src/vendor/zoo-code/shim/openai.d.ts +60 -0
  150. package/src/vendor/zoo-code/shim/os-name.ts +18 -0
  151. package/src/vendor/zoo-code/shim/strip-bom.ts +14 -0
  152. package/src/vendor/zoo-code/shim/vscode.ts +76 -0
  153. package/src/vendor/zoo-code/src/core/config/CustomModesManager.ts +1015 -0
  154. package/src/vendor/zoo-code/src/core/diff/strategies/multi-search-replace.ts +670 -0
  155. package/src/vendor/zoo-code/src/core/prompts/sections/capabilities.ts +46 -0
  156. package/src/vendor/zoo-code/src/core/prompts/sections/custom-instructions.ts +559 -0
  157. package/src/vendor/zoo-code/src/core/prompts/sections/index.ts +10 -0
  158. package/src/vendor/zoo-code/src/core/prompts/sections/markdown-formatting.ts +7 -0
  159. package/src/vendor/zoo-code/src/core/prompts/sections/modes.ts +35 -0
  160. package/src/vendor/zoo-code/src/core/prompts/sections/objective.ts +13 -0
  161. package/src/vendor/zoo-code/src/core/prompts/sections/rules.ts +95 -0
  162. package/src/vendor/zoo-code/src/core/prompts/sections/skills.ts +105 -0
  163. package/src/vendor/zoo-code/src/core/prompts/sections/system-info.ts +30 -0
  164. package/src/vendor/zoo-code/src/core/prompts/sections/tool-use-guidelines.ts +9 -0
  165. package/src/vendor/zoo-code/src/core/prompts/sections/tool-use.ts +7 -0
  166. package/src/vendor/zoo-code/src/core/prompts/system.ts +176 -0
  167. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/access_mcp_resource.ts +41 -0
  168. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_diff.ts +40 -0
  169. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_patch.ts +61 -0
  170. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/ask_followup_question.ts +62 -0
  171. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/attempt_completion.ts +33 -0
  172. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/codebase_search.ts +43 -0
  173. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/converters.ts +109 -0
  174. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit.ts +48 -0
  175. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit_file.ts +72 -0
  176. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/execute_command.ts +54 -0
  177. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/generate_image.ts +51 -0
  178. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/index.ts +75 -0
  179. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/list_files.ts +41 -0
  180. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/mcp_server.ts +75 -0
  181. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/new_task.ts +39 -0
  182. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_command_output.ts +81 -0
  183. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_file.ts +169 -0
  184. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/run_slash_command.ts +31 -0
  185. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_files.ts +50 -0
  186. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_replace.ts +51 -0
  187. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/skill.ts +33 -0
  188. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/switch_mode.ts +31 -0
  189. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/update_todo_list.ts +54 -0
  190. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/write_to_file.ts +40 -0
  191. package/src/vendor/zoo-code/src/core/prompts/types.ts +12 -0
  192. package/src/vendor/zoo-code/src/i18n/index.ts +19 -0
  193. package/src/vendor/zoo-code/src/integrations/misc/extract-text.ts +81 -0
  194. package/src/vendor/zoo-code/src/services/checkpoints/RepoPerTaskCheckpointService.ts +15 -0
  195. package/src/vendor/zoo-code/src/services/checkpoints/ShadowCheckpointService.ts +553 -0
  196. package/src/vendor/zoo-code/src/services/checkpoints/excludes.ts +212 -0
  197. package/src/vendor/zoo-code/src/services/checkpoints/index.ts +3 -0
  198. package/src/vendor/zoo-code/src/services/checkpoints/types.ts +35 -0
  199. package/src/vendor/zoo-code/src/services/code-index/manager.ts +19 -0
  200. package/src/vendor/zoo-code/src/services/mcp/McpHub.ts +36 -0
  201. package/src/vendor/zoo-code/src/services/roo-config/index.ts +441 -0
  202. package/src/vendor/zoo-code/src/services/search/file-search.ts +143 -0
  203. package/src/vendor/zoo-code/src/services/skills/SkillsManager.ts +20 -0
  204. package/src/vendor/zoo-code/src/shared/globalFileNames.ts +9 -0
  205. package/src/vendor/zoo-code/src/shared/language.ts +43 -0
  206. package/src/vendor/zoo-code/src/shared/modes.ts +257 -0
  207. package/src/vendor/zoo-code/src/shared/tools.ts +385 -0
  208. package/src/vendor/zoo-code/src/utils/fs.ts +39 -0
  209. package/src/vendor/zoo-code/src/utils/globalContext.ts +22 -0
  210. package/src/vendor/zoo-code/src/utils/json-schema.ts +16 -0
  211. package/src/vendor/zoo-code/src/utils/logging.ts +21 -0
  212. package/src/vendor/zoo-code/src/utils/mcp-name.ts +190 -0
  213. package/src/vendor/zoo-code/src/utils/object.ts +18 -0
  214. package/src/vendor/zoo-code/src/utils/path.ts +94 -0
  215. package/src/vendor/zoo-code/src/utils/shell.ts +376 -0
  216. package/src/vendor/zoo-code/src/utils/text-normalization.ts +99 -0
  217. package/src/vendor/zoo-code/types/global-settings.ts +19 -0
  218. package/src/vendor/zoo-code/types/index.ts +22 -0
  219. package/src/vendor/zoo-code/types/message.ts +375 -0
  220. package/src/vendor/zoo-code/types/mode.ts +241 -0
  221. package/src/vendor/zoo-code/types/todo.ts +19 -0
  222. package/src/vendor/zoo-code/types/tool-params.ts +116 -0
  223. package/src/vendor/zoo-code/types/tool.ts +67 -0
  224. package/src/vendor/zoo-code/types/vscode.ts +84 -0
  225. package/src/vision/describe.ts +242 -0
  226. package/src/vision/tool.ts +91 -0
  227. package/src/watcher/cli.ts +369 -0
  228. package/src/watcher/github.ts +304 -0
  229. package/src/watcher/index.ts +59 -0
  230. package/src/watcher/state.ts +254 -0
  231. package/src/watcher/watch.ts +562 -0
  232. package/tsconfig.json +18 -0
@@ -0,0 +1,451 @@
1
+ /**
2
+ * Self-contained interactive HTML visualizer for the codemap.
3
+ *
4
+ * Produces ONE html file with inline CSS + vanilla JS and the codemap JSON
5
+ * embedded — no external CDN, no server required: the acceptance criteria
6
+ * say the map must open standalone in a browser with working
7
+ * click-to-highlight and search. Layout is deterministic (BFS layers from
8
+ * root modules, order within a layer by path), so the same repo always
9
+ * renders the same picture.
10
+ *
11
+ * The inline JS deliberately avoids the browser's `</script>` sequence via
12
+ * JSON escaping, keeps all state in plain module-scope variables, and is
13
+ * dependency-free.
14
+ */
15
+
16
+ import type { Codemap } from "./types.js"
17
+
18
+ /** Escape the embedded JSON so it can never terminate the <script> tag. */
19
+ function safeJson(value: Codemap): string {
20
+ return JSON.stringify(value).replace(/</g, "\\u003c")
21
+ }
22
+
23
+ export function renderCodemapHtml(codemap: Codemap): string {
24
+ const json = safeJson(codemap)
25
+ return `<!DOCTYPE html>
26
+ <html lang="en">
27
+ <head>
28
+ <meta charset="utf-8">
29
+ <meta name="viewport" content="width=device-width, initial-scale=1">
30
+ <title>codemap — ${codemap.project}</title>
31
+ <style>
32
+ :root { color-scheme: dark; }
33
+ * { box-sizing: border-box; }
34
+ body {
35
+ margin: 0; background: #0f1115; color: #d6d9df;
36
+ font: 13px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
37
+ height: 100vh; display: flex; flex-direction: column; overflow: hidden;
38
+ }
39
+ header {
40
+ display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
41
+ padding: 10px 16px; background: #161a22; border-bottom: 1px solid #232936;
42
+ }
43
+ header h1 { font-size: 15px; margin: 0; color: #eef1f6; font-weight: 600; }
44
+ header .meta { color: #8b93a3; font-size: 12px; }
45
+ #search {
46
+ flex: 1; min-width: 200px; max-width: 420px; background: #0f1115;
47
+ color: #d6d9df; border: 1px solid #2c3442; border-radius: 6px;
48
+ padding: 6px 10px; outline: none; font-size: 12px;
49
+ }
50
+ #search:focus { border-color: #5b8def; }
51
+ button {
52
+ background: #1d2430; color: #d6d9df; border: 1px solid #2c3442;
53
+ border-radius: 6px; padding: 5px 10px; cursor: pointer; font-size: 12px;
54
+ }
55
+ button:hover { background: #242d3d; }
56
+ .legend { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; font-size: 11px; color: #aab2c0; }
57
+ .legend .swatch { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 4px; vertical-align: -1px; }
58
+ main { flex: 1; display: flex; min-height: 0; }
59
+ #graph { flex: 1; position: relative; min-width: 0; }
60
+ #graph svg { width: 100%; height: 100%; display: block; cursor: grab; }
61
+ #graph svg.dragging { cursor: grabbing; }
62
+ #hint {
63
+ position: absolute; left: 14px; bottom: 10px; color: #6b7280; font-size: 11px;
64
+ pointer-events: none; user-select: none;
65
+ }
66
+ .node { cursor: pointer; }
67
+ .node circle { stroke: #0f1115; stroke-width: 1.5; }
68
+ .node text { fill: #9aa3b2; font-size: 10px; pointer-events: none; }
69
+ .node.dim text { fill: #4a5261; }
70
+ .node.dim circle { opacity: 0.25; }
71
+ .node.highlight circle { stroke: #fff; stroke-width: 2; }
72
+ .edge { stroke: #3b4352; stroke-width: 1; }
73
+ .edge.include { stroke: #6b4f2a; }
74
+ .edge.call { stroke: #4fd08e; stroke-dasharray: 5 4; }
75
+ .edge.dim { opacity: 0.08; }
76
+ .edge.highlight { stroke: #5b8def; stroke-width: 1.6; }
77
+ .edge.highlight.include { stroke: #e2a14f; }
78
+ .edge.highlight.call { stroke: #4fd08e; }
79
+ #detail {
80
+ width: 300px; border-left: 1px solid #232936; background: #141821;
81
+ display: none; flex-direction: column; overflow-y: auto; flex-shrink: 0;
82
+ }
83
+ #detail.open { display: flex; }
84
+ #detail h2 { font-size: 12px; margin: 0; padding: 12px 14px 8px; color: #eef1f6; border-bottom: 1px solid #232936; position: sticky; top: 0; background: #141821; }
85
+ #detail .close { float: right; cursor: pointer; color: #8b93a3; }
86
+ #detail .body { padding: 12px 14px; }
87
+ #detail .kv { display: flex; gap: 8px; margin: 3px 0; }
88
+ #detail .kv .k { color: #8b93a3; width: 84px; flex-shrink: 0; }
89
+ #detail .kv .v { color: #d6d9df; word-break: break-all; }
90
+ #detail h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: #8b93a3; margin: 14px 0 6px; }
91
+ #detail ul { margin: 0; padding-left: 2px; list-style: none; }
92
+ #detail li { padding: 2px 0; }
93
+ #detail li a { color: #5b8def; cursor: pointer; text-decoration: none; }
94
+ #detail li a:hover { text-decoration: underline; }
95
+ #detail .empty { color: #6b7280; font-style: italic; }
96
+ .badge { display: inline-block; padding: 1px 7px; border-radius: 9px; font-size: 10px; color: #0f1115; }
97
+ </style>
98
+ </head>
99
+ <body>
100
+ <header>
101
+ <h1>codemap — <span id="project"></span></h1>
102
+ <span class="meta" id="meta"></span>
103
+ <input id="search" type="search" placeholder="Filter modules by path… (e.g. engine/loop)">
104
+ <button id="btnFit" title="Reset zoom &amp; pan">⌂ fit</button>
105
+ <button id="btnZoomIn" title="Zoom in">+</button>
106
+ <button id="btnZoomOut" title="Zoom out">−</button>
107
+ <span class="legend">
108
+ <span><span class="swatch" style="background:#5b8def"></span>source</span>
109
+ <span><span class="swatch" style="background:#46c17b"></span>test</span>
110
+ <span><span class="swatch" style="background:#22d3ee"></span>entrypoint</span>
111
+ <span><span class="swatch" style="background:#f5a524"></span>config</span>
112
+ <span><span class="swatch" style="background:#a78bfa"></span>vendor</span>
113
+ <span><span class="swatch" style="background:#6b7280"></span>generated</span>
114
+ <span><span class="swatch" style="background:#5b8def; width:0; height:0"></span><span style="color:#6b7280">— import</span></span>
115
+ <span><span class="swatch" style="background:#e2a14f; width:0; height:0"></span><span style="color:#8b6b3a">— include</span></span>
116
+ <span><span class="swatch" style="background:#4fd08e; width:0; height:0"></span><span style="color:#7fbf9a">-- call</span></span>
117
+ </span>
118
+ </header>
119
+ <main>
120
+ <div id="graph">
121
+ <svg id="svg"></svg>
122
+ <div id="hint">scroll to zoom · drag to pan · click a node to highlight its imports, calls and importers</div>
123
+ </div>
124
+ <aside id="detail">
125
+ <h2><span id="detailTitle">Module</span><span class="close" id="detailClose">✕</span></h2>
126
+ <div class="body" id="detailBody"></div>
127
+ </aside>
128
+ </main>
129
+ <script>
130
+ "use strict";
131
+ const CODEMAP = ${json};
132
+
133
+ const ROLE_COLORS = {
134
+ source: "#5b8def", test: "#46c17b", entrypoint: "#22d3ee",
135
+ config: "#f5a524", vendor: "#a78bfa", generated: "#6b7280"
136
+ };
137
+ function edgeClass(kind) {
138
+ return kind === "include" ? "edge include" : kind === "call" ? "edge call" : "edge";
139
+ }
140
+
141
+ // ── deterministic layout: BFS layers from root modules ─────────────────────
142
+ function layout(codemap) {
143
+ const modules = codemap.modules;
144
+ const byPath = new Map(modules.map((m, i) => [m.path, i]));
145
+ const out = modules.map(() => []);
146
+ const inc = modules.map(() => 0);
147
+ for (const e of codemap.edges) {
148
+ const fi = byPath.get(e.from);
149
+ const ti = byPath.get(e.to);
150
+ if (fi === undefined || ti === undefined) continue;
151
+ out[fi].push(ti);
152
+ inc[ti]++;
153
+ }
154
+ const layer = modules.map(() => -1);
155
+ const queue = [];
156
+ modules.forEach((m, i) => { if (inc[i] === 0) { layer[i] = 0; queue.push(i); } });
157
+ let maxLayer = 0;
158
+ let head = 0;
159
+ while (head < queue.length) {
160
+ const i = queue[head++];
161
+ maxLayer = Math.max(maxLayer, layer[i]);
162
+ for (const j of out[i]) {
163
+ if (layer[j] === -1) { layer[j] = layer[i] + 1; queue.push(j); }
164
+ }
165
+ }
166
+ // Anything unreachable from roots (a pure cycle) sits at the deepest layer.
167
+ modules.forEach((m, i) => { if (layer[i] === -1) layer[i] = maxLayer + 1; });
168
+ maxLayer = Math.max(...layer);
169
+
170
+ const perLayer = new Map();
171
+ modules.forEach((m, i) => {
172
+ const l = layer[i];
173
+ if (!perLayer.has(l)) perLayer.set(l, []);
174
+ perLayer.get(l).push(i);
175
+ });
176
+ // Fixed logical canvas width: spreading every layer across its own width
177
+ // makes the widest layer set the canvas size, which fit() then shrinks to
178
+ // sub-pixel dots — a "blank" graph. A fixed width keeps the initial fit
179
+ // legible; over-dense layers simply overlap and the zoom/search reveal
180
+ // them (deliberate v1 tradeoff).
181
+ const W = 2400;
182
+ const ROW = 110;
183
+ const H = (maxLayer + 1) * ROW + 80;
184
+ const positions = modules.map(() => ({ x: 0, y: 0 }));
185
+ for (const [l, arr] of perLayer) {
186
+ const span = W - 120;
187
+ const step = arr.length > 1 ? span / (arr.length - 1) : 0;
188
+ arr.forEach((i, k) => {
189
+ positions[i] = { x: 60 + k * step, y: 60 + l * ROW };
190
+ });
191
+ }
192
+ return { W, H, positions, layer };
193
+ }
194
+
195
+ // ── render ─────────────────────────────────────────────────────────────────
196
+ const svg = document.getElementById("svg");
197
+ const NS = "http://www.w3.org/2000/svg";
198
+ const { W, H, positions, layer } = layout(CODEMAP);
199
+ svg.setAttribute("viewBox", "0 0 " + W + " " + H);
200
+ const viewport = document.createElementNS(NS, "g");
201
+ viewport.setAttribute("id", "viewport");
202
+ svg.appendChild(viewport);
203
+
204
+ const byPath = new Map(CODEMAP.modules.map((m, i) => [m.path, i]));
205
+ const nodeEls = [];
206
+ const labelEls = [];
207
+ const edgeEls = [];
208
+ const incoming = CODEMAP.modules.map(() => []);
209
+ const outgoing = CODEMAP.modules.map(() => []);
210
+ const importIn = CODEMAP.modules.map(() => []);
211
+ const importOut = CODEMAP.modules.map(() => []);
212
+ const callIn = CODEMAP.modules.map(() => []);
213
+ const callOut = CODEMAP.modules.map(() => []);
214
+
215
+ for (const e of CODEMAP.edges) {
216
+ const fi = byPath.get(e.from);
217
+ const ti = byPath.get(e.to);
218
+ if (fi === undefined || ti === undefined) continue;
219
+ outgoing[fi].push(ti);
220
+ incoming[ti].push(fi);
221
+ if (e.kind === "call") {
222
+ callOut[fi].push(ti);
223
+ callIn[ti].push(fi);
224
+ } else {
225
+ importOut[fi].push(ti);
226
+ importIn[ti].push(fi);
227
+ }
228
+ }
229
+
230
+ // edges first (under nodes)
231
+ for (const e of CODEMAP.edges) {
232
+ const fi = byPath.get(e.from);
233
+ const ti = byPath.get(e.to);
234
+ if (fi === undefined || ti === undefined) continue;
235
+ const a = positions[fi];
236
+ const b = positions[ti];
237
+ const line = document.createElementNS(NS, "line");
238
+ line.setAttribute("x1", a.x); line.setAttribute("y1", a.y);
239
+ line.setAttribute("x2", b.x); line.setAttribute("y2", b.y);
240
+ line.setAttribute("class", edgeClass(e.kind));
241
+ line.setAttribute("data-kind", e.kind);
242
+ line.setAttribute("data-from", e.from);
243
+ line.setAttribute("data-to", e.to);
244
+ viewport.appendChild(line);
245
+ edgeEls.push(line);
246
+ }
247
+
248
+ // nodes
249
+ CODEMAP.modules.forEach((m, i) => {
250
+ const g = document.createElementNS(NS, "g");
251
+ g.setAttribute("class", "node");
252
+ g.setAttribute("data-path", m.path);
253
+ g.setAttribute("transform", "translate(" + positions[i].x + "," + positions[i].y + ")");
254
+ const circle = document.createElementNS(NS, "circle");
255
+ circle.setAttribute("r", 7);
256
+ circle.setAttribute("fill", ROLE_COLORS[m.role] || "#5b8def");
257
+ const label = document.createElementNS(NS, "text");
258
+ label.setAttribute("x", 10);
259
+ label.setAttribute("y", 3);
260
+ label.textContent = m.path.split("/").pop() || m.path;
261
+ const title = document.createElementNS(NS, "title");
262
+ title.textContent = m.path + " [" + m.role + "]";
263
+ g.appendChild(circle);
264
+ g.appendChild(label);
265
+ g.appendChild(title);
266
+ g.addEventListener("click", (ev) => { ev.stopPropagation(); selectNode(i); });
267
+ viewport.appendChild(g);
268
+ nodeEls.push(g);
269
+ labelEls.push(label);
270
+ });
271
+
272
+ // ── zoom / pan ─────────────────────────────────────────────────────────────
273
+ let k = 1, tx = 0, ty = 0, dragging = false, lastX = 0, lastY = 0;
274
+ // Labels only pay for themselves when zoomed in far enough to read them;
275
+ // below that they are pure clutter (esp. on 2k+ node repos).
276
+ const LABEL_MIN_SCALE = 0.5;
277
+ function applyTransform() {
278
+ viewport.setAttribute("transform", "translate(" + tx + "," + ty + ") scale(" + k + ")");
279
+ const show = k >= LABEL_MIN_SCALE;
280
+ labelEls.forEach((el) => { el.style.display = show ? "" : "none"; });
281
+ }
282
+ function fit() {
283
+ const rect = svg.getBoundingClientRect();
284
+ k = Math.min(rect.width / W, rect.height / H);
285
+ // Fit-to-tiny is worse than no fit: never shrink below legibility.
286
+ k = Math.max(0.4, Math.min(k, 1));
287
+ tx = (rect.width - W * k) / 2;
288
+ ty = (rect.height - H * k) / 2;
289
+ applyTransform();
290
+ }
291
+ svg.addEventListener("wheel", (ev) => {
292
+ ev.preventDefault();
293
+ const rect = svg.getBoundingClientRect();
294
+ const mx = ev.clientX - rect.left, my = ev.clientY - rect.top;
295
+ const factor = ev.deltaY < 0 ? 1.15 : 1 / 1.15;
296
+ const nk = Math.max(0.02, Math.min(k * factor, 8));
297
+ tx = mx - ((mx - tx) * nk) / k;
298
+ ty = my - ((my - ty) * nk) / k;
299
+ k = nk;
300
+ applyTransform();
301
+ }, { passive: false });
302
+ svg.addEventListener("mousedown", (ev) => {
303
+ if (ev.button !== 0) return;
304
+ dragging = true; lastX = ev.clientX; lastY = ev.clientY;
305
+ svg.classList.add("dragging");
306
+ });
307
+ window.addEventListener("mousemove", (ev) => {
308
+ if (!dragging) return;
309
+ tx += ev.clientX - lastX; ty += ev.clientY - lastY;
310
+ lastX = ev.clientX; lastY = ev.clientY;
311
+ applyTransform();
312
+ });
313
+ window.addEventListener("mouseup", () => { dragging = false; svg.classList.remove("dragging"); });
314
+ document.getElementById("btnFit").addEventListener("click", fit);
315
+ document.getElementById("btnZoomIn").addEventListener("click", () => {
316
+ k = Math.min(k * 1.3, 8); applyTransform();
317
+ });
318
+ document.getElementById("btnZoomOut").addEventListener("click", () => {
319
+ k = Math.max(k / 1.3, 0.02); applyTransform();
320
+ });
321
+ fit();
322
+
323
+ // ── selection / highlight ──────────────────────────────────────────────────
324
+ const detail = document.getElementById("detail");
325
+ function clearHighlight() {
326
+ edgeEls.forEach((el) => el.setAttribute("class", edgeClass(el.getAttribute("data-kind"))));
327
+ nodeEls.forEach((el) => el.classList.remove("dim", "highlight"));
328
+ }
329
+ function selectNode(i) {
330
+ clearHighlight();
331
+ const path = CODEMAP.modules[i].path;
332
+ const connected = new Set([i]);
333
+ for (const j of incoming[i]) connected.add(j);
334
+ for (const j of outgoing[i]) connected.add(j);
335
+ edgeEls.forEach((el) => {
336
+ const f = byPath.get(el.getAttribute("data-from"));
337
+ const t = byPath.get(el.getAttribute("data-to"));
338
+ const active = (f === i && connected.has(t)) || (t === i && connected.has(f));
339
+ if (active) {
340
+ el.classList.add("highlight");
341
+ } else {
342
+ el.classList.add("dim");
343
+ }
344
+ });
345
+ nodeEls.forEach((el, j) => {
346
+ const p = el.getAttribute("data-path");
347
+ if (j === i) { el.classList.add("highlight"); return; }
348
+ if (connected.has(j)) { el.classList.add("highlight"); }
349
+ else { el.classList.add("dim"); }
350
+ });
351
+ showDetail(i);
352
+ }
353
+ function showDetail(i) {
354
+ const m = CODEMAP.modules[i];
355
+ document.getElementById("detailTitle").textContent = m.path.split("/").pop();
356
+ const color = ROLE_COLORS[m.role] || "#5b8def";
357
+ const ext = (CODEMAP.externalDeps && CODEMAP.externalDeps[m.path]) || [];
358
+ let html = "";
359
+ html += '<div class="kv"><span class="k">path</span><span class="v">' + escapeHtml(m.path) + "</span></div>";
360
+ html += '<div class="kv"><span class="k">role</span><span class="v"><span class="badge" style="background:' + color + '">' + m.role + "</span></span></div>";
361
+ html += '<div class="kv"><span class="k">language</span><span class="v">' + m.language + "</span></div>";
362
+ html += '<div class="kv"><span class="k">size</span><span class="v">' + m.sizeBytes + " bytes · " + m.lineCount + " lines</span></div>";
363
+ html += '<div class="kv"><span class="k">layer</span><span class="v">' + layer[i] + "</span></div>";
364
+ if (ext.length) {
365
+ html += '<h3>external deps (' + ext.length + ")</h3><ul>";
366
+ html += ext.slice(0, 40).map((s) => "<li>" + escapeHtml(s) + "</li>").join("");
367
+ if (ext.length > 40) html += "<li>…</li>";
368
+ html += "</ul>";
369
+ }
370
+ html += "<h3>imports (" + importOut[i].length + ")</h3><ul>";
371
+ html += importOut[i].length
372
+ ? importOut[i].map((p) => '<li><a data-jump="' + CODEMAP.modules[p].path + '">' + escapeHtml(CODEMAP.modules[p].path) + "</a></li>").join("")
373
+ : '<li class="empty">none</li>';
374
+ html += "</ul>";
375
+ html += "<h3>imported by (" + importIn[i].length + ")</h3><ul>";
376
+ html += importIn[i].length
377
+ ? importIn[i].map((p) => '<li><a data-jump="' + CODEMAP.modules[p].path + '">' + escapeHtml(CODEMAP.modules[p].path) + "</a></li>").join("")
378
+ : '<li class="empty">none</li>';
379
+ html += "</ul>";
380
+ html += "<h3>calls (" + callOut[i].length + ")</h3><ul>";
381
+ html += callOut[i].length
382
+ ? callOut[i].map((p) => '<li><a data-jump="' + CODEMAP.modules[p].path + '">' + escapeHtml(CODEMAP.modules[p].path) + "</a></li>").join("")
383
+ : '<li class="empty">none</li>';
384
+ html += "</ul>";
385
+ html += "<h3>called by (" + callIn[i].length + ")</h3><ul>";
386
+ html += callIn[i].length
387
+ ? callIn[i].map((p) => '<li><a data-jump="' + CODEMAP.modules[p].path + '">' + escapeHtml(CODEMAP.modules[p].path) + "</a></li>").join("")
388
+ : '<li class="empty">none</li>';
389
+ html += "</ul>";
390
+ const flows = (CODEMAP.flows && CODEMAP.flows[m.path]) || [];
391
+ if (m.role === "entrypoint" && flows.length) {
392
+ html += "<h3>flow from this entrypoint (" + flows.length + ")</h3><ul>";
393
+ html += flows.slice(0, 40).map((p) => '<li><a data-jump="' + p + '">' + escapeHtml(p) + "</a></li>").join("");
394
+ if (flows.length > 40) html += "<li>…</li>";
395
+ html += "</ul>";
396
+ }
397
+ document.getElementById("detailBody").innerHTML = html;
398
+ detail.classList.add("open");
399
+ detail.querySelectorAll("a[data-jump]").forEach((a) => {
400
+ a.addEventListener("click", () => {
401
+ const target = a.getAttribute("data-jump");
402
+ const j = byPath.get(target);
403
+ if (j !== undefined) selectNode(j);
404
+ });
405
+ });
406
+ }
407
+ function escapeHtml(s) {
408
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
409
+ }
410
+ document.getElementById("detailClose").addEventListener("click", () => {
411
+ detail.classList.remove("open");
412
+ clearHighlight();
413
+ });
414
+ svg.addEventListener("click", () => {
415
+ detail.classList.remove("open");
416
+ clearHighlight();
417
+ });
418
+
419
+ // ── search / filter ────────────────────────────────────────────────────────
420
+ const search = document.getElementById("search");
421
+ let lastQuery = "";
422
+ function applyFilter() {
423
+ const q = search.value.trim().toLowerCase();
424
+ if (q === lastQuery) return;
425
+ lastQuery = q;
426
+ const visible = new Set();
427
+ CODEMAP.modules.forEach((m, i) => {
428
+ if (!q || m.path.toLowerCase().includes(q)) visible.add(i);
429
+ });
430
+ nodeEls.forEach((el, i) => {
431
+ el.style.display = visible.has(i) ? "" : "none";
432
+ });
433
+ edgeEls.forEach((el) => {
434
+ const f = byPath.get(el.getAttribute("data-from"));
435
+ const t = byPath.get(el.getAttribute("data-to"));
436
+ el.style.display = visible.has(f) && visible.has(t) ? "" : "none";
437
+ });
438
+ }
439
+ search.addEventListener("input", applyFilter);
440
+ applyFilter();
441
+
442
+ // ── header meta ────────────────────────────────────────────────────────────
443
+ document.getElementById("project").textContent = CODEMAP.project;
444
+ const edgeCount = CODEMAP.edges.length;
445
+ document.getElementById("meta").textContent =
446
+ CODEMAP.modules.length + " modules · " + edgeCount + " edges · fp " + CODEMAP.fingerprint.slice(0, 12) + " · " + CODEMAP.generatedAt;
447
+ </script>
448
+ </body>
449
+ </html>
450
+ `
451
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * codemap storage in the CENTRAL per-project data store — mirroring the
3
+ * codesearch precedent exactly: `<dataRoot>/projects/<project-key>/codemap/`
4
+ * (see src/project-store.ts). Worktrees of a repo share one codemap for free,
5
+ * and the dashboard reads it from the same resolved location.
6
+ */
7
+
8
+ import * as fs from "node:fs"
9
+ import * as path from "node:path"
10
+
11
+ import { resolveProjectDataDir } from "../project-store.js"
12
+ import type { Codemap, CodemapLock } from "./types.js"
13
+
14
+ /** Directory holding a project's codemap artifacts, relative to the project key dir. */
15
+ export const CODEMAP_DIR_NAME = "codemap"
16
+
17
+ /** Workspace root -> absolute codemap storage dir (central project store). */
18
+ export function codemapDir(workspaceRoot: string): string {
19
+ return path.join(resolveProjectDataDir(workspaceRoot), CODEMAP_DIR_NAME)
20
+ }
21
+
22
+ export function codemapJsonPath(workspaceRoot: string): string {
23
+ return path.join(codemapDir(workspaceRoot), "codemap.json")
24
+ }
25
+
26
+ export function codemapLockPath(workspaceRoot: string): string {
27
+ return path.join(codemapDir(workspaceRoot), "codemap.lock")
28
+ }
29
+
30
+ export function codemapHtmlPath(workspaceRoot: string): string {
31
+ return path.join(codemapDir(workspaceRoot), "codemap.html")
32
+ }
33
+
34
+ /** Loose-parse the stored lock; undefined when missing/unreadable/malformed. */
35
+ export function loadLock(workspaceRoot: string): CodemapLock | undefined {
36
+ let raw: string
37
+ try {
38
+ raw = fs.readFileSync(codemapLockPath(workspaceRoot), "utf-8")
39
+ } catch {
40
+ return undefined
41
+ }
42
+ try {
43
+ const parsed = JSON.parse(raw) as Record<string, unknown>
44
+ if (typeof parsed.fingerprints !== "object" || parsed.fingerprints === null) {
45
+ return undefined
46
+ }
47
+ const fingerprints: Record<string, string> = {}
48
+ for (const [k, v] of Object.entries(parsed.fingerprints)) {
49
+ if (typeof v === "string") {
50
+ fingerprints[k] = v
51
+ }
52
+ }
53
+ return {
54
+ project: typeof parsed.project === "string" ? parsed.project : "",
55
+ fingerprints,
56
+ codemapFingerprint: typeof parsed.codemapFingerprint === "string" ? parsed.codemapFingerprint : "",
57
+ }
58
+ } catch {
59
+ return undefined
60
+ }
61
+ }
62
+
63
+ /** Load the stored codemap.json; undefined when missing/unreadable/malformed. */
64
+ export function loadCodemap(workspaceRoot: string): Codemap | undefined {
65
+ let raw: string
66
+ try {
67
+ raw = fs.readFileSync(codemapJsonPath(workspaceRoot), "utf-8")
68
+ } catch {
69
+ return undefined
70
+ }
71
+ try {
72
+ const parsed = JSON.parse(raw) as Codemap
73
+ if (!Array.isArray(parsed.modules) || !Array.isArray(parsed.edges) || typeof parsed.fingerprint !== "string") {
74
+ return undefined
75
+ }
76
+ return parsed
77
+ } catch {
78
+ return undefined
79
+ }
80
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Shared types for the deterministic per-project codemap subsystem
3
+ * (src/codemap/ — module inventory, import/include/call graph,
4
+ * entrypoint-rooted flows, self-contained HTML visualizer,
5
+ * fingerprint-aware watcher, dashboard endpoints).
6
+ *
7
+ * Everything here is produced by a SCRIPT, never an LLM: the whole point
8
+ * (issue #17/#18) is a worker that needs to know "what is this repo, what
9
+ * imports what, what calls what, where do the entrypoints lead" can read
10
+ * codemap.json instead of re-deriving structure via grep every session —
11
+ * and trust it because it was generated mechanically.
12
+ */
13
+
14
+ /** Naming-convention heuristic for a module's role (no semantics needed). */
15
+ export type ModuleRole = "source" | "test" | "entrypoint" | "config" | "vendor" | "generated"
16
+
17
+ /** Coarse language family, used by the extractor + visualizer. */
18
+ export type ModuleLanguage = "typescript" | "javascript" | "python" | "cpp" | "c" | "other"
19
+
20
+ /** One module (file) in the map. */
21
+ export interface ModuleEntry {
22
+ /** Workspace-relative POSIX path (e.g. "src/engine/loop.ts"). */
23
+ path: string
24
+ language: ModuleLanguage
25
+ role: ModuleRole
26
+ /** File size in bytes (for a size/role heuristic). */
27
+ sizeBytes: number
28
+ /** Line count (for the same heuristic + the visualizer's detail panel). */
29
+ lineCount: number
30
+ /** sha256 hex of the file content — the fingerprint key for the lock. */
31
+ hash: string
32
+ }
33
+
34
+ /**
35
+ * One directed edge between two workspace modules. Only edges whose target
36
+ * resolves INSIDE the workspace appear here; edges that resolve outside
37
+ * (node_modules, builtins, absolute paths, failures) are summarized per
38
+ * module in ModuleEntry.externalDeps so the map never explodes with
39
+ * dependency nodes.
40
+ */
41
+ export interface CodemapEdge {
42
+ /** Workspace-relative POSIX path of the source module. */
43
+ from: string
44
+ /** Workspace-relative POSIX path of the target module. */
45
+ to: string
46
+ /**
47
+ * "import" for TS/JS/Python import statements, "include" for C/C++,
48
+ * "call" for TS/JS cross-module function/method/constructor calls
49
+ * (Phase 2 — resolved via the TS checker, not text matching).
50
+ */
51
+ kind: "import" | "include" | "call"
52
+ /**
53
+ * Raw specifier as written in source (e.g. "./b.js", "a.b.c", "foo.h").
54
+ * For call edges: the sorted, comma-joined callee names that make up the
55
+ * edge (one edge per from/to pair, so the module graph stays clean).
56
+ */
57
+ specifier: string
58
+ /** True for TS `import type` / `export type` edges (thinner in the view). */
59
+ isTypeOnly?: boolean
60
+ }
61
+
62
+ /** The full codemap document (`codemap.json`). */
63
+ export interface Codemap {
64
+ /** Human-readable project name (repo basename, or the workspace basename). */
65
+ project: string
66
+ /** Resolved absolute workspace root the map was generated from. */
67
+ root: string
68
+ /** ISO timestamp of generation. */
69
+ generatedAt: string
70
+ /**
71
+ * Deterministic content hash of the map: sha256 over the sorted module
72
+ * paths + hashes and the sorted edges. Stable across regenerations of an
73
+ * unchanged repo, so consumers can cheaply compare "did anything change".
74
+ */
75
+ fingerprint: string
76
+ modules: ModuleEntry[]
77
+ edges: CodemapEdge[]
78
+ /**
79
+ * Per-module deduplicated list of import specifiers that did NOT resolve
80
+ * to a workspace-internal module (node_modules, builtins, failures). Null
81
+ * (not empty) on a module with no such imports.
82
+ */
83
+ externalDeps: Record<string, string[]>
84
+ /**
85
+ * Entrypoint-rooted end-to-end flows (Phase 2, issue #18): for each
86
+ * module whose role is "entrypoint", the sorted list of every module
87
+ * reachable from it along directed import+call edges. Derived, not
88
+ * fingerprinted — a pure function of modules + edges. Deliberately NOT
89
+ * ranked by "importance" (see src/codemap/flows.ts for why).
90
+ */
91
+ flows: Record<string, string[]>
92
+ }
93
+
94
+ /** Per-module content fingerprints — the change-detection lock. */
95
+ export interface CodemapLock {
96
+ project: string
97
+ /** Workspace-relative POSIX path -> sha256 hex of the file content. */
98
+ fingerprints: Record<string, string>
99
+ /** The fingerprint of the codemap.json this lock was written alongside. */
100
+ codemapFingerprint: string
101
+ }