@nexrall/code-core 1.4.76 → 1.4.78
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/dist/agent/effortCap.d.ts +24 -0
- package/dist/agent/effortCap.d.ts.map +1 -0
- package/dist/agent/effortCap.js +66 -0
- package/dist/agent/goal.d.ts +55 -0
- package/dist/agent/goal.d.ts.map +1 -0
- package/dist/agent/goal.js +120 -0
- package/dist/agent/handoffBrief.d.ts +82 -0
- package/dist/agent/handoffBrief.d.ts.map +1 -0
- package/dist/agent/handoffBrief.js +121 -0
- package/dist/agent/hooks.d.ts +10 -2
- package/dist/agent/hooks.d.ts.map +1 -1
- package/dist/agent/hooks.js +9 -3
- package/dist/agent/modelCatalogue.d.ts +23 -0
- package/dist/agent/modelCatalogue.d.ts.map +1 -1
- package/dist/agent/modelCatalogue.js +28 -0
- package/dist/agent/outputStyles.d.ts +27 -0
- package/dist/agent/outputStyles.d.ts.map +1 -0
- package/dist/agent/outputStyles.js +174 -0
- package/dist/agent/skills.d.ts.map +1 -1
- package/dist/agent/skills.js +21 -1
- package/dist/api/billingLines.d.ts +11 -0
- package/dist/api/billingLines.d.ts.map +1 -0
- package/dist/api/billingLines.js +56 -0
- package/dist/api/client.d.ts +2 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/scheduleLines.d.ts +53 -0
- package/dist/api/scheduleLines.d.ts.map +1 -0
- package/dist/api/scheduleLines.js +150 -0
- package/dist/context/contextFiles.d.ts +81 -0
- package/dist/context/contextFiles.d.ts.map +1 -0
- package/dist/context/contextFiles.js +310 -0
- package/dist/diff/collect.d.ts +25 -0
- package/dist/diff/collect.d.ts.map +1 -0
- package/dist/diff/collect.js +169 -0
- package/dist/diff/diffView.d.ts +57 -0
- package/dist/diff/diffView.d.ts.map +1 -0
- package/dist/diff/diffView.js +231 -0
- package/dist/diff/reviewQueue.d.ts +53 -0
- package/dist/diff/reviewQueue.d.ts.map +1 -0
- package/dist/diff/reviewQueue.js +349 -0
- package/dist/events/recap.d.ts +54 -0
- package/dist/events/recap.d.ts.map +1 -0
- package/dist/events/recap.js +161 -0
- package/dist/events/sessionEvents.d.ts +54 -0
- package/dist/events/sessionEvents.d.ts.map +1 -0
- package/dist/events/sessionEvents.js +324 -0
- package/dist/events/sessionStats.d.ts +89 -0
- package/dist/events/sessionStats.d.ts.map +1 -0
- package/dist/events/sessionStats.js +264 -0
- package/dist/import/claudeImport.d.ts +44 -0
- package/dist/import/claudeImport.d.ts.map +1 -0
- package/dist/import/claudeImport.js +337 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -0
- package/dist/mcp/manager.d.ts +8 -0
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +64 -57
- package/dist/permissions/destructiveTokens.d.ts +1 -0
- package/dist/permissions/destructiveTokens.d.ts.map +1 -1
- package/dist/permissions/destructiveTokens.js +93 -0
- package/dist/permissions/rules.d.ts +12 -0
- package/dist/permissions/rules.d.ts.map +1 -1
- package/dist/permissions/rules.js +89 -7
- package/dist/plugins/configStore.d.ts +43 -0
- package/dist/plugins/configStore.d.ts.map +1 -0
- package/dist/plugins/configStore.js +253 -0
- package/dist/plugins/files.d.ts +10 -0
- package/dist/plugins/files.d.ts.map +1 -0
- package/dist/plugins/files.js +85 -0
- package/dist/plugins/index.d.ts +3 -22
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +30 -40
- package/dist/plugins/resolve.d.ts +18 -0
- package/dist/plugins/resolve.d.ts.map +1 -0
- package/dist/plugins/resolve.js +152 -0
- package/dist/plugins/userConfig.d.ts +90 -0
- package/dist/plugins/userConfig.d.ts.map +1 -0
- package/dist/plugins/userConfig.js +332 -0
- package/dist/plugins/validate.d.ts +14 -0
- package/dist/plugins/validate.d.ts.map +1 -0
- package/dist/plugins/validate.js +222 -0
- package/dist/project/projectCommands.d.ts +33 -0
- package/dist/project/projectCommands.d.ts.map +1 -0
- package/dist/project/projectCommands.js +246 -0
- package/dist/search/conversationTurns.d.ts +53 -0
- package/dist/search/conversationTurns.d.ts.map +1 -0
- package/dist/search/conversationTurns.js +170 -0
- package/dist/search/globalSearch.d.ts +144 -0
- package/dist/search/globalSearch.d.ts.map +1 -0
- package/dist/search/globalSearch.js +350 -0
- package/dist/share/sessionChannel.d.ts +136 -0
- package/dist/share/sessionChannel.d.ts.map +1 -0
- package/dist/share/sessionChannel.js +373 -0
- package/dist/share/sessionShare.d.ts +104 -0
- package/dist/share/sessionShare.d.ts.map +1 -0
- package/dist/share/sessionShare.js +336 -0
- package/dist/ui/configCmd.d.ts +48 -0
- package/dist/ui/configCmd.d.ts.map +1 -0
- package/dist/ui/configCmd.js +146 -0
- package/dist/ui/deployPanel.d.ts +91 -0
- package/dist/ui/deployPanel.d.ts.map +1 -0
- package/dist/ui/deployPanel.js +442 -0
- package/dist/ui/mergeAssistant.d.ts +120 -0
- package/dist/ui/mergeAssistant.d.ts.map +1 -0
- package/dist/ui/mergeAssistant.js +391 -0
- package/dist/ui/notify.d.ts +114 -0
- package/dist/ui/notify.d.ts.map +1 -0
- package/dist/ui/notify.js +235 -0
- package/dist/ui/promptSuggest.d.ts +56 -0
- package/dist/ui/promptSuggest.d.ts.map +1 -0
- package/dist/ui/promptSuggest.js +160 -0
- package/dist/ui/taskGraph.d.ts +44 -0
- package/dist/ui/taskGraph.d.ts.map +1 -0
- package/dist/ui/taskGraph.js +289 -0
- package/dist/util/addDir.d.ts +31 -0
- package/dist/util/addDir.d.ts.map +1 -0
- package/dist/util/addDir.js +124 -0
- package/dist/util/batch.d.ts +16 -0
- package/dist/util/batch.d.ts.map +1 -0
- package/dist/util/batch.js +92 -0
- package/dist/util/cellWidth.d.ts +12 -0
- package/dist/util/cellWidth.d.ts.map +1 -0
- package/dist/util/cellWidth.js +161 -0
- package/dist/util/duration.d.ts +8 -0
- package/dist/util/duration.d.ts.map +1 -0
- package/dist/util/duration.js +50 -0
- package/dist/util/loopJobs.d.ts +67 -0
- package/dist/util/loopJobs.d.ts.map +1 -0
- package/dist/util/loopJobs.js +169 -0
- package/dist/util/settingsFile.d.ts +14 -0
- package/dist/util/settingsFile.d.ts.map +1 -0
- package/dist/util/settingsFile.js +99 -0
- package/dist/util/toolLabel.d.ts +2 -0
- package/dist/util/toolLabel.d.ts.map +1 -0
- package/dist/util/toolLabel.js +17 -0
- package/dist/verify/failureTimeline.d.ts +100 -0
- package/dist/verify/failureTimeline.d.ts.map +1 -0
- package/dist/verify/failureTimeline.js +253 -0
- package/dist/verify/verification.d.ts +75 -0
- package/dist/verify/verification.d.ts.map +1 -0
- package/dist/verify/verification.js +315 -0
- package/package.json +5 -4
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PROMPT_CLIP = exports.MAX_TURNS = exports.RESULT_PREVIEW_LINES = exports.MAX_TURN_LINES = exports.BANG_TRANSCRIPT_PREFIX = void 0;
|
|
4
|
+
exports.flatten = flatten;
|
|
5
|
+
exports.clip = clip;
|
|
6
|
+
exports.blockLines = blockLines;
|
|
7
|
+
exports.startsTurn = startsTurn;
|
|
8
|
+
exports.isSyntheticUserText = isSyntheticUserText;
|
|
9
|
+
exports.turnsFrom = turnsFrom;
|
|
10
|
+
exports.lastPrompt = lastPrompt;
|
|
11
|
+
exports.lastTypedPrompt = lastTypedPrompt;
|
|
12
|
+
// Conversation → turns, the pure half (moved from the CLI's ui/transcriptView.ts,
|
|
13
|
+
// 2026-10-04) so /find searches the SAME turn shape in the VS Code panel; the
|
|
14
|
+
// CLI module re-exports everything.
|
|
15
|
+
const types_1 = require("../types");
|
|
16
|
+
const toolLabel_1 = require("../util/toolLabel");
|
|
17
|
+
/** `!cmd` transcripts are synthesised user messages; this is their prefix (moved
|
|
18
|
+
* here from commands/bangShell.ts so the synthetic-user list has one home). */
|
|
19
|
+
exports.BANG_TRANSCRIPT_PREFIX = 'I ran this command myself in the terminal:';
|
|
20
|
+
/** Lines of a turn's body shown by a jump before the rest is summarised. */
|
|
21
|
+
exports.MAX_TURN_LINES = 80;
|
|
22
|
+
/** Tool-result lines kept inline; the rest is a count. */
|
|
23
|
+
exports.RESULT_PREVIEW_LINES = 3;
|
|
24
|
+
/** Newest turns kept — a 400-turn session does not need all of it in memory. */
|
|
25
|
+
exports.MAX_TURNS = 200;
|
|
26
|
+
/** Prompt length in a turn header / search label. */
|
|
27
|
+
exports.PROMPT_CLIP = 100;
|
|
28
|
+
function flatten(s) {
|
|
29
|
+
return s.replace(/\s+/g, ' ').trim();
|
|
30
|
+
}
|
|
31
|
+
function clip(s, max) {
|
|
32
|
+
return s.length <= max ? s : s.slice(0, max - 1) + '\u2026';
|
|
33
|
+
}
|
|
34
|
+
/** One display line per content block, or null for blocks that are not conversation. */
|
|
35
|
+
function blockLines(m, b) {
|
|
36
|
+
switch (b.type) {
|
|
37
|
+
case 'text': {
|
|
38
|
+
const text = b.text.trimEnd();
|
|
39
|
+
if (!text.trim())
|
|
40
|
+
return null;
|
|
41
|
+
if (m.role === 'user')
|
|
42
|
+
return text.split('\n').map((l) => ' ' + l);
|
|
43
|
+
return text.split('\n');
|
|
44
|
+
}
|
|
45
|
+
case 'tool_use':
|
|
46
|
+
return [' \u26a1 ' + (0, toolLabel_1.toolLabel)(b.name, b.input)];
|
|
47
|
+
case 'tool_result': {
|
|
48
|
+
const body = b.content.replace(/\r/g, '').trimEnd().split('\n');
|
|
49
|
+
const kept = body.slice(0, exports.RESULT_PREVIEW_LINES).map((l) => ' \u2502 ' + clip(l, 200));
|
|
50
|
+
if (body.length > exports.RESULT_PREVIEW_LINES)
|
|
51
|
+
kept.push(` \u2514 \u2026 +${body.length - exports.RESULT_PREVIEW_LINES} line(s)`);
|
|
52
|
+
return kept;
|
|
53
|
+
}
|
|
54
|
+
case 'image':
|
|
55
|
+
return [' [image]'];
|
|
56
|
+
case 'document':
|
|
57
|
+
return [' [document]'];
|
|
58
|
+
default:
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** User turns the CLI synthesised — outputs and notices folded in dressed as
|
|
63
|
+
* user messages. None of these is "what the user asked for". */
|
|
64
|
+
const SYNTHETIC_USER_PREFIXES = [
|
|
65
|
+
'<task-notification>', // core's formatBackgroundNotifications
|
|
66
|
+
exports.BANG_TRANSCRIPT_PREFIX, // a `!cmd` transcript
|
|
67
|
+
'[Message from peer session ', // core's renderPeerMessages
|
|
68
|
+
];
|
|
69
|
+
/** Did this user message start a turn, or is it agent bookkeeping? */
|
|
70
|
+
function startsTurn(m) {
|
|
71
|
+
if (m.role !== 'user')
|
|
72
|
+
return false;
|
|
73
|
+
return m.content.some((b) => b.type === 'text' && !(0, types_1.isRuntimeContextBlock)(b));
|
|
74
|
+
}
|
|
75
|
+
function isSyntheticUserText(text) {
|
|
76
|
+
return SYNTHETIC_USER_PREFIXES.some((p) => text.startsWith(p));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Group the conversation into turns. Content before the first real prompt (the
|
|
80
|
+
* opening context block) is not a turn and is skipped.
|
|
81
|
+
*/
|
|
82
|
+
function turnsFrom(messages, opts = {}) {
|
|
83
|
+
const maxLines = opts.maxLines ?? exports.MAX_TURN_LINES;
|
|
84
|
+
const turns = [];
|
|
85
|
+
let current = null;
|
|
86
|
+
let hasPrompt = false;
|
|
87
|
+
// The block a turn's prompt came from: it is printed as `> prompt` by the header,
|
|
88
|
+
// so repeating it as the first body line would show every question twice.
|
|
89
|
+
let promptBlock = null;
|
|
90
|
+
for (const m of messages) {
|
|
91
|
+
if (startsTurn(m)) {
|
|
92
|
+
if (current)
|
|
93
|
+
turns.push(finish(current, maxLines));
|
|
94
|
+
const firstText = m.content.find((b) => b.type === 'text' && !(0, types_1.isRuntimeContextBlock)(b));
|
|
95
|
+
current = {
|
|
96
|
+
index: turns.length + 1,
|
|
97
|
+
prompt: clip(flatten(firstText?.text ?? ''), exports.PROMPT_CLIP),
|
|
98
|
+
lines: [],
|
|
99
|
+
toolCalls: 0,
|
|
100
|
+
dropped: 0,
|
|
101
|
+
};
|
|
102
|
+
promptBlock = firstText ?? null;
|
|
103
|
+
hasPrompt = true;
|
|
104
|
+
}
|
|
105
|
+
if (!hasPrompt || !current)
|
|
106
|
+
continue;
|
|
107
|
+
for (const b of m.content) {
|
|
108
|
+
if (b === promptBlock) {
|
|
109
|
+
promptBlock = null;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (b.type === 'text' && (0, types_1.isRuntimeContextBlock)(b))
|
|
113
|
+
continue; // runtime context: not conversation
|
|
114
|
+
if (b.type === 'tool_use')
|
|
115
|
+
current.toolCalls++;
|
|
116
|
+
const lines = blockLines(m, b);
|
|
117
|
+
if (lines)
|
|
118
|
+
current.lines.push(...lines);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (current)
|
|
122
|
+
turns.push(finish(current, maxLines));
|
|
123
|
+
const kept = turns.length > (opts.maxTurns ?? exports.MAX_TURNS) ? turns.slice(-(opts.maxTurns ?? exports.MAX_TURNS)) : turns;
|
|
124
|
+
kept.forEach((t, i) => {
|
|
125
|
+
t.index = i + 1;
|
|
126
|
+
});
|
|
127
|
+
return kept;
|
|
128
|
+
}
|
|
129
|
+
function finish(t, maxLines) {
|
|
130
|
+
if (t.lines.length > maxLines) {
|
|
131
|
+
t.dropped = t.lines.length - maxLines;
|
|
132
|
+
t.lines = [...t.lines.slice(0, maxLines), ` \u2514 \u2026 ${t.dropped} more line(s) in this turn`];
|
|
133
|
+
}
|
|
134
|
+
return t;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The most recent real prompt in the conversation — the label a `/cost` turn row carries.
|
|
138
|
+
*
|
|
139
|
+
* Scans BACKWARDS so a mid-turn follow-up (folded in by the loop while the agent works) is
|
|
140
|
+
* what the row reads: that is the last thing the user actually asked for. '' when nothing
|
|
141
|
+
* has been typed yet.
|
|
142
|
+
*/
|
|
143
|
+
function lastPrompt(messages, max = exports.PROMPT_CLIP) {
|
|
144
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
145
|
+
const m = messages[i];
|
|
146
|
+
if (m.role !== 'user')
|
|
147
|
+
continue;
|
|
148
|
+
const b = m.content.find((x) => x.type === 'text' && !(0, types_1.isRuntimeContextBlock)(x));
|
|
149
|
+
if (b && b.type === 'text')
|
|
150
|
+
return clip(flatten(b.text), max);
|
|
151
|
+
}
|
|
152
|
+
return '';
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The last prompt the USER actually typed — skips the synthetic user messages (a `!cmd`
|
|
156
|
+
* transcript, a task notification, a peer message), which are turns but were not typed.
|
|
157
|
+
* The §81 handoff goal is built from this; `lastPrompt` deliberately does not skip them.
|
|
158
|
+
*/
|
|
159
|
+
function lastTypedPrompt(messages, max = exports.PROMPT_CLIP) {
|
|
160
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
161
|
+
const m = messages[i];
|
|
162
|
+
if (m.role !== 'user')
|
|
163
|
+
continue;
|
|
164
|
+
const b = m.content.find((x) => x.type === 'text' && !(0, types_1.isRuntimeContextBlock)(x));
|
|
165
|
+
if (b && b.type === 'text' && !isSyntheticUserText(b.text))
|
|
166
|
+
return clip(flatten(b.text), max);
|
|
167
|
+
}
|
|
168
|
+
return '';
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=conversationTurns.js.map
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { Turn } from './conversationTurns';
|
|
2
|
+
import type { Incident } from '../verify/failureTimeline';
|
|
3
|
+
/**
|
|
4
|
+
* Structurally minimal shapes for the two CLI-UI stores (kept local so core
|
|
5
|
+
* needs no import from the CLI; richer objects pass structurally). The CLI's
|
|
6
|
+
* ToolOutputEntry / AgentNode satisfy these unchanged.
|
|
7
|
+
*/
|
|
8
|
+
export interface ToolOutputEntry {
|
|
9
|
+
/** Monotonic id, 1-based — what `/expand <n>` refers to. */
|
|
10
|
+
id: number;
|
|
11
|
+
name: string;
|
|
12
|
+
/** One-line description of the call (the command, the path …). */
|
|
13
|
+
label: string;
|
|
14
|
+
output: string;
|
|
15
|
+
isError: boolean;
|
|
16
|
+
durationMs: number;
|
|
17
|
+
}
|
|
18
|
+
export interface AgentNode {
|
|
19
|
+
name: string;
|
|
20
|
+
label: string;
|
|
21
|
+
files: string[];
|
|
22
|
+
error?: string;
|
|
23
|
+
report?: string;
|
|
24
|
+
}
|
|
25
|
+
export type Scope = 'files' | 'conversation' | 'tools' | 'git' | 'errors' | 'agents';
|
|
26
|
+
/** Display order — §52 lists files first because that is where a code query usually lives. */
|
|
27
|
+
export declare const SCOPES: readonly Scope[];
|
|
28
|
+
export declare const SCOPE_LABEL: Record<Scope, string>;
|
|
29
|
+
/** Hits printed per scope. The counts line stays complete — only the list is capped. */
|
|
30
|
+
export declare const MAX_PER_SCOPE = 5;
|
|
31
|
+
/** A matched line kept for context, so a hit is recognisable without opening anything. */
|
|
32
|
+
export declare const DETAIL_CLIP = 96;
|
|
33
|
+
export declare const LABEL_CLIP = 72;
|
|
34
|
+
export interface Hit {
|
|
35
|
+
scope: Scope;
|
|
36
|
+
/** Stable identity inside the scope, for the "N of M" note and for ordering. */
|
|
37
|
+
key: string;
|
|
38
|
+
/** What the row shows first: a path, a command, an agent. */
|
|
39
|
+
label: string;
|
|
40
|
+
/** The matching line, when the label itself is not the match. */
|
|
41
|
+
detail?: string;
|
|
42
|
+
/** 0 = best. */
|
|
43
|
+
score: number;
|
|
44
|
+
/** 1-based position in the scope's own listing, so the row can be found again. */
|
|
45
|
+
index: number;
|
|
46
|
+
}
|
|
47
|
+
export interface Sources {
|
|
48
|
+
files?: string[];
|
|
49
|
+
turns?: Turn[];
|
|
50
|
+
tools?: ToolOutputEntry[];
|
|
51
|
+
git?: Array<{
|
|
52
|
+
path: string;
|
|
53
|
+
status?: string;
|
|
54
|
+
}>;
|
|
55
|
+
errors?: Incident[];
|
|
56
|
+
agents?: AgentNode[];
|
|
57
|
+
}
|
|
58
|
+
export interface Count {
|
|
59
|
+
scope: Scope;
|
|
60
|
+
count: number;
|
|
61
|
+
}
|
|
62
|
+
export interface Result {
|
|
63
|
+
query: string;
|
|
64
|
+
hits: Hit[];
|
|
65
|
+
counts: Count[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Shorten an ASSEMBLED line to `cols`, keeping its leading indent.
|
|
69
|
+
*
|
|
70
|
+
* `clip` flattens whitespace, which is right for a path or a matched line but destroys a row
|
|
71
|
+
* whose alignment is the point: `clip(' Files 12')` returns `Files 12`, and every column in
|
|
72
|
+
* the counts block collapses. Measured on the first run of the tests in this module.
|
|
73
|
+
*/
|
|
74
|
+
export declare function fitLine(body: string, cols: number, indent?: string): string;
|
|
75
|
+
/**
|
|
76
|
+
* Rank one text against the query. `Infinity` = no match.
|
|
77
|
+
*
|
|
78
|
+
* Substring only, case-insensitive — this is a search box for code, where a fuzzy match is
|
|
79
|
+
* noise. The ladder, weakest last:
|
|
80
|
+
*
|
|
81
|
+
* 0 the text IS the query `auth` vs `auth`
|
|
82
|
+
* 1 the text begins with the query `auth.ts` vs `auth`
|
|
83
|
+
* 2 the query begins a word `src/auth.ts`, `jwt-refresh.ts`
|
|
84
|
+
* 3 the query starts mid-word `myjwthelper.ts`, `contest/a.ts`
|
|
85
|
+
*
|
|
86
|
+
* Rung 1 exists on its own because a tie between `auth.ts` and `x/auth-mid.ts` used to be
|
|
87
|
+
* settled by whatever order the scope listed them in, which put the worse match first.
|
|
88
|
+
*/
|
|
89
|
+
export declare function scoreText(query: string, text: string): number;
|
|
90
|
+
/** Files: the path itself. */
|
|
91
|
+
export declare function searchFiles(query: string, files: string[]): Hit[];
|
|
92
|
+
/** Conversation: prompts and reply lines, via the transcript the turn navigation already builds. */
|
|
93
|
+
export declare function searchConversation(query: string, turns: Turn[]): Hit[];
|
|
94
|
+
/** Tools: the call label and the output the transcript folded away. */
|
|
95
|
+
export declare function searchTools(query: string, entries: ToolOutputEntry[]): Hit[];
|
|
96
|
+
/** Git: changed paths, with the status letter kept in front so `M`/`??` is visible. */
|
|
97
|
+
export declare function searchGit(query: string, changes: Array<{
|
|
98
|
+
path: string;
|
|
99
|
+
status?: string;
|
|
100
|
+
}>): Hit[];
|
|
101
|
+
/** Errors: the failing command, the files it touched, and the re-tests. */
|
|
102
|
+
export declare function searchErrors(query: string, incidents: Incident[]): Hit[];
|
|
103
|
+
/** Agents: type, task label and the files each one touched. */
|
|
104
|
+
export declare function searchAgents(query: string, nodes: AgentNode[]): Hit[];
|
|
105
|
+
export declare function globalSearch(query: string, sources: Sources, perScope?: number): Result;
|
|
106
|
+
export declare function filterScope(hits: Hit[], scope: Scope | null): Hit[];
|
|
107
|
+
/**
|
|
108
|
+
* The §52 counts block:
|
|
109
|
+
*
|
|
110
|
+
* > JWT
|
|
111
|
+
*
|
|
112
|
+
* Files 12
|
|
113
|
+
* Conversation 8
|
|
114
|
+
* Git 3
|
|
115
|
+
* Agents 2
|
|
116
|
+
*
|
|
117
|
+
* Labels pad to the widest, counts right-align to the widest — so the numbers line up even
|
|
118
|
+
* when "Conversation" sits next to "Files". Every line is clipped to `cols`; at narrow widths
|
|
119
|
+
* the label is what gets cut, never the count.
|
|
120
|
+
*/
|
|
121
|
+
export declare function formatCounts(result: Result, cols: number, opts?: {
|
|
122
|
+
table?: boolean;
|
|
123
|
+
}): string[];
|
|
124
|
+
/**
|
|
125
|
+
* One printed row per hit: ` 3. Git M src/app.js`, with the matched line under it when the
|
|
126
|
+
* label alone would not say why it matched.
|
|
127
|
+
*
|
|
128
|
+
* `tag` prepends the 3-letter scope — on when the rows are mixed, off inside a per-scope
|
|
129
|
+
* section where the heading already said it.
|
|
130
|
+
*/
|
|
131
|
+
export declare function formatHits(hits: Hit[], cols: number, indent?: string, tag?: boolean): string[];
|
|
132
|
+
/** `Scopes: /find <q> --files --git` — only the flags that would actually narrow this result. */
|
|
133
|
+
export declare function scopeHint(counts: Count[]): string;
|
|
134
|
+
export interface SearchArgs {
|
|
135
|
+
query: string;
|
|
136
|
+
/** null = search everything. */
|
|
137
|
+
scopes: Scope[] | null;
|
|
138
|
+
/** Flags that are not scopes — reported instead of silently becoming part of the query. */
|
|
139
|
+
bad: string[];
|
|
140
|
+
}
|
|
141
|
+
/** `/find JWT --git --tools` → query "JWT", scopes [git, tools]. `--all` clears the narrowing. */
|
|
142
|
+
export declare function parseSearchArgs(arg: string): SearchArgs;
|
|
143
|
+
export declare function searchUsage(): string;
|
|
144
|
+
//# sourceMappingURL=globalSearch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"globalSearch.d.ts","sourceRoot":"","sources":["../../src/search/globalSearch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Lives in core (moved from the CLI's ui/globalSearch.ts, 2026-10-04) so
|
|
3
|
+
// /find searches with the same engine in the VS Code panel; the CLI module is
|
|
4
|
+
// a re-export shim.
|
|
5
|
+
// §52 Global search — one query across files, conversation, tools, git, errors and agents.
|
|
6
|
+
//
|
|
7
|
+
// WHY: every scope already had its own command (/diff, /expand, /failures, /agents, /find), so
|
|
8
|
+
// answering "where does JWT show up at all?" meant running five of them and remembering which
|
|
9
|
+
// one held the answer. This module is the missing index over the sources the session already
|
|
10
|
+
// holds — pure functions, so a count is exactly what the scope contains and nothing is invented.
|
|
11
|
+
// It finds and ranks; the per-scope commands still do the jumping.
|
|
12
|
+
//
|
|
13
|
+
// Pure (no Ink, no console, no fs) so ranking, clipping and the counts block are unit-testable.
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.LABEL_CLIP = exports.DETAIL_CLIP = exports.MAX_PER_SCOPE = exports.SCOPE_LABEL = exports.SCOPES = void 0;
|
|
16
|
+
exports.fitLine = fitLine;
|
|
17
|
+
exports.scoreText = scoreText;
|
|
18
|
+
exports.searchFiles = searchFiles;
|
|
19
|
+
exports.searchConversation = searchConversation;
|
|
20
|
+
exports.searchTools = searchTools;
|
|
21
|
+
exports.searchGit = searchGit;
|
|
22
|
+
exports.searchErrors = searchErrors;
|
|
23
|
+
exports.searchAgents = searchAgents;
|
|
24
|
+
exports.globalSearch = globalSearch;
|
|
25
|
+
exports.filterScope = filterScope;
|
|
26
|
+
exports.formatCounts = formatCounts;
|
|
27
|
+
exports.formatHits = formatHits;
|
|
28
|
+
exports.scopeHint = scopeHint;
|
|
29
|
+
exports.parseSearchArgs = parseSearchArgs;
|
|
30
|
+
exports.searchUsage = searchUsage;
|
|
31
|
+
const cellWidth_1 = require("../util/cellWidth");
|
|
32
|
+
/** Display order — §52 lists files first because that is where a code query usually lives. */
|
|
33
|
+
exports.SCOPES = ['files', 'conversation', 'tools', 'git', 'errors', 'agents'];
|
|
34
|
+
exports.SCOPE_LABEL = {
|
|
35
|
+
files: 'Files',
|
|
36
|
+
conversation: 'Conversation',
|
|
37
|
+
tools: 'Tools',
|
|
38
|
+
git: 'Git',
|
|
39
|
+
errors: 'Errors',
|
|
40
|
+
agents: 'Agents',
|
|
41
|
+
};
|
|
42
|
+
/** Flags accepted after the query, including the short forms a user is likely to try first. */
|
|
43
|
+
const SCOPE_FLAG = {
|
|
44
|
+
'--files': 'files',
|
|
45
|
+
'--file': 'files',
|
|
46
|
+
'--chat': 'conversation',
|
|
47
|
+
'--conversation': 'conversation',
|
|
48
|
+
'--conv': 'conversation',
|
|
49
|
+
'--tools': 'tools',
|
|
50
|
+
'--tool': 'tools',
|
|
51
|
+
'--git': 'git',
|
|
52
|
+
'--errors': 'errors',
|
|
53
|
+
'--error': 'errors',
|
|
54
|
+
'--agents': 'agents',
|
|
55
|
+
'--agent': 'agents',
|
|
56
|
+
};
|
|
57
|
+
/** Hits printed per scope. The counts line stays complete — only the list is capped. */
|
|
58
|
+
exports.MAX_PER_SCOPE = 5;
|
|
59
|
+
/** A matched line kept for context, so a hit is recognisable without opening anything. */
|
|
60
|
+
exports.DETAIL_CLIP = 96;
|
|
61
|
+
exports.LABEL_CLIP = 72;
|
|
62
|
+
function clip(s, max) {
|
|
63
|
+
const flat = s.replace(/\s+/g, ' ').trim();
|
|
64
|
+
return (0, cellWidth_1.truncateCells)(flat, max);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Shorten an ASSEMBLED line to `cols`, keeping its leading indent.
|
|
68
|
+
*
|
|
69
|
+
* `clip` flattens whitespace, which is right for a path or a matched line but destroys a row
|
|
70
|
+
* whose alignment is the point: `clip(' Files 12')` returns `Files 12`, and every column in
|
|
71
|
+
* the counts block collapses. Measured on the first run of the tests in this module.
|
|
72
|
+
*/
|
|
73
|
+
function fitLine(body, cols, indent = '') {
|
|
74
|
+
const room = Math.max(1, cols - (0, cellWidth_1.visibleLen)(indent));
|
|
75
|
+
if ((0, cellWidth_1.visibleLen)(body) <= room)
|
|
76
|
+
return indent + body;
|
|
77
|
+
const cut = (0, cellWidth_1.truncateCells)(body, room);
|
|
78
|
+
// A cut lands mid-style (the closing code of the run it interrupted is never reached), and an
|
|
79
|
+
// SGR left open bleeds into every row printed after it. Only styled rows get the reset, so
|
|
80
|
+
// NO_COLOR output stays byte-identical to what it always was.
|
|
81
|
+
return indent + (cut.includes('\u001b') ? cut + '\u001b[0m' : cut);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Rank one text against the query. `Infinity` = no match.
|
|
85
|
+
*
|
|
86
|
+
* Substring only, case-insensitive — this is a search box for code, where a fuzzy match is
|
|
87
|
+
* noise. The ladder, weakest last:
|
|
88
|
+
*
|
|
89
|
+
* 0 the text IS the query `auth` vs `auth`
|
|
90
|
+
* 1 the text begins with the query `auth.ts` vs `auth`
|
|
91
|
+
* 2 the query begins a word `src/auth.ts`, `jwt-refresh.ts`
|
|
92
|
+
* 3 the query starts mid-word `myjwthelper.ts`, `contest/a.ts`
|
|
93
|
+
*
|
|
94
|
+
* Rung 1 exists on its own because a tie between `auth.ts` and `x/auth-mid.ts` used to be
|
|
95
|
+
* settled by whatever order the scope listed them in, which put the worse match first.
|
|
96
|
+
*/
|
|
97
|
+
function scoreText(query, text) {
|
|
98
|
+
const q = query.toLowerCase();
|
|
99
|
+
if (!q)
|
|
100
|
+
return Infinity;
|
|
101
|
+
const t = text.toLowerCase();
|
|
102
|
+
const at = t.indexOf(q);
|
|
103
|
+
if (at < 0)
|
|
104
|
+
return Infinity;
|
|
105
|
+
if (t === q)
|
|
106
|
+
return 0;
|
|
107
|
+
if (at === 0)
|
|
108
|
+
return 1;
|
|
109
|
+
if (!/[a-z0-9]/.test(t[at - 1]))
|
|
110
|
+
return 2;
|
|
111
|
+
return 3;
|
|
112
|
+
}
|
|
113
|
+
/** Best first: score, then the order the source listed them in. */
|
|
114
|
+
function top(hits, limit) {
|
|
115
|
+
const sorted = hits.sort((a, b) => a.score - b.score || a.index - b.index);
|
|
116
|
+
return limit === Infinity ? sorted : sorted.slice(0, limit);
|
|
117
|
+
}
|
|
118
|
+
function push(hits, scope, index, label, detail, score) {
|
|
119
|
+
hits.push({
|
|
120
|
+
scope,
|
|
121
|
+
key: `${scope}:${index}`,
|
|
122
|
+
label: clip(label, exports.LABEL_CLIP),
|
|
123
|
+
detail: detail && clip(detail, exports.DETAIL_CLIP),
|
|
124
|
+
score,
|
|
125
|
+
index,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// ─── Per-scope matchers ───────────────────────────────────────────────────────
|
|
129
|
+
/** Files: the path itself. */
|
|
130
|
+
function searchFiles(query, files) {
|
|
131
|
+
const hits = [];
|
|
132
|
+
files.forEach((f, i) => {
|
|
133
|
+
const score = scoreText(query, f);
|
|
134
|
+
if (score !== Infinity)
|
|
135
|
+
push(hits, 'files', i + 1, f, undefined, score);
|
|
136
|
+
});
|
|
137
|
+
return top(hits, Infinity);
|
|
138
|
+
}
|
|
139
|
+
/** Conversation: prompts and reply lines, via the transcript the turn navigation already builds. */
|
|
140
|
+
function searchConversation(query, turns) {
|
|
141
|
+
const hits = [];
|
|
142
|
+
turns.forEach((t) => {
|
|
143
|
+
const score = scoreText(query, t.prompt);
|
|
144
|
+
if (score !== Infinity)
|
|
145
|
+
push(hits, 'conversation', t.index, `turn ${t.index} you ${t.prompt}`, undefined, score);
|
|
146
|
+
t.lines.forEach((line) => {
|
|
147
|
+
const s = scoreText(query, line);
|
|
148
|
+
if (s !== Infinity)
|
|
149
|
+
push(hits, 'conversation', t.index, `turn ${t.index}`, line, s + 1);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
return top(hits, Infinity);
|
|
153
|
+
}
|
|
154
|
+
/** Tools: the call label and the output the transcript folded away. */
|
|
155
|
+
function searchTools(query, entries) {
|
|
156
|
+
const hits = [];
|
|
157
|
+
entries.forEach((e) => {
|
|
158
|
+
const label = `${e.name}: ${e.label}`;
|
|
159
|
+
let best = scoreText(query, label);
|
|
160
|
+
let detail;
|
|
161
|
+
for (const line of e.output.split('\n')) {
|
|
162
|
+
const s = scoreText(query, line);
|
|
163
|
+
if (s !== Infinity) {
|
|
164
|
+
if (s + 1 < best)
|
|
165
|
+
best = s + 1;
|
|
166
|
+
detail = line; // one output line is enough to recognise the call; /expand <id> shows the rest
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// ONE row per tool call: the count then answers "how many calls mention this", and the
|
|
171
|
+
// command that is itself the query does not show up as a second, identical-looking hit.
|
|
172
|
+
if (best !== Infinity)
|
|
173
|
+
push(hits, 'tools', e.id, `#${e.id} ${label}`, detail, best);
|
|
174
|
+
});
|
|
175
|
+
return top(hits, Infinity);
|
|
176
|
+
}
|
|
177
|
+
/** Git: changed paths, with the status letter kept in front so `M`/`??` is visible. */
|
|
178
|
+
function searchGit(query, changes) {
|
|
179
|
+
const hits = [];
|
|
180
|
+
changes.forEach((c, i) => {
|
|
181
|
+
const score = scoreText(query, c.path);
|
|
182
|
+
if (score !== Infinity)
|
|
183
|
+
push(hits, 'git', i + 1, `${c.status ? c.status + ' ' : ''}${c.path}`, undefined, score);
|
|
184
|
+
});
|
|
185
|
+
return top(hits, Infinity);
|
|
186
|
+
}
|
|
187
|
+
/** Errors: the failing command, the files it touched, and the re-tests. */
|
|
188
|
+
function searchErrors(query, incidents) {
|
|
189
|
+
const hits = [];
|
|
190
|
+
incidents.forEach((inc) => {
|
|
191
|
+
const head = `#${inc.id} ${inc.command}`;
|
|
192
|
+
const inHead = scoreText(query, head);
|
|
193
|
+
if (inHead !== Infinity)
|
|
194
|
+
push(hits, 'errors', inc.id, head, undefined, inHead);
|
|
195
|
+
for (const f of inc.files) {
|
|
196
|
+
const s = scoreText(query, f);
|
|
197
|
+
if (s !== Infinity)
|
|
198
|
+
push(hits, 'errors', inc.id, head, f, s + 1);
|
|
199
|
+
}
|
|
200
|
+
for (const r of inc.retests) {
|
|
201
|
+
const s = scoreText(query, r.command);
|
|
202
|
+
if (s !== Infinity)
|
|
203
|
+
push(hits, 'errors', inc.id, head, `[${r.ok ? 'ok' : 'fail'}] ${r.command}`, s + 1);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
return top(hits, Infinity);
|
|
207
|
+
}
|
|
208
|
+
/** Agents: type, task label and the files each one touched. */
|
|
209
|
+
function searchAgents(query, nodes) {
|
|
210
|
+
const hits = [];
|
|
211
|
+
nodes.forEach((n, i) => {
|
|
212
|
+
const head = `${n.name} ${n.label}`;
|
|
213
|
+
const inHead = scoreText(query, head);
|
|
214
|
+
if (inHead !== Infinity)
|
|
215
|
+
push(hits, 'agents', i + 1, head, n.error, inHead);
|
|
216
|
+
for (const f of n.files) {
|
|
217
|
+
const s = scoreText(query, f);
|
|
218
|
+
if (s !== Infinity)
|
|
219
|
+
push(hits, 'agents', i + 1, head, f, s + 1);
|
|
220
|
+
}
|
|
221
|
+
if (n.report) {
|
|
222
|
+
const s = scoreText(query, n.report);
|
|
223
|
+
if (s !== Infinity)
|
|
224
|
+
push(hits, 'agents', i + 1, head, 'report', s + 2);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
return top(hits, Infinity);
|
|
228
|
+
}
|
|
229
|
+
// ─── The search itself ───────────────────────────────────────────────────────
|
|
230
|
+
function globalSearch(query, sources, perScope = exports.MAX_PER_SCOPE) {
|
|
231
|
+
const q = query.trim();
|
|
232
|
+
if (!q)
|
|
233
|
+
return { query: '', hits: [], counts: [] };
|
|
234
|
+
const all = [];
|
|
235
|
+
if (sources.files)
|
|
236
|
+
all.push(...searchFiles(q, sources.files));
|
|
237
|
+
if (sources.turns)
|
|
238
|
+
all.push(...searchConversation(q, sources.turns));
|
|
239
|
+
if (sources.tools)
|
|
240
|
+
all.push(...searchTools(q, sources.tools));
|
|
241
|
+
if (sources.git)
|
|
242
|
+
all.push(...searchGit(q, sources.git));
|
|
243
|
+
if (sources.errors)
|
|
244
|
+
all.push(...searchErrors(q, sources.errors));
|
|
245
|
+
if (sources.agents)
|
|
246
|
+
all.push(...searchAgents(q, sources.agents));
|
|
247
|
+
const counts = exports.SCOPES.map((scope) => ({ scope, count: all.filter((h) => h.scope === scope).length })).filter((c) => c.count > 0);
|
|
248
|
+
const hits = [];
|
|
249
|
+
for (const scope of exports.SCOPES) {
|
|
250
|
+
hits.push(...top(all.filter((h) => h.scope === scope), perScope));
|
|
251
|
+
}
|
|
252
|
+
return { query: q, hits, counts };
|
|
253
|
+
}
|
|
254
|
+
function filterScope(hits, scope) {
|
|
255
|
+
return scope ? hits.filter((h) => h.scope === scope) : hits;
|
|
256
|
+
}
|
|
257
|
+
// ─── Rendering ────────────────────────────────────────────────────────────────
|
|
258
|
+
/**
|
|
259
|
+
* The §52 counts block:
|
|
260
|
+
*
|
|
261
|
+
* > JWT
|
|
262
|
+
*
|
|
263
|
+
* Files 12
|
|
264
|
+
* Conversation 8
|
|
265
|
+
* Git 3
|
|
266
|
+
* Agents 2
|
|
267
|
+
*
|
|
268
|
+
* Labels pad to the widest, counts right-align to the widest — so the numbers line up even
|
|
269
|
+
* when "Conversation" sits next to "Files". Every line is clipped to `cols`; at narrow widths
|
|
270
|
+
* the label is what gets cut, never the count.
|
|
271
|
+
*/
|
|
272
|
+
function formatCounts(result, cols, opts = {}) {
|
|
273
|
+
const out = [];
|
|
274
|
+
if (!result.query)
|
|
275
|
+
return out;
|
|
276
|
+
out.push(fitLine(`> ${result.query}`, cols));
|
|
277
|
+
if (!result.counts.length) {
|
|
278
|
+
out.push('');
|
|
279
|
+
out.push(fitLine('No match in files, conversation, tools, git, errors or agents.', cols, ' '));
|
|
280
|
+
return out;
|
|
281
|
+
}
|
|
282
|
+
// With one scope selected the section heading below already carries its count (`Git 1`),
|
|
283
|
+
// so the table would only repeat it — but the `> query` header still belongs to the result.
|
|
284
|
+
if (opts.table === false)
|
|
285
|
+
return out;
|
|
286
|
+
const labelW = Math.max(...result.counts.map((c) => exports.SCOPE_LABEL[c.scope].length)) + 1;
|
|
287
|
+
const numW = Math.max(...result.counts.map((c) => String(c.count).length));
|
|
288
|
+
out.push('');
|
|
289
|
+
for (const c of result.counts) {
|
|
290
|
+
const label = exports.SCOPE_LABEL[c.scope].padEnd(labelW);
|
|
291
|
+
const num = String(c.count).padStart(numW);
|
|
292
|
+
out.push(fitLine(`${label}${num}`, cols, ' '));
|
|
293
|
+
}
|
|
294
|
+
return out;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* One printed row per hit: ` 3. Git M src/app.js`, with the matched line under it when the
|
|
298
|
+
* label alone would not say why it matched.
|
|
299
|
+
*
|
|
300
|
+
* `tag` prepends the 3-letter scope — on when the rows are mixed, off inside a per-scope
|
|
301
|
+
* section where the heading already said it.
|
|
302
|
+
*/
|
|
303
|
+
function formatHits(hits, cols, indent = ' ', tag = true) {
|
|
304
|
+
const out = [];
|
|
305
|
+
hits.forEach((h, i) => {
|
|
306
|
+
const n = String(i + 1).padStart(3);
|
|
307
|
+
const mark = tag ? `${exports.SCOPE_LABEL[h.scope].slice(0, 3)} ` : '';
|
|
308
|
+
out.push(fitLine(`${n}. ${mark}${h.label}`, cols, indent));
|
|
309
|
+
if (h.detail && h.detail !== h.label)
|
|
310
|
+
out.push(fitLine(h.detail, cols, indent + ' '));
|
|
311
|
+
});
|
|
312
|
+
return out;
|
|
313
|
+
}
|
|
314
|
+
/** `Scopes: /find <q> --files --git` — only the flags that would actually narrow this result. */
|
|
315
|
+
function scopeHint(counts) {
|
|
316
|
+
if (!counts.length)
|
|
317
|
+
return '';
|
|
318
|
+
const flags = counts.map((c) => '--' + c.scope);
|
|
319
|
+
return `Scopes: /find <query> ${flags.join(' ')}`;
|
|
320
|
+
}
|
|
321
|
+
/** `/find JWT --git --tools` → query "JWT", scopes [git, tools]. `--all` clears the narrowing. */
|
|
322
|
+
function parseSearchArgs(arg) {
|
|
323
|
+
const words = arg.trim().split(/\s+/).filter(Boolean);
|
|
324
|
+
const scopes = [];
|
|
325
|
+
const bad = [];
|
|
326
|
+
const rest = [];
|
|
327
|
+
let all = false;
|
|
328
|
+
for (const w of words) {
|
|
329
|
+
const scope = SCOPE_FLAG[w.toLowerCase()];
|
|
330
|
+
if (scope) {
|
|
331
|
+
scopes.push(scope);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (w.toLowerCase() === '--all') {
|
|
335
|
+
all = true;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (w.startsWith('--')) {
|
|
339
|
+
bad.push(w);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
rest.push(w);
|
|
343
|
+
}
|
|
344
|
+
return { query: rest.join(' '), scopes: all || !scopes.length ? null : [...new Set(scopes)], bad };
|
|
345
|
+
}
|
|
346
|
+
function searchUsage() {
|
|
347
|
+
const flags = exports.SCOPES.map((s) => (s === 'conversation' ? '--conversation|--chat' : `--${s}`));
|
|
348
|
+
return `Usage: /find <text> ${flags.map((f) => `[${f}]`).join(' ')}`;
|
|
349
|
+
}
|
|
350
|
+
//# sourceMappingURL=globalSearch.js.map
|