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,173 @@
1
+ /**
2
+ * `import_graph` analysis: (a) what a file imports, resolved to real file
3
+ * paths (not raw specifiers), and (b) what imports it (reverse lookup).
4
+ *
5
+ * Static analysis of `ts.ImportDeclaration` / `ts.ExportDeclaration` /
6
+ * `ts.ImportEqualsDeclaration` nodes — imports are syntactic, not
7
+ * type-dependent, so this needs no type-checking: specifiers are resolved
8
+ * with `ts.resolveModuleName` against the cached program's compiler options
9
+ * (so `paths` aliases like `vscode` → the shim resolve exactly as the
10
+ * compiler would).
11
+ *
12
+ * The whole-program import map is built ONCE per loaded program and cached
13
+ * alongside it (see CodeIntelCache): reverse lookups never rescan the
14
+ * project per call.
15
+ */
16
+
17
+ import * as path from "node:path"
18
+ import * as ts from "typescript"
19
+
20
+ import type { CodeIntel } from "./program.js"
21
+
22
+ export interface ImportEdge {
23
+ /** Raw module specifier as written in source (e.g. "./foo" or "vscode"). */
24
+ specifier: string
25
+ /** Real resolved file path (undefined when resolution fails). */
26
+ resolved: string | undefined
27
+ /** True for `export ... from` re-exports. */
28
+ isReExport: boolean
29
+ /** True for `import type` / `export type`. */
30
+ isTypeOnly: boolean
31
+ }
32
+
33
+ export interface ImportGraph {
34
+ /** Per absolute file path: everything it imports. */
35
+ imports: Map<string, ImportEdge[]>
36
+ /** Per resolved file path: every absolute file path that imports it. */
37
+ importedBy: Map<string, string[]>
38
+ }
39
+
40
+ /** Resolve a module specifier the same way the compiler would. */
41
+ function resolveSpecifier(
42
+ intel: CodeIntel,
43
+ containingFile: string,
44
+ specifier: string,
45
+ ): string | undefined {
46
+ const result = ts.resolveModuleName(specifier, containingFile, intel.compilerOptions, {
47
+ fileExists: ts.sys.fileExists,
48
+ readFile: ts.sys.readFile,
49
+ getCurrentDirectory: () => path.dirname(containingFile),
50
+ directoryExists: ts.sys.directoryExists,
51
+ })
52
+ return result.resolvedModule?.resolvedFileName
53
+ }
54
+
55
+ /**
56
+ * Build (or return the cached) import graph for the program. The graph is a
57
+ * pure function of the program's source files, so it is computed once and
58
+ * reused until the program itself is rebuilt.
59
+ */
60
+ export function getImportGraph(intel: CodeIntel): ImportGraph {
61
+ const cached = (intel as { __importGraph?: ImportGraph }).__importGraph
62
+ if (cached !== undefined) {
63
+ return cached
64
+ }
65
+
66
+ const imports = new Map<string, ImportEdge[]>()
67
+ const importedBy = new Map<string, string[]>()
68
+
69
+ const addReverseEdge = (resolved: string | undefined, importer: string): void => {
70
+ if (resolved === undefined) {
71
+ return
72
+ }
73
+ const list = importedBy.get(resolved) ?? []
74
+ if (!list.includes(importer)) {
75
+ list.push(importer)
76
+ importedBy.set(resolved, list)
77
+ }
78
+ }
79
+
80
+ for (const sourceFile of intel.program.getSourceFiles()) {
81
+ const fileImports: ImportEdge[] = []
82
+
83
+ const collect = (node: ts.Node): void => {
84
+ if (ts.isImportDeclaration(node)) {
85
+ if (ts.isStringLiteral(node.moduleSpecifier)) {
86
+ const specifier = node.moduleSpecifier.text
87
+ const resolved = resolveSpecifier(intel, sourceFile.fileName, specifier)
88
+ fileImports.push({
89
+ specifier,
90
+ resolved,
91
+ isReExport: false,
92
+ // The flag lives on the import clause, not the declaration.
93
+ isTypeOnly: node.importClause?.isTypeOnly === true,
94
+ })
95
+ addReverseEdge(resolved, sourceFile.fileName)
96
+ }
97
+ } else if (ts.isExportDeclaration(node)) {
98
+ // ExportDeclaration.moduleSpecifier is optional (`export {}`).
99
+ if (node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier)) {
100
+ const specifier = node.moduleSpecifier.text
101
+ const resolved = resolveSpecifier(intel, sourceFile.fileName, specifier)
102
+ fileImports.push({
103
+ specifier,
104
+ resolved,
105
+ isReExport: true,
106
+ isTypeOnly: node.isTypeOnly === true,
107
+ })
108
+ addReverseEdge(resolved, sourceFile.fileName)
109
+ }
110
+ } else if (ts.isImportEqualsDeclaration(node)) {
111
+ const ref = node.moduleReference
112
+ if (ts.isExternalModuleReference(ref) && ref.expression !== undefined && ts.isStringLiteral(ref.expression)) {
113
+ const specifier = ref.expression.text
114
+ const resolved = resolveSpecifier(intel, sourceFile.fileName, specifier)
115
+ fileImports.push({
116
+ specifier,
117
+ resolved,
118
+ isReExport: false,
119
+ isTypeOnly: false,
120
+ })
121
+ addReverseEdge(resolved, sourceFile.fileName)
122
+ }
123
+ }
124
+ }
125
+
126
+ ts.forEachChild(sourceFile, (node) => collect(node))
127
+ if (fileImports.length > 0) {
128
+ imports.set(sourceFile.fileName, fileImports)
129
+ }
130
+ }
131
+
132
+ const graph: ImportGraph = { imports, importedBy }
133
+ ;(intel as { __importGraph?: ImportGraph }).__importGraph = graph
134
+ return graph
135
+ }
136
+
137
+ /** Absolute path → workspace-relative (posix) for display; absolute if outside. */
138
+ function displayPath(workspaceRoot: string, p: string): string {
139
+ const rel = path.relative(workspaceRoot, p)
140
+ return rel === "" || rel.startsWith("..") ? p : rel.split(path.sep).join("/")
141
+ }
142
+
143
+ /** Render the import graph for one file. */
144
+ export function formatImportGraph(
145
+ workspaceRoot: string,
146
+ fileName: string,
147
+ graph: ImportGraph,
148
+ ): string {
149
+ const out: string[] = []
150
+
151
+ const edges = graph.imports.get(fileName) ?? []
152
+ out.push(`imports (${edges.length}):`)
153
+ if (edges.length === 0) {
154
+ out.push(" (none)")
155
+ }
156
+ for (const edge of edges) {
157
+ const target = edge.resolved !== undefined ? displayPath(workspaceRoot, edge.resolved) : `(unresolved: ${edge.specifier})`
158
+ const typeOnly = edge.isTypeOnly ? "type " : ""
159
+ const reExport = edge.isReExport ? "export " : "import "
160
+ out.push(` ${reExport}${typeOnly}${edge.specifier} -> ${target}`)
161
+ }
162
+
163
+ const importers = graph.importedBy.get(fileName) ?? []
164
+ out.push(`imported by (${importers.length}):`)
165
+ if (importers.length === 0) {
166
+ out.push(" (none)")
167
+ }
168
+ for (const importer of importers.sort()) {
169
+ out.push(` ${displayPath(workspaceRoot, importer)}`)
170
+ }
171
+
172
+ return out.join("\n")
173
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * `outline` analysis: every top-level (and one level of nested) declaration
3
+ * in a source file, as `line kind signature` — one compact line per symbol,
4
+ * signatures only, no bodies. Built on the real AST (`ts.forEachChild`), not
5
+ * regex: kinds come from the node types, lines from
6
+ * `ts.getLineAndCharacterOfPosition`, and signatures are the node's own
7
+ * source text cut at its body — so `14 function foo(x: string): void` is
8
+ * exactly what the source says, no guesswork.
9
+ */
10
+
11
+ import * as ts from "typescript"
12
+
13
+ export interface OutlineEntry {
14
+ /** 1-based line of the declaration's name. */
15
+ line: number
16
+ kind: string
17
+ signature: string
18
+ /** Nesting depth: 0 = top level, 1 = class/interface/namespace member. */
19
+ depth: number
20
+ }
21
+
22
+ /** Map a TS node to a short human kind label. */
23
+ function kindOf(node: ts.Node): string | undefined {
24
+ if (ts.isFunctionDeclaration(node)) return "function"
25
+ if (ts.isClassDeclaration(node)) return "class"
26
+ if (ts.isInterfaceDeclaration(node)) return "interface"
27
+ if (ts.isTypeAliasDeclaration(node)) return "type"
28
+ if (ts.isEnumDeclaration(node)) return "enum"
29
+ if (ts.isModuleDeclaration(node)) return "namespace"
30
+ if (ts.isVariableStatement(node)) return "var"
31
+ if (ts.isMethodDeclaration(node)) return "method"
32
+ if (ts.isPropertyDeclaration(node)) return "property"
33
+ if (ts.isGetAccessorDeclaration(node)) return "getter"
34
+ if (ts.isSetAccessorDeclaration(node)) return "setter"
35
+ if (ts.isPropertySignature(node)) return "property"
36
+ if (ts.isMethodSignature(node)) return "method"
37
+ if (ts.isCallSignatureDeclaration(node)) return "call-signature"
38
+ if (ts.isConstructSignatureDeclaration(node)) return "construct-signature"
39
+ if (ts.isIndexSignatureDeclaration(node)) return "index-signature"
40
+ return undefined
41
+ }
42
+
43
+ /** True for function-like nodes whose body is a block we must cut off. */
44
+ function hasBlockBody(node: ts.Node): boolean {
45
+ return (
46
+ ts.isFunctionDeclaration(node) ||
47
+ ts.isMethodDeclaration(node) ||
48
+ ts.isGetAccessorDeclaration(node) ||
49
+ ts.isSetAccessorDeclaration(node)
50
+ )
51
+ }
52
+
53
+ /** A container whose members are worth outlining one level deep. */
54
+ function isContainer(node: ts.Node): boolean {
55
+ return (
56
+ ts.isClassDeclaration(node) ||
57
+ ts.isInterfaceDeclaration(node) ||
58
+ ts.isModuleDeclaration(node) ||
59
+ ts.isEnumDeclaration(node)
60
+ )
61
+ }
62
+
63
+ /**
64
+ * The declaration's signature text WITHOUT its body: for functions, source
65
+ * from the node start up to the body's `{`; for types/classes/interfaces,
66
+ * up to the first `{` or `;`. Internal whitespace is collapsed to one space
67
+ * so multi-line signatures stay on a single output line.
68
+ */
69
+ function signatureOf(node: ts.Node, sourceFile: ts.SourceFile): string {
70
+ const full = node.getText(sourceFile)
71
+ let cut = full
72
+ if (hasBlockBody(node)) {
73
+ const body = (node as ts.FunctionLikeDeclaration).body
74
+ if (body !== undefined) {
75
+ cut = full.slice(0, body.getStart(sourceFile) - node.getStart(sourceFile))
76
+ }
77
+ } else {
78
+ const brace = cut.indexOf("{")
79
+ const semi = cut.indexOf(";")
80
+ if (brace !== -1 && (semi === -1 || brace < semi)) {
81
+ cut = cut.slice(0, brace)
82
+ } else if (semi !== -1) {
83
+ cut = cut.slice(0, semi)
84
+ }
85
+ }
86
+ return stripLeadingKeywords(cut.replace(/\s+/g, " ").trim())
87
+ }
88
+
89
+ /**
90
+ * Leading tokens to drop from a signature: visibility/export modifiers and the
91
+ * declaration keyword itself (`export function foo()`, `abstract class Shape`,
92
+ * `public readonly x`). The kind column already prints the keyword, so keeping
93
+ * it would duplicate ("function export function foo()").
94
+ */
95
+ const STRIP_PREFIX = [
96
+ "export", "default", "declare", "abstract", "async",
97
+ "public", "private", "protected", "readonly", "static", "override",
98
+ "function", "class", "interface", "type", "enum", "namespace", "module",
99
+ "const", "let", "var",
100
+ ]
101
+
102
+ /** Strip leading modifier/kind tokens, keeping the name onward intact. */
103
+ function stripLeadingKeywords(text: string): string {
104
+ const words = text.split(/\s+/)
105
+ let i = 0
106
+ while (i < words.length && STRIP_PREFIX.includes(words[i]!)) {
107
+ i++
108
+ }
109
+ return words.slice(i).join(" ")
110
+ }
111
+
112
+ /** Variable statements declare each name separately (kind from the statement). */
113
+ function variableEntries(stmt: ts.VariableStatement, sourceFile: ts.SourceFile): OutlineEntry[] {
114
+ const flags = stmt.declarationList.flags
115
+ const kind = flags & ts.NodeFlags.Const ? "const" : flags & ts.NodeFlags.Let ? "let" : "var"
116
+ return stmt.declarationList.declarations.map((decl) => {
117
+ const name = decl.name.getText(sourceFile)
118
+ const type = decl.type !== undefined ? decl.type.getText(sourceFile) : undefined
119
+ return {
120
+ line: ts.getLineAndCharacterOfPosition(sourceFile, decl.getStart(sourceFile)).line + 1,
121
+ kind,
122
+ // The kind column already prints the keyword — don't repeat it.
123
+ signature: `${name}${type !== undefined ? `: ${type}` : ""}`,
124
+ depth: 0,
125
+ }
126
+ })
127
+ }
128
+
129
+ /**
130
+ * Outline a source file. `maxDepth` controls how deep into the declaration
131
+ * tree we go: 0 = top level only, 1 (default) = also members of classes,
132
+ * interfaces, and namespaces. Function bodies are never descended into —
133
+ * nested locals are noise for a structure overview.
134
+ */
135
+ export function outlineSourceFile(sourceFile: ts.SourceFile, maxDepth = 1): OutlineEntry[] {
136
+ const entries: OutlineEntry[] = []
137
+
138
+ const walk = (node: ts.Node, depth: number): void => {
139
+ if (ts.isVariableStatement(node)) {
140
+ entries.push(...variableEntries(node, sourceFile))
141
+ } else {
142
+ const kind = kindOf(node)
143
+ if (kind !== undefined) {
144
+ const named = node as { name?: ts.Node }
145
+ entries.push({
146
+ line:
147
+ ts.getLineAndCharacterOfPosition(sourceFile, named.name ? named.name.getStart(sourceFile) : node.getStart(sourceFile))
148
+ .line + 1,
149
+ kind,
150
+ signature: signatureOf(node, sourceFile),
151
+ depth,
152
+ })
153
+ }
154
+ }
155
+
156
+ if (depth >= maxDepth) {
157
+ return
158
+ }
159
+ // Never look inside function bodies; containers get one deeper level.
160
+ if (hasBlockBody(node) || ts.isVariableStatement(node)) {
161
+ return
162
+ }
163
+ if (isContainer(node)) {
164
+ ts.forEachChild(node, (child) => walk(child, depth + 1))
165
+ return
166
+ }
167
+ ts.forEachChild(node, (child) => walk(child, depth))
168
+ }
169
+
170
+ ts.forEachChild(sourceFile, (child) => walk(child, 0))
171
+ return entries
172
+ }
173
+
174
+ /** Render outline entries as the compact `line kind signature` lines. */
175
+ export function formatOutline(entries: OutlineEntry[]): string {
176
+ if (entries.length === 0) {
177
+ return "No declarations found in this file."
178
+ }
179
+ return entries.map((e) => `${" ".repeat(e.depth)}${e.line} ${e.kind} ${e.signature}`).join("\n")
180
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Position → identifier resolution shared by `go_to_definition` and
3
+ * `find_references`.
4
+ *
5
+ * The TS LanguageService APIs (`getDefinitionAtPosition`,
6
+ * `getReferencesAtPosition`) take a character OFFSET into the file, not a
7
+ * line number — so the tools' `{ line, character?, symbol? }` parameter
8
+ * shape is resolved here into a concrete `ts.Identifier` the LS can answer
9
+ * about:
10
+ * - `character` given (1-based column): the identifier touching that column
11
+ * - `symbol` given: the first identifier on the line whose text matches
12
+ * - neither: the first identifier on the line
13
+ *
14
+ * Identifiers come from a real AST walk (`ts.forEachChild`), so text inside
15
+ * strings, comments, and import specifiers is naturally excluded — except
16
+ * import clause names (`import { foo }` parses `foo` as an Identifier), which
17
+ * is exactly what we want: "go to definition" on an imported name must
18
+ * resolve through the checker to its real declaration.
19
+ */
20
+
21
+ import * as ts from "typescript"
22
+
23
+ /** Collect every Identifier whose text range falls on `line` (1-based). */
24
+ export function identifiersOnLine(sourceFile: ts.SourceFile, line: number): ts.Identifier[] {
25
+ const line0 = line - 1
26
+ const found: ts.Identifier[] = []
27
+ const visit = (node: ts.Node): void => {
28
+ if (ts.isIdentifier(node)) {
29
+ const { line: nodeLine } = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile))
30
+ if (nodeLine === line0) {
31
+ found.push(node)
32
+ }
33
+ }
34
+ ts.forEachChild(node, visit)
35
+ }
36
+ visit(sourceFile)
37
+ return found
38
+ }
39
+
40
+ /**
41
+ * Resolve a (1-based line, optional 1-based character) + optional symbol name
42
+ * to the identifier a user means, or undefined when nothing resolvable sits
43
+ * at that spot. "Touching" semantics: a character pointing at the LAST char
44
+ * of an identifier still selects it, so a model that guessed the column one
45
+ * short of the end still gets the right symbol.
46
+ */
47
+ export function identifierAtPosition(
48
+ sourceFile: ts.SourceFile,
49
+ line: number,
50
+ character: number | undefined,
51
+ symbol: string | undefined,
52
+ ): ts.Identifier | undefined {
53
+ if (line < 1 || line > sourceFile.getLineStarts().length) {
54
+ return undefined
55
+ }
56
+ const onLine = identifiersOnLine(sourceFile, line)
57
+ if (onLine.length === 0) {
58
+ return undefined
59
+ }
60
+
61
+ if (character !== undefined && character >= 1) {
62
+ const lineStart = ts.getPositionOfLineAndCharacter(sourceFile, line - 1, 0)
63
+ const pos = lineStart + (character - 1)
64
+ for (const id of onLine) {
65
+ if (pos >= id.getStart(sourceFile) && pos <= id.getEnd()) {
66
+ return id
67
+ }
68
+ }
69
+ return undefined
70
+ }
71
+
72
+ if (symbol !== undefined && symbol.trim() !== "") {
73
+ return onLine.find((id) => id.text === symbol)
74
+ }
75
+
76
+ return onLine[0]
77
+ }