pi-hashline-edit-pro 7.0.0 → 7.1.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.
- package/README.md +28 -9
- package/index.ts +31 -28
- package/package.json +1 -1
- package/src/auto-read-all.ts +2 -2
- package/src/config-ui.ts +6 -1
- package/src/config.ts +6 -1
- package/src/edit-common.ts +15 -7
- package/src/model-gate.ts +1 -0
- package/src/outline/parsers.ts +0 -1
- package/src/outline/render.ts +154 -82
- package/src/outline/vendor/README.md +4 -0
- package/src/outline/vendor/parsers/blade.ts +1 -1
- package/src/outline/vendor/parsers/java.ts +144 -331
- package/src/outline/vendor/parsers/manager.ts +12 -183
- package/src/outline/vendor/parsers/php.ts +16 -81
- package/src/outline/vendor/parsers/sass.ts +0 -11
- package/src/outline/vendor/parsers/scss.ts +0 -8
- package/src/outline/vendor/types.ts +0 -1
- package/src/outline-tool.ts +184 -0
- package/src/prompts.ts +1 -0
- package/src/read.ts +1 -0
- package/src/structured.ts +32 -0
- package/tool-prompts/outline-guidelines.md +2 -0
- package/tool-prompts/outline-snippet.md +1 -0
- package/tool-prompts/outline.md +3 -0
- package/tool-prompts/read-guidelines.md +1 -1
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ the result is the post-edit diff with fresh anchors, so the next edit needs no r
|
|
|
29
29
|
- [Quickstart](#quickstart)
|
|
30
30
|
- [Tools](#tools)
|
|
31
31
|
- [read](#read)
|
|
32
|
+
- [outline](#outline)
|
|
32
33
|
- [replace](#replace)
|
|
33
34
|
- [replace_match](#replace_match)
|
|
34
35
|
- [insert](#insert)
|
|
@@ -87,6 +88,7 @@ pi install /path/to/pi-hashline-edit-pro
|
|
|
87
88
|
| Built-in | Effect |
|
|
88
89
|
| --- | --- |
|
|
89
90
|
| `read` | overridden, returns `anchor│content` rows |
|
|
91
|
+
| `outline` | new opt-in tool; returns one file's structural outline |
|
|
90
92
|
| `edit` | disabled |
|
|
91
93
|
| `grep` | replaced by an anchored `grep`; pi's own `grep` returns when Anchor grep is off |
|
|
92
94
|
| `copy`, `move` | disabled while Copy/move is off |
|
|
@@ -155,7 +157,7 @@ Nothing commits until an edit call returns: the extension validates the request
|
|
|
155
157
|
|
|
156
158
|
## Tools
|
|
157
159
|
|
|
158
|
-
The extension registers
|
|
160
|
+
The extension registers nine tools: `read`, `outline`, `replace`, `replace_match`, `insert`, `copy`, `move`, `grep`, and `undo_last_change`. The built-in `edit` tool is disabled. `copy` and `move` are enabled by default; turn Copy/move off in `/hashline-config` to remove both. `replace_match` is enabled by default; turn Replace match off in `/hashline-config` to remove it. `outline` is off by default; turn Outline tool on in `/hashline-config` to enable it, which also activates it when the host's default tool list omits it. `replace`, `replace_match`, `insert`, `copy`, and `move` do not need a `path` parameter by default: the file is resolved from the anchors' session ownership alone, so an edit can only land on the file the anchors were served for. A `path` passed anyway is checked against anchor ownership: a matching path is accepted, a mismatch is rejected. Opt in with `/hashline-config` to require `path` in `replace`, `replace_match`, `insert`, `copy`, and `move` for RPC visibility (for example pimacs.el); anchors still resolve the target and `path` must match.
|
|
159
161
|
|
|
160
162
|
### read
|
|
161
163
|
|
|
@@ -167,7 +169,7 @@ The extension registers eight tools: `read`, `replace`, `replace_match`, `insert
|
|
|
167
169
|
| `offset` | Line number (1-indexed) or a served anchor to start reading from. |
|
|
168
170
|
| `limit` | Maximum number of lines to return from `offset`. |
|
|
169
171
|
|
|
170
|
-
Output is capped at 2000 lines and 50KB. Paged output ends with a continuation hint, for example `[Showing lines 1-50 of 120. Use offset=emnox to continue.]`.
|
|
172
|
+
Output is capped at 2000 lines and 50KB. Paged output ends with a continuation hint, for example `[Showing lines 1-50 of 120. Use offset=emnox to continue.]`. When the `outline` tool is enabled, a truncated read appends a notice pointing at it.
|
|
171
173
|
|
|
172
174
|
A line whose `anchor│content` row exceeds 50KB is replaced by a marker that keeps the line's anchor: `anchor│[Line N is 2.2MB, exceeds 50.0KB; content not shown. Use bash: sed -n 'Np' <path> | head -c 51200]`. The marker is served like a normal row, so the whole line can still be replaced through it.
|
|
173
175
|
|
|
@@ -180,6 +182,17 @@ Edge cases:
|
|
|
180
182
|
- BOMs are stripped for display. Non-UTF-8 bytes are shown as `U+FFFD`; editing such a file rewrites it as UTF-8, with a warning.
|
|
181
183
|
- Files over 1,353,139 lines or 100MB are rejected with `[E_FILE_TOO_LARGE]`.
|
|
182
184
|
|
|
185
|
+
### outline
|
|
186
|
+
|
|
187
|
+
`outline` returns the structural outline of one file: a `=== path (language) — N lines ===` header, then one `anchor│content` row per symbol whose content is the first line of that symbol's declaration (annotations and attributes excluded). A `[limit N]` line under a multi-line symbol is the span to pass as `read`'s `limit` when reading the body from that anchor, and an `[N nested]` line marks children hidden by `depth`. Hiding children that way is a view choice rather than truncation, so `truncated` stays false and the section still counts as complete. The structured result reports `complete`, which is true only when no row or byte cap cut the outline, every rendered row was served, and no line was skipped for a missing anchor; a head-and-tail preview that omits lines is incomplete. Files the parser cannot outline fall back to the same head-and-tail preview auto-read all uses. A row whose line is longer than 200 characters is clipped and is not served for editing, so read that line first. The tool is offered only while Outline tool is on in `/hashline-config`.
|
|
188
|
+
|
|
189
|
+
| Parameter | Description |
|
|
190
|
+
| --- | --- |
|
|
191
|
+
| `path` | Path to the file to outline (relative or absolute). |
|
|
192
|
+
| `depth` | Nesting depth: `0` lists top-level symbols only, `1` adds their members, and the default is `2` (maximum `10`). |
|
|
193
|
+
|
|
194
|
+
Rows are the file's real lines, so an outline anchor is editable with `replace` or `insert` without a separate `read`, exactly like an auto-read-all outline row. The outline shows only each symbol's declaration line, so an edit inside a body still needs a `read` of that span.
|
|
195
|
+
|
|
183
196
|
### replace
|
|
184
197
|
|
|
185
198
|
`replace` removes a range of lines and puts new lines in their place. One edit per call, with the fields at the top level:
|
|
@@ -338,9 +351,9 @@ Auto-read all is off by default and has three modes, selected in `/hashline-conf
|
|
|
338
351
|
|
|
339
352
|
Files are filtered before injection: symlinks, directories, image extensions (including SVG), binary files (a NUL byte in the first 8KB), files over 200KB, any path with a vendored segment (vendor, node_modules, bower_components, third_party, thirdparty, jspm_packages, .venv, venv, site-packages, __pycache__, .tox, .gradle, .terraform, Pods, Carthage, DerivedData, coreui, coreui-icons, case-insensitive; `resources/views/vendor` is kept so Laravel view overrides stay attached), and vendored or generated names and patterns (*.min.js, *.min.css, *.min.mjs, *-min.js, *-min.css, *.bundle.*, *.chunk.*, *.umd.js, *.map, *.lock, package-lock.json, yarn.lock, composer.lock, Gemfile.lock, Cargo.lock, poetry.lock, Pipfile.lock, go.sum, flake.lock, *.generated.*, *.gen.*, *_pb2.py, *.pb.go, *.g.dart, *.freezed.dart, *.designer.cs, *.g.cs, *.snap, _ide_helper.php, _ide_helper_models.php, .phpstorm.meta.php, .eslintcache, coreui-icons.*, coreui.css) are skipped. The attachment stops at 500 files or at a byte budget derived from the model context window (200KB floor, 2MB ceiling), and it never drops below one file; `full` also caps the budget at 2MB, while `outline` follows the context-derived budget because outlines stay compact. Files are ranked before the budget applies: working-tree changes first, then manifest and entry files, then the dominant source tree, then everything else, and no single top-level directory may take more than 40% of the budget in the first pass, so one large subtree cannot crowd out the rest. Skipped and not-attached files are named at the end of the message so the model can `read` them on demand. A model matched by `disableOnModels` skips the injection entirely.
|
|
340
353
|
|
|
341
|
-
Each attached file starts with a header (`=== path ===` in `full` mode, `=== path (language) — lines ===` in `outline` mode) followed by its anchor rows. Edit directly from the attachment with replace and insert, so no `read` is needed. `full` attaches files whole; `outline` attaches each file's structural outline within the budget. In outline mode
|
|
354
|
+
Each attached file starts with a header (`=== path ===` in `full` mode, `=== path (language) — lines ===` in `outline` mode) followed by its anchor rows. Edit directly from the attachment with replace and insert, so no `read` is needed. `full` attaches files whole; `outline` attaches each file's structural outline within the budget. In outline mode each symbol appears as the first line of its declaration, a `[limit N]` line under it gives the span to read when the symbol spans several lines, and `[N nested]` marks children hidden by auto-read all's six-level depth cap.
|
|
342
355
|
|
|
343
|
-
A coverage line after the header reports how many attached files were served in full; a section truncated by the per-file budget is not counted as complete. Each attached file begins with a `=== path ===` header, and files that were not attached are named in the footer.
|
|
356
|
+
A coverage line after the header reports how many attached files were served in full; a section truncated by the per-file budget, a preview that omitted lines, or one that includes a clipped row is not counted as complete. Children hidden by the outline depth cap are marked `[N nested]` and do not make a section incomplete. Each attached file begins with a `=== path ===` header, and files that were not attached are named in the footer.
|
|
344
357
|
|
|
345
358
|
The setting lives in `/hashline-config` as Auto-read all and in `config.json` as `autoReadAll` (`"off"`, `"outline"`, or `"full"`; older configs with `"on"` or `true` are read as `"full"`, `"git"` as `"full"`, `"outline(git)"` as `"outline"` — both with Git repos only on — and `false` as `"off"`), and as `autoReadAllRequireGit`, the Git repos only switch (on by default). Extra folders and files are ignored via `/hashline-config` as Ignore folders/files and via `config.json` as `autoReadAllIgnore` (array of folder names, file names, or globs, for example `["docs", "scratch.md", "*.test.ts"]`). A single-segment entry skips any folder or file with that exact name (case-insensitive); an entry containing glob characters (`*`, `?`, `[`, `]`, `{`, `}`) is matched as a glob against the file name, or against the whole relative path when it also contains a slash, with the same syntax as `grep`'s `glob`; any other entry with a slash (for example `"src/tmp"`) skips that path. Custom ignores are counted with the vendor/name/pattern skips in the footer.
|
|
346
359
|
|
|
@@ -348,7 +361,7 @@ The setting lives in `/hashline-config` as Auto-read all and in `config.json` as
|
|
|
348
361
|
|
|
349
362
|
| Command | Description |
|
|
350
363
|
| --- | --- |
|
|
351
|
-
| `/hashline-config` | Open the settings window: auto-read anchors, auto-read all mode, git repos only, ignore folders/files, disable on models, read on disabled models, diff context lines, `grep` tool, copy/move tools, replace_match tool, required `path`, strict input, and undo retention. Persists across sessions. |
|
|
364
|
+
| `/hashline-config` | Open the settings window: auto-read anchors, auto-read all mode, git repos only, ignore folders/files, disable on models, read on disabled models, diff context lines, `grep` tool, copy/move tools, replace_match tool, outline tool, required `path`, strict input, and undo retention. Persists across sessions. |
|
|
352
365
|
| `/clear-anchors` | Clear the session's anchor claims. Anchors are re-claimed on the next `read`. |
|
|
353
366
|
|
|
354
367
|
Settings live in `~/.config/pi-hashline-edit-pro/config.json`, created when a setting is first changed in `/hashline-config`:
|
|
@@ -362,6 +375,7 @@ Settings live in `~/.config/pi-hashline-edit-pro/config.json`, created when a se
|
|
|
362
375
|
"anchorGrepEnabled": true,
|
|
363
376
|
"copyMoveEnabled": true,
|
|
364
377
|
"replaceMatchEnabled": true,
|
|
378
|
+
"outlineEnabled": false,
|
|
365
379
|
"requirePath": false,
|
|
366
380
|
"strictInput": false,
|
|
367
381
|
"diffContextLines": 1,
|
|
@@ -380,6 +394,7 @@ Settings live in `~/.config/pi-hashline-edit-pro/config.json`, created when a se
|
|
|
380
394
|
| `anchorGrepEnabled` | Anchor grep | `true` | Register an anchored `grep` in place of pi's own `grep` while it is on. |
|
|
381
395
|
| `copyMoveEnabled` | Copy/move | `true` | Offer the `copy` and `move` tools; when off, both are removed from the active tools. |
|
|
382
396
|
| `replaceMatchEnabled` | Replace match | `true` | Offer the `replace_match` tool; when off, it is removed from the active tools. |
|
|
397
|
+
| `outlineEnabled` | Outline tool | `false` | Register the `outline` tool for one file's structure on demand; when off, it is removed from the active tools. |
|
|
383
398
|
| `requirePath` | Require path | `false` | `replace`, `replace_match`, `insert`, `copy`, and `move` require a `path` argument that must match anchor ownership. |
|
|
384
399
|
| `strictInput` | Strict input | `false` | Reject auto-fixable slips (`[W_BAD_SHAPE]`, `[W_BAD_REF]`, `[W_INVALID_PATCH]`, `[W_BARE_HASH_PREFIX]`, `[W_ANCHOR_CASE]`) with `[E_BAD_SHAPE]` instead of applying them with a warning. |
|
|
385
400
|
| `diffContextLines` | Diff context | `1` | Surrounding lines in post-edit diffs, 0-10 (needs Auto-read). |
|
|
@@ -387,7 +402,7 @@ Settings live in `~/.config/pi-hashline-edit-pro/config.json`, created when a se
|
|
|
387
402
|
| `readOnDisabledModels` | Read on disabled models | `"vanilla"` | `read` for a model matched by `disableOnModels`: `"vanilla"` keeps pi's ordinary `read` active while the anchored edit tools stay off; `"remove"` disables `read` with the rest of the anchored tools. |
|
|
388
403
|
| `undoRetentionDays` | Undo retention | `30` | Days to keep undo history; records older than this are deleted when the store opens. `0` keeps them forever (0-3650). |
|
|
389
404
|
|
|
390
|
-
`disableOnModels` turns off the whole anchored surface for the models you name, so another edit tool (`apply_patch`, a shell read) can own the session without competing instructions. A match removes `replace`, `replace_match`, `insert`, `copy`, `move`, `grep`, and `undo_last_change` from the active tools, and also `read` when Read on disabled models is `remove`, registering pi's plain `grep` for those models, and it also skips the auto-read-all injection, the auto-read block after `write`, the post-edit diff substitution, and the `write` hook that refuses a `write` echoing a served anchor. Because the extension's `read` replaces the built-in one, a matched model has no `read` at all under `remove` unless another extension provides one; the default `vanilla` gives those models pi's ordinary `read` while the anchored edit tools stay off. The list is checked on `session_start`, on `model_select`, and again on `before_agent_start`, so a model change inside a session switches the surface immediately and another extension cannot re-add the tools mid-session. The default `[]` leaves every model unchanged.
|
|
405
|
+
`disableOnModels` turns off the whole anchored surface for the models you name, so another edit tool (`apply_patch`, a shell read) can own the session without competing instructions. A match removes `outline`, `replace`, `replace_match`, `insert`, `copy`, `move`, `grep`, and `undo_last_change` from the active tools, and also `read` when Read on disabled models is `remove`, registering pi's plain `grep` for those models, and it also skips the auto-read-all injection, the auto-read block after `write`, the post-edit diff substitution, and the `write` hook that refuses a `write` echoing a served anchor. Because the extension's `read` replaces the built-in one, a matched model has no `read` at all under `remove` unless another extension provides one; the default `vanilla` gives those models pi's ordinary `read` while the anchored edit tools stay off. The list is checked on `session_start`, on `model_select`, and again on `before_agent_start`, so a model change inside a session switches the surface immediately and another extension cannot re-add the tools mid-session. The default `[]` leaves every model unchanged.
|
|
391
406
|
|
|
392
407
|
When `PI_HASHLINE_DIR` is unset or empty, non-Windows platforms honor `XDG_CONFIG_HOME` when set (falling back to `~/.config`); on Windows the directory always uses `~/.config`, where `~` is `%USERPROFILE%`. To move the directory explicitly, see [Isolated state](#isolated-state).
|
|
393
408
|
|
|
@@ -397,7 +412,7 @@ When `PI_HASHLINE_DIR` is unset or empty, non-Windows platforms honor `XDG_CONFI
|
|
|
397
412
|
| --- | --- | --- |
|
|
398
413
|
| Output cap | 2000 lines and 50KB | `read`, auto-read after `write`, post-edit diffs, patches, previews, `details.patch` |
|
|
399
414
|
| Oversized row | 50KB per `anchor│content` row | replaced by an anchor-keeping marker you can still edit through |
|
|
400
|
-
| Line cap | 1,353,139 lines per file | `read`, `replace`, `replace_match`, `insert`, `copy`, `move` (`[E_FILE_TOO_LARGE]`) |
|
|
415
|
+
| Line cap | 1,353,139 lines per file | `read`, `outline`, `replace`, `replace_match`, `insert`, `copy`, `move` (`[E_FILE_TOO_LARGE]`) |
|
|
401
416
|
| File size | 100MB | all tools (`[E_FILE_TOO_LARGE]`) |
|
|
402
417
|
| Hash window | first 500 bytes of a line | anchor identity for long lines |
|
|
403
418
|
| Patch guard | 1MB of pre-edit + post-edit text | patch generation is skipped and `patchTruncated` is set |
|
|
@@ -405,6 +420,7 @@ When `PI_HASHLINE_DIR` is unset or empty, non-Windows platforms honor `XDG_CONFI
|
|
|
405
420
|
| Grep output | 2000 rows and 50KB | `grep` |
|
|
406
421
|
| Grep row fragment | 500 bytes | long match and context rows shown as `...` fragments |
|
|
407
422
|
| Auto-read all files | 500 files, 200KB per file | files larger than 200KB and files past the cap are omitted |
|
|
423
|
+
| Outline rows | 400 symbol rows and 50KB of content per file (a truncation footer may follow) | `outline`, auto-read all `outline` mode |
|
|
408
424
|
| Auto-read all budget | 200KB floor; 2MB ceiling in `full` | total injection size derived from the model context window; `outline` is not capped at 2MB |
|
|
409
425
|
| Stale-range feedback | first 100 lines | rows returned with `[E_RANGE_STALE]` |
|
|
410
426
|
| Session anchors | 1,353,139 anchors in use | all tools; the least recently read or edited files are freed when the quota is exhausted (`[W_ANCHOR_RECLAIMED]`) |
|
|
@@ -414,14 +430,16 @@ When `PI_HASHLINE_DIR` is unset or empty, non-Windows platforms honor `XDG_CONFI
|
|
|
414
430
|
|
|
415
431
|
## Tool result details
|
|
416
432
|
|
|
417
|
-
All
|
|
433
|
+
All nine tools return machine-readable metadata in `details` alongside the model-visible text.
|
|
418
434
|
|
|
419
|
-
All
|
|
435
|
+
All nine tools also declare an `outputSchema` and return `structuredContent`, so codemode scripts receive anchors, line numbers, diffs, and errors as data instead of parsing the rendered text. In a codemode session `tools.read()` resolves to the file's text, like the built-in `read`; pass `anchors: true` to receive `{ text, lines, notices }`, where `text` is the raw file content of the served window and `lines` carries the anchored rows. The model still receives `content`. Every structured result is either `{ ok: true, kind, ... }` or `{ ok: false, kind: "error", error: { code, message } }`; a failed call is still a tool error (`isError: true`) and also carries `structuredContent`, so a script can branch on the code without matching the message.
|
|
420
436
|
|
|
421
437
|
`read` returns `{ kind: "read", path, text, lines, totalLines, startLine, nextOffset, truncated, blockedByLongLine, hadUtf8DecodeErrors, notices }`, where `text` is the raw file content of the served window (no anchors, no footers) and each entry of `lines` is `{ line, text, anchor, rendered }` for a row that was served (`text` there is what was shown, so an oversized line appears as its marker while keeping its anchor). `notices` carries non-file notes such as the UTF-8 rewrite note and `[W_ANCHOR_RECLAIMED]`. An image read returns `{ type: "image", data, mimeType, note }`.
|
|
422
438
|
|
|
423
439
|
`replace`, `replace_match`, `insert`, `copy`, `move`, and `undo_last_change` return `{ kind: "edit", verb, classification, path, text, diff, warnings, hints, firstChangedLine, anchors, anchorsOmitted }`, where `verb` is `"replaced"`, `"inserted"`, `"copied"`, `"moved"`, or `"undone"`, `anchors` are the live rows of the post-edit diff, and `anchorsOmitted` is true when the diff was truncated or carried no rows, meaning a fresh read is needed. A cross-file move reports the destination file and its anchors.
|
|
424
440
|
|
|
441
|
+
`outline` returns `{ kind: "outline", path, text, totalLines, depth, lines, truncated, complete, notices }`, where `text` is the outline body without the notices, `lines` is the anchored rows it served, `complete` is true only when no cap cut the outline, every rendered row was served, and no line was skipped for a missing anchor; `notices` carries non-file notes such as the UTF-8 rewrite note and `[W_ANCHOR_RECLAIMED]`; in a codemode session `tools.outline()` resolves to the served outline text (with notices) unless `anchors: true` is passed.
|
|
442
|
+
|
|
425
443
|
`grep` returns `{ kind: "grep", text, matches, files, truncated, results }`, where each entry of `results` is `{ path, matchLines, lines, hadUtf8DecodeErrors }` and `lines` uses the same `{ line, text, anchor, rendered }` shape as `read`.
|
|
426
444
|
|
|
427
445
|
| Tool | `details` |
|
|
@@ -431,6 +449,7 @@ All eight tools also declare an `outputSchema` and return `structuredContent`, s
|
|
|
431
449
|
| `replace_match` | Same shape as `replace`: `diff` (post-edit diff with current anchors), `patch`, `patchTruncated`, `firstChangedLine`, `snapshotId`, `classification` (`"noop"` when nothing changed), and `metrics` with the same counters. |
|
|
432
450
|
| `copy`, `move` | Same shape as `replace`: `diff` (post-edit diff with current anchors), `patch`, `patchTruncated`, `firstChangedLine`, `snapshotId`, `classification` (`"noop"` when a move changes nothing), and `metrics` with the same counters. |
|
|
433
451
|
| `undo_last_change` | `diff` (the undo diff with restored anchors), `patch`, `patchTruncated`, and `metrics` in the same shape as `replace`. |
|
|
452
|
+
| `outline` | `truncated` (true when the row or byte cap cut the outline), `complete` (true only when no cap cut the outline, every rendered row was served, and no line was skipped for a missing anchor), `snapshotId`, and `metrics` with `rows` (every rendered symbol row, including clipped ones) and `truncated`; structured `lines` holds only the rows that were served for editing. |
|
|
434
453
|
| `grep` | `metrics` with `matches` (capped at `limit`), `files`, and `truncated`; `truncation` (the standard pi truncation report) when output was cut; and `linesTruncated` (true when long lines were shown as fragments). |
|
|
435
454
|
|
|
436
455
|
`snapshotId`, `firstChangedLine`, and `metrics.warnings` are the three fields external consumers most often read; `snapshotId` is a string fingerprint, `firstChangedLine` is a 1-based line number on the result file, and `metrics.warnings` counts the `[W_*]` notices in `details.warnings`; hint `[H_*]` notices live in `details.hints` and are not counted.
|
package/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { regCopy, regMove } from "./src/copy-move";
|
|
|
8
8
|
import { regGrep, regVanillaGrep } from "./src/grep";
|
|
9
9
|
import { regUndo, clearUndo } from "./src/replace-undo";
|
|
10
10
|
import { regRead, fmtReadPreview } from "./src/read";
|
|
11
|
+
import { regOutline } from "./src/outline-tool";
|
|
11
12
|
import { ANCHOR_TOOL_NAMES, modelDisabled, type ModelLike } from "./src/model-gate";
|
|
12
13
|
import { buildAutoReadAllInjection, autoReadAllBudget, isOutlineAutoReadAll } from "./src/auto-read-all";
|
|
13
14
|
import type { RMetrics } from "./src/replace-response";
|
|
@@ -15,7 +16,7 @@ import type { ReplaceDetails } from "./src/replace";
|
|
|
15
16
|
import { extractHints, extractWarnings } from "./src/replace-render";
|
|
16
17
|
import { MAX_HASH_LINES } from "./src/hashline";
|
|
17
18
|
import { withStructuredText } from "./src/structured";
|
|
18
|
-
import type { AutoReadAllMode, ReadOnDisabledModels } from "./src/config";
|
|
19
|
+
import type { AutoReadAllMode, Config, ReadOnDisabledModels } from "./src/config";
|
|
19
20
|
import {
|
|
20
21
|
readConfig,
|
|
21
22
|
readConfigWithStatus,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
toggleAnchorGrep,
|
|
26
27
|
toggleCopyMove,
|
|
27
28
|
toggleReplaceMatch,
|
|
29
|
+
toggleOutline,
|
|
28
30
|
cycleReadOnDisabledModels,
|
|
29
31
|
toggleRequirePath,
|
|
30
32
|
toggleStrictInput,
|
|
@@ -38,7 +40,7 @@ import { loadHashStore, pruneExpiredUndo, pruneMissing } from "./src/hash-store"
|
|
|
38
40
|
import { type AnchorSessionContext, initRegistry, gcRegistrySidecars, clearRegistry, freeAnchors, freeAnchorsForPaths, sessionKeyFor, sessionContextReadable, withAnchorSession, releaseRegistrySession, formatAnchorReclaimNotice, takeReclaimedPaths } from "./src/anchor-registry";
|
|
39
41
|
import { serveRows } from "./src/served";
|
|
40
42
|
import { finalizeTurn, planAssistantMessage } from "./src/batch";
|
|
41
|
-
import { currentEditFlags } from "./src/edit-common";
|
|
43
|
+
import { currentEditFlags, editToolEnabled } from "./src/edit-common";
|
|
42
44
|
import { HashlineConfigOverlay } from "./src/config-ui";
|
|
43
45
|
import { registerWriteHook } from "./src/write-hook";
|
|
44
46
|
import { readNormFile } from "./src/file-reader";
|
|
@@ -53,8 +55,18 @@ interface PendingRegistrySession {
|
|
|
53
55
|
alive: () => boolean;
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
function configEnablesTool(tool: string, config: Config): boolean {
|
|
59
|
+
if (tool === "grep") return config.anchorGrepEnabled === true;
|
|
60
|
+
return editToolEnabled(tool, {
|
|
61
|
+
replaceMatchEnabled: config.replaceMatchEnabled !== false,
|
|
62
|
+
copyMoveEnabled: config.copyMoveEnabled !== false,
|
|
63
|
+
outlineEnabled: config.outlineEnabled === true,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
56
67
|
export default function (pi: ExtensionAPI): void {
|
|
57
68
|
regRead(pi);
|
|
69
|
+
regOutline(pi);
|
|
58
70
|
|
|
59
71
|
regReplace(pi);
|
|
60
72
|
regReplaceMatch(pi);
|
|
@@ -82,11 +94,21 @@ export default function (pi: ExtensionAPI): void {
|
|
|
82
94
|
let gateApplied = false;
|
|
83
95
|
const pendingRegistrySessions: PendingRegistrySession[] = [];
|
|
84
96
|
|
|
97
|
+
function setAnchorToolEnabled(tools: readonly string[], enabled: boolean): void {
|
|
98
|
+
const active = pi.getActiveTools();
|
|
99
|
+
pi.setActiveTools(enabled ? [...new Set([...active, ...tools])] : active.filter((tool) => !tools.includes(tool)));
|
|
100
|
+
for (const tool of tools) {
|
|
101
|
+
if (enabled) baseAnchorTools.add(tool);
|
|
102
|
+
else baseAnchorTools.delete(tool);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
85
106
|
async function refreshEditTools(): Promise<void> {
|
|
86
107
|
try {
|
|
87
108
|
codemodeActive = pi.getActiveTools().includes("codemode");
|
|
88
109
|
const flags = await currentEditFlags(codemodeActive);
|
|
89
110
|
if (readFlavor !== "vanilla") regRead(pi, flags);
|
|
111
|
+
regOutline(pi, flags);
|
|
90
112
|
regReplace(pi, flags);
|
|
91
113
|
regReplaceMatch(pi, flags);
|
|
92
114
|
regInsert(pi, flags);
|
|
@@ -144,10 +166,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
144
166
|
const enabled = ANCHOR_TOOL_NAMES.filter((tool) => {
|
|
145
167
|
if (tool === "read") return true;
|
|
146
168
|
if (!baseAnchorTools.has(tool)) return false;
|
|
147
|
-
|
|
148
|
-
if (tool === "copy" || tool === "move") return config.copyMoveEnabled !== false;
|
|
149
|
-
if (tool === "grep") return config.anchorGrepEnabled === true;
|
|
150
|
-
return true;
|
|
169
|
+
return configEnablesTool(tool, config);
|
|
151
170
|
});
|
|
152
171
|
const active = pi.getActiveTools().filter((tool) => tool !== "edit");
|
|
153
172
|
const base = enabled.includes("grep") ? active.filter((tool) => tool !== "grep") : active;
|
|
@@ -216,12 +235,9 @@ export default function (pi: ExtensionAPI): void {
|
|
|
216
235
|
autoReadAllInjected = sessionBranch.some((entry) => entry.type === "custom_message" && entry.customType === AUTO_READ_ALL_CUSTOM_TYPE);
|
|
217
236
|
await refreshEditTools();
|
|
218
237
|
pi.setActiveTools(
|
|
219
|
-
pi.getActiveTools().filter((
|
|
220
|
-
if (config.copyMoveEnabled === false && (t === "copy" || t === "move")) return false;
|
|
221
|
-
if (config.replaceMatchEnabled === false && t === "replace_match") return false;
|
|
222
|
-
return true;
|
|
223
|
-
}),
|
|
238
|
+
pi.getActiveTools().filter((tool) => tool === "grep" || configEnablesTool(tool, config)),
|
|
224
239
|
);
|
|
240
|
+
setAnchorToolEnabled(["outline"], config.outlineEnabled === true);
|
|
225
241
|
await syncModelGate(ctx.model, ctx.cwd);
|
|
226
242
|
const debugValue = process.env.PI_HASHLINE_DEBUG;
|
|
227
243
|
if (debugValue === "1" || debugValue === "true") {
|
|
@@ -278,7 +294,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
278
294
|
}));
|
|
279
295
|
|
|
280
296
|
pi.registerCommand("hashline-config", {
|
|
281
|
-
description: "Open the hashline settings window (auto-read, diff context, auto-read all, git repos only, ignore folders/files, grep, copy/move, replace_match, path, strict input, disable on models, read on disabled models)",
|
|
297
|
+
description: "Open the hashline settings window (auto-read, diff context, auto-read all, git repos only, ignore folders/files, grep, copy/move, replace_match, outline, path, strict input, disable on models, read on disabled models)",
|
|
282
298
|
handler: async (_args, ctx) => {
|
|
283
299
|
if (!ctx.hasUI) {
|
|
284
300
|
ctx.ui.notify("/hashline-config requires interactive mode", "error");
|
|
@@ -307,22 +323,9 @@ export default function (pi: ExtensionAPI): void {
|
|
|
307
323
|
if (enabled) baseAnchorTools.add("grep");
|
|
308
324
|
else baseAnchorTools.delete("grep");
|
|
309
325
|
}
|
|
310
|
-
else if (key === "copyMoveEnabled")
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
pi.setActiveTools(enabled ? [...new Set([...active, "copy", "move"])] : active.filter((t) => t !== "copy" && t !== "move"));
|
|
314
|
-
for (const tool of ["copy", "move"]) {
|
|
315
|
-
if (enabled) baseAnchorTools.add(tool);
|
|
316
|
-
else baseAnchorTools.delete(tool);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
else if (key === "replaceMatchEnabled") {
|
|
320
|
-
const enabled = await toggleReplaceMatch();
|
|
321
|
-
const active = pi.getActiveTools();
|
|
322
|
-
pi.setActiveTools(enabled ? [...new Set([...active, "replace_match"])] : active.filter((t) => t !== "replace_match"));
|
|
323
|
-
if (enabled) baseAnchorTools.add("replace_match");
|
|
324
|
-
else baseAnchorTools.delete("replace_match");
|
|
325
|
-
}
|
|
326
|
+
else if (key === "copyMoveEnabled") setAnchorToolEnabled(["copy", "move"], await toggleCopyMove());
|
|
327
|
+
else if (key === "replaceMatchEnabled") setAnchorToolEnabled(["replace_match"], await toggleReplaceMatch());
|
|
328
|
+
else if (key === "outlineEnabled") setAnchorToolEnabled(["outline"], await toggleOutline());
|
|
326
329
|
else if (key === "requirePath") await toggleRequirePath();
|
|
327
330
|
else if (key === "strictInput") await toggleStrictInput();
|
|
328
331
|
await refreshEditTools();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-hashline-edit-pro",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Hash-anchored read/replace/insert/grep tools for pi-coding-agent. Every line gets a unique 5-character lowercase tokenizer-friendly anchor that stays stable across edits; stale or ambiguous anchors are rejected, never fuzzy-matched. Undo persists across restarts.",
|
|
6
6
|
"main": "index.ts",
|
package/src/auto-read-all.ts
CHANGED
|
@@ -31,7 +31,7 @@ const HEADER =
|
|
|
31
31
|
"[hashline auto-read-all] Every non-ignored project file is attached below as `=== path ===` then `anchor│content` rows with live anchors.\nEdit directly from the attachment with replace and insert; do not call read for attached files.\nFiles listed as not attached can be read normally.\nAnchors are lowercase and stay valid until their line is edited.";
|
|
32
32
|
|
|
33
33
|
const OUTLINE_HEADER =
|
|
34
|
-
"[hashline auto-read-all] Every non-ignored project file is outlined below as `=== path (language) — lines ===` then `anchor
|
|
34
|
+
"[hashline auto-read-all] Every non-ignored project file is outlined below as `=== path (language) — lines ===` then `anchor│content` rows with live anchors: each symbol row is the first line of that symbol's declaration (annotations and attributes excluded), a `[limit N]` line under a multi-line symbol is the span to request when reading from that anchor, a `[N nested]` line marks children hidden by the six-level depth cap, and a file the parser cannot outline falls back to a head-and-tail preview.\nEdit a row's anchor with replace or insert, or read from it with `offset` set to that anchor (and the row's `limit` when the next line shows one); the outline shows only symbol declaration lines, so read a span before editing inside a body. A row whose line is too long is clipped with a trailing `...` and is not served for editing; read that line first. Do not call read for attached files except to fetch a symbol span or a clipped row.\nFiles listed as not attached can be read normally.\nAnchors are lowercase and stay valid until their line is edited.";
|
|
35
35
|
|
|
36
36
|
const IMAGE_EXTENSIONS = new Set([
|
|
37
37
|
".avif",
|
|
@@ -554,7 +554,7 @@ async function renderFile(file: string, cwd: string, outline: boolean): Promise<
|
|
|
554
554
|
if (outline) {
|
|
555
555
|
const section = await buildFileOutline({ displayPath: file, content: normalized, hashes: fileHashes });
|
|
556
556
|
serveRows(absolutePath, fileHashes, fileLines, section.servedHashes);
|
|
557
|
-
return { file, text: section.text, totalLines: fileHashes.length, absolutePath, complete:
|
|
557
|
+
return { file, text: section.text, totalLines: fileHashes.length, absolutePath, complete: section.complete };
|
|
558
558
|
}
|
|
559
559
|
const preview = await fmtReadPreview(normalized, {}, fileHashes, absolutePath, AUTO_READ_ALL_MAX_BUDGET_BYTES, MAX_HASH_LINES);
|
|
560
560
|
serveRows(absolutePath, fileHashes, fileLines, preview.servedHashes);
|
package/src/config-ui.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Key, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from
|
|
|
2
2
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { DEFAULT_UNDO_RETENTION_DAYS, readConfig, type Config } from "./config";
|
|
4
4
|
|
|
5
|
-
export type ConfigToggleKey = "autoRead" | "autoReadAll" | "autoReadAllRequireGit" | "autoReadAllIgnore" | "anchorGrepEnabled" | "copyMoveEnabled" | "replaceMatchEnabled" | "requirePath" | "strictInput" | "diffContextLines" | "disableOnModels" | "readOnDisabledModels" | "undoRetentionDays";
|
|
5
|
+
export type ConfigToggleKey = "autoRead" | "autoReadAll" | "autoReadAllRequireGit" | "autoReadAllIgnore" | "anchorGrepEnabled" | "copyMoveEnabled" | "replaceMatchEnabled" | "outlineEnabled" | "requirePath" | "strictInput" | "diffContextLines" | "disableOnModels" | "readOnDisabledModels" | "undoRetentionDays";
|
|
6
6
|
|
|
7
7
|
export interface ConfigRow {
|
|
8
8
|
key: ConfigToggleKey;
|
|
@@ -36,6 +36,7 @@ export function configRows(config: Config): ConfigRow[] {
|
|
|
36
36
|
{ key: "anchorGrepEnabled", group: "Tools", depth: 0, label: "Anchor grep", hint: "Use the anchored grep in place of pi's own grep", enabled: config.anchorGrepEnabled === true },
|
|
37
37
|
{ key: "copyMoveEnabled", group: "Tools", depth: 0, label: "Copy/move", hint: "Enable the copy and move tools", enabled: config.copyMoveEnabled !== false },
|
|
38
38
|
{ key: "replaceMatchEnabled", group: "Tools", depth: 0, label: "Replace match", hint: "Enable the replace_match tool", enabled: config.replaceMatchEnabled !== false },
|
|
39
|
+
{ key: "outlineEnabled", group: "Tools", depth: 0, label: "Outline tool", hint: "Offer the outline tool for one file's structure on demand", enabled: config.outlineEnabled === true },
|
|
39
40
|
{ key: "requirePath", group: "Edit behavior", depth: 0, label: "Require path", hint: "Edit tools must also send a matching path", enabled: config.requirePath === true },
|
|
40
41
|
{ key: "strictInput", group: "Edit behavior", depth: 0, label: "Strict input", hint: "Reject auto-fixable slips instead of warnings", enabled: config.strictInput === true },
|
|
41
42
|
{ key: "disableOnModels", group: "Model gating", depth: 0, label: "Disable on models", hint: "Model globs that turn off anchored tools (comma-separated)", enabled: (config.disableOnModels ?? []).length > 0, entries: config.disableOnModels ?? [] },
|
|
@@ -101,6 +102,10 @@ export class HashlineConfigOverlay {
|
|
|
101
102
|
this.rows = configRows(await readConfig());
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
rowKeys(): ConfigToggleKey[] {
|
|
106
|
+
return this.rows.map((row) => row.key);
|
|
107
|
+
}
|
|
108
|
+
|
|
104
109
|
private runToggle(row: ConfigRow, delta?: number, value?: string): void {
|
|
105
110
|
this.opts.tui.requestRender(true);
|
|
106
111
|
void this.opts.onToggle(row.key, delta, value).then(async () => {
|
package/src/config.ts
CHANGED
|
@@ -21,6 +21,7 @@ export interface Config {
|
|
|
21
21
|
anchorGrepEnabled: boolean;
|
|
22
22
|
copyMoveEnabled?: boolean;
|
|
23
23
|
replaceMatchEnabled?: boolean;
|
|
24
|
+
outlineEnabled?: boolean;
|
|
24
25
|
autoReadAll?: AutoReadAllMode;
|
|
25
26
|
autoReadAllRequireGit?: boolean;
|
|
26
27
|
autoReadAllIgnore?: string[];
|
|
@@ -37,6 +38,7 @@ const DEFAULT_CONFIG: Config = {
|
|
|
37
38
|
anchorGrepEnabled: true,
|
|
38
39
|
copyMoveEnabled: true,
|
|
39
40
|
replaceMatchEnabled: true,
|
|
41
|
+
outlineEnabled: false,
|
|
40
42
|
autoReadAll: "off",
|
|
41
43
|
autoReadAllRequireGit: true,
|
|
42
44
|
autoReadAllIgnore: [],
|
|
@@ -123,6 +125,7 @@ function parseConfig(content: string): Config {
|
|
|
123
125
|
const anchorGrepEnabled = parsed.anchorGrepEnabled;
|
|
124
126
|
const copyMoveEnabled = parsed.copyMoveEnabled;
|
|
125
127
|
const replaceMatchEnabled = parsed.replaceMatchEnabled;
|
|
128
|
+
const outlineEnabled = parsed.outlineEnabled;
|
|
126
129
|
const autoReadAll = parsed.autoReadAll;
|
|
127
130
|
const autoReadAllRequireGit = parsed.autoReadAllRequireGit;
|
|
128
131
|
const requirePath = parsed.requirePath;
|
|
@@ -138,6 +141,7 @@ function parseConfig(content: string): Config {
|
|
|
138
141
|
anchorGrepEnabled: typeof anchorGrepEnabled === "boolean" ? anchorGrepEnabled : DEFAULT_CONFIG.anchorGrepEnabled,
|
|
139
142
|
copyMoveEnabled: typeof copyMoveEnabled === "boolean" ? copyMoveEnabled : DEFAULT_CONFIG.copyMoveEnabled,
|
|
140
143
|
replaceMatchEnabled: typeof replaceMatchEnabled === "boolean" ? replaceMatchEnabled : DEFAULT_CONFIG.replaceMatchEnabled,
|
|
144
|
+
outlineEnabled: typeof outlineEnabled === "boolean" ? outlineEnabled : DEFAULT_CONFIG.outlineEnabled,
|
|
141
145
|
autoReadAll: parseAutoReadAllMode(autoReadAll),
|
|
142
146
|
autoReadAllRequireGit: parseAutoReadAllRequireGit(autoReadAllRequireGit, autoReadAll),
|
|
143
147
|
requirePath: typeof requirePath === "boolean" ? requirePath : DEFAULT_CONFIG.requirePath,
|
|
@@ -254,7 +258,7 @@ export async function writeConfig(config: Config): Promise<void> {
|
|
|
254
258
|
}
|
|
255
259
|
|
|
256
260
|
|
|
257
|
-
type ToggleKey = "autoRead" | "autoReadAllRequireGit" | "anchorGrepEnabled" | "copyMoveEnabled" | "replaceMatchEnabled" | "requirePath" | "strictInput";
|
|
261
|
+
type ToggleKey = "autoRead" | "autoReadAllRequireGit" | "anchorGrepEnabled" | "copyMoveEnabled" | "replaceMatchEnabled" | "outlineEnabled" | "requirePath" | "strictInput";
|
|
258
262
|
|
|
259
263
|
async function toggleFlag(key: ToggleKey): Promise<boolean> {
|
|
260
264
|
const config = await updateConfig((c) => { c[key] = !(c[key] === true); });
|
|
@@ -265,6 +269,7 @@ export const toggleAutoReadAllRequireGit = (): Promise<boolean> => toggleFlag("a
|
|
|
265
269
|
export const toggleAnchorGrep = (): Promise<boolean> => toggleFlag("anchorGrepEnabled");
|
|
266
270
|
export const toggleCopyMove = (): Promise<boolean> => toggleFlag("copyMoveEnabled");
|
|
267
271
|
export const toggleReplaceMatch = (): Promise<boolean> => toggleFlag("replaceMatchEnabled");
|
|
272
|
+
export const toggleOutline = (): Promise<boolean> => toggleFlag("outlineEnabled");
|
|
268
273
|
function steppedIndex(length: number, index: number, delta: number): number {
|
|
269
274
|
return ((index + delta) % length + length) % length;
|
|
270
275
|
}
|
package/src/edit-common.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface EditToolFlags {
|
|
|
17
17
|
autoReadAllOutline: boolean;
|
|
18
18
|
replaceMatchEnabled: boolean;
|
|
19
19
|
copyMoveEnabled: boolean;
|
|
20
|
+
outlineEnabled: boolean;
|
|
20
21
|
codemode: boolean;
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -28,9 +29,19 @@ export const DEFAULT_EDIT_FLAGS: EditToolFlags = {
|
|
|
28
29
|
autoReadAllOutline: false,
|
|
29
30
|
replaceMatchEnabled: true,
|
|
30
31
|
copyMoveEnabled: true,
|
|
32
|
+
outlineEnabled: false,
|
|
31
33
|
codemode: false,
|
|
32
34
|
};
|
|
33
35
|
|
|
36
|
+
export type EditToolGate = Pick<EditToolFlags, "replaceMatchEnabled" | "copyMoveEnabled" | "outlineEnabled">;
|
|
37
|
+
|
|
38
|
+
export function editToolEnabled(tool: string, flags: EditToolGate): boolean {
|
|
39
|
+
if (tool === "outline") return flags.outlineEnabled;
|
|
40
|
+
if (tool === "replace_match") return flags.replaceMatchEnabled;
|
|
41
|
+
if (tool === "copy" || tool === "move") return flags.copyMoveEnabled;
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
export async function currentEditFlags(codemode = false): Promise<EditToolFlags> {
|
|
35
46
|
const config = await readConfig();
|
|
36
47
|
return {
|
|
@@ -41,19 +52,20 @@ export async function currentEditFlags(codemode = false): Promise<EditToolFlags>
|
|
|
41
52
|
autoReadAllOutline: (config.autoReadAll ?? "off") === "outline",
|
|
42
53
|
replaceMatchEnabled: config.replaceMatchEnabled !== false,
|
|
43
54
|
copyMoveEnabled: config.copyMoveEnabled !== false,
|
|
55
|
+
outlineEnabled: config.outlineEnabled === true,
|
|
44
56
|
codemode
|
|
45
57
|
};
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
function preferenceGuideline(flags: EditToolFlags): string {
|
|
49
|
-
const tools = gatedEditOps(["read", "replace", "replace_match", "insert", "copy", "move", "undo_last_change"], flags);
|
|
61
|
+
const tools = gatedEditOps(["read", "outline", "replace", "replace_match", "insert", "copy", "move", "undo_last_change"], flags);
|
|
50
62
|
return `Remember to use the hashline tools for every file read and edit: ${joinOps(tools, { backtick: true })}.`;
|
|
51
63
|
}
|
|
52
64
|
|
|
53
65
|
const SHARED_EDIT_OPS = ["replace", "replace_match", "insert", "copy", "move"];
|
|
54
66
|
const SHARED_PAYLOAD_OPS = ["replace", "replace_match", "insert"];
|
|
55
67
|
const SHARED_DIFF_OPS = ["replace", "replace_match", "insert", "copy", "move", "undo_last_change"];
|
|
56
|
-
const SHARED_SCRIPT_OPS = ["read", "replace", "replace_match", "insert", "copy", "move", "grep", "undo_last_change"];
|
|
68
|
+
const SHARED_SCRIPT_OPS = ["read", "outline", "replace", "replace_match", "insert", "copy", "move", "grep", "undo_last_change"];
|
|
57
69
|
|
|
58
70
|
function resultContractGuideline(flags: EditToolFlags): string {
|
|
59
71
|
const tools = operationNames(SHARED_SCRIPT_OPS, flags);
|
|
@@ -161,11 +173,7 @@ export function withReplaceMatchPrompts(base: { description: string; snippet: st
|
|
|
161
173
|
}
|
|
162
174
|
|
|
163
175
|
function gatedEditOps(ops: string[], flags: EditToolFlags): string[] {
|
|
164
|
-
return ops.filter((op) =>
|
|
165
|
-
if (op === "replace_match") return flags.replaceMatchEnabled;
|
|
166
|
-
if (op === "copy" || op === "move") return flags.copyMoveEnabled;
|
|
167
|
-
return true;
|
|
168
|
-
});
|
|
176
|
+
return ops.filter((op) => editToolEnabled(op, flags));
|
|
169
177
|
}
|
|
170
178
|
|
|
171
179
|
function joinOps(ops: string[], options?: { backtick?: boolean; separator?: "/" }): string {
|
package/src/model-gate.ts
CHANGED