@thegitai/cli 1.0.0-beta.9 → 1.0.0-preview.2

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 (56) hide show
  1. package/README.md +36 -2
  2. package/dist/bin/ai.js +138 -18
  3. package/dist/parsers/NOTICE +18 -0
  4. package/dist/src/agent-mode.js +5 -0
  5. package/dist/src/api/auth.js +3 -3
  6. package/dist/src/api/browser-login.js +0 -16
  7. package/dist/src/api/chat.js +59 -11
  8. package/dist/src/api/http.js +49 -1
  9. package/dist/src/api/models.js +26 -20
  10. package/dist/src/artifact-policy.js +3 -0
  11. package/dist/src/background-jobs.js +410 -0
  12. package/dist/src/cli-args.js +0 -5
  13. package/dist/src/client-environment.js +2 -0
  14. package/dist/src/colors.js +50 -0
  15. package/dist/src/core/clipboard.js +19 -0
  16. package/dist/src/core/image-path-extractor.js +144 -0
  17. package/dist/src/executor.js +48 -12
  18. package/dist/src/help-text.js +11 -6
  19. package/dist/src/markdown-renderer.js +1 -1
  20. package/dist/src/patcher.js +97 -12
  21. package/dist/src/project-orientation.js +99 -0
  22. package/dist/src/scanner.js +50 -12
  23. package/dist/src/scratch-dir.js +75 -0
  24. package/dist/src/secret-preview.js +0 -10
  25. package/dist/src/session-safety.js +0 -19
  26. package/dist/src/session-store.js +0 -1
  27. package/dist/src/todo-list.js +106 -0
  28. package/dist/src/tool-executor.js +194 -21
  29. package/dist/src/tools/delete-file.js +23 -5
  30. package/dist/src/tools/index.js +6 -0
  31. package/dist/src/tools/patch-file.js +33 -7
  32. package/dist/src/tools/path-suggest.js +81 -8
  33. package/dist/src/tools/read-document.js +2 -2
  34. package/dist/src/tools/read-file.js +17 -8
  35. package/dist/src/tools/replace-document-text.js +10 -12
  36. package/dist/src/tools/restore-checkpoint.js +1 -1
  37. package/dist/src/tools/run-command.js +109 -24
  38. package/dist/src/tools/run-node-script.js +27 -5
  39. package/dist/src/tools/shell-job-kill.js +48 -0
  40. package/dist/src/tools/shell-job-output.js +51 -0
  41. package/dist/src/tools/str-replace.js +33 -7
  42. package/dist/src/tools/undo-edit.js +1 -1
  43. package/dist/src/tools/update-todos.js +27 -0
  44. package/dist/src/tools/write-file.js +26 -6
  45. package/dist/src/tree-sitter-runtime.js +8 -1
  46. package/dist/src/ui/repl.js +342 -23
  47. package/dist/src/ui/tui/bridge.js +0 -4
  48. package/dist/src/ui/tui/build-frame.js +220 -24
  49. package/dist/src/ui/tui/shell-input.js +33 -4
  50. package/dist/src/ui/tui/terminal-title.js +81 -0
  51. package/dist/src/version.js +0 -6
  52. package/dist/vendor/web-tree-sitter/LICENSE +21 -0
  53. package/dist/vendor/web-tree-sitter/NOTICE +13 -0
  54. package/dist/vendor/web-tree-sitter/web-tree-sitter.cjs +4063 -0
  55. package/dist/vendor/web-tree-sitter/web-tree-sitter.wasm +0 -0
  56. package/package.json +14 -15
@@ -1,7 +1,8 @@
1
- import chalk from 'chalk';
1
+ import chalk from '../colors.js';
2
2
  import path from 'node:path';
3
3
  import { normalizeProjectRelativePath } from '../artifact-policy.js';
4
- import { readProjectFile, writeProjectFile } from '../patcher.js';
4
+ import { classifyProjectPath, readProjectFile, writeProjectFile, } from '../patcher.js';
5
+ import { repairFilePath } from './path-suggest.js';
5
6
  import { upsertIndexFile } from '../project-index.js';
6
7
  import { isTuiMode } from '../runtime-mode.js';
7
8
  import { getCurrentFileHash, resolveRedactionTokens } from '../session-safety.js';
@@ -76,7 +77,7 @@ function buildStrReplacePreview(oldString, newString) {
76
77
  }
