@pugi/cli 0.1.0-beta.9 → 0.1.0-beta.91
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/CHANGELOG.md +132 -0
- package/LICENSE +1 -1
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/deploy.js +40 -40
- package/dist/commands/flatten.js +191 -0
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +42 -27
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/agents/adaptive-router.js +330 -0
- package/dist/core/agents/query-decomposer.js +297 -0
- package/dist/core/agents/registry.js +3 -3
- package/dist/core/approvals/shortcut-resolver.js +98 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/ask-user/question.js +92 -0
- package/dist/core/audit/audit-trail.js +275 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-open-browser.js +4 -4
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash/redirect.js +281 -0
- package/dist/core/bash-classifier.js +436 -40
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/checkpoints/shadow-git.js +670 -0
- package/dist/core/citations/parser.js +109 -0
- package/dist/core/classifier/yolo-classifier.js +88 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/anvil-fanout.js +25 -25
- package/dist/core/consensus/diff-capture.js +121 -12
- package/dist/core/consensus/rubric.js +21 -21
- package/dist/core/context/builder.js +6 -6
- package/dist/core/context/compaction-events.js +8 -8
- package/dist/core/context/compaction.js +31 -31
- package/dist/core/context/index.js +15 -8
- package/dist/core/context/invariants.js +51 -51
- package/dist/core/context/markdown-loader.js +28 -10
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/context/pugiignore.js +41 -41
- package/dist/core/context/repo-skeleton.js +37 -37
- package/dist/core/context/tool-eviction.js +55 -0
- package/dist/core/context/watcher.js +32 -32
- package/dist/core/context/working-set.js +23 -23
- package/dist/core/coordinator/agent-tools.js +77 -0
- package/dist/core/coordinator/agent-toolset.js +65 -0
- package/dist/core/coordinator/fsm.js +73 -0
- package/dist/core/coordinator/mode-fsm.js +70 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/credentials.js +13 -13
- package/dist/core/cron/scheduler.js +138 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +93 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/engine-live.js +46 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +40 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/apply-patch-layer-e.js +189 -0
- package/dist/core/edits/dispatch.js +333 -7
- package/dist/core/edits/format-detector.js +260 -0
- package/dist/core/edits/format-matrix.js +26 -0
- package/dist/core/edits/fuzzy-ladder.js +650 -0
- package/dist/core/edits/index.js +5 -1
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-a-apply.js +15 -15
- package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
- package/dist/core/edits/layer-b-apply.js +9 -9
- package/dist/core/edits/layer-c-apply.js +6 -6
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/marker-parser.js +12 -12
- package/dist/core/edits/security-gate.js +27 -27
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +29 -29
- package/dist/core/engine/anvil-client.js +214 -26
- package/dist/core/engine/auto-compact.js +179 -0
- package/dist/core/engine/budgets.js +186 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/intensity.js +158 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +1295 -227
- package/dist/core/engine/prompts.js +129 -19
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1792 -59
- package/dist/core/evaluation/golden-dataset.js +293 -0
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/flatten/flatten-repo.js +439 -0
- package/dist/core/format/osc8-link.js +28 -0
- package/dist/core/hook-chains.js +392 -0
- package/dist/core/hooks/citation-verify-hook.js +138 -0
- package/dist/core/hooks/citation-verify.js +112 -0
- package/dist/core/hooks/events.js +46 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +216 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/hooks/worktree-events.js +158 -0
- package/dist/core/image/renderer.js +71 -0
- package/dist/core/init/detector.js +582 -0
- package/dist/core/init/template-renderer.js +242 -0
- package/dist/core/jobs/registry.js +18 -18
- package/dist/core/ledger/results-tsv.js +142 -0
- package/dist/core/log-discipline/stdout-redirect.js +51 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +551 -41
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/lsp/server-detect.js +173 -0
- package/dist/core/lsp/symbol-cache.js +162 -0
- package/dist/core/lsp/symbol-tools.js +664 -0
- package/dist/core/mcp/client.js +97 -28
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +39 -17
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/mcp/trust.js +10 -10
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/passive-extract.js +130 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory/secret-scanner.js +304 -0
- package/dist/core/memory-sync/queue.js +170 -0
- package/dist/core/metrics/extract.js +113 -0
- package/dist/core/modes/roo-modes.js +68 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +287 -5
- package/dist/core/permission.js +82 -22
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/bash-parser.js +371 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/constrained-edit.js +91 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/network-egress.js +137 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/plan-mode/ui-state.js +51 -0
- package/dist/core/plans/plan-artifact.js +721 -0
- package/dist/core/policy-limits/etag-store.js +122 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/prompt-cache/client-cache.js +99 -0
- package/dist/core/prompts/assembly.js +29 -0
- package/dist/core/prompts/registry.js +364 -0
- package/dist/core/pugi-md/cc-compat-rules.js +735 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/python/uv-installer.js +270 -0
- package/dist/core/python/uv-resolver.js +83 -0
- package/dist/core/rate-limit/narrator.js +146 -0
- package/dist/core/recipes/cli-types.js +20 -0
- package/dist/core/recipes/loader.js +103 -0
- package/dist/core/recipes/runner.js +345 -0
- package/dist/core/recipes/schema.js +587 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/ask.js +37 -37
- package/dist/core/repl/cancellation.js +26 -26
- package/dist/core/repl/cap-warning.js +4 -4
- package/dist/core/repl/clipboard-read.js +11 -11
- package/dist/core/repl/dispatch-fsm.js +12 -12
- package/dist/core/repl/history-search.js +15 -15
- package/dist/core/repl/history.js +28 -18
- package/dist/core/repl/kill-ring.js +5 -5
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/privacy-banner.js +22 -22
- package/dist/core/repl/session.js +2148 -217
- package/dist/core/repl/slash-commands.js +501 -41
- package/dist/core/repl/store/index.js +1 -1
- package/dist/core/repl/store/jsonl-log.js +22 -22
- package/dist/core/repl/store/lockfile.js +10 -10
- package/dist/core/repl/store/session-store.js +136 -107
- package/dist/core/repl/store/types.js +15 -15
- package/dist/core/repl/store/uuid-v7.js +12 -12
- package/dist/core/repl/workspace-context.js +43 -21
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/page-rank.js +105 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/retry-budget/retry-cap.js +74 -0
- package/dist/core/routing/lead-worker.js +43 -0
- package/dist/core/routing/pre-flight-estimator.js +108 -0
- package/dist/core/runs/run-tree.js +103 -0
- package/dist/core/security/injection-scanner.js +367 -0
- package/dist/core/security/output-filter.js +418 -0
- package/dist/core/session/env-file.js +105 -0
- package/dist/core/session/section-budgets.js +140 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +324 -5
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +30 -30
- package/dist/core/skills/loader.js +22 -22
- package/dist/core/skills/sources.js +27 -27
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/statusline.js +99 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +132 -43
- package/dist/core/subagents/index.js +19 -6
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/tool-schema/compressor.js +89 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/trust.js +2 -2
- package/dist/core/tui/thinking-block.js +64 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/watch-markers/marker-watcher.js +133 -0
- package/dist/core/worktree/include-parser.js +249 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +36 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +4185 -549
- package/dist/runtime/commands/agents.js +31 -31
- package/dist/runtime/commands/budget.js +5 -5
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/config.js +73 -39
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +27 -4
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +579 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +187 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +200 -38
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +582 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +12 -12
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/privacy.js +17 -17
- package/dist/runtime/commands/recipe.js +325 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +68 -53
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/roster.js +14 -14
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/skills.js +31 -31
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +54 -22
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +8 -8
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +22 -22
- package/dist/runtime/sigint-guard.js +272 -0
- package/dist/runtime/update-check.js +28 -28
- package/dist/runtime/version.js +65 -0
- package/dist/runtime/worktree-bootstrap.js +579 -0
- package/dist/skills/bundled/batch.js +617 -0
- package/dist/skills/bundled/index.js +45 -0
- package/dist/skills/bundled/loop.js +358 -0
- package/dist/skills/bundled/remember.js +383 -0
- package/dist/skills/bundled/simplify.js +289 -0
- package/dist/skills/bundled/skillify.js +373 -0
- package/dist/skills/bundled/stuck.js +558 -0
- package/dist/skills/bundled/verify.js +439 -0
- package/dist/testing/vcr.js +486 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +89 -28
- package/dist/tools/ask-user-question.js +337 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +624 -46
- package/dist/tools/brief.js +224 -0
- package/dist/tools/cron.js +433 -0
- package/dist/tools/enter-worktree.js +250 -0
- package/dist/tools/exit-worktree.js +147 -0
- package/dist/tools/file-tools.js +161 -44
- package/dist/tools/lsp-tools.js +377 -1
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +99 -4
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/sleep.js +99 -0
- package/dist/tools/synthetic-output.js +133 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/verify-plan-execution.js +295 -0
- package/dist/tools/web-fetch-injection-scanner.js +207 -0
- package/dist/tools/web-fetch.js +195 -10
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +11 -1
- package/dist/tui/ask-modal.js +14 -14
- package/dist/tui/ask-user-question-chips.js +315 -0
- package/dist/tui/ask-user-question-prompt.js +203 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +85 -11
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/device-flow.js +2 -2
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +247 -32
- package/dist/tui/login-picker.js +3 -3
- package/dist/tui/markdown-render.js +6 -6
- package/dist/tui/multi-file-diff-approval.js +375 -0
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +36 -1
- package/dist/tui/repl-render.js +176 -25
- package/dist/tui/repl-splash-art.js +16 -16
- package/dist/tui/repl-splash-mascot.js +48 -24
- package/dist/tui/repl-splash.js +22 -22
- package/dist/tui/repl.js +125 -45
- package/dist/tui/slash-palette.js +6 -6
- package/dist/tui/splash.js +2 -2
- package/dist/tui/status-bar.js +109 -31
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +53 -4
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/dist/tui/workspace-context.js +2 -2
- package/package.json +31 -16
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +12 -0
- package/test/scenarios/identity.scenario.txt +12 -0
- package/test/scenarios/persona-handoff.scenario.txt +12 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
package/dist/core/lsp/client.js
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* LSP client wrapper —
|
|
2
|
+
* LSP client wrapper — Phase 1 (LSP tool + worktree + apply_patch).
|
|
3
3
|
*
|
|
4
4
|
* Wraps a Language Server Protocol server process (started locally via
|
|
5
5
|
* stdio) behind a small async surface used by the LSP tools:
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* - hover(file, line, col)
|
|
8
|
+
* - definition(file, line, col)
|
|
9
|
+
* - references(file, line, col)
|
|
10
|
+
* - diagnostics(file)
|
|
11
11
|
*
|
|
12
12
|
* Why no `vscode-languageserver-protocol` dep:
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
14
|
+
* The + posture for `apps/pugi-cli` keeps the dep tree intentionally
|
|
15
|
+
* lean (zod, ink, react, undici, tar — that's most of it). Pulling
|
|
16
|
+
* `vscode-languageserver-protocol` + transitive `vscode-jsonrpc` for
|
|
17
|
+
* four operations would expand the install footprint disproportionately.
|
|
18
|
+
* The LSP framing protocol (Content-Length headers + JSON-RPC bodies)
|
|
19
|
+
* is documented in the LSP spec §3 and stable since 2017; we implement
|
|
20
|
+
* the framer ourselves in ~80 LOC. Pulling in the official packages
|
|
21
|
+
* later (when we add 20+ operations) stays an open option — every
|
|
22
|
+
* public surface here mirrors the official type names so a future swap
|
|
23
|
+
* is mechanical.
|
|
24
24
|
*
|
|
25
25
|
* Why we spawn `npx <server>` for stock languages:
|
|
26
26
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
27
|
+
* - typescript-language-server: `npx typescript-language-server --stdio`
|
|
28
|
+
* - pyright: `pyright-langserver --stdio` (operator-installed)
|
|
29
|
+
* - gopls: `gopls` (operator-installed via `go install`)
|
|
30
|
+
* - rust-analyzer: `rust-analyzer` (operator-installed via `rustup`)
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
32
|
+
* `npx typescript-language-server` resolves the binary from the
|
|
33
|
+
* workspace's `node_modules/.bin` first, then falls back to the global
|
|
34
|
+
* npm cache. The other servers are operator-installed system binaries
|
|
35
|
+
* (we run `which <name>` once at start and fail loud with
|
|
36
|
+
* `lsp_unavailable` if absent — see `detectServer` below). This keeps
|
|
37
|
+
* `pugi` install-time zero-deps for non-TS workspaces.
|
|
38
38
|
*
|
|
39
39
|
* Lifecycle contract:
|
|
40
40
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
41
|
+
* 1. `startLspClient(lang, opts)` spawns the server, performs the LSP
|
|
42
|
+
* `initialize` handshake, sends `initialized`, and returns a client.
|
|
43
|
+
* 2. Each operation auto-opens the target file via `textDocument/didOpen`
|
|
44
|
+
* on first touch (we keep a `Set<string>` of opened URIs). Files are
|
|
45
|
+
* never closed mid-session — the server is per-CLI-invocation and
|
|
46
|
+
* the process exit cleans up.
|
|
47
|
+
* 3. `stop()` sends `shutdown` + `exit` (best-effort) and SIGKILLs the
|
|
48
|
+
* child after a 1s grace window so the CLI never hangs on a
|
|
49
|
+
* misbehaving server.
|
|
50
50
|
*
|
|
51
51
|
* Cancellation: every async operation accepts an optional CancellationToken
|
|
52
|
-
*
|
|
52
|
+
* . On abort, we send LSP `$/cancelRequest` for the in-flight ID and
|
|
53
53
|
* reject the promise with `OperatorAbortedError`.
|
|
54
54
|
*
|
|
55
55
|
* Privacy: requests stay client-side. There is no Anvil round-trip; the
|
|
@@ -130,7 +130,7 @@ export class LspClient {
|
|
|
130
130
|
child.stderr.on('data', () => { });
|
|
131
131
|
}
|
|
132
132
|
child.on('exit', () => this.onExit());
|
|
133
|
-
// R1 fix (2026-05-26, PR
|
|
133
|
+
// R1 fix (2026-05-26, PR r1, P2 #11): mirror onExit for the
|
|
134
134
|
// 'error' event. A late-fired spawn error (EIO, ENOMEM, etc.) or
|
|
135
135
|
// any unhandled child-process error would otherwise leave
|
|
136
136
|
// in-flight pending requests dangling until their per-request
|
|
@@ -206,6 +206,189 @@ export class LspClient {
|
|
|
206
206
|
return { ok: true, value: locations };
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* PUGI-78 Phase 1: document outline. Returns the LSP raw shape (either
|
|
211
|
+
* `DocumentSymbol[]` hierarchical or `SymbolInformation[]` flat); the
|
|
212
|
+
* symbol-tools layer normalizes both to the agent-facing flat form.
|
|
213
|
+
*/
|
|
214
|
+
async documentSymbols(file, token) {
|
|
215
|
+
return this.withDocument(file, async (uri) => {
|
|
216
|
+
const raw = await this.sendRequest('textDocument/documentSymbol', { textDocument: { uri } }, token);
|
|
217
|
+
if (!Array.isArray(raw))
|
|
218
|
+
return { ok: true, value: [] };
|
|
219
|
+
return { ok: true, value: raw };
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* PUGI-78 Phase 1: workspace-wide symbol fuzzy search. The query is
|
|
224
|
+
* server-defined (substring / fuzzy / prefix); we forward verbatim.
|
|
225
|
+
* Files outside the workspace are kept as raw URIs — the symbol-tools
|
|
226
|
+
* layer surfaces them to the operator as-is.
|
|
227
|
+
*/
|
|
228
|
+
async workspaceSymbols(query, token) {
|
|
229
|
+
// workspace/symbol does not need a document open, but we still
|
|
230
|
+
// honor the cancellation token. We call sendRequest directly via
|
|
231
|
+
// the typed internal accessor used by the handshake.
|
|
232
|
+
try {
|
|
233
|
+
const internal = this;
|
|
234
|
+
const raw = await internal.sendRequest('workspace/symbol', { query }, token);
|
|
235
|
+
if (!Array.isArray(raw))
|
|
236
|
+
return { ok: true, value: [] };
|
|
237
|
+
return { ok: true, value: raw };
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
if (error instanceof OperatorAbortedError) {
|
|
241
|
+
return { ok: false, reason: 'operator_aborted', detail: error.message };
|
|
242
|
+
}
|
|
243
|
+
if (error instanceof Error && error.message === 'request_timeout') {
|
|
244
|
+
return { ok: false, reason: 'request_timeout', detail: 'workspace/symbol timed out' };
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
ok: false,
|
|
248
|
+
reason: 'lsp_error',
|
|
249
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* PUGI-78 Phase 1: implementations of an interface / abstract method.
|
|
255
|
+
* LSP `textDocument/implementation` returns the same `Location[]` shape
|
|
256
|
+
* as `definition`, so we re-use `normalizeLocations`.
|
|
257
|
+
*/
|
|
258
|
+
async implementations(file, pos, token) {
|
|
259
|
+
return this.withDocument(file, async (uri) => {
|
|
260
|
+
const raw = await this.sendRequest('textDocument/implementation', { textDocument: { uri }, position: pos }, token);
|
|
261
|
+
const locations = normalizeLocations(raw, this.cwd);
|
|
262
|
+
return { ok: true, value: locations };
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* PUGI-78 Phase 1: type-definition (vs value-definition). Same wire
|
|
267
|
+
* shape as `definition` — server reports the location of the symbol's
|
|
268
|
+
* type declaration, not its instantiation site.
|
|
269
|
+
*/
|
|
270
|
+
async typeDefinition(file, pos, token) {
|
|
271
|
+
return this.withDocument(file, async (uri) => {
|
|
272
|
+
const raw = await this.sendRequest('textDocument/typeDefinition', { textDocument: { uri }, position: pos }, token);
|
|
273
|
+
const locations = normalizeLocations(raw, this.cwd);
|
|
274
|
+
return { ok: true, value: locations };
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* PUGI-78 Phase 1: signature help at a call site. Returns the active
|
|
279
|
+
* overload's label, parameters, and active-parameter index. Null when
|
|
280
|
+
* the server reports no signature (cursor is not inside a call).
|
|
281
|
+
*/
|
|
282
|
+
async signatureHelp(file, pos, token) {
|
|
283
|
+
return this.withDocument(file, async (uri) => {
|
|
284
|
+
const raw = await this.sendRequest('textDocument/signatureHelp', { textDocument: { uri }, position: pos }, token);
|
|
285
|
+
return { ok: true, value: normalizeSignatureHelp(raw) };
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* PUGI-78 Phase 1: prepare a rename. Returns the range LSP says the
|
|
290
|
+
* symbol occupies; the dispatcher uses this to confirm the cursor is
|
|
291
|
+
* actually on a renameable token before issuing the full rename.
|
|
292
|
+
*/
|
|
293
|
+
async prepareRename(file, pos, token) {
|
|
294
|
+
return this.withDocument(file, async (uri) => {
|
|
295
|
+
try {
|
|
296
|
+
const raw = await this.sendRequest('textDocument/prepareRename', { textDocument: { uri }, position: pos }, token);
|
|
297
|
+
if (!raw)
|
|
298
|
+
return { ok: true, value: null };
|
|
299
|
+
// LSP allows { range, placeholder } OR a raw Range; surface
|
|
300
|
+
// either as a flat Range.
|
|
301
|
+
if (typeof raw === 'object' && raw && 'start' in raw && 'end' in raw) {
|
|
302
|
+
const range = parseRange(raw);
|
|
303
|
+
return { ok: true, value: range ?? null };
|
|
304
|
+
}
|
|
305
|
+
if (typeof raw === 'object' && raw && 'range' in raw) {
|
|
306
|
+
const range = parseRange(raw.range);
|
|
307
|
+
return { ok: true, value: range ?? null };
|
|
308
|
+
}
|
|
309
|
+
return { ok: true, value: null };
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
// Some servers (pyright pre-1.1) lack prepareRename support and
|
|
313
|
+
// return a JSON-RPC error. The dispatcher should still be able
|
|
314
|
+
// to attempt the rename — surface null instead of bubbling the
|
|
315
|
+
// failure as the agent surface treats null as "skip prepare".
|
|
316
|
+
if (error instanceof Error && /method not (found|supported)/i.test(error.message)) {
|
|
317
|
+
return { ok: true, value: null };
|
|
318
|
+
}
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* PUGI-78 Phase 1: atomic rename refactor. Returns the workspace edit
|
|
325
|
+
* the server proposes; the dispatcher previews + applies in a future
|
|
326
|
+
* ticket (Phase 2). For Phase 1 the agent surface lists affected files
|
|
327
|
+
* + line:character of each edit so the model can summarize the impact.
|
|
328
|
+
*/
|
|
329
|
+
async rename(file, pos, newName, token) {
|
|
330
|
+
return this.withDocument(file, async (uri) => {
|
|
331
|
+
const raw = await this.sendRequest('textDocument/rename', { textDocument: { uri }, position: pos, newName }, token);
|
|
332
|
+
return { ok: true, value: normalizeWorkspaceEdit(raw, this.cwd) };
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* PUGI-78 Phase 1: quick-fix list at a range. The server returns either
|
|
337
|
+
* `Command[]` (legacy) or `CodeAction[]` (modern); we normalize to the
|
|
338
|
+
* union shape so the surface stays stable.
|
|
339
|
+
*/
|
|
340
|
+
async codeActions(file, range, token) {
|
|
341
|
+
return this.withDocument(file, async (uri) => {
|
|
342
|
+
const raw = await this.sendRequest('textDocument/codeAction', {
|
|
343
|
+
textDocument: { uri },
|
|
344
|
+
range,
|
|
345
|
+
context: { diagnostics: [] },
|
|
346
|
+
}, token);
|
|
347
|
+
return { ok: true, value: normalizeCodeActions(raw) };
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* PUGI-78 Phase 1: formatter. Returns the text edits the server would
|
|
352
|
+
* apply for `textDocument/formatting`. The dispatcher composes them
|
|
353
|
+
* into a single rewrite in a future ticket; Phase 1 surfaces the edits
|
|
354
|
+
* for inspection.
|
|
355
|
+
*/
|
|
356
|
+
async formatting(file, token, options) {
|
|
357
|
+
return this.withDocument(file, async (uri) => {
|
|
358
|
+
const raw = await this.sendRequest('textDocument/formatting', {
|
|
359
|
+
textDocument: { uri },
|
|
360
|
+
options: {
|
|
361
|
+
tabSize: options?.tabSize ?? 2,
|
|
362
|
+
insertSpaces: options?.insertSpaces ?? true,
|
|
363
|
+
},
|
|
364
|
+
}, token);
|
|
365
|
+
return { ok: true, value: normalizeTextEdits(raw) };
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* PUGI-78 Phase 1: call hierarchy at a symbol position. The LSP wire
|
|
370
|
+
* is a two-step protocol — first `prepareCallHierarchy` to get the
|
|
371
|
+
* item handle, then `incomingCalls` and `outgoingCalls`. We surface
|
|
372
|
+
* both edges in a single call.
|
|
373
|
+
*/
|
|
374
|
+
async callHierarchy(file, pos, token) {
|
|
375
|
+
return this.withDocument(file, async (uri) => {
|
|
376
|
+
const prepared = await this.sendRequest('textDocument/prepareCallHierarchy', { textDocument: { uri }, position: pos }, token);
|
|
377
|
+
if (!Array.isArray(prepared) || prepared.length === 0) {
|
|
378
|
+
return { ok: true, value: { incoming: [], outgoing: [] } };
|
|
379
|
+
}
|
|
380
|
+
const item = prepared[0];
|
|
381
|
+
const incomingRaw = await this.sendRequest('callHierarchy/incomingCalls', { item }, token);
|
|
382
|
+
const outgoingRaw = await this.sendRequest('callHierarchy/outgoingCalls', { item }, token);
|
|
383
|
+
return {
|
|
384
|
+
ok: true,
|
|
385
|
+
value: {
|
|
386
|
+
incoming: normalizeCallHierarchyEdges(incomingRaw, 'from', this.cwd),
|
|
387
|
+
outgoing: normalizeCallHierarchyEdges(outgoingRaw, 'to', this.cwd),
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
});
|
|
391
|
+
}
|
|
209
392
|
/**
|
|
210
393
|
* Diagnostics in LSP arrive as PUSH (`textDocument/publishDiagnostics`)
|
|
211
394
|
* not pull. We open the document, wait one short tick for the server
|
|
@@ -260,7 +443,7 @@ export class LspClient {
|
|
|
260
443
|
detail: error instanceof Error ? error.message : String(error),
|
|
261
444
|
};
|
|
262
445
|
}
|
|
263
|
-
// R1 fix (2026-05-26, PR
|
|
446
|
+
// R1 fix (2026-05-26, PR r1, Fix 8): realpath containment.
|
|
264
447
|
// Without this gate, a workspace-local symlink (e.g. `alias` ->
|
|
265
448
|
// `/etc/passwd`) passed the lexical `absPath.startsWith(cwd)`
|
|
266
449
|
// check, then `readFileSync(absPath, 'utf8')` happily followed the
|
|
@@ -385,7 +568,7 @@ export class LspClient {
|
|
|
385
568
|
const headerText = this.buffer.subarray(0, headerEnd).toString('ascii');
|
|
386
569
|
const lengthMatch = headerText.match(/Content-Length:\s*(\d+)/i);
|
|
387
570
|
if (!lengthMatch || lengthMatch[1] === undefined) {
|
|
388
|
-
// R1 fix (2026-05-26, PR
|
|
571
|
+
// R1 fix (2026-05-26, PR r1, Fix 7): malformed header —
|
|
389
572
|
// instead of nuking the entire buffer (which would discard ANY
|
|
390
573
|
// subsequent valid messages already queued in `this.buffer`),
|
|
391
574
|
// scan forward for the next `Content-Length:` marker and resync
|
|
@@ -465,10 +648,59 @@ export class LspClient {
|
|
|
465
648
|
}
|
|
466
649
|
}
|
|
467
650
|
}
|
|
651
|
+
/**
|
|
652
|
+
* Map a short LSP language slug to the settings.json key. β7 L9 — the
|
|
653
|
+
* settings schema spells out the full language name (`typescript`,
|
|
654
|
+
* `python`, ...) for human readability; the short slug (`ts`, `py`) is
|
|
655
|
+
* what every internal call site uses. Keep this map narrow and explicit.
|
|
656
|
+
*/
|
|
657
|
+
const SETTINGS_KEY_BY_LANG = {
|
|
658
|
+
ts: 'typescript',
|
|
659
|
+
js: 'javascript',
|
|
660
|
+
py: 'python',
|
|
661
|
+
go: 'go',
|
|
662
|
+
rust: 'rust',
|
|
663
|
+
};
|
|
664
|
+
/**
|
|
665
|
+
* Report whether the operator has explicitly disabled this language via
|
|
666
|
+
* `.pugi/settings.json::lsp.<language> = false`. Absent section or
|
|
667
|
+
* absent key means "enabled by default" — backwards-compatible with the
|
|
668
|
+
* surface that ignored settings entirely. Returns true ONLY when
|
|
669
|
+
* the operator explicitly set the value to false.
|
|
670
|
+
*/
|
|
671
|
+
export function isLspLanguageDisabled(lang, lspSettings) {
|
|
672
|
+
if (!lspSettings)
|
|
673
|
+
return false;
|
|
674
|
+
const key = SETTINGS_KEY_BY_LANG[lang];
|
|
675
|
+
return lspSettings[key] === false;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Probe every registered language server. Operator-facing helper for
|
|
679
|
+
* `pugi lsp servers` — returns one row per language with the binary
|
|
680
|
+
* name, whether it was found on PATH, and whether the settings toggle
|
|
681
|
+
* has explicitly disabled it.
|
|
682
|
+
*/
|
|
683
|
+
export function inspectLspServers(lspSettings) {
|
|
684
|
+
const out = [];
|
|
685
|
+
for (const lang of Object.keys(LANGUAGE_SERVERS)) {
|
|
686
|
+
const server = LANGUAGE_SERVERS[lang];
|
|
687
|
+
out.push({
|
|
688
|
+
language: lang,
|
|
689
|
+
command: server.command + (server.args.length > 0 ? ` ${server.args.join(' ')}` : ''),
|
|
690
|
+
available: detectBinary(server.probe),
|
|
691
|
+
enabled: !isLspLanguageDisabled(lang, lspSettings),
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
return out;
|
|
695
|
+
}
|
|
468
696
|
/**
|
|
469
697
|
* Start an LSP client for the given language. Returns either an `LspClient`
|
|
470
698
|
* ready to use, or a structured failure (`lsp_unavailable`,
|
|
471
699
|
* `language_unsupported`).
|
|
700
|
+
*
|
|
701
|
+
* β7 L9: respects `.pugi/settings.json::lsp.<language> = false` —
|
|
702
|
+
* a disabled language reports `lsp_disabled` so the caller surface can
|
|
703
|
+
* tell the operator the binary IS available but settings says no.
|
|
472
704
|
*/
|
|
473
705
|
export async function startLspClient(lang, opts) {
|
|
474
706
|
const server = opts.serverOverride ?? LANGUAGE_SERVERS[lang];
|
|
@@ -479,6 +711,14 @@ export async function startLspClient(lang, opts) {
|
|
|
479
711
|
detail: `no LSP server registered for language: ${lang}`,
|
|
480
712
|
};
|
|
481
713
|
}
|
|
714
|
+
if (!opts.serverOverride && isLspLanguageDisabled(lang, opts.lspSettings)) {
|
|
715
|
+
return {
|
|
716
|
+
ok: false,
|
|
717
|
+
reason: 'lsp_disabled',
|
|
718
|
+
detail: `${lang} is disabled in .pugi/settings.json::lsp.${SETTINGS_KEY_BY_LANG[lang]}. ` +
|
|
719
|
+
`Remove the override (or set it to true) to enable.`,
|
|
720
|
+
};
|
|
721
|
+
}
|
|
482
722
|
if (!opts.serverOverride) {
|
|
483
723
|
const available = detectBinary(server.probe);
|
|
484
724
|
if (!available) {
|
|
@@ -575,6 +815,27 @@ async function initializeHandshake(client, cwd) {
|
|
|
575
815
|
definition: { linkSupport: false },
|
|
576
816
|
references: {},
|
|
577
817
|
publishDiagnostics: { relatedInformation: false },
|
|
818
|
+
// PUGI-78 Phase 1: full 13-tool symbol surface. Each block
|
|
819
|
+
// mirrors the LSP §3.17 dynamic capability advertisement so
|
|
820
|
+
// the server enables the corresponding feature (e.g. pyright
|
|
821
|
+
// gates `workspace/symbol` on this flag). `dynamicRegistration`
|
|
822
|
+
// stays false for every entry — we never register a capability
|
|
823
|
+
// post-initialize; the static block is the only handshake.
|
|
824
|
+
documentSymbol: {
|
|
825
|
+
hierarchicalDocumentSymbolSupport: true,
|
|
826
|
+
symbolKind: { valueSet: Array.from({ length: 26 }, (_, i) => i + 1) },
|
|
827
|
+
},
|
|
828
|
+
signatureHelp: { signatureInformation: { documentationFormat: ['plaintext', 'markdown'] } },
|
|
829
|
+
implementation: { linkSupport: false },
|
|
830
|
+
typeDefinition: { linkSupport: false },
|
|
831
|
+
rename: { prepareSupport: true },
|
|
832
|
+
codeAction: { codeActionLiteralSupport: { codeActionKind: { valueSet: ['', 'quickfix', 'refactor', 'source'] } } },
|
|
833
|
+
formatting: {},
|
|
834
|
+
callHierarchy: {},
|
|
835
|
+
},
|
|
836
|
+
workspace: {
|
|
837
|
+
symbol: { symbolKind: { valueSet: Array.from({ length: 26 }, (_, i) => i + 1) } },
|
|
838
|
+
workspaceEdit: { documentChanges: true },
|
|
578
839
|
},
|
|
579
840
|
},
|
|
580
841
|
workspaceFolders: [{ uri: rootUri, name: 'pugi-workspace' }],
|
|
@@ -597,9 +858,9 @@ function detectBinary(name) {
|
|
|
597
858
|
}
|
|
598
859
|
function normalizeHover(raw) {
|
|
599
860
|
// LSP `Hover.contents` can be:
|
|
600
|
-
//
|
|
601
|
-
//
|
|
602
|
-
//
|
|
861
|
+
// - string
|
|
862
|
+
// - { kind: 'markdown' | 'plaintext', value: string }
|
|
863
|
+
// - Array<string | { language: string, value: string }>
|
|
603
864
|
if (!raw || typeof raw !== 'object') {
|
|
604
865
|
return { content: String(raw ?? ''), raw };
|
|
605
866
|
}
|
|
@@ -705,6 +966,249 @@ function normalizeDiagnostic(raw) {
|
|
|
705
966
|
};
|
|
706
967
|
return out;
|
|
707
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* PUGI-78 Phase 1: collapse a `textDocument/signatureHelp` response to
|
|
971
|
+
* one active overload. LSP returns `{ signatures, activeSignature,
|
|
972
|
+
* activeParameter }`; we surface the active overload's label, params,
|
|
973
|
+
* and the active-param index. Returns null when the server reports no
|
|
974
|
+
* signatures (cursor not at a call site).
|
|
975
|
+
*/
|
|
976
|
+
function normalizeSignatureHelp(raw) {
|
|
977
|
+
if (!raw || typeof raw !== 'object')
|
|
978
|
+
return null;
|
|
979
|
+
const obj = raw;
|
|
980
|
+
if (!Array.isArray(obj.signatures) || obj.signatures.length === 0)
|
|
981
|
+
return null;
|
|
982
|
+
const idx = typeof obj.activeSignature === 'number' && obj.activeSignature >= 0
|
|
983
|
+
? Math.min(obj.activeSignature, obj.signatures.length - 1)
|
|
984
|
+
: 0;
|
|
985
|
+
const sig = obj.signatures[idx];
|
|
986
|
+
if (!sig || typeof sig !== 'object')
|
|
987
|
+
return null;
|
|
988
|
+
const sigObj = sig;
|
|
989
|
+
if (typeof sigObj.label !== 'string')
|
|
990
|
+
return null;
|
|
991
|
+
const params = [];
|
|
992
|
+
if (Array.isArray(sigObj.parameters)) {
|
|
993
|
+
for (const p of sigObj.parameters) {
|
|
994
|
+
if (!p || typeof p !== 'object')
|
|
995
|
+
continue;
|
|
996
|
+
const po = p;
|
|
997
|
+
// LSP allows `label` as string OR [start,end] tuple of offsets;
|
|
998
|
+
// we surface the string form, and for tuple form we slice the
|
|
999
|
+
// signature label.
|
|
1000
|
+
let label = '';
|
|
1001
|
+
if (typeof po.label === 'string')
|
|
1002
|
+
label = po.label;
|
|
1003
|
+
else if (Array.isArray(po.label) && po.label.length === 2 && typeof po.label[0] === 'number' && typeof po.label[1] === 'number') {
|
|
1004
|
+
label = sigObj.label.slice(po.label[0], po.label[1]);
|
|
1005
|
+
}
|
|
1006
|
+
if (!label)
|
|
1007
|
+
continue;
|
|
1008
|
+
const doc = extractMarkupString(po.documentation);
|
|
1009
|
+
params.push({ label, ...(doc ? { documentation: doc } : {}) });
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
const docTop = extractMarkupString(sigObj.documentation);
|
|
1013
|
+
const out = {
|
|
1014
|
+
label: sigObj.label,
|
|
1015
|
+
parameters: params,
|
|
1016
|
+
raw,
|
|
1017
|
+
...(docTop ? { documentation: docTop } : {}),
|
|
1018
|
+
...(typeof obj.activeParameter === 'number' ? { activeParameter: obj.activeParameter } : {}),
|
|
1019
|
+
};
|
|
1020
|
+
return out;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* LSP `MarkupContent | string` -> string. Used by both signature help
|
|
1024
|
+
* (top-level docs + per-param docs) and hover normalization.
|
|
1025
|
+
*/
|
|
1026
|
+
function extractMarkupString(raw) {
|
|
1027
|
+
if (typeof raw === 'string')
|
|
1028
|
+
return raw.length > 0 ? raw : undefined;
|
|
1029
|
+
if (raw && typeof raw === 'object' && 'value' in raw) {
|
|
1030
|
+
const value = raw.value;
|
|
1031
|
+
if (typeof value === 'string' && value.length > 0)
|
|
1032
|
+
return value;
|
|
1033
|
+
}
|
|
1034
|
+
return undefined;
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* PUGI-78 Phase 1: collapse a `textDocument/rename` `WorkspaceEdit` to
|
|
1038
|
+
* the agent-facing flat list. Handles BOTH shapes:
|
|
1039
|
+
* - legacy `changes: { [uri]: TextEdit[] }`
|
|
1040
|
+
* - modern `documentChanges: (TextDocumentEdit | FileOp)[]`
|
|
1041
|
+
*
|
|
1042
|
+
* File-op entries (`CreateFile`, `RenameFile`, `DeleteFile`) are NOT
|
|
1043
|
+
* surfaced in Phase 1 — the only renames Pugi enables out of the box
|
|
1044
|
+
* are symbol-level. Phase 2 wires the file-op variants.
|
|
1045
|
+
*/
|
|
1046
|
+
function normalizeWorkspaceEdit(raw, cwd) {
|
|
1047
|
+
if (!raw || typeof raw !== 'object')
|
|
1048
|
+
return null;
|
|
1049
|
+
const obj = raw;
|
|
1050
|
+
const edits = [];
|
|
1051
|
+
const fileSet = new Set();
|
|
1052
|
+
const pushEdit = (uri, edit) => {
|
|
1053
|
+
if (!edit || typeof edit !== 'object')
|
|
1054
|
+
return;
|
|
1055
|
+
const editObj = edit;
|
|
1056
|
+
const range = parseRange(editObj.range);
|
|
1057
|
+
if (!range)
|
|
1058
|
+
return;
|
|
1059
|
+
const newText = typeof editObj.newText === 'string' ? editObj.newText : '';
|
|
1060
|
+
const file = uriToWorkspacePath(uri, cwd);
|
|
1061
|
+
if (!file)
|
|
1062
|
+
return;
|
|
1063
|
+
fileSet.add(file);
|
|
1064
|
+
edits.push({ file, line: range.start.line, character: range.start.character, newText });
|
|
1065
|
+
};
|
|
1066
|
+
if (obj.changes && typeof obj.changes === 'object') {
|
|
1067
|
+
for (const [uri, list] of Object.entries(obj.changes)) {
|
|
1068
|
+
if (!Array.isArray(list))
|
|
1069
|
+
continue;
|
|
1070
|
+
for (const edit of list)
|
|
1071
|
+
pushEdit(uri, edit);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
if (Array.isArray(obj.documentChanges)) {
|
|
1075
|
+
for (const docChange of obj.documentChanges) {
|
|
1076
|
+
if (!docChange || typeof docChange !== 'object')
|
|
1077
|
+
continue;
|
|
1078
|
+
const dc = docChange;
|
|
1079
|
+
// Skip file-op entries — they have a `kind` discriminator.
|
|
1080
|
+
if (typeof dc.kind === 'string' && dc.kind !== '')
|
|
1081
|
+
continue;
|
|
1082
|
+
const td = dc.textDocument;
|
|
1083
|
+
if (!td || typeof td !== 'object')
|
|
1084
|
+
continue;
|
|
1085
|
+
const uri = td.uri;
|
|
1086
|
+
if (typeof uri !== 'string')
|
|
1087
|
+
continue;
|
|
1088
|
+
if (Array.isArray(dc.edits)) {
|
|
1089
|
+
for (const edit of dc.edits)
|
|
1090
|
+
pushEdit(uri, edit);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
if (fileSet.size === 0)
|
|
1095
|
+
return null;
|
|
1096
|
+
return { files: Array.from(fileSet).sort(), edits, raw };
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Convert an LSP `file://` URI to a workspace-relative path. Empty
|
|
1100
|
+
* string when the URI escapes the workspace; the caller decides whether
|
|
1101
|
+
* to drop the edit or surface the raw URI.
|
|
1102
|
+
*/
|
|
1103
|
+
function uriToWorkspacePath(uri, cwd) {
|
|
1104
|
+
try {
|
|
1105
|
+
const url = new URL(uri);
|
|
1106
|
+
if (url.protocol !== 'file:')
|
|
1107
|
+
return uri;
|
|
1108
|
+
const abs = decodeURIComponent(url.pathname);
|
|
1109
|
+
if (abs === cwd)
|
|
1110
|
+
return '.';
|
|
1111
|
+
if (abs.startsWith(cwd + sep))
|
|
1112
|
+
return abs.slice(cwd.length + 1);
|
|
1113
|
+
return uri;
|
|
1114
|
+
}
|
|
1115
|
+
catch {
|
|
1116
|
+
return '';
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* PUGI-78 Phase 1: parse `textDocument/codeAction` response. Server
|
|
1121
|
+
* returns either `Command[]` or `CodeAction[]`. Both have `title`;
|
|
1122
|
+
* `CodeAction` additionally carries `kind` + `isPreferred` + `edit`.
|
|
1123
|
+
*/
|
|
1124
|
+
function normalizeCodeActions(raw) {
|
|
1125
|
+
if (!Array.isArray(raw))
|
|
1126
|
+
return [];
|
|
1127
|
+
const out = [];
|
|
1128
|
+
for (const item of raw) {
|
|
1129
|
+
if (!item || typeof item !== 'object')
|
|
1130
|
+
continue;
|
|
1131
|
+
const obj = item;
|
|
1132
|
+
if (typeof obj.title !== 'string' || obj.title.length === 0)
|
|
1133
|
+
continue;
|
|
1134
|
+
out.push({
|
|
1135
|
+
title: obj.title,
|
|
1136
|
+
...(typeof obj.kind === 'string' ? { kind: obj.kind } : {}),
|
|
1137
|
+
...(typeof obj.isPreferred === 'boolean' ? { isPreferred: obj.isPreferred } : {}),
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
return out;
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* PUGI-78 Phase 1: parse a `TextEdit[]` payload returned by the
|
|
1144
|
+
* formatter. Skips rows with missing range or non-string newText.
|
|
1145
|
+
*/
|
|
1146
|
+
function normalizeTextEdits(raw) {
|
|
1147
|
+
if (!Array.isArray(raw))
|
|
1148
|
+
return [];
|
|
1149
|
+
const out = [];
|
|
1150
|
+
for (const item of raw) {
|
|
1151
|
+
if (!item || typeof item !== 'object')
|
|
1152
|
+
continue;
|
|
1153
|
+
const obj = item;
|
|
1154
|
+
const range = parseRange(obj.range);
|
|
1155
|
+
if (!range)
|
|
1156
|
+
continue;
|
|
1157
|
+
const newText = typeof obj.newText === 'string' ? obj.newText : '';
|
|
1158
|
+
out.push({ range, newText });
|
|
1159
|
+
}
|
|
1160
|
+
return out;
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* PUGI-78 Phase 1: collapse `callHierarchy/{incoming,outgoing}Calls`.
|
|
1164
|
+
* The two response shapes share the same `from`/`to` field for the
|
|
1165
|
+
* counterpart item; `fromRanges`/`toRanges` flatten to `callRanges`.
|
|
1166
|
+
*/
|
|
1167
|
+
function normalizeCallHierarchyEdges(raw, itemKey, cwd) {
|
|
1168
|
+
if (!Array.isArray(raw))
|
|
1169
|
+
return [];
|
|
1170
|
+
const out = [];
|
|
1171
|
+
const rangeKey = itemKey === 'from' ? 'fromRanges' : 'toRanges';
|
|
1172
|
+
for (const row of raw) {
|
|
1173
|
+
if (!row || typeof row !== 'object')
|
|
1174
|
+
continue;
|
|
1175
|
+
const rowObj = row;
|
|
1176
|
+
const item = rowObj[itemKey];
|
|
1177
|
+
if (!item || typeof item !== 'object')
|
|
1178
|
+
continue;
|
|
1179
|
+
const itemObj = item;
|
|
1180
|
+
if (typeof itemObj.name !== 'string' || itemObj.name.length === 0)
|
|
1181
|
+
continue;
|
|
1182
|
+
const kind = typeof itemObj.kind === 'number' ? itemObj.kind : 0;
|
|
1183
|
+
if (!kind)
|
|
1184
|
+
continue;
|
|
1185
|
+
const uri = typeof itemObj.uri === 'string' ? itemObj.uri : '';
|
|
1186
|
+
if (!uri)
|
|
1187
|
+
continue;
|
|
1188
|
+
const file = uriToWorkspacePath(uri, cwd);
|
|
1189
|
+
const selRange = parseRange(itemObj.selectionRange) ?? parseRange(itemObj.range);
|
|
1190
|
+
if (!selRange)
|
|
1191
|
+
continue;
|
|
1192
|
+
const rangesRaw = rowObj[rangeKey];
|
|
1193
|
+
const callRanges = [];
|
|
1194
|
+
if (Array.isArray(rangesRaw)) {
|
|
1195
|
+
for (const r of rangesRaw) {
|
|
1196
|
+
const parsed = parseRange(r);
|
|
1197
|
+
if (parsed)
|
|
1198
|
+
callRanges.push(parsed);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
out.push({
|
|
1202
|
+
name: itemObj.name,
|
|
1203
|
+
kind,
|
|
1204
|
+
file,
|
|
1205
|
+
line: selRange.start.line,
|
|
1206
|
+
character: selRange.start.character,
|
|
1207
|
+
callRanges,
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
return out;
|
|
1211
|
+
}
|
|
708
1212
|
/**
|
|
709
1213
|
* Test-only surface so specs can hand-craft an `LspClient` over a mock
|
|
710
1214
|
* stdio pipe without paying for the real `startLspClient` spawn cost.
|
|
@@ -715,5 +1219,11 @@ export const __test__ = {
|
|
|
715
1219
|
normalizeHover,
|
|
716
1220
|
normalizeLocations,
|
|
717
1221
|
normalizeDiagnostic,
|
|
1222
|
+
normalizeSignatureHelp,
|
|
1223
|
+
normalizeWorkspaceEdit,
|
|
1224
|
+
normalizeCodeActions,
|
|
1225
|
+
normalizeTextEdits,
|
|
1226
|
+
normalizeCallHierarchyEdges,
|
|
1227
|
+
uriToWorkspacePath,
|
|
718
1228
|
};
|
|
719
1229
|
//# sourceMappingURL=client.js.map
|