@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.
Files changed (110) hide show
  1. package/dist/agent/goal.d.ts +55 -0
  2. package/dist/agent/goal.d.ts.map +1 -0
  3. package/dist/agent/goal.js +120 -0
  4. package/dist/agent/handoffBrief.d.ts +82 -0
  5. package/dist/agent/handoffBrief.d.ts.map +1 -0
  6. package/dist/agent/handoffBrief.js +121 -0
  7. package/dist/agent/modelCatalogue.d.ts +23 -0
  8. package/dist/agent/modelCatalogue.d.ts.map +1 -1
  9. package/dist/agent/modelCatalogue.js +28 -0
  10. package/dist/agent/outputStyles.d.ts +27 -0
  11. package/dist/agent/outputStyles.d.ts.map +1 -0
  12. package/dist/agent/outputStyles.js +174 -0
  13. package/dist/agent/skills.d.ts.map +1 -1
  14. package/dist/agent/skills.js +7 -0
  15. package/dist/api/billingLines.d.ts +11 -0
  16. package/dist/api/billingLines.d.ts.map +1 -0
  17. package/dist/api/billingLines.js +56 -0
  18. package/dist/api/client.d.ts +2 -0
  19. package/dist/api/client.d.ts.map +1 -1
  20. package/dist/api/scheduleLines.d.ts +53 -0
  21. package/dist/api/scheduleLines.d.ts.map +1 -0
  22. package/dist/api/scheduleLines.js +150 -0
  23. package/dist/context/contextFiles.d.ts +81 -0
  24. package/dist/context/contextFiles.d.ts.map +1 -0
  25. package/dist/context/contextFiles.js +310 -0
  26. package/dist/diff/collect.d.ts +25 -0
  27. package/dist/diff/collect.d.ts.map +1 -0
  28. package/dist/diff/collect.js +169 -0
  29. package/dist/diff/diffView.d.ts +57 -0
  30. package/dist/diff/diffView.d.ts.map +1 -0
  31. package/dist/diff/diffView.js +231 -0
  32. package/dist/diff/reviewQueue.d.ts +53 -0
  33. package/dist/diff/reviewQueue.d.ts.map +1 -0
  34. package/dist/diff/reviewQueue.js +349 -0
  35. package/dist/events/recap.d.ts +54 -0
  36. package/dist/events/recap.d.ts.map +1 -0
  37. package/dist/events/recap.js +161 -0
  38. package/dist/events/sessionEvents.d.ts +54 -0
  39. package/dist/events/sessionEvents.d.ts.map +1 -0
  40. package/dist/events/sessionEvents.js +324 -0
  41. package/dist/events/sessionStats.d.ts +89 -0
  42. package/dist/events/sessionStats.d.ts.map +1 -0
  43. package/dist/events/sessionStats.js +264 -0
  44. package/dist/import/claudeImport.d.ts +44 -0
  45. package/dist/import/claudeImport.d.ts.map +1 -0
  46. package/dist/import/claudeImport.js +337 -0
  47. package/dist/index.d.ts +33 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +33 -0
  50. package/dist/project/projectCommands.d.ts +33 -0
  51. package/dist/project/projectCommands.d.ts.map +1 -0
  52. package/dist/project/projectCommands.js +246 -0
  53. package/dist/search/conversationTurns.d.ts +53 -0
  54. package/dist/search/conversationTurns.d.ts.map +1 -0
  55. package/dist/search/conversationTurns.js +170 -0
  56. package/dist/search/globalSearch.d.ts +144 -0
  57. package/dist/search/globalSearch.d.ts.map +1 -0
  58. package/dist/search/globalSearch.js +350 -0
  59. package/dist/share/sessionChannel.d.ts +136 -0
  60. package/dist/share/sessionChannel.d.ts.map +1 -0
  61. package/dist/share/sessionChannel.js +373 -0
  62. package/dist/share/sessionShare.d.ts +104 -0
  63. package/dist/share/sessionShare.d.ts.map +1 -0
  64. package/dist/share/sessionShare.js +336 -0
  65. package/dist/ui/configCmd.d.ts +48 -0
  66. package/dist/ui/configCmd.d.ts.map +1 -0
  67. package/dist/ui/configCmd.js +146 -0
  68. package/dist/ui/deployPanel.d.ts +91 -0
  69. package/dist/ui/deployPanel.d.ts.map +1 -0
  70. package/dist/ui/deployPanel.js +442 -0
  71. package/dist/ui/mergeAssistant.d.ts +120 -0
  72. package/dist/ui/mergeAssistant.d.ts.map +1 -0
  73. package/dist/ui/mergeAssistant.js +391 -0
  74. package/dist/ui/notify.d.ts +114 -0
  75. package/dist/ui/notify.d.ts.map +1 -0
  76. package/dist/ui/notify.js +235 -0
  77. package/dist/ui/promptSuggest.d.ts +56 -0
  78. package/dist/ui/promptSuggest.d.ts.map +1 -0
  79. package/dist/ui/promptSuggest.js +160 -0
  80. package/dist/ui/taskGraph.d.ts +44 -0
  81. package/dist/ui/taskGraph.d.ts.map +1 -0
  82. package/dist/ui/taskGraph.js +289 -0
  83. package/dist/util/addDir.d.ts +31 -0
  84. package/dist/util/addDir.d.ts.map +1 -0
  85. package/dist/util/addDir.js +124 -0
  86. package/dist/util/batch.d.ts +16 -0
  87. package/dist/util/batch.d.ts.map +1 -0
  88. package/dist/util/batch.js +92 -0
  89. package/dist/util/cellWidth.d.ts +12 -0
  90. package/dist/util/cellWidth.d.ts.map +1 -0
  91. package/dist/util/cellWidth.js +161 -0
  92. package/dist/util/duration.d.ts +8 -0
  93. package/dist/util/duration.d.ts.map +1 -0
  94. package/dist/util/duration.js +50 -0
  95. package/dist/util/loopJobs.d.ts +67 -0
  96. package/dist/util/loopJobs.d.ts.map +1 -0
  97. package/dist/util/loopJobs.js +169 -0
  98. package/dist/util/settingsFile.d.ts +14 -0
  99. package/dist/util/settingsFile.d.ts.map +1 -0
  100. package/dist/util/settingsFile.js +99 -0
  101. package/dist/util/toolLabel.d.ts +2 -0
  102. package/dist/util/toolLabel.d.ts.map +1 -0
  103. package/dist/util/toolLabel.js +17 -0
  104. package/dist/verify/failureTimeline.d.ts +100 -0
  105. package/dist/verify/failureTimeline.d.ts.map +1 -0
  106. package/dist/verify/failureTimeline.js +253 -0
  107. package/dist/verify/verification.d.ts +75 -0
  108. package/dist/verify/verification.d.ts.map +1 -0
  109. package/dist/verify/verification.js +315 -0
  110. 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"}