@danypops/pi-lector 0.13.9 → 0.14.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/extension/src/apply-patch/rendering.ts +2 -1
- package/extension/src/code-intelligence/operations.ts +109 -3
- package/extension/src/code-intelligence/rendering.ts +129 -5
- package/extension/src/cross-workspace-search/rendering.ts +10 -2
- package/extension/src/editor/editor-state.ts +38 -1
- package/extension/src/editor/index.ts +9 -1
- package/extension/src/editor/modal-editor-component.ts +9 -3
- package/extension/src/external-search/rendering.ts +46 -12
- package/extension/src/find-files/rendering.ts +2 -1
- package/extension/src/find-symbols/rendering.ts +2 -1
- package/extension/src/git/operations.ts +23 -1
- package/extension/src/git/rendering.ts +48 -8
- package/extension/src/index.ts +495 -55
- package/extension/src/line-edit/rendering.ts +2 -1
- package/extension/src/package-source/rendering.ts +4 -3
- package/extension/src/presentation/model-content.ts +62 -0
- package/extension/src/presentation/presentation-contract.ts +148 -0
- package/extension/src/presentation/tool-presentation.ts +170 -0
- package/extension/src/repo-cache/rendering.ts +4 -3
- package/extension/src/search/rendering.ts +6 -3
- package/extension/src/symbol-annotation/operations.ts +8 -4
- package/extension/src/vehicle-client.ts +4 -2
- package/extension/src/workspace-cache/rendering.ts +4 -3
- package/package.json +4 -4
package/extension/src/index.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { resolve } from "node:path";
|
|
|
4
4
|
import type {
|
|
5
5
|
CachedRepositoryPage,
|
|
6
6
|
ContentHash,
|
|
7
|
-
ContextBundleResult,
|
|
8
7
|
Diagnostic,
|
|
9
8
|
DocumentSymbolEntry,
|
|
10
9
|
EditOutcome,
|
|
@@ -31,7 +30,7 @@ import type {
|
|
|
31
30
|
WorkspaceLocation,
|
|
32
31
|
WorkspaceMapResult,
|
|
33
32
|
} from "@danypops/lector";
|
|
34
|
-
import { DEFAULT_EXTERNAL_SEARCH_MAX_RESULTS, PACKAGE_ECOSYSTEMS } from "@danypops/lector";
|
|
33
|
+
import { codeActionPreviewId, DEFAULT_EXTERNAL_SEARCH_MAX_RESULTS, PACKAGE_ECOSYSTEMS } from "@danypops/lector";
|
|
35
34
|
import {
|
|
36
35
|
type AgentToolResult,
|
|
37
36
|
createEditToolDefinition,
|
|
@@ -56,6 +55,12 @@ import {
|
|
|
56
55
|
type CallHierarchyToolDetails,
|
|
57
56
|
formatCallHierarchyCall,
|
|
58
57
|
formatCallHierarchyResult,
|
|
58
|
+
formatCodeActionApplyCall,
|
|
59
|
+
formatCodeActionApplyResult,
|
|
60
|
+
formatCodeActionPreviewCall,
|
|
61
|
+
formatCodeActionPreviewResult,
|
|
62
|
+
formatDiagnosticDeltaCall,
|
|
63
|
+
formatDiagnosticDeltaResult,
|
|
59
64
|
formatDiagnosticsCall,
|
|
60
65
|
formatDiagnosticsResult,
|
|
61
66
|
formatDocumentSymbolsCall,
|
|
@@ -68,8 +73,12 @@ import {
|
|
|
68
73
|
formatGoToImplementationResult,
|
|
69
74
|
formatHoverCall,
|
|
70
75
|
formatHoverResult,
|
|
76
|
+
formatImpactAnalysisCall,
|
|
77
|
+
formatImpactAnalysisResult,
|
|
71
78
|
formatReachableFromCall,
|
|
72
79
|
formatReachableFromResult,
|
|
80
|
+
formatTypeHierarchyCall,
|
|
81
|
+
formatTypeHierarchyResult,
|
|
73
82
|
formatWorkspaceMapCall,
|
|
74
83
|
formatWorkspaceMapResult,
|
|
75
84
|
} from "./code-intelligence/rendering.ts";
|
|
@@ -111,6 +120,9 @@ import {
|
|
|
111
120
|
PACKAGE_SOURCE_LIST_VISIBLE_ROWS,
|
|
112
121
|
packageSourceListMoreLine,
|
|
113
122
|
} from "./package-source/rendering.ts";
|
|
123
|
+
import { formatSemanticModelContent } from "./presentation/model-content.ts";
|
|
124
|
+
import { withLectorPresentation } from "./presentation/presentation-contract.ts";
|
|
125
|
+
import { presentationTitle } from "./presentation/tool-presentation.ts";
|
|
114
126
|
import { createLectorReadOperations } from "./read/operations.ts";
|
|
115
127
|
import { createReferenceBasedRenameOperations } from "./reference-based-rename/operations.ts";
|
|
116
128
|
import { createRenameOperations } from "./rename/operations.ts";
|
|
@@ -179,7 +191,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
179
191
|
const customToolNames = new Set<string>();
|
|
180
192
|
function registerLectorTool<TParams extends TSchema, TDetails = unknown, TState = unknown>(tool: ToolDefinition<TParams, TDetails, TState>): void {
|
|
181
193
|
customToolNames.add(tool.name);
|
|
182
|
-
pi.registerTool(tool);
|
|
194
|
+
pi.registerTool(withLectorPresentation(tool));
|
|
183
195
|
}
|
|
184
196
|
pi.on("tool_result", (event) => {
|
|
185
197
|
if (!customToolNames.has(event.toolName)) return;
|
|
@@ -461,7 +473,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
461
473
|
return { content: [{ type: "text", text }], details: result };
|
|
462
474
|
},
|
|
463
475
|
renderCall(args, theme) {
|
|
464
|
-
return new Text(
|
|
476
|
+
return new Text(
|
|
477
|
+
`${theme.fg("toolTitle", theme.bold(presentationTitle("localize_context")))} ${theme.fg("accent", typeof args.query === "string" ? `"${args.query}"` : "")}`,
|
|
478
|
+
0,
|
|
479
|
+
0,
|
|
480
|
+
);
|
|
465
481
|
},
|
|
466
482
|
renderResult(result, { isPartial }, theme, context) {
|
|
467
483
|
if (isPartial) return new Text(theme.fg("warning", "Localizing..."), 0, 0);
|
|
@@ -472,8 +488,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
472
488
|
.join("\n");
|
|
473
489
|
return new Text(theme.fg("error", errorText || "localize_context failed"), 0, 0);
|
|
474
490
|
}
|
|
475
|
-
const
|
|
476
|
-
|
|
491
|
+
const contentText = result.content
|
|
492
|
+
.filter((block) => block.type === "text")
|
|
493
|
+
.map((block) => block.text)
|
|
494
|
+
.join("\n");
|
|
495
|
+
return new Text(contentText || "Localization complete", 0, 0);
|
|
477
496
|
},
|
|
478
497
|
});
|
|
479
498
|
|
|
@@ -774,6 +793,186 @@ export default function (pi: ExtensionAPI) {
|
|
|
774
793
|
},
|
|
775
794
|
});
|
|
776
795
|
|
|
796
|
+
registerLectorTool({
|
|
797
|
+
name: "code_action_preview",
|
|
798
|
+
label: "Code Action Preview",
|
|
799
|
+
description:
|
|
800
|
+
"Request bounded language-server code actions for one range. Returns opaque preview ids, affected files, exact edits, provenance, and truncation status. This operation is read-only; use code_action_apply separately to mutate files.",
|
|
801
|
+
promptSnippet: "Preview bounded language-server fixes for one range",
|
|
802
|
+
promptGuidelines: ["Preview first, inspect every affected path/edit, then pass exactly one returned previewId to code_action_apply."],
|
|
803
|
+
parameters: Type.Object({
|
|
804
|
+
path: Type.String({ description: "Absolute or cwd-relative file path" }),
|
|
805
|
+
startLine: Type.Number({ description: "1-indexed range start line" }),
|
|
806
|
+
startCharacter: Type.Number({ description: "1-indexed range start character" }),
|
|
807
|
+
endLine: Type.Number({ description: "1-indexed range end line" }),
|
|
808
|
+
endCharacter: Type.Number({ description: "1-indexed range end character" }),
|
|
809
|
+
only: Type.Optional(Type.Array(Type.String(), { maxItems: 20, description: "Code-action kinds such as quickfix" })),
|
|
810
|
+
includeCommandActions: Type.Optional(Type.Boolean({ description: "Include command-only actions in preview; guarded apply still denies them" })),
|
|
811
|
+
maxActions: Type.Number({ description: "Maximum actions" }),
|
|
812
|
+
maxEdits: Type.Number({ description: "Maximum edits per action" }),
|
|
813
|
+
maxFiles: Type.Number({ description: "Maximum affected files per action" }),
|
|
814
|
+
maxBytes: Type.Number({ description: "Maximum response JSON bytes" }),
|
|
815
|
+
deadlineMs: Type.Number({ description: "Wall-clock deadline in milliseconds" }),
|
|
816
|
+
}),
|
|
817
|
+
async execute(toolCallId, params, signal, _onUpdate, ctx): Promise<AgentToolResult<OperationOutputs["workspace.previewCodeActions"]>> {
|
|
818
|
+
const path = resolve(cwd, params.path);
|
|
819
|
+
const vehicleCall: LectorVehicleCall = { toolName: "code_action_preview", toolCallId, signal, context: ctx };
|
|
820
|
+
const result = await codeIntelligenceOperations.previewCodeActions(
|
|
821
|
+
path,
|
|
822
|
+
{
|
|
823
|
+
range: {
|
|
824
|
+
start: { line: params.startLine, character: params.startCharacter },
|
|
825
|
+
end: { line: params.endLine, character: params.endCharacter },
|
|
826
|
+
},
|
|
827
|
+
...(params.only ? { only: params.only } : {}),
|
|
828
|
+
...(params.includeCommandActions !== undefined ? { includeCommandActions: params.includeCommandActions } : {}),
|
|
829
|
+
maxActions: params.maxActions,
|
|
830
|
+
maxEdits: params.maxEdits,
|
|
831
|
+
maxFiles: params.maxFiles,
|
|
832
|
+
maxBytes: params.maxBytes,
|
|
833
|
+
deadlineMs: params.deadlineMs,
|
|
834
|
+
},
|
|
835
|
+
vehicleCall,
|
|
836
|
+
);
|
|
837
|
+
const text = result.actions
|
|
838
|
+
.map((action) => `${action.id} ${action.kind ?? "action"} ${action.title} -- ${action.affectedPaths.join(", ") || "command only"}`)
|
|
839
|
+
.join("\n");
|
|
840
|
+
return { content: [{ type: "text", text: text || "No code actions." }], details: result };
|
|
841
|
+
},
|
|
842
|
+
renderCall(args, theme, context) {
|
|
843
|
+
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
844
|
+
text.setText(formatCodeActionPreviewCall(args, theme));
|
|
845
|
+
return text;
|
|
846
|
+
},
|
|
847
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
848
|
+
if (isPartial) return new Text(theme.fg("warning", "Finding code actions..."), 0, 0);
|
|
849
|
+
if (context.isError) {
|
|
850
|
+
const errorText = result.content
|
|
851
|
+
.filter((block) => block.type === "text")
|
|
852
|
+
.map((block) => block.text)
|
|
853
|
+
.join("\n");
|
|
854
|
+
return new Text(theme.fg("error", errorText || "code_action_preview failed"), 0, 0);
|
|
855
|
+
}
|
|
856
|
+
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
857
|
+
text.setText(formatCodeActionPreviewResult(result.details, expanded, theme));
|
|
858
|
+
return text;
|
|
859
|
+
},
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
registerLectorTool({
|
|
863
|
+
name: "code_action_apply",
|
|
864
|
+
label: "Apply Code Action",
|
|
865
|
+
description:
|
|
866
|
+
"Apply one previously previewed language-server WorkspaceEdit atomically with workspace containment, document-version and content-hash guards. Returns a transaction id for exact transaction-aware revert. Command-only actions are denied.",
|
|
867
|
+
promptSnippet: "Atomically apply one previewed language-server edit",
|
|
868
|
+
promptGuidelines: ["Use only after inspecting code_action_preview; retain transactionId for diagnostic_delta and revert."],
|
|
869
|
+
parameters: Type.Object({
|
|
870
|
+
path: Type.String({ description: "The same file path used to obtain the preview" }),
|
|
871
|
+
previewId: Type.String({ description: "Opaque id returned by code_action_preview" }),
|
|
872
|
+
}),
|
|
873
|
+
async execute(toolCallId, params, signal, _onUpdate, ctx): Promise<AgentToolResult<OperationOutputs["workspace.applyCodeAction"]>> {
|
|
874
|
+
const vehicleCall: LectorVehicleCall = { toolName: "code_action_apply", toolCallId, signal, context: ctx };
|
|
875
|
+
const result = await codeIntelligenceOperations.applyCodeAction(resolve(cwd, params.path), codeActionPreviewId(params.previewId), vehicleCall);
|
|
876
|
+
const text = [
|
|
877
|
+
...result.touchedPaths,
|
|
878
|
+
...(result.transactionId ? [`transaction ${result.transactionId}`] : []),
|
|
879
|
+
...(result.pendingCommand ? [`pending command ${result.pendingCommand.command}`] : []),
|
|
880
|
+
].join("\n");
|
|
881
|
+
return { content: [{ type: "text", text: text || "Code action made no file changes." }], details: result };
|
|
882
|
+
},
|
|
883
|
+
renderCall(args, theme, context) {
|
|
884
|
+
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
885
|
+
text.setText(formatCodeActionApplyCall(args, theme));
|
|
886
|
+
return text;
|
|
887
|
+
},
|
|
888
|
+
renderResult(result, { isPartial }, theme, context) {
|
|
889
|
+
if (isPartial) return new Text(theme.fg("warning", "Applying code action..."), 0, 0);
|
|
890
|
+
if (context.isError) {
|
|
891
|
+
const errorText = result.content
|
|
892
|
+
.filter((block) => block.type === "text")
|
|
893
|
+
.map((block) => block.text)
|
|
894
|
+
.join("\n");
|
|
895
|
+
return new Text(theme.fg("error", errorText || "code_action_apply failed"), 0, 0);
|
|
896
|
+
}
|
|
897
|
+
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
898
|
+
text.setText(formatCodeActionApplyResult(result.details, theme));
|
|
899
|
+
return text;
|
|
900
|
+
},
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
registerLectorTool({
|
|
904
|
+
name: "diagnostic_delta",
|
|
905
|
+
label: "Diagnostic Delta",
|
|
906
|
+
description:
|
|
907
|
+
"Return only diagnostics introduced, resolved, or changed by one atomic mutation transaction or git-ref diff across its bounded affected-file cone, with completeness and provenance. Transaction results include the exact safe revert operation.",
|
|
908
|
+
promptSnippet: "Inspect diagnostic changes caused by a transaction or git diff",
|
|
909
|
+
promptGuidelines: [
|
|
910
|
+
"Use source=transaction with the id returned by an atomic mutation, or source=git with a baseline ref; unchanged diagnostics are omitted.",
|
|
911
|
+
],
|
|
912
|
+
parameters: Type.Object({
|
|
913
|
+
path: Type.String({ description: "Project directory or a path inside it" }),
|
|
914
|
+
source: Type.String({ description: "transaction | git" }),
|
|
915
|
+
sourceId: Type.String({ description: "Mutation transaction id or git baseline ref" }),
|
|
916
|
+
maxResults: Type.Optional(Type.Number({ description: "Maximum results per delta class" })),
|
|
917
|
+
maxBytes: Type.Optional(Type.Number({ description: "Maximum response JSON bytes" })),
|
|
918
|
+
maxDepth: Type.Optional(Type.Number({ description: "Git source: maximum impact depth" })),
|
|
919
|
+
maxNodes: Type.Optional(Type.Number({ description: "Git source: maximum graph nodes" })),
|
|
920
|
+
maxEdges: Type.Optional(Type.Number({ description: "Git source: maximum graph edges" })),
|
|
921
|
+
deadlineMs: Type.Optional(Type.Number({ description: "Git source: wall-clock deadline" })),
|
|
922
|
+
maxFiles: Type.Optional(Type.Number({ description: "Git source: maximum graph files" })),
|
|
923
|
+
maxSymbolsPerFile: Type.Optional(Type.Number({ description: "Git source: maximum declarations per file" })),
|
|
924
|
+
autoPopulate: Type.Optional(Type.Boolean({ description: "Git source: populate a missing/stale graph" })),
|
|
925
|
+
}),
|
|
926
|
+
async execute(_toolCallId, params): Promise<AgentToolResult<OperationOutputs["workspace.diagnosticDelta"]>> {
|
|
927
|
+
const source =
|
|
928
|
+
params.source === "transaction"
|
|
929
|
+
? ({ kind: "transaction", transactionId: params.sourceId } as const)
|
|
930
|
+
: params.source === "git"
|
|
931
|
+
? ({ kind: "git", ref: params.sourceId } as const)
|
|
932
|
+
: undefined;
|
|
933
|
+
if (!source) throw new TypeError("diagnostic_delta source must be transaction or git");
|
|
934
|
+
const result = await codeIntelligenceOperations.diagnosticDelta(resolve(cwd, params.path), source, {
|
|
935
|
+
maxResults: params.maxResults,
|
|
936
|
+
maxBytes: params.maxBytes,
|
|
937
|
+
maxDepth: params.maxDepth,
|
|
938
|
+
maxNodes: params.maxNodes,
|
|
939
|
+
maxEdges: params.maxEdges,
|
|
940
|
+
deadlineMs: params.deadlineMs,
|
|
941
|
+
maxFiles: params.maxFiles,
|
|
942
|
+
maxSymbolsPerFile: params.maxSymbolsPerFile,
|
|
943
|
+
autoPopulate: params.autoPopulate,
|
|
944
|
+
});
|
|
945
|
+
const text = [
|
|
946
|
+
...result.introduced.map(
|
|
947
|
+
(diagnostic) => `introduced ${diagnostic.severity} ${diagnostic.range.path}:${diagnostic.range.start.line} -- ${diagnostic.message}`,
|
|
948
|
+
),
|
|
949
|
+
...result.resolved.map(
|
|
950
|
+
(diagnostic) => `resolved ${diagnostic.severity} ${diagnostic.range.path}:${diagnostic.range.start.line} -- ${diagnostic.message}`,
|
|
951
|
+
),
|
|
952
|
+
...result.changed.map(({ before, after }) => `changed ${after.range.path}:${after.range.start.line} -- ${before.message} -> ${after.message}`),
|
|
953
|
+
].join("\n");
|
|
954
|
+
return { content: [{ type: "text", text: text || "No diagnostic changes." }], details: result };
|
|
955
|
+
},
|
|
956
|
+
renderCall(args, theme, context) {
|
|
957
|
+
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
958
|
+
text.setText(formatDiagnosticDeltaCall(args, theme));
|
|
959
|
+
return text;
|
|
960
|
+
},
|
|
961
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
962
|
+
if (isPartial) return new Text(theme.fg("warning", "Comparing diagnostics..."), 0, 0);
|
|
963
|
+
if (context.isError) {
|
|
964
|
+
const errorText = result.content
|
|
965
|
+
.filter((block) => block.type === "text")
|
|
966
|
+
.map((block) => block.text)
|
|
967
|
+
.join("\n");
|
|
968
|
+
return new Text(theme.fg("error", errorText || "diagnostic_delta failed"), 0, 0);
|
|
969
|
+
}
|
|
970
|
+
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
971
|
+
text.setText(formatDiagnosticDeltaResult(result.details, expanded, theme));
|
|
972
|
+
return text;
|
|
973
|
+
},
|
|
974
|
+
});
|
|
975
|
+
|
|
777
976
|
registerLectorTool({
|
|
778
977
|
name: "call_hierarchy",
|
|
779
978
|
label: "Call Hierarchy",
|
|
@@ -840,6 +1039,137 @@ export default function (pi: ExtensionAPI) {
|
|
|
840
1039
|
},
|
|
841
1040
|
});
|
|
842
1041
|
|
|
1042
|
+
registerLectorTool({
|
|
1043
|
+
name: "type_hierarchy",
|
|
1044
|
+
label: "Type Hierarchy",
|
|
1045
|
+
description:
|
|
1046
|
+
"Resolve a type at an exact file position, or list its direct supertypes or subtypes. ACTIONS: prepare, supertypes, subtypes. Capability-unavailable is reported distinctly from an empty hierarchy.",
|
|
1047
|
+
promptSnippet: "Resolve a type hierarchy at an exact position",
|
|
1048
|
+
promptGuidelines: ["Use supertypes/subtypes instead of grepping for extends or implements; the language server resolves semantic relationships."],
|
|
1049
|
+
parameters: Type.Object({
|
|
1050
|
+
direction: Type.String({ description: "prepare | supertypes | subtypes" }),
|
|
1051
|
+
...positionParameters,
|
|
1052
|
+
maxResults: Type.Optional(Type.Number({ description: "Maximum hierarchy items (default 1000)" })),
|
|
1053
|
+
maxBytes: Type.Optional(Type.Number({ description: "Maximum JSON bytes (default 1 MiB)" })),
|
|
1054
|
+
deadlineMs: Type.Optional(Type.Number({ description: "Wall-clock deadline in milliseconds (default 10000, maximum 120000)" })),
|
|
1055
|
+
}),
|
|
1056
|
+
async execute(_toolCallId, params): Promise<AgentToolResult<OperationOutputs["workspace.prepareTypeHierarchy"]>> {
|
|
1057
|
+
const path = resolve(cwd, params.path);
|
|
1058
|
+
const bounds = { maxResults: params.maxResults, maxBytes: params.maxBytes, deadlineMs: params.deadlineMs };
|
|
1059
|
+
const result =
|
|
1060
|
+
params.direction === "prepare"
|
|
1061
|
+
? await codeIntelligenceOperations.prepareTypeHierarchy(path, params.line, params.character, bounds)
|
|
1062
|
+
: params.direction === "supertypes"
|
|
1063
|
+
? await codeIntelligenceOperations.supertypes(path, params.line, params.character, bounds)
|
|
1064
|
+
: params.direction === "subtypes"
|
|
1065
|
+
? await codeIntelligenceOperations.subtypes(path, params.line, params.character, bounds)
|
|
1066
|
+
: undefined;
|
|
1067
|
+
if (!result) throw new Error(`unknown type_hierarchy direction: ${String(params.direction)}`);
|
|
1068
|
+
const text =
|
|
1069
|
+
result.items.length === 0
|
|
1070
|
+
? "No type-hierarchy items found."
|
|
1071
|
+
: result.items.map((item) => `${item.kind} ${item.name} -- ${item.location.path}:${item.location.line}:${item.location.character}`).join("\n");
|
|
1072
|
+
return { content: [{ type: "text", text: `${describeIntelligenceSource(result.provenance)}\n${text}` }], details: result };
|
|
1073
|
+
},
|
|
1074
|
+
renderCall(args, theme, context) {
|
|
1075
|
+
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1076
|
+
text.setText(formatTypeHierarchyCall(args, theme));
|
|
1077
|
+
return text;
|
|
1078
|
+
},
|
|
1079
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
1080
|
+
if (isPartial) return new Text(theme.fg("warning", "Resolving type hierarchy..."), 0, 0);
|
|
1081
|
+
if (context.isError) {
|
|
1082
|
+
const errorText = result.content
|
|
1083
|
+
.filter((block) => block.type === "text")
|
|
1084
|
+
.map((block) => block.text)
|
|
1085
|
+
.join("\n");
|
|
1086
|
+
return new Text(theme.fg("error", errorText || "type_hierarchy failed"), 0, 0);
|
|
1087
|
+
}
|
|
1088
|
+
const details = result.details;
|
|
1089
|
+
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1090
|
+
text.setText(renderIntelligenceSource(formatTypeHierarchyResult(details, expanded, theme), details.provenance, theme));
|
|
1091
|
+
return text;
|
|
1092
|
+
},
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
registerLectorTool({
|
|
1096
|
+
name: "impact_analysis",
|
|
1097
|
+
label: "Impact Analysis",
|
|
1098
|
+
description:
|
|
1099
|
+
"Map a git diff or mutation transaction to changed declarations, semantic callers/references, package boundaries, diagnostics, and related tests. Associations preserve semantic-edge versus filename-heuristic evidence.",
|
|
1100
|
+
promptSnippet: "Find symbols and tests affected by a change",
|
|
1101
|
+
promptGuidelines: [
|
|
1102
|
+
"Use after a mutation or against a git ref to choose targeted verification from explicit evidence rather than filename guesses alone.",
|
|
1103
|
+
],
|
|
1104
|
+
parameters: Type.Object({
|
|
1105
|
+
path: Type.String({ description: "Project directory or a path inside it" }),
|
|
1106
|
+
source: Type.String({ description: "git | mutation" }),
|
|
1107
|
+
ref: Type.Optional(Type.String({ description: "Git ref for source=git" })),
|
|
1108
|
+
transactionId: Type.Optional(Type.String({ description: "Mutation transaction id for source=mutation" })),
|
|
1109
|
+
maxDepth: Type.Number({ description: "Maximum reverse-graph hops" }),
|
|
1110
|
+
maxNodes: Type.Number({ description: "Maximum graph nodes" }),
|
|
1111
|
+
maxEdges: Type.Number({ description: "Maximum graph edges" }),
|
|
1112
|
+
maxBytes: Type.Number({ description: "Maximum response JSON bytes" }),
|
|
1113
|
+
deadlineMs: Type.Number({ description: "Wall-clock deadline in milliseconds" }),
|
|
1114
|
+
maxFiles: Type.Number({ description: "Maximum source files for graph freshness/population" }),
|
|
1115
|
+
maxSymbolsPerFile: Type.Number({ description: "Maximum declarations per source file" }),
|
|
1116
|
+
autoPopulate: Type.Optional(Type.Boolean({ description: "Populate once when no complete graph exists" })),
|
|
1117
|
+
coverage: Type.Optional(
|
|
1118
|
+
Type.Array(
|
|
1119
|
+
Type.Object({
|
|
1120
|
+
testPath: Type.String(),
|
|
1121
|
+
coveredPaths: Type.Array(Type.String(), { maxItems: 1000 }),
|
|
1122
|
+
}),
|
|
1123
|
+
{ maxItems: 1000, description: "Optional test-to-covered-source evidence" },
|
|
1124
|
+
),
|
|
1125
|
+
),
|
|
1126
|
+
}),
|
|
1127
|
+
async execute(_toolCallId, params): Promise<AgentToolResult<OperationOutputs["workspace.impactAnalysis"]>> {
|
|
1128
|
+
const source =
|
|
1129
|
+
params.source === "git"
|
|
1130
|
+
? ({ kind: "git", ...(params.ref !== undefined ? { ref: params.ref } : {}) } as const)
|
|
1131
|
+
: params.source === "mutation" && params.transactionId
|
|
1132
|
+
? ({ kind: "mutation", transactionId: params.transactionId } as const)
|
|
1133
|
+
: undefined;
|
|
1134
|
+
if (!source) throw new TypeError("impact_analysis requires source=git or source=mutation with transactionId");
|
|
1135
|
+
const result = await codeIntelligenceOperations.impactAnalysis(resolve(cwd, params.path), source, {
|
|
1136
|
+
maxDepth: params.maxDepth,
|
|
1137
|
+
maxNodes: params.maxNodes,
|
|
1138
|
+
maxEdges: params.maxEdges,
|
|
1139
|
+
maxBytes: params.maxBytes,
|
|
1140
|
+
deadlineMs: params.deadlineMs,
|
|
1141
|
+
maxFiles: params.maxFiles,
|
|
1142
|
+
maxSymbolsPerFile: params.maxSymbolsPerFile,
|
|
1143
|
+
...(params.autoPopulate !== undefined ? { autoPopulate: params.autoPopulate } : {}),
|
|
1144
|
+
...(params.coverage !== undefined ? { coverage: params.coverage } : {}),
|
|
1145
|
+
});
|
|
1146
|
+
const text = [
|
|
1147
|
+
...result.changedSymbols.map(({ symbol, side }) => `changed ${side} ${symbol.kind} ${symbol.name} -- ${symbol.location.path}`),
|
|
1148
|
+
...result.impactedSymbols.map(({ symbol, depth }) => `impact depth=${depth} ${symbol.kind} ${symbol.name} -- ${symbol.location.path}`),
|
|
1149
|
+
...result.relatedTests.map(({ symbol, evidence }) => `test ${evidence.kind} -- ${symbol.location.path}`),
|
|
1150
|
+
].join("\n");
|
|
1151
|
+
return { content: [{ type: "text", text: text || "No changed symbols resolved." }], details: result };
|
|
1152
|
+
},
|
|
1153
|
+
renderCall(args, theme, context) {
|
|
1154
|
+
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1155
|
+
text.setText(formatImpactAnalysisCall(args, theme));
|
|
1156
|
+
return text;
|
|
1157
|
+
},
|
|
1158
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
1159
|
+
if (isPartial) return new Text(theme.fg("warning", "Analyzing impact..."), 0, 0);
|
|
1160
|
+
if (context.isError) {
|
|
1161
|
+
const errorText = result.content
|
|
1162
|
+
.filter((block) => block.type === "text")
|
|
1163
|
+
.map((block) => block.text)
|
|
1164
|
+
.join("\n");
|
|
1165
|
+
return new Text(theme.fg("error", errorText || "impact_analysis failed"), 0, 0);
|
|
1166
|
+
}
|
|
1167
|
+
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1168
|
+
text.setText(formatImpactAnalysisResult(result.details, expanded, theme));
|
|
1169
|
+
return text;
|
|
1170
|
+
},
|
|
1171
|
+
});
|
|
1172
|
+
|
|
843
1173
|
registerLectorTool({
|
|
844
1174
|
name: "reference_based_rename",
|
|
845
1175
|
label: "Reference-Based Rename",
|
|
@@ -874,7 +1204,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
874
1204
|
const toPath = typeof args.toPath === "string" ? args.toPath : "";
|
|
875
1205
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
876
1206
|
text.setText(
|
|
877
|
-
`${theme.fg("toolTitle", theme.bold("reference_based_rename"))} ${theme.fg("accent", fromPath)} ${theme.fg("dim", "->")} ${theme.fg("accent", toPath)}`,
|
|
1207
|
+
`${theme.fg("toolTitle", theme.bold(presentationTitle("reference_based_rename")))} ${theme.fg("accent", fromPath)} ${theme.fg("dim", "->")} ${theme.fg("accent", toPath)}`,
|
|
878
1208
|
);
|
|
879
1209
|
return text;
|
|
880
1210
|
},
|
|
@@ -939,7 +1269,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
939
1269
|
const action = typeof args.action === "string" ? args.action : "";
|
|
940
1270
|
const path = typeof args.path === "string" ? args.path : "";
|
|
941
1271
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
942
|
-
text.setText(`${theme.fg("toolTitle", theme.bold("rename"
|
|
1272
|
+
text.setText(`${theme.fg("toolTitle", theme.bold(presentationTitle("rename", action)))} ${theme.fg("accent", path)}`);
|
|
943
1273
|
return text;
|
|
944
1274
|
},
|
|
945
1275
|
renderResult(result, { isPartial }, theme, context) {
|
|
@@ -1012,6 +1342,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1012
1342
|
childId: Type.Optional(Type.String({ description: "The contained annotation's id -- required for contain/uncontain" })),
|
|
1013
1343
|
rootId: Type.Optional(Type.String({ description: "The subtree's root annotation id -- required for tree" })),
|
|
1014
1344
|
maxDepth: Type.Optional(Type.Number({ description: "Maximum containment hops from rootId to include -- required for tree" })),
|
|
1345
|
+
autoPopulate: Type.Optional(Type.Boolean({ description: "For create/refresh: populate a genuinely not-cached graph once before resolving anchors" })),
|
|
1346
|
+
maxFiles: Type.Optional(Type.Number({ description: "For create/refresh autoPopulate: explicit maximum files to scan" })),
|
|
1347
|
+
maxSymbolsPerFile: Type.Optional(Type.Number({ description: "For create/refresh autoPopulate: explicit maximum declarations per file" })),
|
|
1015
1348
|
}),
|
|
1016
1349
|
async execute(toolCallId, params, signal, _onUpdate, ctx): Promise<AgentToolResult<SymbolAnnotationToolDetails>> {
|
|
1017
1350
|
const path = resolve(cwd, params.path);
|
|
@@ -1034,6 +1367,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1034
1367
|
params.body,
|
|
1035
1368
|
resolveAnchorInputs(params.anchors),
|
|
1036
1369
|
vehicleCall,
|
|
1370
|
+
{
|
|
1371
|
+
...(params.autoPopulate !== undefined ? { autoPopulate: params.autoPopulate } : {}),
|
|
1372
|
+
...(params.maxFiles !== undefined ? { maxFiles: params.maxFiles } : {}),
|
|
1373
|
+
...(params.maxSymbolsPerFile !== undefined ? { maxSymbolsPerFile: params.maxSymbolsPerFile } : {}),
|
|
1374
|
+
},
|
|
1037
1375
|
);
|
|
1038
1376
|
details.annotation = annotation;
|
|
1039
1377
|
text = formatAnnotationDetail(annotation);
|
|
@@ -1063,6 +1401,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1063
1401
|
params.body,
|
|
1064
1402
|
resolveAnchorInputs(params.anchors),
|
|
1065
1403
|
vehicleCall,
|
|
1404
|
+
{
|
|
1405
|
+
...(params.autoPopulate !== undefined ? { autoPopulate: params.autoPopulate } : {}),
|
|
1406
|
+
...(params.maxFiles !== undefined ? { maxFiles: params.maxFiles } : {}),
|
|
1407
|
+
...(params.maxSymbolsPerFile !== undefined ? { maxSymbolsPerFile: params.maxSymbolsPerFile } : {}),
|
|
1408
|
+
},
|
|
1066
1409
|
);
|
|
1067
1410
|
details.annotation = annotation;
|
|
1068
1411
|
text = annotation ? formatAnnotationDetail(annotation) : `no annotation "${params.id}"`;
|
|
@@ -1107,10 +1450,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1107
1450
|
? ` ${args.rootId}`
|
|
1108
1451
|
: "";
|
|
1109
1452
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1110
|
-
text.setText(`${theme.fg("toolTitle", theme.bold("symbol_annotations"
|
|
1453
|
+
text.setText(`${theme.fg("toolTitle", theme.bold(presentationTitle("symbol_annotations", action)))}${theme.fg("accent", id)}`);
|
|
1111
1454
|
return text;
|
|
1112
1455
|
},
|
|
1113
|
-
renderResult(result, { isPartial }, theme, context) {
|
|
1456
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
1114
1457
|
if (isPartial) return new Text(theme.fg("warning", "Working on annotation..."), 0, 0);
|
|
1115
1458
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1116
1459
|
if (context.isError) {
|
|
@@ -1123,11 +1466,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1123
1466
|
}
|
|
1124
1467
|
const details = result.details as SymbolAnnotationToolDetails | undefined;
|
|
1125
1468
|
if (details?.annotations) {
|
|
1126
|
-
text.setText(formatAnnotationListSummary(details.annotations, theme));
|
|
1469
|
+
text.setText(expanded ? details.annotations.map(formatAnnotationDetail).join("\n\n") : formatAnnotationListSummary(details.annotations, theme));
|
|
1127
1470
|
return text;
|
|
1128
1471
|
}
|
|
1129
1472
|
if (details?.annotation) {
|
|
1130
|
-
text.setText(formatAnnotationSummary(details.annotation, theme));
|
|
1473
|
+
text.setText(expanded ? formatAnnotationDetail(details.annotation) : formatAnnotationSummary(details.annotation, theme));
|
|
1131
1474
|
return text;
|
|
1132
1475
|
}
|
|
1133
1476
|
if (details?.scrubbed !== undefined) {
|
|
@@ -1169,10 +1512,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
1169
1512
|
description: "Restrict to one edge kind; omit for any kind",
|
|
1170
1513
|
}),
|
|
1171
1514
|
),
|
|
1515
|
+
autoPopulate: Type.Optional(Type.Boolean({ description: "Populate a genuinely not-cached graph once before traversing" })),
|
|
1516
|
+
maxFiles: Type.Optional(Type.Number({ description: "For autoPopulate: explicit maximum files to scan" })),
|
|
1517
|
+
maxSymbolsPerFile: Type.Optional(Type.Number({ description: "For autoPopulate: explicit maximum declarations per file" })),
|
|
1172
1518
|
}),
|
|
1173
1519
|
async execute(_toolCallId, params) {
|
|
1174
1520
|
const path = resolve(cwd, params.path);
|
|
1175
|
-
const symbols = await codeIntelligenceOperations.reachableFrom(path, params.line, params.character, params.maxDepth, params.kind
|
|
1521
|
+
const symbols = await codeIntelligenceOperations.reachableFrom(path, params.line, params.character, params.maxDepth, params.kind, {
|
|
1522
|
+
...(params.autoPopulate !== undefined ? { autoPopulate: params.autoPopulate } : {}),
|
|
1523
|
+
...(params.maxFiles !== undefined ? { maxFiles: params.maxFiles } : {}),
|
|
1524
|
+
...(params.maxSymbolsPerFile !== undefined ? { maxSymbolsPerFile: params.maxSymbolsPerFile } : {}),
|
|
1525
|
+
});
|
|
1176
1526
|
const text =
|
|
1177
1527
|
symbols.length === 0
|
|
1178
1528
|
? "Nothing reachable at this position."
|
|
@@ -1292,7 +1642,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1292
1642
|
if (params.action === "job_status") {
|
|
1293
1643
|
if (!params.jobId) throw new Error("workspace_cache action=job_status requires jobId");
|
|
1294
1644
|
const job = await cacheOperations.jobStatus(params.jobId);
|
|
1295
|
-
return {
|
|
1645
|
+
return {
|
|
1646
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("workspace_cache", "job_status"), job) }],
|
|
1647
|
+
details: { action: "job_status", job },
|
|
1648
|
+
};
|
|
1296
1649
|
}
|
|
1297
1650
|
if (params.action === "wait") {
|
|
1298
1651
|
if (!params.jobId) throw new Error("workspace_cache action=wait requires jobId");
|
|
@@ -1314,7 +1667,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1314
1667
|
);
|
|
1315
1668
|
}
|
|
1316
1669
|
const job = outcome.kind === "terminal" ? outcome.job : await cacheOperations.jobStatus(params.jobId);
|
|
1317
|
-
return {
|
|
1670
|
+
return {
|
|
1671
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("workspace_cache", "wait"), job) }],
|
|
1672
|
+
details: { action: "wait", job },
|
|
1673
|
+
};
|
|
1318
1674
|
}
|
|
1319
1675
|
if (!params.directory) throw new Error(`workspace_cache action=${params.action} requires directory`);
|
|
1320
1676
|
const directory = resolve(cwd, params.directory);
|
|
@@ -1322,10 +1678,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
1322
1678
|
const maxSymbolsPerFile = params.maxSymbolsPerFile ?? 100;
|
|
1323
1679
|
if (params.action === "status") {
|
|
1324
1680
|
const status = await cacheOperations.status(directory, maxFiles, maxSymbolsPerFile);
|
|
1325
|
-
return {
|
|
1681
|
+
return {
|
|
1682
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("workspace_cache", "status"), status) }],
|
|
1683
|
+
details: { action: "status", status },
|
|
1684
|
+
};
|
|
1326
1685
|
}
|
|
1327
1686
|
const job = await cacheOperations.submit(directory, maxFiles, maxSymbolsPerFile, params.waitMs ?? 3_000);
|
|
1328
|
-
return {
|
|
1687
|
+
return {
|
|
1688
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("workspace_cache", "populate"), job) }],
|
|
1689
|
+
details: { action: "populate", job },
|
|
1690
|
+
};
|
|
1329
1691
|
},
|
|
1330
1692
|
renderCall(args, theme, context) {
|
|
1331
1693
|
const action = args.action === "populate" || args.action === "wait" || args.action === "job_status" ? args.action : "status";
|
|
@@ -1354,18 +1716,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
1354
1716
|
name: "git",
|
|
1355
1717
|
label: "Git",
|
|
1356
1718
|
description:
|
|
1357
|
-
"Working tree status, recent commit log, unified diff, one symbol's own declaration diff across two versions, ref-scoped blob/text/ancestry queries with no checkout, and a real disposable checkout at another ref, for a real git repository, in one tool. Fails clearly if `directory` is not inside a git repository. ACTIONS: status (working tree state, ahead/behind tracking), log (recent commits, bounded by maxCount), diff (unified diff against `ref`, defaulting to HEAD, bounded by maxBytes), compare-symbol (a named symbol's own declaration text diffed between fromRef and toRef, or fromRef and the current working tree when toRef is omitted -- tree-sitter syntactic tier only, TypeScript/JavaScript files only, no project-aware cross-reference resolution), show (a path's exact blob content at `ref`, no checkout), grep-ref (text search across `ref`'s own tree, no checkout -- the ref-scoped equivalent of search_code), ls-ref (every file path in `ref`'s own tree, no checkout), is-ancestor (is `ancestorRef` a real ancestor of, or the same commit as, `ref` -- the backport/reachability check \"was this fix ported to this branch\" actually needs), worktree-add (materializes `ref` as a real, read-only project via a detached git worktree and returns its own `directory` -- pass that straight to find_symbols/search_code/this tool itself for full semantic queries against another branch/commit, not just text), worktree-remove (releases and deletes a worktree-add-created checkout -- `directory` is that checkout's own returned directory, not the source repo's).",
|
|
1719
|
+
"Working tree status, recent commit log, unified diff, one symbol's own declaration diff across two versions, ref-scoped blob/text/ancestry queries with no checkout, and a real disposable checkout at another ref, for a real git repository, in one tool. Fails clearly if `directory` is not inside a git repository. ACTIONS: status (working tree state, ahead/behind tracking), log (recent commits, bounded by maxCount), diff (unified diff against `ref`, defaulting to HEAD, bounded by maxBytes), compare-symbol (a named symbol's own declaration text diffed between fromRef and toRef, or fromRef and the current working tree when toRef is omitted -- tree-sitter syntactic tier only, TypeScript/JavaScript files only, no project-aware cross-reference resolution), show (a path's exact blob content at `ref`, no checkout), grep-ref (text search across `ref`'s own tree, no checkout -- the ref-scoped equivalent of search_code), grep-history (bounded extended-regex search across commit trees reachable from all refs, with topological paging and commit provenance), ls-ref (every file path in `ref`'s own tree, no checkout), is-ancestor (is `ancestorRef` a real ancestor of, or the same commit as, `ref` -- the backport/reachability check \"was this fix ported to this branch\" actually needs), worktree-add (materializes `ref` as a real, read-only project via a detached git worktree and returns its own `directory` -- pass that straight to find_symbols/search_code/this tool itself for full semantic queries against another branch/commit, not just text), worktree-remove (releases and deletes a worktree-add-created checkout -- `directory` is that checkout's own returned directory, not the source repo's).",
|
|
1358
1720
|
promptSnippet:
|
|
1359
1721
|
"Show a repository's status, log, diff, one symbol's diff across versions, a ref-scoped blob/text/ancestry query, or a real checkout at another ref",
|
|
1360
1722
|
promptGuidelines: [
|
|
1361
|
-
"maxCount is required for action=log; maxBytes is required for action=diff/compare-symbol/grep-ref -- every bounded query needs its bound stated explicitly, never defaulted silently.",
|
|
1723
|
+
"maxCount is required for action=log; maxBytes is required for action=diff/compare-symbol/grep-ref/grep-history -- every bounded query needs its bound stated explicitly, never defaulted silently.",
|
|
1362
1724
|
"path, symbol, and fromRef are required for action=compare-symbol; toRef is optional and means 'the current working tree' when omitted.",
|
|
1363
1725
|
"ref is required for action=worktree-add. A repeated worktree-add for the same (directory, ref) reuses the existing checkout unless forceRefresh is set -- use that when ref is a branch that may have moved.",
|
|
1364
1726
|
"action=worktree-remove's directory is worktree-add's own returned directory, never the source repo's -- always call it once done with a worktree to reclaim disk.",
|
|
1365
|
-
"ref and path are required for action=show; ref and pattern (maxMatches, maxBytes) are required for action=grep-ref; ref (maxResults) is required for action=ls-ref; ancestorRef and ref are required for action=is-ancestor.
|
|
1727
|
+
"ref and path are required for action=show; ref and pattern (maxMatches, maxBytes) are required for action=grep-ref; pattern, commitOffset, maxCommits, maxMatches, maxBytes, and deadlineMs are required for action=grep-history; ref (maxResults) is required for action=ls-ref; ancestorRef and ref are required for action=is-ancestor. These actions inspect Git objects directly with no checkout.",
|
|
1366
1728
|
],
|
|
1367
1729
|
parameters: Type.Object({
|
|
1368
|
-
action: Type.String({
|
|
1730
|
+
action: Type.String({
|
|
1731
|
+
description: "status | log | diff | compare-symbol | show | grep-ref | grep-history | ls-ref | is-ancestor | worktree-add | worktree-remove",
|
|
1732
|
+
}),
|
|
1369
1733
|
directory: Type.String({ description: "Directory inside the repository to check, absolute or relative to the current working directory" }),
|
|
1370
1734
|
maxCount: Type.Optional(Type.Number({ description: "Maximum number of commits to return, most recent first -- required for action=log" })),
|
|
1371
1735
|
ref: Type.Optional(
|
|
@@ -1376,7 +1740,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1376
1740
|
),
|
|
1377
1741
|
maxBytes: Type.Optional(
|
|
1378
1742
|
Type.Number({
|
|
1379
|
-
|
|
1743
|
+
minimum: 1,
|
|
1744
|
+
maximum: 8 * 1024 * 1024,
|
|
1745
|
+
description: "Maximum diff/comparison/grep output size in bytes before truncating -- required for action=diff/compare-symbol/grep-ref/grep-history",
|
|
1380
1746
|
}),
|
|
1381
1747
|
),
|
|
1382
1748
|
path: Type.Optional(
|
|
@@ -1392,14 +1758,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
1392
1758
|
description: "action=worktree-add only: recreate an already-reused worktree at ref's current tip instead of returning the existing one",
|
|
1393
1759
|
}),
|
|
1394
1760
|
),
|
|
1395
|
-
pattern: Type.Optional(
|
|
1761
|
+
pattern: Type.Optional(
|
|
1762
|
+
Type.String({ maxLength: 4096, description: "Pattern to search for -- required for grep-ref; an extended regular expression for grep-history" }),
|
|
1763
|
+
),
|
|
1396
1764
|
pathspecs: Type.Optional(
|
|
1397
|
-
Type.Array(Type.String(), {
|
|
1765
|
+
Type.Array(Type.String({ minLength: 1, maxLength: 1024 }), {
|
|
1766
|
+
maxItems: 64,
|
|
1398
1767
|
description:
|
|
1399
|
-
'Narrows action=grep-ref (glob-based, e.g. "*.go") or action=ls-ref (prefix-based, e.g. "pkg/dpll"); omitted searches/lists the whole tree',
|
|
1768
|
+
'Narrows action=grep-ref/grep-history (glob-based, e.g. "*.go") or action=ls-ref (prefix-based, e.g. "pkg/dpll"); omitted searches/lists the whole tree',
|
|
1400
1769
|
}),
|
|
1401
1770
|
),
|
|
1402
|
-
maxMatches: Type.Optional(
|
|
1771
|
+
maxMatches: Type.Optional(
|
|
1772
|
+
Type.Number({ minimum: 1, maximum: 10_000, description: "Maximum grep matches to return -- required for action=grep-ref/grep-history" }),
|
|
1773
|
+
),
|
|
1774
|
+
commitOffset: Type.Optional(
|
|
1775
|
+
Type.Number({ minimum: 0, maximum: 1_000_000, description: "Number of topologically ordered commits to skip -- required for action=grep-history" }),
|
|
1776
|
+
),
|
|
1777
|
+
maxCommits: Type.Optional(Type.Number({ minimum: 1, maximum: 512, description: "Maximum commit trees to search -- required for action=grep-history" })),
|
|
1778
|
+
deadlineMs: Type.Optional(Type.Number({ minimum: 1, maximum: 120_000, description: "Wall-clock budget -- required for action=grep-history" })),
|
|
1403
1779
|
maxResults: Type.Optional(Type.Number({ description: "Maximum file paths to return -- required for action=ls-ref" })),
|
|
1404
1780
|
ancestorRef: Type.Optional(Type.String({ description: "The candidate ancestor ref -- required for action=is-ancestor" })),
|
|
1405
1781
|
}),
|
|
@@ -1409,7 +1785,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1409
1785
|
if (params.action === "status") {
|
|
1410
1786
|
const summary = await gitOperations.status(directory, vehicleCall);
|
|
1411
1787
|
const details: GitToolDetails = { action: "status", summary };
|
|
1412
|
-
return { content: [{ type: "text", text:
|
|
1788
|
+
return { content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("git", "status"), summary) }], details };
|
|
1413
1789
|
}
|
|
1414
1790
|
if (params.action === "log") {
|
|
1415
1791
|
if (params.maxCount === undefined) throw new Error("git action=log requires maxCount");
|
|
@@ -1430,18 +1806,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
1430
1806
|
if (params.maxBytes === undefined) throw new Error("git action=compare-symbol requires maxBytes");
|
|
1431
1807
|
const comparison = await gitOperations.compareSymbol(directory, params.path, params.symbol, params.fromRef, params.toRef, params.maxBytes);
|
|
1432
1808
|
const details: GitToolDetails = { action: "compare-symbol", comparison };
|
|
1433
|
-
return { content: [{ type: "text", text:
|
|
1809
|
+
return { content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("git", "compare-symbol"), comparison) }], details };
|
|
1434
1810
|
}
|
|
1435
1811
|
if (params.action === "worktree-add") {
|
|
1436
1812
|
if (!params.ref) throw new Error("git action=worktree-add requires ref");
|
|
1437
1813
|
const worktreeAdd = await gitOperations.worktreeAdd(directory, params.ref, params.forceRefresh, vehicleCall);
|
|
1438
1814
|
const details: GitToolDetails = { action: "worktree-add", worktreeAdd };
|
|
1439
|
-
return { content: [{ type: "text", text:
|
|
1815
|
+
return { content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("git", "worktree-add"), worktreeAdd) }], details };
|
|
1440
1816
|
}
|
|
1441
1817
|
if (params.action === "worktree-remove") {
|
|
1442
1818
|
const worktreeRemove = await gitOperations.worktreeRemove(directory, vehicleCall);
|
|
1443
1819
|
const details: GitToolDetails = { action: "worktree-remove", worktreeRemove };
|
|
1444
|
-
return { content: [{ type: "text", text:
|
|
1820
|
+
return { content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("git", "worktree-remove"), worktreeRemove) }], details };
|
|
1445
1821
|
}
|
|
1446
1822
|
if (params.action === "show") {
|
|
1447
1823
|
if (!params.ref || !params.path) throw new Error("git action=show requires ref and path");
|
|
@@ -1454,20 +1830,46 @@ export default function (pi: ExtensionAPI) {
|
|
|
1454
1830
|
if (params.maxMatches === undefined || params.maxBytes === undefined) throw new Error("git action=grep-ref requires maxMatches and maxBytes");
|
|
1455
1831
|
const grep = await gitOperations.grep(directory, params.ref, params.pattern, params.pathspecs, params.maxMatches, params.maxBytes, vehicleCall);
|
|
1456
1832
|
const details: GitToolDetails = { action: "grep-ref", grep };
|
|
1457
|
-
return { content: [{ type: "text", text:
|
|
1833
|
+
return { content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("git", "grep-ref"), grep) }], details };
|
|
1834
|
+
}
|
|
1835
|
+
if (params.action === "grep-history") {
|
|
1836
|
+
if (!params.pattern) throw new Error("git action=grep-history requires pattern");
|
|
1837
|
+
if (
|
|
1838
|
+
params.commitOffset === undefined ||
|
|
1839
|
+
params.maxCommits === undefined ||
|
|
1840
|
+
params.maxMatches === undefined ||
|
|
1841
|
+
params.maxBytes === undefined ||
|
|
1842
|
+
params.deadlineMs === undefined
|
|
1843
|
+
)
|
|
1844
|
+
throw new Error("git action=grep-history requires commitOffset, maxCommits, maxMatches, maxBytes, and deadlineMs");
|
|
1845
|
+
const historyGrep = await gitOperations.grepHistory(
|
|
1846
|
+
directory,
|
|
1847
|
+
params.pattern,
|
|
1848
|
+
params.pathspecs,
|
|
1849
|
+
{
|
|
1850
|
+
commitOffset: params.commitOffset,
|
|
1851
|
+
maxCommits: params.maxCommits,
|
|
1852
|
+
maxMatches: params.maxMatches,
|
|
1853
|
+
maxBytes: params.maxBytes,
|
|
1854
|
+
deadlineMs: params.deadlineMs,
|
|
1855
|
+
},
|
|
1856
|
+
vehicleCall,
|
|
1857
|
+
);
|
|
1858
|
+
const details: GitToolDetails = { action: "grep-history", historyGrep };
|
|
1859
|
+
return { content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("git", "grep-history"), historyGrep) }], details };
|
|
1458
1860
|
}
|
|
1459
1861
|
if (params.action === "ls-ref") {
|
|
1460
1862
|
if (!params.ref) throw new Error("git action=ls-ref requires ref");
|
|
1461
1863
|
if (params.maxResults === undefined) throw new Error("git action=ls-ref requires maxResults");
|
|
1462
1864
|
const listFiles = await gitOperations.listFiles(directory, params.ref, params.pathspecs, params.maxResults, vehicleCall);
|
|
1463
1865
|
const details: GitToolDetails = { action: "ls-ref", listFiles };
|
|
1464
|
-
return { content: [{ type: "text", text:
|
|
1866
|
+
return { content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("git", "ls-ref"), listFiles) }], details };
|
|
1465
1867
|
}
|
|
1466
1868
|
if (params.action === "is-ancestor") {
|
|
1467
1869
|
if (!params.ancestorRef || !params.ref) throw new Error("git action=is-ancestor requires ancestorRef and ref");
|
|
1468
1870
|
const result = await gitOperations.isAncestor(directory, params.ancestorRef, params.ref, vehicleCall);
|
|
1469
1871
|
const details: GitToolDetails = { action: "is-ancestor", isAncestor: { ancestorRef: params.ancestorRef, ref: params.ref, result } };
|
|
1470
|
-
return { content: [{ type: "text", text:
|
|
1872
|
+
return { content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("git", "is-ancestor"), { isAncestor: result }) }], details };
|
|
1471
1873
|
}
|
|
1472
1874
|
throw new Error(`unknown git action: ${String(params.action)}`);
|
|
1473
1875
|
},
|
|
@@ -1497,7 +1899,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1497
1899
|
name: "search_code",
|
|
1498
1900
|
label: "Search Code",
|
|
1499
1901
|
description:
|
|
1500
|
-
"Multi-file text/regex search scoped to a real project directory
|
|
1902
|
+
"Multi-file text/regex search scoped to a real project directory. Uses a bounded resident FFF index when ready and explicit ripgrep loading/stale/degraded fallback otherwise; respects ignore rules and skips generated dependency output. Bounded by maxMatches and maxBytes; results include lexical provenance.",
|
|
1501
1903
|
promptSnippet: "Search a project's files for a pattern",
|
|
1502
1904
|
parameters: Type.Object({
|
|
1503
1905
|
directory: Type.String({ description: "Directory inside the project to search, absolute or relative to the current working directory" }),
|
|
@@ -1508,8 +1910,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1508
1910
|
async execute(_toolCallId, params) {
|
|
1509
1911
|
const directory = resolve(cwd, params.directory);
|
|
1510
1912
|
const result = await searchOperations.search(params.query, directory, params.maxMatches, params.maxBytes);
|
|
1511
|
-
const
|
|
1913
|
+
const matches =
|
|
1512
1914
|
result.matches.length === 0 ? "No matches found." : result.matches.map((m) => `${m.path}:${m.lineNumber}: ${m.line.replace(/\n$/, "")}`).join("\n");
|
|
1915
|
+
const source = result.provenance ? `lexical via ${result.provenance.backend} (${result.provenance.indexState})\n` : "";
|
|
1916
|
+
const text = `${source}${matches}`;
|
|
1513
1917
|
return { content: [{ type: "text", text }], details: { result } };
|
|
1514
1918
|
},
|
|
1515
1919
|
renderCall(args, theme, context) {
|
|
@@ -1751,7 +2155,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1751
2155
|
const action = typeof args.action === "string" ? args.action : "";
|
|
1752
2156
|
const path = typeof args.path === "string" ? args.path : "";
|
|
1753
2157
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1754
|
-
text.setText(`${theme.fg("toolTitle", theme.bold("mutation_history"
|
|
2158
|
+
text.setText(`${theme.fg("toolTitle", theme.bold(presentationTitle("mutation_history", action)))} ${theme.fg("accent", path)}`);
|
|
1755
2159
|
return text;
|
|
1756
2160
|
},
|
|
1757
2161
|
renderResult(result, { isPartial }, theme, context) {
|
|
@@ -1827,7 +2231,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1827
2231
|
maxResults: params.maxResults,
|
|
1828
2232
|
cursor: params.cursor,
|
|
1829
2233
|
});
|
|
1830
|
-
return {
|
|
2234
|
+
return {
|
|
2235
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("package_source", "list"), page) }],
|
|
2236
|
+
details: { action: "list", page },
|
|
2237
|
+
};
|
|
1831
2238
|
}
|
|
1832
2239
|
if (params.action === "remove") {
|
|
1833
2240
|
if (!params.name || !params.resolvedVersion) throw new Error("package_source action=remove requires ecosystem, name, and resolvedVersion");
|
|
@@ -1837,11 +2244,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
1837
2244
|
params.name,
|
|
1838
2245
|
params.resolvedVersion,
|
|
1839
2246
|
);
|
|
1840
|
-
return {
|
|
2247
|
+
return {
|
|
2248
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("package_source", "remove"), result) }],
|
|
2249
|
+
details: { action: "remove", result },
|
|
2250
|
+
};
|
|
1841
2251
|
}
|
|
1842
2252
|
if (params.action === "clean") {
|
|
1843
2253
|
const result = await packageSourceOperations.clean(optionalPackageEcosystem(params.ecosystem));
|
|
1844
|
-
return {
|
|
2254
|
+
return {
|
|
2255
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("package_source", "clean"), result) }],
|
|
2256
|
+
details: { action: "clean", result },
|
|
2257
|
+
};
|
|
1845
2258
|
}
|
|
1846
2259
|
if (!params.directory || !params.name) throw new Error("package_source action=resolve requires directory and name");
|
|
1847
2260
|
const directory = resolve(cwd, params.directory);
|
|
@@ -1852,7 +2265,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1852
2265
|
params.registry ?? null,
|
|
1853
2266
|
optionalPackageEcosystem(params.ecosystem),
|
|
1854
2267
|
);
|
|
1855
|
-
return {
|
|
2268
|
+
return {
|
|
2269
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("package_source", "resolve"), result) }],
|
|
2270
|
+
details: { action: "resolve", result },
|
|
2271
|
+
};
|
|
1856
2272
|
},
|
|
1857
2273
|
renderCall(args, theme, context) {
|
|
1858
2274
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -1949,12 +2365,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
1949
2365
|
params.forceRefresh,
|
|
1950
2366
|
vehicleCall,
|
|
1951
2367
|
);
|
|
1952
|
-
return {
|
|
2368
|
+
return {
|
|
2369
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("repo_cache", "fetch"), result) }],
|
|
2370
|
+
details: { action: "fetch", result },
|
|
2371
|
+
};
|
|
1953
2372
|
}
|
|
1954
2373
|
if (params.action === "evict") {
|
|
1955
2374
|
if (!params.owner || !params.repo) throw new Error("repo_cache action=evict requires owner and repo");
|
|
1956
2375
|
const result = await repoCacheEvictOperations.evict(params.host ?? "github.com", params.owner, params.repo, params.ref ?? null, vehicleCall);
|
|
1957
|
-
return {
|
|
2376
|
+
return {
|
|
2377
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("repo_cache", "evict"), result) }],
|
|
2378
|
+
details: { action: "evict", result },
|
|
2379
|
+
};
|
|
1958
2380
|
}
|
|
1959
2381
|
if (params.maxResults === undefined) throw new Error("repo_cache action=list requires maxResults");
|
|
1960
2382
|
const page = await repoCacheListOperations.list(
|
|
@@ -1963,7 +2385,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1963
2385
|
params.cursor,
|
|
1964
2386
|
vehicleCall,
|
|
1965
2387
|
);
|
|
1966
|
-
return {
|
|
2388
|
+
return {
|
|
2389
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("repo_cache", "list"), page) }],
|
|
2390
|
+
details: { action: "list", page },
|
|
2391
|
+
};
|
|
1967
2392
|
},
|
|
1968
2393
|
renderCall(args, theme, context) {
|
|
1969
2394
|
const action = args.action === "list" || args.action === "evict" ? args.action : "fetch";
|
|
@@ -2036,14 +2461,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
2036
2461
|
};
|
|
2037
2462
|
if (params.action === "github_repos") {
|
|
2038
2463
|
const result = await externalSearchOperations.githubRepos(params.query, maxResults, vehicleCall);
|
|
2039
|
-
return {
|
|
2464
|
+
return {
|
|
2465
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("external_search", "github_repos"), result) }],
|
|
2466
|
+
details: { action: "github_repos", result },
|
|
2467
|
+
};
|
|
2040
2468
|
}
|
|
2041
2469
|
if (params.action === "npm_packages") {
|
|
2042
2470
|
const result = await externalSearchOperations.npmPackages(params.query, maxResults, vehicleCall);
|
|
2043
|
-
return {
|
|
2471
|
+
return {
|
|
2472
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("external_search", "npm_packages"), result) }],
|
|
2473
|
+
details: { action: "npm_packages", result },
|
|
2474
|
+
};
|
|
2044
2475
|
}
|
|
2045
2476
|
const result = await externalSearchOperations.sourcegraphCode(params.query, maxResults, vehicleCall);
|
|
2046
|
-
return {
|
|
2477
|
+
return {
|
|
2478
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("external_search", "sourcegraph_code"), result) }],
|
|
2479
|
+
details: { action: "sourcegraph_code", result },
|
|
2480
|
+
};
|
|
2047
2481
|
},
|
|
2048
2482
|
renderCall(args, theme, context) {
|
|
2049
2483
|
const action = args.action === "npm_packages" || args.action === "sourcegraph_code" ? args.action : "github_repos";
|
|
@@ -2051,7 +2485,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2051
2485
|
text.setText(formatExternalSearchCall(action, args, theme));
|
|
2052
2486
|
return text;
|
|
2053
2487
|
},
|
|
2054
|
-
renderResult(result, { isPartial }, theme, context) {
|
|
2488
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
2055
2489
|
if (isPartial) return new Text(theme.fg("warning", "Searching..."), 0, 0);
|
|
2056
2490
|
if (context.isError) {
|
|
2057
2491
|
const errorText = result.content
|
|
@@ -2062,9 +2496,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
2062
2496
|
}
|
|
2063
2497
|
const details = result.details as ExternalSearchToolDetails | undefined;
|
|
2064
2498
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
2065
|
-
if (details?.action === "npm_packages") text.setText(formatNpmPackageSearchResult(details.result, theme));
|
|
2066
|
-
else if (details?.action === "sourcegraph_code") text.setText(formatSourcegraphCodeSearchResult(details.result, theme));
|
|
2067
|
-
else text.setText(formatGithubRepoSearchResult(details?.action === "github_repos" ? details.result : undefined, theme));
|
|
2499
|
+
if (details?.action === "npm_packages") text.setText(formatNpmPackageSearchResult(details.result, expanded, theme));
|
|
2500
|
+
else if (details?.action === "sourcegraph_code") text.setText(formatSourcegraphCodeSearchResult(details.result, expanded, theme));
|
|
2501
|
+
else text.setText(formatGithubRepoSearchResult(details?.action === "github_repos" ? details.result : undefined, expanded, theme));
|
|
2068
2502
|
return text;
|
|
2069
2503
|
},
|
|
2070
2504
|
});
|
|
@@ -2087,11 +2521,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
2087
2521
|
async execute(_toolCallId, params) {
|
|
2088
2522
|
const directories = params.directories.map((directory) => resolve(cwd, directory));
|
|
2089
2523
|
const results = await crossWorkspaceSearchOperations.findSymbols(params.query, directories, params.timeoutMs, params.maxResults);
|
|
2090
|
-
return {
|
|
2524
|
+
return {
|
|
2525
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("find_symbols_across_projects"), results) }],
|
|
2526
|
+
details: { results },
|
|
2527
|
+
};
|
|
2091
2528
|
},
|
|
2092
2529
|
renderCall(args, theme, context) {
|
|
2093
2530
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
2094
|
-
text.setText(formatCrossWorkspaceCall(args, theme));
|
|
2531
|
+
text.setText(formatCrossWorkspaceCall("find_symbols_across_projects", args, theme));
|
|
2095
2532
|
return text;
|
|
2096
2533
|
},
|
|
2097
2534
|
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
@@ -2114,7 +2551,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2114
2551
|
name: "search_code_across_projects",
|
|
2115
2552
|
label: "Search Code Across Projects",
|
|
2116
2553
|
description:
|
|
2117
|
-
"Fans out
|
|
2554
|
+
"Fans out bounded indexed lexical text/regex search with explicit ripgrep fallback provenance across several explicitly-named project directories at once and reports one outcome per project. Directories are required and explicit -- never every project this daemon happens to have registered, which can include unrelated projects from other concurrent sessions. Each directory resolves to its OWN nearest project root (package.json/tsconfig.json/go.mod/Cargo.toml/...), not the outer repo's git root -- sibling packages under one monorepo stay distinct scopes rather than collapsing into one. A result's collapsedWith lists any other requested directories that genuinely did resolve to the same workspace; empty means it got its own.",
|
|
2118
2555
|
promptSnippet: "Search for a pattern across several projects at once",
|
|
2119
2556
|
parameters: Type.Object({
|
|
2120
2557
|
directories: Type.Array(Type.String(), { description: "Project directories to search, each absolute or relative to the current working directory" }),
|
|
@@ -2126,11 +2563,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
2126
2563
|
async execute(_toolCallId, params) {
|
|
2127
2564
|
const directories = params.directories.map((directory) => resolve(cwd, directory));
|
|
2128
2565
|
const results = await crossWorkspaceSearchOperations.searchText(params.query, directories, params.maxMatches, params.maxBytes, params.timeoutMs);
|
|
2129
|
-
return {
|
|
2566
|
+
return {
|
|
2567
|
+
content: [{ type: "text", text: formatSemanticModelContent(presentationTitle("search_code_across_projects"), results) }],
|
|
2568
|
+
details: { results },
|
|
2569
|
+
};
|
|
2130
2570
|
},
|
|
2131
2571
|
renderCall(args, theme, context) {
|
|
2132
2572
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
2133
|
-
text.setText(formatCrossWorkspaceCall(args, theme));
|
|
2573
|
+
text.setText(formatCrossWorkspaceCall("search_code_across_projects", args, theme));
|
|
2134
2574
|
return text;
|
|
2135
2575
|
},
|
|
2136
2576
|
renderResult(result, { expanded, isPartial }, theme, context) {
|