@semalt-ai/code 1.8.5 → 1.20.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.
Files changed (192) hide show
  1. package/.claude/settings.local.json +7 -1
  2. package/.github/workflows/ci.yml +69 -0
  3. package/ARCHITECTURE.md +6 -95
  4. package/CLAUDE.md +196 -316
  5. package/README.md +148 -4
  6. package/docs/ARCHITECTURE.md +1321 -0
  7. package/docs/CONFIG.md +340 -0
  8. package/docs/HISTORY.md +245 -0
  9. package/examples/embed.js +74 -0
  10. package/index.js +251 -10
  11. package/lib/agent.js +856 -120
  12. package/lib/api.js +239 -50
  13. package/lib/args.js +74 -2
  14. package/lib/audit.js +23 -1
  15. package/lib/background.js +584 -0
  16. package/lib/checkpoints.js +757 -0
  17. package/lib/commands/auth.js +94 -0
  18. package/lib/commands/chat-session.js +489 -0
  19. package/lib/commands/chat-slash.js +415 -0
  20. package/lib/commands/chat-turn.js +669 -0
  21. package/lib/commands/chat.js +407 -0
  22. package/lib/commands/custom.js +157 -0
  23. package/lib/commands/history-utils.js +66 -0
  24. package/lib/commands/index.js +268 -0
  25. package/lib/commands/mcp.js +113 -0
  26. package/lib/commands/oneshot.js +193 -0
  27. package/lib/commands/registry.js +269 -0
  28. package/lib/commands/tasks.js +89 -0
  29. package/lib/compact.js +87 -0
  30. package/lib/config.js +360 -11
  31. package/lib/constants.js +401 -3
  32. package/lib/deny.js +199 -0
  33. package/lib/doctor.js +160 -0
  34. package/lib/headless.js +202 -0
  35. package/lib/hooks.js +286 -0
  36. package/lib/images.js +270 -0
  37. package/lib/internals.js +49 -0
  38. package/lib/mcp/boundary.js +131 -0
  39. package/lib/mcp/client.js +270 -0
  40. package/lib/mcp/oauth.js +134 -0
  41. package/lib/memory.js +209 -0
  42. package/lib/metrics.js +37 -2
  43. package/lib/payload.js +54 -0
  44. package/lib/permission-rules.js +401 -0
  45. package/lib/permissions.js +123 -26
  46. package/lib/pricing.js +67 -0
  47. package/lib/proc.js +62 -0
  48. package/lib/prompts.js +99 -8
  49. package/lib/sandbox.js +568 -0
  50. package/lib/sdk.js +328 -0
  51. package/lib/secrets.js +211 -0
  52. package/lib/skills.js +223 -0
  53. package/lib/subagents.js +516 -0
  54. package/lib/tool_registry.js +2862 -0
  55. package/lib/tool_specs.js +263 -9
  56. package/lib/tools.js +352 -1039
  57. package/lib/ui/anim.js +86 -0
  58. package/lib/ui/ansi.js +17 -27
  59. package/lib/ui/chat-history.js +253 -71
  60. package/lib/ui/create-ui.js +67 -24
  61. package/lib/ui/diff.js +90 -25
  62. package/lib/ui/file-activity.js +236 -0
  63. package/lib/ui/format.js +195 -29
  64. package/lib/ui/input-field.js +21 -11
  65. package/lib/ui/md-stream.js +234 -0
  66. package/lib/ui/render-operation.js +113 -0
  67. package/lib/ui/select.js +1 -4
  68. package/lib/ui/status-bar.js +146 -36
  69. package/lib/ui/stream.js +20 -13
  70. package/lib/ui/theme.js +190 -44
  71. package/lib/ui/tool-operation.js +190 -0
  72. package/lib/ui/utils.js +9 -5
  73. package/lib/ui/web-activity.js +270 -0
  74. package/lib/ui/writer.js +159 -45
  75. package/lib/ui.js +1 -1
  76. package/lib/verify.js +229 -0
  77. package/lib/web-extract.js +213 -0
  78. package/lib/web-summarize.js +68 -0
  79. package/package.json +19 -4
  80. package/scripts/lint.js +57 -0
  81. package/test/agent-loop.test.js +389 -0
  82. package/test/anim-driver.test.js +153 -0
  83. package/test/ask-user-display.test.js +226 -0
  84. package/test/ask-user-gate.test.js +231 -0
  85. package/test/background.test.js +414 -0
  86. package/test/chat-history-nocolor.test.js +155 -0
  87. package/test/chat-relogin.test.js +207 -0
  88. package/test/chat.test.js +114 -0
  89. package/test/checkpoints-agent.test.js +181 -0
  90. package/test/checkpoints.test.js +650 -0
  91. package/test/command-registry.test.js +160 -0
  92. package/test/compact.test.js +116 -0
  93. package/test/completion-lazy.test.js +52 -0
  94. package/test/config-merge.test.js +324 -0
  95. package/test/config-quarantine.test.js +128 -0
  96. package/test/config-write-guard-allow-anywhere.test.js +56 -0
  97. package/test/config-write-guard-skip.test.js +46 -0
  98. package/test/config-write-guard.test.js +153 -0
  99. package/test/context-split.test.js +215 -0
  100. package/test/cost-doctor.test.js +142 -0
  101. package/test/custom-commands-chat.test.js +106 -0
  102. package/test/custom-commands.test.js +230 -0
  103. package/test/defer-detail-band.test.js +403 -0
  104. package/test/deny-windows.test.js +120 -0
  105. package/test/deny.test.js +83 -0
  106. package/test/detail-band-tab-flatten.test.js +242 -0
  107. package/test/download-allow-anywhere.test.js +66 -0
  108. package/test/download-confine.test.js +153 -0
  109. package/test/exec-diff.test.js +268 -0
  110. package/test/executors.test.js +599 -0
  111. package/test/extract-tool-calls.test.js +349 -0
  112. package/test/fetch-url-validation.test.js +219 -0
  113. package/test/file-activity.test.js +522 -0
  114. package/test/fixtures/tool-calls.js +57 -0
  115. package/test/fixtures/web-page.js +91 -0
  116. package/test/git-tools.test.js +384 -0
  117. package/test/grep-glob-serialize.test.js +242 -0
  118. package/test/grep-glob.test.js +268 -0
  119. package/test/grep-path-target.test.js +227 -0
  120. package/test/harness/README.md +57 -0
  121. package/test/harness/chat-harness.js +143 -0
  122. package/test/harness/memwarn-headless-child.js +65 -0
  123. package/test/harness/mock-llm.js +120 -0
  124. package/test/harness/mock-mcp-server.js +142 -0
  125. package/test/harness/sse-server.js +69 -0
  126. package/test/headless.test.js +348 -0
  127. package/test/history-utils.test.js +88 -0
  128. package/test/hooks-agent.test.js +238 -0
  129. package/test/hooks-verify-sandbox.test.js +232 -0
  130. package/test/hooks.test.js +216 -0
  131. package/test/http-get-user-agent.test.js +142 -0
  132. package/test/images-api.test.js +208 -0
  133. package/test/images.test.js +238 -0
  134. package/test/input-field-ctrl-o.test.js +37 -0
  135. package/test/live-height-physical.test.js +281 -0
  136. package/test/max-iterations.test.js +218 -0
  137. package/test/mcp-boundary.test.js +57 -0
  138. package/test/mcp-client.test.js +267 -0
  139. package/test/mcp-oauth.test.js +86 -0
  140. package/test/md-stream.test.js +183 -0
  141. package/test/memory-truncation-warning.test.js +222 -0
  142. package/test/memory.test.js +198 -0
  143. package/test/native-dispatch.test.js +409 -0
  144. package/test/native-live-narration.test.js +254 -0
  145. package/test/output-chokepoint.test.js +188 -0
  146. package/test/output-heredoc-leak.test.js +195 -0
  147. package/test/output-preview.test.js +245 -0
  148. package/test/path-guards.test.js +134 -0
  149. package/test/payload.test.js +99 -0
  150. package/test/permission-rules-agent.test.js +210 -0
  151. package/test/permission-rules.test.js +297 -0
  152. package/test/permissions.test.js +362 -0
  153. package/test/plan-mode.test.js +167 -0
  154. package/test/read-paginate.test.js +275 -0
  155. package/test/readonly-tools.test.js +177 -0
  156. package/test/render-operation.test.js +317 -0
  157. package/test/replay-descriptor-xml.test.js +216 -0
  158. package/test/replay-descriptor.test.js +189 -0
  159. package/test/replay-web-aggregate.test.js +291 -0
  160. package/test/replay-web-persist.test.js +241 -0
  161. package/test/result-cap.test.js +233 -0
  162. package/test/running-glyph-anim.test.js +111 -0
  163. package/test/sandbox-agent.test.js +147 -0
  164. package/test/sandbox-integration.test.js +216 -0
  165. package/test/sandbox.test.js +408 -0
  166. package/test/sdk.test.js +234 -0
  167. package/test/shell-output-cap.test.js +181 -0
  168. package/test/skills-chat.test.js +110 -0
  169. package/test/skills.test.js +295 -0
  170. package/test/smoke.test.js +68 -0
  171. package/test/status-bar-driver.test.js +93 -0
  172. package/test/status-bar-pause.test.js +164 -0
  173. package/test/status-bar-resync.test.js +188 -0
  174. package/test/stream-parser.test.js +171 -0
  175. package/test/subagents-agent.test.js +178 -0
  176. package/test/subagents.test.js +222 -0
  177. package/test/theme-palette.test.js +166 -0
  178. package/test/tool-registry.test.js +85 -0
  179. package/test/trim-budget.test.js +101 -0
  180. package/test/truncate-visible.test.js +78 -0
  181. package/test/verify-agent.test.js +317 -0
  182. package/test/verify.test.js +141 -0
  183. package/test/view-image.test.js +199 -0
  184. package/test/web-activity-ordering.test.js +203 -0
  185. package/test/web-activity.test.js +207 -0
  186. package/test/web-data-extraction-guidance.test.js +71 -0
  187. package/test/web-extract.test.js +185 -0
  188. package/test/web-fetch-agent.test.js +291 -0
  189. package/test/web-fetch-mode.test.js +193 -0
  190. package/test/web-search.test.js +380 -0
  191. package/lib/commands.js +0 -1438
  192. package/path +0 -1
