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,280 @@
1
+ /**
2
+ * OpenAI-format tool schemas for the four code-intelligence tools.
3
+ *
4
+ * NEW design work, NOT a port: no code-intelligence tools exist in the
5
+ * upstream Zoo Code sources this project vendors (the vendored
6
+ * `search_files` is a regex-based text search, not a symbol resolver). The
7
+ * schema SHAPE follows the vendored native-tools conventions exactly (see
8
+ * src/vendor/zoo-code/src/core/prompts/tools/native-tools/codebase_search.ts):
9
+ * `type: "function"`, `strict: true`, `additionalProperties: false`, a long
10
+ * prose description with Parameters/Examples, one const per parameter
11
+ * description. The implementation lives in src/codeintel/.
12
+ *
13
+ * Parameter-shape rationale (the spec's "design around what the API needs"):
14
+ * `getDefinitionAtPosition` / `getReferencesAtPosition` take a character
15
+ * OFFSET, not a line — so `line` alone is genuinely ambiguous on a line with
16
+ * several symbols. `character` (1-based column) is the precise answer;
17
+ * `symbol` (the name) is the cheap alternative the model can supply without
18
+ * counting columns; with neither, the first identifier on the line is used.
19
+ */
20
+
21
+ import type OpenAI from "openai"
22
+
23
+ // ─── outline ─────────────────────────────────────────────────────────────────
24
+
25
+ const OUTLINE_DESCRIPTION = `List every top-level (and one level of nested) declaration in a source file — functions, classes, interfaces, types, enums, namespaces, consts, and class/interface members — as one compact line per symbol: "<line> <kind> <signature>". Signatures only, no bodies, so this is a cheap structural map of a file WITHOUT reading it in full.
26
+
27
+ Use this instead of read_file when you only need a file's structure (symbol names, kinds, and line numbers) to plan where to read or what to call.
28
+
29
+ Only works on TypeScript/JavaScript source files (.ts/.tsx/.mts/.cts/.js/.jsx). Non-TS files return a clear "not supported" result.
30
+
31
+ Parameters:
32
+ - path: (required) Path to the source file, relative to the workspace root.
33
+
34
+ Example: { "path": "src/engine/loop.ts" }`
35
+
36
+ const OUTLINE_PATH_PARAMETER_DESCRIPTION = `Path to the source file to outline, relative to the workspace root`
37
+
38
+ export const outlineTool = {
39
+ type: "function",
40
+ function: {
41
+ name: "outline",
42
+ description: OUTLINE_DESCRIPTION,
43
+ strict: true,
44
+ parameters: {
45
+ type: "object",
46
+ properties: {
47
+ path: {
48
+ type: "string",
49
+ description: OUTLINE_PATH_PARAMETER_DESCRIPTION,
50
+ },
51
+ },
52
+ required: ["path"],
53
+ additionalProperties: false,
54
+ },
55
+ },
56
+ } satisfies OpenAI.Chat.ChatCompletionTool
57
+
58
+ // ─── go_to_definition ────────────────────────────────────────────────────────
59
+
60
+ const GO_TO_DEFINITION_DESCRIPTION = `Resolve the definition of the symbol at a given line of a source file, using the TypeScript compiler's real symbol resolution (not a text search). Returns the real definition location(s) as "<file>:<line>:<column> <kind> <name> — <snippet of the definition line>" — one entry per definition; overloaded functions return ALL definitions. Definitions in vendored/d.ts/node_modules files are returned as their real locations.
61
+
62
+ Use this to jump straight to where a symbol is actually defined instead of grep-ing for it.
63
+
64
+ Only works on TypeScript/JavaScript source files. A symbol with no resolvable definition (ambient, any-typed, etc.) returns a clear "could not resolve" message.
65
+
66
+ Parameters:
67
+ - path: (required) Path to the source file containing the symbol, relative to the workspace root.
68
+ - line: (required) 1-based line number of the symbol.
69
+ - character: (optional) 1-based column of the symbol within the line. Needed when the line has multiple symbols and you want a specific one; omit to use the symbol name or the first identifier on the line.
70
+ - symbol: (optional) The symbol's name, used to disambiguate when the line has several identifiers (e.g. { "path": "src/foo.ts", "line": 12, "symbol": "bar" }). Ignored when character is given.
71
+
72
+ Example: go to the definition of the call on line 42: { "path": "src/engine/loop.ts", "line": 42, "symbol": "truncateHistory" }`
73
+
74
+ const GTD_PATH_PARAMETER_DESCRIPTION = `Path to the source file containing the symbol, relative to the workspace root`
75
+ const GTD_LINE_PARAMETER_DESCRIPTION = `1-based line number of the symbol`
76
+ const GTD_CHARACTER_PARAMETER_DESCRIPTION = `1-based column of the symbol within the line (disambiguates on multi-symbol lines); omit to use symbol name or the first identifier on the line`
77
+ const GTD_SYMBOL_PARAMETER_DESCRIPTION = `Symbol name to disambiguate when the line has several identifiers; ignored when character is given`
78
+
79
+ export const goToDefinitionTool = {
80
+ type: "function",
81
+ function: {
82
+ name: "go_to_definition",
83
+ description: GO_TO_DEFINITION_DESCRIPTION,
84
+ strict: true,
85
+ parameters: {
86
+ type: "object",
87
+ properties: {
88
+ path: {
89
+ type: "string",
90
+ description: GTD_PATH_PARAMETER_DESCRIPTION,
91
+ },
92
+ line: {
93
+ type: "integer",
94
+ description: GTD_LINE_PARAMETER_DESCRIPTION,
95
+ },
96
+ character: {
97
+ type: ["integer", "null"],
98
+ description: GTD_CHARACTER_PARAMETER_DESCRIPTION,
99
+ },
100
+ symbol: {
101
+ type: ["string", "null"],
102
+ description: GTD_SYMBOL_PARAMETER_DESCRIPTION,
103
+ },
104
+ },
105
+ required: ["path", "line", "character", "symbol"],
106
+ additionalProperties: false,
107
+ },
108
+ },
109
+ } satisfies OpenAI.Chat.ChatCompletionTool
110
+
111
+ // ─── find_references ─────────────────────────────────────────────────────────
112
+
113
+ const FIND_REFERENCES_DESCRIPTION = `Find every real usage site of the symbol at a given line of a source file, using the TypeScript compiler's real reference resolution (not grep). Returns compact "<file>:<line> — <snippet of the line>" entries (the declaration site itself is included), so you can see all callers/uses without reading each file. Results are capped at 50 with a clear "N more references not shown" note — narrow your search to a more specific symbol if you need the rest.
114
+
115
+ Use this to answer "who uses this symbol / what would this change affect".
116
+
117
+ Only works on TypeScript/JavaScript source files.
118
+
119
+ Parameters:
120
+ - path: (required) Path to the source file containing the symbol, relative to the workspace root.
121
+ - line: (required) 1-based line number of the symbol.
122
+ - character: (optional) 1-based column of the symbol within the line; omit to use symbol name or the first identifier on the line.
123
+ - symbol: (optional) Symbol name to disambiguate when the line has several identifiers; ignored when character is given.
124
+
125
+ Example: { "path": "src/config/mode-models.ts", "line": 12, "symbol": "resolveModelForMode" }`
126
+
127
+ const FR_PATH_PARAMETER_DESCRIPTION = `Path to the source file containing the symbol, relative to the workspace root`
128
+ const FR_LINE_PARAMETER_DESCRIPTION = `1-based line number of the symbol`
129
+ const FR_CHARACTER_PARAMETER_DESCRIPTION = `1-based column of the symbol within the line (disambiguates on multi-symbol lines); omit to use symbol name or the first identifier on the line`
130
+ const FR_SYMBOL_PARAMETER_DESCRIPTION = `Symbol name to disambiguate when the line has several identifiers; ignored when character is given`
131
+
132
+ export const findReferencesTool = {
133
+ type: "function",
134
+ function: {
135
+ name: "find_references",
136
+ description: FIND_REFERENCES_DESCRIPTION,
137
+ strict: true,
138
+ parameters: {
139
+ type: "object",
140
+ properties: {
141
+ path: {
142
+ type: "string",
143
+ description: FR_PATH_PARAMETER_DESCRIPTION,
144
+ },
145
+ line: {
146
+ type: "integer",
147
+ description: FR_LINE_PARAMETER_DESCRIPTION,
148
+ },
149
+ character: {
150
+ type: ["integer", "null"],
151
+ description: FR_CHARACTER_PARAMETER_DESCRIPTION,
152
+ },
153
+ symbol: {
154
+ type: ["string", "null"],
155
+ description: FR_SYMBOL_PARAMETER_DESCRIPTION,
156
+ },
157
+ },
158
+ required: ["path", "line", "character", "symbol"],
159
+ additionalProperties: false,
160
+ },
161
+ },
162
+ } satisfies OpenAI.Chat.ChatCompletionTool
163
+
164
+ // ─── rename_symbol ───────────────────────────────────────────────────────────
165
+
166
+ const RENAME_SYMBOL_DESCRIPTION = `Rename a symbol at a given line of a source file to a new name EVERYWHERE it's used across the workspace, as ONE atomic operation — either every call site updates or none do (on any failure, already-applied edits are rolled back and the error names the failing site). Uses the TypeScript compiler's real reference resolution (the same engine as find_references) to enumerate sites, so it is correct on overloads, re-exports, and aliased imports where a text find/replace would miss or wrongly hit.
167
+
168
+ Use this instead of composing N separate apply_diff/search_replace edits when you need to rename a symbol used across multiple files — one call replaces the whole read-every-file + edit-every-file round trip.
169
+
170
+ Only works on TypeScript/JavaScript source files, and refuses (with nothing changed) when any reference site lies outside the workspace root.
171
+
172
+ Parameters:
173
+ - path: (required) Path to the source file containing the symbol, relative to the workspace root.
174
+ - line: (required) 1-based line number of the symbol.
175
+ - character: (optional) 1-based column of the symbol within the line; omit to use the symbol name or the first identifier on the line.
176
+ - symbol: (optional) The symbol's name, used to disambiguate when the line has several identifiers (e.g. { "path": "src/foo.ts", "line": 12, "symbol": "bar" }). Ignored when character is given.
177
+ - new_name: (required) The new identifier to rename the symbol to (must be a valid TypeScript identifier).
178
+
179
+ Example: rename the function on line 1 of src/util.ts to "compute": { "path": "src/util.ts", "line": 1, "symbol": "util", "new_name": "compute" }`
180
+
181
+ const RS_PATH_PARAMETER_DESCRIPTION = `Path to the source file containing the symbol, relative to the workspace root`
182
+ const RS_LINE_PARAMETER_DESCRIPTION = `1-based line number of the symbol`
183
+ const RS_CHARACTER_PARAMETER_DESCRIPTION = `1-based column of the symbol within the line (disambiguates on multi-symbol lines); omit to use the symbol name or the first identifier on the line`
184
+ const RS_SYMBOL_PARAMETER_DESCRIPTION = `Symbol name to disambiguate when the line has several identifiers; ignored when character is given`
185
+ const RS_NEW_NAME_PARAMETER_DESCRIPTION = `The new identifier to rename the symbol to (must be a valid TypeScript identifier)`
186
+
187
+ export const renameSymbolTool = {
188
+ type: "function",
189
+ function: {
190
+ name: "rename_symbol",
191
+ description: RENAME_SYMBOL_DESCRIPTION,
192
+ strict: true,
193
+ parameters: {
194
+ type: "object",
195
+ properties: {
196
+ path: {
197
+ type: "string",
198
+ description: RS_PATH_PARAMETER_DESCRIPTION,
199
+ },
200
+ line: {
201
+ type: "integer",
202
+ description: RS_LINE_PARAMETER_DESCRIPTION,
203
+ },
204
+ character: {
205
+ type: ["integer", "null"],
206
+ description: RS_CHARACTER_PARAMETER_DESCRIPTION,
207
+ },
208
+ symbol: {
209
+ type: ["string", "null"],
210
+ description: RS_SYMBOL_PARAMETER_DESCRIPTION,
211
+ },
212
+ new_name: {
213
+ type: "string",
214
+ description: RS_NEW_NAME_PARAMETER_DESCRIPTION,
215
+ },
216
+ },
217
+ required: ["path", "line", "character", "symbol", "new_name"],
218
+ additionalProperties: false,
219
+ },
220
+ },
221
+ } satisfies OpenAI.Chat.ChatCompletionTool
222
+
223
+ // ─── import_graph ────────────────────────────────────────────────────────────
224
+
225
+ const IMPORT_GRAPH_DESCRIPTION = `Show a file's import relationships: (a) everything it imports, resolved to the REAL file paths the TypeScript compiler resolves them to (relative specifiers like "./foo" resolve to the actual file; bare specifiers like "vscode" resolve to the module the compiler maps them to, e.g. the vendored shim), and (b) everything that imports it (reverse lookup across the whole program). Static analysis of import/export declarations — deterministic and fast (the program-wide import map is cached).
226
+
227
+ Use this to understand module dependencies before editing, or to find every file that depends on a module you're about to change.
228
+
229
+ Only works on TypeScript/JavaScript source files.
230
+
231
+ Parameters:
232
+ - path: (required) Path to the source file, relative to the workspace root.
233
+
234
+ Example: { "path": "src/tools/executor.ts" }`
235
+
236
+ const IG_PATH_PARAMETER_DESCRIPTION = `Path to the source file, relative to the workspace root`
237
+
238
+ export const importGraphTool = {
239
+ type: "function",
240
+ function: {
241
+ name: "import_graph",
242
+ description: IMPORT_GRAPH_DESCRIPTION,
243
+ strict: true,
244
+ parameters: {
245
+ type: "object",
246
+ properties: {
247
+ path: {
248
+ type: "string",
249
+ description: IG_PATH_PARAMETER_DESCRIPTION,
250
+ },
251
+ },
252
+ required: ["path"],
253
+ additionalProperties: false,
254
+ },
255
+ },
256
+ } satisfies OpenAI.Chat.ChatCompletionTool
257
+
258
+ export const CODE_INTEL_TOOLS: readonly OpenAI.Chat.ChatCompletionTool[] = [
259
+ outlineTool,
260
+ goToDefinitionTool,
261
+ findReferencesTool,
262
+ importGraphTool,
263
+ ]
264
+
265
+ export const CODE_INTEL_TOOL_NAMES: readonly string[] = [
266
+ outlineTool.function.name,
267
+ goToDefinitionTool.function.name,
268
+ findReferencesTool.function.name,
269
+ importGraphTool.function.name,
270
+ ]
271
+
272
+ /**
273
+ * The EDIT-capable code-intelligence tool (`rename_symbol`). Deliberately NOT
274
+ * part of CODE_INTEL_TOOLS: that set is appended for every mode including the
275
+ * read-only reviewer/QA executors (which never register write tools), whereas
276
+ * rename_symbol edits files and must only be advertised to executors that
277
+ * actually register it (the headless executor — see src/tools/executor.ts).
278
+ * Callers append it explicitly, gated on executor capability.
279
+ */
280
+ export const RENAME_SYMBOL_TOOL: OpenAI.Chat.ChatCompletionTool = renameSymbolTool
@@ -0,0 +1,135 @@
1
+ /**
2
+ * The codemap build pipeline: inventory → fingerprint diff → extract →
3
+ * write. Deterministic end to end, NO LLM anywhere (issue #17's hard
4
+ * requirement).
5
+ *
6
+ * Fingerprint-aware regeneration: the lock (codemap.lock) stores a sha256
7
+ * per module path. When the current inventory exactly matches the lock —
8
+ * same key set, same hash per key — and a codemap.json already exists, the
9
+ * build writes NOTHING and reports "unchanged". Any diff (edited, added,
10
+ * deleted file) triggers a FULL regeneration: extraction is fast and cheap,
11
+ * so per-module incremental regeneration is not worth the complexity for v1
12
+ * (the issue explicitly allows full regen as long as it's fast).
13
+ */
14
+
15
+ import * as fs from "node:fs"
16
+ import * as fsp from "node:fs/promises"
17
+ import * as path from "node:path"
18
+
19
+ import { inventoryModules } from "./files.js"
20
+ import { codemapFingerprint, contentHash, fingerprintMap, lockMatches } from "./fingerprint.js"
21
+ import { computeEntrypointFlows } from "./flows.js"
22
+ import { renderCodemapHtml } from "./html.js"
23
+ import { codemapDir, codemapHtmlPath, codemapJsonPath, codemapLockPath, loadCodemap, loadLock } from "./lock.js"
24
+ import { extractEdges } from "./extract.js"
25
+ import type { Codemap, CodemapLock, CodemapEdge, ModuleEntry } from "./types.js"
26
+
27
+ export interface CodemapBuildOptions {
28
+ workspaceRoot: string
29
+ /** Skip the fingerprint check and regenerate unconditionally. */
30
+ force?: boolean
31
+ }
32
+
33
+ export interface CodemapBuildResult {
34
+ /** False when the fingerprint check short-circuited (nothing written). */
35
+ changed: boolean
36
+ project: string
37
+ root: string
38
+ modules: ModuleEntry[]
39
+ edges: CodemapEdge[]
40
+ /** Per-module external (unresolved/out-of-workspace) specifiers. */
41
+ externalDeps: Record<string, string[]>
42
+ /** Entrypoint-rooted reachability (Phase 2, issue #18). */
43
+ flows: Record<string, string[]>
44
+ fingerprint: string
45
+ jsonPath: string
46
+ htmlPath: string
47
+ lockPath: string
48
+ }
49
+
50
+ /** Resolve the human-readable project name (repo dir basename). */
51
+ export function projectName(workspaceRoot: string): string {
52
+ return path.basename(path.resolve(workspaceRoot)) || "project"
53
+ }
54
+
55
+ /**
56
+ * Build (or refresh) the codemap for a workspace. When nothing changed since
57
+ * the last build, no files are written and `changed` is false.
58
+ */
59
+ export async function buildCodemap(options: CodemapBuildOptions): Promise<CodemapBuildResult> {
60
+ const root = path.resolve(options.workspaceRoot)
61
+ const project = projectName(root)
62
+
63
+ const modules = await inventoryModules(root, contentHash)
64
+
65
+ if (!options.force) {
66
+ const lock = loadLock(root)
67
+ const existing = loadCodemap(root)
68
+ if (lockMatches(lock, modules) && existing !== undefined) {
69
+ return {
70
+ changed: false,
71
+ project: existing.project || project,
72
+ root,
73
+ modules: existing.modules,
74
+ edges: existing.edges,
75
+ externalDeps: existing.externalDeps,
76
+ // Derived from modules+edges; a map written before flows existed
77
+ // simply has none until the next real regeneration.
78
+ flows: existing.flows ?? {},
79
+ fingerprint: existing.fingerprint,
80
+ jsonPath: codemapJsonPath(root),
81
+ htmlPath: codemapHtmlPath(root),
82
+ lockPath: codemapLockPath(root),
83
+ }
84
+ }
85
+ }
86
+
87
+ const { edges, externalDeps } = await extractEdges(root, modules)
88
+ const flows = computeEntrypointFlows(modules, edges)
89
+ const fingerprint = codemapFingerprint({ project, modules, edges, externalDeps })
90
+
91
+ const codemap: Codemap = {
92
+ project,
93
+ root,
94
+ generatedAt: new Date().toISOString(),
95
+ fingerprint,
96
+ modules,
97
+ edges,
98
+ externalDeps,
99
+ flows,
100
+ }
101
+ const lock: CodemapLock = {
102
+ project,
103
+ fingerprints: fingerprintMap(modules),
104
+ codemapFingerprint: fingerprint,
105
+ }
106
+
107
+ const dir = codemapDir(root)
108
+ await fsp.mkdir(dir, { recursive: true })
109
+ await fsp.writeFile(codemapJsonPath(root), JSON.stringify(codemap, null, 2) + "\n", "utf-8")
110
+ await fsp.writeFile(codemapLockPath(root), JSON.stringify(lock, null, 2) + "\n", "utf-8")
111
+ await fsp.writeFile(codemapHtmlPath(root), renderCodemapHtml(codemap), "utf-8")
112
+
113
+ return {
114
+ changed: true,
115
+ project,
116
+ root,
117
+ modules,
118
+ edges,
119
+ externalDeps,
120
+ flows,
121
+ fingerprint,
122
+ jsonPath: codemapJsonPath(root),
123
+ htmlPath: codemapHtmlPath(root),
124
+ lockPath: codemapLockPath(root),
125
+ }
126
+ }
127
+
128
+ /** True when a codemap already exists for the workspace (dashboard 404 helper). */
129
+ export function codemapExists(workspaceRoot: string): boolean {
130
+ try {
131
+ return fs.existsSync(codemapJsonPath(workspaceRoot))
132
+ } catch {
133
+ return false
134
+ }
135
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * `headlesscode codemap` subcommand — build/refresh a project's deterministic
3
+ * module/import map (issue #17).
4
+ *
5
+ * npx tsx src/cli.ts codemap --workspace <path> [--force] [--watch] [--interval-ms <n>]
6
+ *
7
+ * Generates codemap.json / codemap.lock / codemap.html into the CENTRAL
8
+ * per-project data store (~/.local/share/headlesscode/projects/<key>/codemap/ —
9
+ * same resolution as the codesearch index; worktrees of a repo share it).
10
+ * Regeneration is fingerprint-aware: an unchanged repo produces NO writes.
11
+ *
12
+ * `--watch` turns the command into a long-running poll loop (regenerate on
13
+ * change, sleep, repeat) — the watcher mode a systemd timer or cron job can
14
+ * also drive with one-shot invocations instead; see docs/codemap.md.
15
+ */
16
+
17
+ import * as path from "node:path"
18
+ import { setTimeout as sleep } from "node:timers/promises"
19
+
20
+ import { buildCodemap } from "./build.js"
21
+ import { codemapJsonPath } from "./lock.js"
22
+
23
+ const CODEMAP_USAGE = `headlesscode codemap — build/refresh a project's deterministic codemap
24
+
25
+ Usage:
26
+ headlesscode codemap --workspace <path> [--force] [--watch] [--interval-ms <n>]
27
+
28
+ Options:
29
+ --workspace <path> Workspace root to map (required)
30
+ --force Regenerate even when the fingerprint lock says nothing changed
31
+ --watch Watch mode: poll for changes and regenerate, forever
32
+ (Ctrl-C to stop). One-shot runs are what a cron job /
33
+ systemd timer should invoke instead.
34
+ --interval-ms <n> Watch-mode poll interval in ms (default: 60000)
35
+ --help Show this help and exit
36
+
37
+ The map is written to the CENTRAL per-project store:
38
+ ~/.local/share/headlesscode/projects/<project-key>/codemap/
39
+ codemap.json machine-readable module/edge graph (dashboard-served)
40
+ codemap.lock per-module content fingerprints (change detection)
41
+ codemap.html self-contained interactive visualizer (open in a browser)
42
+
43
+ No LLM is involved anywhere in the pipeline — the map is generated
44
+ deterministically from the repo's source files.
45
+ `
46
+
47
+ interface CodemapCliOptions {
48
+ workspace?: string
49
+ force: boolean
50
+ watch: boolean
51
+ intervalMs: number
52
+ help: boolean
53
+ }
54
+
55
+ export function parseCodemapArgs(argv: string[]): { options: CodemapCliOptions; error?: string } {
56
+ const options: CodemapCliOptions = { force: false, watch: false, intervalMs: 60_000, help: false }
57
+ for (let i = 0; i < argv.length; i++) {
58
+ const arg = argv[i]
59
+ const eq = arg.indexOf("=")
60
+ const flag = eq === -1 ? arg : arg.slice(0, eq)
61
+ const inlineValue = eq === -1 ? undefined : arg.slice(eq + 1)
62
+ const next = (): string | undefined => {
63
+ if (inlineValue !== undefined) {
64
+ return inlineValue
65
+ }
66
+ const v = argv[i + 1]
67
+ if (v === undefined || v.startsWith("--")) {
68
+ return undefined
69
+ }
70
+ i++
71
+ return v
72
+ }
73
+ switch (flag) {
74
+ case "--workspace": {
75
+ const value = next()
76
+ if (value === undefined) {
77
+ return { options, error: "Missing value for --workspace" }
78
+ }
79
+ options.workspace = value
80
+ break
81
+ }
82
+ case "--interval-ms": {
83
+ const value = next()
84
+ const n = value === undefined ? Number.NaN : Number(value)
85
+ if (!Number.isInteger(n) || n <= 0) {
86
+ return { options, error: "--interval-ms requires a positive integer" }
87
+ }
88
+ options.intervalMs = n
89
+ break
90
+ }
91
+ case "--force":
92
+ options.force = true
93
+ break
94
+ case "--watch":
95
+ options.watch = true
96
+ break
97
+ case "--help":
98
+ case "-h":
99
+ options.help = true
100
+ break
101
+ default:
102
+ return { options, error: `Unknown argument: ${arg}` }
103
+ }
104
+ }
105
+ return { options }
106
+ }
107
+
108
+ // `--force` always regenerates (buildCodemap skips the lock check), so only
109
+ // two outcomes exist: a real (re)generation, or an unchanged short-circuit.
110
+ function report(result: Awaited<ReturnType<typeof buildCodemap>>): void {
111
+ if (result.changed) {
112
+ process.stdout.write(
113
+ `codemap: regenerated ${result.modules.length} modules, ${result.edges.length} edges` +
114
+ ` (fp ${result.fingerprint.slice(0, 12)})\n` +
115
+ ` ${result.jsonPath}\n` +
116
+ ` ${result.htmlPath}\n`,
117
+ )
118
+ return
119
+ }
120
+ process.stdout.write(
121
+ `codemap: unchanged — no writes (${result.modules.length} modules, ${result.edges.length} edges cached, ` +
122
+ `fp ${result.fingerprint.slice(0, 12)})\n`,
123
+ )
124
+ }
125
+
126
+ export async function codemapMain(argv: string[]): Promise<number> {
127
+ const { options, error } = parseCodemapArgs(argv)
128
+ if (error) {
129
+ process.stderr.write(`headlesscode codemap: ${error}\n\n${CODEMAP_USAGE}`)
130
+ return 2
131
+ }
132
+ if (options.help) {
133
+ process.stdout.write(CODEMAP_USAGE)
134
+ return 0
135
+ }
136
+ if (!options.workspace) {
137
+ process.stderr.write(`headlesscode codemap: --workspace <path> is required\n\n${CODEMAP_USAGE}`)
138
+ return 2
139
+ }
140
+
141
+ const workspaceRoot = path.resolve(options.workspace)
142
+
143
+ const runOnce = async (): Promise<void> => {
144
+ const result = await buildCodemap({ workspaceRoot, force: options.force })
145
+ report(result)
146
+ }
147
+
148
+ if (!options.watch) {
149
+ try {
150
+ await runOnce()
151
+ return 0
152
+ } catch (err) {
153
+ process.stderr.write(`headlesscode codemap: ${err instanceof Error ? err.message : String(err)}\n`)
154
+ return 1
155
+ }
156
+ }
157
+
158
+ // Watch mode: clean shutdown on SIGINT/SIGTERM (same idiom as watchMain —
159
+ // see src/watcher/cli.ts).
160
+ const controller = new AbortController()
161
+ const onSignal = (): void => controller.abort()
162
+ process.on("SIGINT", onSignal)
163
+ process.on("SIGTERM", onSignal)
164
+
165
+ process.stdout.write(`codemap: watching ${workspaceRoot} every ${options.intervalMs}ms (Ctrl-C to stop)\n`)
166
+ try {
167
+ for (;;) {
168
+ if (controller.signal.aborted) {
169
+ break
170
+ }
171
+ try {
172
+ await runOnce()
173
+ } catch (err) {
174
+ process.stderr.write(`headlesscode codemap: ${err instanceof Error ? err.message : String(err)}\n`)
175
+ }
176
+ await Promise.race([
177
+ sleep(options.intervalMs),
178
+ new Promise<void>((resolve) => controller.signal.addEventListener("abort", () => resolve(), { once: true })),
179
+ ])
180
+ }
181
+ } finally {
182
+ process.off("SIGINT", onSignal)
183
+ process.off("SIGTERM", onSignal)
184
+ }
185
+ process.stdout.write(`codemap: stopped\n`)
186
+ return 0
187
+ }
188
+
189
+ /** Convenience for tests/scripts: absolute path of the stored codemap.json. */
190
+ export { codemapJsonPath }