@nexrall/code-core 1.4.76 → 1.4.77
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/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/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 +7 -0
- 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 +33 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -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,310 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// `/pin` and `/exclude` — the two lists that decide which project files ride
|
|
3
|
+
// along WITHOUT the user retyping them:
|
|
4
|
+
//
|
|
5
|
+
// /pin src/auth.ts keep this file in front of the model even after a
|
|
6
|
+
// /compact rewrites the conversation away
|
|
7
|
+
// /exclude generated/ never suggest it in the @ menu, never attach it
|
|
8
|
+
//
|
|
9
|
+
// Both lists live per project in ~/.nexrall/cli-context/<name>-<hash>.json so
|
|
10
|
+
// they survive restarts (the whole point of a pin is that it is durable, and an
|
|
11
|
+
// exclusion the user has to retype every morning is worse than useless).
|
|
12
|
+
// Pure string/path logic plus a couple of tiny fs helpers, so every rule here
|
|
13
|
+
// is unit-testable — see test/contextFiles.test.ts.
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.EMPTY_CONTEXT_SET = void 0;
|
|
49
|
+
exports.contextSetPath = contextSetPath;
|
|
50
|
+
exports.loadContextSet = loadContextSet;
|
|
51
|
+
exports.saveContextSet = saveContextSet;
|
|
52
|
+
exports.relInProject = relInProject;
|
|
53
|
+
exports.globToRegExp = globToRegExp;
|
|
54
|
+
exports.isExcluded = isExcluded;
|
|
55
|
+
exports.addPinned = addPinned;
|
|
56
|
+
exports.removePinned = removePinned;
|
|
57
|
+
exports.addExcluded = addExcluded;
|
|
58
|
+
exports.removeExcluded = removeExcluded;
|
|
59
|
+
exports.clearPinned = clearPinned;
|
|
60
|
+
exports.clearExcluded = clearExcluded;
|
|
61
|
+
exports.readPinned = readPinned;
|
|
62
|
+
exports.describeContextSet = describeContextSet;
|
|
63
|
+
// Lives in core (moved from the CLI's ui/contextFiles.ts, 2026-10-04) so the
|
|
64
|
+
// VS Code panel manages the same per-project pinned/excluded set; the
|
|
65
|
+
// CLI module is a re-export shim.
|
|
66
|
+
const fs = __importStar(require("fs"));
|
|
67
|
+
const os = __importStar(require("os"));
|
|
68
|
+
const path = __importStar(require("path"));
|
|
69
|
+
const crypto = __importStar(require("crypto"));
|
|
70
|
+
exports.EMPTY_CONTEXT_SET = { pinned: [], excluded: [] };
|
|
71
|
+
// ─── Storage ─────────────────────────────────────────────────────────────────
|
|
72
|
+
// NEXRALL_CLI_CONTEXT_DIR overrides the store root — used by tests so they never
|
|
73
|
+
// touch the real ~/.nexrall/cli-context (same pattern as NEXRALL_CLI_SESSIONS_DIR).
|
|
74
|
+
function contextRoot() {
|
|
75
|
+
return process.env.NEXRALL_CLI_CONTEXT_DIR || path.join(os.homedir(), '.nexrall', 'cli-context');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* One file per project directory. The hash is of the resolved path, so two
|
|
79
|
+
* projects that happen to share a folder name ("api", "app") cannot read each
|
|
80
|
+
* other's list; the readable prefix is there for a human poking at the folder.
|
|
81
|
+
*/
|
|
82
|
+
function contextSetPath(workDir) {
|
|
83
|
+
const abs = path.resolve(workDir);
|
|
84
|
+
const key = crypto.createHash('sha1').update(abs).digest('hex').slice(0, 12);
|
|
85
|
+
const label = path
|
|
86
|
+
.basename(abs)
|
|
87
|
+
.replace(/[^\w.-]+/g, '-')
|
|
88
|
+
.slice(0, 24) || 'project';
|
|
89
|
+
return path.join(contextRoot(), `${label}-${key}.json`);
|
|
90
|
+
}
|
|
91
|
+
function cleanList(v) {
|
|
92
|
+
if (!Array.isArray(v))
|
|
93
|
+
return [];
|
|
94
|
+
return [...new Set(v.filter((x) => typeof x === 'string' && x.trim() !== '').map((x) => x.trim()))];
|
|
95
|
+
}
|
|
96
|
+
/** Missing or corrupt file → an empty set: a bad file must not stop the CLI. */
|
|
97
|
+
function loadContextSet(workDir) {
|
|
98
|
+
try {
|
|
99
|
+
const raw = JSON.parse(fs.readFileSync(contextSetPath(workDir), 'utf-8'));
|
|
100
|
+
return { pinned: cleanList(raw.pinned), excluded: cleanList(raw.excluded) };
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return { ...exports.EMPTY_CONTEXT_SET };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function saveContextSet(workDir, set) {
|
|
107
|
+
try {
|
|
108
|
+
const file = contextSetPath(workDir);
|
|
109
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
110
|
+
fs.writeFileSync(file, JSON.stringify({ pinned: set.pinned, excluded: set.excluded }, null, 2), 'utf-8');
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
/* non-fatal: the in-memory lists still work for this session */
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// ─── Paths ───────────────────────────────────────────────────────────────────
|
|
117
|
+
/**
|
|
118
|
+
* `@src/a.ts` / `src/a.ts` / `./src/a.ts` → `src/a.ts`. Null when the path is
|
|
119
|
+
* empty, IS the project root, or escapes it (`../secrets`, `/etc/passwd`) — a
|
|
120
|
+
* pin is fed back to the model, so it may only ever name something inside.
|
|
121
|
+
* Purely lexical (no fs): a not-yet-created file can still be pinned.
|
|
122
|
+
*/
|
|
123
|
+
function relInProject(input, workDir) {
|
|
124
|
+
const raw = input.trim().replace(/^@/, '').trim();
|
|
125
|
+
if (!raw)
|
|
126
|
+
return null;
|
|
127
|
+
const root = path.resolve(workDir);
|
|
128
|
+
const rel = path.relative(root, path.resolve(root, raw));
|
|
129
|
+
if (!rel || rel.startsWith('..') || path.isAbsolute(rel))
|
|
130
|
+
return null;
|
|
131
|
+
return rel.split(path.sep).join('/');
|
|
132
|
+
}
|
|
133
|
+
// ─── The /exclude matcher ────────────────────────────────────────────────────
|
|
134
|
+
const compiled = new Map();
|
|
135
|
+
/**
|
|
136
|
+
* Glob for an `/exclude` pattern:
|
|
137
|
+
* • a trailing "/" is decoration — "generated/" and "generated" mean the same
|
|
138
|
+
* thing here, because a match on the directory already covers its contents;
|
|
139
|
+
* • a pattern WITHOUT a "/" matches a path segment at any depth
|
|
140
|
+
* ("node_modules" hides every one of them, exactly like .gitignore);
|
|
141
|
+
* • a pattern WITH a "/" is anchored to the project root
|
|
142
|
+
* ("src/generated" does not hide "vendor/src/generated");
|
|
143
|
+
* • either way the match ends at a path boundary, so a pattern naming a
|
|
144
|
+
* DIRECTORY hides everything inside it ("src/generated" hides
|
|
145
|
+
* "src/generated/x.ts"), while "node_modules" still never matches
|
|
146
|
+
* "node_modules_backup";
|
|
147
|
+
* • "*" stops at a "/", "**" crosses them, "?" is one character.
|
|
148
|
+
*/
|
|
149
|
+
function globToRegExp(pattern) {
|
|
150
|
+
const hit = compiled.get(pattern);
|
|
151
|
+
if (hit)
|
|
152
|
+
return hit;
|
|
153
|
+
const p = pattern.trim().replace(/^\.\//, '').replace(/\/+$/, '');
|
|
154
|
+
const anchored = p.includes('/');
|
|
155
|
+
let re = '';
|
|
156
|
+
for (let i = 0; i < p.length; i++) {
|
|
157
|
+
const ch = p[i];
|
|
158
|
+
if (ch === '*') {
|
|
159
|
+
if (p[i + 1] === '*') {
|
|
160
|
+
re += '.*';
|
|
161
|
+
i++;
|
|
162
|
+
}
|
|
163
|
+
else
|
|
164
|
+
re += '[^/]*';
|
|
165
|
+
}
|
|
166
|
+
else if (ch === '?')
|
|
167
|
+
re += '[^/]';
|
|
168
|
+
else
|
|
169
|
+
re += ch.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
170
|
+
}
|
|
171
|
+
const rx = new RegExp(anchored ? `^${re}(/|$)` : `(^|/)${re}(/|$)`);
|
|
172
|
+
compiled.set(pattern, rx);
|
|
173
|
+
return rx;
|
|
174
|
+
}
|
|
175
|
+
/** True when `rel` (project-relative, POSIX) is hidden by any of `patterns`. */
|
|
176
|
+
function isExcluded(rel, patterns) {
|
|
177
|
+
if (!patterns.length)
|
|
178
|
+
return false;
|
|
179
|
+
const norm = rel.replace(/^\.\//, '');
|
|
180
|
+
return patterns.some((p) => {
|
|
181
|
+
try {
|
|
182
|
+
return globToRegExp(p).test(norm);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function sorted(list) {
|
|
190
|
+
return [...list].sort((a, b) => a.localeCompare(b));
|
|
191
|
+
}
|
|
192
|
+
function addPinned(set, rel) {
|
|
193
|
+
if (isExcluded(rel, set.excluded)) {
|
|
194
|
+
return { ok: false, message: `${rel} is excluded — /unexclude it first`, set };
|
|
195
|
+
}
|
|
196
|
+
if (set.pinned.includes(rel)) {
|
|
197
|
+
return { ok: false, message: `${rel} is already pinned`, set };
|
|
198
|
+
}
|
|
199
|
+
return { ok: true, message: `Pinned ${rel}`, set: { ...set, pinned: [...set.pinned, rel] } };
|
|
200
|
+
}
|
|
201
|
+
function removePinned(set, rel) {
|
|
202
|
+
if (!set.pinned.includes(rel)) {
|
|
203
|
+
return { ok: false, message: `${rel} is not pinned`, set };
|
|
204
|
+
}
|
|
205
|
+
return { ok: true, message: `Unpinned ${rel}`, set: { ...set, pinned: set.pinned.filter((p) => p !== rel) } };
|
|
206
|
+
}
|
|
207
|
+
function addExcluded(set, pattern) {
|
|
208
|
+
const p = pattern.trim().replace(/^@/, '').replace(/\/+$/, '');
|
|
209
|
+
if (!p)
|
|
210
|
+
return { ok: false, message: 'Nothing to exclude', set };
|
|
211
|
+
if (set.excluded.includes(p))
|
|
212
|
+
return { ok: false, message: `${p} is already excluded`, set };
|
|
213
|
+
// An exclusion overrides a pin — say so instead of silently leaving a file
|
|
214
|
+
// pinned AND excluded, which would read as "it is still in context".
|
|
215
|
+
const dropped = set.pinned.filter((rel) => isExcluded(rel, [p]));
|
|
216
|
+
const edit = {
|
|
217
|
+
ok: true,
|
|
218
|
+
message: `Excluded ${p}` + (dropped.length ? ` — unpinned ${dropped.join(', ')}` : ''),
|
|
219
|
+
set: { ...set, excluded: [...set.excluded, p], pinned: set.pinned.filter((rel) => !dropped.includes(rel)) },
|
|
220
|
+
};
|
|
221
|
+
return edit;
|
|
222
|
+
}
|
|
223
|
+
function removeExcluded(set, pattern) {
|
|
224
|
+
const p = pattern.trim().replace(/^@/, '').replace(/\/+$/, '');
|
|
225
|
+
if (!set.excluded.includes(p))
|
|
226
|
+
return { ok: false, message: `${p} is not excluded`, set };
|
|
227
|
+
return { ok: true, message: `Unexcluded ${p}`, set: { ...set, excluded: set.excluded.filter((x) => x !== p) } };
|
|
228
|
+
}
|
|
229
|
+
/** Bare `/unpin` / `/unexclude` clear the matching list. */
|
|
230
|
+
function clearPinned(set) {
|
|
231
|
+
return set.pinned.length
|
|
232
|
+
? { ok: true, message: `Unpinned all ${set.pinned.length} file(s)`, set: { ...set, pinned: [] } }
|
|
233
|
+
: { ok: false, message: 'Nothing is pinned', set };
|
|
234
|
+
}
|
|
235
|
+
function clearExcluded(set) {
|
|
236
|
+
return set.excluded.length
|
|
237
|
+
? { ok: true, message: `Cleared ${set.excluded.length} exclusion(s)`, set: { ...set, excluded: [] } }
|
|
238
|
+
: { ok: false, message: 'Nothing is excluded', set };
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Contents of the pinned files, shaped like an `@` attachment so the model sees
|
|
242
|
+
* the same framing it already knows. Budgets mirror expandMentions': per file,
|
|
243
|
+
* and for the batch, because pinned paths accumulate over weeks and a runaway
|
|
244
|
+
* 200 KB JSON would otherwise be re-sent on every compaction.
|
|
245
|
+
*/
|
|
246
|
+
function readPinned(workDir, pinned, maxBytes = 100000, maxTotal = 300000) {
|
|
247
|
+
const out = { attachments: [], notes: [] };
|
|
248
|
+
let total = 0;
|
|
249
|
+
for (const rel of pinned) {
|
|
250
|
+
if (total >= maxTotal) {
|
|
251
|
+
out.notes.push(`${rel} skipped — pinned size limit reached`);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const abs = path.join(workDir, rel);
|
|
255
|
+
let st;
|
|
256
|
+
try {
|
|
257
|
+
st = fs.statSync(abs);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
out.notes.push(`${rel} is missing — /unpin it or restore the file`);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (!st.isFile()) {
|
|
264
|
+
out.notes.push(`${rel} is not a file`);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
let buf;
|
|
268
|
+
try {
|
|
269
|
+
const fd = fs.openSync(abs, 'r');
|
|
270
|
+
try {
|
|
271
|
+
const want = Math.min(maxBytes, Math.max(0, maxTotal - total), st.size);
|
|
272
|
+
buf = Buffer.alloc(want);
|
|
273
|
+
fs.readSync(fd, buf, 0, want, 0);
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
fs.closeSync(fd);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
out.notes.push(`${rel} could not be read`);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (buf.subarray(0, 8000).includes(0)) {
|
|
284
|
+
out.notes.push(`${rel} is a binary file — not attached`);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const content = buf.toString('utf-8');
|
|
288
|
+
total += buf.length;
|
|
289
|
+
out.attachments.push({
|
|
290
|
+
rel,
|
|
291
|
+
kind: 'file',
|
|
292
|
+
content,
|
|
293
|
+
truncated: st.size > buf.length,
|
|
294
|
+
lines: content.split('\n').length,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return out;
|
|
298
|
+
}
|
|
299
|
+
/** Human-facing summary of what each list currently holds. */
|
|
300
|
+
function describeContextSet(set) {
|
|
301
|
+
const lines = [];
|
|
302
|
+
lines.push(set.pinned.length
|
|
303
|
+
? `Pinned (kept through /compact): ${sorted(set.pinned).join(', ')}`
|
|
304
|
+
: 'Nothing pinned. /pin <path> keeps a file in context across /compact.');
|
|
305
|
+
lines.push(set.excluded.length
|
|
306
|
+
? `Excluded from context: ${sorted(set.excluded).join(', ')}`
|
|
307
|
+
: 'Nothing excluded. /exclude <glob> hides paths from the @ menu and attachments.');
|
|
308
|
+
return lines;
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=contextFiles.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type DiffFile } from './diffView';
|
|
2
|
+
/**
|
|
3
|
+
* Raw `git diff` of everything uncommitted (staged + unstaged, vs HEAD) plus the list of
|
|
4
|
+
* untracked files, or null outside a repo. Colour and external diff drivers are forced off
|
|
5
|
+
* so the parser in diffView.ts always sees plain unified diff.
|
|
6
|
+
*/
|
|
7
|
+
export declare function gitDiffRaw(cwd: string): {
|
|
8
|
+
diff: string;
|
|
9
|
+
untracked: string[];
|
|
10
|
+
} | null;
|
|
11
|
+
/**
|
|
12
|
+
* The whole uncommitted changeset as DiffFiles: the parsed diff first, then the
|
|
13
|
+
* untracked files (inlined while reasonable, listed-only when huge or binary).
|
|
14
|
+
* Null when the directory is not a git repository.
|
|
15
|
+
*/
|
|
16
|
+
export declare function collectChangedFiles(workDir: string): DiffFile[] | null;
|
|
17
|
+
/**
|
|
18
|
+
* Current branch (or 7-char sha when detached) read straight from `.git/HEAD` — no
|
|
19
|
+
* process spawn, so it is cheap enough to refresh after every turn. Walks up from
|
|
20
|
+
* `cwd` like git does, and follows the `gitdir:` pointer that worktrees and
|
|
21
|
+
* submodules use instead of a `.git` directory. Null outside a repo or on any
|
|
22
|
+
* read problem: a status bar must never throw.
|
|
23
|
+
*/
|
|
24
|
+
export declare function gitBranch(cwd: string): string | null;
|
|
25
|
+
//# sourceMappingURL=collect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../src/diff/collect.ts"],"names":[],"mappings":"AAaA,OAAO,EAAsD,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE/F;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAmCpF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CAqBtE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA+BpD"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Collecting the uncommitted changeset — "everything git would show if you ran
|
|
3
|
+
// `git diff HEAD` plus the untracked files" — as parsed DiffFiles.
|
|
4
|
+
//
|
|
5
|
+
// Moved from two CLI homes (2026-10-04, both re-export/call in through here):
|
|
6
|
+
// • gitDiffRaw was commands/sessionTools.ts — raw `git diff` + ls-files;
|
|
7
|
+
// • the collect step was chat.ts's changedFilesIn — parse + fold in untracked.
|
|
8
|
+
// Lives in core so the VS Code panel's /review and /diff render the SAME data
|
|
9
|
+
// the CLI renders, from the same git porcelain invocation (colour and external
|
|
10
|
+
// diff drivers forced off so the parser always sees plain unified diff).
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.gitDiffRaw = gitDiffRaw;
|
|
46
|
+
exports.collectChangedFiles = collectChangedFiles;
|
|
47
|
+
exports.gitBranch = gitBranch;
|
|
48
|
+
const child_process_1 = require("child_process");
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const diffView_1 = require("./diffView");
|
|
52
|
+
/**
|
|
53
|
+
* Raw `git diff` of everything uncommitted (staged + unstaged, vs HEAD) plus the list of
|
|
54
|
+
* untracked files, or null outside a repo. Colour and external diff drivers are forced off
|
|
55
|
+
* so the parser in diffView.ts always sees plain unified diff.
|
|
56
|
+
*/
|
|
57
|
+
function gitDiffRaw(cwd) {
|
|
58
|
+
const run = (args) => (0, child_process_1.execFileSync)('git', args, {
|
|
59
|
+
cwd,
|
|
60
|
+
encoding: 'utf8',
|
|
61
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
62
|
+
timeout: 15000,
|
|
63
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
64
|
+
});
|
|
65
|
+
try {
|
|
66
|
+
run(['rev-parse', '--is-inside-work-tree']);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const base = ['-c', 'core.quotepath=off', 'diff', '--no-color', '--no-ext-diff', '--no-textconv'];
|
|
72
|
+
let diff;
|
|
73
|
+
try {
|
|
74
|
+
diff = run([...base, 'HEAD']);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// No commits yet: HEAD does not exist — staged changes are all there is.
|
|
78
|
+
try {
|
|
79
|
+
diff = run([...base, '--cached']);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
diff = '';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
let untracked = [];
|
|
86
|
+
try {
|
|
87
|
+
untracked = run(['-c', 'core.quotepath=off', 'ls-files', '--others', '--exclude-standard'])
|
|
88
|
+
.split('\n')
|
|
89
|
+
.filter(Boolean);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
/* ignore */
|
|
93
|
+
}
|
|
94
|
+
return { diff, untracked };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The whole uncommitted changeset as DiffFiles: the parsed diff first, then the
|
|
98
|
+
* untracked files (inlined while reasonable, listed-only when huge or binary).
|
|
99
|
+
* Null when the directory is not a git repository.
|
|
100
|
+
*/
|
|
101
|
+
function collectChangedFiles(workDir) {
|
|
102
|
+
const raw = gitDiffRaw(workDir);
|
|
103
|
+
if (!raw)
|
|
104
|
+
return null;
|
|
105
|
+
const files = (0, diffView_1.parseUnifiedDiff)(raw.diff);
|
|
106
|
+
const seen = new Set(files.map((f) => f.path));
|
|
107
|
+
for (const rel of raw.untracked) {
|
|
108
|
+
if (seen.has(rel))
|
|
109
|
+
continue;
|
|
110
|
+
try {
|
|
111
|
+
const abs = path.resolve(workDir, rel);
|
|
112
|
+
const st = fs.statSync(abs);
|
|
113
|
+
if (!st.isFile())
|
|
114
|
+
continue;
|
|
115
|
+
files.push(st.size > diffView_1.MAX_UNTRACKED_BYTES
|
|
116
|
+
? { path: rel, status: 'added', additions: 0, deletions: 0, hunks: [] }
|
|
117
|
+
: (0, diffView_1.newFileDiff)(rel, fs.readFileSync(abs, 'utf8')));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
/* unreadable — skip */
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return files;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Current branch (or 7-char sha when detached) read straight from `.git/HEAD` — no
|
|
127
|
+
* process spawn, so it is cheap enough to refresh after every turn. Walks up from
|
|
128
|
+
* `cwd` like git does, and follows the `gitdir:` pointer that worktrees and
|
|
129
|
+
* submodules use instead of a `.git` directory. Null outside a repo or on any
|
|
130
|
+
* read problem: a status bar must never throw.
|
|
131
|
+
*/
|
|
132
|
+
function gitBranch(cwd) {
|
|
133
|
+
try {
|
|
134
|
+
let dir = path.resolve(cwd);
|
|
135
|
+
for (let i = 0; i < 40; i++) {
|
|
136
|
+
const dotGit = path.join(dir, '.git');
|
|
137
|
+
let st = null;
|
|
138
|
+
try {
|
|
139
|
+
st = fs.statSync(dotGit);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
/* keep walking */
|
|
143
|
+
}
|
|
144
|
+
if (st) {
|
|
145
|
+
let gitDir = dotGit;
|
|
146
|
+
if (st.isFile()) {
|
|
147
|
+
const m = /^gitdir:\s*(.+)\s*$/m.exec(fs.readFileSync(dotGit, 'utf8'));
|
|
148
|
+
if (!m)
|
|
149
|
+
return null;
|
|
150
|
+
gitDir = path.resolve(dir, m[1]);
|
|
151
|
+
}
|
|
152
|
+
const head = fs.readFileSync(path.join(gitDir, 'HEAD'), 'utf8').trim();
|
|
153
|
+
const ref = /^ref:\s*refs\/heads\/(.+)$/.exec(head);
|
|
154
|
+
if (ref)
|
|
155
|
+
return ref[1];
|
|
156
|
+
return /^[0-9a-f]{7,}$/i.test(head) ? head.slice(0, 7) : null;
|
|
157
|
+
}
|
|
158
|
+
const parent = path.dirname(dir);
|
|
159
|
+
if (parent === dir)
|
|
160
|
+
return null;
|
|
161
|
+
dir = parent;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
/* fall through */
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=collect.js.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type DiffLineKind = 'file' | 'meta' | 'hunk' | 'add' | 'del' | 'ctx' | 'note';
|
|
2
|
+
export interface DiffLine {
|
|
3
|
+
kind: DiffLineKind;
|
|
4
|
+
text: string;
|
|
5
|
+
}
|
|
6
|
+
export interface DiffHunk {
|
|
7
|
+
header: string;
|
|
8
|
+
/** Raw body lines, each starting with '+', '-', ' ' or '\' (no-newline marker). */
|
|
9
|
+
lines: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface DiffFile {
|
|
12
|
+
path: string;
|
|
13
|
+
/** Previous path when the file was renamed. */
|
|
14
|
+
oldPath?: string;
|
|
15
|
+
status: 'modified' | 'added' | 'deleted' | 'renamed' | 'binary';
|
|
16
|
+
additions: number;
|
|
17
|
+
deletions: number;
|
|
18
|
+
hunks: DiffHunk[];
|
|
19
|
+
}
|
|
20
|
+
/** A hunk longer than this is folded in the overview (full view never folds hunks). */
|
|
21
|
+
export declare const MAX_HUNK_LINES = 40;
|
|
22
|
+
/** Body lines kept from a folded hunk. */
|
|
23
|
+
export declare const FOLDED_HUNK_KEEP = 30;
|
|
24
|
+
/** Total lines one rendering may emit before it points at `/diff <file>`. */
|
|
25
|
+
export declare const MAX_VIEW_LINES = 400;
|
|
26
|
+
/** Untracked files larger than this are listed, not inlined. */
|
|
27
|
+
export declare const MAX_UNTRACKED_BYTES = 200000;
|
|
28
|
+
/** Parse the output of `git diff` (unified, no colour) into files. */
|
|
29
|
+
export declare function parseUnifiedDiff(text: string): DiffFile[];
|
|
30
|
+
/** Synthesise the diff of an untracked (new) text file so `/diff` can show it too. */
|
|
31
|
+
export declare function newFileDiff(path: string, content: string): DiffFile;
|
|
32
|
+
export declare function totals(files: DiffFile[]): {
|
|
33
|
+
files: number;
|
|
34
|
+
additions: number;
|
|
35
|
+
deletions: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Find the file a user means by `/diff <name>`: exact path, then unique suffix
|
|
39
|
+
* ("chat.ts"), then unique substring. Returns the matches so the caller can say
|
|
40
|
+
* "ambiguous" instead of silently picking one.
|
|
41
|
+
*/
|
|
42
|
+
export declare function findFiles(files: DiffFile[], query: string): DiffFile[];
|
|
43
|
+
/** One summary row per file: ` M path +12 −3`. */
|
|
44
|
+
export declare function summaryLines(files: DiffFile[]): DiffLine[];
|
|
45
|
+
export interface RenderOptions {
|
|
46
|
+
/** Fold hunks longer than MAX_HUNK_LINES (overview). The single-file view passes false. */
|
|
47
|
+
foldHunks?: boolean;
|
|
48
|
+
/** Cap on emitted lines; defaults to MAX_VIEW_LINES. */
|
|
49
|
+
maxLines?: number;
|
|
50
|
+
}
|
|
51
|
+
/** Display lines for one file: header, then each hunk, long ones folded when asked. */
|
|
52
|
+
export declare function renderFile(f: DiffFile, opts?: RenderOptions): DiffLine[];
|
|
53
|
+
/** Whole-tree view: every file rendered, capped overall so a big refactor doesn't flood. */
|
|
54
|
+
export declare function renderAll(files: DiffFile[], opts?: RenderOptions): DiffLine[];
|
|
55
|
+
/** Cap a single-file view; over the cap, tells the user how many lines were left out. */
|
|
56
|
+
export declare function capLines(lines: DiffLine[], max?: number): DiffLine[];
|
|
57
|
+
//# sourceMappingURL=diffView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diffView.d.ts","sourceRoot":"","sources":["../../src/diff/diffView.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAErF,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,uFAAuF;AACvF,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,0CAA0C;AAC1C,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,6EAA6E;AAC7E,eAAO,MAAM,cAAc,MAAM,CAAC;AAClC,gEAAgE;AAChE,eAAO,MAAM,mBAAmB,SAAU,CAAC;AAE3C,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAiEzD;AAED,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAYnE;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAMjG;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,EAAE,CAStE;AAUD,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAiB1D;AAED,MAAM,WAAW,aAAa;IAC5B,2FAA2F;IAC3F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,GAAE,aAAkB,GAAG,QAAQ,EAAE,CA+B5E;AAED,4FAA4F;AAC5F,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,IAAI,GAAE,aAAkB,GAAG,QAAQ,EAAE,CAoBjF;AAED,yFAAyF;AACzF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,GAAG,SAAqB,GAAG,QAAQ,EAAE,CAGhF"}
|