@ilya-lesikov/pi-pi 0.1.0
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/3p/pi-ask-user/index.ts +1835 -0
- package/3p/pi-ask-user/package.json +50 -0
- package/3p/pi-ask-user/single-select-layout.ts +203 -0
- package/3p/pi-lsp/extensions/lsp/client.ts +542 -0
- package/3p/pi-lsp/extensions/lsp/config.ts +135 -0
- package/3p/pi-lsp/extensions/lsp/effects/command.ts +42 -0
- package/3p/pi-lsp/extensions/lsp/effects/filesystem.ts +50 -0
- package/3p/pi-lsp/extensions/lsp/effects/runtime.ts +21 -0
- package/3p/pi-lsp/extensions/lsp/errors.ts +113 -0
- package/3p/pi-lsp/extensions/lsp/formatting.ts +315 -0
- package/3p/pi-lsp/extensions/lsp/index.ts +231 -0
- package/3p/pi-lsp/extensions/lsp/protocol.ts +209 -0
- package/3p/pi-lsp/extensions/lsp/retry.ts +39 -0
- package/3p/pi-lsp/extensions/lsp/tools/programs.ts +293 -0
- package/3p/pi-lsp/extensions/lsp/tools.ts +89 -0
- package/3p/pi-lsp/extensions/lsp/types.ts +243 -0
- package/3p/pi-lsp/package.json +54 -0
- package/3p/pi-plannotator/AGENTS.md +583 -0
- package/3p/pi-plannotator/apps/pi-extension/README.md +228 -0
- package/3p/pi-plannotator/apps/pi-extension/assistant-message.ts +128 -0
- package/3p/pi-plannotator/apps/pi-extension/config.test.ts +166 -0
- package/3p/pi-plannotator/apps/pi-extension/config.ts +318 -0
- package/3p/pi-plannotator/apps/pi-extension/current-pi-session.ts +147 -0
- package/3p/pi-plannotator/apps/pi-extension/index.ts +1279 -0
- package/3p/pi-plannotator/apps/pi-extension/package.json +56 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.test.ts +13 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +616 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +342 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator.json +12 -0
- package/3p/pi-plannotator/apps/pi-extension/server/agent-jobs.ts +515 -0
- package/3p/pi-plannotator/apps/pi-extension/server/ai-runtime.ts +169 -0
- package/3p/pi-plannotator/apps/pi-extension/server/annotations.ts +85 -0
- package/3p/pi-plannotator/apps/pi-extension/server/external-annotations.ts +189 -0
- package/3p/pi-plannotator/apps/pi-extension/server/handlers.ts +210 -0
- package/3p/pi-plannotator/apps/pi-extension/server/helpers.ts +78 -0
- package/3p/pi-plannotator/apps/pi-extension/server/ide.ts +46 -0
- package/3p/pi-plannotator/apps/pi-extension/server/integrations.ts +195 -0
- package/3p/pi-plannotator/apps/pi-extension/server/network.test.ts +158 -0
- package/3p/pi-plannotator/apps/pi-extension/server/network.ts +268 -0
- package/3p/pi-plannotator/apps/pi-extension/server/pr.ts +126 -0
- package/3p/pi-plannotator/apps/pi-extension/server/project.ts +64 -0
- package/3p/pi-plannotator/apps/pi-extension/server/reference.ts +362 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverAnnotate.ts +199 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverPlan.ts +505 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +1229 -0
- package/3p/pi-plannotator/apps/pi-extension/server/vcs.ts +121 -0
- package/3p/pi-plannotator/apps/pi-extension/server.test.ts +966 -0
- package/3p/pi-plannotator/apps/pi-extension/server.ts +44 -0
- package/3p/pi-plannotator/apps/pi-extension/tool-scope.test.ts +88 -0
- package/3p/pi-plannotator/apps/pi-extension/tool-scope.ts +39 -0
- package/3p/pi-plannotator/apps/pi-extension/tsconfig.json +16 -0
- package/3p/pi-plannotator/apps/pi-extension/vendor.sh +48 -0
- package/3p/pi-plannotator/package.json +53 -0
- package/3p/pi-plannotator/packages/ai/ai.test.ts +1407 -0
- package/3p/pi-plannotator/packages/ai/base-session.ts +94 -0
- package/3p/pi-plannotator/packages/ai/context.ts +250 -0
- package/3p/pi-plannotator/packages/ai/endpoints.ts +326 -0
- package/3p/pi-plannotator/packages/ai/index.ts +105 -0
- package/3p/pi-plannotator/packages/ai/package.json +21 -0
- package/3p/pi-plannotator/packages/ai/provider.ts +103 -0
- package/3p/pi-plannotator/packages/ai/providers/claude-agent-sdk.ts +447 -0
- package/3p/pi-plannotator/packages/ai/providers/codex-sdk.ts +430 -0
- package/3p/pi-plannotator/packages/ai/providers/command-path.ts +115 -0
- package/3p/pi-plannotator/packages/ai/providers/opencode-sdk.ts +546 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-events.ts +110 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-sdk-node.ts +425 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-sdk.ts +469 -0
- package/3p/pi-plannotator/packages/ai/session-manager.ts +195 -0
- package/3p/pi-plannotator/packages/ai/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/ai/types.ts +379 -0
- package/3p/pi-plannotator/packages/server/agent-jobs.ts +538 -0
- package/3p/pi-plannotator/packages/server/agent-review-message.test.ts +135 -0
- package/3p/pi-plannotator/packages/server/agent-review-message.ts +243 -0
- package/3p/pi-plannotator/packages/server/ai-runtime.ts +108 -0
- package/3p/pi-plannotator/packages/server/annotate.ts +401 -0
- package/3p/pi-plannotator/packages/server/browser.test.ts +87 -0
- package/3p/pi-plannotator/packages/server/browser.ts +245 -0
- package/3p/pi-plannotator/packages/server/claude-review.ts +352 -0
- package/3p/pi-plannotator/packages/server/code-nav.ts +73 -0
- package/3p/pi-plannotator/packages/server/codex-review-schema.json +41 -0
- package/3p/pi-plannotator/packages/server/codex-review.ts +330 -0
- package/3p/pi-plannotator/packages/server/config.ts +8 -0
- package/3p/pi-plannotator/packages/server/draft.ts +1 -0
- package/3p/pi-plannotator/packages/server/editor-annotations.ts +76 -0
- package/3p/pi-plannotator/packages/server/external-annotations.test.ts +18 -0
- package/3p/pi-plannotator/packages/server/external-annotations.ts +207 -0
- package/3p/pi-plannotator/packages/server/git.ts +141 -0
- package/3p/pi-plannotator/packages/server/goal-setup.test.ts +55 -0
- package/3p/pi-plannotator/packages/server/goal-setup.ts +248 -0
- package/3p/pi-plannotator/packages/server/ide.ts +43 -0
- package/3p/pi-plannotator/packages/server/image.test.ts +63 -0
- package/3p/pi-plannotator/packages/server/image.ts +66 -0
- package/3p/pi-plannotator/packages/server/index.ts +648 -0
- package/3p/pi-plannotator/packages/server/integrations.test.ts +172 -0
- package/3p/pi-plannotator/packages/server/integrations.ts +214 -0
- package/3p/pi-plannotator/packages/server/jj.test.ts +69 -0
- package/3p/pi-plannotator/packages/server/jj.ts +86 -0
- package/3p/pi-plannotator/packages/server/p4.ts +417 -0
- package/3p/pi-plannotator/packages/server/package.json +40 -0
- package/3p/pi-plannotator/packages/server/path-utils.ts +18 -0
- package/3p/pi-plannotator/packages/server/pr.ts +146 -0
- package/3p/pi-plannotator/packages/server/project.test.ts +115 -0
- package/3p/pi-plannotator/packages/server/project.ts +45 -0
- package/3p/pi-plannotator/packages/server/reference-handlers.ts +404 -0
- package/3p/pi-plannotator/packages/server/remote.test.ts +150 -0
- package/3p/pi-plannotator/packages/server/remote.ts +74 -0
- package/3p/pi-plannotator/packages/server/repo.ts +80 -0
- package/3p/pi-plannotator/packages/server/resolve-file.test.ts +310 -0
- package/3p/pi-plannotator/packages/server/review-workspace.test.ts +1032 -0
- package/3p/pi-plannotator/packages/server/review-workspace.ts +48 -0
- package/3p/pi-plannotator/packages/server/review.ts +1318 -0
- package/3p/pi-plannotator/packages/server/sessions.ts +111 -0
- package/3p/pi-plannotator/packages/server/share-url.ts +53 -0
- package/3p/pi-plannotator/packages/server/shared-handlers.test.ts +43 -0
- package/3p/pi-plannotator/packages/server/shared-handlers.ts +177 -0
- package/3p/pi-plannotator/packages/server/storage.test.ts +176 -0
- package/3p/pi-plannotator/packages/server/storage.ts +17 -0
- package/3p/pi-plannotator/packages/server/tour/tour-review.test.ts +146 -0
- package/3p/pi-plannotator/packages/server/tour/tour-review.ts +604 -0
- package/3p/pi-plannotator/packages/server/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/server/vcs.test.ts +48 -0
- package/3p/pi-plannotator/packages/server/vcs.ts +80 -0
- package/3p/pi-plannotator/packages/shared/agent-jobs.ts +132 -0
- package/3p/pi-plannotator/packages/shared/agents.ts +53 -0
- package/3p/pi-plannotator/packages/shared/annotate-args.test.ts +386 -0
- package/3p/pi-plannotator/packages/shared/annotate-args.ts +107 -0
- package/3p/pi-plannotator/packages/shared/at-reference.test.ts +99 -0
- package/3p/pi-plannotator/packages/shared/at-reference.ts +52 -0
- package/3p/pi-plannotator/packages/shared/checklist.ts +52 -0
- package/3p/pi-plannotator/packages/shared/code-file.test.ts +112 -0
- package/3p/pi-plannotator/packages/shared/code-file.ts +41 -0
- package/3p/pi-plannotator/packages/shared/code-nav.test.ts +515 -0
- package/3p/pi-plannotator/packages/shared/code-nav.ts +436 -0
- package/3p/pi-plannotator/packages/shared/compress.ts +51 -0
- package/3p/pi-plannotator/packages/shared/config.ts +262 -0
- package/3p/pi-plannotator/packages/shared/crypto.test.ts +172 -0
- package/3p/pi-plannotator/packages/shared/crypto.ts +97 -0
- package/3p/pi-plannotator/packages/shared/data-dir.ts +42 -0
- package/3p/pi-plannotator/packages/shared/diff-paths.test.ts +30 -0
- package/3p/pi-plannotator/packages/shared/diff-paths.ts +137 -0
- package/3p/pi-plannotator/packages/shared/draft.ts +64 -0
- package/3p/pi-plannotator/packages/shared/external-annotation.ts +397 -0
- package/3p/pi-plannotator/packages/shared/extract-code-paths.test.ts +59 -0
- package/3p/pi-plannotator/packages/shared/extract-code-paths.ts +66 -0
- package/3p/pi-plannotator/packages/shared/favicon.ts +5 -0
- package/3p/pi-plannotator/packages/shared/feedback-templates.test.ts +65 -0
- package/3p/pi-plannotator/packages/shared/feedback-templates.ts +29 -0
- package/3p/pi-plannotator/packages/shared/goal-setup.test.ts +231 -0
- package/3p/pi-plannotator/packages/shared/goal-setup.ts +336 -0
- package/3p/pi-plannotator/packages/shared/html-to-markdown.test.ts +62 -0
- package/3p/pi-plannotator/packages/shared/html-to-markdown.ts +32 -0
- package/3p/pi-plannotator/packages/shared/improvement-hooks.test.ts +135 -0
- package/3p/pi-plannotator/packages/shared/improvement-hooks.ts +115 -0
- package/3p/pi-plannotator/packages/shared/integrations-common.ts +243 -0
- package/3p/pi-plannotator/packages/shared/jj-core.test.ts +236 -0
- package/3p/pi-plannotator/packages/shared/jj-core.ts +433 -0
- package/3p/pi-plannotator/packages/shared/package.json +55 -0
- package/3p/pi-plannotator/packages/shared/pfm-reminder.test.ts +88 -0
- package/3p/pi-plannotator/packages/shared/pfm-reminder.ts +80 -0
- package/3p/pi-plannotator/packages/shared/pr-github.ts +661 -0
- package/3p/pi-plannotator/packages/shared/pr-gitlab.test.ts +202 -0
- package/3p/pi-plannotator/packages/shared/pr-gitlab.ts +620 -0
- package/3p/pi-plannotator/packages/shared/pr-provider.test.ts +266 -0
- package/3p/pi-plannotator/packages/shared/pr-provider.ts +123 -0
- package/3p/pi-plannotator/packages/shared/pr-stack.test.ts +104 -0
- package/3p/pi-plannotator/packages/shared/pr-stack.ts +194 -0
- package/3p/pi-plannotator/packages/shared/pr-types.ts +326 -0
- package/3p/pi-plannotator/packages/shared/project.ts +71 -0
- package/3p/pi-plannotator/packages/shared/prompts-integration.test.ts +421 -0
- package/3p/pi-plannotator/packages/shared/prompts.test.ts +504 -0
- package/3p/pi-plannotator/packages/shared/prompts.ts +247 -0
- package/3p/pi-plannotator/packages/shared/reference-common.ts +87 -0
- package/3p/pi-plannotator/packages/shared/repo.ts +71 -0
- package/3p/pi-plannotator/packages/shared/resolve-file.test.ts +113 -0
- package/3p/pi-plannotator/packages/shared/resolve-file.ts +509 -0
- package/3p/pi-plannotator/packages/shared/review-args.test.ts +64 -0
- package/3p/pi-plannotator/packages/shared/review-args.ts +85 -0
- package/3p/pi-plannotator/packages/shared/review-core.test.ts +286 -0
- package/3p/pi-plannotator/packages/shared/review-core.ts +895 -0
- package/3p/pi-plannotator/packages/shared/review-workspace-node.ts +230 -0
- package/3p/pi-plannotator/packages/shared/review-workspace.ts +436 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff-types.ts +76 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff.test.ts +322 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff.ts +520 -0
- package/3p/pi-plannotator/packages/shared/storage.ts +378 -0
- package/3p/pi-plannotator/packages/shared/tour.ts +61 -0
- package/3p/pi-plannotator/packages/shared/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/shared/types.ts +29 -0
- package/3p/pi-plannotator/packages/shared/url-to-markdown.test.ts +177 -0
- package/3p/pi-plannotator/packages/shared/url-to-markdown.ts +351 -0
- package/3p/pi-plannotator/packages/shared/vcs-core.test.ts +332 -0
- package/3p/pi-plannotator/packages/shared/vcs-core.ts +482 -0
- package/3p/pi-plannotator/packages/shared/worktree-pool.test.ts +162 -0
- package/3p/pi-plannotator/packages/shared/worktree-pool.ts +103 -0
- package/3p/pi-plannotator/packages/shared/worktree.ts +119 -0
- package/3p/pi-subagents/package.json +50 -0
- package/3p/pi-subagents/src/agent-manager.ts +413 -0
- package/3p/pi-subagents/src/agent-runner.ts +502 -0
- package/3p/pi-subagents/src/agent-types.ts +248 -0
- package/3p/pi-subagents/src/context.ts +58 -0
- package/3p/pi-subagents/src/cross-extension-rpc.ts +135 -0
- package/3p/pi-subagents/src/custom-agents.ts +137 -0
- package/3p/pi-subagents/src/default-agents.ts +144 -0
- package/3p/pi-subagents/src/env.ts +33 -0
- package/3p/pi-subagents/src/group-join.ts +141 -0
- package/3p/pi-subagents/src/index.ts +1811 -0
- package/3p/pi-subagents/src/invocation-config.ts +40 -0
- package/3p/pi-subagents/src/memory.ts +165 -0
- package/3p/pi-subagents/src/model-resolver.ts +81 -0
- package/3p/pi-subagents/src/output-file.ts +77 -0
- package/3p/pi-subagents/src/prompts.ts +85 -0
- package/3p/pi-subagents/src/skill-loader.ts +79 -0
- package/3p/pi-subagents/src/types.ts +111 -0
- package/3p/pi-subagents/src/ui/agent-widget.ts +496 -0
- package/3p/pi-subagents/src/ui/conversation-viewer.ts +250 -0
- package/3p/pi-subagents/src/worktree.ts +162 -0
- package/3p/pi-tasks/package.json +52 -0
- package/3p/pi-tasks/src/auto-clear.ts +91 -0
- package/3p/pi-tasks/src/index.ts +1150 -0
- package/3p/pi-tasks/src/process-tracker.ts +140 -0
- package/3p/pi-tasks/src/reminder-cadence.ts +90 -0
- package/3p/pi-tasks/src/task-store.ts +324 -0
- package/3p/pi-tasks/src/tasks-config.ts +27 -0
- package/3p/pi-tasks/src/types.ts +40 -0
- package/3p/pi-tasks/src/ui/settings-menu.ts +152 -0
- package/3p/pi-tasks/src/ui/task-widget.ts +296 -0
- package/AGENTS.md +28 -0
- package/LICENSE +201 -0
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +77 -0
- package/extensions/orchestrator/agents/code-reviewer.ts +89 -0
- package/extensions/orchestrator/agents/explore.ts +33 -0
- package/extensions/orchestrator/agents/librarian.ts +43 -0
- package/extensions/orchestrator/agents/plan-reviewer.ts +75 -0
- package/extensions/orchestrator/agents/planner.ts +61 -0
- package/extensions/orchestrator/agents/registry.ts +156 -0
- package/extensions/orchestrator/agents/task.ts +50 -0
- package/extensions/orchestrator/agents/tool-routing.ts +84 -0
- package/extensions/orchestrator/ast-search.ts +85 -0
- package/extensions/orchestrator/cbm.ts +330 -0
- package/extensions/orchestrator/command-handlers.test.ts +163 -0
- package/extensions/orchestrator/command-handlers.ts +116 -0
- package/extensions/orchestrator/commands.test.ts +81 -0
- package/extensions/orchestrator/commands.ts +75 -0
- package/extensions/orchestrator/config.test.ts +221 -0
- package/extensions/orchestrator/config.ts +230 -0
- package/extensions/orchestrator/context.test.ts +293 -0
- package/extensions/orchestrator/context.ts +206 -0
- package/extensions/orchestrator/event-handlers.test.ts +190 -0
- package/extensions/orchestrator/event-handlers.ts +1416 -0
- package/extensions/orchestrator/exa.ts +104 -0
- package/extensions/orchestrator/flant-infra.ts +486 -0
- package/extensions/orchestrator/index.ts +80 -0
- package/extensions/orchestrator/integration.test.ts +1214 -0
- package/extensions/orchestrator/orchestrator.test.ts +252 -0
- package/extensions/orchestrator/orchestrator.ts +538 -0
- package/extensions/orchestrator/phases/brainstorm.test.ts +15 -0
- package/extensions/orchestrator/phases/brainstorm.ts +273 -0
- package/extensions/orchestrator/phases/implementation.ts +40 -0
- package/extensions/orchestrator/phases/machine.test.ts +293 -0
- package/extensions/orchestrator/phases/machine.ts +209 -0
- package/extensions/orchestrator/phases/planning.ts +255 -0
- package/extensions/orchestrator/phases/review.ts +193 -0
- package/extensions/orchestrator/plannotator.ts +56 -0
- package/extensions/orchestrator/pp-menu.ts +866 -0
- package/extensions/orchestrator/state.test.ts +343 -0
- package/extensions/orchestrator/state.ts +237 -0
- package/extensions/orchestrator/validate-artifacts.test.ts +88 -0
- package/extensions/orchestrator/validate-artifacts.ts +272 -0
- package/extensions/orchestrator/vendor.d.ts +26 -0
- package/package.json +73 -0
- package/scripts/postinstall.sh +18 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CommandResolver service — decides how a configured LSP server command can be
|
|
3
|
+
* launched: directly from PATH (`global`), via `npx` as a fallback, or not at
|
|
4
|
+
* all (`null`).
|
|
5
|
+
*
|
|
6
|
+
* Isolated behind an Effect service so config resolution stays pure and tests
|
|
7
|
+
* can inject deterministic availability without shelling out.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Context, Effect } from 'effect';
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
12
|
+
|
|
13
|
+
export type CommandAvailability = 'global' | 'npx' | null;
|
|
14
|
+
|
|
15
|
+
export interface CommandResolverService {
|
|
16
|
+
readonly resolve: (command: string, cwd: string) => Effect.Effect<CommandAvailability>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class CommandResolver extends Context.Tag('Lsp/CommandResolver')<
|
|
20
|
+
CommandResolver,
|
|
21
|
+
CommandResolverService
|
|
22
|
+
>() {}
|
|
23
|
+
|
|
24
|
+
function commandAvailableVia(command: string, cwd: string): CommandAvailability {
|
|
25
|
+
try {
|
|
26
|
+
const whichCmd = process.platform === 'win32' ? 'where' : 'which';
|
|
27
|
+
execSync(`${whichCmd} ${command}`, { stdio: 'pipe', timeout: 5_000 });
|
|
28
|
+
return 'global';
|
|
29
|
+
} catch {
|
|
30
|
+
// not on PATH
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
execSync(`npx --yes ${command} --version`, { stdio: 'pipe', cwd, timeout: 15_000 });
|
|
34
|
+
return 'npx';
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const nodeCommandResolverService: CommandResolverService = {
|
|
41
|
+
resolve: (command, cwd) => Effect.sync(() => commandAvailableVia(command, cwd)),
|
|
42
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileSystem service — the only place the LSP extension touches disk.
|
|
3
|
+
*
|
|
4
|
+
* Wrapping Node's `fs/promises` behind an Effect service keeps config loading
|
|
5
|
+
* pure and injectable: tests can swap in an in-memory implementation, and
|
|
6
|
+
* failures surface as typed `ConfigReadError` / `ConfigWriteError` values.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Context, Effect } from 'effect';
|
|
10
|
+
import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
11
|
+
import { dirname } from 'node:path';
|
|
12
|
+
|
|
13
|
+
import { ConfigReadError, ConfigWriteError } from '../errors';
|
|
14
|
+
|
|
15
|
+
export interface FileSystemService {
|
|
16
|
+
/** Read a UTF-8 file, failing with ConfigReadError when unreadable/missing. */
|
|
17
|
+
readonly readTextFile: (path: string) => Effect.Effect<string, ConfigReadError>;
|
|
18
|
+
/** Whether a path exists. Never fails. */
|
|
19
|
+
readonly fileExists: (path: string) => Effect.Effect<boolean>;
|
|
20
|
+
/** Write a UTF-8 file, creating parent directories first. */
|
|
21
|
+
readonly writeTextFile: (path: string, content: string) => Effect.Effect<void, ConfigWriteError>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class FileSystem extends Context.Tag('Lsp/FileSystem')<FileSystem, FileSystemService>() {}
|
|
25
|
+
|
|
26
|
+
export const nodeFileSystemService: FileSystemService = {
|
|
27
|
+
readTextFile: (path) =>
|
|
28
|
+
Effect.tryPromise({
|
|
29
|
+
try: () => readFile(path, 'utf8'),
|
|
30
|
+
catch: (cause) => new ConfigReadError({ path, cause }),
|
|
31
|
+
}),
|
|
32
|
+
|
|
33
|
+
fileExists: (path) =>
|
|
34
|
+
Effect.tryPromise({
|
|
35
|
+
try: () => access(path),
|
|
36
|
+
catch: (cause) => cause,
|
|
37
|
+
}).pipe(
|
|
38
|
+
Effect.as(true),
|
|
39
|
+
Effect.catchAll(() => Effect.succeed(false)),
|
|
40
|
+
),
|
|
41
|
+
|
|
42
|
+
writeTextFile: (path, content) =>
|
|
43
|
+
Effect.tryPromise({
|
|
44
|
+
try: async () => {
|
|
45
|
+
await mkdir(dirname(path), { recursive: true });
|
|
46
|
+
await writeFile(path, content, 'utf8');
|
|
47
|
+
},
|
|
48
|
+
catch: (cause) => new ConfigWriteError({ path, cause }),
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live Effect layer for the LSP extension.
|
|
3
|
+
*
|
|
4
|
+
* Merges the disk-facing FileSystem service and the command-availability
|
|
5
|
+
* CommandResolver service. Build it once per extension activation and reuse it
|
|
6
|
+
* for every config/scaffold program.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Layer } from 'effect';
|
|
10
|
+
|
|
11
|
+
import { CommandResolver, nodeCommandResolverService } from './command';
|
|
12
|
+
import { FileSystem, nodeFileSystemService } from './filesystem';
|
|
13
|
+
|
|
14
|
+
export function makeRuntimeLayer() {
|
|
15
|
+
return Layer.mergeAll(
|
|
16
|
+
Layer.succeed(FileSystem, nodeFileSystemService),
|
|
17
|
+
Layer.succeed(CommandResolver, nodeCommandResolverService),
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type LspServices = FileSystem | CommandResolver;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tagged error types for the LSP extension.
|
|
3
|
+
*
|
|
4
|
+
* Modeled with Effect's `Data.TaggedError` so failures are typed, pattern
|
|
5
|
+
* matchable, and carry structured context. Helpers at the bottom convert these
|
|
6
|
+
* into human-readable messages, tool `details`, and native `Error`s when an
|
|
7
|
+
* Effect needs to cross back into Promise-land.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Data } from 'effect';
|
|
11
|
+
|
|
12
|
+
export class ConfigReadError extends Data.TaggedError('ConfigReadError')<{
|
|
13
|
+
readonly path: string;
|
|
14
|
+
readonly cause: unknown;
|
|
15
|
+
}> {
|
|
16
|
+
get message(): string {
|
|
17
|
+
return `Failed to read ${this.path}: ${causeMessage(this.cause)}`;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class ConfigWriteError extends Data.TaggedError('ConfigWriteError')<{
|
|
22
|
+
readonly path: string;
|
|
23
|
+
readonly cause: unknown;
|
|
24
|
+
}> {
|
|
25
|
+
get message(): string {
|
|
26
|
+
return `Failed to write ${this.path}: ${causeMessage(this.cause)}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class LspValidationError extends Data.TaggedError('LspValidationError')<{
|
|
31
|
+
readonly reason: string;
|
|
32
|
+
}> {
|
|
33
|
+
get message(): string {
|
|
34
|
+
return this.reason;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class NoCapableServerError extends Data.TaggedError('NoCapableServerError')<{
|
|
39
|
+
readonly operation: string;
|
|
40
|
+
readonly filePath: string;
|
|
41
|
+
}> {
|
|
42
|
+
get message(): string {
|
|
43
|
+
return `No LSP server with '${this.operation}' capability found for ${this.filePath}. Check /lsp status.`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class NoServerAvailableError extends Data.TaggedError('NoServerAvailableError')<{
|
|
48
|
+
readonly operation: string;
|
|
49
|
+
}> {
|
|
50
|
+
get message(): string {
|
|
51
|
+
return `No LSP server available for ${this.operation}.`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class LspOperationError extends Data.TaggedError('LspOperationError')<{
|
|
56
|
+
readonly operation: string;
|
|
57
|
+
readonly server?: string;
|
|
58
|
+
readonly cause: unknown;
|
|
59
|
+
}> {
|
|
60
|
+
get message(): string {
|
|
61
|
+
const server = this.server ? ` (${this.server})` : '';
|
|
62
|
+
return `LSP ${this.operation}${server} failed: ${causeMessage(this.cause)}`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type LspExtensionError =
|
|
67
|
+
| ConfigReadError
|
|
68
|
+
| ConfigWriteError
|
|
69
|
+
| LspValidationError
|
|
70
|
+
| NoCapableServerError
|
|
71
|
+
| NoServerAvailableError
|
|
72
|
+
| LspOperationError;
|
|
73
|
+
|
|
74
|
+
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
export function causeMessage(cause: unknown): string {
|
|
77
|
+
if (cause instanceof Error) return cause.message;
|
|
78
|
+
return String(cause);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function errorMessage(error: unknown): string {
|
|
82
|
+
if (error instanceof Error) return error.message;
|
|
83
|
+
if (typeof error === 'object' && error !== null && 'message' in error) {
|
|
84
|
+
const message = (error as { message?: unknown }).message;
|
|
85
|
+
if (typeof message === 'string') return message;
|
|
86
|
+
}
|
|
87
|
+
return String(error);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function errorDetails(error: unknown): Record<string, unknown> {
|
|
91
|
+
if (typeof error === 'object' && error !== null && '_tag' in error) {
|
|
92
|
+
const tagged = error as { _tag: string } & Record<string, unknown>;
|
|
93
|
+
const details: Record<string, unknown> = { error: tagged._tag };
|
|
94
|
+
for (const [key, value] of Object.entries(tagged)) {
|
|
95
|
+
if (key === '_tag' || key === 'cause') continue;
|
|
96
|
+
details[key] = value;
|
|
97
|
+
}
|
|
98
|
+
details.message = errorMessage(error);
|
|
99
|
+
return details;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { error: 'lsp_error', message: errorMessage(error) };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Convert a tagged/unknown error into a native Error for Promise rejection. */
|
|
106
|
+
export function toNativeError(error: unknown): Error {
|
|
107
|
+
if (error instanceof Error) return error;
|
|
108
|
+
const native = new Error(errorMessage(error));
|
|
109
|
+
if (typeof error === 'object' && error !== null && '_tag' in error) {
|
|
110
|
+
native.name = String((error as { _tag: unknown })._tag);
|
|
111
|
+
}
|
|
112
|
+
return native;
|
|
113
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format LSP responses into concise text for the LLM.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { uriToPath } from './client';
|
|
6
|
+
import type {
|
|
7
|
+
CallHierarchyIncomingCall,
|
|
8
|
+
CallHierarchyItem,
|
|
9
|
+
CallHierarchyOutgoingCall,
|
|
10
|
+
CodeAction,
|
|
11
|
+
Diagnostic,
|
|
12
|
+
DocumentSymbol,
|
|
13
|
+
Hover,
|
|
14
|
+
Location,
|
|
15
|
+
Range,
|
|
16
|
+
SymbolInformation,
|
|
17
|
+
} from './types';
|
|
18
|
+
import { DiagnosticSeverity, SymbolKind } from './types';
|
|
19
|
+
|
|
20
|
+
// ── Shared ──────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
function severityLabel(severity?: number): string {
|
|
23
|
+
switch (severity) {
|
|
24
|
+
case DiagnosticSeverity.Error:
|
|
25
|
+
return 'ERROR';
|
|
26
|
+
case DiagnosticSeverity.Warning:
|
|
27
|
+
return 'WARN';
|
|
28
|
+
case DiagnosticSeverity.Information:
|
|
29
|
+
return 'INFO';
|
|
30
|
+
case DiagnosticSeverity.Hint:
|
|
31
|
+
return 'HINT';
|
|
32
|
+
default:
|
|
33
|
+
return 'UNKNOWN';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function symbolKindLabel(kind: SymbolKind): string {
|
|
38
|
+
const labels: Record<number, string> = {
|
|
39
|
+
[SymbolKind.File]: 'file',
|
|
40
|
+
[SymbolKind.Module]: 'module',
|
|
41
|
+
[SymbolKind.Namespace]: 'namespace',
|
|
42
|
+
[SymbolKind.Package]: 'package',
|
|
43
|
+
[SymbolKind.Class]: 'class',
|
|
44
|
+
[SymbolKind.Method]: 'method',
|
|
45
|
+
[SymbolKind.Property]: 'property',
|
|
46
|
+
[SymbolKind.Field]: 'field',
|
|
47
|
+
[SymbolKind.Constructor]: 'constructor',
|
|
48
|
+
[SymbolKind.Enum]: 'enum',
|
|
49
|
+
[SymbolKind.Interface]: 'interface',
|
|
50
|
+
[SymbolKind.Function]: 'function',
|
|
51
|
+
[SymbolKind.Variable]: 'variable',
|
|
52
|
+
[SymbolKind.Constant]: 'constant',
|
|
53
|
+
[SymbolKind.String]: 'string',
|
|
54
|
+
[SymbolKind.Number]: 'number',
|
|
55
|
+
[SymbolKind.Boolean]: 'boolean',
|
|
56
|
+
[SymbolKind.Array]: 'array',
|
|
57
|
+
[SymbolKind.Object]: 'object',
|
|
58
|
+
[SymbolKind.Key]: 'key',
|
|
59
|
+
[SymbolKind.Null]: 'null',
|
|
60
|
+
[SymbolKind.EnumMember]: 'enum-member',
|
|
61
|
+
[SymbolKind.Struct]: 'struct',
|
|
62
|
+
[SymbolKind.Event]: 'event',
|
|
63
|
+
[SymbolKind.Operator]: 'operator',
|
|
64
|
+
[SymbolKind.TypeParameter]: 'type-param',
|
|
65
|
+
};
|
|
66
|
+
return labels[kind] ?? `kind(${kind})`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function fmtRange(range: Range): string {
|
|
70
|
+
const sl = range.start.line + 1;
|
|
71
|
+
const sc = range.start.character + 1;
|
|
72
|
+
const el = range.end.line + 1;
|
|
73
|
+
const ec = range.end.character + 1;
|
|
74
|
+
return sl === el ? `line ${sl}:${sc}-${ec}` : `lines ${sl}:${sc}-${el}:${ec}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function relativePath(uri: string, rootPath: string): string {
|
|
78
|
+
let p = uriToPath(uri);
|
|
79
|
+
if (p.startsWith(rootPath + '/')) p = p.slice(rootPath.length + 1);
|
|
80
|
+
return p;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── Diagnostics ─────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
interface DiagnosticGroup {
|
|
86
|
+
source: string;
|
|
87
|
+
diagnostics: Diagnostic[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function formatDiagnostics(filePath: string, groups: DiagnosticGroup[]): string {
|
|
91
|
+
const allDiags = groups.flatMap((g) => g.diagnostics);
|
|
92
|
+
|
|
93
|
+
const totalErrors = allDiags.filter((d) => d.severity === DiagnosticSeverity.Error).length;
|
|
94
|
+
const totalWarnings = allDiags.filter((d) => d.severity === DiagnosticSeverity.Warning).length;
|
|
95
|
+
const totalOther = allDiags.length - totalErrors - totalWarnings;
|
|
96
|
+
|
|
97
|
+
if (allDiags.length === 0) {
|
|
98
|
+
return `${filePath}: No diagnostics — all clean ✓`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const summaryParts: string[] = [];
|
|
102
|
+
if (totalErrors > 0) summaryParts.push(`${totalErrors} error${totalErrors !== 1 ? 's' : ''}`);
|
|
103
|
+
if (totalWarnings > 0)
|
|
104
|
+
summaryParts.push(`${totalWarnings} warning${totalWarnings !== 1 ? 's' : ''}`);
|
|
105
|
+
if (totalOther > 0) summaryParts.push(`${totalOther} info/hint`);
|
|
106
|
+
|
|
107
|
+
const lines = [`Diagnostics for ${filePath}: ${summaryParts.join(', ')}`];
|
|
108
|
+
|
|
109
|
+
for (const group of groups) {
|
|
110
|
+
if (group.diagnostics.length === 0) continue;
|
|
111
|
+
lines.push('', `── ${group.source} ──`);
|
|
112
|
+
for (let i = 0; i < group.diagnostics.length; i++) {
|
|
113
|
+
const d = group.diagnostics[i];
|
|
114
|
+
const sev = severityLabel(d.severity);
|
|
115
|
+
const loc = fmtRange(d.range);
|
|
116
|
+
const src = d.source ? `[${d.source}]` : '';
|
|
117
|
+
const code = d.code ? `(${d.code})` : '';
|
|
118
|
+
lines.push(`${i + 1}. ${sev} ${loc} ${src}${code}`);
|
|
119
|
+
lines.push(` ${d.message}`);
|
|
120
|
+
if (d.codeDescription?.href) lines.push(` Docs: ${d.codeDescription.href}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return lines.join('\n');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── Hover ───────────────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
export function formatHover(
|
|
130
|
+
hover: Hover | null,
|
|
131
|
+
filePath: string,
|
|
132
|
+
line: number,
|
|
133
|
+
character: number,
|
|
134
|
+
): string {
|
|
135
|
+
if (!hover) return `No hover information at ${filePath}:${line + 1}:${character + 1}`;
|
|
136
|
+
|
|
137
|
+
const contents = hover.contents;
|
|
138
|
+
let text: string;
|
|
139
|
+
|
|
140
|
+
if (typeof contents === 'string') {
|
|
141
|
+
text = contents;
|
|
142
|
+
} else if (Array.isArray(contents)) {
|
|
143
|
+
text = contents
|
|
144
|
+
.map((c) => (typeof c === 'string' ? c : `\`\`\`${c.language}\n${c.value}\n\`\`\``))
|
|
145
|
+
.join('\n\n');
|
|
146
|
+
} else if ('value' in contents) {
|
|
147
|
+
text = contents.value;
|
|
148
|
+
} else {
|
|
149
|
+
text = JSON.stringify(contents);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return `Hover at ${filePath}:${line + 1}:${character + 1}:\n\n${text}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── Locations (definition / references / implementation) ────────────────────
|
|
156
|
+
|
|
157
|
+
export function formatLocations(
|
|
158
|
+
locations: Location[],
|
|
159
|
+
kind: string,
|
|
160
|
+
filePath: string,
|
|
161
|
+
line: number,
|
|
162
|
+
character: number,
|
|
163
|
+
rootPath: string,
|
|
164
|
+
): string {
|
|
165
|
+
const queryPos = `${filePath}:${line + 1}:${character + 1}`;
|
|
166
|
+
|
|
167
|
+
if (locations.length === 0) return `No ${kind} found for symbol at ${queryPos}`;
|
|
168
|
+
|
|
169
|
+
const formatted = locations.map((loc, i) => {
|
|
170
|
+
const p = relativePath(loc.uri, rootPath);
|
|
171
|
+
return `${i + 1}. ${p}:${loc.range.start.line + 1}:${loc.range.start.character + 1}`;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return `${kind} for symbol at ${queryPos} (${locations.length} result${locations.length !== 1 ? 's' : ''}):\n\n${formatted.join('\n')}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ── Document Symbols ────────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
function isDocumentSymbol(item: DocumentSymbol | SymbolInformation): item is DocumentSymbol {
|
|
180
|
+
return 'selectionRange' in item;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function formatDocSymbolTree(symbols: DocumentSymbol[], indent: number): string[] {
|
|
184
|
+
const lines: string[] = [];
|
|
185
|
+
const prefix = ' '.repeat(indent);
|
|
186
|
+
for (const sym of symbols) {
|
|
187
|
+
const kind = symbolKindLabel(sym.kind);
|
|
188
|
+
const line = sym.selectionRange.start.line + 1;
|
|
189
|
+
const col = sym.selectionRange.start.character + 1;
|
|
190
|
+
const detail = sym.detail ? ` — ${sym.detail}` : '';
|
|
191
|
+
lines.push(`${prefix}${sym.name} (${kind}) line ${line}:${col}${detail}`);
|
|
192
|
+
if (sym.children?.length) {
|
|
193
|
+
lines.push(...formatDocSymbolTree(sym.children, indent + 1));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return lines;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function formatDocumentSymbols(
|
|
200
|
+
symbols: (DocumentSymbol | SymbolInformation)[],
|
|
201
|
+
filePath: string,
|
|
202
|
+
rootPath: string,
|
|
203
|
+
): string {
|
|
204
|
+
if (symbols.length === 0) return `No symbols found in ${filePath}`;
|
|
205
|
+
|
|
206
|
+
if (symbols.length > 0 && isDocumentSymbol(symbols[0])) {
|
|
207
|
+
const tree = formatDocSymbolTree(symbols as DocumentSymbol[], 0);
|
|
208
|
+
return `Symbols in ${filePath} (${symbols.length} top-level):\n\n${tree.join('\n')}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Flat SymbolInformation
|
|
212
|
+
const formatted = (symbols as SymbolInformation[]).map((sym, i) => {
|
|
213
|
+
const kind = symbolKindLabel(sym.kind);
|
|
214
|
+
const p = relativePath(sym.location.uri, rootPath);
|
|
215
|
+
const line = sym.location.range.start.line + 1;
|
|
216
|
+
const col = sym.location.range.start.character + 1;
|
|
217
|
+
const container = sym.containerName ? ` in ${sym.containerName}` : '';
|
|
218
|
+
return `${i + 1}. ${sym.name} (${kind}) ${p}:${line}:${col}${container}`;
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return `Symbols in ${filePath} (${symbols.length}):\n\n${formatted.join('\n')}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── Workspace Symbols ───────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
export function formatWorkspaceSymbols(
|
|
227
|
+
symbols: SymbolInformation[],
|
|
228
|
+
query: string,
|
|
229
|
+
rootPath: string,
|
|
230
|
+
): string {
|
|
231
|
+
if (symbols.length === 0) return `No workspace symbols matching "${query}"`;
|
|
232
|
+
|
|
233
|
+
const formatted = symbols.slice(0, 50).map((sym, i) => {
|
|
234
|
+
const kind = symbolKindLabel(sym.kind);
|
|
235
|
+
const p = relativePath(sym.location.uri, rootPath);
|
|
236
|
+
const line = sym.location.range.start.line + 1;
|
|
237
|
+
const col = sym.location.range.start.character + 1;
|
|
238
|
+
const container = sym.containerName ? ` in ${sym.containerName}` : '';
|
|
239
|
+
return `${i + 1}. ${sym.name} (${kind}) ${p}:${line}:${col}${container}`;
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const truncated = symbols.length > 50 ? `\n\n(showing 50 of ${symbols.length})` : '';
|
|
243
|
+
return `Workspace symbols matching "${query}" (${symbols.length}):\n\n${formatted.join('\n')}${truncated}`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ── Call Hierarchy ──────────────────────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
function formatCallItem(item: CallHierarchyItem, rootPath: string): string {
|
|
249
|
+
const kind = symbolKindLabel(item.kind);
|
|
250
|
+
const p = relativePath(item.uri, rootPath);
|
|
251
|
+
const line = item.selectionRange.start.line + 1;
|
|
252
|
+
const detail = item.detail ? ` — ${item.detail}` : '';
|
|
253
|
+
return `${item.name} (${kind}) ${p}:${line}${detail}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function formatCallHierarchy(
|
|
257
|
+
items: CallHierarchyItem[],
|
|
258
|
+
filePath: string,
|
|
259
|
+
line: number,
|
|
260
|
+
character: number,
|
|
261
|
+
rootPath: string,
|
|
262
|
+
): string {
|
|
263
|
+
const queryPos = `${filePath}:${line + 1}:${character + 1}`;
|
|
264
|
+
|
|
265
|
+
if (items.length === 0) return `No call hierarchy item at ${queryPos}`;
|
|
266
|
+
|
|
267
|
+
const formatted = items.map((item, i) => `${i + 1}. ${formatCallItem(item, rootPath)}`);
|
|
268
|
+
return `Call hierarchy at ${queryPos}:\n\n${formatted.join('\n')}`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function formatIncomingCalls(
|
|
272
|
+
calls: CallHierarchyIncomingCall[],
|
|
273
|
+
target: CallHierarchyItem,
|
|
274
|
+
rootPath: string,
|
|
275
|
+
): string {
|
|
276
|
+
if (calls.length === 0) return `No incoming calls to ${target.name}`;
|
|
277
|
+
|
|
278
|
+
const formatted = calls.map((call, i) => `${i + 1}. ${formatCallItem(call.from, rootPath)}`);
|
|
279
|
+
return `Incoming calls to ${target.name} (${calls.length}):\n\n${formatted.join('\n')}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function formatOutgoingCalls(
|
|
283
|
+
calls: CallHierarchyOutgoingCall[],
|
|
284
|
+
source: CallHierarchyItem,
|
|
285
|
+
rootPath: string,
|
|
286
|
+
): string {
|
|
287
|
+
if (calls.length === 0) return `No outgoing calls from ${source.name}`;
|
|
288
|
+
|
|
289
|
+
const formatted = calls.map((call, i) => `${i + 1}. ${formatCallItem(call.to, rootPath)}`);
|
|
290
|
+
return `Outgoing calls from ${source.name} (${calls.length}):\n\n${formatted.join('\n')}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ── Code Actions ────────────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
export function formatCodeActions(actions: CodeAction[], filePath: string, line: number): string {
|
|
296
|
+
if (actions.length === 0) return `No code actions available at ${filePath}:${line + 1}`;
|
|
297
|
+
|
|
298
|
+
const formatted = actions.map((action, i) => {
|
|
299
|
+
const parts = [`${i + 1}. ${action.title}`];
|
|
300
|
+
if (action.kind) parts[0] += ` [${action.kind}]`;
|
|
301
|
+
if (action.isPreferred) parts[0] += ' ★ preferred';
|
|
302
|
+
|
|
303
|
+
if (action.edit?.changes) {
|
|
304
|
+
const files = Object.keys(action.edit.changes);
|
|
305
|
+
const totalEdits = files.reduce((s, f) => s + (action.edit!.changes![f]?.length ?? 0), 0);
|
|
306
|
+
parts.push(
|
|
307
|
+
` Changes: ${totalEdits} edit${totalEdits !== 1 ? 's' : ''} across ${files.length} file${files.length !== 1 ? 's' : ''}`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return parts.join('\n');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return `Code actions at ${filePath}:${line + 1} (${actions.length} available):\n\n${formatted.join('\n\n')}`;
|
|
315
|
+
}
|