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,78 @@
1
+ /**
2
+ * CALL-edge extraction for the codemap's Phase 2 (issue #18): which function
3
+ * in module A calls which function in module B — resolved with the real TS
4
+ * checker, not text matching.
5
+ *
6
+ * For every CallExpression/NewExpression in the program we ask the type
7
+ * checker for the RESOLVED signature and take its `declaration` — the actual
8
+ * function/method/constructor the call binds to. The declaration's source
9
+ * file is the edge's target. This is the same resolution the language
10
+ * service uses for go-to-definition, so overloads, re-exports, aliased
11
+ * imports and method calls all land on the real implementation (the
12
+ * deterministic answer the issue's "no LLM" requirement demands — no
13
+ * name-matching heuristics anywhere).
14
+ *
15
+ * Call targets that resolve OUTSIDE the scanned workspace (lib.d.ts,
16
+ * node_modules) or don't resolve at all (calls on `any`, dynamic JS calls)
17
+ * are carried with `resolvedFile: undefined` — the consumer (codemap's
18
+ * extractor) drops them, because external call targets are already visible
19
+ * through the module's import edges + externalDeps.
20
+ */
21
+
22
+ import * as ts from "typescript"
23
+
24
+ import type { CodeIntel } from "./program.js"
25
+
26
+ /** One call site in a source file, resolved to its real callee declaration. */
27
+ export interface CallEdge {
28
+ /** Human-readable callee expression as written, e.g. "greet", "obj.method", "Foo". */
29
+ callee: string
30
+ /** Absolute path of the file declaring the callee (undefined when unresolvable). */
31
+ resolvedFile: string | undefined
32
+ }
33
+
34
+ /** Per absolute file path: every call site in that file. */
35
+ export type CallGraph = Map<string, CallEdge[]>
36
+
37
+ /**
38
+ * Build (or return the cached) call graph for the program. A pure function
39
+ * of the program's source files, so it is computed once and reused until the
40
+ * program itself is rebuilt — same caching idiom as the import graph.
41
+ *
42
+ * Walking every program source file (not just `projectFiles`) matters: a
43
+ * module can be part of the program because a project file imports it, and
44
+ * calls FROM it must still resolve.
45
+ */
46
+ export function getCallGraph(intel: CodeIntel): CallGraph {
47
+ const cached = (intel as { __callGraph?: CallGraph }).__callGraph
48
+ if (cached !== undefined) {
49
+ return cached
50
+ }
51
+
52
+ const checker = intel.program.getTypeChecker()
53
+ const graph: CallGraph = new Map()
54
+
55
+ for (const sourceFile of intel.program.getSourceFiles()) {
56
+ const edges: CallEdge[] = []
57
+
58
+ const collect = (node: ts.Node): void => {
59
+ if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
60
+ const signature = checker.getResolvedSignature(node)
61
+ const declaration = signature?.declaration
62
+ edges.push({
63
+ callee: node.expression.getText(sourceFile),
64
+ resolvedFile: declaration?.getSourceFile().fileName,
65
+ })
66
+ }
67
+ ts.forEachChild(node, collect)
68
+ }
69
+ collect(sourceFile)
70
+
71
+ if (edges.length > 0) {
72
+ graph.set(sourceFile.fileName, edges)
73
+ }
74
+ }
75
+
76
+ ;(intel as { __callGraph?: CallGraph }).__callGraph = graph
77
+ return graph
78
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * `find_references` analysis: every real usage site of the symbol at
3
+ * (file, line[, character | symbol]) via the language service's real
4
+ * reference finding — `getReferencesAtPosition` — never grep.
5
+ *
6
+ * Output is compact `file:line` entries plus a short snippet of the line
7
+ * itself, so the model doesn't have to read each file just to see context.
8
+ * Results are capped (see MAX_REFERENCES): a symbol with a huge number of
9
+ * usages (e.g. a common utility) must not dump hundreds of lines back into
10
+ * the model's context, which would defeat the point of the tool.
11
+ */
12
+
13
+ import * as ts from "typescript"
14
+
15
+ import { scriptKindFromFileName, type CodeIntel } from "./program.js"
16
+ import { identifierAtPosition } from "./position.js"
17
+
18
+ export interface ReferenceSite {
19
+ fileName: string
20
+ line: number
21
+ character: number
22
+ /**
23
+ * Absolute character offset of the identifier's start within `fileName`
24
+ * (the language service's raw `textSpan.start`). The tools only need
25
+ * line/column for display, but `rename_symbol` needs the exact offset to
26
+ * edit the file — computed here once instead of re-derived per consumer.
27
+ */
28
+ start: number
29
+ /** Write access (assignment / declaration) vs read access. */
30
+ isWriteAccess: boolean
31
+ snippet: string
32
+ }
33
+
34
+ /** Hard cap on reference entries per call (keeps model context bounded). */
35
+ export const MAX_REFERENCES = 50
36
+
37
+ /** Read a line from the program's snapshot or straight from disk. */
38
+ function snippetFor(intel: CodeIntel, fileName: string, line1based: number): string {
39
+ const sourceFile = intel.program.getSourceFile(fileName)
40
+ const text = sourceFile?.text ?? (ts.sys.readFile(fileName) ?? "")
41
+ const line = text.split(/\r?\n/)[line1based - 1]
42
+ if (line === undefined) {
43
+ return ""
44
+ }
45
+ const trimmed = line.trim()
46
+ return trimmed.length > 120 ? `${trimmed.slice(0, 120)}…` : trimmed
47
+ }
48
+
49
+ /** Parse a file standalone (for files created after the program was loaded). */
50
+ function readStandalone(file: string): ts.SourceFile | undefined {
51
+ const text = ts.sys.readFile(file)
52
+ if (text === undefined) {
53
+ return undefined
54
+ }
55
+ return ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, scriptKindFromFileName(file))
56
+ }
57
+
58
+ /**
59
+ * Find all references to the symbol at `line` (1-based) in `file`.
60
+ * `character` (1-based column) and/or `symbol` (name) disambiguate when the
61
+ * line holds multiple identifiers. The result includes the declaration site
62
+ * itself (the language service counts it as a "reference").
63
+ */
64
+ export function findReferences(
65
+ intel: CodeIntel,
66
+ file: string,
67
+ line: number,
68
+ character: number | undefined,
69
+ symbol: string | undefined,
70
+ ): ReferenceSite[] {
71
+ const sourceFile = intel.program.getSourceFile(file) ?? readStandalone(file)
72
+ if (sourceFile === undefined) {
73
+ throw new Error(`file is not part of the loaded TypeScript program: ${file}`)
74
+ }
75
+ const identifier = identifierAtPosition(sourceFile, line, character, symbol)
76
+ if (identifier === undefined) {
77
+ return []
78
+ }
79
+
80
+ intel.includeExtraFile(file)
81
+ const references = intel.languageService.getReferencesAtPosition(file, identifier.getStart(sourceFile)) ?? []
82
+
83
+ const seen = new Set<string>()
84
+ const sites: ReferenceSite[] = []
85
+ for (const ref of references) {
86
+ const key = `${ref.fileName}:${ref.textSpan.start}`
87
+ if (seen.has(key)) {
88
+ continue
89
+ }
90
+ seen.add(key)
91
+ // Line/column must be computed against the REFERENCE's own file, which
92
+ // may differ from the file we started from (references span the program).
93
+ const refSourceFile = intel.program.getSourceFile(ref.fileName)
94
+ if (refSourceFile === undefined) {
95
+ continue
96
+ }
97
+ const lineAndChar = ts.getLineAndCharacterOfPosition(refSourceFile, ref.textSpan.start)
98
+ sites.push({
99
+ fileName: ref.fileName,
100
+ line: lineAndChar.line + 1,
101
+ character: lineAndChar.character + 1,
102
+ start: ref.textSpan.start,
103
+ isWriteAccess: ref.isWriteAccess,
104
+ snippet: snippetFor(intel, ref.fileName, lineAndChar.line + 1),
105
+ })
106
+ }
107
+ sites.sort((a, b) => a.fileName.localeCompare(b.fileName) || a.line - b.line)
108
+ return sites
109
+ }
110
+
111
+ /** Render reference sites as compact `file:line — snippet` lines, capped. */
112
+ export function formatReferences(rel: (p: string) => string, sites: ReferenceSite[]): string {
113
+ if (sites.length === 0) {
114
+ return "No references found for that symbol."
115
+ }
116
+ const shown = sites.slice(0, MAX_REFERENCES)
117
+ const lines = shown.map((r) => `${rel(r.fileName)}:${r.line}${r.isWriteAccess ? " (write)" : ""} — ${r.snippet}`)
118
+ const overflow = sites.length - shown.length
119
+ if (overflow > 0) {
120
+ lines.push(`…${overflow} more reference(s) not shown — narrow your search to a more specific symbol`)
121
+ }
122
+ return lines.join("\n")
123
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * `go_to_definition` analysis: resolve a symbol at (file, line[, character |
3
+ * symbol]) to its real definition site(s) via the type checker's actual
4
+ * symbol resolution — the LanguageService's `getDefinitionAtPosition` — not a
5
+ * text search.
6
+ *
7
+ * Handles the three hard cases the spec calls out:
8
+ * - definition in a `.d.ts` / vendored / `node_modules` location: returned
9
+ * as the real location, never refused for being outside `src/`
10
+ * - symbol with no resolvable definition (ambient / `any`-typed /
11
+ * genuinely unresolvable): a clear "could not resolve" result, not a crash
12
+ * - multiple candidate definitions (e.g. an overloaded function): ALL of
13
+ * them are returned, never one silently picked
14
+ */
15
+
16
+ import * as path from "node:path"
17
+ import * as ts from "typescript"
18
+
19
+ import { scriptKindFromFileName, type CodeIntel } from "./program.js"
20
+ import { identifierAtPosition } from "./position.js"
21
+
22
+ export interface DefinitionSite {
23
+ /** Absolute path of the defining file. */
24
+ fileName: string
25
+ /** 1-based line + 1-based column of the definition's name. */
26
+ line: number
27
+ character: number
28
+ kind: string
29
+ name: string
30
+ /** Container (e.g. class/namespace) the definition lives in, if any. */
31
+ containerName?: string
32
+ /** The full text of the definition's line, for context without a read_file. */
33
+ snippet: string
34
+ }
35
+
36
+ /** Read a definition's line text from the program or straight from disk. */
37
+ function snippetFor(intel: CodeIntel, fileName: string, line1based: number): string {
38
+ const sourceFile = intel.program.getSourceFile(fileName)
39
+ const text = sourceFile?.text ?? (ts.sys.readFile(fileName) ?? "")
40
+ const lines = text.split(/\r?\n/)
41
+ const line = lines[line1based - 1]
42
+ if (line === undefined) {
43
+ return ""
44
+ }
45
+ const trimmed = line.trim()
46
+ return trimmed.length > 120 ? `${trimmed.slice(0, 120)}…` : trimmed
47
+ }
48
+
49
+ /**
50
+ * Resolve the definition(s) of the symbol at `line` (1-based) in `file`.
51
+ * `character` (1-based column) and/or `symbol` (name) disambiguate when the
52
+ * line holds multiple identifiers.
53
+ */
54
+ export function findDefinition(
55
+ intel: CodeIntel,
56
+ file: string,
57
+ line: number,
58
+ character: number | undefined,
59
+ symbol: string | undefined,
60
+ ): DefinitionSite[] {
61
+ const sourceFile = intel.program.getSourceFile(file) ?? readStandalone(file)
62
+ if (sourceFile === undefined) {
63
+ throw new Error(`file is not part of the loaded TypeScript program: ${file}`)
64
+ }
65
+ const identifier = identifierAtPosition(sourceFile, line, character, symbol)
66
+ if (identifier === undefined) {
67
+ return []
68
+ }
69
+
70
+ intel.includeExtraFile(file)
71
+ const definitions = intel.languageService.getDefinitionAtPosition(file, identifier.getStart(sourceFile)) ?? []
72
+
73
+ // The language service returns ONE definition per position — but an
74
+ // overloaded function is one symbol with several declarations, and the spec
75
+ // says return ALL candidates, never one silently picked. Expand through the
76
+ // type checker's symbol: resolve alias imports to their target, then take
77
+ // every declaration the symbol has.
78
+ const checker = intel.program.getTypeChecker()
79
+ let resolvedSymbol = checker.getSymbolAtLocation(identifier)
80
+ if (resolvedSymbol !== undefined && (resolvedSymbol.flags & ts.SymbolFlags.Alias) !== 0) {
81
+ resolvedSymbol = checker.getAliasedSymbol(resolvedSymbol)
82
+ }
83
+ const symbolDeclarations = (resolvedSymbol?.declarations ?? []).map(declarationSite)
84
+
85
+ // Dedup by file+line: the language service's answer and the checker's
86
+ // declaration list usually overlap (non-overloaded symbols appear once).
87
+ const seen = new Set<string>()
88
+ const sites: DefinitionSite[] = []
89
+ const push = (site: DefinitionSite): void => {
90
+ const key = `${site.fileName}:${site.line}:${site.character}`
91
+ if (seen.has(key)) {
92
+ return
93
+ }
94
+ seen.add(key)
95
+ sites.push(site)
96
+ }
97
+
98
+ for (const def of definitions) {
99
+ // Line/column must be computed against the DEFINITION's own file
100
+ // (it may not even be in the program — e.g. a d.ts pulled from
101
+ // node_modules is, but a definition in an untyped extra file is not).
102
+ const defSourceFile = intel.program.getSourceFile(def.fileName) ?? readStandalone(def.fileName)
103
+ if (defSourceFile === undefined) {
104
+ continue
105
+ }
106
+ const lineAndChar = ts.getLineAndCharacterOfPosition(defSourceFile, def.textSpan.start)
107
+ push({
108
+ fileName: def.fileName,
109
+ line: lineAndChar.line + 1,
110
+ character: lineAndChar.character + 1,
111
+ kind: def.kind,
112
+ name: def.name,
113
+ containerName: def.containerName,
114
+ snippet: snippetFor(intel, def.fileName, lineAndChar.line + 1),
115
+ })
116
+ }
117
+ for (const site of symbolDeclarations) {
118
+ push(site)
119
+ }
120
+ return sites.sort((a, b) => a.fileName.localeCompare(b.fileName) || a.line - b.line)
121
+ }
122
+
123
+ /** Short kind label for a checker symbol declaration (overload expansion). */
124
+ function kindOfDeclaration(decl: ts.Declaration): string {
125
+ if (ts.isFunctionDeclaration(decl)) return "function"
126
+ if (ts.isClassDeclaration(decl)) return "class"
127
+ if (ts.isInterfaceDeclaration(decl)) return "interface"
128
+ if (ts.isTypeAliasDeclaration(decl)) return "type"
129
+ if (ts.isEnumDeclaration(decl)) return "enum"
130
+ if (ts.isModuleDeclaration(decl)) return "namespace"
131
+ if (ts.isMethodDeclaration(decl) || ts.isMethodSignature(decl)) return "method"
132
+ if (ts.isPropertyDeclaration(decl) || ts.isPropertySignature(decl)) return "property"
133
+ if (ts.isGetAccessorDeclaration(decl)) return "getter"
134
+ if (ts.isSetAccessorDeclaration(decl)) return "setter"
135
+ if (ts.isVariableDeclaration(decl)) {
136
+ const list = decl.parent
137
+ if (ts.isVariableDeclarationList(list)) {
138
+ if (list.flags & ts.NodeFlags.Const) return "const"
139
+ if (list.flags & ts.NodeFlags.Let) return "let"
140
+ return "var"
141
+ }
142
+ return "variable"
143
+ }
144
+ return "declaration"
145
+ }
146
+
147
+ /** A definition site from a raw declaration node (name anchor, own file). */
148
+ function declarationSite(decl: ts.Declaration): DefinitionSite {
149
+ const sourceFile = decl.getSourceFile()
150
+ const nameNode = (decl as { name?: ts.Node }).name
151
+ const anchor = nameNode ?? decl
152
+ const lineAndChar = ts.getLineAndCharacterOfPosition(sourceFile, anchor.getStart(sourceFile))
153
+ const lineText = sourceFile.text.split(/\r?\n/)[lineAndChar.line] ?? ""
154
+ const trimmed = lineText.trim()
155
+ return {
156
+ fileName: sourceFile.fileName,
157
+ line: lineAndChar.line + 1,
158
+ character: lineAndChar.character + 1,
159
+ kind: kindOfDeclaration(decl),
160
+ name: nameNode !== undefined && ts.isIdentifier(nameNode) ? nameNode.text : "",
161
+ snippet: trimmed.length > 120 ? `${trimmed.slice(0, 120)}…` : trimmed,
162
+ }
163
+ }
164
+
165
+ /** Parse a file standalone (for files created after the program was loaded). */
166
+ function readStandalone(file: string): ts.SourceFile | undefined {
167
+ const text = ts.sys.readFile(file)
168
+ if (text === undefined) {
169
+ return undefined
170
+ }
171
+ return ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, scriptKindFromFileName(file))
172
+ }
173
+
174
+ /** Render definition sites as compact, dedup-friendly lines. */
175
+ export function formatDefinitions(rel: (p: string) => string, defs: DefinitionSite[]): string {
176
+ if (defs.length === 0) {
177
+ return "Could not resolve a definition for that symbol (it may be ambient, any-typed, or otherwise unresolvable by the type checker)."
178
+ }
179
+ return defs
180
+ .map(
181
+ (d) =>
182
+ `${rel(d.fileName)}:${d.line}:${d.character} ${d.kind} ${d.name}${d.containerName ? ` (in ${d.containerName})` : ""} — ${d.snippet}`,
183
+ )
184
+ .join("\n")
185
+ }
186
+
187
+ /** Relative-path formatter used by the handlers (stable ordering across tools). */
188
+ export function makeRelativizer(workspaceRoot: string): (p: string) => string {
189
+ return (p) => {
190
+ const rel = path.relative(workspaceRoot, p)
191
+ return rel === "" || rel.startsWith("..") ? p : rel.split(path.sep).join("/")
192
+ }
193
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Tool handlers for the four code-intelligence tools (`outline`,
3
+ * `go_to_definition`, `find_references`, `import_graph`).
4
+ *
5
+ * Thin layer over the shared analysis module (src/codeintel/program.ts +
6
+ * the per-tool analyzers): argument parsing, path safety, the "not supported
7
+ * for this file type" honesty rule, and result formatting. All four share ONE
8
+ * cached `ts.Program` per workspace root — see program.ts for the caching +
9
+ * mtime-invalidation contract.
10
+ *
11
+ * Non-TS/JS files NEVER produce a guessed answer: they get a clear
12
+ * "[Error] ... not a supported file type" result. Missing files and files
13
+ * that escaped the workspace root error the same way the rest of the
14
+ * executor does.
15
+ */
16
+
17
+ import * as fs from "node:fs"
18
+ import * as path from "node:path"
19
+ import * as ts from "typescript"
20
+
21
+ import type { ToolContext, ToolResult } from "../engine/types.js"
22
+ import { resolveWithinWorkspace } from "../tools/executor.js"
23
+ import { getCodeIntelCache, isSupportedSource, scriptKindFromFileName } from "./program.js"
24
+ import { formatOutline, outlineSourceFile } from "./outline.js"
25
+ import { findDefinition, formatDefinitions, makeRelativizer } from "./go-to-definition.js"
26
+ import { findReferences, formatReferences } from "./find-references.js"
27
+ import { formatImportGraph, getImportGraph } from "./import-graph.js"
28
+ import { formatRenameResult, renameSymbol } from "./rename-symbol.js"
29
+
30
+ function ok(content: string): ToolResult {
31
+ return { content, isError: false }
32
+ }
33
+
34
+ function err(content: string): ToolResult {
35
+ return { content: `[Error] ${content}`, isError: true }
36
+ }
37
+
38
+ function requireStringArg(args: Record<string, unknown>, key: string, tool: string): string {
39
+ const v = args[key]
40
+ if (typeof v !== "string" || v.trim() === "") {
41
+ throw new Error(`${tool}: missing or invalid string argument '${key}' (got ${JSON.stringify(v)})`)
42
+ }
43
+ return v
44
+ }
45
+
46
+ function optionalStringArg(args: Record<string, unknown>, key: string): string | undefined {
47
+ const v = args[key]
48
+ if (v === undefined || v === null) {
49
+ return undefined
50
+ }
51
+ if (typeof v !== "string") {
52
+ return undefined
53
+ }
54
+ return v.trim() === "" ? undefined : v
55
+ }
56
+
57
+ /** Parse a 1-based line (required) or column (optional); invalid → throw/undefined. */
58
+ function lineArg(args: Record<string, unknown>, tool: string): number {
59
+ const v = args["line"]
60
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN
61
+ if (!Number.isInteger(n) || n < 1) {
62
+ throw new Error(`${tool}: invalid line '${JSON.stringify(v)}' — must be a 1-based line number`)
63
+ }
64
+ return n
65
+ }
66
+
67
+ function optionalCharacterArg(args: Record<string, unknown>): number | undefined {
68
+ const v = args["character"]
69
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN
70
+ if (Number.isInteger(n) && n >= 1) {
71
+ return n
72
+ }
73
+ return undefined
74
+ }
75
+
76
+ /** Path-safety-checked absolute target + existence/type/support validation. */
77
+ function resolveTarget(ctx: ToolContext, tool: string, p: string): string {
78
+ const target = resolveWithinWorkspace(ctx.workspaceRoot, p)
79
+ const rel = path.relative(ctx.workspaceRoot, target).split(path.sep).join("/") || path.basename(target)
80
+
81
+ let stat: fs.Stats
82
+ try {
83
+ stat = fs.statSync(target)
84
+ } catch {
85
+ throw new Error(`${tool}: cannot stat '${rel}' — file does not exist`)
86
+ }
87
+ if (!stat.isFile()) {
88
+ throw new Error(`${tool}: '${rel}' is not a file`)
89
+ }
90
+ if (!isSupportedSource(target)) {
91
+ throw new Error(
92
+ `${tool}: '${rel}' is not a supported file type — code-intelligence tools work on TypeScript/JavaScript source files only (.ts/.tsx/.mts/.cts/.js/.jsx)`,
93
+ )
94
+ }
95
+ return target
96
+ }
97
+
98
+ /** The cached program intel for a workspace (load-or-reuse). */
99
+ function intelFor(ctx: ToolContext) {
100
+ return getCodeIntelCache(ctx.workspaceRoot).get()
101
+ }
102
+
103
+ // ─── outline ─────────────────────────────────────────────────────────────────
104
+
105
+ export function outlineHandler(args: Record<string, unknown>, ctx: ToolContext): ToolResult {
106
+ const tool = "outline"
107
+ const filePath = requireStringArg(args, "path", tool)
108
+ const target = resolveTarget(ctx, tool, filePath)
109
+ const rel = path.relative(ctx.workspaceRoot, target).split(path.sep).join("/") || path.basename(target)
110
+
111
+ const intel = intelFor(ctx)
112
+ const sourceFile = intel.program.getSourceFile(target) ?? readStandalone(target)
113
+ if (sourceFile === undefined) {
114
+ return err(`${tool}: cannot parse '${rel}' as TypeScript`)
115
+ }
116
+ const entries = outlineSourceFile(sourceFile)
117
+ return ok(`Outline of ${rel}:\n${formatOutline(entries)}`)
118
+ }
119
+
120
+ // ─── go_to_definition ────────────────────────────────────────────────────────
121
+
122
+ export function goToDefinitionHandler(args: Record<string, unknown>, ctx: ToolContext): ToolResult {
123
+ const tool = "go_to_definition"
124
+ const filePath = requireStringArg(args, "path", tool)
125
+ const target = resolveTarget(ctx, tool, filePath)
126
+ const line = lineArg(args, tool)
127
+ const character = optionalCharacterArg(args)
128
+ const symbol = optionalStringArg(args, "symbol")
129
+
130
+ const intel = intelFor(ctx)
131
+ const defs = findDefinition(intel, target, line, character, symbol)
132
+ const rel = makeRelativizer(ctx.workspaceRoot)
133
+ return ok(formatDefinitions(rel, defs))
134
+ }
135
+
136
+ // ─── find_references ─────────────────────────────────────────────────────────
137
+
138
+ export function findReferencesHandler(args: Record<string, unknown>, ctx: ToolContext): ToolResult {
139
+ const tool = "find_references"
140
+ const filePath = requireStringArg(args, "path", tool)
141
+ const target = resolveTarget(ctx, tool, filePath)
142
+ const line = lineArg(args, tool)
143
+ const character = optionalCharacterArg(args)
144
+ const symbol = optionalStringArg(args, "symbol")
145
+
146
+ const intel = intelFor(ctx)
147
+ const sites = findReferences(intel, target, line, character, symbol)
148
+ const rel = makeRelativizer(ctx.workspaceRoot)
149
+ return ok(formatReferences(rel, sites))
150
+ }
151
+
152
+ // ─── rename_symbol ───────────────────────────────────────────────────────────
153
+
154
+ export function renameSymbolHandler(args: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult> {
155
+ const tool = "rename_symbol"
156
+ const filePath = requireStringArg(args, "path", tool)
157
+ const target = resolveTarget(ctx, tool, filePath)
158
+ const line = lineArg(args, tool)
159
+ const character = optionalCharacterArg(args)
160
+ const symbol = optionalStringArg(args, "symbol")
161
+ const newName = requireStringArg(args, "new_name", tool)
162
+
163
+ const intel = intelFor(ctx)
164
+ // Throws (wrapped by ToolExecutor.execute as `Tool 'rename_symbol' failed:
165
+ // …`) on any validation failure or a mid-write failure after rollback.
166
+ return renameSymbol(intel, ctx.workspaceRoot, target, line, character, symbol, newName).then((result) =>
167
+ ok(formatRenameResult(ctx.workspaceRoot, result)),
168
+ )
169
+ }
170
+
171
+ // ─── import_graph ────────────────────────────────────────────────────────────
172
+
173
+ export function importGraphHandler(args: Record<string, unknown>, ctx: ToolContext): ToolResult {
174
+ const tool = "import_graph"
175
+ const filePath = requireStringArg(args, "path", tool)
176
+ const target = resolveTarget(ctx, tool, filePath)
177
+
178
+ const intel = intelFor(ctx)
179
+ const graph = getImportGraph(intel)
180
+ return ok(formatImportGraph(ctx.workspaceRoot, target, graph))
181
+ }
182
+
183
+ /** Parse a source file standalone (for files created after program load). */
184
+ function readStandalone(file: string): ts.SourceFile | undefined {
185
+ const text = ts.sys.readFile(file)
186
+ if (text === undefined) {
187
+ return undefined
188
+ }
189
+ return ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, scriptKindFromFileName(file))
190
+ }