77
78
  export async function strReplace(context, args) {
78
79
  const { rootDir, projectIndex, autoYes, confirmPatch } = context;
79
- const filePath = String(args.filePath ?? args.file_path ?? '').trim();
80
+ const filePath = repairFilePath(rootDir, String(args.filePath ?? args.file_path ?? '').trim());
80
81
  let oldString = typeof args.old_string === 'string'
81
82
  ? args.old_string
82
83
  : typeof args.oldString === 'string'
@@ -105,6 +106,21 @@ export async function strReplace(context, args) {
105
106
  failureCategory: 'invalid_argument',
106
107
  };
107
108
  }
109
+ const pathKind = classifyProjectPath(rootDir, filePath);
110
+ if (pathKind === 'outside') {
111
+ return {
112
+ ok: false,
113
+ filePath,
114
+ error: `Refusing to edit outside the project root: ${filePath}. Editable locations are the project root and the session scratch directory ($THEGITAI_SCRATCH_DIR).`,
115
+ failureCategory: 'invalid_argument',
116
+ failureDetails: {
117
+ category: 'invalid_argument',
118
+ tool: 'str_replace',
119
+ action: 'Edit files inside the project root, or use an absolute path under the session scratch directory ($THEGITAI_SCRATCH_DIR) for temporary files.',
120
+ },
121
+ };
122
+ }
123
+ const scratchPath = pathKind === 'scratch';
108
124
  let originalContent;
109
125
  try {
110
126
  originalContent = readProjectFile(rootDir, filePath);
@@ -168,7 +184,13 @@ export async function strReplace(context, args) {
168
184
  ok: false,
169
185
  skipped: true,
170
186
  filePath,
171
- error: 'User declined str_replace',
187
+ failureCategory: 'user_declined',
188
+ failureDetails: {
189
+ category: 'user_declined',
190
+ tool: 'str_replace',
191
+ action: 'Respect the real user’s decision. Do not retry the same or an equivalent edit; reconsider the approach or ask one specific question if needed.',
192
+ },
193
+ error: 'The real user rejected this proposed edit. Nothing was changed; this was not a tool failure or an automated system skip.',
172
194
  };
173
195
  }
174
196
  }
