aura-basetools 0.13.2 → 0.14.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 +49 -55
- package/bin/aura-mcp.mjs +27 -41
- package/bin/aura.mjs +9 -33
- package/lib/ops.mjs +14 -35
- package/lib/transaction-store.mjs +3 -82
- package/locator-lab/args.mjs +3 -3
- package/locator-lab/locator-api.mjs +10 -172
- package/locator-lab/write-path.mjs +14 -73
- package/package.json +1 -1
- package/test/standalone-test.mjs +6 -64
- package/test/write-test.mjs +3 -18
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# aura-basetools 0.
|
|
1
|
+
# aura-basetools 0.14.0
|
|
2
2
|
|
|
3
|
-
Byte-exact file tools for coding agents: read by intent, edit by
|
|
3
|
+
Byte-exact file tools for coding agents: read by intent, edit by address, and write whole files without corrupting bytes. One engine shared by a standalone CLI and an MCP server.
|
|
4
4
|
|
|
5
|
-
面向 coding agent
|
|
5
|
+
面向 coding agent 的逐字节文件工具:按意图读取、按地址编辑、整文件写入;CLI 与 MCP 共用同一套引擎。
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -21,7 +21,7 @@ The engine is intentionally not a general file manager: it reads and edits UTF-8
|
|
|
21
21
|
### Install
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npm i -g aura-basetools@0.
|
|
24
|
+
npm i -g aura-basetools@0.14.0
|
|
25
25
|
aura --version
|
|
26
26
|
```
|
|
27
27
|
|
|
@@ -31,8 +31,6 @@ aura --version
|
|
|
31
31
|
aura basetools read --file src/app.ts --mode map
|
|
32
32
|
aura basetools edit --file src/app.ts --hunks '[{"target":"main","replace":"..."}]'
|
|
33
33
|
aura basetools write --file generated.ts --content @body.ts
|
|
34
|
-
aura basetools undo
|
|
35
|
-
aura basetools transactions
|
|
36
34
|
aura basetools mcp
|
|
37
35
|
```
|
|
38
36
|
|
|
@@ -77,36 +75,35 @@ The server exposes:
|
|
|
77
75
|
| Tool | Purpose |
|
|
78
76
|
|---|---|
|
|
79
77
|
| `aura_basetools_read` | Intent-based views: `map`, `digest`, `full`, `around`, `lines`, `search`, `changed`, `auto` |
|
|
80
|
-
| `aura_basetools_edit` | Anchored multi-hunk edits; `old` + `replace_all
|
|
78
|
+
| `aura_basetools_edit` | Anchored multi-hunk edits; `old` + `replace_all` |
|
|
81
79
|
| `aura_basetools_write` | Whole-file create/overwrite with read-before-write and read-back verification |
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
|
|
81
|
+
0.14.0 removed the public `undo` / `transactions` tools (0 uses in 642 sessions); failure rollback remains internal and automatic.
|
|
84
82
|
|
|
85
83
|
The older tool names (`aura_read`, `aura_edit`, `aura_write`, ...) are no longer exposed by MCP or DSH.
|
|
86
84
|
|
|
87
|
-
###
|
|
85
|
+
### Why aura-basetools
|
|
88
86
|
|
|
89
|
-
|
|
87
|
+
Compared with the DSH built-in `dsh-tool-fs` tools, the differences are deliberate:
|
|
90
88
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
89
|
+
- **Byte fidelity, not text normalization.** Leading BOM and per-line endings are preserved; only the addressed bytes change. The byte-fidelity suite asserts this with 102 checks.
|
|
90
|
+
- **Read by intent, not whole file.** `map` / `digest` / `lines` / `search` return the structural slice you ask for; the customer comparison measured about 1/24 of the context for the same file.
|
|
91
|
+
- **Edits refuse stale bases.** A region-level gate rejects an edit when the addressed region changed after the read that authorized it, and points at the lines to re-read; `expect` makes the intended text explicit.
|
|
92
|
+
- **Writes are verified, not assumed.** Every write goes through a temp file, flush, rename, and a byte-for-byte read-back; multi-target edits plan first and write nothing if any target fails.
|
|
93
|
+
- **Addressing is structural.** `target` / `lines` / `old` / `expect` / handles replace "copy the literal text exactly"; failure receipts include ready-to-use retry addresses.
|
|
94
|
+
- **The core is DSH-free.** `aura-basetools` has no runtime dependencies and no DSH imports; the DSH adapter is a separate optional package.
|
|
95
|
+
|
|
96
|
+
Scope note: the byte-level comparison is against the DSH built-in tools. Other clients may already preserve BOM/CRLF (for example ZCode's built-in `Edit`); do not generalize this comparison to every editor.
|
|
97
|
+
|
|
98
|
+
### Verification
|
|
99
99
|
|
|
100
|
-
The
|
|
100
|
+
The release gate is the source of truth:
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
- A region-level stale gate and an optional strict read-before-write policy reject edits whose basis changed.
|
|
106
|
-
- Write transactions record original bytes, support rollback/undo, and reject undo when a file changed after the transaction.
|
|
107
|
-
- Multi-target writes plan first; if planning fails, no file is written.
|
|
102
|
+
```bash
|
|
103
|
+
node build/release-check.mjs
|
|
104
|
+
```
|
|
108
105
|
|
|
109
|
-
|
|
106
|
+
It runs the full suite set, range fixtures, packaged tests, split-package install, and consistency checks. Exact counts and pass/fail are recorded in the GitHub release notes; they are not copied here, so they cannot go stale.
|
|
110
107
|
|
|
111
108
|
### Boundaries
|
|
112
109
|
|
|
@@ -129,7 +126,7 @@ MIT.
|
|
|
129
126
|
`aura-basetools` 是一个 Node.js(>=18)包,运行时零依赖,提供:
|
|
130
127
|
|
|
131
128
|
- **CLI**:`aura basetools <verb>`
|
|
132
|
-
- **MCP 服务**:stdio
|
|
129
|
+
- **MCP 服务**:stdio 上的三个工具
|
|
133
130
|
- **不依赖 DSH**:DeepSeek Harness 适配器是独立的可选包 `aura-basetools-dsh`
|
|
134
131
|
|
|
135
132
|
它不是通用文件管理器:只处理 UTF-8 文本,保留字节级格式;如果被寻址区域在“授权读取”之后被改动,编辑会被拒绝。
|
|
@@ -137,7 +134,7 @@ MIT.
|
|
|
137
134
|
### 安装
|
|
138
135
|
|
|
139
136
|
```bash
|
|
140
|
-
npm i -g aura-basetools@0.
|
|
137
|
+
npm i -g aura-basetools@0.14.0
|
|
141
138
|
aura --version
|
|
142
139
|
```
|
|
143
140
|
|
|
@@ -147,8 +144,6 @@ aura --version
|
|
|
147
144
|
aura basetools read --file src/app.ts --mode map
|
|
148
145
|
aura basetools edit --file src/app.ts --hunks '[{"target":"main","replace":"..."}]'
|
|
149
146
|
aura basetools write --file generated.ts --content @body.ts
|
|
150
|
-
aura basetools undo
|
|
151
|
-
aura basetools transactions
|
|
152
147
|
aura basetools mcp
|
|
153
148
|
```
|
|
154
149
|
|
|
@@ -193,36 +188,35 @@ npx -y --package aura-basetools aura basetools mcp
|
|
|
193
188
|
| 工具 | 作用 |
|
|
194
189
|
|---|---|
|
|
195
190
|
| `aura_basetools_read` | `map` / `digest` / `full` / `around` / `lines` / `search` / `changed` / `auto` |
|
|
196
|
-
| `aura_basetools_edit` | 多 hunk 锚点编辑;`old` + `replace_all
|
|
191
|
+
| `aura_basetools_edit` | 多 hunk 锚点编辑;`old` + `replace_all` |
|
|
197
192
|
| `aura_basetools_write` | 整文件新建/覆写;覆写默认要求先读,写后回读校验 |
|
|
198
|
-
|
|
199
|
-
|
|
193
|
+
|
|
194
|
+
0.14.0 移除了公开 `undo` / `transactions`(642 个会话中 0 次使用);失败回滚仍是内部自动行为。
|
|
200
195
|
|
|
201
196
|
旧工具名(`aura_read`、`aura_edit`、`aura_write` 等)不再由 MCP/DSH 暴露。
|
|
202
197
|
|
|
203
|
-
###
|
|
198
|
+
### 为什么是 aura-basetools
|
|
204
199
|
|
|
205
|
-
|
|
200
|
+
与 DSH 内置 `dsh-tool-fs` 相比,差异是刻意设计的:
|
|
206
201
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
Linux CI 已配置,但本机 0.13.0 发布闸是 Windows 实跑;不能据此声称 Linux 已通过。
|
|
202
|
+
- **字节保真,不做文本归一化。** 保留前导 BOM 与逐行行尾,只改被寻址的字节;字节保真套件用 102 条断言守着。
|
|
203
|
+
- **按意图读,不读整份。** `map` / `digest` / `lines` / `search` 只返回你要的结构切片;客户版对照实测同一文件上下文约 1/24。
|
|
204
|
+
- **依据过期就拒绝。** 区域级陈旧闸门在“授权读取之后该区域被改过”时拒绝,并指出重读哪几行;`expect` 让目标原文显式化。
|
|
205
|
+
- **写完要验,不靠相信。** 每次写盘走临时文件、flush、rename,再做逐字节回读;多目标先全量规划,任一失败则一个字节都不写。
|
|
206
|
+
- **寻址是结构化的。** `target` / `lines` / `old` / `expect` / 句柄替代“把原文一字不差抄对”;失败回执带可直接用的重试地址。
|
|
207
|
+
- **核心不依赖 DSH。** `aura-basetools` 零运行时依赖、无 DSH import;DSH 适配器是独立可选包。
|
|
208
|
+
|
|
209
|
+
范围说明:字节级对照的对象是 DSH 内置工具;其他客户端可能本来就保留 BOM/CRLF(例如 ZCode 内置 `Edit`),不要把该对照泛化到所有编辑器。
|
|
210
|
+
|
|
211
|
+
### 验证
|
|
212
|
+
|
|
213
|
+
发布闸是唯一事实源:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
node build/release-check.mjs
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
它跑完整套件、三套靶场、包内验收、拆包安装与一致性检查。本次发布的精确读数与通过/失败记录在 GitHub Release notes 里,本文不手抄,避免过期。
|
|
226
220
|
|
|
227
221
|
### 边界
|
|
228
222
|
|
package/bin/aura-mcp.mjs
CHANGED
|
@@ -5,12 +5,10 @@
|
|
|
5
5
|
* 为什么是它:Codex / Claude Code / Cursor / Zed 这些 agent 都吃 MCP —— 一次写壳,到处能用,
|
|
6
6
|
* 且**不要求对方装 DSH**(本文件只依赖 node:fs 与本包的引擎)。
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* 暴露三个工具:
|
|
9
9
|
* aura_basetools_read 七种取景(map/digest/full/around/lines/search/changed)+ 版本令牌
|
|
10
10
|
* aura_basetools_edit 四层寻址 + expect + dry_run;逐字节写回 + 陈旧区域闸门 + 落盘后回读校验
|
|
11
|
-
* aura_basetools_write 整份写入(对应内置 Write
|
|
12
|
-
* aura_basetools_undo 撤销最近一次/指定写事务(默认拒绝覆盖后续编辑)
|
|
13
|
-
* aura_basetools_transactions 列出当前工作区写事务
|
|
11
|
+
* aura_basetools_write 整份写入(对应内置 Write):新建或整文件替换,同走原子写与回读校验
|
|
14
12
|
*
|
|
15
13
|
* 接法(写进各家的 MCP 配置即可):
|
|
16
14
|
* codex: codex mcp add aura -- npx -y --package aura-basetools aura basetools mcp
|
|
@@ -21,9 +19,9 @@
|
|
|
21
19
|
* `aura-basetools`;npx 在"多个 bin 且没有与包名同名的"时候会拒绝猜,直接报
|
|
22
20
|
* `could not determine executable to run`(实测)。`--package` 明确指定包,`aura` 才是命令。
|
|
23
21
|
*/
|
|
24
|
-
import { readFileSync } from 'node:fs'
|
|
22
|
+
import { readFileSync, realpathSync } from 'node:fs'
|
|
25
23
|
import process from 'node:process'
|
|
26
|
-
import { pathToFileURL } from 'node:url'
|
|
24
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
27
25
|
import {
|
|
28
26
|
openProvider,
|
|
29
27
|
readEnvelope,
|
|
@@ -32,8 +30,6 @@ import {
|
|
|
32
30
|
// 多目标(fs-contract-v2)的引擎绑定:与单目标同一处(`lib/ops.mjs`),bin 里不另找引擎。
|
|
33
31
|
readManyWith,
|
|
34
32
|
editManyEnvelope,
|
|
35
|
-
undoEnvelope,
|
|
36
|
-
transactionsEnvelope,
|
|
37
33
|
assertReadTargets,
|
|
38
34
|
assertEditTargets,
|
|
39
35
|
assertTargetShape,
|
|
@@ -81,11 +77,6 @@ const HUNK_SCHEMA = {
|
|
|
81
77
|
type: 'object',
|
|
82
78
|
properties: {
|
|
83
79
|
target: { type: 'string', description: '符号名,如 `toTodoList` 或 `function:apply`;跳过全部文本匹配' },
|
|
84
|
-
intent: {
|
|
85
|
-
type: 'string',
|
|
86
|
-
description:
|
|
87
|
-
'模糊意图地址:符号名片段、短语或近似原文(如 `parseConfig`、`config loader`、`// retry the upload`),由引擎确定性地落成一个精确区间;歧义时报候选并拒绝(不猜)。单独使用(不与 target/lines/line_cue/anchor/old/context/handle 同用);可用 near 与 expect',
|
|
88
|
-
},
|
|
89
80
|
lines: {
|
|
90
81
|
oneOf: [
|
|
91
82
|
{ type: 'array' },
|
|
@@ -137,7 +128,7 @@ const HUNK_SCHEMA = {
|
|
|
137
128
|
replace_all: {
|
|
138
129
|
type: 'boolean',
|
|
139
130
|
description:
|
|
140
|
-
'与 old 同用:替换全部非重叠出现(从左到右),不再要求唯一。不能与 target/
|
|
131
|
+
'与 old 同用:替换全部非重叠出现(从左到右),不再要求唯一。不能与 target/lines/line_cue/anchor/context/handle/near 同用;读取后出现次数变了会整体拒绝。',
|
|
141
132
|
},
|
|
142
133
|
eol: {
|
|
143
134
|
type: 'string',
|
|
@@ -198,14 +189,13 @@ const TOOLS = [
|
|
|
198
189
|
{
|
|
199
190
|
name: 'aura_basetools_edit',
|
|
200
191
|
description:
|
|
201
|
-
'Edit or create a UTF-8 text file with hunks in one call. Address each hunk by
|
|
192
|
+
'Edit or create a UTF-8 text file with hunks in one call. Address each hunk by target (symbol), lines, line_cue, anchor, or old (verbatim content + optional context). With `old`, set `replace_all: true` to replace every non-overlapping occurrence. Writes back byte-for-byte (BOM and per-line endings preserved), refuses a hunk whose addressed region changed since your last read, and reads the file back to verify. All hunks must locate or nothing is written.',
|
|
202
193
|
inputSchema: {
|
|
203
194
|
type: 'object',
|
|
204
195
|
properties: {
|
|
205
196
|
file_path: { type: 'string' },
|
|
206
197
|
hunks: { type: 'array', items: HUNK_SCHEMA },
|
|
207
198
|
dry_run: { type: 'boolean', description: '只定位不落盘' },
|
|
208
|
-
include_transaction: { type: 'boolean', description: '在回执里返回 transaction id(默认不返回,保持输出稳定)' },
|
|
209
199
|
require_observation: {
|
|
210
200
|
type: 'boolean',
|
|
211
201
|
description: '严格新鲜度:要求这一版文件在本会话里被读过(或由本工具写过);否则 PATCH_UNOBSERVED。create 不受影响;dry_run 同样执行这条判决。',
|
|
@@ -230,7 +220,7 @@ const TOOLS = [
|
|
|
230
220
|
{
|
|
231
221
|
name: 'aura_basetools_write',
|
|
232
222
|
description:
|
|
233
|
-
'Write a whole UTF-8 text file at once (create or overwrite), matching a plain Write tool. Preserves an existing file\'s BOM and line endings, goes through the same
|
|
223
|
+
'Write a whole UTF-8 text file at once (create or overwrite), matching a plain Write tool. Preserves an existing file\'s BOM and line endings, goes through the same atomic write path as aura_basetools_edit, and reads the file back to verify bytes. Prefer aura_basetools_edit for partial changes.',
|
|
234
224
|
inputSchema: {
|
|
235
225
|
type: 'object',
|
|
236
226
|
properties: {
|
|
@@ -247,28 +237,10 @@ const TOOLS = [
|
|
|
247
237
|
enum: ['auto', 'preserve', 'lf', 'crlf'],
|
|
248
238
|
description: '覆写时的换行风格:auto(默认)适配文件已有风格;preserve 按 content 原样写;lf/crlf 强制。新建文件始终按 content 原样写(非 auto 会被拒绝)。',
|
|
249
239
|
},
|
|
250
|
-
include_transaction: { type: 'boolean', description: '在回执里返回 transaction id(默认不返回)' },
|
|
251
240
|
},
|
|
252
241
|
required: ['file_path', 'content'],
|
|
253
242
|
},
|
|
254
243
|
},
|
|
255
|
-
{
|
|
256
|
-
name: 'aura_basetools_undo',
|
|
257
|
-
description:
|
|
258
|
-
'Undo the latest committed write transaction, or the transaction with the given id. Refuses if a file changed after the transaction; pass force=true to override. Restores the exact bytes captured before the write.',
|
|
259
|
-
inputSchema: {
|
|
260
|
-
type: 'object',
|
|
261
|
-
properties: {
|
|
262
|
-
id: { type: 'string', description: '事务 id;省略则撤销最近一条 committed 事务' },
|
|
263
|
-
force: { type: 'boolean', description: '即使文件在事务后又被改过也强制恢复' },
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
name: 'aura_basetools_transactions',
|
|
269
|
-
description: 'List write transactions for the current workspace (id, status, file count, time).',
|
|
270
|
-
inputSchema: { type: 'object', properties: {} },
|
|
271
|
-
},
|
|
272
244
|
]
|
|
273
245
|
|
|
274
246
|
/**
|
|
@@ -351,7 +323,6 @@ async function editTargetsCall(provider, args) {
|
|
|
351
323
|
return await editManyEnvelope(provider, {
|
|
352
324
|
targets,
|
|
353
325
|
dryRun: args.dry_run === true,
|
|
354
|
-
includeTransaction: args.include_transaction === true,
|
|
355
326
|
...(args.require_observation === true ? { requireObservation: true } : {}),
|
|
356
327
|
})
|
|
357
328
|
}
|
|
@@ -436,7 +407,6 @@ async function callTool(provider, name, args = {}) {
|
|
|
436
407
|
hunks: Array.isArray(args.hunks) ? args.hunks : [],
|
|
437
408
|
dryRun: args.dry_run === true,
|
|
438
409
|
create: creating ? args.create : undefined,
|
|
439
|
-
includeTransaction: args.include_transaction === true,
|
|
440
410
|
...(args.require_observation === true ? { requireObservation: true } : {}),
|
|
441
411
|
})
|
|
442
412
|
}
|
|
@@ -446,13 +416,10 @@ async function callTool(provider, name, args = {}) {
|
|
|
446
416
|
content: args.content,
|
|
447
417
|
dryRun: args.dry_run === true,
|
|
448
418
|
noOverwrite: args.no_overwrite === true,
|
|
449
|
-
includeTransaction: args.include_transaction === true,
|
|
450
419
|
allowUnobserved: args.allow_unobserved === true,
|
|
451
420
|
...(typeof args.eol === 'string' ? { eol: args.eol } : {}),
|
|
452
421
|
})
|
|
453
422
|
}
|
|
454
|
-
if (name === 'aura_basetools_undo') return await undoEnvelope(provider, { id: args.id, force: args.force === true })
|
|
455
|
-
if (name === 'aura_basetools_transactions') return transactionsEnvelope(provider)
|
|
456
423
|
return { ok: false, error: `unknown tool ${JSON.stringify(name)}`, code: 'UNKNOWN_TOOL' }
|
|
457
424
|
}
|
|
458
425
|
|
|
@@ -561,6 +528,25 @@ export async function serve() {
|
|
|
561
528
|
|
|
562
529
|
// 直接运行本文件(`aura-mcp`,package.json 里声明的第二个 bin)也要起服务,与 `aura basetools mcp` 等价。
|
|
563
530
|
// 用 pathToFileURL 比手拼 file:// 靠得住(Windows 盘符就是那种手拼会踩的地方)。
|
|
564
|
-
|
|
531
|
+
/**
|
|
532
|
+
* 直接执行本文件才起 stdio 服务。
|
|
533
|
+
*
|
|
534
|
+
* 不能只比 `import.meta.url === pathToFileURL(argv[1])`:macOS 的临时目录
|
|
535
|
+
* `/var/folders/...` 是 `/private/var/folders/...` 的符号链接,Node 给模块的 URL 与
|
|
536
|
+
* argv 的写法可能一侧已 realpath、另一侧没有 ⇒ 服务静默退出,客户端看到 0 个工具
|
|
537
|
+
* (post-publish / split-package 在 macOS 上正是这条)。两边都过 realpath 再比。
|
|
538
|
+
*/
|
|
539
|
+
function isDirectRun() {
|
|
540
|
+
const invoked = process.argv[1]
|
|
541
|
+
if (invoked === undefined) return false
|
|
542
|
+
if (import.meta.url === pathToFileURL(invoked).href) return true
|
|
543
|
+
try {
|
|
544
|
+
return realpathSync.native(fileURLToPath(import.meta.url)) === realpathSync.native(invoked)
|
|
545
|
+
} catch {
|
|
546
|
+
return false
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (isDirectRun()) {
|
|
565
551
|
await serve()
|
|
566
552
|
}
|
package/bin/aura.mjs
CHANGED
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
* aura basetools edit --file F (--hunks '<json>' | --hunks-file P) [--dry-run] [--create <文本|@文件>]
|
|
7
7
|
* aura basetools read --targets '[...]|@spec.json' 一次读多个目标(与 --file 互斥)
|
|
8
8
|
* aura basetools edit --targets '[...]|@spec.json' 一次改多个文件(与 --file 互斥;任一规划失败 ⇒ 零写入)
|
|
9
|
-
* aura basetools undo [--id <事务 id>] [--force] 撤销最近一次/指定写事务
|
|
10
|
-
* aura basetools transactions 列出当前工作区写事务
|
|
11
9
|
* aura basetools mcp
|
|
12
10
|
* aura --help | --version
|
|
13
11
|
*
|
|
@@ -27,8 +25,6 @@ import {
|
|
|
27
25
|
// 多目标(fs-contract-v2)的引擎绑定:与单目标同一处(`lib/ops.mjs`),bin 里不另找引擎。
|
|
28
26
|
readManyWith,
|
|
29
27
|
editManyEnvelope,
|
|
30
|
-
undoEnvelope,
|
|
31
|
-
transactionsEnvelope,
|
|
32
28
|
capabilitiesEnvelope,
|
|
33
29
|
writeEnvelope,
|
|
34
30
|
assertReadTargets,
|
|
@@ -74,7 +70,7 @@ let command = argv[0]
|
|
|
74
70
|
* 有了这份名单,“下一个是已知布尔旗标”才当没带值,**其它以 `--` 开头的东西都当值**。
|
|
75
71
|
* 代价是名单要维护;收益是那个静默丢值的路径从根上消失,而不是靠叫用户改写法。
|
|
76
72
|
*/
|
|
77
|
-
const BOOLEAN_FLAGS = new Set(['pretty', 'dry-run', 'plan', '
|
|
73
|
+
const BOOLEAN_FLAGS = new Set(['pretty', 'dry-run', 'plan', 'no-overwrite', 'replace-all', 'require-observation', 'allow-unobserved', 'json'])
|
|
78
74
|
const isBooleanFlag = (token) => typeof token === 'string' && BOOLEAN_FLAGS.has(token.replace(/^--/, ''))
|
|
79
75
|
|
|
80
76
|
/**
|
|
@@ -90,15 +86,13 @@ const COMMAND_FLAGS = {
|
|
|
90
86
|
bool: ['pretty'],
|
|
91
87
|
},
|
|
92
88
|
edit: {
|
|
93
|
-
value: ['cwd', 'file', 'targets', 'hunks', 'hunks-file', 'create', 'target', '
|
|
94
|
-
bool: ['pretty', 'dry-run', 'plan', '
|
|
89
|
+
value: ['cwd', 'file', 'targets', 'hunks', 'hunks-file', 'create', 'target', 'lines', 'start-line', 'end-line', 'line-cue', 'anchor', 'old', 'context', 'handle', 'line', 'position', 'replace', 'expect', 'near', 'eol'],
|
|
90
|
+
bool: ['pretty', 'dry-run', 'plan', 'replace-all', 'require-observation'],
|
|
95
91
|
},
|
|
96
92
|
write: {
|
|
97
93
|
value: ['cwd', 'file', 'content', 'eol'],
|
|
98
|
-
bool: ['pretty', 'dry-run', 'plan', '
|
|
94
|
+
bool: ['pretty', 'dry-run', 'plan', 'no-overwrite', 'allow-unobserved'],
|
|
99
95
|
},
|
|
100
|
-
undo: { value: ['cwd', 'id'], bool: ['pretty', 'force'] },
|
|
101
|
-
transactions: { value: ['cwd'], bool: ['pretty'] },
|
|
102
96
|
doctor: { value: ['cwd'], bool: ['pretty', 'json'] },
|
|
103
97
|
capabilities: { value: ['cwd'], bool: ['pretty'] },
|
|
104
98
|
mcp: { value: ['cwd'], bool: ['pretty'] },
|
|
@@ -177,20 +171,20 @@ const USAGE = [
|
|
|
177
171
|
" aura basetools edit --file <路径> (--hunks '<json>' | --hunks-file <路径>) [--dry-run|--plan] [--eol auto|preserve|lf|crlf] [--require-observation]",
|
|
178
172
|
' aura basetools edit --file <路径> --lines <a,b> --replace <文本> [--expect <文本>] [--near <n>]',
|
|
179
173
|
' aura basetools edit --file <路径> --old <原文> --replace <文本> --replace-all # 改全部非重叠出现',
|
|
180
|
-
|
|
174
|
+
" (只改一处时不必包成 JSON:--target / --lines / --start-line+--end-line / --line-cue / --anchor 任选其一)",
|
|
181
175
|
// 句柄(fs-contract-v2 §5):读侧发的柄 + 三种定位写法。下面三行是三种**用法**,不是三种键。
|
|
182
176
|
' aura basetools edit --file <路径> --handle <h…> --replace <文本> # 整段替换:区间由柄定,不靠转述',
|
|
183
177
|
' aura basetools edit --file <路径> --handle <h…> --line <段内行号> --replace <文本> # 只换区间内第 k 行(1-based,从区间首行数起)',
|
|
184
178
|
' aura basetools edit --file <路径> --handle <h…> --position before|after --replace <文本> # 在区间边界插入,区间本身不动',
|
|
185
179
|
" (--line / --position 是从属键:只有与 --handle 同时给才成立,单独给不构成一次编辑)",
|
|
186
180
|
' aura basetools mcp 启动 MCP stdio 服务',
|
|
187
|
-
' aura basetools write --file <路径> --content <文本|@文件> [--no-overwrite] [--dry-run|--plan] [--
|
|
181
|
+
' aura basetools write --file <路径> --content <文本|@文件> [--no-overwrite] [--dry-run|--plan] [--allow-unobserved] [--eol auto|preserve|lf|crlf]',
|
|
188
182
|
' (覆写默认要求先读过这一版;--allow-unobserved 才允许盲写)',
|
|
189
183
|
' aura basetools doctor [--json] 自检:Node/版本/MCP 握手/缓存与锁目录可写',
|
|
190
184
|
' aura basetools capabilities [--pretty] 能力清单:读模式/地址档/语言/限制(JSON)',
|
|
191
185
|
' aura --help | --version',
|
|
192
186
|
'',
|
|
193
|
-
|
|
187
|
+
'hunks 形状:[{"target":"符号名"|"lines":[a,b]|"line_cue":"唯一词"|"anchor":"记得的原文",',
|
|
194
188
|
' "replace":"替换文本","expect":"该处应是什么","near":行号}]',
|
|
195
189
|
'',
|
|
196
190
|
'例:',
|
|
@@ -311,7 +305,7 @@ if (command === '--version' || command === '-v') emit({ ok: true, version: VERSI
|
|
|
311
305
|
* 旧顶层动词只给**迁移提示**,不再作为别名执行——保留别名等于保留未来的撞名。
|
|
312
306
|
* 只提示、不执行:`aura basetools read` 不会偷偷改文件。
|
|
313
307
|
*/
|
|
314
|
-
const MOVED_COMMANDS = new Set(['read', 'outline', 'edit', 'write', '
|
|
308
|
+
const MOVED_COMMANDS = new Set(['read', 'outline', 'edit', 'write', 'mcp', 'doctor', 'capabilities'])
|
|
315
309
|
if (!namespaced && MOVED_COMMANDS.has(command)) {
|
|
316
310
|
emit(
|
|
317
311
|
{
|
|
@@ -356,26 +350,12 @@ if (command === 'write') {
|
|
|
356
350
|
content,
|
|
357
351
|
dryRun: has('dry-run') || has('plan'),
|
|
358
352
|
noOverwrite: has('no-overwrite'),
|
|
359
|
-
includeTransaction: has('transaction'),
|
|
360
353
|
allowUnobserved: has('allow-unobserved'),
|
|
361
354
|
...(typeof eol === 'string' ? { eol } : {}),
|
|
362
355
|
})
|
|
363
356
|
emit(env, env.ok ? 0 : 2)
|
|
364
357
|
}
|
|
365
358
|
|
|
366
|
-
if (command === 'undo') {
|
|
367
|
-
const env = await undoEnvelope(provider, {
|
|
368
|
-
id: typeof flag('id') === 'string' ? flag('id') : undefined,
|
|
369
|
-
force: has('force'),
|
|
370
|
-
})
|
|
371
|
-
emit(env, env.ok ? 0 : 2)
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
if (command === 'transactions') {
|
|
375
|
-
const env = transactionsEnvelope(provider)
|
|
376
|
-
emit(env, 0)
|
|
377
|
-
}
|
|
378
|
-
|
|
379
359
|
if (command === 'doctor') {
|
|
380
360
|
/**
|
|
381
361
|
* `aura basetools doctor`:把“装完能不能用”的机械检查放在 CLI 自己身上。
|
|
@@ -492,7 +472,6 @@ if (command === 'edit') {
|
|
|
492
472
|
const env = await editManyEnvelope(provider, {
|
|
493
473
|
targets: spec.targets,
|
|
494
474
|
dryRun: has('dry-run') || has('plan') || spec.dryRun,
|
|
495
|
-
includeTransaction: has('transaction'),
|
|
496
475
|
...(has('require-observation') ? { requireObservation: true } : {}),
|
|
497
476
|
})
|
|
498
477
|
emit(env, env.ok ? 0 : 2)
|
|
@@ -509,8 +488,6 @@ if (command === 'edit') {
|
|
|
509
488
|
*/
|
|
510
489
|
const flat = flatHunk({
|
|
511
490
|
target: flag('target'),
|
|
512
|
-
// 意图地址(T16):`--intent 'config loader'` 一句话定位一处改动,不要求符号全名或原文。
|
|
513
|
-
intent: flag('intent'),
|
|
514
491
|
lines: flag('lines'),
|
|
515
492
|
start_line: flag('start-line'),
|
|
516
493
|
end_line: flag('end-line'),
|
|
@@ -593,7 +570,7 @@ if (command === 'edit') {
|
|
|
593
570
|
{
|
|
594
571
|
ok: false,
|
|
595
572
|
error:
|
|
596
|
-
|
|
573
|
+
'edit 需要 --hunks/--hunks-file,或平铺一组地址旗标(--target / --lines / --start-line+--end-line / --line-cue / --anchor / --handle(配 --line 或 --position),外加 --replace),或 --create <整份内容>',
|
|
597
574
|
code: 'USAGE',
|
|
598
575
|
},
|
|
599
576
|
1,
|
|
@@ -607,7 +584,6 @@ if (command === 'edit') {
|
|
|
607
584
|
hunks: hunks ?? [],
|
|
608
585
|
dryRun: has('dry-run') || has('plan'),
|
|
609
586
|
create,
|
|
610
|
-
includeTransaction: has('transaction'),
|
|
611
587
|
...(has('require-observation') ? { requireObservation: true } : {}),
|
|
612
588
|
})
|
|
613
589
|
emit(env, env.ok ? 0 : 2)
|
package/lib/ops.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import fs from 'node:fs'
|
|
|
13
13
|
import { fileURLToPath } from 'node:url'
|
|
14
14
|
import { createNodeFsProvider, DEFAULT_MAX_TEXT_BYTES } from './fs-provider.mjs'
|
|
15
15
|
import { withWorkspaceLock } from './workspace-lock.mjs'
|
|
16
|
-
import { createTransaction
|
|
16
|
+
import { createTransaction } from './transaction-store.mjs'
|
|
17
17
|
import { createDiskStore } from './observation-store.mjs'
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -93,8 +93,8 @@ export function capabilitiesEnvelope() {
|
|
|
93
93
|
addressKinds: [...new Set([...engineArgs.FLAT_ADDRESS_KEYS, 'old', 'context', 'position'])],
|
|
94
94
|
options: ['replace_all', 'require_observation'],
|
|
95
95
|
writeSafety: {
|
|
96
|
-
cli: ['
|
|
97
|
-
mcp: ['
|
|
96
|
+
cli: ['workspace-lock', 'stale-region-gate', 'byte-verify'],
|
|
97
|
+
mcp: ['workspace-lock', 'stale-region-gate', 'byte-verify'],
|
|
98
98
|
dsh: ['stale-region-gate', 'byte-verify'],
|
|
99
99
|
},
|
|
100
100
|
},
|
|
@@ -103,14 +103,14 @@ export function capabilitiesEnvelope() {
|
|
|
103
103
|
operations: ['create', 'overwrite'],
|
|
104
104
|
options: ['eol'],
|
|
105
105
|
writeSafety: {
|
|
106
|
-
cli: ['
|
|
107
|
-
mcp: ['
|
|
108
|
-
// DSH
|
|
106
|
+
cli: ['workspace-lock', 'stale-region-gate', 'observation-gate', 'byte-verify'],
|
|
107
|
+
mcp: ['workspace-lock', 'stale-region-gate', 'observation-gate', 'byte-verify'],
|
|
108
|
+
// DSH 壳没有工作区锁;它经过引擎的区域闸、严格新鲜度与回读校验。
|
|
109
109
|
dsh: ['stale-region-gate', 'observation-gate', 'byte-verify'],
|
|
110
110
|
},
|
|
111
111
|
},
|
|
112
112
|
],
|
|
113
|
-
commands: ['basetools read', 'basetools edit', 'basetools write', 'basetools
|
|
113
|
+
commands: ['basetools read', 'basetools edit', 'basetools write', 'basetools mcp', 'basetools doctor', 'basetools capabilities', '--version', '--help'],
|
|
114
114
|
langs,
|
|
115
115
|
langsCount: langs.length,
|
|
116
116
|
limits: {
|
|
@@ -489,12 +489,9 @@ export async function editEnvelope(provider, request) {
|
|
|
489
489
|
// 事务需要工作区根与真实文件系统;测试/嵌入式 provider 没有 root 时保持原来的引擎行为。
|
|
490
490
|
if (provider.root === undefined) return editEnvelopeInner(provider, request)
|
|
491
491
|
/**
|
|
492
|
-
*
|
|
493
|
-
* 把 `aura undo`(最近一次 committed)顶掉——用户以为撤销了真实编辑,实际 restored:0。
|
|
494
|
-
* 预演也不写盘,不需要工作区锁。
|
|
492
|
+
* 预演不建事务、不写盘,也不需要工作区锁;0.14.0 起事务日志只用于失败回滚。
|
|
495
493
|
*/
|
|
496
494
|
if (request.dryRun === true) return editEnvelopeInner(provider, request)
|
|
497
|
-
const includeTransaction = request.includeTransaction === true
|
|
498
495
|
try {
|
|
499
496
|
return await withWorkspaceLock(provider.root, async () => {
|
|
500
497
|
const tx = createTransaction(provider.root, 'aura_basetools_edit')
|
|
@@ -508,10 +505,10 @@ export async function editEnvelope(provider, request) {
|
|
|
508
505
|
error.rollback = rollback
|
|
509
506
|
throw error
|
|
510
507
|
}
|
|
511
|
-
return
|
|
508
|
+
return env
|
|
512
509
|
}
|
|
513
|
-
|
|
514
|
-
return
|
|
510
|
+
await tx.rollback(provider)
|
|
511
|
+
return env
|
|
515
512
|
})
|
|
516
513
|
} catch (error) {
|
|
517
514
|
return transactionError(error)
|
|
@@ -523,7 +520,6 @@ export async function editManyEnvelope(provider, request) {
|
|
|
523
520
|
if (provider.root === undefined) return editManyEnvelopeInner(provider, request)
|
|
524
521
|
// 与单目标同一条:预演不建事务、不写盘,不能顶掉真正的最近 committed 事务。
|
|
525
522
|
if (request.dryRun === true) return editManyEnvelopeInner(provider, request)
|
|
526
|
-
const includeTransaction = request.includeTransaction === true
|
|
527
523
|
try {
|
|
528
524
|
return await withWorkspaceLock(provider.root, async () => {
|
|
529
525
|
const tx = createTransaction(provider.root, 'aura_basetools_edit')
|
|
@@ -538,14 +534,13 @@ export async function editManyEnvelope(provider, request) {
|
|
|
538
534
|
error.rollback = rollback
|
|
539
535
|
throw error
|
|
540
536
|
}
|
|
541
|
-
return
|
|
537
|
+
return batch
|
|
542
538
|
}
|
|
543
539
|
const rollback = await tx.rollback(provider)
|
|
544
540
|
const finalBatch = writePath.rebuildAfterRollback(batch, rollback)
|
|
545
541
|
return {
|
|
546
542
|
...finalBatch,
|
|
547
543
|
ok: false,
|
|
548
|
-
...(includeTransaction ? { transaction: tx.summary() } : {}),
|
|
549
544
|
}
|
|
550
545
|
} catch (error) {
|
|
551
546
|
const rollback = await tx.rollback(provider)
|
|
@@ -579,7 +574,6 @@ function compactWriteReceipt(env, content) {
|
|
|
579
574
|
dryRun,
|
|
580
575
|
...(result.write_verify === undefined ? {} : { write_verify: result.write_verify }),
|
|
581
576
|
...(result.unverified_region === true ? { unverified_region: true } : {}),
|
|
582
|
-
...(result.transaction === undefined ? {} : { transaction: result.transaction }),
|
|
583
577
|
},
|
|
584
578
|
}
|
|
585
579
|
}
|
|
@@ -600,7 +594,7 @@ function compactWriteReceipt(env, content) {
|
|
|
600
594
|
* `preserve` 按 content 原样写(与内置 Write 的“给什么写什么”对齐),`lf` / `crlf` 强制统一。
|
|
601
595
|
* 新建文件永远按 content 原样写;显式传非 `auto` 的 eol 会被拒绝(不做静默忽略)。
|
|
602
596
|
*/
|
|
603
|
-
export async function writeEnvelope(provider, { file, content, dryRun = false, noOverwrite = false,
|
|
597
|
+
export async function writeEnvelope(provider, { file, content, dryRun = false, noOverwrite = false, allowUnobserved = false, eol = 'auto' } = {}) {
|
|
604
598
|
if (typeof file !== 'string' || file.length === 0) {
|
|
605
599
|
return { ok: false, error: 'write needs a file path', code: 'WRITE_BAD_ARGS' }
|
|
606
600
|
}
|
|
@@ -627,7 +621,7 @@ export async function writeEnvelope(provider, { file, content, dryRun = false, n
|
|
|
627
621
|
if (dryRun) {
|
|
628
622
|
return { ok: true, result: { path: displayPath, operation: 'create', bytes: Buffer.byteLength(content, 'utf8'), dryRun: true } }
|
|
629
623
|
}
|
|
630
|
-
return compactWriteReceipt(await editEnvelope(provider, { file, create: content
|
|
624
|
+
return compactWriteReceipt(await editEnvelope(provider, { file, create: content }), content)
|
|
631
625
|
}
|
|
632
626
|
return compactWriteReceipt(
|
|
633
627
|
await editEnvelope(provider, {
|
|
@@ -635,7 +629,6 @@ export async function writeEnvelope(provider, { file, content, dryRun = false, n
|
|
|
635
629
|
wholeFile: content,
|
|
636
630
|
eol,
|
|
637
631
|
dryRun,
|
|
638
|
-
includeTransaction,
|
|
639
632
|
requireObservation: allowUnobserved !== true,
|
|
640
633
|
skipStaleGate: allowUnobserved === true,
|
|
641
634
|
toolName: 'aura_basetools_write',
|
|
@@ -646,17 +639,3 @@ export async function writeEnvelope(provider, { file, content, dryRun = false, n
|
|
|
646
639
|
return transactionError(error)
|
|
647
640
|
}
|
|
648
641
|
}
|
|
649
|
-
|
|
650
|
-
export async function undoEnvelope(provider, { id, force } = {}) { try {
|
|
651
|
-
return {
|
|
652
|
-
ok: true,
|
|
653
|
-
...(await withWorkspaceLock(provider.root, () => undoTransaction(provider.root, provider, { id, force: force === true }))),
|
|
654
|
-
}
|
|
655
|
-
} catch (error) {
|
|
656
|
-
return transactionError(error)
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
export function transactionsEnvelope(provider) {
|
|
661
|
-
return { ok: true, transactions: listTransactions(provider.root) }
|
|
662
|
-
}
|