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.
- package/ATTRIBUTION.md +53 -0
- package/CODE_OF_CONDUCT.md +130 -0
- package/CONTRIBUTING.md +107 -0
- package/LICENSE +202 -0
- package/README.md +486 -0
- package/SECURITY.md +211 -0
- package/bin/headlesscode.mjs +83 -0
- package/package.json +63 -0
- package/shared/prompts/review-mode-prompt-short.md +93 -0
- package/shared/prompts/review-mode-prompt.md +281 -0
- package/shared/rules-code/rules.md +22 -0
- package/shared/stacks/cpp/rules.md +30 -0
- package/shared/stacks/fastapi/rules.md +30 -0
- package/shared/stacks/javascript/rules.md +37 -0
- package/shared/stacks/postgresql/rules.md +31 -0
- package/shared/stacks/python/rules.md +35 -0
- package/shared/stacks/react/rules.md +11 -0
- package/shared/stacks/typescript/rules.md +10 -0
- package/src/budget/budget.ts +221 -0
- package/src/budget/concurrency.ts +126 -0
- package/src/budget/cost.ts +309 -0
- package/src/budget/index.ts +8 -0
- package/src/checkpoints/cli.ts +256 -0
- package/src/checkpoints/service.ts +227 -0
- package/src/cli.ts +1535 -0
- package/src/cloud/docker-provider.ts +334 -0
- package/src/cloud/provider.ts +300 -0
- package/src/codeintel/call-graph.ts +78 -0
- package/src/codeintel/find-references.ts +123 -0
- package/src/codeintel/go-to-definition.ts +193 -0
- package/src/codeintel/handlers.ts +190 -0
- package/src/codeintel/import-graph.ts +173 -0
- package/src/codeintel/outline.ts +180 -0
- package/src/codeintel/position.ts +77 -0
- package/src/codeintel/program.ts +350 -0
- package/src/codeintel/rename-symbol.ts +213 -0
- package/src/codeintel/tools.ts +280 -0
- package/src/codemap/build.ts +135 -0
- package/src/codemap/cli.ts +190 -0
- package/src/codemap/extract.ts +339 -0
- package/src/codemap/files.ts +236 -0
- package/src/codemap/fingerprint.ts +65 -0
- package/src/codemap/flows.ts +62 -0
- package/src/codemap/html.ts +451 -0
- package/src/codemap/lock.ts +80 -0
- package/src/codemap/types.ts +101 -0
- package/src/codesearch/airunner-embedder.ts +185 -0
- package/src/codesearch/chunk.ts +339 -0
- package/src/codesearch/cli.ts +223 -0
- package/src/codesearch/embedder.ts +332 -0
- package/src/codesearch/files.ts +280 -0
- package/src/codesearch/index.ts +469 -0
- package/src/codesearch/ollama-embedder.ts +205 -0
- package/src/codesearch/search.ts +141 -0
- package/src/codesearch/types.ts +100 -0
- package/src/config/mode-models.ts +218 -0
- package/src/dashboard/aggregate.ts +364 -0
- package/src/dashboard/chat-thread.ts +141 -0
- package/src/dashboard/checkpoints.ts +124 -0
- package/src/dashboard/cli.ts +193 -0
- package/src/dashboard/codemap.ts +44 -0
- package/src/dashboard/files.ts +121 -0
- package/src/dashboard/page.ts +2803 -0
- package/src/dashboard/self-improvement-metrics.ts +282 -0
- package/src/dashboard/server.ts +1103 -0
- package/src/dashboard/session-launch.ts +310 -0
- package/src/dashboard/timeline.ts +273 -0
- package/src/dashboard/tool-exec.ts +107 -0
- package/src/dashboard/trend-cli.ts +141 -0
- package/src/dashboard/trend.ts +413 -0
- package/src/decision-proxy/cli.ts +261 -0
- package/src/decision-proxy/proxy.ts +569 -0
- package/src/deploy/gate-cli.ts +147 -0
- package/src/deploy/gate.ts +254 -0
- package/src/engine/condense.ts +512 -0
- package/src/engine/events.ts +428 -0
- package/src/engine/handoff.ts +71 -0
- package/src/engine/lazy-tools.ts +160 -0
- package/src/engine/local-explore.ts +653 -0
- package/src/engine/logger.ts +96 -0
- package/src/engine/loop.ts +5517 -0
- package/src/engine/parser.ts +347 -0
- package/src/engine/prompt.ts +860 -0
- package/src/engine/reports.ts +47 -0
- package/src/engine/stacks.ts +448 -0
- package/src/engine/types.ts +291 -0
- package/src/engine/usage.ts +186 -0
- package/src/github/app-auth.ts +161 -0
- package/src/github/cli.ts +448 -0
- package/src/github/installations.ts +133 -0
- package/src/github/pr.ts +321 -0
- package/src/github/provision.ts +118 -0
- package/src/github/push.ts +122 -0
- package/src/index-util.ts +50 -0
- package/src/index.ts +81 -0
- package/src/init/cli.ts +248 -0
- package/src/init/gitignore.ts +74 -0
- package/src/llm/ollama.ts +308 -0
- package/src/llm/openrouter.ts +868 -0
- package/src/llm/preflight.ts +367 -0
- package/src/llm/transcript-capture.ts +84 -0
- package/src/memory/embed.ts +110 -0
- package/src/memory/index.ts +22 -0
- package/src/memory/local.ts +259 -0
- package/src/memory/summarizer.ts +283 -0
- package/src/memory/types.ts +153 -0
- package/src/memory/uwuchat.ts +157 -0
- package/src/migrate/cli.ts +115 -0
- package/src/orchestrator/analyze-cli.ts +104 -0
- package/src/orchestrator/auto-split.ts +206 -0
- package/src/orchestrator/cleanup.ts +1003 -0
- package/src/orchestrator/cli.ts +3571 -0
- package/src/orchestrator/cost-estimate.ts +564 -0
- package/src/orchestrator/cost-history-cli.ts +242 -0
- package/src/orchestrator/cost-history.ts +397 -0
- package/src/orchestrator/git-sync.ts +250 -0
- package/src/orchestrator/index.ts +153 -0
- package/src/orchestrator/log-analysis.ts +0 -0
- package/src/orchestrator/merge-check.ts +108 -0
- package/src/orchestrator/pipeline.ts +411 -0
- package/src/orchestrator/resume.ts +1940 -0
- package/src/orchestrator/reviewer.ts +503 -0
- package/src/orchestrator/split.ts +296 -0
- package/src/orchestrator/state.ts +542 -0
- package/src/orchestrator/status.ts +697 -0
- package/src/orchestrator/verification-gate.ts +134 -0
- package/src/orchestrator/watch.ts +898 -0
- package/src/permissions/commands.ts +1083 -0
- package/src/permissions/config.ts +241 -0
- package/src/permissions/index.ts +12 -0
- package/src/permissions/protected-files.ts +96 -0
- package/src/permissions/store-protection.ts +272 -0
- package/src/project-store.ts +648 -0
- package/src/projects/cli.ts +382 -0
- package/src/qa/qa.ts +487 -0
- package/src/tools/browser/handler.ts +346 -0
- package/src/tools/browser/service.ts +406 -0
- package/src/tools/browser/smoke.ts +78 -0
- package/src/tools/browser/tool.ts +99 -0
- package/src/tools/executor.ts +2575 -0
- package/src/tools/language-detect.ts +183 -0
- package/src/tools/output-summarizer.ts +369 -0
- package/src/tools/run-tests.ts +302 -0
- package/src/tools/set-indentation-tool.ts +49 -0
- package/src/tools/test-selection.ts +160 -0
- package/src/vendor/tests/smoke.ts +103 -0
- package/src/vendor/zoo-code/VENDOR-NOTES.md +213 -0
- package/src/vendor/zoo-code/shim/anthropic.ts +71 -0
- package/src/vendor/zoo-code/shim/openai.d.ts +60 -0
- package/src/vendor/zoo-code/shim/os-name.ts +18 -0
- package/src/vendor/zoo-code/shim/strip-bom.ts +14 -0
- package/src/vendor/zoo-code/shim/vscode.ts +76 -0
- package/src/vendor/zoo-code/src/core/config/CustomModesManager.ts +1015 -0
- package/src/vendor/zoo-code/src/core/diff/strategies/multi-search-replace.ts +670 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/capabilities.ts +46 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/custom-instructions.ts +559 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/index.ts +10 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/markdown-formatting.ts +7 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/modes.ts +35 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/objective.ts +13 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/rules.ts +95 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/skills.ts +105 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/system-info.ts +30 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/tool-use-guidelines.ts +9 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/tool-use.ts +7 -0
- package/src/vendor/zoo-code/src/core/prompts/system.ts +176 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/access_mcp_resource.ts +41 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_diff.ts +40 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_patch.ts +61 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/ask_followup_question.ts +62 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/attempt_completion.ts +33 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/codebase_search.ts +43 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/converters.ts +109 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit.ts +48 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit_file.ts +72 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/execute_command.ts +54 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/generate_image.ts +51 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/index.ts +75 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/list_files.ts +41 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/mcp_server.ts +75 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/new_task.ts +39 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_command_output.ts +81 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_file.ts +169 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/run_slash_command.ts +31 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_files.ts +50 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_replace.ts +51 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/skill.ts +33 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/switch_mode.ts +31 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/update_todo_list.ts +54 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/write_to_file.ts +40 -0
- package/src/vendor/zoo-code/src/core/prompts/types.ts +12 -0
- package/src/vendor/zoo-code/src/i18n/index.ts +19 -0
- package/src/vendor/zoo-code/src/integrations/misc/extract-text.ts +81 -0
- package/src/vendor/zoo-code/src/services/checkpoints/RepoPerTaskCheckpointService.ts +15 -0
- package/src/vendor/zoo-code/src/services/checkpoints/ShadowCheckpointService.ts +553 -0
- package/src/vendor/zoo-code/src/services/checkpoints/excludes.ts +212 -0
- package/src/vendor/zoo-code/src/services/checkpoints/index.ts +3 -0
- package/src/vendor/zoo-code/src/services/checkpoints/types.ts +35 -0
- package/src/vendor/zoo-code/src/services/code-index/manager.ts +19 -0
- package/src/vendor/zoo-code/src/services/mcp/McpHub.ts +36 -0
- package/src/vendor/zoo-code/src/services/roo-config/index.ts +441 -0
- package/src/vendor/zoo-code/src/services/search/file-search.ts +143 -0
- package/src/vendor/zoo-code/src/services/skills/SkillsManager.ts +20 -0
- package/src/vendor/zoo-code/src/shared/globalFileNames.ts +9 -0
- package/src/vendor/zoo-code/src/shared/language.ts +43 -0
- package/src/vendor/zoo-code/src/shared/modes.ts +257 -0
- package/src/vendor/zoo-code/src/shared/tools.ts +385 -0
- package/src/vendor/zoo-code/src/utils/fs.ts +39 -0
- package/src/vendor/zoo-code/src/utils/globalContext.ts +22 -0
- package/src/vendor/zoo-code/src/utils/json-schema.ts +16 -0
- package/src/vendor/zoo-code/src/utils/logging.ts +21 -0
- package/src/vendor/zoo-code/src/utils/mcp-name.ts +190 -0
- package/src/vendor/zoo-code/src/utils/object.ts +18 -0
- package/src/vendor/zoo-code/src/utils/path.ts +94 -0
- package/src/vendor/zoo-code/src/utils/shell.ts +376 -0
- package/src/vendor/zoo-code/src/utils/text-normalization.ts +99 -0
- package/src/vendor/zoo-code/types/global-settings.ts +19 -0
- package/src/vendor/zoo-code/types/index.ts +22 -0
- package/src/vendor/zoo-code/types/message.ts +375 -0
- package/src/vendor/zoo-code/types/mode.ts +241 -0
- package/src/vendor/zoo-code/types/todo.ts +19 -0
- package/src/vendor/zoo-code/types/tool-params.ts +116 -0
- package/src/vendor/zoo-code/types/tool.ts +67 -0
- package/src/vendor/zoo-code/types/vscode.ts +84 -0
- package/src/vision/describe.ts +242 -0
- package/src/vision/tool.ts +91 -0
- package/src/watcher/cli.ts +369 -0
- package/src/watcher/github.ts +304 -0
- package/src/watcher/index.ts +59 -0
- package/src/watcher/state.ts +254 -0
- package/src/watcher/watch.ts +562 -0
- 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
|
+
}
|