@@ -177,13 +199,16 @@ export async function strReplace(context, args) {
177
199
  const replacements = changed ? (replaceAll ? n : 1) : 0;
178
200
  let indexedChunks = 0;
179
201
  let retrievalTokensUsed = 0;
180
- if (changed) {
202
+ if (changed && !scratchPath) {
181
203
  const indexResult = await upsertIndexFile(projectIndex, filePath);
182
204
  indexedChunks = indexResult.indexedChunks;
183
205
  retrievalTokensUsed = indexResult.retrievalTokensUsed ?? 0;
184
206
  }
185
- invalidateShellDiagnosticsCache(rootDir, filePath);
186
- const diagnostics = runShellDiagnostics(rootDir, filePath);
207
+ let diagnostics;
208
+ if (!scratchPath) {
209
+ invalidateShellDiagnosticsCache(rootDir, filePath);
210
+ diagnostics = runShellDiagnostics(rootDir, filePath);
211
+ }
187
212
  const originalLines = originalContent.split('\n').length;
188
213
  const nextLines = nextContent.split('\n').length;
189
214
  if (!isTuiMode()) {
@@ -196,6 +221,7 @@ export async function strReplace(context, args) {
196
221
  filePath,
197
222
  changed,
198
223
  operation: 'str_replace',
224
+ ...(scratchPath ? { scratch: true } : {}),
199
225
  replacements,
200
226
  indexedChunks,
201
227
  retrievalTokensUsed,
@@ -1,4 +1,4 @@
1
- import chalk from 'chalk';
1
+ import chalk from '../colors.js';
2
2
  import { hashStoredContent, readFileEditSnapshot, storedContentBuffer, } from '../edit-journal.js';
3
3
  import { deleteProjectFile, writeProjectFile, writeProjectFileBuffer, } from '../patcher.js';
4
4
  import { removeIndexFile, upsertIndexFile, } from '../project-index.js';
@@ -0,0 +1,27 @@
1
+ import { extractTodosArg, replaceTodos } from '../todo-list.js';
2
+ export function updateTodos(_context, args) {
3
+ const rawTodos = extractTodosArg(args);
4
+ if (rawTodos === undefined) {
5
+ return {
6
+ ok: false,
7
+ error: 'todos is required (pass [] to clear the list).',
8
+ failureCategory: 'missing_required_argument',
9
+ };
10
+ }
11
+ const result = replaceTodos(rawTodos);
12
+ if (!result.ok) {
13
+ return {
14
+ ok: false,
15
+ error: result.error,
16
+ failureCategory: 'invalid_argument',
17
+ };
18
+ }
19
+ const { snapshot, normalizations } = result;
20
+ return {
21
+ ok: true,
22
+ todos: snapshot.items,
23
+ completedCount: snapshot.completedCount,
24
+ totalCount: snapshot.totalCount,
25
+ ...(normalizations.length ? { normalizations } : {}),
26
+ };
27
+ }
@@ -1,7 +1,7 @@
1
- import chalk from 'chalk';
1
+ import chalk from '../colors.js';
2
2
  import path from 'node:path';
3
3
  import { normalizeProjectRelativePath } from '../artifact-policy.js';
4
- import { writeProjectFile } from '../patcher.js';
4
+ import { classifyProjectPath, writeProjectFile } from '../patcher.js';
5
5
  import { upsertIndexFile } from '../project-index.js';
6
6
  import { isTuiMode } from '../runtime-mode.js';
7
7
  import { getCurrentFileHash, hasFreshFullReadCoverage, resolveRedactionTokens, } from '../session-safety.js';
@@ -25,9 +25,25 @@ export async function writeFile(context, args) {
25
25
  failureCategory: 'invalid_argument',
26
26
  };
27
27
  }
28
+ const pathKind = classifyProjectPath(rootDir, filePath);
29
+ if (pathKind === 'outside') {
30
+ return {
31
+ ok: false,
32
+ filePath,
33
+ error: `Refusing to write outside the project root: ${filePath}. Writable locations are the project root and the session scratch directory ($THEGITAI_SCRATCH_DIR).`,
34
+ failureCategory: 'invalid_argument',
35
+ failureDetails: {
36
+ category: 'invalid_argument',
37
+ tool: 'write_file',
38
+ action: 'Write inside the project root, or use an absolute path under the session scratch directory ($THEGITAI_SCRATCH_DIR) for temporary files.',
39
+ },
40
+ };
41
+ }
42
+ const scratchPath = pathKind === 'scratch';
28
43
  const coveragePath = normalizeProjectRelativePath(rootDir, filePath) ?? filePath;
29
44
  const currentHash = getCurrentFileHash(rootDir, filePath);
30
- if (currentHash !== null &&
45
+ if (!scratchPath &&
46
+ currentHash !== null &&
31
47
  context.safety &&
32
48
  !hasFreshFullReadCoverage(context.safety, coveragePath, currentHash)) {
33
49
  return {
@@ -47,13 +63,16 @@ export async function writeFile(context, args) {
47
63
  const { changed } = writeProjectFile(rootDir, filePath, content);
48
64
  let indexedChunks = 0;
49
65
  let retrievalTokensUsed = 0;
50
- if (changed) {
66
+ if (changed && !scratchPath) {
51
67
  const indexResult = await upsertIndexFile(projectIndex, filePath);
52
68
  indexedChunks = indexResult.indexedChunks;
53
69
  retrievalTokensUsed = indexResult.retrievalTokensUsed ?? 0;
54
70
  }
55
- invalidateShellDiagnosticsCache(rootDir, filePath);
56
- const diagnostics = runShellDiagnostics(rootDir, filePath);
71
+ let diagnostics;
72
+ if (!scratchPath) {
73
+ invalidateShellDiagnosticsCache(rootDir, filePath);
74
+ diagnostics = runShellDiagnostics(rootDir, filePath);
75
+ }
57
76
  if (!isTuiMode()) {
58
77
  const icon = changed ? '✨' : '📝';
59
78
  const label = changed ? 'Created/Updated' : 'Created/Updated (no change)';
@@ -64,6 +83,7 @@ export async function writeFile(context, args) {
64
83
  filePath,
65
84
  changed,
66
85
  operation: 'write',
86
+ ...(scratchPath ? { scratch: true } : {}),
67
87
  indexedChunks,
68
88
  retrievalTokensUsed,
69
89
  bytesWritten: Buffer.byteLength(content, 'utf-8'),
@@ -5,8 +5,15 @@ import { fileURLToPath } from 'node:url';
5
5
  import { addSignatureForNode } from './extractors/index.js';
6
6
  import { getRepoMapLanguageForFile, } from './repo-map-languages.js';
7
7
  const require = createRequire(import.meta.url);
8
- const TreeSitter = require('web-tree-sitter');
9
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ function resolveVendoredTreeSitter() {
10
+ const candidates = [
11
+ path.resolve(__dirname, '..', 'vendor', 'web-tree-sitter', 'web-tree-sitter.cjs'),
12
+ path.resolve(__dirname, '..', '..', 'vendor', 'web-tree-sitter', 'web-tree-sitter.cjs'),
13
+ ];
14
+ return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
15
+ }
16
+ const TreeSitter = require(resolveVendoredTreeSitter());
10
17
  let parserInitPromise = null;
11
18
  const parserCache = Object.create(null);
12
19
  const languageCache = Object.create(null);