@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,245 @@
|
|
|
1
|
+
import type { PublishDiagnosticsParams } from "../config/types.ts";
|
|
2
|
+
import { uriToFile } from "../utils.ts";
|
|
3
|
+
import {
|
|
4
|
+
type DiagnosticCacheEntry,
|
|
5
|
+
isTentativePushEntry,
|
|
6
|
+
isValidPublishDiagnosticsParams,
|
|
7
|
+
} from "./client-diagnostic-evidence.ts";
|
|
8
|
+
import type { ClientDiagnosticSnapshot, OpenDocumentState } from "./client-document-state.ts";
|
|
9
|
+
import { hasCurrentDiagnosticEvidence } from "./client-document-state.ts";
|
|
10
|
+
import { getDiagnosticFileState } from "./client-file-state.ts";
|
|
11
|
+
|
|
12
|
+
interface ApplyPushOptions {
|
|
13
|
+
store: Map<string, DiagnosticCacheEntry>;
|
|
14
|
+
openDocuments: ReadonlyMap<string, OpenDocumentState>;
|
|
15
|
+
params: PublishDiagnosticsParams;
|
|
16
|
+
evidenceRevision: number;
|
|
17
|
+
/** Client-side sync moment for the URI, or undefined when never synchronized. */
|
|
18
|
+
unversionedSyncMoment: number | undefined;
|
|
19
|
+
/** Whether a lifecycle close marked this URI fail-closed for versioned pushes. */
|
|
20
|
+
closedVersionedBarrier: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Apply one valid push publication.
|
|
25
|
+
*
|
|
26
|
+
* Returns whether the publication was accepted and whether it promoted a
|
|
27
|
+
* tentative entry to confirmed. Fail-closed policy (ADR 0020): unversioned
|
|
28
|
+
* pushes are rejected for closed and untracked URIs and for arrivals before
|
|
29
|
+
* the URI's sync moment; versioned pushes for a URI closed by a lifecycle
|
|
30
|
+
* operation are rejected because their version cannot be verified. An
|
|
31
|
+
* unversioned push that arrives after the sync moment of an open document
|
|
32
|
+
* is accepted and re-stamped with that document's current synchronization
|
|
33
|
+
* state. The first valid publication for a synchronization is tentative; a
|
|
34
|
+
* later valid publication for the same synchronization ID and evidence
|
|
35
|
+
* revision promotes the cache to confirmed (ADR 0021).
|
|
36
|
+
*/
|
|
37
|
+
export function applyPushDiagnostics(options: ApplyPushOptions): {
|
|
38
|
+
accepted: boolean;
|
|
39
|
+
promoted: boolean;
|
|
40
|
+
} {
|
|
41
|
+
if (!isValidPublishDiagnosticsParams(options.params)) return { accepted: false, promoted: false };
|
|
42
|
+
const openDocument = options.openDocuments.get(options.params.uri);
|
|
43
|
+
if (options.params.version !== undefined) {
|
|
44
|
+
if (!Number.isInteger(options.params.version)) return { accepted: false, promoted: false };
|
|
45
|
+
if (openDocument && options.params.version !== openDocument.version) {
|
|
46
|
+
return { accepted: false, promoted: false };
|
|
47
|
+
}
|
|
48
|
+
if (!openDocument && options.closedVersionedBarrier)
|
|
49
|
+
return { accepted: false, promoted: false };
|
|
50
|
+
}
|
|
51
|
+
if (!Array.isArray(options.params.diagnostics)) return { accepted: false, promoted: false };
|
|
52
|
+
if (!acceptUnversionedPush(options, openDocument)) return { accepted: false, promoted: false };
|
|
53
|
+
const entry = buildPushCacheEntry(options, openDocument);
|
|
54
|
+
const previous = options.store.get(options.params.uri);
|
|
55
|
+
const promoted =
|
|
56
|
+
entry.source === "push" &&
|
|
57
|
+
entry.publications !== undefined &&
|
|
58
|
+
entry.publications >= 2 &&
|
|
59
|
+
isTentativePushEntry(previous);
|
|
60
|
+
options.store.set(options.params.uri, entry);
|
|
61
|
+
return { accepted: true, promoted };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Gate one unversioned push publication against the sync-moment policy. */
|
|
65
|
+
function acceptUnversionedPush(
|
|
66
|
+
options: ApplyPushOptions,
|
|
67
|
+
openDocument: OpenDocumentState | undefined,
|
|
68
|
+
): boolean {
|
|
69
|
+
if (options.params.version !== undefined) return true;
|
|
70
|
+
if (!openDocument) return false;
|
|
71
|
+
return !(
|
|
72
|
+
options.unversionedSyncMoment !== undefined && Date.now() < options.unversionedSyncMoment
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build the stored entry for one accepted push publication.
|
|
78
|
+
*
|
|
79
|
+
* Time-gated unversioned acceptance re-stamps the entry with the open
|
|
80
|
+
* document's current synchronization state, so the push proves that
|
|
81
|
+
* document's synchronization. Versioned pushes keep the current-revision
|
|
82
|
+
* check: a version match alone does not prove a current generation.
|
|
83
|
+
*/
|
|
84
|
+
function buildPushCacheEntry(
|
|
85
|
+
options: ApplyPushOptions,
|
|
86
|
+
openDocument: OpenDocumentState | undefined,
|
|
87
|
+
): DiagnosticCacheEntry {
|
|
88
|
+
const currentRevision = openDocument?.evidenceRevision === options.evidenceRevision;
|
|
89
|
+
const unversioned = options.params.version === undefined && openDocument !== undefined;
|
|
90
|
+
const synchronizationId =
|
|
91
|
+
unversioned || currentRevision ? openDocument?.synchronizationId : undefined;
|
|
92
|
+
const evidenceRevision = openDocument?.evidenceRevision;
|
|
93
|
+
const previous = options.store.get(options.params.uri);
|
|
94
|
+
// A later valid publication for the same synchronization ID and evidence
|
|
95
|
+
// revision continues the entry's publication count: a confirmed pull
|
|
96
|
+
// entry keeps the synchronization confirmed, and a tentative push entry
|
|
97
|
+
// is promoted. Any other publication starts a fresh count.
|
|
98
|
+
const sameSynchronization =
|
|
99
|
+
synchronizationId !== undefined &&
|
|
100
|
+
previous?.synchronizationId === synchronizationId &&
|
|
101
|
+
previous.evidenceRevision === evidenceRevision;
|
|
102
|
+
const publications = sameSynchronization ? Math.min((previous.publications ?? 1) + 1, 2) : 1;
|
|
103
|
+
return {
|
|
104
|
+
diagnostics: options.params.diagnostics,
|
|
105
|
+
receivedAt: Date.now(),
|
|
106
|
+
source: "push",
|
|
107
|
+
synchronizationId,
|
|
108
|
+
evidenceRevision,
|
|
109
|
+
version: options.params.version,
|
|
110
|
+
publications,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Build one client snapshot without dropping empty-cache freshness. */
|
|
115
|
+
export function buildClientDiagnosticSnapshot(options: {
|
|
116
|
+
store: ReadonlyMap<string, DiagnosticCacheEntry>;
|
|
117
|
+
openDocuments: ReadonlyMap<string, OpenDocumentState>;
|
|
118
|
+
failedDocuments: ReadonlySet<string>;
|
|
119
|
+
evidenceRevision: number;
|
|
120
|
+
}): ClientDiagnosticSnapshot {
|
|
121
|
+
const documents = collectDocumentFreshness(options);
|
|
122
|
+
const cache = collectCachedDiagnostics(options);
|
|
123
|
+
return {
|
|
124
|
+
entries: cache.entries,
|
|
125
|
+
documents,
|
|
126
|
+
current: cache.current && documents.every((document) => document.current),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function collectDocumentFreshness(options: {
|
|
131
|
+
openDocuments: ReadonlyMap<string, OpenDocumentState>;
|
|
132
|
+
failedDocuments: ReadonlySet<string>;
|
|
133
|
+
store: ReadonlyMap<string, DiagnosticCacheEntry>;
|
|
134
|
+
evidenceRevision: number;
|
|
135
|
+
}): ClientDiagnosticSnapshot["documents"] {
|
|
136
|
+
const open = collectOpenDocumentFreshness(options);
|
|
137
|
+
return [
|
|
138
|
+
...open.documents,
|
|
139
|
+
...collectFailedDocumentFreshness(options.failedDocuments, open.uris),
|
|
140
|
+
...collectUntrackedCacheFreshness(options.store, open.uris, options.failedDocuments),
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function collectOpenDocumentFreshness(options: {
|
|
145
|
+
openDocuments: ReadonlyMap<string, OpenDocumentState>;
|
|
146
|
+
failedDocuments: ReadonlySet<string>;
|
|
147
|
+
store: ReadonlyMap<string, DiagnosticCacheEntry>;
|
|
148
|
+
evidenceRevision: number;
|
|
149
|
+
}): { documents: ClientDiagnosticSnapshot["documents"]; uris: ReadonlySet<string> } {
|
|
150
|
+
const documents: ClientDiagnosticSnapshot["documents"] = [];
|
|
151
|
+
const uris = new Set<string>();
|
|
152
|
+
for (const [uri, document] of options.openDocuments) {
|
|
153
|
+
uris.add(uri);
|
|
154
|
+
const entry = options.store.get(uri);
|
|
155
|
+
const fileState = getDiagnosticFileState(uriToFile(uri));
|
|
156
|
+
const current = hasCurrentDiagnosticEvidence(document, entry, options.evidenceRevision);
|
|
157
|
+
const failed = options.failedDocuments.has(uri);
|
|
158
|
+
// A current tentative push maps to unconfirmed, not to a new public
|
|
159
|
+
// status: it matches the synchronization but cannot confirm it yet, so
|
|
160
|
+
// it must not claim current snapshot or document state (issue #351).
|
|
161
|
+
const tentative = current && isTentativePushEntry(entry);
|
|
162
|
+
documents.push({
|
|
163
|
+
uri,
|
|
164
|
+
current: fileState === "present" && current && !failed && !tentative,
|
|
165
|
+
status: statusForDocument(fileState, current, failed, tentative),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return { documents, uris };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function collectFailedDocumentFreshness(
|
|
172
|
+
failedDocuments: ReadonlySet<string>,
|
|
173
|
+
openUris: ReadonlySet<string>,
|
|
174
|
+
): ClientDiagnosticSnapshot["documents"] {
|
|
175
|
+
return Array.from(failedDocuments)
|
|
176
|
+
.filter((uri) => !openUris.has(uri))
|
|
177
|
+
.map((uri) => ({
|
|
178
|
+
uri,
|
|
179
|
+
current: false,
|
|
180
|
+
status:
|
|
181
|
+
getDiagnosticFileState(uriToFile(uri)) === "removed"
|
|
182
|
+
? ("removed" as const)
|
|
183
|
+
: ("failed" as const),
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function collectUntrackedCacheFreshness(
|
|
188
|
+
store: ReadonlyMap<string, DiagnosticCacheEntry>,
|
|
189
|
+
openUris: ReadonlySet<string>,
|
|
190
|
+
failedDocuments: ReadonlySet<string>,
|
|
191
|
+
): ClientDiagnosticSnapshot["documents"] {
|
|
192
|
+
return Array.from(store.keys())
|
|
193
|
+
.filter((uri) => !openUris.has(uri) && !failedDocuments.has(uri))
|
|
194
|
+
.map((uri) => ({
|
|
195
|
+
uri,
|
|
196
|
+
current: false,
|
|
197
|
+
status:
|
|
198
|
+
getDiagnosticFileState(uriToFile(uri)) === "removed"
|
|
199
|
+
? ("removed" as const)
|
|
200
|
+
: getDiagnosticFileState(uriToFile(uri)) === "unreadable"
|
|
201
|
+
? ("failed" as const)
|
|
202
|
+
: ("unconfirmed" as const),
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function statusForDocument(
|
|
207
|
+
fileState: ReturnType<typeof getDiagnosticFileState>,
|
|
208
|
+
current: boolean,
|
|
209
|
+
failed: boolean,
|
|
210
|
+
tentative: boolean,
|
|
211
|
+
): "confirmed" | "unconfirmed" | "failed" | "removed" {
|
|
212
|
+
if (fileState === "removed") return "removed";
|
|
213
|
+
if (fileState === "unreadable" || failed) return "failed";
|
|
214
|
+
if (tentative || !current) return "unconfirmed";
|
|
215
|
+
return "confirmed";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function collectCachedDiagnostics(options: {
|
|
219
|
+
store: ReadonlyMap<string, DiagnosticCacheEntry>;
|
|
220
|
+
openDocuments: ReadonlyMap<string, OpenDocumentState>;
|
|
221
|
+
evidenceRevision: number;
|
|
222
|
+
}): {
|
|
223
|
+
entries: ClientDiagnosticSnapshot["entries"];
|
|
224
|
+
current: boolean;
|
|
225
|
+
} {
|
|
226
|
+
const entries: ClientDiagnosticSnapshot["entries"] = [];
|
|
227
|
+
let current = true;
|
|
228
|
+
for (const [uri, entry] of options.store) {
|
|
229
|
+
if (getDiagnosticFileState(uriToFile(uri)) === "removed") continue;
|
|
230
|
+
const entryCurrent = hasCurrentDiagnosticEvidence(
|
|
231
|
+
options.openDocuments.get(uri),
|
|
232
|
+
entry,
|
|
233
|
+
options.evidenceRevision,
|
|
234
|
+
);
|
|
235
|
+
// A current tentative error is useful partial evidence. Keep its entry
|
|
236
|
+
// non-current so it cannot establish a clean or settled result (ADR 0021).
|
|
237
|
+
const tentative = entryCurrent && isTentativePushEntry(entry);
|
|
238
|
+
const confirmed = entryCurrent && !tentative;
|
|
239
|
+
current &&= confirmed;
|
|
240
|
+
if (entry.diagnostics.length > 0) {
|
|
241
|
+
entries.push({ uri, diagnostics: entry.diagnostics, current: confirmed });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return { entries, current };
|
|
245
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Client-side diagnostic capability state — static (initialize result) and
|
|
2
|
+
// dynamic (client/registerCapability) tracking with fail-closed validation.
|
|
3
|
+
//
|
|
4
|
+
// Static state lives on the client's initialize-result capabilities; dynamic
|
|
5
|
+
// state lives in the per-instance `ClientDynamicRegistrations` below. Pull
|
|
6
|
+
// support is enabled only while the static provider shape is valid or the
|
|
7
|
+
// dynamic set for `textDocument/diagnostic` is non-empty.
|
|
8
|
+
|
|
9
|
+
/** LSP method for document diagnostic pulls. */
|
|
10
|
+
export const DOCUMENT_DIAGNOSTIC_METHOD = "textDocument/diagnostic";
|
|
11
|
+
|
|
12
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Validate one untrusted `DiagnosticOptions` / `DiagnosticRegistrationOptions`
|
|
18
|
+
* value from a static initialize result or a dynamic registration.
|
|
19
|
+
*
|
|
20
|
+
* Required per the LSP specification: boolean `interFileDependencies` and
|
|
21
|
+
* `workspaceDiagnostics`. Optional: string `identifier`, and `documentSelector`
|
|
22
|
+
* (absent, null, or an array of string selectors / document-filter records).
|
|
23
|
+
* Unknown fields such as kotlin-lsp's `workDoneProgress` are tolerated.
|
|
24
|
+
*/
|
|
25
|
+
export function isValidDiagnosticOptions(value: unknown): boolean {
|
|
26
|
+
if (!isRecord(value)) return false;
|
|
27
|
+
if (typeof value.interFileDependencies !== "boolean") return false;
|
|
28
|
+
if (typeof value.workspaceDiagnostics !== "boolean") return false;
|
|
29
|
+
if (value.identifier !== undefined && typeof value.identifier !== "string") return false;
|
|
30
|
+
if (value.documentSelector !== undefined && value.documentSelector !== null) {
|
|
31
|
+
if (!Array.isArray(value.documentSelector)) return false;
|
|
32
|
+
if (!value.documentSelector.every((entry) => typeof entry === "string" || isRecord(entry))) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Dynamic capability registrations for one client instance.
|
|
41
|
+
*
|
|
42
|
+
* A replacement client starts with an empty instance, so late registrations
|
|
43
|
+
* on a superseded instance never affect the replacement. Unregistration
|
|
44
|
+
* removes one id; pull stays enabled until the last id is removed.
|
|
45
|
+
*/
|
|
46
|
+
export class ClientDynamicRegistrations {
|
|
47
|
+
private readonly idsByMethod = new Map<string, Set<string>>();
|
|
48
|
+
|
|
49
|
+
/** Record one registration id for a method. Duplicate ids are harmless. */
|
|
50
|
+
register(method: string, id: string): void {
|
|
51
|
+
let ids = this.idsByMethod.get(method);
|
|
52
|
+
if (ids === undefined) {
|
|
53
|
+
ids = new Set();
|
|
54
|
+
this.idsByMethod.set(method, ids);
|
|
55
|
+
}
|
|
56
|
+
ids.add(id);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Remove one registration id; unknown ids are harmless no-ops. */
|
|
60
|
+
unregister(method: string, id: string): void {
|
|
61
|
+
this.idsByMethod.get(method)?.delete(id);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Whether any registration id remains active for a method. */
|
|
65
|
+
has(method: string): boolean {
|
|
66
|
+
return (this.idsByMethod.get(method)?.size ?? 0) > 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Snapshot of the active registration ids for a method. */
|
|
70
|
+
ids(method: string): readonly string[] {
|
|
71
|
+
return [...(this.idsByMethod.get(method) ?? [])];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Drop all registrations — capability loss on shutdown, crash, or disposal. */
|
|
75
|
+
clear(): void {
|
|
76
|
+
this.idsByMethod.clear();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CodeQueryResult,
|
|
3
|
+
type CodeRequestControl,
|
|
4
|
+
completedCodeQuery,
|
|
5
|
+
isCodeRequestInterruption,
|
|
6
|
+
partialCodeQuery,
|
|
7
|
+
throwIfCodeRequestInterrupted,
|
|
8
|
+
unavailableCodeQuery,
|
|
9
|
+
} from "@mrclrchtr/supi-code-runtime/api";
|
|
10
|
+
import type { Diagnostic } from "../config/types.ts";
|
|
11
|
+
import { TENTATIVE_PUSH_UNAVAILABLE_REASON } from "../diagnostics/evidence.ts";
|
|
12
|
+
import {
|
|
13
|
+
type DiagnosticSynchronization,
|
|
14
|
+
incompleteDiagnosticResult,
|
|
15
|
+
raceDiagnosticPull,
|
|
16
|
+
} from "./client-diagnostic-evidence.ts";
|
|
17
|
+
import type { DiagnosticObserver, DiagnosticPushWaitOutcome } from "./client-diagnostic-timing.ts";
|
|
18
|
+
import type { DiagnosticWaitRegistry } from "./client-diagnostic-waiters.ts";
|
|
19
|
+
|
|
20
|
+
interface FileDiagnosticCollectionOptions {
|
|
21
|
+
readonly supportsPull: boolean;
|
|
22
|
+
readonly syncStart: number;
|
|
23
|
+
readonly maxWaitMs: number;
|
|
24
|
+
readonly request: DiagnosticSynchronization;
|
|
25
|
+
readonly cachedDiagnostics: Diagnostic[] | null;
|
|
26
|
+
readonly observer: DiagnosticObserver;
|
|
27
|
+
readonly waiters: DiagnosticWaitRegistry;
|
|
28
|
+
readonly current: () => boolean;
|
|
29
|
+
readonly freshPush: () => boolean;
|
|
30
|
+
/** Receive time of a current tentative push that is already cached. */
|
|
31
|
+
readonly currentPushReceivedAt: () => number | undefined;
|
|
32
|
+
readonly diagnostics: () => Diagnostic[];
|
|
33
|
+
readonly pullDiagnostics: (timeoutMs: number, signal: AbortSignal) => Promise<boolean>;
|
|
34
|
+
/** Observe the push wait outcome without finishing the observer; the caller finishes once. */
|
|
35
|
+
readonly onPushWait?: (outcome: DiagnosticPushWaitOutcome) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type PullCollectionOutcome = "failed" | "pull" | "push" | "released";
|
|
39
|
+
|
|
40
|
+
/** Collect fresh pull or push evidence for one synchronized document. */
|
|
41
|
+
export async function collectSynchronizedFileDiagnostics(
|
|
42
|
+
options: FileDiagnosticCollectionOptions,
|
|
43
|
+
control?: CodeRequestControl,
|
|
44
|
+
): Promise<CodeQueryResult<Diagnostic[]>> {
|
|
45
|
+
throwIfCodeRequestInterrupted(control);
|
|
46
|
+
if (options.supportsPull) {
|
|
47
|
+
const pullOutcome = await collectPullEvidence(options, control);
|
|
48
|
+
if (pullOutcome === "pull" || pullOutcome === "push") {
|
|
49
|
+
return completedCodeQuery(options.diagnostics());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throwIfCodeRequestInterrupted(control);
|
|
54
|
+
if (!options.current()) {
|
|
55
|
+
options.observer.pushWaitCompleted(1, "released");
|
|
56
|
+
return unavailableCodeQuery(
|
|
57
|
+
"Diagnostic collection ended before the current document synchronization was confirmed.",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (options.freshPush()) {
|
|
61
|
+
options.observer.pushWaitCompleted(1, "published");
|
|
62
|
+
return completedCodeQuery(options.diagnostics());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const push = await waitForConfirmedPush(options, control);
|
|
66
|
+
if (options.onPushWait) options.onPushWait(push);
|
|
67
|
+
else options.observer.pushWaitCompleted(1, push);
|
|
68
|
+
if (push === "published") {
|
|
69
|
+
return completedCodeQuery(options.diagnostics());
|
|
70
|
+
}
|
|
71
|
+
if (push === "tentative") {
|
|
72
|
+
// A current tentative error is useful partial evidence, but an empty
|
|
73
|
+
// publication cannot establish that the document is clean (ADR 0021).
|
|
74
|
+
const diagnostics = options.diagnostics();
|
|
75
|
+
return diagnostics.length > 0
|
|
76
|
+
? partialCodeQuery(diagnostics, TENTATIVE_PUSH_UNAVAILABLE_REASON)
|
|
77
|
+
: unavailableCodeQuery(TENTATIVE_PUSH_UNAVAILABLE_REASON);
|
|
78
|
+
}
|
|
79
|
+
return incompleteDiagnosticResult(
|
|
80
|
+
options.cachedDiagnostics,
|
|
81
|
+
push === "released" ? "released" : "timed-out",
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Wait until a confirmed push publication settles the synchronization.
|
|
87
|
+
*
|
|
88
|
+
* Every accepted publication releases the waiter. A confirmed push ends the
|
|
89
|
+
* wait as "published". A publication that stays tentative (no republish
|
|
90
|
+
* arrived for the same synchronization) ends as "tentative" when the budget
|
|
91
|
+
* expires. A wait with no publication at all keeps the waiter's
|
|
92
|
+
* "timed-out", and a lifecycle release stays "released".
|
|
93
|
+
*/
|
|
94
|
+
async function waitForConfirmedPush(
|
|
95
|
+
options: FileDiagnosticCollectionOptions,
|
|
96
|
+
control?: CodeRequestControl,
|
|
97
|
+
): Promise<DiagnosticPushWaitOutcome> {
|
|
98
|
+
// A tentative publication may already sit in the cache when the wait
|
|
99
|
+
// starts (ADR 0021). Do not grant repeated callers a new full wait window:
|
|
100
|
+
// bound them by the first operation window or the cached publication age.
|
|
101
|
+
const initialPushReceivedAt = options.currentPushReceivedAt();
|
|
102
|
+
const waitDeadline = Math.min(
|
|
103
|
+
options.syncStart + options.maxWaitMs,
|
|
104
|
+
initialPushReceivedAt === undefined
|
|
105
|
+
? Number.POSITIVE_INFINITY
|
|
106
|
+
: initialPushReceivedAt + options.maxWaitMs,
|
|
107
|
+
);
|
|
108
|
+
let observedPublication = initialPushReceivedAt !== undefined;
|
|
109
|
+
for (;;) {
|
|
110
|
+
throwIfCodeRequestInterrupted(control);
|
|
111
|
+
// A republish can arrive after the previous wait released but before the
|
|
112
|
+
// next waiter registers; re-check the store on every loop pass.
|
|
113
|
+
if (options.freshPush()) return "published";
|
|
114
|
+
const push = await options.waiters.waitForPush(
|
|
115
|
+
options.request.uri,
|
|
116
|
+
Math.max(0, waitDeadline - Date.now()),
|
|
117
|
+
control,
|
|
118
|
+
);
|
|
119
|
+
// A lifecycle release ends the wait definitively: a tentative
|
|
120
|
+
// publication observed earlier must not reclassify the release.
|
|
121
|
+
if (push === "released") return "released";
|
|
122
|
+
// A promotion can land while the timed-out waiter tears down; the cache
|
|
123
|
+
// must complete the wait before the timeout classifies the outcome.
|
|
124
|
+
if (options.freshPush()) return "published";
|
|
125
|
+
if (push === "timed-out") {
|
|
126
|
+
return observedPublication || options.currentPushReceivedAt() !== undefined
|
|
127
|
+
? "tentative"
|
|
128
|
+
: "timed-out";
|
|
129
|
+
}
|
|
130
|
+
observedPublication = true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function collectPullEvidence(
|
|
135
|
+
options: FileDiagnosticCollectionOptions,
|
|
136
|
+
control?: CodeRequestControl,
|
|
137
|
+
): Promise<PullCollectionOutcome> {
|
|
138
|
+
const remaining = options.maxWaitMs - (Date.now() - options.syncStart);
|
|
139
|
+
if (remaining <= 0) {
|
|
140
|
+
options.observer.pullTimedOut();
|
|
141
|
+
return "failed";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const controller = new AbortController();
|
|
146
|
+
// Link the caller's cancellation to the local pull controller so an
|
|
147
|
+
// in-flight pull receives protocol cancellation and stops promptly.
|
|
148
|
+
const onAbort = () => controller.abort(control?.signal?.reason);
|
|
149
|
+
if (control?.signal?.aborted) onAbort();
|
|
150
|
+
else control?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
151
|
+
try {
|
|
152
|
+
const outcome = await raceDiagnosticPull({
|
|
153
|
+
pull: options.pullDiagnostics(remaining, controller.signal),
|
|
154
|
+
waitForChange: () => options.waiters.waitForChange(),
|
|
155
|
+
freshPush: options.freshPush,
|
|
156
|
+
current: options.current,
|
|
157
|
+
});
|
|
158
|
+
if (outcome !== "pull") controller.abort();
|
|
159
|
+
if (outcome === "pull") options.observer.pullCompleted(1);
|
|
160
|
+
else options.observer.pullFailed(undefined);
|
|
161
|
+
if (outcome === "push") options.observer.pushWaitCompleted(1, "published");
|
|
162
|
+
return outcome;
|
|
163
|
+
} finally {
|
|
164
|
+
control?.signal?.removeEventListener("abort", onAbort);
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (isCodeRequestInterruption(error, control)) throw error;
|
|
168
|
+
options.observer.pullFailed(error);
|
|
169
|
+
return "failed";
|
|
170
|
+
}
|
|
171
|
+
}
|