package/lib/tool_specs.js CHANGED
@@ -50,7 +50,11 @@ const TOOL_SPECS = {
50
50
  },
51
51
 
52
52
  read_file: {
53
- description: 'Read a UTF-8 text file and return its full contents.',
53
+ description: 'Read a UTF-8 text file. Paginated: by default returns the first ~2000 lines; '
54
+ + 'if the file is longer the result ends with a [PARTIAL] notice giving the total line count '
55
+ + 'and the start_line for the next page. Use start_line/end_line to read a specific slice '
56
+ + '(also capped to ~2000 lines). Set show_line_numbers=true when you need line numbers to '
57
+ + 'target an edit_file call (off by default — keeps content copyable for replace_in_file and cheaper).',
54
58
  parameters: {
55
59
  type: 'object',
56
60
  properties: {
@@ -58,6 +62,39 @@ const TOOL_SPECS = {
58
62
  type: 'string',
59
63
  description: 'Absolute or relative path to the file to read',
60
64
  },
65
+ start_line: {
66
+ type: 'integer',
67
+ description: 'First line to read (1-based, inclusive). Omit to start at line 1.',
68
+ },
69
+ end_line: {
70
+ type: 'integer',
71
+ description: 'Last line to read (1-based, inclusive). Omit to read to the line cap. '
72
+ + 'A range wider than the line cap is still capped — page with start_line.',
73
+ },
74
+ show_line_numbers: {
75
+ type: 'boolean',
76
+ description: 'Prefix each line with its 1-based line number (aligned with edit_file). '
77
+ + 'Default false. Turn on when you intend to edit by line number.',
78
+ },
79
+ },
80
+ required: ['path'],
81
+ },
82
+ },
83
+
84
+ view_image: {
85
+ description: 'Load a LOCAL image file (PNG, JPEG, GIF, or WebP) into YOUR OWN vision context so you can '
86
+ + 'analyze it — read text/diagrams in it, inspect a screenshot, compare a mockup, etc. The image is made '
87
+ + 'visible to YOU (the model) for the next turn; it is NOT displayed to the user, so never say "take a look" '
88
+ + 'or otherwise refer to the image as something the user can see. To analyze an image that lives at a URL, '
89
+ + 'first use `download <url>` to save it to the working directory, then call view_image on the saved path. '
90
+ + 'Unsupported formats, oversized files (image_max_bytes), or missing paths return a clear error, not a crash.',
91
+ parameters: {
92
+ type: 'object',
93
+ properties: {
94
+ path: {
95
+ type: 'string',
96
+ description: 'Absolute or relative path to a local image file (PNG/JPEG/GIF/WebP) to load into vision context',
97
+ },
61
98
  },
62
99
  required: ['path'],
63
100
  },
@@ -228,7 +265,7 @@ const TOOL_SPECS = {
228
265
  },
229
266
 
230
267
  edit_file: {
231
- description: 'Replace the contents of a single line in a file, identified by 1-based line number.',
268
+ description: 'Replace a single line, or a contiguous range of lines, in a file by 1-based line number. With end_line set, lines line..end_line are replaced wholesale by content (which may itself span multiple lines) — a regex-free way to swap a block: read the slice with read_file (start_line/end_line + show_line_numbers), then replace that exact range. For large block edits this and a literal replace_in_file are the two supported paths.',
232
269
  parameters: {
233
270
  type: 'object',
234
271
  properties: {
@@ -238,12 +275,17 @@ const TOOL_SPECS = {
238
275
  },
239
276
  line: {
240
277
  type: 'integer',
241
- description: '1-based line number of the line to replace',
278
+ description: '1-based line number of the (first) line to replace',
279
+ minimum: 1,
280
+ },
281
+ end_line: {
282
+ type: 'integer',
283
+ description: 'Optional 1-based last line of the range to replace (inclusive). Omit for a single-line edit. Must be >= line and within the file.',
242
284
  minimum: 1,
243
285
  },
244
286
  content: {
245
287
  type: 'string',
246
- description: 'New text for the target line; trailing newline is added automatically when the file is rejoined',
288
+ description: 'New text for the target line or range; may contain newlines to expand a range into several lines. Trailing newline is added automatically when the file is rejoined.',
247
289
  default: '',
248
290
  },
249
291
  },
@@ -289,8 +331,72 @@ const TOOL_SPECS = {
289
331
  },
290
332
  },
291
333
 
334
+ grep: {
335
+ description: 'Search file contents by regular expression across the working tree and return the actual matches (file:line:text), so you can navigate to the relevant lines and read only a slice instead of whole files. Honors .gitignore, skips binary files and node_modules/.git. Prefers ripgrep when available with an identical pure-Node fallback. output_mode selects what is returned: "content" (default) gives file:line:text per match ("show me the lines"); "files_with_matches" gives just the unique file paths ("which files contain this"); "count" gives per-file and total match counts ("how many") — count/files_with_matches return almost nothing, so prefer them when you do not need the line text. Results are bounded by head_limit (default 100); when more matches exist than are shown, a truncation notice tells you how many were omitted — narrow the pattern, switch to count/files_with_matches, or raise head_limit.',
336
+ parameters: {
337
+ type: 'object',
338
+ properties: {
339
+ pattern: {
340
+ type: 'string',
341
+ description: 'Regular expression to search for (matched per line)',
342
+ },
343
+ path: {
344
+ type: 'string',
345
+ description: 'Optional search target: a FILE path (search just that file — absolute or relative, like search_in_file), a DIRECTORY path (search recursively under it), or a GLOB filter limiting which files in the working tree are searched, e.g. "*.js" or "src/**/*.ts". A path that is neither an existing file nor directory is treated as a glob. If a supplied path/glob matches nothing to search, grep returns a diagnostic error rather than a silent zero-match result.',
346
+ },
347
+ ignore_case: {
348
+ type: 'boolean',
349
+ description: 'Case-insensitive match when true',
350
+ default: false,
351
+ },
352
+ output_mode: {
353
+ type: 'string',
354
+ enum: ['content', 'files_with_matches', 'count'],
355
+ description: 'What to return: "content" = file:line:text per match (default); "files_with_matches" = unique file paths only; "count" = match counts per file and total. Use count/files_with_matches when you only need how many / which files.',
356
+ default: 'content',
357
+ },
358
+ head_limit: {
359
+ type: 'integer',
360
+ description: 'Maximum number of results to serialize into context (default 100): match lines in content mode, files in files_with_matches/count mode. Excess is omitted with a truncation notice.',
361
+ },
362
+ offset: {
363
+ type: 'integer',
364
+ description: 'Number of results to skip before serializing (for paging through a large match set). Default 0.',
365
+ },
366
+ },
367
+ required: ['pattern'],
368
+ },
369
+ },
370
+
371
+ glob: {
372
+ description: 'List files matching a glob pattern (relative paths). Skips node_modules/.git and hidden entries. The file list is bounded by head_limit (default 100); when more files match than are shown, a truncation notice reports how many were omitted — narrow the glob or raise head_limit.',
373
+ parameters: {
374
+ type: 'object',
375
+ properties: {
376
+ pattern: {
377
+ type: 'string',
378
+ description: 'Glob pattern such as "*.ts" or "src/**/*.js"; matches the basename when it has no slash, otherwise the relative path',
379
+ },
380
+ path: {
381
+ type: 'string',
382
+ description: 'Base directory to search from; defaults to the current working directory',
383
+ default: '.',
384
+ },
385
+ head_limit: {
386
+ type: 'integer',
387
+ description: 'Maximum number of file paths to serialize into context (default 100). Excess is omitted with a truncation notice.',
388
+ },
389
+ offset: {
390
+ type: 'integer',
391
+ description: 'Number of file paths to skip before serializing (for paging). Default 0.',
392
+ },
393
+ },
394
+ required: ['pattern'],
395
+ },
396
+ },
397
+
292
398
  replace_in_file: {
293
- description: 'Regex-replace occurrences of a pattern in a file; returns the number of replacements made.',
399
+ description: 'Exact string replacement in a file (Claude Code Edit model). The search text is matched LITERALLY by default — byte-for-byte, NO regex — so paste the exact verbatim code, including all whitespace and indentation, even if it contains ( ) { } . [ ] $ etc. The match must be UNIQUE: if the search string is not found the call ERRORS and the file is unchanged; if it appears more than once the call ERRORS (asking you to add surrounding context) unless you set replace_all:true. Returns the honest number of replacements made. Set regex:true to interpret the search as a JavaScript regular expression instead (bounded: long or backtracking-prone regexes are rejected). For block edits by line number, see line-range edit_file.',
294
400
  parameters: {
295
401
  type: 'object',
296
402
  properties: {
@@ -300,16 +406,26 @@ const TOOL_SPECS = {
300
406
  },
301
407
  search: {
302
408
  type: 'string',
303
- description: 'JavaScript regular-expression pattern to search for',
409
+ description: 'Exact text to find. Matched literally (verbatim, no regex) unless regex:true is set. Include enough surrounding context that it appears EXACTLY ONCE in the file, or set replace_all:true.',
304
410
  },
305
411
  replace: {
306
412
  type: 'string',
307
- description: 'Replacement string; supports the standard $1, $2, $& back-references',
413
+ description: 'Replacement string. In literal mode (default) it is inserted as raw text. In regex mode it supports the standard $1, $2, $& back-references.',
308
414
  default: '',
309
415
  },
416
+ replace_all: {
417
+ type: 'boolean',
418
+ description: 'Replace ALL occurrences instead of requiring a unique match. Without this, matching more than one occurrence is an error. The result reports the actual number replaced.',
419
+ default: false,
420
+ },
421
+ regex: {
422
+ type: 'boolean',
423
+ description: 'Opt into regex mode: interpret search as a JavaScript regular expression (bounded by a length cap + nested-quantifier guard). Default false = literal exact match. The uniqueness guard still applies in regex mode (use the g flag or replace_all to replace multiple).',
424
+ default: false,
425
+ },
310
426
  flags: {
311
427
  type: 'string',
312
- description: 'Regex flags, any combination of g/i/m/s/u/y; the "g" flag is added automatically if omitted',
428
+ description: 'Regex flags (only meaningful with regex:true), any combination of g/i/m/s/u/y. Without "g" (and without replace_all) the match must be unique.',
313
429
  default: '',
314
430
  },
315
431
  },
@@ -384,7 +500,7 @@ const TOOL_SPECS = {
384
500
  },
385
501
 
386
502
  http_get: {
387
- description: 'Perform an HTTP GET and return the response status code and body, truncated to a configured byte cap.',
503
+ description: 'Fetch a web page. The `mode` parameter selects how much processing to apply: "summarized" (default) runs the page through Readability + Markdown conversion and a secondary-model summary so only a compact result enters context — use it for find/answer tasks; "extracted" returns the extracted main-content Markdown without the summary — use it to read an article/doc verbatim or grab an exact snippet/quote; "raw" bypasses extraction entirely and returns the ORIGINAL fetched HTML/content (token-capped) — use it to analyze a page\'s HTML/CSS/JavaScript/markup/structure, which extraction destroys. Plain-text/JSON responses pass through unchanged in summarized/extracted modes. Optionally pass intent to focus the summary. The deprecated booleans summarize=false and raw=true are aliases for mode="extracted". DATA EXTRACTION: to pull SPECIFIC VALUES out of a page (hex colors, version strings, URLs, IDs, counts), do NOT use http_get — use `download` (or sandboxed `curl`) to save the page to the working directory, then `grep` it (e.g. `grep -oiE \'#[0-9a-f]{6}\'` for hex colors) so only the matching lines enter context, not the whole page. Reserve mode="raw" for when you actually need to read the markup structure; it loads the entire (token-capped) page and is expensive for simple value extraction. For SPA / asset-heavy sites the values often live in linked assets (e.g. /_nuxt/*.css or *.js, bundled stylesheets) rather than the top-level HTML — download+grep those asset URLs.',
388
504
  parameters: {
389
505
  type: 'object',
390
506
  properties: {
@@ -392,11 +508,46 @@ const TOOL_SPECS = {
392
508
  type: 'string',
393
509
  description: 'HTTP or HTTPS URL to fetch',
394
510
  },
511
+ mode: {
512
+ type: 'string',
513
+ enum: ['summarized', 'extracted', 'raw'],
514
+ description: 'How to process the page. "summarized" (default): extract main content, convert to Markdown, then summarize with a secondary model — for find/answer tasks. "extracted": extracted Markdown of the main content, no summary — for reading a doc/article verbatim or an exact snippet. "raw": the original fetched HTML/content, token-capped, NO extraction — for analyzing the page\'s HTML/CSS/JS/structure.',
515
+ },
516
+ summarize: {
517
+ type: 'boolean',
518
+ description: 'DEPRECATED alias (prefer mode). false ≡ mode="extracted" (extracted Markdown, no summary). An explicit mode wins over this.',
519
+ },
520
+ raw: {
521
+ type: 'boolean',
522
+ description: 'DEPRECATED alias (prefer mode). true ≡ mode="extracted". NOTE: this does NOT return raw HTML — use mode="raw" for the original markup. An explicit mode wins over this.',
523
+ },
524
+ intent: {
525
+ type: 'string',
526
+ description: 'Why you are fetching this page; focuses the summary on answering it.',
527
+ },
395
528
  },
396
529
  required: ['url'],
397
530
  },
398
531
  },
399
532
 
533
+ web_search: {
534
+ description: 'Search the web and return a compact list of candidate results — each with a title, url, and snippet. Use this to FIND relevant pages when you do not already know the URL, instead of guessing URLs. IMPORTANT: this returns only short snippets, NOT page content. Read the snippets, pick the single most relevant result (or the few that matter), and then fetch ONLY those with http_get to read them — use http_get mode="summarized" for reading/answering and mode="raw" only for analyzing markup. Do NOT fetch every result; that wastes context. Optional count caps how many results to return (the backend clamps it).',
535
+ parameters: {
536
+ type: 'object',
537
+ properties: {
538
+ query: {
539
+ type: 'string',
540
+ description: 'The search query.',
541
+ },
542
+ count: {
543
+ type: 'integer',
544
+ description: 'Optional maximum number of results to return (clamped by the backend; defaults to the backend default).',
545
+ },
546
+ },
547
+ required: ['query'],
548
+ },
549
+ },
550
+
400
551
  ask_user: {
401
552
  description: 'Prompt the interactive user for input and return their answer; auto-answers "y" in non-TTY mode.',
402
553
  parameters: {
@@ -462,6 +613,109 @@ const TOOL_SPECS = {
462
613
  },
463
614
  },
464
615
 
616
+ // ──────────────────────────────────────────────────────────────────
617
+ // Native git tools (Task 5.1). Implemented by shelling out to `git`
618
+ // through the same sandbox + deny-list chokepoint as <shell>, but they
619
+ // parse output into structured results. Read-only: git_status, git_diff,
620
+ // git_log (and the LIST ops of git_branch / git_worktree). Mutating:
621
+ // git_add, git_commit, git_branch (create/delete), git_checkout,
622
+ // git_worktree (add/remove). NOTE: git operations are NOT reversible via
623
+ // /rewind — checkpoints (Task 4.3) snapshot file-tool mutations only.
624
+ // ──────────────────────────────────────────────────────────────────
625
+
626
+ git_status: {
627
+ description: 'Show the working-tree status as structured lists of staged, unstaged, and untracked files plus the current branch. Read-only.',
628
+ parameters: { type: 'object', properties: {}, required: [] },
629
+ },
630
+
631
+ git_diff: {
632
+ description: 'Show changes as a structured diff (files, hunks, +/- counts). Defaults to unstaged changes; set staged=true for the staged (index) diff. Read-only.',
633
+ parameters: {
634
+ type: 'object',
635
+ properties: {
636
+ staged: { type: 'boolean', description: 'Diff the staged (index) changes instead of the working tree', default: false },
637
+ path: { type: 'string', description: 'Optional path to limit the diff to' },
638
+ },
639
+ required: [],
640
+ },
641
+ },
642
+
643
+ git_log: {
644
+ description: 'List recent commits as structured records (hash, author, email, date, subject). Read-only.',
645
+ parameters: {
646
+ type: 'object',
647
+ properties: {
648
+ count: { type: 'integer', description: 'Maximum number of commits to return (default 20)', minimum: 1, default: 20 },
649
+ path: { type: 'string', description: 'Optional path to limit history to' },
650
+ },
651
+ required: [],
652
+ },
653
+ },
654
+
655
+ git_add: {
656
+ description: 'Stage changes for commit. Provide `paths` (one or more files) or set all=true to stage everything. Mutating — requires approval.',
657
+ parameters: {
658
+ type: 'object',
659
+ properties: {
660
+ paths: { type: 'string', description: 'File path(s) to stage; space-separated for multiple' },
661
+ all: { type: 'boolean', description: 'Stage all changes (git add -A)', default: false },
662
+ },
663
+ required: [],
664
+ },
665
+ },
666
+
667
+ git_commit: {
668
+ description: 'Create a commit with the given message and return the new commit hash and branch. The message is required and must be non-empty (the tool never invents one). Mutating — requires approval. NOTE: a commit is not reversible via /rewind.',
669
+ parameters: {
670
+ type: 'object',
671
+ properties: {
672
+ message: { type: 'string', description: 'Commit message (required, non-empty)' },
673
+ all: { type: 'boolean', description: 'Automatically stage modified/deleted tracked files before committing (git commit -a)', default: false },
674
+ },
675
+ required: ['message'],
676
+ },
677
+ },
678
+
679
+ git_branch: {
680
+ description: 'List branches (no name — read-only) or create/delete a branch (name given — mutating). Set delete=true to delete (force=true for -D). Branch creation/deletion is not reversible via /rewind.',
681
+ parameters: {
682
+ type: 'object',
683
+ properties: {
684
+ name: { type: 'string', description: 'Branch name to create or delete; omit to list branches' },
685
+ delete: { type: 'boolean', description: 'Delete the named branch instead of creating it', default: false },
686
+ force: { type: 'boolean', description: 'Force the operation (e.g. delete an unmerged branch with -D)', default: false },
687
+ },
688
+ required: [],
689
+ },
690
+ },
691
+
692
+ git_checkout: {
693
+ description: 'Switch to a branch or ref (set create=true to create and switch with -b). Mutating — requires approval. WARNING: checkout can overwrite or discard uncommitted changes in the working tree, and those changes are NOT recoverable via /rewind (checkpoints snapshot file-tool mutations only, not git operations).',
694
+ parameters: {
695
+ type: 'object',
696
+ properties: {
697
+ name: { type: 'string', description: 'Branch or ref to switch to' },
698
+ create: { type: 'boolean', description: 'Create the branch and switch to it (git checkout -b)', default: false },
699
+ force: { type: 'boolean', description: 'Force checkout, discarding local changes (git checkout -f)', default: false },
700
+ },
701
+ required: ['name'],
702
+ },
703
+ },
704
+
705
+ git_worktree: {
706
+ description: 'Manage linked worktrees for running parallel agents in isolated working trees. op=list (read-only) lists worktrees; op=add creates one (optionally on a new `branch`); op=remove deletes one (force=true to override). add/remove are mutating and not reversible via /rewind.',
707
+ parameters: {
708
+ type: 'object',
709
+ properties: {
710
+ op: { type: 'string', description: 'Operation: list | add | remove', enum: ['list', 'add', 'remove'], default: 'list' },
711
+ path: { type: 'string', description: 'Worktree directory path (required for add/remove)' },
712
+ branch: { type: 'string', description: 'For add: create and check out this new branch in the worktree' },
713
+ force: { type: 'boolean', description: 'For remove: remove even with local changes', default: false },
714
+ },
715
+ required: [],
716
+ },
717
+ },
718
+
465
719
  // ──────────────────────────────────────────────────────────────────
466
720
  // Parser-wrapper tags. These are XML envelopes that carry other tool
467
721
  // calls; they are not invocable tools themselves. Entries exist only