@mrclrchtr/supi-code-intelligence 4.9.0 → 5.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 +2 -1
- 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 +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 -3
- package/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +34 -21
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/node_modules/@mrclrchtr/supi-lsp/README.md +80 -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 +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 -3
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +34 -21
- 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 +10 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +206 -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 +114 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +372 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +14 -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 +491 -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 +215 -0
- 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 +370 -266
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +52 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +185 -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 +467 -115
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +238 -11
- package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +10 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/config/defaults.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +45 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +38 -0
- 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 +13 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +300 -24
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +5 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +464 -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 +40 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +50 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +136 -120
- 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 +26 -6
- 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 +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 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +34 -21
- 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/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +7 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +1 -1
- 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 +236 -24
- package/src/analysis/health/recovery.ts +6 -2
- package/src/analysis/provider.ts +58 -0
- package/src/analysis/readiness.ts +30 -9
- package/src/analysis/refactor/apply.ts +59 -20
- package/src/analysis/refactor/mutation-authority.ts +143 -0
- package/src/analysis/refactor/position.ts +47 -0
- package/src/analysis/refactor/safety.ts +18 -10
- 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 -4
- package/src/analysis/search/ast-scan.ts +1 -1
- package/src/analysis/search/deadline.ts +2 -2
- package/src/analysis/search/pattern-analysis.ts +46 -32
- package/src/analysis/search/pattern.ts +12 -2
- 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/diagnostics/evidence.ts +51 -0
- package/src/session/capability-adapter.ts +12 -3
- package/src/session/find-workflow.ts +17 -7
- package/src/session/graph/collect.ts +18 -2
- package/src/session/graph-workflow.ts +19 -12
- package/src/session/health-refresh.ts +148 -0
- package/src/session/health-types.ts +37 -15
- package/src/session/health-workflow.ts +37 -39
- package/src/session/inspect/collect.ts +20 -3
- package/src/session/inspect-workflow.ts +10 -5
- package/src/session/orientation/collect.ts +10 -1
- 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 +2 -1
- package/src/session/orientation-workflow.ts +15 -8
- package/src/session/refactor-plans.ts +2 -0
- package/src/session/refactor-workflow.ts +59 -14
- package/src/session/session.ts +4 -1
- 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 +199 -25
- package/src/substrate/lsp/state.ts +2 -2
- package/src/substrate/workspace-provider-host.ts +25 -3
- package/src/tool/find/execute.ts +7 -1
- package/src/tool/find/render.ts +20 -3
- package/src/tool/find/tui.ts +23 -21
- package/src/tool/graph/execute.ts +23 -3
- package/src/tool/graph/tui.ts +59 -46
- package/src/tool/health/execute.ts +7 -2
- package/src/tool/health/markdown.ts +56 -14
- package/src/tool/health/refresh-status.ts +105 -0
- package/src/tool/health/tui.ts +95 -98
- package/src/tool/infra/workflow-control.ts +8 -0
- package/src/tool/inspect/execute.ts +7 -1
- package/src/tool/inspect/tui.ts +26 -18
- package/src/tool/orientation/execute.ts +21 -3
- package/src/tool/orientation/tui.ts +51 -35
- package/src/tool/refactor-apply/execute.ts +3 -0
- package/src/tool/refactor-apply/tui.ts +43 -25
- package/src/tool/refactor-plan/execute.ts +4 -0
- package/src/tool/refactor-plan/tui.ts +33 -20
- package/src/tool/register.ts +127 -22
- package/src/tool/resolve/execute.ts +18 -1
- package/src/tool/resolve/tui.ts +42 -24
- package/src/tool/result/display.ts +107 -0
- package/src/tool/result/errors.ts +20 -17
- package/src/tool/result/find.ts +52 -8
- package/src/tool/result/graph.ts +44 -1
- package/src/tool/result/health.ts +53 -1
- package/src/tool/result/inspect.ts +113 -1
- package/src/tool/result/orientation.ts +66 -14
- package/src/tool/result/refactor.ts +36 -4
- package/src/tool/result/resolve.ts +44 -7
- package/src/tool/result/types.ts +20 -0
- 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 -247
package/src/tool/find/execute.ts
CHANGED
|
@@ -26,12 +26,18 @@ export async function executeFindTool(
|
|
|
26
26
|
return searchErrorResult(`**Error:** ${outcome.message}`, {
|
|
27
27
|
scope: Array.isArray(params?.scope) ? params.scope.join(", ") : null,
|
|
28
28
|
nextQueries: ["Fix the search input and retry"],
|
|
29
|
+
message: outcome.message,
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const assembly = assembleFindWorkflowResult(outcome);
|
|
33
34
|
return {
|
|
34
35
|
content: renderFindResult(assembly),
|
|
35
|
-
details: {
|
|
36
|
+
details: {
|
|
37
|
+
type: "search",
|
|
38
|
+
data: assembly.details,
|
|
39
|
+
status: "completed",
|
|
40
|
+
displaySections: assembly.displaySections,
|
|
41
|
+
},
|
|
36
42
|
};
|
|
37
43
|
}
|
package/src/tool/find/render.ts
CHANGED
|
@@ -38,9 +38,7 @@ function renderSemantic(assembly: FindResultAssembly, evidence: EvidenceListMeta
|
|
|
38
38
|
const { outcome } = assembly;
|
|
39
39
|
if (outcome.data.kind !== "semantic") return "";
|
|
40
40
|
const symbols = outcome.data.symbols;
|
|
41
|
-
if (symbols.length === 0)
|
|
42
|
-
return `**Semantic search** — \`${outcome.query}\`\n\nNo semantic results found in \`${outcome.scopeLabel}\`.`;
|
|
43
|
-
}
|
|
41
|
+
if (symbols.length === 0) return renderEmptySemantic(assembly, evidence);
|
|
44
42
|
|
|
45
43
|
const lines = [
|
|
46
44
|
`**Semantic search** — \`${outcome.query}\` (${symbols.length} symbol${symbols.length === 1 ? "" : "s"} found)`,
|
|
@@ -55,3 +53,22 @@ function renderSemantic(assembly: FindResultAssembly, evidence: EvidenceListMeta
|
|
|
55
53
|
if (disclosure) lines.push(disclosure);
|
|
56
54
|
return lines.join("\n");
|
|
57
55
|
}
|
|
56
|
+
|
|
57
|
+
function renderEmptySemantic(assembly: FindResultAssembly, evidence: EvidenceListMetadata): string {
|
|
58
|
+
const { outcome } = assembly;
|
|
59
|
+
const lines = [
|
|
60
|
+
`**Semantic search** — \`${outcome.query}\``,
|
|
61
|
+
"",
|
|
62
|
+
evidence.partialReason
|
|
63
|
+
? `No LSP workspace-symbol results were collected in \`${outcome.scopeLabel}\`.`
|
|
64
|
+
: `No LSP workspace-symbol results found in \`${outcome.scopeLabel}\`.`,
|
|
65
|
+
];
|
|
66
|
+
const disclosure = renderEvidenceListMetadataDisclosure(evidence);
|
|
67
|
+
if (disclosure) lines.push("", disclosure);
|
|
68
|
+
lines.push("", "Document-level semantic symbols can differ from the workspace index.");
|
|
69
|
+
const fileQuery = assembly.assembled.actions.find(
|
|
70
|
+
(action) => action.kind === "query" && action.instruction.startsWith("If you know the file"),
|
|
71
|
+
);
|
|
72
|
+
if (fileQuery?.kind === "query") lines.push(fileQuery.instruction);
|
|
73
|
+
return lines.join("\n");
|
|
74
|
+
}
|
package/src/tool/find/tui.ts
CHANGED
|
@@ -1,39 +1,41 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TUI renderer for code_find — renderCall + renderResult.
|
|
3
|
-
*/
|
|
1
|
+
/** TUI renderer for code_find. */
|
|
4
2
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
5
3
|
import { Text } from "@earendil-works/pi-tui";
|
|
6
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
formatCallValue,
|
|
6
|
+
type ResultOptios,
|
|
7
|
+
renderSimpleResult,
|
|
8
|
+
type ToolRendererContext,
|
|
9
|
+
type ToolResult,
|
|
10
|
+
} from "../../ui/tui/common.ts";
|
|
7
11
|
import type { CodeFindToolParams } from "./execute.ts";
|
|
8
12
|
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
/** Render the compact code_find call header. */
|
|
14
|
+
export function renderFindCall(
|
|
15
|
+
args: unknown,
|
|
16
|
+
theme: Theme,
|
|
17
|
+
_context: ToolRendererContext | undefined,
|
|
18
|
+
): Text {
|
|
19
|
+
const params = (args ?? {}) as Partial<CodeFindToolParams>;
|
|
20
|
+
const mode = formatCallValue(params.mode, 20) ?? "mode?";
|
|
14
21
|
|
|
15
22
|
let content = theme.fg("toolTitle", "code_find");
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
content += ` ${theme.fg("accent", JSON.stringify(params.query))}`;
|
|
19
|
-
}
|
|
20
|
-
|
|
23
|
+
const query = formatCallValue(params.query);
|
|
24
|
+
if (query) content += ` ${theme.fg("accent", JSON.stringify(query))}`;
|
|
21
25
|
content += ` ${theme.fg("muted", mode)}`;
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
27
|
+
const kind = formatCallValue(params.kind, 30);
|
|
28
|
+
if (kind) content += theme.fg("dim", ` [${kind}]`);
|
|
26
29
|
|
|
27
30
|
return new Text(content, 0, 0);
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
/**
|
|
31
|
-
|
|
33
|
+
/** Render code_find progress, status, and structured result details. */
|
|
32
34
|
export function renderFindResult(
|
|
33
35
|
result: ToolResult,
|
|
34
36
|
options: ResultOptios,
|
|
35
37
|
theme: Theme,
|
|
36
|
-
|
|
38
|
+
context: ToolRendererContext | undefined,
|
|
37
39
|
): ReturnType<typeof renderSimpleResult> {
|
|
38
|
-
return renderSimpleResult(result, options, theme, "Searching…");
|
|
40
|
+
return renderSimpleResult(result, options, theme, "Searching…", context);
|
|
39
41
|
}
|
|
@@ -4,6 +4,7 @@ import type { GraphWorkflowInput, RequestedGraphRelation } from "../../session/g
|
|
|
4
4
|
import type { GraphTargetInput } from "../../session/target-input.ts";
|
|
5
5
|
import type { CodeIntelResult, CodeIntelToolExecCtx } from "../../types/index.ts";
|
|
6
6
|
import { toWorkflowControl } from "../infra/workflow-control.ts";
|
|
7
|
+
import { createToolDisplaySection } from "../result/display.ts";
|
|
7
8
|
import { searchErrorResult } from "../result/errors.ts";
|
|
8
9
|
import { assembleGraphResult } from "../result/graph.ts";
|
|
9
10
|
import { renderGraphResult } from "./markdown.ts";
|
|
@@ -25,7 +26,7 @@ export async function executeGraphTool(
|
|
|
25
26
|
|
|
26
27
|
if (outcome.kind === "unavailable") throw new Error(outcome.reason);
|
|
27
28
|
if (outcome.kind === "invalid-input") {
|
|
28
|
-
return searchErrorResult(`**Error:** ${outcome.message}
|
|
29
|
+
return searchErrorResult(`**Error:** ${outcome.message}`, { message: outcome.message });
|
|
29
30
|
}
|
|
30
31
|
if (outcome.kind === "disambiguation" || outcome.kind === "kind-mismatch") {
|
|
31
32
|
const lines = [
|
|
@@ -45,7 +46,21 @@ export async function executeGraphTool(
|
|
|
45
46
|
"Retry without `symbolKind`, use an observed provider kind, or choose a handle.",
|
|
46
47
|
);
|
|
47
48
|
}
|
|
48
|
-
return searchErrorResult(lines.join("\n")
|
|
49
|
+
return searchErrorResult(lines.join("\n"), {
|
|
50
|
+
message:
|
|
51
|
+
outcome.kind === "kind-mismatch"
|
|
52
|
+
? `No target matched provider kind ${outcome.requestedKind}.`
|
|
53
|
+
: "The target is ambiguous.",
|
|
54
|
+
displaySections: [
|
|
55
|
+
createToolDisplaySection({
|
|
56
|
+
key: "graph.candidates",
|
|
57
|
+
title: "Candidates",
|
|
58
|
+
items: outcome.candidates,
|
|
59
|
+
format: (candidate) =>
|
|
60
|
+
`${candidate.targetId} — ${candidate.name} (${candidate.kind ?? "unknown"}) at ${candidate.file}:${candidate.line}:${candidate.character}`,
|
|
61
|
+
}),
|
|
62
|
+
],
|
|
63
|
+
});
|
|
49
64
|
}
|
|
50
65
|
|
|
51
66
|
const assembly = assembleGraphResult({
|
|
@@ -57,6 +72,11 @@ export async function executeGraphTool(
|
|
|
57
72
|
});
|
|
58
73
|
return {
|
|
59
74
|
content: renderGraphResult(assembly),
|
|
60
|
-
details: {
|
|
75
|
+
details: {
|
|
76
|
+
type: "search",
|
|
77
|
+
data: assembly.details,
|
|
78
|
+
status: "completed",
|
|
79
|
+
displaySections: assembly.displaySections,
|
|
80
|
+
},
|
|
61
81
|
};
|
|
62
82
|
}
|
package/src/tool/graph/tui.ts
CHANGED
|
@@ -1,73 +1,76 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TUI renderer for code_graph — renderCall + renderResult.
|
|
3
|
-
*
|
|
4
|
-
* Dual-surface rendering: chrome built from details, markdown body
|
|
5
|
-
* available as a collapsible detail view.
|
|
6
|
-
*/
|
|
7
|
-
|
|
1
|
+
/** TUI renderer for code_graph. */
|
|
8
2
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
9
3
|
import { Container, Spacer, Text } from "@earendil-works/pi-tui";
|
|
10
4
|
import {
|
|
11
5
|
buildSimpleCompact,
|
|
12
6
|
buildSimpleHeader,
|
|
13
7
|
type EvidenceEntry,
|
|
8
|
+
formatCallPath,
|
|
9
|
+
formatCallValue,
|
|
14
10
|
type ResultOptios,
|
|
11
|
+
renderDomainError,
|
|
12
|
+
renderDomainResult,
|
|
15
13
|
renderEvidenceLines,
|
|
14
|
+
renderExecutionError,
|
|
16
15
|
renderMarkdownDetail,
|
|
17
16
|
renderPartial,
|
|
18
17
|
renderStructuredDetailBody,
|
|
18
|
+
renderToolDisplaySections,
|
|
19
|
+
renderTruncationDisclosure,
|
|
20
|
+
type ToolRendererContext,
|
|
19
21
|
type ToolResult,
|
|
20
22
|
} from "../../ui/tui/common.ts";
|
|
21
23
|
import type { CodeGraphToolParams, GraphRelation } from "./execute.ts";
|
|
22
24
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
/** Render the compact code_graph call header. */
|
|
26
|
+
export function renderGraphCall(
|
|
27
|
+
args: unknown,
|
|
28
|
+
theme: Theme,
|
|
29
|
+
_context: ToolRendererContext | undefined,
|
|
30
|
+
): Text {
|
|
31
|
+
const params = (args ?? {}) as Partial<CodeGraphToolParams>;
|
|
32
|
+
const relations: GraphRelation[] = Array.isArray(params.relations)
|
|
33
|
+
? (params.relations as GraphRelation[])
|
|
34
|
+
: ["references"];
|
|
28
35
|
const relationLabel = formatRelations(relations);
|
|
29
36
|
|
|
30
37
|
let content = theme.fg("toolTitle", "code_graph");
|
|
38
|
+
if (relationLabel) content += ` ${theme.fg("accent", relationLabel)}`;
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const target = formatTarget(params);
|
|
37
|
-
if (target) {
|
|
38
|
-
content += ` ${theme.fg("muted", target)}`;
|
|
39
|
-
}
|
|
40
|
+
const target = formatTarget(params.target);
|
|
41
|
+
if (target) content += ` ${theme.fg("muted", target)}`;
|
|
40
42
|
|
|
41
43
|
return new Text(content, 0, 0);
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
+
/** Render code_graph progress, status, and structured result details. */
|
|
46
47
|
export function renderGraphResult(
|
|
47
48
|
result: ToolResult,
|
|
48
49
|
options: ResultOptios,
|
|
49
50
|
theme: Theme,
|
|
50
|
-
|
|
51
|
+
context: ToolRendererContext | undefined,
|
|
51
52
|
): Container | Text {
|
|
52
|
-
if (options.isPartial)
|
|
53
|
-
return renderPartial("Collecting relations…", theme);
|
|
54
|
-
}
|
|
53
|
+
if (options.isPartial) return renderPartial("Collecting relations…", theme);
|
|
55
54
|
|
|
56
|
-
const container = new Container();
|
|
57
55
|
const details =
|
|
58
56
|
result.details?.type === "search" ? (result.details.data as Record<string, unknown>) : null;
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
57
|
+
const executionError = renderExecutionError(context, "code_graph failed", theme);
|
|
58
|
+
if (executionError) return executionError;
|
|
59
|
+
const domainError = renderDomainError(result, theme);
|
|
60
|
+
if (domainError) return renderDomainResult(result, options, theme, domainError);
|
|
64
61
|
|
|
65
62
|
if (!options.expanded) {
|
|
66
|
-
|
|
63
|
+
const compact = buildSimpleCompact(details ?? undefined, theme);
|
|
64
|
+
const truncation = renderTruncationDisclosure(result, theme);
|
|
65
|
+
if (!truncation) return compact;
|
|
66
|
+
const container = new Container();
|
|
67
|
+
container.addChild(compact);
|
|
68
|
+
container.addChild(new Spacer(1));
|
|
69
|
+
container.addChild(truncation);
|
|
67
70
|
return container;
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
|
|
73
|
+
const container = new Container();
|
|
71
74
|
const header = buildSimpleHeader(details ?? undefined, theme);
|
|
72
75
|
if (header) container.addChild(header);
|
|
73
76
|
|
|
@@ -77,28 +80,38 @@ export function renderGraphResult(
|
|
|
77
80
|
renderEvidenceLines(container, evidenceLists, theme);
|
|
78
81
|
}
|
|
79
82
|
|
|
83
|
+
renderToolDisplaySections(container, result.details?.displaySections, theme);
|
|
80
84
|
renderStructuredDetailBody(container, details ?? undefined, theme);
|
|
85
|
+
const truncation = renderTruncationDisclosure(result, theme);
|
|
86
|
+
if (truncation) {
|
|
87
|
+
container.addChild(new Spacer(1));
|
|
88
|
+
container.addChild(truncation);
|
|
89
|
+
}
|
|
81
90
|
renderMarkdownDetail(container, result, theme);
|
|
82
|
-
|
|
83
91
|
return container;
|
|
84
92
|
}
|
|
85
93
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
function formatRelations(relations: GraphRelation[]): string {
|
|
94
|
+
function formatRelations(relations: readonly GraphRelation[]): string {
|
|
89
95
|
if (relations.length === 0) return "";
|
|
90
96
|
if (relations.length === 1) return `→ ${relations[0]}`;
|
|
91
97
|
if (relations.length <= 3) return `→ ${relations.join(", ")}`;
|
|
92
98
|
return `→ ${relations.slice(0, 2).join(", ")} +${relations.length - 2}`;
|
|
93
99
|
}
|
|
94
100
|
|
|
95
|
-
function formatTarget(
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
function formatTarget(target: unknown): string {
|
|
102
|
+
if (!target || typeof target !== "object") return "";
|
|
103
|
+
const record = target as Record<string, unknown>;
|
|
104
|
+
|
|
105
|
+
if (record.symbol && typeof record.symbol === "object") {
|
|
106
|
+
const query = formatCallValue((record.symbol as Record<string, unknown>).query);
|
|
107
|
+
return query ? `of ${query}` : "";
|
|
108
|
+
}
|
|
109
|
+
if (record.anchor && typeof record.anchor === "object") {
|
|
110
|
+
const anchor = record.anchor as Record<string, unknown>;
|
|
111
|
+
const file = formatCallPath(anchor.file);
|
|
112
|
+
const line = typeof anchor.line === "number" ? `:${anchor.line}` : "";
|
|
113
|
+
return file ? `at ${file}${line}` : "";
|
|
101
114
|
}
|
|
102
|
-
|
|
103
|
-
return "";
|
|
115
|
+
const handle = formatCallValue(record.handle);
|
|
116
|
+
return handle ? `of ${handle}` : "";
|
|
104
117
|
}
|
|
@@ -21,12 +21,17 @@ export async function executeHealthTool(
|
|
|
21
21
|
const outcome = await ctx.session.health(params as HealthWorkflowInput, toWorkflowControl(ctx));
|
|
22
22
|
if (outcome.kind === "unavailable") throw new Error(outcome.reason);
|
|
23
23
|
if (outcome.kind === "invalid-input") {
|
|
24
|
-
return healthErrorResult(`**Error:** ${outcome.message}`,
|
|
24
|
+
return healthErrorResult(`**Error:** ${outcome.message}`, outcome.message);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const assembly = assembleHealthResult(outcome.data);
|
|
28
28
|
return {
|
|
29
29
|
content: renderHealthResult(assembly, ctx.cwd),
|
|
30
|
-
details: {
|
|
30
|
+
details: {
|
|
31
|
+
type: "health",
|
|
32
|
+
data: assembly.details,
|
|
33
|
+
status: "completed",
|
|
34
|
+
displaySections: assembly.displaySections,
|
|
35
|
+
},
|
|
31
36
|
};
|
|
32
37
|
}
|
|
@@ -51,16 +51,24 @@ function renderRefreshStatus(
|
|
|
51
51
|
|
|
52
52
|
switch (data.refresh.kind) {
|
|
53
53
|
case "completed":
|
|
54
|
-
lines.push(
|
|
55
|
-
|
|
54
|
+
lines.push(
|
|
55
|
+
`**${refreshAttemptLabel(data.refresh)}**: ${asSentence(completedRefreshText(data.refresh))}`,
|
|
56
|
+
);
|
|
57
|
+
lines.push(`**Stale assessment**: ${asSentence(staleAssessmentText(data.refresh))}`);
|
|
56
58
|
lines.push("");
|
|
57
59
|
return;
|
|
58
|
-
case "failed":
|
|
59
|
-
|
|
60
|
+
case "failed": {
|
|
61
|
+
const evidence = data.refresh.diagnosticEvidence
|
|
62
|
+
? `; ${formatDiagnosticEvidence(data.refresh.diagnosticEvidence)}`
|
|
63
|
+
: "";
|
|
64
|
+
lines.push(
|
|
65
|
+
`**${refreshAttemptLabel(data.refresh)}**: failed — ${data.refresh.reason}${evidence}`,
|
|
66
|
+
);
|
|
60
67
|
lines.push("");
|
|
61
68
|
return;
|
|
69
|
+
}
|
|
62
70
|
case "not-attempted":
|
|
63
|
-
lines.push(`**Diagnostic refresh**: not
|
|
71
|
+
lines.push(`**Diagnostic refresh attempt**: not started — ${data.refresh.reason}`);
|
|
64
72
|
if (data.refresh.lastAttempt) renderLastAttempt(lines, data.refresh.lastAttempt, cwd);
|
|
65
73
|
lines.push("");
|
|
66
74
|
return;
|
|
@@ -69,7 +77,7 @@ function renderRefreshStatus(
|
|
|
69
77
|
renderLastAttempt(lines, data.refresh.lastAttempt, cwd);
|
|
70
78
|
} else {
|
|
71
79
|
lines.push(
|
|
72
|
-
"**Diagnostic refresh**: not
|
|
80
|
+
"**Diagnostic refresh attempt**: not requested this session. Use `refresh: true` to try one.",
|
|
73
81
|
);
|
|
74
82
|
}
|
|
75
83
|
lines.push("");
|
|
@@ -79,10 +87,19 @@ function renderRefreshStatus(
|
|
|
79
87
|
function completedRefreshText(
|
|
80
88
|
attempt: Extract<HealthRefreshAttempt, { kind: "completed" }>,
|
|
81
89
|
): string {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
const base =
|
|
91
|
+
attempt.attemptedActiveClients === 0 && attempt.restartedClients === 0
|
|
92
|
+
? "completed no-op — no active clients were targeted"
|
|
93
|
+
: `completed — ${attempt.attemptedActiveClients} active client${plural(attempt.attemptedActiveClients)} targeted, ${attempt.restartedClients} restarted`;
|
|
94
|
+
return attempt.operationScope === "workspace-runtime"
|
|
95
|
+
? `${base}; ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}`
|
|
96
|
+
: base;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function refreshAttemptLabel(attempt: Pick<HealthRefreshAttempt, "operationScope">): string {
|
|
100
|
+
return attempt.operationScope === "file-runtime"
|
|
101
|
+
? "File LSP maintenance attempt"
|
|
102
|
+
: "Diagnostic refresh attempt";
|
|
86
103
|
}
|
|
87
104
|
|
|
88
105
|
function staleAssessmentText(
|
|
@@ -90,6 +107,9 @@ function staleAssessmentText(
|
|
|
90
107
|
): string {
|
|
91
108
|
if (attempt.staleAssessment.warning) return attempt.staleAssessment.warning;
|
|
92
109
|
const matches = `${attempt.staleAssessment.matchedFileCount} file${plural(attempt.staleAssessment.matchedFileCount)} match the stale-module heuristic`;
|
|
110
|
+
if (attempt.staleAssessment.scope === "file") {
|
|
111
|
+
return `${matches} in the requested file; workspace clustering was not assessed`;
|
|
112
|
+
}
|
|
93
113
|
return attempt.staleAssessment.suspected
|
|
94
114
|
? `${matches}; a clustered stale-module pattern is suspected`
|
|
95
115
|
: `${matches}; no clustered stale-module pattern is suspected`;
|
|
@@ -97,12 +117,19 @@ function staleAssessmentText(
|
|
|
97
117
|
|
|
98
118
|
function renderLastAttempt(lines: string[], attempt: HealthRefreshAttempt, cwd: string): void {
|
|
99
119
|
const outcome =
|
|
100
|
-
attempt.kind === "completed"
|
|
120
|
+
attempt.kind === "completed"
|
|
121
|
+
? completedRefreshText(attempt)
|
|
122
|
+
: `failed — ${attempt.reason}${attempt.diagnosticEvidence ? `; ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}` : ""}`;
|
|
123
|
+
const label = refreshAttemptLabel(attempt);
|
|
101
124
|
lines.push(
|
|
102
|
-
`**Last
|
|
125
|
+
`**Last ${label.toLowerCase()}**: ${asSentence(outcome)} Started ${formatElapsed(Date.now() - attempt.attemptedAt)}; requested ${formatDiagnosticScope(attempt.requestedDiagnosticScope, cwd)}; operation scope: ${refreshOperationScopeText(attempt)}.`,
|
|
103
126
|
);
|
|
104
127
|
}
|
|
105
128
|
|
|
129
|
+
function refreshOperationScopeText(attempt: HealthRefreshAttempt): string {
|
|
130
|
+
return attempt.operationScope === "file-runtime" ? "file runtime" : "workspace runtime";
|
|
131
|
+
}
|
|
132
|
+
|
|
106
133
|
function formatElapsed(milliseconds: number): string {
|
|
107
134
|
const seconds = Math.max(0, Math.round(milliseconds / 1_000));
|
|
108
135
|
if (seconds < 60) return `${seconds}s ago`;
|
|
@@ -143,6 +170,7 @@ function renderDiagnosticsSection(lines: string[], data: HealthData, cwd: string
|
|
|
143
170
|
lines.push("Diagnostics were not requested.");
|
|
144
171
|
} else {
|
|
145
172
|
lines.push(`**Evidence scope**: ${formatDiagnosticScope(observation.scope, cwd)}.`);
|
|
173
|
+
lines.push(`**Evidence coverage**: ${formatDiagnosticEvidence(observation.evidence)}.`);
|
|
146
174
|
lines.push("");
|
|
147
175
|
renderDiagnosticObservation(lines, data, cwd);
|
|
148
176
|
}
|
|
@@ -150,15 +178,25 @@ function renderDiagnosticsSection(lines: string[], data: HealthData, cwd: string
|
|
|
150
178
|
lines.push("");
|
|
151
179
|
}
|
|
152
180
|
|
|
181
|
+
function formatDiagnosticEvidence(evidence: {
|
|
182
|
+
requested: number;
|
|
183
|
+
confirmed: number;
|
|
184
|
+
unconfirmed: number;
|
|
185
|
+
failed: number;
|
|
186
|
+
removed: number;
|
|
187
|
+
}): string {
|
|
188
|
+
return `${evidence.requested} requested, ${evidence.confirmed} confirmed, ${evidence.unconfirmed} unconfirmed, ${evidence.failed} failed, ${evidence.removed} removed`;
|
|
189
|
+
}
|
|
190
|
+
|
|
153
191
|
function renderDiagnosticObservation(lines: string[], data: HealthData, cwd: string): void {
|
|
154
192
|
const observation = data.diagnostics;
|
|
155
193
|
if (observation.kind === "not-requested") return;
|
|
156
194
|
if (observation.kind === "unavailable") {
|
|
157
|
-
lines.push(`Diagnostics unavailable — ${observation.reason}
|
|
195
|
+
lines.push(`Diagnostics unavailable — ${asSentence(observation.reason)}`);
|
|
158
196
|
return;
|
|
159
197
|
}
|
|
160
198
|
if (observation.kind === "partial") {
|
|
161
|
-
lines.push(`Diagnostics partially collected — ${observation.reason}
|
|
199
|
+
lines.push(`Diagnostics partially collected — ${asSentence(observation.reason)}`);
|
|
162
200
|
if (observation.entries.length > 0) {
|
|
163
201
|
lines.push("");
|
|
164
202
|
lines.push("Partial results:");
|
|
@@ -173,6 +211,10 @@ function renderDiagnosticObservation(lines: string[], data: HealthData, cwd: str
|
|
|
173
211
|
renderDiagnosticEntries(lines, data, cwd);
|
|
174
212
|
}
|
|
175
213
|
|
|
214
|
+
function asSentence(text: string): string {
|
|
215
|
+
return /[.!?]$/.test(text) ? text : `${text}.`;
|
|
216
|
+
}
|
|
217
|
+
|
|
176
218
|
function emptyDiagnosticText(scope: HealthDiagnosticScope, cwd: string): string {
|
|
177
219
|
if (scope.kind === "file") {
|
|
178
220
|
return `No errors or warnings found for \`${makeRelative(cwd, scope.path)}\`.`;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/** Render current and retained health refresh status for the TUI. */
|
|
2
|
+
|
|
3
|
+
export function readRefreshStatus(data: Record<string, unknown> | null): string | null {
|
|
4
|
+
const refresh = readRecord(data?.refresh);
|
|
5
|
+
if (!refresh) return null;
|
|
6
|
+
|
|
7
|
+
switch (refresh.kind) {
|
|
8
|
+
case "completed": {
|
|
9
|
+
const attempted = readNumber(refresh.attemptedActiveClients);
|
|
10
|
+
const restarted = readNumber(refresh.restartedClients);
|
|
11
|
+
const stale = readRecord(refresh.staleAssessment);
|
|
12
|
+
const noOp = attempted === 0 && restarted === 0;
|
|
13
|
+
const label = refreshAttemptLabel(refresh);
|
|
14
|
+
const base = noOp
|
|
15
|
+
? `${label} completed no-op`
|
|
16
|
+
: `${label} completed: ${attempted} clients targeted, ${restarted} restarted`;
|
|
17
|
+
const evidence = formatDiagnosticEvidence(readRecord(refresh.diagnosticEvidence));
|
|
18
|
+
const withEvidence = evidence ? `${base}; ${evidence}` : base;
|
|
19
|
+
return stale?.suspected === true
|
|
20
|
+
? `${withEvidence}; stale pattern suspected in ${readNumber(stale.matchedFileCount)} files`
|
|
21
|
+
: withEvidence;
|
|
22
|
+
}
|
|
23
|
+
case "failed": {
|
|
24
|
+
const evidence = formatDiagnosticEvidence(readRecord(refresh.diagnosticEvidence));
|
|
25
|
+
return `${refreshAttemptLabel(refresh)} failed${typeof refresh.reason === "string" ? `: ${refresh.reason}` : ""}${evidence ? `; ${evidence}` : ""}`;
|
|
26
|
+
}
|
|
27
|
+
case "not-attempted":
|
|
28
|
+
return `refresh attempt not started${formatLastRefreshSuffix(refresh)}`;
|
|
29
|
+
case "not-requested":
|
|
30
|
+
return `refresh not requested${formatLastRefreshSuffix(refresh)}`;
|
|
31
|
+
default:
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function readPreviousRefreshStatus(data: Record<string, unknown> | null): string | null {
|
|
37
|
+
const refresh = readRecord(data?.refresh);
|
|
38
|
+
if (!refresh || (refresh.kind !== "not-requested" && refresh.kind !== "not-attempted")) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const attempt = readRecord(refresh.lastAttempt);
|
|
42
|
+
if (!attempt) return null;
|
|
43
|
+
const evidence = formatCompactDiagnosticEvidence(readRecord(attempt.diagnosticEvidence));
|
|
44
|
+
if (attempt.kind === "failed") {
|
|
45
|
+
return `last ${refreshAttemptLabel(attempt)} failed${evidence ? ` (${evidence})` : ""}`;
|
|
46
|
+
}
|
|
47
|
+
if (attempt.kind === "completed") {
|
|
48
|
+
return `last ${refreshAttemptLabel(attempt)} completed${evidence ? ` (${evidence})` : ""}`;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function refreshAttemptLabel(attempt: Record<string, unknown>): string {
|
|
54
|
+
return attempt.operationScope === "file-runtime" ? "file maintenance attempt" : "refresh attempt";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function formatLastRefreshSuffix(refresh: Record<string, unknown>): string {
|
|
58
|
+
const attempt = readRecord(refresh.lastAttempt);
|
|
59
|
+
if (!attempt) return "";
|
|
60
|
+
const evidence = formatDiagnosticEvidence(readRecord(attempt.diagnosticEvidence));
|
|
61
|
+
if (attempt.kind === "failed") {
|
|
62
|
+
const reason = typeof attempt.reason === "string" ? `: ${attempt.reason}` : "";
|
|
63
|
+
return `; last ${refreshAttemptLabel(attempt)} failed${reason}${evidence ? `; ${evidence}` : ""}`;
|
|
64
|
+
}
|
|
65
|
+
if (attempt.kind === "completed") {
|
|
66
|
+
return `; last ${refreshAttemptLabel(attempt)} completed${evidence ? `; ${evidence}` : ""}`;
|
|
67
|
+
}
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function formatCompactDiagnosticEvidence(evidence: Record<string, unknown> | null): string | null {
|
|
72
|
+
const counts = readEvidenceCounts(evidence);
|
|
73
|
+
if (!counts) return null;
|
|
74
|
+
return `req ${counts[0]} · conf ${counts[1]} · unconf ${counts[2]} · failed ${counts[3]} · removed ${counts[4]}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function formatDiagnosticEvidence(evidence: Record<string, unknown> | null): string | null {
|
|
78
|
+
const counts = readEvidenceCounts(evidence);
|
|
79
|
+
if (!counts) return null;
|
|
80
|
+
return `${counts[0]} requested, ${counts[1]} confirmed, ${counts[2]} unconfirmed, ${counts[3]} failed, ${counts[4]} removed`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readEvidenceCounts(evidence: Record<string, unknown> | null): number[] | null {
|
|
84
|
+
if (!evidence) return null;
|
|
85
|
+
const counts = [
|
|
86
|
+
evidence.requested,
|
|
87
|
+
evidence.confirmed,
|
|
88
|
+
evidence.unconfirmed,
|
|
89
|
+
evidence.failed,
|
|
90
|
+
evidence.removed,
|
|
91
|
+
];
|
|
92
|
+
return counts.every(isEvidenceCount) ? counts : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isEvidenceCount(value: unknown): value is number {
|
|
96
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readRecord(value: unknown): Record<string, unknown> | null {
|
|
100
|
+
return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function readNumber(value: unknown): number {
|
|
104
|
+
return typeof value === "number" ? value : 0;
|
|
105
|
+
}
|