@oh-my-pi/pi-coding-agent 14.5.14 → 14.6.1
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/CHANGELOG.md +49 -0
- package/package.json +7 -7
- package/src/autoresearch/command-resume.md +5 -8
- package/src/autoresearch/git.ts +41 -51
- package/src/autoresearch/helpers.ts +43 -359
- package/src/autoresearch/index.ts +281 -273
- package/src/autoresearch/prompt-setup.md +43 -0
- package/src/autoresearch/prompt.md +52 -193
- package/src/autoresearch/resume-message.md +2 -8
- package/src/autoresearch/state.ts +59 -166
- package/src/autoresearch/storage.ts +687 -0
- package/src/autoresearch/tools/init-experiment.ts +201 -290
- package/src/autoresearch/tools/log-experiment.ts +304 -517
- package/src/autoresearch/tools/run-experiment.ts +117 -296
- package/src/autoresearch/tools/update-notes.ts +116 -0
- package/src/autoresearch/types.ts +16 -66
- package/src/cli/list-models.ts +66 -0
- package/src/config/settings-schema.ts +1 -1
- package/src/config/settings.ts +20 -1
- package/src/cursor.ts +1 -1
- package/src/edit/index.ts +9 -31
- package/src/edit/line-hash.ts +70 -43
- package/src/edit/modes/hashline.lark +26 -0
- package/src/edit/modes/hashline.ts +898 -1099
- package/src/edit/modes/patch.ts +0 -7
- package/src/edit/modes/replace.ts +0 -4
- package/src/edit/renderer.ts +22 -20
- package/src/edit/streaming.ts +8 -28
- package/src/eval/eval.lark +24 -30
- package/src/eval/js/context-manager.ts +5 -162
- package/src/eval/js/prelude.txt +0 -12
- package/src/eval/parse.ts +129 -129
- package/src/eval/py/prelude.py +1 -219
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +2 -2
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/main.ts +18 -3
- package/src/modes/components/session-observer-overlay.ts +5 -2
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tree-selector.ts +4 -5
- package/src/modes/components/welcome.ts +11 -1
- package/src/modes/controllers/command-controller.ts +2 -6
- package/src/modes/controllers/event-controller.ts +7 -5
- package/src/modes/controllers/extension-ui-controller.ts +3 -15
- package/src/modes/controllers/input-controller.ts +0 -1
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +5 -7
- package/src/prompts/system/system-prompt.md +14 -38
- package/src/prompts/tools/ast-edit.md +8 -8
- package/src/prompts/tools/ast-grep.md +10 -10
- package/src/prompts/tools/eval.md +13 -31
- package/src/prompts/tools/find.md +2 -1
- package/src/prompts/tools/hashline.md +66 -57
- package/src/prompts/tools/search.md +2 -2
- package/src/session/agent-session.ts +1 -1
- package/src/session/session-manager.ts +17 -13
- package/src/tools/ast-edit.ts +141 -44
- package/src/tools/ast-grep.ts +112 -36
- package/src/tools/eval.ts +2 -53
- package/src/tools/find.ts +16 -15
- package/src/tools/gh-renderer.ts +184 -59
- package/src/tools/path-utils.ts +36 -196
- package/src/tools/search.ts +56 -35
- package/src/utils/edit-mode.ts +2 -11
- package/src/utils/file-display-mode.ts +1 -1
- package/src/utils/git.ts +59 -24
- package/src/utils/session-color.ts +0 -12
- package/src/utils/title-generator.ts +22 -38
- package/src/autoresearch/apply-contract-to-state.ts +0 -24
- package/src/autoresearch/contract.ts +0 -288
- package/src/edit/modes/atom.lark +0 -29
- package/src/edit/modes/atom.ts +0 -1773
- package/src/prompts/tools/atom.md +0 -150
package/src/tools/gh-renderer.ts
CHANGED
|
@@ -12,10 +12,13 @@ import type {
|
|
|
12
12
|
import { formatShortSha } from "./gh-format";
|
|
13
13
|
import {
|
|
14
14
|
formatExpandHint,
|
|
15
|
+
formatMoreItems,
|
|
15
16
|
formatStatusIcon,
|
|
16
17
|
PREVIEW_LIMITS,
|
|
17
18
|
replaceTabs,
|
|
18
19
|
type ToolUIColor,
|
|
20
|
+
type ToolUIStatus,
|
|
21
|
+
TRUNCATE_LENGTHS,
|
|
19
22
|
truncateToWidth as truncateVisualWidth,
|
|
20
23
|
} from "./render-utils";
|
|
21
24
|
|
|
@@ -23,6 +26,10 @@ type GithubToolRenderArgs = {
|
|
|
23
26
|
op?: string;
|
|
24
27
|
run?: string;
|
|
25
28
|
branch?: string;
|
|
29
|
+
repo?: string;
|
|
30
|
+
issue?: string;
|
|
31
|
+
pr?: string | string[];
|
|
32
|
+
query?: string;
|
|
26
33
|
};
|
|
27
34
|
|
|
28
35
|
const SUCCESS_CONCLUSIONS = new Set(["success", "neutral", "skipped"]);
|
|
@@ -31,6 +38,87 @@ const RUNNING_STATUSES = new Set(["in_progress"]);
|
|
|
31
38
|
const PENDING_STATUSES = new Set(["queued", "requested", "waiting", "pending"]);
|
|
32
39
|
const FALLBACK_WIDTH = 80;
|
|
33
40
|
|
|
41
|
+
const OP_TITLES: Record<string, string> = {
|
|
42
|
+
repo_view: "GitHub Repo",
|
|
43
|
+
issue_view: "GitHub Issue",
|
|
44
|
+
pr_view: "GitHub PR",
|
|
45
|
+
pr_diff: "GitHub PR Diff",
|
|
46
|
+
pr_checkout: "GitHub PR Checkout",
|
|
47
|
+
pr_push: "GitHub PR Push",
|
|
48
|
+
search_issues: "GitHub Search Issues",
|
|
49
|
+
search_prs: "GitHub Search PRs",
|
|
50
|
+
run_watch: "GitHub Run Watch",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function formatOpTitle(op: string | undefined): string {
|
|
54
|
+
if (op && OP_TITLES[op]) return OP_TITLES[op];
|
|
55
|
+
return "GitHub";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function extractIssueId(value: string | undefined): string | undefined {
|
|
59
|
+
if (!value) return undefined;
|
|
60
|
+
const trimmed = value.trim();
|
|
61
|
+
if (!trimmed) return undefined;
|
|
62
|
+
if (/^\d+$/.test(trimmed)) return `#${trimmed}`;
|
|
63
|
+
const match = trimmed.match(/\/(?:issues|pull)\/(\d+)/);
|
|
64
|
+
if (match) return `#${match[1]}`;
|
|
65
|
+
return truncateVisualWidth(trimmed, TRUNCATE_LENGTHS.SHORT);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function formatPrIdentifier(pr: string | string[] | undefined): string | undefined {
|
|
69
|
+
if (pr === undefined) return undefined;
|
|
70
|
+
if (Array.isArray(pr)) {
|
|
71
|
+
const parts = pr.map(p => extractIssueId(p)).filter((p): p is string => p !== undefined);
|
|
72
|
+
if (parts.length === 0) return undefined;
|
|
73
|
+
if (parts.length > 3) {
|
|
74
|
+
return `${parts.slice(0, 3).join(", ")}, +${parts.length - 3} more`;
|
|
75
|
+
}
|
|
76
|
+
return parts.join(", ");
|
|
77
|
+
}
|
|
78
|
+
return extractIssueId(pr);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function buildOpMeta(args: GithubToolRenderArgs): string[] {
|
|
82
|
+
const meta: string[] = [];
|
|
83
|
+
const op = args.op;
|
|
84
|
+
switch (op) {
|
|
85
|
+
case "issue_view": {
|
|
86
|
+
const id = extractIssueId(args.issue);
|
|
87
|
+
if (id) meta.push(id);
|
|
88
|
+
if (args.repo) meta.push(args.repo);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case "pr_view":
|
|
92
|
+
case "pr_diff":
|
|
93
|
+
case "pr_checkout":
|
|
94
|
+
case "pr_push": {
|
|
95
|
+
const id = formatPrIdentifier(args.pr);
|
|
96
|
+
if (id) meta.push(id);
|
|
97
|
+
else if (args.branch) meta.push(args.branch);
|
|
98
|
+
if (args.repo) meta.push(args.repo);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case "search_issues":
|
|
102
|
+
case "search_prs": {
|
|
103
|
+
if (args.query) meta.push(truncateVisualWidth(args.query, TRUNCATE_LENGTHS.CONTENT));
|
|
104
|
+
if (args.repo) meta.push(args.repo);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "repo_view": {
|
|
108
|
+
if (args.repo) meta.push(args.repo);
|
|
109
|
+
if (args.branch) meta.push(args.branch);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case "run_watch":
|
|
113
|
+
break;
|
|
114
|
+
default: {
|
|
115
|
+
if (args.repo) meta.push(args.repo);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return meta;
|
|
120
|
+
}
|
|
121
|
+
|
|
34
122
|
function getWatchHeader(watch: GhRunWatchViewDetails): string {
|
|
35
123
|
if (watch.mode === "run" && watch.run) {
|
|
36
124
|
if (watch.state === "watching") {
|
|
@@ -183,7 +271,7 @@ function renderFailedLogs(
|
|
|
183
271
|
return lines;
|
|
184
272
|
}
|
|
185
273
|
|
|
186
|
-
function
|
|
274
|
+
function buildWatchLines(
|
|
187
275
|
watch: GhRunWatchViewDetails,
|
|
188
276
|
theme: Theme,
|
|
189
277
|
options: RenderResultOptions,
|
|
@@ -215,92 +303,129 @@ function buildRenderedLines(
|
|
|
215
303
|
return lines;
|
|
216
304
|
}
|
|
217
305
|
|
|
218
|
-
function
|
|
219
|
-
|
|
220
|
-
theme: Theme,
|
|
221
|
-
): Component {
|
|
222
|
-
const text = result.content
|
|
306
|
+
function extractText(content: Array<{ type: string; text?: string }>): string {
|
|
307
|
+
return content
|
|
223
308
|
.filter(part => part.type === "text")
|
|
224
309
|
.map(part => part.text)
|
|
225
310
|
.filter((value): value is string => typeof value === "string" && value.length > 0)
|
|
226
311
|
.join("\n");
|
|
227
|
-
|
|
228
|
-
return new Text(replaceTabs(text), 0, 0);
|
|
229
|
-
}
|
|
312
|
+
}
|
|
230
313
|
|
|
314
|
+
function renderFallbackComponent(
|
|
315
|
+
result: { content: Array<{ type: string; text?: string }>; isError?: boolean },
|
|
316
|
+
options: RenderResultOptions,
|
|
317
|
+
theme: Theme,
|
|
318
|
+
args: GithubToolRenderArgs,
|
|
319
|
+
): Component {
|
|
320
|
+
const text = extractText(result.content);
|
|
321
|
+
const title = formatOpTitle(args.op);
|
|
322
|
+
const meta = buildOpMeta(args);
|
|
323
|
+
const isError = result.isError === true;
|
|
324
|
+
const status: ToolUIStatus = isError ? "error" : text ? "success" : "warning";
|
|
231
325
|
const header = renderStatusLine(
|
|
232
326
|
{
|
|
233
|
-
icon:
|
|
234
|
-
title
|
|
235
|
-
|
|
327
|
+
icon: status,
|
|
328
|
+
title,
|
|
329
|
+
titleColor: isError ? "error" : "accent",
|
|
330
|
+
meta,
|
|
236
331
|
},
|
|
237
332
|
theme,
|
|
238
333
|
);
|
|
239
|
-
return new Text(header, 0, 0);
|
|
240
|
-
}
|
|
241
334
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
335
|
+
if (!text) {
|
|
336
|
+
const empty = isError ? "request failed" : "no output";
|
|
337
|
+
return new Text(`${header}\n${theme.fg("dim", empty)}`, 0, 0);
|
|
338
|
+
}
|
|
245
339
|
|
|
246
|
-
|
|
247
|
-
const icon =
|
|
248
|
-
options.spinnerFrame !== undefined
|
|
249
|
-
? formatStatusIcon("running", uiTheme, options.spinnerFrame)
|
|
250
|
-
: formatStatusIcon("pending", uiTheme);
|
|
340
|
+
const allLines = replaceTabs(text).split("\n");
|
|
251
341
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
342
|
+
return {
|
|
343
|
+
render(width: number): string[] {
|
|
344
|
+
const lineWidth = Math.max(24, width || FALLBACK_WIDTH);
|
|
345
|
+
const expanded = options.expanded;
|
|
346
|
+
const limit = expanded ? allLines.length : Math.min(allLines.length, PREVIEW_LIMITS.OUTPUT_EXPANDED);
|
|
347
|
+
const visible = allLines.slice(0, limit);
|
|
348
|
+
const remaining = allLines.length - visible.length;
|
|
349
|
+
|
|
350
|
+
const out: string[] = [header];
|
|
351
|
+
for (const line of visible) {
|
|
352
|
+
const colored = isError ? theme.fg("error", line) : theme.fg("toolOutput", line);
|
|
353
|
+
out.push(truncateVisualWidth(colored, lineWidth));
|
|
354
|
+
}
|
|
355
|
+
if (!expanded && remaining > 0) {
|
|
356
|
+
const hint = formatExpandHint(theme, expanded, true);
|
|
357
|
+
const more = `${formatMoreItems(remaining, "line")}${hint ? ` ${hint}` : ""}`;
|
|
358
|
+
out.push(theme.fg("dim", more));
|
|
359
|
+
}
|
|
360
|
+
return out.map(line => truncateToWidth(line, lineWidth));
|
|
361
|
+
},
|
|
362
|
+
invalidate() {},
|
|
363
|
+
};
|
|
364
|
+
}
|
|
255
365
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
366
|
+
function renderWatchCall(args: GithubToolRenderArgs, options: RenderResultOptions, theme: Theme): Component {
|
|
367
|
+
const icon =
|
|
368
|
+
options.spinnerFrame !== undefined
|
|
369
|
+
? formatStatusIcon("running", theme, options.spinnerFrame)
|
|
370
|
+
: formatStatusIcon("pending", theme);
|
|
371
|
+
|
|
372
|
+
const runId = typeof args.run === "string" && args.run.trim().length > 0 ? args.run.trim() : undefined;
|
|
373
|
+
const branch = typeof args.branch === "string" && args.branch.trim().length > 0 ? args.branch.trim() : undefined;
|
|
374
|
+
|
|
375
|
+
const titleText = theme.fg("accent", "GitHub Run Watch");
|
|
376
|
+
let metaText: string;
|
|
377
|
+
if (runId) {
|
|
378
|
+
metaText = theme.fg("muted", `#${runId}`);
|
|
379
|
+
} else if (branch) {
|
|
380
|
+
metaText = theme.fg("text", branch);
|
|
381
|
+
} else {
|
|
382
|
+
metaText = theme.fg("muted", "current HEAD");
|
|
383
|
+
}
|
|
262
384
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
lines.push(`${icon} ${title} ${meta}`);
|
|
268
|
-
} else if (branch) {
|
|
269
|
-
// "⠋ GitHub Run Watch feature-branch"
|
|
270
|
-
const title = uiTheme.fg("accent", "GitHub Run Watch");
|
|
271
|
-
const meta = uiTheme.fg("text", branch);
|
|
272
|
-
lines.push(`${icon} ${title} ${meta}`);
|
|
273
|
-
} else {
|
|
274
|
-
// "⠋ GitHub Run Watch current HEAD"
|
|
275
|
-
const title = uiTheme.fg("accent", "GitHub Run Watch");
|
|
276
|
-
const meta = uiTheme.fg("muted", "current HEAD");
|
|
277
|
-
lines.push(`${icon} ${title} ${meta}`);
|
|
278
|
-
}
|
|
385
|
+
const header = `${icon} ${titleText} ${metaText}`;
|
|
386
|
+
const wait = theme.fg("dim", " waiting for workflow data...");
|
|
387
|
+
return new Text(`${header}\n${wait}`, 0, 0);
|
|
388
|
+
}
|
|
279
389
|
|
|
280
|
-
|
|
390
|
+
export const githubToolRenderer = {
|
|
391
|
+
renderCall(args: GithubToolRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component {
|
|
392
|
+
const op = typeof args.op === "string" && args.op.trim().length > 0 ? args.op.trim() : undefined;
|
|
393
|
+
if (op === "run_watch") {
|
|
394
|
+
return renderWatchCall({ ...args, op }, options, uiTheme);
|
|
395
|
+
}
|
|
281
396
|
|
|
282
|
-
|
|
397
|
+
const status: ToolUIStatus = options.spinnerFrame !== undefined ? "running" : "pending";
|
|
398
|
+
const header = renderStatusLine(
|
|
399
|
+
{
|
|
400
|
+
icon: status,
|
|
401
|
+
spinnerFrame: options.spinnerFrame,
|
|
402
|
+
title: formatOpTitle(op),
|
|
403
|
+
meta: buildOpMeta({ ...args, op }),
|
|
404
|
+
},
|
|
405
|
+
uiTheme,
|
|
406
|
+
);
|
|
407
|
+
return new Text(header, 0, 0);
|
|
283
408
|
},
|
|
284
409
|
|
|
285
410
|
renderResult(
|
|
286
411
|
result: { content: Array<{ type: string; text?: string }>; details?: GhToolDetails; isError?: boolean },
|
|
287
412
|
options: RenderResultOptions,
|
|
288
413
|
uiTheme: Theme,
|
|
414
|
+
args?: GithubToolRenderArgs,
|
|
289
415
|
): Component {
|
|
290
416
|
const watch = result.details?.watch;
|
|
291
|
-
if (
|
|
292
|
-
return
|
|
417
|
+
if (watch) {
|
|
418
|
+
return {
|
|
419
|
+
render(width: number): string[] {
|
|
420
|
+
const lineWidth = Math.max(24, width || FALLBACK_WIDTH);
|
|
421
|
+
return buildWatchLines(watch, uiTheme, options, lineWidth).map(line => truncateToWidth(line, lineWidth));
|
|
422
|
+
},
|
|
423
|
+
invalidate() {},
|
|
424
|
+
};
|
|
293
425
|
}
|
|
294
426
|
|
|
295
|
-
return {
|
|
296
|
-
render(width: number): string[] {
|
|
297
|
-
const lineWidth = Math.max(24, width || FALLBACK_WIDTH);
|
|
298
|
-
return buildRenderedLines(watch, uiTheme, options, lineWidth).map(line => truncateToWidth(line, lineWidth));
|
|
299
|
-
},
|
|
300
|
-
invalidate() {},
|
|
301
|
-
};
|
|
427
|
+
return renderFallbackComponent(result, options, uiTheme, args ?? {});
|
|
302
428
|
},
|
|
303
429
|
|
|
304
430
|
mergeCallAndResult: true,
|
|
305
|
-
inline: true,
|
|
306
431
|
};
|
package/src/tools/path-utils.ts
CHANGED
|
@@ -47,15 +47,6 @@ function fileExists(filePath: string): boolean {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
async function pathExists(filePath: string): Promise<boolean> {
|
|
51
|
-
try {
|
|
52
|
-
await fs.promises.access(filePath, fs.constants.F_OK);
|
|
53
|
-
return true;
|
|
54
|
-
} catch {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
50
|
function normalizeAtPrefix(filePath: string): string {
|
|
60
51
|
if (!filePath.startsWith("@")) return filePath;
|
|
61
52
|
|
|
@@ -210,11 +201,17 @@ export interface ParsedFindPattern {
|
|
|
210
201
|
hasGlob: boolean;
|
|
211
202
|
}
|
|
212
203
|
|
|
204
|
+
export interface ResolvedSearchTarget {
|
|
205
|
+
basePath: string;
|
|
206
|
+
glob?: string;
|
|
207
|
+
}
|
|
208
|
+
|
|
213
209
|
export interface ResolvedMultiSearchPath {
|
|
214
210
|
basePath: string;
|
|
215
211
|
glob?: string;
|
|
216
212
|
scopePath: string;
|
|
217
213
|
exactFilePaths?: string[];
|
|
214
|
+
targets?: ResolvedSearchTarget[];
|
|
218
215
|
}
|
|
219
216
|
|
|
220
217
|
export interface ResolvedMultiFindPattern {
|
|
@@ -294,77 +291,6 @@ export function combineSearchGlobs(prefixGlob?: string, suffixGlob?: string): st
|
|
|
294
291
|
return `${normalizedPrefix}/${normalizedSuffix}`;
|
|
295
292
|
}
|
|
296
293
|
|
|
297
|
-
type TopLevelSeparator = "comma" | "whitespace";
|
|
298
|
-
|
|
299
|
-
function splitTopLevel(value: string, separator: TopLevelSeparator): string[] {
|
|
300
|
-
const parts: string[] = [];
|
|
301
|
-
let current = "";
|
|
302
|
-
let braceDepth = 0;
|
|
303
|
-
let bracketDepth = 0;
|
|
304
|
-
let parenDepth = 0;
|
|
305
|
-
let quote: '"' | "'" | undefined;
|
|
306
|
-
let escaped = false;
|
|
307
|
-
|
|
308
|
-
const pushCurrent = () => {
|
|
309
|
-
const normalized = current.trim();
|
|
310
|
-
if (normalized.length > 0) {
|
|
311
|
-
parts.push(normalized);
|
|
312
|
-
}
|
|
313
|
-
current = "";
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
for (const char of value) {
|
|
317
|
-
if (escaped) {
|
|
318
|
-
current += char;
|
|
319
|
-
escaped = false;
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (char === "\\") {
|
|
324
|
-
current += char;
|
|
325
|
-
escaped = true;
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (quote) {
|
|
330
|
-
current += char;
|
|
331
|
-
if (char === quote) {
|
|
332
|
-
quote = undefined;
|
|
333
|
-
}
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (char === '"' || char === "'") {
|
|
338
|
-
quote = char;
|
|
339
|
-
current += char;
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (char === "{") braceDepth += 1;
|
|
344
|
-
else if (char === "}" && braceDepth > 0) braceDepth -= 1;
|
|
345
|
-
else if (char === "[") bracketDepth += 1;
|
|
346
|
-
else if (char === "]" && bracketDepth > 0) bracketDepth -= 1;
|
|
347
|
-
else if (char === "(") parenDepth += 1;
|
|
348
|
-
else if (char === ")" && parenDepth > 0) parenDepth -= 1;
|
|
349
|
-
|
|
350
|
-
const topLevel = braceDepth === 0 && bracketDepth === 0 && parenDepth === 0;
|
|
351
|
-
const isWhitespace = /\s/.test(char);
|
|
352
|
-
if (topLevel && separator === "comma" && char === ",") {
|
|
353
|
-
pushCurrent();
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
if (topLevel && separator === "whitespace" && isWhitespace) {
|
|
357
|
-
pushCurrent();
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
current += char;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
pushCurrent();
|
|
365
|
-
return parts.length > 1 ? parts : [value.trim()];
|
|
366
|
-
}
|
|
367
|
-
|
|
368
294
|
function normalizePosixPath(filePath: string): string {
|
|
369
295
|
return filePath.replace(/\\/g, "/");
|
|
370
296
|
}
|
|
@@ -412,121 +338,12 @@ function toScopeDisplay(items: string[], cwd: string): string {
|
|
|
412
338
|
.join(", ");
|
|
413
339
|
}
|
|
414
340
|
|
|
415
|
-
function
|
|
416
|
-
|
|
417
|
-
TOP_LEVEL_INTERNAL_URL_PREFIXES.some(prefix => token.startsWith(prefix)) ||
|
|
418
|
-
token.startsWith(".") ||
|
|
419
|
-
token.startsWith("/") ||
|
|
420
|
-
token.startsWith("~") ||
|
|
421
|
-
token.startsWith("@") ||
|
|
422
|
-
token.includes("/") ||
|
|
423
|
-
token.includes("\\") ||
|
|
424
|
-
hasGlobPathChars(token) ||
|
|
425
|
-
/\.[^./\\]+$/.test(token)
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
async function areDelimitedTokensResolvable(
|
|
430
|
-
tokens: string[],
|
|
431
|
-
cwd: string,
|
|
432
|
-
parseBasePath: (value: string) => string,
|
|
433
|
-
allowBareExistingTokens: boolean,
|
|
434
|
-
): Promise<boolean> {
|
|
435
|
-
for (const token of tokens) {
|
|
436
|
-
if (TOP_LEVEL_INTERNAL_URL_PREFIXES.some(prefix => token.startsWith(prefix))) {
|
|
437
|
-
return false;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (!allowBareExistingTokens && !looksLikeDelimitedPathToken(token)) {
|
|
441
|
-
// Bare names like "packages" don't look like path tokens syntactically,
|
|
442
|
-
// but may still be valid directory names. Check existence before rejecting.
|
|
443
|
-
const resolvedExactPath = resolveToCwd(token, cwd);
|
|
444
|
-
if (!(await pathExists(resolvedExactPath))) {
|
|
445
|
-
return false;
|
|
446
|
-
}
|
|
447
|
-
continue;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
const basePath = parseBasePath(token);
|
|
451
|
-
const resolvedBasePath = resolveToCwd(basePath, cwd);
|
|
452
|
-
if (await pathExists(resolvedBasePath)) {
|
|
453
|
-
continue;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (!allowBareExistingTokens) {
|
|
457
|
-
return false;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
const resolvedExactPath = resolveToCwd(token, cwd);
|
|
461
|
-
if (!(await pathExists(resolvedExactPath))) {
|
|
462
|
-
return false;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
return true;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
async function filterResolvableTokens(
|
|
470
|
-
tokens: string[],
|
|
471
|
-
cwd: string,
|
|
472
|
-
parseBasePath: (value: string) => string,
|
|
473
|
-
): Promise<string[]> {
|
|
474
|
-
const out: string[] = [];
|
|
475
|
-
for (const token of tokens) {
|
|
476
|
-
if (TOP_LEVEL_INTERNAL_URL_PREFIXES.some(prefix => token.startsWith(prefix))) continue;
|
|
477
|
-
const basePath = parseBasePath(token);
|
|
478
|
-
const resolvedBasePath = resolveToCwd(basePath, cwd);
|
|
479
|
-
if (await pathExists(resolvedBasePath)) {
|
|
480
|
-
out.push(token);
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
const resolvedExactPath = resolveToCwd(token, cwd);
|
|
484
|
-
if (await pathExists(resolvedExactPath)) {
|
|
485
|
-
out.push(token);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
return out;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
async function splitDelimitedSearchInput(
|
|
492
|
-
rawInput: string,
|
|
493
|
-
cwd: string,
|
|
494
|
-
parseBasePath: (value: string) => string,
|
|
495
|
-
): Promise<string[] | undefined> {
|
|
496
|
-
const trimmed = rawInput.trim();
|
|
497
|
-
if (!trimmed) return undefined;
|
|
498
|
-
|
|
499
|
-
const resolvedExactPath = resolveToCwd(trimmed, cwd);
|
|
500
|
-
if (await pathExists(resolvedExactPath)) {
|
|
501
|
-
return undefined;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const commaSeparated = splitTopLevel(trimmed, "comma");
|
|
505
|
-
if (commaSeparated.length > 1) {
|
|
506
|
-
const resolvable = await filterResolvableTokens(commaSeparated, cwd, parseBasePath);
|
|
507
|
-
if (resolvable.length >= 1) {
|
|
508
|
-
return [...new Set(resolvable)];
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
const whitespaceSeparated = splitTopLevel(trimmed, "whitespace");
|
|
513
|
-
if (
|
|
514
|
-
whitespaceSeparated.length > 1 &&
|
|
515
|
-
(await areDelimitedTokensResolvable(whitespaceSeparated, cwd, parseBasePath, false))
|
|
516
|
-
) {
|
|
517
|
-
return [...new Set(whitespaceSeparated)];
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
return undefined;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
export async function resolveMultiSearchPath(
|
|
524
|
-
rawPath: string,
|
|
341
|
+
async function resolveSearchPathItems(
|
|
342
|
+
pathItems: string[],
|
|
525
343
|
cwd: string,
|
|
526
344
|
suffixGlob?: string,
|
|
527
345
|
): Promise<ResolvedMultiSearchPath | undefined> {
|
|
528
|
-
|
|
529
|
-
if (!pathItems || pathItems.length < 1) {
|
|
346
|
+
if (pathItems.length < 1) {
|
|
530
347
|
return undefined;
|
|
531
348
|
}
|
|
532
349
|
|
|
@@ -556,21 +373,37 @@ export async function resolveMultiSearchPath(
|
|
|
556
373
|
}
|
|
557
374
|
return relativeBasePath === "." ? path.basename(item.absoluteBasePath) : relativeBasePath;
|
|
558
375
|
});
|
|
376
|
+
const rootPath = path.parse(commonBasePath).root;
|
|
377
|
+
const isDegenerateRoot = commonBasePath === rootPath && parsedItems.length > 1;
|
|
378
|
+
const targets = isDegenerateRoot
|
|
379
|
+
? parsedItems.map(item => ({
|
|
380
|
+
basePath: item.absoluteBasePath,
|
|
381
|
+
glob: item.parsedPath.glob ? combineSearchGlobs(item.parsedPath.glob, suffixGlob) : suffixGlob,
|
|
382
|
+
}))
|
|
383
|
+
: undefined;
|
|
559
384
|
|
|
560
385
|
return {
|
|
561
386
|
basePath: commonBasePath,
|
|
562
387
|
glob: buildBraceUnion(combinedPatterns),
|
|
563
388
|
scopePath: toScopeDisplay(pathItems, cwd),
|
|
564
389
|
exactFilePaths: allExactFiles ? parsedItems.map(item => item.absoluteBasePath) : undefined,
|
|
390
|
+
targets,
|
|
565
391
|
};
|
|
566
392
|
}
|
|
567
393
|
|
|
568
|
-
export async function
|
|
569
|
-
|
|
394
|
+
export async function resolveExplicitSearchPaths(
|
|
395
|
+
pathItems: string[],
|
|
396
|
+
cwd: string,
|
|
397
|
+
suffixGlob?: string,
|
|
398
|
+
): Promise<ResolvedMultiSearchPath | undefined> {
|
|
399
|
+
return resolveSearchPathItems([...new Set(pathItems)], cwd, suffixGlob);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async function resolveFindPatternItems(
|
|
403
|
+
patternItems: string[],
|
|
570
404
|
cwd: string,
|
|
571
405
|
): Promise<ResolvedMultiFindPattern | undefined> {
|
|
572
|
-
|
|
573
|
-
if (!patternItems || patternItems.length <= 1) {
|
|
406
|
+
if (patternItems.length <= 1) {
|
|
574
407
|
return undefined;
|
|
575
408
|
}
|
|
576
409
|
|
|
@@ -602,6 +435,13 @@ export async function resolveMultiFindPattern(
|
|
|
602
435
|
};
|
|
603
436
|
}
|
|
604
437
|
|
|
438
|
+
export async function resolveExplicitFindPatterns(
|
|
439
|
+
patternItems: string[],
|
|
440
|
+
cwd: string,
|
|
441
|
+
): Promise<ResolvedMultiFindPattern | undefined> {
|
|
442
|
+
return resolveFindPatternItems([...new Set(patternItems)], cwd);
|
|
443
|
+
}
|
|
444
|
+
|
|
605
445
|
export function resolveReadPath(filePath: string, cwd: string): string {
|
|
606
446
|
const resolved = resolveToCwd(filePath, cwd);
|
|
607
447
|
const shellEscapedVariant = tryShellEscapedPath(resolved);
|