@thegitai/cli 1.0.0-preview.36 → 1.0.0-preview.37
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/src/edit-journal.js +33 -2
- package/dist/src/patcher.js +43 -4
- package/dist/src/tools/delete-file.js +5 -1
- package/dist/src/ui/repl.js +39 -5
- package/package.json +6 -6
package/dist/src/edit-journal.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { existsSync, lstatSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { closeSync, existsSync, lstatSync, openSync, readFileSync, readSync, } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { BINARY_ARTIFACT_EXTENSIONS } from './artifact-policy.js';
|
|
6
6
|
import { resolveProjectPath } from './patcher.js';
|
|
@@ -30,13 +30,33 @@ function snapshotEncoding(filePath, content) {
|
|
|
30
30
|
return 'base64';
|
|
31
31
|
return 'utf8';
|
|
32
32
|
}
|
|
33
|
+
const MAX_SNAPSHOT_READ_BYTES = MAX_STORED_CONTENT_CHARS;
|
|
34
|
+
const SNAPSHOT_HASH_CHUNK_BYTES = 1024 * 1024;
|
|
35
|
+
function hashFileInChunks(absPath) {
|
|
36
|
+
const hash = createHash('sha256');
|
|
37
|
+
const chunk = Buffer.allocUnsafe(SNAPSHOT_HASH_CHUNK_BYTES);
|
|
38
|
+
const fd = openSync(absPath, 'r');
|
|
39
|
+
try {
|
|
40
|
+
for (;;) {
|
|
41
|
+
const read = readSync(fd, chunk, 0, chunk.length, null);
|
|
42
|
+
if (read <= 0)
|
|
43
|
+
break;
|
|
44
|
+
hash.update(chunk.subarray(0, read));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
closeSync(fd);
|
|
49
|
+
}
|
|
50
|
+
return `sha256:${hash.digest('hex')}`;
|
|
51
|
+
}
|
|
33
52
|
export function readFileEditSnapshot(rootDir, filePath) {
|
|
34
53
|
try {
|
|
35
54
|
const absPath = resolveProjectPath(rootDir, filePath);
|
|
36
55
|
if (!existsSync(absPath)) {
|
|
37
56
|
return { exists: false, content: null, contentEncoding: 'utf8', hash: null };
|
|
38
57
|
}
|
|
39
|
-
|
|
58
|
+
const stat = lstatSync(absPath);
|
|
59
|
+
if (stat.isSymbolicLink()) {
|
|
40
60
|
return {
|
|
41
61
|
exists: true,
|
|
42
62
|
content: null,
|
|
@@ -45,6 +65,17 @@ export function readFileEditSnapshot(rootDir, filePath) {
|
|
|
45
65
|
error: `Refusing to snapshot symbolic link: ${filePath}`,
|
|
46
66
|
};
|
|
47
67
|
}
|
|
68
|
+
if (stat.isFile() && stat.size > MAX_SNAPSHOT_READ_BYTES) {
|
|
69
|
+
return {
|
|
70
|
+
exists: true,
|
|
71
|
+
content: null,
|
|
72
|
+
contentEncoding: BINARY_ARTIFACT_EXTENSIONS.has(path.extname(filePath).toLowerCase())
|
|
73
|
+
? 'base64'
|
|
74
|
+
: 'utf8',
|
|
75
|
+
hash: hashFileInChunks(absPath),
|
|
76
|
+
error: `File is too large to snapshot (${stat.size} bytes).`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
48
79
|
const content = readFileSync(absPath);
|
|
49
80
|
const encoding = snapshotEncoding(filePath, content);
|
|
50
81
|
return {
|
package/dist/src/patcher.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from './colors.js';
|
|
2
|
-
import { chmodSync, closeSync, constants, existsSync, fchmodSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, unlinkSync, writeFileSync, } from 'fs';
|
|
2
|
+
import { chmodSync, closeSync, constants, existsSync, fchmodSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, realpathSync, unlinkSync, writeFileSync, } from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { createInterface } from 'readline';
|
|
5
5
|
import { runCommand } from './executor.js';
|
|
@@ -271,22 +271,61 @@ export function writeProjectFileBuffer(rootDir, filePath, content) {
|
|
|
271
271
|
}
|
|
272
272
|
return { absPath, changed: true };
|
|
273
273
|
}
|
|
274
|
+
const MAX_DELETED_CONTENT_BYTES = 64 * 1024;
|
|
275
|
+
const DELETED_BINARY_SNIFF_BYTES = 4096;
|
|
276
|
+
function headHasNulByte(absPath) {
|
|
277
|
+
const chunk = Buffer.allocUnsafe(DELETED_BINARY_SNIFF_BYTES);
|
|
278
|
+
let fd = null;
|
|
279
|
+
try {
|
|
280
|
+
fd = openSync(absPath, 'r');
|
|
281
|
+
const read = readSync(fd, chunk, 0, chunk.length, 0);
|
|
282
|
+
return read > 0 && chunk.subarray(0, read).includes(0);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
if (fd !== null)
|
|
289
|
+
closeSync(fd);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
274
292
|
export function deleteProjectFile(rootDir, filePath) {
|
|
275
293
|
const absPath = resolveProjectPath(rootDir, filePath);
|
|
276
294
|
if (!existsSync(absPath)) {
|
|
277
295
|
return { deleted: false, absPath };
|
|
278
296
|
}
|
|
297
|
+
let bytes;
|
|
279
298
|
let content;
|
|
299
|
+
let contentOmitted;
|
|
280
300
|
try {
|
|
281
|
-
|
|
282
|
-
|
|
301
|
+
const stat = lstatSync(absPath);
|
|
302
|
+
if (!stat.isSymbolicLink()) {
|
|
303
|
+
bytes = stat.size;
|
|
304
|
+
if (stat.size > MAX_DELETED_CONTENT_BYTES) {
|
|
305
|
+
contentOmitted = headHasNulByte(absPath) ? 'binary' : 'too_large';
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
const buffer = readFileSync(absPath);
|
|
309
|
+
if (buffer.includes(0))
|
|
310
|
+
contentOmitted = 'binary';
|
|
311
|
+
else
|
|
312
|
+
content = buffer.toString('utf-8');
|
|
313
|
+
}
|
|
283
314
|
}
|
|
284
315
|
}
|
|
285
316
|
catch {
|
|
317
|
+
bytes = undefined;
|
|
286
318
|
content = undefined;
|
|
319
|
+
contentOmitted = undefined;
|
|
287
320
|
}
|
|
288
321
|
unlinkSync(absPath);
|
|
289
|
-
return {
|
|
322
|
+
return {
|
|
323
|
+
deleted: true,
|
|
324
|
+
absPath,
|
|
325
|
+
...(bytes === undefined ? {} : { bytes }),
|
|
326
|
+
...(content === undefined ? {} : { content }),
|
|
327
|
+
...(contentOmitted === undefined ? {} : { contentOmitted }),
|
|
328
|
+
};
|
|
290
329
|
}
|
|
291
330
|
export function readProjectFile(rootDir, filePath) {
|
|
292
331
|
const absPath = resolveProjectPath(rootDir, filePath);
|
|
@@ -51,7 +51,11 @@ export async function deleteFile(context, args) {
|
|
|
51
51
|
changed: result.deleted,
|
|
52
52
|
deleted: result.deleted,
|
|
53
53
|
...(scratchPath ? { scratch: true } : {}),
|
|
54
|
-
|
|
54
|
+
...(result.bytes === undefined ? {} : { bytes: result.bytes }),
|
|
55
|
+
...(result.contentOmitted === undefined
|
|
56
|
+
? {}
|
|
57
|
+
: { contentOmitted: result.contentOmitted }),
|
|
58
|
+
...(result.content === undefined ? {} : { content: result.content }),
|
|
55
59
|
diagnostics: result.deleted && !scratchPath ? runShellDiagnostics(rootDir) : undefined,
|
|
56
60
|
};
|
|
57
61
|
}
|
package/dist/src/ui/repl.js
CHANGED
|
@@ -527,11 +527,43 @@ function buildWriteFileDiff(content) {
|
|
|
527
527
|
return '';
|
|
528
528
|
return `@@ -0,0 +1,${lines.length} @@\n${lines.map((line) => `+${line}`).join('\n')}`;
|
|
529
529
|
}
|
|
530
|
+
const MAX_DELETE_PREVIEW_LINES = 400;
|
|
530
531
|
function buildDeleteFileDiff(content) {
|
|
531
532
|
const lines = splitDiffLines(String(content ?? ''));
|
|
532
|
-
if (lines.length === 0)
|
|
533
|
+
if (lines.length === 0) {
|
|
534
|
+
return { removed: 0, text: '' };
|
|
535
|
+
}
|
|
536
|
+
const shown = lines.slice(0, MAX_DELETE_PREVIEW_LINES);
|
|
537
|
+
const rows = [
|
|
538
|
+
`@@ -1,${lines.length} +0,0 @@`,
|
|
539
|
+
...shown.map((line) => `-${line}`),
|
|
540
|
+
];
|
|
541
|
+
const omitted = lines.length - shown.length;
|
|
542
|
+
if (omitted > 0) {
|
|
543
|
+
rows.push(`@@ ${omitted} more removed line(s) not shown @@`);
|
|
544
|
+
}
|
|
545
|
+
return { removed: lines.length, text: rows.join('\n') };
|
|
546
|
+
}
|
|
547
|
+
function deletedFileSummary(result) {
|
|
548
|
+
const bytes = typeof result?.bytes === 'number' ? result.bytes : null;
|
|
549
|
+
if (bytes === null) {
|
|
533
550
|
return '';
|
|
534
|
-
|
|
551
|
+
}
|
|
552
|
+
const reason = result?.contentOmitted === 'binary' ? ', binary' : '';
|
|
553
|
+
return ` (${formatFileSize(bytes)}${reason})`;
|
|
554
|
+
}
|
|
555
|
+
function formatFileSize(bytes) {
|
|
556
|
+
if (bytes < 1024) {
|
|
557
|
+
return `${bytes} B`;
|
|
558
|
+
}
|
|
559
|
+
const units = ['KiB', 'MiB', 'GiB', 'TiB'];
|
|
560
|
+
let value = bytes / 1024;
|
|
561
|
+
let unit = 0;
|
|
562
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
563
|
+
value /= 1024;
|
|
564
|
+
unit += 1;
|
|
565
|
+
}
|
|
566
|
+
return `${value >= 10 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
|
|
535
567
|
}
|
|
536
568
|
function isFileChangeTool(name) {
|
|
537
569
|
return (name === 'patch_file' ||
|
|
@@ -659,9 +691,11 @@ function buildFileChangeEntry(event) {
|
|
|
659
691
|
};
|
|
660
692
|
}
|
|
661
693
|
const content = typeof result?.content === 'string' ? result.content : '';
|
|
662
|
-
const
|
|
663
|
-
const preview =
|
|
664
|
-
const summary = preview
|
|
694
|
+
const diff = buildDeleteFileDiff(content);
|
|
695
|
+
const preview = diff.text ? parseDiffPreview(diff.text) : undefined;
|
|
696
|
+
const summary = preview
|
|
697
|
+
? ` (+0 -${diff.removed})`
|
|
698
|
+
: deletedFileSummary(result);
|
|
665
699
|
return {
|
|
666
700
|
body: '',
|
|
667
701
|
diffPreview: preview,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thegitai/cli",
|
|
3
|
-
"version": "1.0.0-preview.
|
|
3
|
+
"version": "1.0.0-preview.37",
|
|
4
4
|
"description": "TheGitAI is an agentic AI coding tool for your terminal. It reads and searches your repository, writes and edits files, runs your tests, and verifies the change before handing it back.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentic-ai",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"@lydell/node-pty-linux-x64": "1.1.0",
|
|
45
45
|
"@lydell/node-pty-win32-arm64": "1.1.0",
|
|
46
46
|
"@lydell/node-pty-win32-x64": "1.1.0",
|
|
47
|
-
"@thegitai/tui-darwin-arm64": "1.0.0-preview.
|
|
48
|
-
"@thegitai/tui-darwin-x64": "1.0.0-preview.
|
|
49
|
-
"@thegitai/tui-linux-arm64": "1.0.0-preview.
|
|
50
|
-
"@thegitai/tui-linux-x64": "1.0.0-preview.
|
|
51
|
-
"@thegitai/tui-win32-x64": "1.0.0-preview.
|
|
47
|
+
"@thegitai/tui-darwin-arm64": "1.0.0-preview.37",
|
|
48
|
+
"@thegitai/tui-darwin-x64": "1.0.0-preview.37",
|
|
49
|
+
"@thegitai/tui-linux-arm64": "1.0.0-preview.37",
|
|
50
|
+
"@thegitai/tui-linux-x64": "1.0.0-preview.37",
|
|
51
|
+
"@thegitai/tui-win32-x64": "1.0.0-preview.37",
|
|
52
52
|
"@vscode/ripgrep": "1.18.0"
|
|
53
53
|
},
|
|
54
54
|
"repository": {
|