codeep 2.15.0 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -7
- package/dist/acp/serverHandlers.js +1 -1
- package/dist/acp/session.js +22 -1
- package/dist/config/index.js +20 -4
- package/dist/config/providers.d.ts +3 -2
- package/dist/config/providers.js +163 -69
- package/dist/renderer/App.d.ts +89 -0
- package/dist/renderer/App.js +637 -43
- package/dist/renderer/Screen.d.ts +1 -0
- package/dist/renderer/Screen.js +8 -3
- package/dist/renderer/commands/helpers.d.ts +189 -0
- package/dist/renderer/commands/helpers.js +345 -0
- package/dist/renderer/commands/registry.js +2 -1
- package/dist/renderer/commands.js +218 -267
- package/dist/renderer/components/AgentTimeline.d.ts +44 -0
- package/dist/renderer/components/AgentTimeline.js +157 -0
- package/dist/renderer/components/Autocomplete.d.ts +25 -0
- package/dist/renderer/components/Autocomplete.js +35 -0
- package/dist/renderer/components/Status.d.ts +2 -0
- package/dist/renderer/layout.d.ts +5 -1
- package/dist/renderer/layout.js +12 -0
- package/dist/renderer/main.js +110 -30
- package/dist/utils/agent.js +1 -1
- package/dist/utils/agents.d.ts +1 -1
- package/dist/utils/agents.js +1 -1
- package/dist/utils/checkpoints.d.ts +1 -1
- package/dist/utils/checkpoints.js +1 -1
- package/dist/utils/diffPreview.d.ts +31 -0
- package/dist/utils/diffPreview.js +102 -0
- package/dist/utils/git.d.ts +28 -0
- package/dist/utils/git.js +111 -1
- package/dist/utils/mentions.d.ts +195 -0
- package/dist/utils/mentions.js +672 -0
- package/dist/utils/resourceImpact.d.ts +25 -0
- package/dist/utils/resourceImpact.js +54 -0
- package/dist/utils/tokenTracker.js +52 -37
- package/dist/utils/webFetch.d.ts +101 -0
- package/dist/utils/webFetch.js +375 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
|
@@ -404,3 +404,105 @@ export function getDiffStats(diffs) {
|
|
|
404
404
|
totalFiles: diffs.length,
|
|
405
405
|
};
|
|
406
406
|
}
|
|
407
|
+
// ─── Selective apply (per-hunk accept/reject) ───────────────────────────────
|
|
408
|
+
/**
|
|
409
|
+
* Apply a subset of a file diff's hunks to the original content.
|
|
410
|
+
*
|
|
411
|
+
* Hunk indices in `acceptedHunks` refer to positions in `diff.hunks`
|
|
412
|
+
* (0-based). Hunks not in the set are skipped — their original lines
|
|
413
|
+
* stay, their additions are dropped.
|
|
414
|
+
*
|
|
415
|
+
* Returns the resulting file content. The caller writes it to disk.
|
|
416
|
+
*
|
|
417
|
+
* For `type === 'create'`, the whole file is either accepted (any hunk
|
|
418
|
+
* accepted) or rejected (empty set) — there's no original to merge
|
|
419
|
+
* against. For `type === 'delete'`, accepting any hunk deletes the file.
|
|
420
|
+
*/
|
|
421
|
+
export function applyHunks(diff, acceptedHunks) {
|
|
422
|
+
// Create: accept-all-or-nothing — there's no original content to
|
|
423
|
+
// selectively merge into.
|
|
424
|
+
if (diff.type === 'create') {
|
|
425
|
+
return acceptedHunks.size > 0 ? (diff.newContent ?? '') : (diff.oldContent ?? '');
|
|
426
|
+
}
|
|
427
|
+
const oldLines = (diff.oldContent ?? '').split('\n');
|
|
428
|
+
// No accepted hunks → original content unchanged.
|
|
429
|
+
if (acceptedHunks.size === 0) {
|
|
430
|
+
return oldLines.join('\n');
|
|
431
|
+
}
|
|
432
|
+
const acceptedHunkList = diff.hunks
|
|
433
|
+
.map((h, i) => ({ hunk: h, index: i }))
|
|
434
|
+
.filter(({ index }) => acceptedHunks.has(index));
|
|
435
|
+
// Per-original-line union model.
|
|
436
|
+
//
|
|
437
|
+
// The obvious implementation — walk the hunks in order, copying original
|
|
438
|
+
// lines between them and replaying each hunk's line list — is wrong for
|
|
439
|
+
// hunks that sit close together, because unified-diff context OVERLAPS:
|
|
440
|
+
// hunk N's trailing context is hunk N+1's leading context, so the shared
|
|
441
|
+
// lines get emitted twice. Worse, with a small gap a later hunk's `remove`
|
|
442
|
+
// can fall *inside* an earlier hunk's already-emitted context, so the
|
|
443
|
+
// deletion is silently lost. Both corrupt the user's file on `/apply`.
|
|
444
|
+
//
|
|
445
|
+
// Instead reduce every accepted hunk to two facts per original line — is it
|
|
446
|
+
// removed, and what is inserted after it — then rebuild the file once.
|
|
447
|
+
// Overlap becomes a set union rather than double emission, and the result
|
|
448
|
+
// is independent of hunk order.
|
|
449
|
+
const removed = new Set(); // old line numbers deleted
|
|
450
|
+
const insertAfter = new Map(); // old line number → inserted lines (0 = file head)
|
|
451
|
+
for (const { hunk } of acceptedHunkList) {
|
|
452
|
+
// Adds before any context in this hunk belong right before its start,
|
|
453
|
+
// not at the top of the file.
|
|
454
|
+
let anchor = Math.max(0, hunk.oldStart - 1);
|
|
455
|
+
for (const line of hunk.lines) {
|
|
456
|
+
if (line.type === 'add') {
|
|
457
|
+
const at = insertAfter.get(anchor);
|
|
458
|
+
if (at)
|
|
459
|
+
at.push(line.content);
|
|
460
|
+
else
|
|
461
|
+
insertAfter.set(anchor, [line.content]);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (line.oldLineNum === undefined)
|
|
465
|
+
continue;
|
|
466
|
+
anchor = line.oldLineNum;
|
|
467
|
+
if (line.type === 'remove')
|
|
468
|
+
removed.add(line.oldLineNum);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const result = [];
|
|
472
|
+
for (const head of insertAfter.get(0) ?? [])
|
|
473
|
+
result.push(head);
|
|
474
|
+
for (let n = 1; n <= oldLines.length; n++) {
|
|
475
|
+
if (!removed.has(n))
|
|
476
|
+
result.push(oldLines[n - 1]);
|
|
477
|
+
const added = insertAfter.get(n);
|
|
478
|
+
if (added)
|
|
479
|
+
for (const line of added)
|
|
480
|
+
result.push(line);
|
|
481
|
+
}
|
|
482
|
+
return result.join('\n');
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Apply accepted hunks across multiple file diffs and return the
|
|
486
|
+
* resulting content for each. The caller writes the files to disk.
|
|
487
|
+
*
|
|
488
|
+
* `accepted` maps file path → set of accepted hunk indices. Files not
|
|
489
|
+
* in the map are skipped entirely.
|
|
490
|
+
*/
|
|
491
|
+
export function applyHunksToFiles(diffs, accepted) {
|
|
492
|
+
const results = [];
|
|
493
|
+
for (const diff of diffs) {
|
|
494
|
+
const acceptedSet = accepted.get(diff.path);
|
|
495
|
+
if (!acceptedSet)
|
|
496
|
+
continue;
|
|
497
|
+
const content = applyHunks(diff, acceptedSet);
|
|
498
|
+
results.push({ path: diff.path, content, type: diff.type });
|
|
499
|
+
}
|
|
500
|
+
return results;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Count how many hunks in a diff contain actual changes (not just
|
|
504
|
+
* context). Used to label hunks in the UI ("hunk 2/5").
|
|
505
|
+
*/
|
|
506
|
+
export function countChangeHunks(diff) {
|
|
507
|
+
return diff.hunks.filter((h) => h.lines.some((l) => l.type === 'add' || l.type === 'remove')).length;
|
|
508
|
+
}
|
package/dist/utils/git.d.ts
CHANGED
|
@@ -83,3 +83,31 @@ export declare function createBranchAndCommit(prompt: string, actions: ActionLog
|
|
|
83
83
|
hash?: string;
|
|
84
84
|
error?: string;
|
|
85
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* Result of resolving a `@git <ref>` mention.
|
|
88
|
+
*/
|
|
89
|
+
export interface GitContentResult {
|
|
90
|
+
success: boolean;
|
|
91
|
+
/** Raw output from git (diff text, file content, or commit metadata). */
|
|
92
|
+
content: string;
|
|
93
|
+
/** A short label for the [Attached files]-style block header. */
|
|
94
|
+
label: string;
|
|
95
|
+
error?: string;
|
|
96
|
+
}
|
|
97
|
+
/** Max bytes we'll inline from a single `@git` mention. */
|
|
98
|
+
export declare const MAX_GIT_BYTES: number;
|
|
99
|
+
export declare function isSafeGitRef(token: string): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Resolve a `@git <ref>` mention to inline content. The `ref` can be:
|
|
102
|
+
*
|
|
103
|
+
* - `diff` — unstaged changes (`git diff`)
|
|
104
|
+
* - `diff --staged` — staged changes (`git diff --cached`)
|
|
105
|
+
* - `diff a..b` — diff between two refs (`git diff a..b`)
|
|
106
|
+
* - `HEAD` — the latest commit's full diff vs its parent
|
|
107
|
+
* - `<sha>` — a specific commit's patch (`git show <sha>`)
|
|
108
|
+
* - `<ref>:<path>` — a file at a ref (`git show main:src/x.ts`)
|
|
109
|
+
* - `<ref>` — any other git ref → `git show`
|
|
110
|
+
*
|
|
111
|
+
* Sync (spawn-based) so it slots into the mention-expansion pipeline.
|
|
112
|
+
*/
|
|
113
|
+
export declare function getGitContent(ref: string, cwd?: string): GitContentResult;
|
package/dist/utils/git.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execSync, spawnSync } from 'child_process';
|
|
1
|
+
import { execSync, execFileSync, spawnSync } from 'child_process';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
/**
|
|
@@ -397,3 +397,113 @@ export function createBranchAndCommit(prompt, actions, cwd = process.cwd()) {
|
|
|
397
397
|
hash: commitResult.hash,
|
|
398
398
|
};
|
|
399
399
|
}
|
|
400
|
+
/** Max bytes we'll inline from a single `@git` mention. */
|
|
401
|
+
export const MAX_GIT_BYTES = 64 * 1024;
|
|
402
|
+
/**
|
|
403
|
+
* Characters a git ref/pathspec may contain for `@git` mentions. Deliberately
|
|
404
|
+
* conservative: word chars plus the punctuation real refs use
|
|
405
|
+
* (`main..feature`, `HEAD~3`, `v1.2.0^{}`, `main:src/x.ts`, `origin/main`).
|
|
406
|
+
* A leading `-` is rejected separately so a ref can never be read as a flag.
|
|
407
|
+
*/
|
|
408
|
+
const SAFE_GIT_REF = /^[A-Za-z0-9._/:~^@{}-]+$/;
|
|
409
|
+
export function isSafeGitRef(token) {
|
|
410
|
+
return token.length > 0 && !token.startsWith('-') && SAFE_GIT_REF.test(token);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* The only flags `@git diff …` may pass through. An allowlist rather than a
|
|
414
|
+
* deny-list because several git flags write files or run commands
|
|
415
|
+
* (`--output=`, `--ext-diff`), which would turn a mention into a side effect.
|
|
416
|
+
*/
|
|
417
|
+
const GIT_DIFF_FLAG_ALLOWLIST = new Set([
|
|
418
|
+
'--staged', '--cached', '--stat', '--numstat', '--shortstat',
|
|
419
|
+
'--name-only', '--name-status', '--patch', '-p', '--no-color',
|
|
420
|
+
]);
|
|
421
|
+
/**
|
|
422
|
+
* Resolve a `@git <ref>` mention to inline content. The `ref` can be:
|
|
423
|
+
*
|
|
424
|
+
* - `diff` — unstaged changes (`git diff`)
|
|
425
|
+
* - `diff --staged` — staged changes (`git diff --cached`)
|
|
426
|
+
* - `diff a..b` — diff between two refs (`git diff a..b`)
|
|
427
|
+
* - `HEAD` — the latest commit's full diff vs its parent
|
|
428
|
+
* - `<sha>` — a specific commit's patch (`git show <sha>`)
|
|
429
|
+
* - `<ref>:<path>` — a file at a ref (`git show main:src/x.ts`)
|
|
430
|
+
* - `<ref>` — any other git ref → `git show`
|
|
431
|
+
*
|
|
432
|
+
* Sync (spawn-based) so it slots into the mention-expansion pipeline.
|
|
433
|
+
*/
|
|
434
|
+
export function getGitContent(ref, cwd = process.cwd()) {
|
|
435
|
+
if (!isGitRepository(cwd)) {
|
|
436
|
+
return { success: false, content: '', label: ref, error: 'not a git repository' };
|
|
437
|
+
}
|
|
438
|
+
const trimmed = ref.trim();
|
|
439
|
+
if (!trimmed) {
|
|
440
|
+
return { success: false, content: '', label: ref, error: 'empty git ref' };
|
|
441
|
+
}
|
|
442
|
+
// Pick the git subcommand based on the ref shape. NOTE: the ref comes from
|
|
443
|
+
// free-form prompt text (`@git <ref>`), which may be pasted from an issue,
|
|
444
|
+
// a log, or model output — so it is UNTRUSTED. We therefore (a) build an
|
|
445
|
+
// argv array and spawn git directly with `execFileSync` (no `/bin/sh`, so
|
|
446
|
+
// `;`, `|`, backticks and friends are inert), and (b) validate every token,
|
|
447
|
+
// because argv alone doesn't stop *argument* injection — a ref that starts
|
|
448
|
+
// with `-` would still be read by git as a flag (e.g. `--output=…` writes a
|
|
449
|
+
// file). Anything unrecognized is rejected rather than guessed at.
|
|
450
|
+
let args;
|
|
451
|
+
let label;
|
|
452
|
+
if (trimmed === 'diff') {
|
|
453
|
+
args = ['diff'];
|
|
454
|
+
label = 'diff (unstaged)';
|
|
455
|
+
}
|
|
456
|
+
else if (trimmed === 'diff --staged' || trimmed === 'diff --cached' || trimmed === 'staged') {
|
|
457
|
+
args = ['diff', '--cached'];
|
|
458
|
+
label = 'diff (staged)';
|
|
459
|
+
}
|
|
460
|
+
else if (trimmed.startsWith('diff ')) {
|
|
461
|
+
// e.g. `diff main..feature` or `diff HEAD~3` or `diff --stat`
|
|
462
|
+
const rest = trimmed.slice('diff '.length).trim();
|
|
463
|
+
const tokens = rest.split(/\s+/).filter(Boolean);
|
|
464
|
+
const bad = tokens.find(t => !(isSafeGitRef(t) || GIT_DIFF_FLAG_ALLOWLIST.has(t)));
|
|
465
|
+
if (bad) {
|
|
466
|
+
return { success: false, content: '', label: ref, error: `unsupported git argument: ${bad}` };
|
|
467
|
+
}
|
|
468
|
+
args = ['diff', ...tokens];
|
|
469
|
+
label = `diff (${rest})`;
|
|
470
|
+
}
|
|
471
|
+
else if (trimmed === 'HEAD' || trimmed === '@') {
|
|
472
|
+
// Show the latest commit's patch.
|
|
473
|
+
args = ['show', 'HEAD'];
|
|
474
|
+
label = 'HEAD';
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
// Any other ref → `git show`. Works for SHAs, tags, branches, and
|
|
478
|
+
// `<ref>:<path>` (file-at-ref) forms.
|
|
479
|
+
if (!isSafeGitRef(trimmed)) {
|
|
480
|
+
return { success: false, content: '', label: ref, error: `unsupported git ref: ${trimmed}` };
|
|
481
|
+
}
|
|
482
|
+
args = ['show', trimmed];
|
|
483
|
+
label = trimmed;
|
|
484
|
+
}
|
|
485
|
+
try {
|
|
486
|
+
const out = execFileSync('git', args, {
|
|
487
|
+
cwd,
|
|
488
|
+
encoding: 'utf-8',
|
|
489
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
490
|
+
});
|
|
491
|
+
const content = (out ?? '').trimEnd();
|
|
492
|
+
if (!content) {
|
|
493
|
+
return { success: false, content: '', label, error: 'empty result (no changes / unknown ref)' };
|
|
494
|
+
}
|
|
495
|
+
// Truncate to the cap so a massive diff can't blow the context.
|
|
496
|
+
const capped = content.length > MAX_GIT_BYTES
|
|
497
|
+
? content.slice(0, MAX_GIT_BYTES) + `\n\n… (truncated at ${MAX_GIT_BYTES / 1024}KB)`
|
|
498
|
+
: content;
|
|
499
|
+
return { success: true, content: capped, label };
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
503
|
+
// git show exits non-zero on unknown refs; surface a friendly reason.
|
|
504
|
+
const reason = /unknown revision|bad revision|ambiguous argument/i.test(msg)
|
|
505
|
+
? `unknown git ref: ${trimmed}`
|
|
506
|
+
: msg;
|
|
507
|
+
return { success: false, content: '', label, error: reason };
|
|
508
|
+
}
|
|
509
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@-mention` context expansion for the CLI chat input.
|
|
3
|
+
*
|
|
4
|
+
* When the user types `@path/to/file.ts` inline in their prompt, we
|
|
5
|
+
* detect those mentions, read the file contents, and inject them as an
|
|
6
|
+
* "[Attached files]" block prepended to the prompt — same format as the
|
|
7
|
+
* explicit `/add` command, so the agent sees a single, consistent shape.
|
|
8
|
+
*
|
|
9
|
+
* Supported mention forms (case-sensitive `@`):
|
|
10
|
+
* @src/index.ts → relative-to-project-root file
|
|
11
|
+
* @./local.ts → relative-to-cwd file
|
|
12
|
+
* @/abs/path.ts → absolute path
|
|
13
|
+
* @"path with space.ts" → quoted (spaces/special chars allowed)
|
|
14
|
+
* @'path with space.ts' → single-quoted variant
|
|
15
|
+
*
|
|
16
|
+
* A `@` immediately followed by whitespace, another `@`, or a non-path
|
|
17
|
+
* character (e.g. an email like `user@host`, or a GitHub `@handle`) is
|
|
18
|
+
* left untouched.
|
|
19
|
+
*
|
|
20
|
+
* Mentions are resolved against the project root (or cwd when no
|
|
21
|
+
* project is open). Files larger than `MAX_MENTION_BYTES` are skipped
|
|
22
|
+
* with a warning rather than silently truncated — the user should
|
|
23
|
+
* explicitly `/add` very large files if they really want them.
|
|
24
|
+
*/
|
|
25
|
+
/** Max file size we'll auto-inline from a mention (100 KB). */
|
|
26
|
+
export declare const MAX_MENTION_BYTES: number;
|
|
27
|
+
/** Result of expanding `@-mentions` in a prompt. */
|
|
28
|
+
export interface MentionExpansionResult {
|
|
29
|
+
/** The prompt with file contents prepended (or the original if no mentions). */
|
|
30
|
+
enrichedPrompt: string;
|
|
31
|
+
/**
|
|
32
|
+
* The prompt with each mention's `@` sigil removed but WITHOUT the attached
|
|
33
|
+
* block — i.e. `enrichedPrompt` minus its header. Callers that merge several
|
|
34
|
+
* expanders into one block need this: parsing the block back out of
|
|
35
|
+
* `enrichedPrompt` with a regex silently left the file bodies behind and
|
|
36
|
+
* attached every mentioned file twice.
|
|
37
|
+
*/
|
|
38
|
+
strippedPrompt: string;
|
|
39
|
+
/** Successfully loaded files: `[fullPath, relativePath, content][]`. */
|
|
40
|
+
loaded: Array<{
|
|
41
|
+
fullPath: string;
|
|
42
|
+
relativePath: string;
|
|
43
|
+
content: string;
|
|
44
|
+
}>;
|
|
45
|
+
/** Mentions that couldn't be resolved, with a human-readable reason. */
|
|
46
|
+
failures: Array<{
|
|
47
|
+
mention: string;
|
|
48
|
+
reason: string;
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The raw text of a mention match (without the leading `@`).
|
|
53
|
+
* Used internally by the tokenizer.
|
|
54
|
+
*/
|
|
55
|
+
interface MentionToken {
|
|
56
|
+
/** Full match including `@`, for replacement. */
|
|
57
|
+
raw: string;
|
|
58
|
+
/** The path portion (without quotes if it was quoted). */
|
|
59
|
+
path: string;
|
|
60
|
+
/** Start index in the source string. */
|
|
61
|
+
start: number;
|
|
62
|
+
/** End index (exclusive). */
|
|
63
|
+
end: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The single source of truth for "may a mention start after this character?".
|
|
67
|
+
* `MENTION_RE`'s lookbehind above and the editor's `detectMentionQuery` picker
|
|
68
|
+
* MUST agree — when they diverged, the picker happily completed mentions
|
|
69
|
+
* (e.g. after `]`) that the expander then ignored, so the file silently never
|
|
70
|
+
* got attached. Import this rather than re-spelling the class.
|
|
71
|
+
*/
|
|
72
|
+
export declare const MENTION_BOUNDARY: RegExp;
|
|
73
|
+
/**
|
|
74
|
+
* Extract all `@-mention` tokens from `text`. Returns them in document
|
|
75
|
+
* order. Pure (no FS) — testable without touching the disk.
|
|
76
|
+
*/
|
|
77
|
+
export declare function extractMentions(text: string): MentionToken[];
|
|
78
|
+
export interface MentionExpansionOptions {
|
|
79
|
+
/**
|
|
80
|
+
* The root directory mentions are resolved against when they're
|
|
81
|
+
* relative (not starting with `/` or `.`). Usually the project root
|
|
82
|
+
* or `process.cwd()`.
|
|
83
|
+
*/
|
|
84
|
+
root: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Expand all `@-mentions` in `prompt`: load each referenced file,
|
|
88
|
+
* prepend the contents as an `[Attached files]` block, and strip the
|
|
89
|
+
* `@path` tokens from the visible prompt (replacing them with a bare
|
|
90
|
+
* path so the agent still sees what was referenced).
|
|
91
|
+
*
|
|
92
|
+
* Failures (missing file, too large, not a file) are collected and
|
|
93
|
+
* returned rather than thrown — the caller decides how to surface them.
|
|
94
|
+
*/
|
|
95
|
+
export declare function expandMentions(prompt: string, opts: MentionExpansionOptions): MentionExpansionResult;
|
|
96
|
+
/**
|
|
97
|
+
* Max total bytes of file content we'll inline from a single `@folder`
|
|
98
|
+
* mention (200 KB). Prevents a huge directory from blowing the context
|
|
99
|
+
* window — the user can raise this via explicit `/add` if they really
|
|
100
|
+
* want everything.
|
|
101
|
+
*/
|
|
102
|
+
export declare const MAX_FOLDER_BYTES: number;
|
|
103
|
+
/** One `@folder <path>` mention match. */
|
|
104
|
+
interface FolderToken {
|
|
105
|
+
/** Full match including `@folder `, for display in failures. */
|
|
106
|
+
raw: string;
|
|
107
|
+
/** The path portion (after `@folder `). */
|
|
108
|
+
path: string;
|
|
109
|
+
/** Start index of the match (pointing at the `@`). */
|
|
110
|
+
start: number;
|
|
111
|
+
/** End index (exclusive). */
|
|
112
|
+
end: number;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Extract all `@folder`/`@dir` mentions from `text`. Pure (no FS).
|
|
116
|
+
* Returns them in document order.
|
|
117
|
+
*/
|
|
118
|
+
export declare function extractFolderMentions(text: string): FolderToken[];
|
|
119
|
+
/**
|
|
120
|
+
* Expand all `@folder`/`@dir` mentions in `prompt`: recursively read
|
|
121
|
+
* every source file under each directory, and return them in the same
|
|
122
|
+
* shape as `expandMentions` (so the caller can merge the results).
|
|
123
|
+
*
|
|
124
|
+
* Skips the same ignored directories (`node_modules`, `.git`, …) and
|
|
125
|
+
* binary/generated extensions as the autocomplete scanner. Caps total
|
|
126
|
+
* content per mention at `MAX_FOLDER_BYTES` so a single huge tree
|
|
127
|
+
* can't blow the context window.
|
|
128
|
+
*
|
|
129
|
+
* Sync (filesystem reads only) — call before or after `expandMentions`.
|
|
130
|
+
*/
|
|
131
|
+
export declare function expandFolderMentions(prompt: string, opts: MentionExpansionOptions): MentionExpansionResult;
|
|
132
|
+
/**
|
|
133
|
+
* Expand both `@folder` and `@file` mentions in one pass, merging the
|
|
134
|
+
* loaded files into a single `[Attached files]` block (instead of two
|
|
135
|
+
* separate blocks when called back-to-back).
|
|
136
|
+
*
|
|
137
|
+
* `@web` mentions are async and handled separately in `webFetch.ts`.
|
|
138
|
+
*/
|
|
139
|
+
export declare function expandFileAndFolderMentions(prompt: string, opts: MentionExpansionOptions): MentionExpansionResult;
|
|
140
|
+
/** Format the `[Attached files]` block prepended to the enriched prompt. */
|
|
141
|
+
export declare function formatFileBlock(files: Array<{
|
|
142
|
+
relativePath: string;
|
|
143
|
+
content: string;
|
|
144
|
+
}>): string;
|
|
145
|
+
export interface MentionSuggestion {
|
|
146
|
+
/** Display label for the picker (e.g. `src/index.ts`). */
|
|
147
|
+
label: string;
|
|
148
|
+
/** The path to insert after `@` when picked. */
|
|
149
|
+
insertPath: string;
|
|
150
|
+
/** A short hint — the file's directory or type. */
|
|
151
|
+
detail: string;
|
|
152
|
+
}
|
|
153
|
+
export interface SuggestOptions {
|
|
154
|
+
/** Root directory to scan. */
|
|
155
|
+
root: string;
|
|
156
|
+
/** Filter prefix typed so far (e.g. `src/ind` from `@src/ind`). */
|
|
157
|
+
query?: string;
|
|
158
|
+
/** Max suggestions to return. */
|
|
159
|
+
limit?: number;
|
|
160
|
+
/** Extra directories to skip (merged with the defaults). */
|
|
161
|
+
extraIgnoreDirs?: string[];
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Build (or reuse from cache) the flat list of suggestible files under
|
|
165
|
+
* `root`, then filter by `query`. The scan walks up to `maxScan` files,
|
|
166
|
+
* skipping ignored directories and binary/generated extensions.
|
|
167
|
+
*/
|
|
168
|
+
export declare function suggestMentions(opts: SuggestOptions): MentionSuggestion[];
|
|
169
|
+
/** Clear the suggestion cache. Call between tests so fixtures don't leak. */
|
|
170
|
+
export declare function clearSuggestionCache(): void;
|
|
171
|
+
/** True if `fullPath`'s basename looks like it holds secrets. */
|
|
172
|
+
export declare function isSensitiveFile(fullPath: string): boolean;
|
|
173
|
+
/** One `@git <ref>` mention match. */
|
|
174
|
+
interface GitToken {
|
|
175
|
+
raw: string;
|
|
176
|
+
ref: string;
|
|
177
|
+
start: number;
|
|
178
|
+
end: number;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Extract all `@git <ref>` mentions from `text`. Pure (no FS / no git).
|
|
182
|
+
*/
|
|
183
|
+
export declare function extractGitMentions(text: string): GitToken[];
|
|
184
|
+
/**
|
|
185
|
+
* Expand all `@git <ref>` mentions in `prompt`: resolve each ref to
|
|
186
|
+
* git content (diff, file-at-ref, or commit patch) and inject it as
|
|
187
|
+
* a `[Git ref]` block. Sync (git is run via `execSync`).
|
|
188
|
+
*
|
|
189
|
+
* The block is appended *after* any `[Attached files]` block from
|
|
190
|
+
* `@folder`/`@file` expansion, so the final prompt reads:
|
|
191
|
+
*
|
|
192
|
+
* [Attached files] … [Git ref] … <user text>
|
|
193
|
+
*/
|
|
194
|
+
export declare function expandGitMentions(prompt: string, opts: MentionExpansionOptions): Promise<MentionExpansionResult>;
|
|
195
|
+
export {};
|