@mrclrchtr/supi-code-intelligence 4.10.0 → 6.0.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/README.md +4 -3
- package/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
- package/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
- package/node_modules/@mrclrchtr/supi-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -6
- package/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/node_modules/@mrclrchtr/supi-lsp/README.md +111 -5
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -6
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/node_modules/@mrclrchtr/supi-lsp/package.json +7 -6
- package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +22 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +245 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +78 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +171 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +413 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +15 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +214 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +62 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +690 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +9 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +97 -16
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +206 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +448 -298
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +70 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +231 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-file-state.ts +25 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +529 -119
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +161 -33
- package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +12 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/config/config.ts +5 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/config/defaults.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +3 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-extends.ts +68 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-path.ts +8 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +176 -24
- package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +45 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +42 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +46 -10
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +79 -9
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +17 -7
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +350 -26
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +5 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +586 -96
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-refactor-provider.ts +130 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +45 -329
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/refactor-planning.ts +57 -120
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-edit-normalizer.ts +320 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-symbol-mapper.ts +207 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/readiness.ts +72 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-controller.ts +151 -26
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +42 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +92 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +165 -118
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +49 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +102 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +17 -7
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -6
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/LICENSE +21 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/README.md +258 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/dist/babel.cjs +257 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/dist/jiti.cjs +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-cli.mjs +34 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-hooks.mjs +124 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-native.mjs +121 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-register.d.mts +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-register.mjs +4 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-static.mjs +23 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.cjs +30 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.d.cts +8 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.d.mts +8 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.mjs +29 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/types.d.ts +420 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/package.json +146 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +6 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +0 -9
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +0 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +16 -12
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-controller.ts +65 -87
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +47 -95
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-timing.ts +147 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client-lifecycle.ts +39 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client.ts +561 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-message-size.ts +24 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-protocol.ts +452 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-sites.ts +4 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +12 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/exports.ts +4 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/imports.ts +4 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/node-at.ts +5 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +33 -12
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/blocking-test-bootstrap.mjs +34 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/bootstrap.mjs +8 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store-helpers.ts +23 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store.ts +400 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/request-control.ts +48 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime-parser-helpers.ts +24 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime-query-helpers.ts +43 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime.ts +421 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/service.ts +116 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/worker-main.ts +201 -0
- package/package.json +5 -5
- package/src/analysis/health/diagnostics.ts +221 -77
- package/src/analysis/health/file-scope.ts +184 -0
- package/src/analysis/health/recovery.ts +14 -3
- package/src/analysis/provider.ts +58 -0
- package/src/analysis/readiness.ts +34 -9
- package/src/analysis/refactor/apply.ts +45 -1
- package/src/analysis/refactor/mutation-authority.ts +143 -0
- package/src/analysis/relations/callees.ts +9 -1
- package/src/analysis/relations/types.ts +5 -2
- package/src/analysis/search/ast-scan-timing.ts +9 -2
- package/src/analysis/search/ast-scan.ts +5 -2
- package/src/analysis/search/deadline.ts +2 -2
- package/src/analysis/search/pattern-analysis.ts +54 -33
- package/src/analysis/search/pattern.ts +18 -3
- package/src/analysis/target/anchored.ts +20 -11
- package/src/analysis/target/file.ts +18 -11
- package/src/analysis/target/identity.ts +19 -3
- package/src/analysis/target/symbol.ts +36 -15
- package/src/api.ts +4 -0
- package/src/config.ts +35 -1
- package/src/diagnostics/evidence.ts +51 -0
- package/src/extension.ts +57 -14
- package/src/overview/overview-data.ts +9 -2
- package/src/overview/overview.ts +49 -30
- package/src/overview/types.ts +2 -0
- package/src/session/capability-adapter.ts +12 -3
- package/src/session/find-types.ts +2 -2
- package/src/session/find-workflow.ts +20 -9
- package/src/session/graph/collect.ts +18 -2
- package/src/session/graph-workflow.ts +19 -12
- package/src/session/health-refresh.ts +175 -0
- package/src/session/health-types.ts +43 -15
- package/src/session/health-workflow.ts +69 -54
- package/src/session/input/workflows.ts +2 -2
- package/src/session/inspect/collect.ts +20 -3
- package/src/session/inspect-workflow.ts +12 -6
- package/src/session/orientation/collect.ts +12 -3
- package/src/session/orientation/context-facts.ts +3 -1
- package/src/session/orientation/context-sections.ts +20 -7
- package/src/session/orientation/gather.ts +17 -11
- package/src/session/orientation-types.ts +4 -1
- package/src/session/orientation-workflow.ts +23 -10
- package/src/session/refactor-plans.ts +2 -0
- package/src/session/refactor-workflow.ts +61 -15
- package/src/session/session.ts +29 -2
- package/src/session/target-workflow.ts +54 -23
- package/src/session/workflow-control.ts +7 -3
- package/src/substrate/lsp/lifecycle.ts +26 -46
- package/src/substrate/lsp/maintenance.ts +284 -28
- package/src/substrate/lsp/recovery.ts +11 -5
- package/src/substrate/lsp/state.ts +2 -2
- package/src/substrate/workspace-provider-host.ts +25 -3
- package/src/tool/{find → code_find}/execute.ts +2 -16
- package/src/tool/code_find/guidance.ts +8 -0
- package/src/tool/{find → code_find}/render.ts +21 -4
- package/src/tool/{result/find.ts → code_find/result.ts} +78 -9
- package/src/tool/code_find/spec.ts +37 -0
- package/src/tool/code_find/tui.ts +41 -0
- package/src/tool/code_graph/execute.ts +24 -0
- package/src/tool/code_graph/guidance.ts +6 -0
- package/src/tool/{graph → code_graph}/markdown.ts +1 -1
- package/src/tool/{result/graph.ts → code_graph/result.ts} +111 -3
- package/src/tool/code_graph/spec.ts +39 -0
- package/src/tool/code_graph/tui.ts +117 -0
- package/src/tool/{health → code_health}/execute.ts +2 -13
- package/src/tool/code_health/guidance.ts +8 -0
- package/src/tool/{health → code_health}/markdown.ts +119 -27
- package/src/tool/code_health/refresh-status.ts +105 -0
- package/src/tool/{result/health.ts → code_health/result.ts} +80 -3
- package/src/tool/code_health/spec.ts +41 -0
- package/src/tool/{health → code_health}/tui.ts +95 -98
- package/src/tool/{inspect → code_inspect}/execute.ts +2 -16
- package/src/tool/code_inspect/guidance.ts +6 -0
- package/src/tool/{inspect → code_inspect}/markdown.ts +1 -1
- package/src/tool/{result/inspect.ts → code_inspect/result.ts} +143 -2
- package/src/tool/code_inspect/spec.ts +25 -0
- package/src/tool/code_inspect/tui.ts +44 -0
- package/src/tool/code_orientation/execute.ts +25 -0
- package/src/tool/code_orientation/guidance.ts +9 -0
- package/src/tool/{orientation → code_orientation}/markdown.ts +1 -1
- package/src/tool/code_orientation/result.ts +248 -0
- package/src/tool/code_orientation/spec.ts +25 -0
- package/src/tool/{orientation → code_orientation}/tui.ts +51 -35
- package/src/tool/code_refactor_apply/execute.ts +17 -0
- package/src/tool/code_refactor_apply/guidance.ts +5 -0
- package/src/tool/{refactor-apply/execute.ts → code_refactor_apply/result.ts} +8 -11
- package/src/tool/code_refactor_apply/spec.ts +26 -0
- package/src/tool/code_refactor_apply/tui.ts +73 -0
- package/src/tool/code_refactor_plan/execute.ts +27 -0
- package/src/tool/code_refactor_plan/guidance.ts +8 -0
- package/src/tool/{refactor-plan/execute.ts → code_refactor_plan/result.ts} +13 -22
- package/src/tool/code_refactor_plan/spec.ts +25 -0
- package/src/tool/code_refactor_plan/tui.ts +54 -0
- package/src/tool/code_resolve/execute.ts +19 -0
- package/src/tool/code_resolve/guidance.ts +8 -0
- package/src/tool/{resolve → code_resolve}/markdown.ts +1 -1
- package/src/tool/{result/resolve.ts → code_resolve/result.ts} +79 -29
- package/src/tool/code_resolve/spec.ts +25 -0
- package/src/tool/code_resolve/tui.ts +60 -0
- package/src/tool/guidance.ts +71 -52
- package/src/tool/infra/truncate.ts +40 -0
- package/src/tool/infra/workflow-control.ts +8 -0
- package/src/tool/{refactor-plan/markdown.ts → refactor-markdown.ts} +4 -7
- package/src/tool/register.ts +105 -88
- package/src/tool/result/display.ts +107 -0
- package/src/tool/result/errors.ts +20 -17
- package/src/tool/result/refactor.ts +37 -8
- package/src/tool/result/types.ts +20 -0
- package/src/tool/schemas.ts +26 -157
- package/src/tool/specs.ts +40 -126
- package/src/types/execution.ts +18 -5
- package/src/types/index.ts +8 -1
- package/src/ui/status-command.ts +5 -3
- package/src/ui/status-overlay.ts +4 -0
- package/src/ui/tui/common.ts +108 -10
- package/src/ui/tui/display.ts +48 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime.ts +0 -351
- package/src/tool/find/tui.ts +0 -39
- package/src/tool/graph/execute.ts +0 -62
- package/src/tool/graph/tui.ts +0 -104
- package/src/tool/inspect/tui.ts +0 -36
- package/src/tool/orientation/execute.ts +0 -62
- package/src/tool/refactor-apply/tui.ts +0 -55
- package/src/tool/refactor-plan/tui.ts +0 -41
- package/src/tool/resolve/execute.ts +0 -36
- package/src/tool/resolve/tui.ts +0 -42
- package/src/tool/result/orientation.ts +0 -132
- /package/src/tool/{find → code_find}/ast-kinds.ts +0 -0
- /package/src/tool/{find → code_find}/markdown.ts +0 -0
- /package/src/tool/{find → code_find}/modes.ts +0 -0
- /package/src/tool/{health → code_health}/semantic-state.ts +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// Single-file diagnostics collection for code_health.
|
|
2
|
+
//
|
|
3
|
+
// Owns the file-scope health request: the live pull, the tsconfig scope
|
|
4
|
+
// decision for the requested file, and the evidence factories shared by the
|
|
5
|
+
// file-scope outcomes. Kept separate from the workspace-tracked collection
|
|
6
|
+
// (diagnostics.ts) so the scope-decision surface stays in one place.
|
|
7
|
+
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import {
|
|
10
|
+
type CodeRequestControl,
|
|
11
|
+
isCodeRequestInterruption,
|
|
12
|
+
} from "@mrclrchtr/supi-code-runtime/api";
|
|
13
|
+
import {
|
|
14
|
+
type Diagnostic,
|
|
15
|
+
type DiagnosticEvidenceDocument,
|
|
16
|
+
type DiagnosticEvidenceSummary,
|
|
17
|
+
type FileScopeDecision,
|
|
18
|
+
getFileScopeDecision,
|
|
19
|
+
TENTATIVE_PUSH_UNAVAILABLE_REASON,
|
|
20
|
+
type WorkspaceLspRuntime,
|
|
21
|
+
} from "@mrclrchtr/supi-lsp/api";
|
|
22
|
+
import type {
|
|
23
|
+
HealthDiagnosticEntry,
|
|
24
|
+
HealthDiagnosticMessage,
|
|
25
|
+
HealthDiagnosticObservation,
|
|
26
|
+
HealthDiagnosticScope,
|
|
27
|
+
} from "../../session/health-types.ts";
|
|
28
|
+
|
|
29
|
+
/** Collect a live per-file diagnostic pull with its tsconfig scope decision. */
|
|
30
|
+
export async function collectScopedFileDiagnostics(options: {
|
|
31
|
+
service: WorkspaceLspRuntime;
|
|
32
|
+
scope: Extract<HealthDiagnosticScope, { kind: "file" }>;
|
|
33
|
+
cwd: string;
|
|
34
|
+
detailed?: boolean;
|
|
35
|
+
requestControl?: CodeRequestControl;
|
|
36
|
+
}): Promise<HealthDiagnosticObservation> {
|
|
37
|
+
const { service, scope, cwd, detailed, requestControl } = options;
|
|
38
|
+
try {
|
|
39
|
+
const result = await service.fileDiagnostics(scope.path, 4, requestControl);
|
|
40
|
+
if (result.kind === "unavailable") {
|
|
41
|
+
const evidence =
|
|
42
|
+
result.reason === TENTATIVE_PUSH_UNAVAILABLE_REASON
|
|
43
|
+
? singleFileEvidence(scope.path, "unconfirmed")
|
|
44
|
+
: unavailableFileEvidence(scope.path);
|
|
45
|
+
return unavailableDiagnostics(
|
|
46
|
+
scope,
|
|
47
|
+
result.reason,
|
|
48
|
+
evidence,
|
|
49
|
+
fileScopeStatus(scope.path, cwd),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const entries = toFileDiagnosticEntries(scope.path, result.data, detailed);
|
|
54
|
+
const status = fileScopeStatus(scope.path, cwd);
|
|
55
|
+
return result.kind === "partial"
|
|
56
|
+
? {
|
|
57
|
+
kind: "partial",
|
|
58
|
+
scope,
|
|
59
|
+
entries,
|
|
60
|
+
evidence: singleFileEvidence(scope.path, "unconfirmed"),
|
|
61
|
+
reason: result.reason,
|
|
62
|
+
...(status ? { scopeStatus: status } : {}),
|
|
63
|
+
}
|
|
64
|
+
: {
|
|
65
|
+
kind: "completed",
|
|
66
|
+
scope,
|
|
67
|
+
entries,
|
|
68
|
+
evidence: singleFileEvidence(scope.path, "confirmed"),
|
|
69
|
+
...(status ? { scopeStatus: status } : {}),
|
|
70
|
+
};
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (isCodeRequestInterruption(error, requestControl)) throw error;
|
|
73
|
+
return unavailableDiagnostics(
|
|
74
|
+
scope,
|
|
75
|
+
errorMessage(error, "File diagnostic request failed."),
|
|
76
|
+
unavailableFileEvidence(scope.path),
|
|
77
|
+
fileScopeStatus(scope.path, cwd),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Tsconfig scope decision for the requested file, when it can be computed.
|
|
84
|
+
*
|
|
85
|
+
* Uses the same decision model the diagnostic filter delegates to, so the
|
|
86
|
+
* rendered status line cannot drift from filter behavior. Telemetry-only:
|
|
87
|
+
* a failure to compute never fails the diagnostic request.
|
|
88
|
+
*/
|
|
89
|
+
export function fileScopeStatus(filePath: string, cwd: string): FileScopeDecision | null {
|
|
90
|
+
try {
|
|
91
|
+
return getFileScopeDecision(filePath, cwd);
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function toFileDiagnosticEntries(
|
|
98
|
+
file: string,
|
|
99
|
+
diagnostics: ReadonlyArray<Pick<Diagnostic, "severity" | "message" | "source" | "range">>,
|
|
100
|
+
detailed?: boolean,
|
|
101
|
+
): HealthDiagnosticEntry[] {
|
|
102
|
+
const errors = diagnostics.filter((diagnostic) => (diagnostic.severity ?? 1) === 1).length;
|
|
103
|
+
const warnings = diagnostics.filter((diagnostic) => (diagnostic.severity ?? 1) === 2).length;
|
|
104
|
+
if (!hasIssueCounts(errors, warnings)) return [];
|
|
105
|
+
const entry: HealthDiagnosticEntry = { file, errors, warnings };
|
|
106
|
+
if (detailed) {
|
|
107
|
+
return [{ ...entry, messages: extractMessages(diagnostics) }];
|
|
108
|
+
}
|
|
109
|
+
return [entry];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Message extraction ────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
const MAX_MESSAGES_PER_FILE = 3;
|
|
115
|
+
|
|
116
|
+
function extractMessages(
|
|
117
|
+
diagnostics: ReadonlyArray<Pick<Diagnostic, "severity" | "message" | "source" | "range">>,
|
|
118
|
+
): HealthDiagnosticMessage[] {
|
|
119
|
+
const errorsAndWarnings = diagnostics
|
|
120
|
+
.filter((d) => (d.severity ?? 1) <= 2)
|
|
121
|
+
.sort(
|
|
122
|
+
(a, b) => (a.severity ?? 1) - (b.severity ?? 1) || a.range.start.line - b.range.start.line,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return errorsAndWarnings.slice(0, MAX_MESSAGES_PER_FILE).map((d) => ({
|
|
126
|
+
line: d.range.start.line + 1,
|
|
127
|
+
severity: (d.severity ?? 1) === 1 ? ("error" as const) : ("warning" as const),
|
|
128
|
+
message: typeof d.message === "string" ? d.message : d.message.value,
|
|
129
|
+
...(d.source ? { source: d.source } : {}),
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function unavailableDiagnostics(
|
|
134
|
+
scope: HealthDiagnosticScope,
|
|
135
|
+
reason: string,
|
|
136
|
+
evidence: DiagnosticEvidenceSummary = emptyEvidence(),
|
|
137
|
+
scopeStatus?: FileScopeDecision | null,
|
|
138
|
+
): HealthDiagnosticObservation {
|
|
139
|
+
return {
|
|
140
|
+
kind: "unavailable",
|
|
141
|
+
scope,
|
|
142
|
+
entries: [],
|
|
143
|
+
evidence,
|
|
144
|
+
reason,
|
|
145
|
+
...(scopeStatus ? { scopeStatus } : {}),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function unavailableFileEvidence(file: string): DiagnosticEvidenceSummary {
|
|
150
|
+
return singleFileEvidence(file, existsSync(file) ? "failed" : "removed");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function singleFileEvidence(
|
|
154
|
+
file: string,
|
|
155
|
+
status: DiagnosticEvidenceDocument["status"],
|
|
156
|
+
): DiagnosticEvidenceSummary {
|
|
157
|
+
return {
|
|
158
|
+
requested: 1,
|
|
159
|
+
confirmed: status === "confirmed" ? 1 : 0,
|
|
160
|
+
unconfirmed: status === "unconfirmed" ? 1 : 0,
|
|
161
|
+
failed: status === "failed" ? 1 : 0,
|
|
162
|
+
removed: status === "removed" ? 1 : 0,
|
|
163
|
+
documents: [{ file, status }],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function emptyEvidence(): DiagnosticEvidenceSummary {
|
|
168
|
+
return {
|
|
169
|
+
requested: 0,
|
|
170
|
+
confirmed: 0,
|
|
171
|
+
unconfirmed: 0,
|
|
172
|
+
failed: 0,
|
|
173
|
+
removed: 0,
|
|
174
|
+
documents: [],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function hasIssueCounts(errors: number, warnings: number): boolean {
|
|
179
|
+
return errors > 0 || warnings > 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function errorMessage(error: unknown, fallback: string): string {
|
|
183
|
+
return error instanceof Error && error.message ? error.message : fallback;
|
|
184
|
+
}
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
// Recovery and state description helpers for code_health.
|
|
2
2
|
// Extracted from orchestrate.ts.
|
|
3
3
|
|
|
4
|
-
import type { CapabilityState } from "@mrclrchtr/supi-code-runtime/api";
|
|
5
|
-
import type {
|
|
4
|
+
import type { CapabilityState, CodeRequestControl } from "@mrclrchtr/supi-code-runtime/api";
|
|
5
|
+
import type {
|
|
6
|
+
DiagnosticEvidenceSummary,
|
|
7
|
+
RecoverDiagnosticsResult,
|
|
8
|
+
WorkspaceLspRuntime,
|
|
9
|
+
} from "@mrclrchtr/supi-lsp/api";
|
|
6
10
|
|
|
7
11
|
// ── Recovery ──────────────────────────────────────────────────────────
|
|
8
12
|
|
|
9
13
|
interface RecoverOptions {
|
|
10
14
|
service: WorkspaceLspRuntime;
|
|
11
15
|
progress?: () => void;
|
|
16
|
+
control?: CodeRequestControl;
|
|
17
|
+
/** Evidence from the maintenance refresh, so the recovery pass skips its own refresh. */
|
|
18
|
+
initialEvidence?: DiagnosticEvidenceSummary;
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
/** Run the runtime's best-effort recovery and preserve its established outcome. */
|
|
@@ -16,7 +23,11 @@ export async function recoverDiagnosticRuntime(
|
|
|
16
23
|
opts: RecoverOptions,
|
|
17
24
|
): Promise<RecoverDiagnosticsResult> {
|
|
18
25
|
opts.progress?.();
|
|
19
|
-
return opts.service.recoverDiagnostics({
|
|
26
|
+
return opts.service.recoverDiagnostics({
|
|
27
|
+
restartIfStillStale: true,
|
|
28
|
+
...(opts.initialEvidence !== undefined ? { initialEvidence: opts.initialEvidence } : {}),
|
|
29
|
+
control: opts.control,
|
|
30
|
+
});
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
// ── State description helpers ─────────────────────────────────────────
|
package/src/analysis/provider.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
|
+
type CodeRequestControl,
|
|
14
15
|
getDefaultWorkspaceRuntime,
|
|
15
16
|
type SemanticProvider,
|
|
16
17
|
type StructuralProvider,
|
|
@@ -85,6 +86,54 @@ export function getCodeProviderState(cwd: string): CodeProviderState {
|
|
|
85
86
|
/** Alias for getCodeProviderState — kept for backward compatibility. */
|
|
86
87
|
export const getCodeProvider = getCodeProviderState;
|
|
87
88
|
|
|
89
|
+
/** Bind one exact request-control value to every semantic provider operation. */
|
|
90
|
+
export function withSemanticRequestControl<T extends SemanticProvider>(
|
|
91
|
+
provider: T | null,
|
|
92
|
+
control: CodeRequestControl | undefined,
|
|
93
|
+
): T | null {
|
|
94
|
+
if (!provider || !control) return provider;
|
|
95
|
+
return {
|
|
96
|
+
...provider,
|
|
97
|
+
references: (file, position) => provider.references(file, position, control),
|
|
98
|
+
implementation: (file, position) => provider.implementation(file, position, control),
|
|
99
|
+
documentSymbols: (file) => provider.documentSymbols(file, control),
|
|
100
|
+
workspaceSymbols: (query) => provider.workspaceSymbols(query, control),
|
|
101
|
+
hover: provider.hover
|
|
102
|
+
? (file, position) => provider.hover?.(file, position, control)
|
|
103
|
+
: undefined,
|
|
104
|
+
definition: provider.definition
|
|
105
|
+
? (file, position) => provider.definition?.(file, position, control)
|
|
106
|
+
: undefined,
|
|
107
|
+
refactor: provider.refactor ? (request) => provider.refactor?.(request, control) : undefined,
|
|
108
|
+
rename: provider.rename
|
|
109
|
+
? (file, position, newName) => provider.rename?.(file, position, newName, control)
|
|
110
|
+
: undefined,
|
|
111
|
+
codeActions: provider.codeActions
|
|
112
|
+
? (file, position) => provider.codeActions?.(file, position, control)
|
|
113
|
+
: undefined,
|
|
114
|
+
} as T;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Bind one exact request-control value to every structural provider operation. */
|
|
118
|
+
export function withStructuralRequestControl<T extends StructuralProvider>(
|
|
119
|
+
provider: T | null,
|
|
120
|
+
control: CodeRequestControl | undefined,
|
|
121
|
+
): T | null {
|
|
122
|
+
if (!provider || !control) return provider;
|
|
123
|
+
return {
|
|
124
|
+
...provider,
|
|
125
|
+
calleesAt: (file, line, character, depthOrOptions) => {
|
|
126
|
+
const depth = typeof depthOrOptions === "string" ? depthOrOptions : depthOrOptions?.depth;
|
|
127
|
+
return provider.calleesAt(file, line, character, { depth, control });
|
|
128
|
+
},
|
|
129
|
+
exports: (file) => provider.exports(file, control),
|
|
130
|
+
outline: (file) => provider.outline(file, control),
|
|
131
|
+
imports: (file) => provider.imports(file, control),
|
|
132
|
+
nodeAt: (file, line, character) => provider.nodeAt(file, line, character, control),
|
|
133
|
+
callSites: (file) => provider.callSites(file, control),
|
|
134
|
+
} as T;
|
|
135
|
+
}
|
|
136
|
+
|
|
88
137
|
/**
|
|
89
138
|
* Create a single CodeProvider that delegates semantic methods to the
|
|
90
139
|
* semantic (LSP) provider and structural methods to the structural
|
|
@@ -138,6 +187,15 @@ function createCompositeProvider(
|
|
|
138
187
|
semantic?.definition?.(...args) ?? unavailableCodeQuery("Semantic definition unavailable")
|
|
139
188
|
);
|
|
140
189
|
},
|
|
190
|
+
async refactor(...args: Parameters<NonNullable<SemanticProvider["refactor"]>>) {
|
|
191
|
+
const result = await semantic?.refactor?.(...args);
|
|
192
|
+
return (
|
|
193
|
+
result ?? {
|
|
194
|
+
kind: "unavailable" as const,
|
|
195
|
+
reason: "Refactor not available",
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
},
|
|
141
199
|
async codeActions(...args: Parameters<NonNullable<SemanticProvider["codeActions"]>>) {
|
|
142
200
|
return semantic?.codeActions?.(...args) ?? [];
|
|
143
201
|
},
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type CodeRequestControl,
|
|
3
|
+
getDefaultWorkspaceRuntime,
|
|
4
|
+
throwIfCodeRequestInterrupted,
|
|
5
|
+
} from "@mrclrchtr/supi-code-runtime/api";
|
|
2
6
|
import {
|
|
3
7
|
getWorkspaceLspRuntime,
|
|
4
8
|
type SemanticReadinessResult,
|
|
@@ -8,13 +12,20 @@ import {
|
|
|
8
12
|
|
|
9
13
|
export const DEFAULT_SEMANTIC_STARTUP_TIMEOUT_MS = 15_000;
|
|
10
14
|
|
|
15
|
+
/** Neutral reason for a typed semantic readiness timeout. */
|
|
16
|
+
export const SEMANTIC_READINESS_TIMEOUT_REASON =
|
|
17
|
+
"Semantic provider did not become ready within the wait window; retry shortly.";
|
|
18
|
+
|
|
11
19
|
export type SemanticStartupScope = { kind: "workspace" } | { kind: "file"; file: string };
|
|
12
20
|
|
|
13
21
|
export async function ensureSemanticReadiness(
|
|
14
22
|
cwd: string,
|
|
15
23
|
scope: SemanticStartupScope,
|
|
16
24
|
timeoutMs: number = DEFAULT_SEMANTIC_STARTUP_TIMEOUT_MS,
|
|
25
|
+
control?: CodeRequestControl,
|
|
17
26
|
): Promise<SemanticReadinessResult> {
|
|
27
|
+
// A cancelled caller stops before any readiness work starts.
|
|
28
|
+
throwIfCodeRequestInterrupted(control);
|
|
18
29
|
const workspace = getDefaultWorkspaceRuntime().getWorkspace(cwd);
|
|
19
30
|
if (workspace.semantic.provider === null) {
|
|
20
31
|
return {
|
|
@@ -24,7 +35,7 @@ export async function ensureSemanticReadiness(
|
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
if (workspace.semantic.state.kind === "ready") {
|
|
27
|
-
return resolveReadySemanticState(cwd, scope, timeoutMs);
|
|
38
|
+
return resolveReadySemanticState(cwd, scope, timeoutMs, control);
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
if (workspace.semantic.state.kind !== "pending") {
|
|
@@ -34,13 +45,15 @@ export async function ensureSemanticReadiness(
|
|
|
34
45
|
};
|
|
35
46
|
}
|
|
36
47
|
|
|
37
|
-
|
|
48
|
+
// The request deadline bounds the internal readiness budget when it is
|
|
49
|
+
// earlier than the relative startup timeout.
|
|
50
|
+
const deadline = Math.min(Date.now() + timeoutMs, control?.deadline ?? Infinity);
|
|
38
51
|
|
|
39
52
|
const remainingAfterService = deadline - Date.now();
|
|
40
53
|
if (remainingAfterService <= 0) {
|
|
41
54
|
return { kind: "timeout" };
|
|
42
55
|
}
|
|
43
|
-
const lspState = await resolveSemanticServiceState(cwd, remainingAfterService);
|
|
56
|
+
const lspState = await resolveSemanticServiceState(cwd, remainingAfterService, control);
|
|
44
57
|
if (lspState.kind === "pending") {
|
|
45
58
|
return { kind: "timeout" };
|
|
46
59
|
}
|
|
@@ -58,8 +71,13 @@ export async function ensureSemanticReadiness(
|
|
|
58
71
|
if (remainingAfterLsp <= 0) {
|
|
59
72
|
return { kind: "timeout" };
|
|
60
73
|
}
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
if (scope.kind === "workspace") {
|
|
75
|
+
return control
|
|
76
|
+
? lspState.runtime.waitUntilReadyForWorkspace({ timeoutMs: remainingAfterLsp }, control)
|
|
77
|
+
: lspState.runtime.waitUntilReadyForWorkspace({ timeoutMs: remainingAfterLsp });
|
|
78
|
+
}
|
|
79
|
+
return control
|
|
80
|
+
? lspState.runtime.waitUntilReadyForFile(scope.file, { timeoutMs: remainingAfterLsp }, control)
|
|
63
81
|
: lspState.runtime.waitUntilReadyForFile(scope.file, { timeoutMs: remainingAfterLsp });
|
|
64
82
|
}
|
|
65
83
|
|
|
@@ -67,11 +85,17 @@ function resolveReadySemanticState(
|
|
|
67
85
|
cwd: string,
|
|
68
86
|
scope: SemanticStartupScope,
|
|
69
87
|
timeoutMs: number,
|
|
88
|
+
control?: CodeRequestControl,
|
|
70
89
|
): Promise<SemanticReadinessResult> | SemanticReadinessResult {
|
|
71
90
|
const lspState = getWorkspaceLspRuntime(cwd);
|
|
72
91
|
if (lspState.kind === "ready") {
|
|
73
|
-
|
|
74
|
-
|
|
92
|
+
if (scope.kind === "workspace") {
|
|
93
|
+
return control
|
|
94
|
+
? lspState.runtime.waitUntilReadyForWorkspace({ timeoutMs }, control)
|
|
95
|
+
: lspState.runtime.waitUntilReadyForWorkspace({ timeoutMs });
|
|
96
|
+
}
|
|
97
|
+
return control
|
|
98
|
+
? lspState.runtime.waitUntilReadyForFile(scope.file, { timeoutMs }, control)
|
|
75
99
|
: lspState.runtime.waitUntilReadyForFile(scope.file, { timeoutMs });
|
|
76
100
|
}
|
|
77
101
|
return {
|
|
@@ -86,8 +110,9 @@ function resolveReadySemanticState(
|
|
|
86
110
|
async function resolveSemanticServiceState(
|
|
87
111
|
cwd: string,
|
|
88
112
|
timeoutMs: number,
|
|
113
|
+
control?: CodeRequestControl,
|
|
89
114
|
): Promise<WorkspaceLspRuntimeState> {
|
|
90
115
|
const initialState = getWorkspaceLspRuntime(cwd);
|
|
91
116
|
if (initialState.kind !== "pending") return initialState;
|
|
92
|
-
return waitForWorkspaceLspRuntime(cwd, timeoutMs);
|
|
117
|
+
return waitForWorkspaceLspRuntime(cwd, timeoutMs, control);
|
|
93
118
|
}
|
|
@@ -16,6 +16,7 @@ import { readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:
|
|
|
16
16
|
import { basename, dirname, join } from "node:path";
|
|
17
17
|
import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
18
18
|
import type { FileEdit, WorkspaceEdit } from "@mrclrchtr/supi-code-runtime/api";
|
|
19
|
+
import { revalidateMutationAuthority } from "./mutation-authority.ts";
|
|
19
20
|
import { compareCodePositions, createLogicalLineIndex } from "./position.ts";
|
|
20
21
|
import { validateEditAgainstFiles } from "./safety.ts";
|
|
21
22
|
|
|
@@ -24,8 +25,12 @@ export type ApplyResult =
|
|
|
24
25
|
| { kind: "error"; reason: string };
|
|
25
26
|
|
|
26
27
|
export interface ApplyOptions {
|
|
28
|
+
/** Canonical roots authorized by the semantic provider that made the plan. */
|
|
29
|
+
authorizedMutationRoots: readonly string[];
|
|
27
30
|
/** Expected SHA-256 fingerprints per file, validated after queue acquisition. */
|
|
28
31
|
expectedFingerprints?: ReadonlyMap<string, string>;
|
|
32
|
+
/** Current LSP document version, or null when the document is not open. */
|
|
33
|
+
getOpenDocumentVersion?: (file: string) => number | null;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
/**
|
|
@@ -49,16 +54,24 @@ export interface ApplyOptions {
|
|
|
49
54
|
*/
|
|
50
55
|
export async function applyWorkspaceEdit(
|
|
51
56
|
edit: WorkspaceEdit,
|
|
52
|
-
options: ApplyOptions
|
|
57
|
+
options: ApplyOptions,
|
|
53
58
|
): Promise<ApplyResult> {
|
|
54
59
|
const grouped = groupEditsByFile(edit.edits);
|
|
55
60
|
const files = [...grouped.keys()].sort();
|
|
56
61
|
return withAllMutationQueues(files, async () => {
|
|
62
|
+
const authority = revalidateMutationAuthority(files, options.authorizedMutationRoots);
|
|
63
|
+
if (authority.kind === "unavailable") {
|
|
64
|
+
return { kind: "error", reason: authority.reason };
|
|
65
|
+
}
|
|
66
|
+
|
|
57
67
|
if (options.expectedFingerprints) {
|
|
58
68
|
const freshness = validateFingerprints(files, options.expectedFingerprints);
|
|
59
69
|
if (!freshness.ok) return { kind: "error", reason: freshness.reason };
|
|
60
70
|
}
|
|
61
71
|
|
|
72
|
+
const documentState = validateDocumentPreconditions(edit, options.getOpenDocumentVersion);
|
|
73
|
+
if (!documentState.ok) return { kind: "error", reason: documentState.reason };
|
|
74
|
+
|
|
62
75
|
const validation = validateEditAgainstFiles(edit);
|
|
63
76
|
if (!validation.safe) {
|
|
64
77
|
return { kind: "error", reason: validation.reason };
|
|
@@ -76,6 +89,37 @@ export async function applyWorkspaceEdit(
|
|
|
76
89
|
});
|
|
77
90
|
}
|
|
78
91
|
|
|
92
|
+
function validateDocumentPreconditions(
|
|
93
|
+
edit: WorkspaceEdit,
|
|
94
|
+
getOpenDocumentVersion: ApplyOptions["getOpenDocumentVersion"],
|
|
95
|
+
): { ok: true } | { ok: false; reason: string } {
|
|
96
|
+
const preconditions = edit.documentPreconditions ?? [];
|
|
97
|
+
if (preconditions.length === 0) return { ok: true };
|
|
98
|
+
if (!getOpenDocumentVersion) {
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
reason: "Stored document preconditions cannot be revalidated.",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const precondition of preconditions) {
|
|
106
|
+
const currentVersion = getOpenDocumentVersion(precondition.file);
|
|
107
|
+
if (precondition.kind === "open-document-version" && currentVersion !== precondition.version) {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
reason: `File ${precondition.file} open document version has changed since the plan was generated.`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (precondition.kind === "disk-content" && currentVersion !== null) {
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
reason: `File ${precondition.file} is now open and no longer uses disk content as its master.`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return { ok: true };
|
|
121
|
+
}
|
|
122
|
+
|
|
79
123
|
function validateFingerprints(
|
|
80
124
|
files: string[],
|
|
81
125
|
expected: ReadonlyMap<string, string>,
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { lstatSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export type MutationAuthorityResult =
|
|
5
|
+
| { kind: "authorized"; canonicalRoots: string[] }
|
|
6
|
+
| { kind: "unavailable"; reason: string };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Establish canonical provider roots and verify that all mutation files are
|
|
10
|
+
* regular files inside at least one root.
|
|
11
|
+
*/
|
|
12
|
+
export function establishMutationAuthority(
|
|
13
|
+
files: readonly string[],
|
|
14
|
+
providerRoots: readonly string[],
|
|
15
|
+
): MutationAuthorityResult {
|
|
16
|
+
const roots = canonicalizeProviderRoots(providerRoots);
|
|
17
|
+
if (roots.kind === "unavailable") return roots;
|
|
18
|
+
|
|
19
|
+
const fileValidation = validateFiles(files, roots.canonicalRoots);
|
|
20
|
+
return fileValidation.kind === "unavailable" ? fileValidation : roots;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Revalidate stored canonical roots and mutation files immediately before mutation. */
|
|
24
|
+
export function revalidateMutationAuthority(
|
|
25
|
+
files: readonly string[],
|
|
26
|
+
canonicalRoots: readonly string[],
|
|
27
|
+
): MutationAuthorityResult {
|
|
28
|
+
if (canonicalRoots.length === 0) {
|
|
29
|
+
return { kind: "unavailable", reason: "Mutation plan has no authorized provider root." };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const root of canonicalRoots) {
|
|
33
|
+
if (!path.isAbsolute(root) || path.normalize(root) !== root) {
|
|
34
|
+
return { kind: "unavailable", reason: `Stored mutation root "${root}" is not canonical.` };
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
if (!lstatSync(root).isDirectory() || realpathSync(root) !== root) {
|
|
38
|
+
return { kind: "unavailable", reason: `Stored mutation root "${root}" has changed.` };
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
return { kind: "unavailable", reason: `Stored mutation root "${root}" is unavailable.` };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return validateFiles(files, canonicalRoots);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function canonicalizeProviderRoots(providerRoots: readonly string[]): MutationAuthorityResult {
|
|
49
|
+
if (providerRoots.length === 0) {
|
|
50
|
+
return { kind: "unavailable", reason: "Semantic route did not authorize a mutation root." };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const canonicalRoots = new Set<string>();
|
|
54
|
+
for (const root of providerRoots) {
|
|
55
|
+
if (!path.isAbsolute(root)) {
|
|
56
|
+
return { kind: "unavailable", reason: `Mutation root "${root}" is not absolute.` };
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const canonicalRoot = realpathSync(root);
|
|
60
|
+
if (!statSync(canonicalRoot).isDirectory()) {
|
|
61
|
+
return {
|
|
62
|
+
kind: "unavailable",
|
|
63
|
+
reason: `Mutation root "${root}" is not a directory.`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
canonicalRoots.add(canonicalRoot);
|
|
67
|
+
} catch {
|
|
68
|
+
return {
|
|
69
|
+
kind: "unavailable",
|
|
70
|
+
reason: `Mutation root "${root}" cannot be resolved to a canonical directory.`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { kind: "authorized", canonicalRoots: [...canonicalRoots].sort() };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function validateFiles(
|
|
79
|
+
files: readonly string[],
|
|
80
|
+
canonicalRoots: readonly string[],
|
|
81
|
+
): MutationAuthorityResult {
|
|
82
|
+
for (const file of new Set(files)) {
|
|
83
|
+
const validation = validateFile(file, canonicalRoots);
|
|
84
|
+
if (validation) return { kind: "unavailable", reason: validation };
|
|
85
|
+
}
|
|
86
|
+
return { kind: "authorized", canonicalRoots: [...canonicalRoots] };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function validateFile(file: string, canonicalRoots: readonly string[]): string | null {
|
|
90
|
+
if (!path.isAbsolute(file) || path.normalize(file) !== file) {
|
|
91
|
+
return `Mutation file "${file}" is not a normalized absolute path.`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let canonicalParent: string;
|
|
95
|
+
try {
|
|
96
|
+
canonicalParent = realpathSync(path.dirname(file));
|
|
97
|
+
} catch {
|
|
98
|
+
return `Mutation file "${file}" does not have a resolvable canonical parent.`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
if (!lstatSync(file).isFile()) {
|
|
103
|
+
return `Mutation file "${file}" is not a regular file.`;
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
return `Mutation file "${file}" is not a readable regular file.`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let canonicalFile: string;
|
|
110
|
+
try {
|
|
111
|
+
canonicalFile = realpathSync(file);
|
|
112
|
+
} catch {
|
|
113
|
+
return `Mutation file "${file}" cannot be resolved to a canonical file.`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const expectedCanonicalFile = path.join(canonicalParent, path.basename(file));
|
|
117
|
+
if (path.resolve(canonicalFile) !== path.resolve(expectedCanonicalFile)) {
|
|
118
|
+
return `Mutation file "${file}" resolves through an unsupported file link.`;
|
|
119
|
+
}
|
|
120
|
+
if (!canonicalRoots.some((root) => isWithinOrEqual(root, canonicalFile))) {
|
|
121
|
+
return `Mutation file "${file}" is outside the authorized provider roots.`;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isWithinOrEqual(root: string, candidate: string): boolean {
|
|
127
|
+
return isMutationPathWithinRoot(root, candidate, path);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Check canonical path containment with the selected platform path rules. */
|
|
131
|
+
export function isMutationPathWithinRoot(
|
|
132
|
+
root: string,
|
|
133
|
+
candidate: string,
|
|
134
|
+
pathApi: Pick<typeof path, "isAbsolute" | "relative" | "sep">,
|
|
135
|
+
): boolean {
|
|
136
|
+
const difference = pathApi.relative(root, candidate);
|
|
137
|
+
return (
|
|
138
|
+
difference === "" ||
|
|
139
|
+
(difference !== ".." &&
|
|
140
|
+
!difference.startsWith(`..${pathApi.sep}`) &&
|
|
141
|
+
!pathApi.isAbsolute(difference))
|
|
142
|
+
);
|
|
143
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Structural callee lookup — finds direct calls in the enclosing scope at a target position.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import type { CodeRequestControl } from "@mrclrchtr/supi-code-runtime/api";
|
|
5
6
|
import type { CalleeEntry, CalleeScope, RelationsServiceDeps } from "./types.ts";
|
|
6
7
|
|
|
7
8
|
export interface CalleesResult {
|
|
@@ -26,12 +27,19 @@ export async function collectCallees(
|
|
|
26
27
|
deps: RelationsServiceDeps,
|
|
27
28
|
maxResults?: number,
|
|
28
29
|
depth: "direct" | "deep" = "direct",
|
|
30
|
+
control?: CodeRequestControl,
|
|
29
31
|
): Promise<CalleesResult> {
|
|
30
32
|
if (!deps.provider?.calleesAt) {
|
|
31
33
|
return unavailableCallees(targetName);
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
const
|
|
36
|
+
const depthOrOptions = control ? { depth, control } : depth;
|
|
37
|
+
const result = await deps.provider.calleesAt(
|
|
38
|
+
targetFile,
|
|
39
|
+
targetLine,
|
|
40
|
+
targetCharacter,
|
|
41
|
+
depthOrOptions,
|
|
42
|
+
);
|
|
35
43
|
if (result.kind !== "success" || !result.data) {
|
|
36
44
|
return unavailableCallees(targetName);
|
|
37
45
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SemanticProvider } from "@mrclrchtr/supi-code-runtime/api";
|
|
1
|
+
import type { CodeRequestControl, SemanticProvider } from "@mrclrchtr/supi-code-runtime/api";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Shared types for the relations analysis modules.
|
|
@@ -58,7 +58,10 @@ export interface RelationsServiceDeps {
|
|
|
58
58
|
file: string,
|
|
59
59
|
line: number,
|
|
60
60
|
character: number,
|
|
61
|
-
|
|
61
|
+
depthOrOptions?:
|
|
62
|
+
| "direct"
|
|
63
|
+
| "deep"
|
|
64
|
+
| { depth?: "direct" | "deep"; control?: CodeRequestControl },
|
|
62
65
|
) => Promise<{
|
|
63
66
|
kind: string;
|
|
64
67
|
data?: {
|