aura-basetools 0.14.0 → 0.16.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 +7 -3
- package/bin/aura-mcp.mjs +11 -0
- package/bin/aura.mjs +49 -8
- package/lib/ops.mjs +238 -6
- package/lib/transaction-store.mjs +63 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# aura-basetools 0.
|
|
1
|
+
# aura-basetools 0.16.0
|
|
2
2
|
|
|
3
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
|
|
|
@@ -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.16.0
|
|
25
25
|
aura --version
|
|
26
26
|
```
|
|
27
27
|
|
|
@@ -90,6 +90,8 @@ Compared with the DSH built-in `dsh-tool-fs` tools, the differences are delibera
|
|
|
90
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
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
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
|
+
- **Changes can prove themselves.** `edit` / `write` accept an explicit `verify` command; a non-zero exit or timeout rolls the transaction back and returns the command output plus the rollback state. No auto-discovery of repo scripts.
|
|
94
|
+
- **Changes can span calls.** `tx begin` opens a transaction; `edit` / `write` with `--tx <id>` append to it; `tx commit` checks files were not changed externally (`TX_STALE`) and can run `verify`; `tx rollback` restores all of them.
|
|
93
95
|
- **Addressing is structural.** `target` / `lines` / `old` / `expect` / handles replace "copy the literal text exactly"; failure receipts include ready-to-use retry addresses.
|
|
94
96
|
- **The core is DSH-free.** `aura-basetools` has no runtime dependencies and no DSH imports; the DSH adapter is a separate optional package.
|
|
95
97
|
|
|
@@ -134,7 +136,7 @@ MIT.
|
|
|
134
136
|
### 安装
|
|
135
137
|
|
|
136
138
|
```bash
|
|
137
|
-
npm i -g aura-basetools@0.
|
|
139
|
+
npm i -g aura-basetools@0.16.0
|
|
138
140
|
aura --version
|
|
139
141
|
```
|
|
140
142
|
|
|
@@ -203,6 +205,8 @@ npx -y --package aura-basetools aura basetools mcp
|
|
|
203
205
|
- **按意图读,不读整份。** `map` / `digest` / `lines` / `search` 只返回你要的结构切片;客户版对照实测同一文件上下文约 1/24。
|
|
204
206
|
- **依据过期就拒绝。** 区域级陈旧闸门在“授权读取之后该区域被改过”时拒绝,并指出重读哪几行;`expect` 让目标原文显式化。
|
|
205
207
|
- **写完要验,不靠相信。** 每次写盘走临时文件、flush、rename,再做逐字节回读;多目标先全量规划,任一失败则一个字节都不写。
|
|
208
|
+
- **变更可以自证。** `edit` / `write` 接受显式 `verify` 命令;非零退出或超时会把整个事务回滚,回执带命令输出与回滚状态。不自动发现仓库脚本。
|
|
209
|
+
- **变更可以跨调用。** `tx begin` 开事务;`edit` / `write --tx <id>` 追加;`tx commit` 先检查文件没被外部改过(`TX_STALE`),可带 `verify`;`tx rollback` 恢复全部。
|
|
206
210
|
- **寻址是结构化的。** `target` / `lines` / `old` / `expect` / 句柄替代“把原文一字不差抄对”;失败回执带可直接用的重试地址。
|
|
207
211
|
- **核心不依赖 DSH。** `aura-basetools` 零运行时依赖、无 DSH import;DSH 适配器是独立可选包。
|
|
208
212
|
|
package/bin/aura-mcp.mjs
CHANGED
|
@@ -200,6 +200,10 @@ const TOOLS = [
|
|
|
200
200
|
type: 'boolean',
|
|
201
201
|
description: '严格新鲜度:要求这一版文件在本会话里被读过(或由本工具写过);否则 PATCH_UNOBSERVED。create 不受影响;dry_run 同样执行这条判决。',
|
|
202
202
|
},
|
|
203
|
+
verify: {
|
|
204
|
+
type: 'string',
|
|
205
|
+
description: '写后验证命令(显式给出才执行;在 workspace 根下、受超时/输出上限约束)。非零退出 ⇒ 自动回滚并返回 PATCH_VERIFY_FAILED。不自动发现/执行仓库脚本。',
|
|
206
|
+
},
|
|
203
207
|
create: { type: 'string', description: '文件不存在时用它创建(整份内容)' },
|
|
204
208
|
targets: TARGETS_SCHEMA,
|
|
205
209
|
},
|
|
@@ -237,6 +241,10 @@ const TOOLS = [
|
|
|
237
241
|
enum: ['auto', 'preserve', 'lf', 'crlf'],
|
|
238
242
|
description: '覆写时的换行风格:auto(默认)适配文件已有风格;preserve 按 content 原样写;lf/crlf 强制。新建文件始终按 content 原样写(非 auto 会被拒绝)。',
|
|
239
243
|
},
|
|
244
|
+
verify: {
|
|
245
|
+
type: 'string',
|
|
246
|
+
description: '写后验证命令(显式给出才执行;非零退出 ⇒ 自动回滚并返回 PATCH_VERIFY_FAILED)。',
|
|
247
|
+
},
|
|
240
248
|
},
|
|
241
249
|
required: ['file_path', 'content'],
|
|
242
250
|
},
|
|
@@ -324,6 +332,7 @@ async function editTargetsCall(provider, args) {
|
|
|
324
332
|
targets,
|
|
325
333
|
dryRun: args.dry_run === true,
|
|
326
334
|
...(args.require_observation === true ? { requireObservation: true } : {}),
|
|
335
|
+
...(typeof args.verify === 'string' ? { verify: args.verify } : {}),
|
|
327
336
|
})
|
|
328
337
|
}
|
|
329
338
|
|
|
@@ -408,6 +417,7 @@ async function callTool(provider, name, args = {}) {
|
|
|
408
417
|
dryRun: args.dry_run === true,
|
|
409
418
|
create: creating ? args.create : undefined,
|
|
410
419
|
...(args.require_observation === true ? { requireObservation: true } : {}),
|
|
420
|
+
...(typeof args.verify === 'string' ? { verify: args.verify } : {}),
|
|
411
421
|
})
|
|
412
422
|
}
|
|
413
423
|
if (name === 'aura_basetools_write') {
|
|
@@ -418,6 +428,7 @@ async function callTool(provider, name, args = {}) {
|
|
|
418
428
|
noOverwrite: args.no_overwrite === true,
|
|
419
429
|
allowUnobserved: args.allow_unobserved === true,
|
|
420
430
|
...(typeof args.eol === 'string' ? { eol: args.eol } : {}),
|
|
431
|
+
...(typeof args.verify === 'string' ? { verify: args.verify } : {}),
|
|
421
432
|
})
|
|
422
433
|
}
|
|
423
434
|
return { ok: false, error: `unknown tool ${JSON.stringify(name)}`, code: 'UNKNOWN_TOOL' }
|
package/bin/aura.mjs
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* aura CLI(薄壳):参数解析 → `lib/ops.mjs` → 一行 JSON。
|
|
4
4
|
*
|
|
5
5
|
* aura basetools read --file F [--mode …] [--around X] [--lines a,b] [--search S] [--since V] [--max-chars N]
|
|
6
|
-
* aura basetools edit --file F (--hunks '<json>' | --hunks-file P) [--dry-run] [--create <文本|@文件>]
|
|
6
|
+
* aura basetools edit --file F (--hunks '<json>' | --hunks-file P) [--dry-run] [--verify "命令"] [--create <文本|@文件>]
|
|
7
7
|
* aura basetools read --targets '[...]|@spec.json' 一次读多个目标(与 --file 互斥)
|
|
8
8
|
* aura basetools edit --targets '[...]|@spec.json' 一次改多个文件(与 --file 互斥;任一规划失败 ⇒ 零写入)
|
|
9
|
+
* aura basetools tx begin | commit --id <id> [--verify "命令"] | rollback --id <id> | status --id <id>
|
|
9
10
|
* aura basetools mcp
|
|
10
11
|
* aura --help | --version
|
|
11
12
|
*
|
|
@@ -25,6 +26,10 @@ import {
|
|
|
25
26
|
// 多目标(fs-contract-v2)的引擎绑定:与单目标同一处(`lib/ops.mjs`),bin 里不另找引擎。
|
|
26
27
|
readManyWith,
|
|
27
28
|
editManyEnvelope,
|
|
29
|
+
beginTransactionEnvelope,
|
|
30
|
+
commitTransactionEnvelope,
|
|
31
|
+
rollbackTransactionEnvelope,
|
|
32
|
+
statusTransactionEnvelope,
|
|
28
33
|
capabilitiesEnvelope,
|
|
29
34
|
writeEnvelope,
|
|
30
35
|
assertReadTargets,
|
|
@@ -86,13 +91,14 @@ const COMMAND_FLAGS = {
|
|
|
86
91
|
bool: ['pretty'],
|
|
87
92
|
},
|
|
88
93
|
edit: {
|
|
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'],
|
|
94
|
+
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', 'verify', 'tx'],
|
|
90
95
|
bool: ['pretty', 'dry-run', 'plan', 'replace-all', 'require-observation'],
|
|
91
96
|
},
|
|
92
97
|
write: {
|
|
93
|
-
value: ['cwd', 'file', 'content', 'eol'],
|
|
98
|
+
value: ['cwd', 'file', 'content', 'eol', 'verify', 'tx'],
|
|
94
99
|
bool: ['pretty', 'dry-run', 'plan', 'no-overwrite', 'allow-unobserved'],
|
|
95
100
|
},
|
|
101
|
+
tx: { value: ['cwd', 'id', 'verify'], bool: ['pretty', 'json'] },
|
|
96
102
|
doctor: { value: ['cwd'], bool: ['pretty', 'json'] },
|
|
97
103
|
capabilities: { value: ['cwd'], bool: ['pretty'] },
|
|
98
104
|
mcp: { value: ['cwd'], bool: ['pretty'] },
|
|
@@ -109,7 +115,9 @@ function validateCommandFlags(command, tokens) {
|
|
|
109
115
|
if (spec === undefined) return undefined
|
|
110
116
|
const valueFlags = new Set(spec.value)
|
|
111
117
|
const boolFlags = new Set(spec.bool)
|
|
112
|
-
|
|
118
|
+
// `tx` 的第一个裸位置参数是子命令(begin/commit/rollback/status);其余命令不允许位置参数。
|
|
119
|
+
const start = command === 'tx' && tokens[0] !== undefined && !tokens[0].startsWith('--') ? 1 : 0
|
|
120
|
+
for (let i = start; i < tokens.length; i += 1) {
|
|
113
121
|
const token = tokens[i]
|
|
114
122
|
if (typeof token !== 'string' || !token.startsWith('--')) {
|
|
115
123
|
return `unexpected argument ${JSON.stringify(token)} — this command takes flags only`
|
|
@@ -166,9 +174,9 @@ const USAGE = [
|
|
|
166
174
|
// `--file` 指向**目录**时直接给一张仓库地图(模块/目录/依赖方向),不需要额外旗标。
|
|
167
175
|
' (--file 传目录 ⇒ 仓库地图:模块清单 + 目录骨架 + 被依赖最多的模块 + 入口点)',
|
|
168
176
|
' aura basetools read --targets <json|@文件> # 一次读多个目标(≤32 项;与 --file 互斥)',
|
|
169
|
-
' aura basetools edit --targets <json|@文件> [--dry-run|--plan]
|
|
177
|
+
' aura basetools edit --targets <json|@文件> [--dry-run|--plan] [--verify "命令"] # 一次改多个文件(先全量规划,任一失败则零写入)',
|
|
170
178
|
" aura basetools edit --file <路径> --create <内容|@文件> # 建文件(不必再给 --hunks)",
|
|
171
|
-
" aura basetools edit --file <路径> (--hunks '<json>' | --hunks-file <路径>) [--dry-run|--plan] [--eol auto|preserve|lf|crlf] [--require-observation]",
|
|
179
|
+
" aura basetools edit --file <路径> (--hunks '<json>' | --hunks-file <路径>) [--dry-run|--plan] [--verify \"命令\"] [--eol auto|preserve|lf|crlf] [--require-observation]",
|
|
172
180
|
' aura basetools edit --file <路径> --lines <a,b> --replace <文本> [--expect <文本>] [--near <n>]',
|
|
173
181
|
' aura basetools edit --file <路径> --old <原文> --replace <文本> --replace-all # 改全部非重叠出现',
|
|
174
182
|
" (只改一处时不必包成 JSON:--target / --lines / --start-line+--end-line / --line-cue / --anchor 任选其一)",
|
|
@@ -177,8 +185,12 @@ const USAGE = [
|
|
|
177
185
|
' aura basetools edit --file <路径> --handle <h…> --line <段内行号> --replace <文本> # 只换区间内第 k 行(1-based,从区间首行数起)',
|
|
178
186
|
' aura basetools edit --file <路径> --handle <h…> --position before|after --replace <文本> # 在区间边界插入,区间本身不动',
|
|
179
187
|
" (--line / --position 是从属键:只有与 --handle 同时给才成立,单独给不构成一次编辑)",
|
|
188
|
+
' aura basetools tx begin 开一个跨调用事务(后续 edit/write 用 --tx <id> 加入)',
|
|
189
|
+
' aura basetools tx commit --id <id> [--verify "命令"] 提交;verify 失败/文件被外部改过 ⇒ 拒绝并回滚',
|
|
190
|
+
' aura basetools tx rollback --id <id> 回滚整个事务(按 afterVersion 校验)',
|
|
191
|
+
' aura basetools tx status --id <id> 只读状态',
|
|
180
192
|
' aura basetools mcp 启动 MCP stdio 服务',
|
|
181
|
-
' aura basetools write --file <路径> --content <文本|@文件> [--no-overwrite] [--dry-run|--plan] [--allow-unobserved] [--eol auto|preserve|lf|crlf]',
|
|
193
|
+
' aura basetools write --file <路径> --content <文本|@文件> [--no-overwrite] [--dry-run|--plan] [--verify "命令"] [--allow-unobserved] [--eol auto|preserve|lf|crlf]',
|
|
182
194
|
' (覆写默认要求先读过这一版;--allow-unobserved 才允许盲写)',
|
|
183
195
|
' aura basetools doctor [--json] 自检:Node/版本/MCP 握手/缓存与锁目录可写',
|
|
184
196
|
' aura basetools capabilities [--pretty] 能力清单:读模式/地址档/语言/限制(JSON)',
|
|
@@ -305,7 +317,7 @@ if (command === '--version' || command === '-v') emit({ ok: true, version: VERSI
|
|
|
305
317
|
* 旧顶层动词只给**迁移提示**,不再作为别名执行——保留别名等于保留未来的撞名。
|
|
306
318
|
* 只提示、不执行:`aura basetools read` 不会偷偷改文件。
|
|
307
319
|
*/
|
|
308
|
-
const MOVED_COMMANDS = new Set(['read', 'outline', 'edit', 'write', 'mcp', 'doctor', 'capabilities'])
|
|
320
|
+
const MOVED_COMMANDS = new Set(['read', 'outline', 'edit', 'write', 'tx', 'mcp', 'doctor', 'capabilities'])
|
|
309
321
|
if (!namespaced && MOVED_COMMANDS.has(command)) {
|
|
310
322
|
emit(
|
|
311
323
|
{
|
|
@@ -329,6 +341,29 @@ if (command === 'mcp') {
|
|
|
329
341
|
|
|
330
342
|
const provider = openProvider(typeof flag('cwd') === 'string' ? flag('cwd') : process.cwd())
|
|
331
343
|
|
|
344
|
+
if (command === 'tx') {
|
|
345
|
+
const action = argv[1]
|
|
346
|
+
const id = typeof flag('id') === 'string' ? flag('id') : undefined
|
|
347
|
+
if (action === 'begin') {
|
|
348
|
+
const env = beginTransactionEnvelope(provider)
|
|
349
|
+
emit(env, env.ok ? 0 : 2)
|
|
350
|
+
}
|
|
351
|
+
if (action === 'commit') {
|
|
352
|
+
const verify = typeof flag('verify') === 'string' ? flag('verify') : undefined
|
|
353
|
+
const env = await commitTransactionEnvelope(provider, { id, verify })
|
|
354
|
+
emit(env, env.ok ? 0 : 2)
|
|
355
|
+
}
|
|
356
|
+
if (action === 'rollback') {
|
|
357
|
+
const env = await rollbackTransactionEnvelope(provider, { id })
|
|
358
|
+
emit(env, env.ok ? 0 : 2)
|
|
359
|
+
}
|
|
360
|
+
if (action === 'status') {
|
|
361
|
+
const env = statusTransactionEnvelope(provider, { id })
|
|
362
|
+
emit(env, env.ok ? 0 : 2)
|
|
363
|
+
}
|
|
364
|
+
emit({ ok: false, error: 'tx 需要 begin | commit | rollback | status(commit/rollback/status 还要 --id)', code: 'USAGE' }, 1)
|
|
365
|
+
}
|
|
366
|
+
|
|
332
367
|
if (command === 'capabilities') {
|
|
333
368
|
const env = { ok: true, version: VERSION, ...capabilitiesEnvelope() }
|
|
334
369
|
process.stdout.write(`${JSON.stringify(env, null, has('pretty') ? 2 : 0)}\n`)
|
|
@@ -352,6 +387,8 @@ if (command === 'write') {
|
|
|
352
387
|
noOverwrite: has('no-overwrite'),
|
|
353
388
|
allowUnobserved: has('allow-unobserved'),
|
|
354
389
|
...(typeof eol === 'string' ? { eol } : {}),
|
|
390
|
+
...(typeof flag('verify') === 'string' ? { verify: flag('verify') } : {}),
|
|
391
|
+
...(typeof flag('tx') === 'string' ? { transaction: flag('tx') } : {}),
|
|
355
392
|
})
|
|
356
393
|
emit(env, env.ok ? 0 : 2)
|
|
357
394
|
}
|
|
@@ -473,6 +510,8 @@ if (command === 'edit') {
|
|
|
473
510
|
targets: spec.targets,
|
|
474
511
|
dryRun: has('dry-run') || has('plan') || spec.dryRun,
|
|
475
512
|
...(has('require-observation') ? { requireObservation: true } : {}),
|
|
513
|
+
...(typeof flag('verify') === 'string' ? { verify: flag('verify') } : {}),
|
|
514
|
+
...(typeof flag('tx') === 'string' ? { transaction: flag('tx') } : {}),
|
|
476
515
|
})
|
|
477
516
|
emit(env, env.ok ? 0 : 2)
|
|
478
517
|
}
|
|
@@ -585,6 +624,8 @@ if (command === 'edit') {
|
|
|
585
624
|
dryRun: has('dry-run') || has('plan'),
|
|
586
625
|
create,
|
|
587
626
|
...(has('require-observation') ? { requireObservation: true } : {}),
|
|
627
|
+
...(typeof flag('verify') === 'string' ? { verify: flag('verify') } : {}),
|
|
628
|
+
...(typeof flag('tx') === 'string' ? { transaction: flag('tx') } : {}),
|
|
588
629
|
})
|
|
589
630
|
emit(env, env.ok ? 0 : 2)
|
|
590
631
|
}
|
package/lib/ops.mjs
CHANGED
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
* (`flatHunk` 也从这里出去),于是「引擎在哪」这个问题在整棵树里只有一个答案。
|
|
11
11
|
*/
|
|
12
12
|
import fs from 'node:fs'
|
|
13
|
+
import { spawnSync } from 'node:child_process'
|
|
13
14
|
import { fileURLToPath } from 'node:url'
|
|
14
15
|
import { createNodeFsProvider, DEFAULT_MAX_TEXT_BYTES } from './fs-provider.mjs'
|
|
15
16
|
import { withWorkspaceLock } from './workspace-lock.mjs'
|
|
16
|
-
import { createTransaction } from './transaction-store.mjs'
|
|
17
|
+
import { createTransaction, getTransactionMeta, openTransaction } from './transaction-store.mjs'
|
|
17
18
|
import { createDiskStore } from './observation-store.mjs'
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -91,7 +92,7 @@ export function capabilitiesEnvelope() {
|
|
|
91
92
|
{
|
|
92
93
|
name: 'aura_basetools_edit',
|
|
93
94
|
addressKinds: [...new Set([...engineArgs.FLAT_ADDRESS_KEYS, 'old', 'context', 'position'])],
|
|
94
|
-
options: ['replace_all', 'require_observation'],
|
|
95
|
+
options: ['replace_all', 'require_observation', 'verify'],
|
|
95
96
|
writeSafety: {
|
|
96
97
|
cli: ['workspace-lock', 'stale-region-gate', 'byte-verify'],
|
|
97
98
|
mcp: ['workspace-lock', 'stale-region-gate', 'byte-verify'],
|
|
@@ -101,7 +102,7 @@ export function capabilitiesEnvelope() {
|
|
|
101
102
|
{
|
|
102
103
|
name: 'aura_basetools_write',
|
|
103
104
|
operations: ['create', 'overwrite'],
|
|
104
|
-
options: ['eol'],
|
|
105
|
+
options: ['eol', 'verify'],
|
|
105
106
|
writeSafety: {
|
|
106
107
|
cli: ['workspace-lock', 'stale-region-gate', 'observation-gate', 'byte-verify'],
|
|
107
108
|
mcp: ['workspace-lock', 'stale-region-gate', 'observation-gate', 'byte-verify'],
|
|
@@ -110,7 +111,7 @@ export function capabilitiesEnvelope() {
|
|
|
110
111
|
},
|
|
111
112
|
},
|
|
112
113
|
],
|
|
113
|
-
commands: ['basetools read', 'basetools edit', 'basetools write', 'basetools mcp', 'basetools doctor', 'basetools capabilities', '--version', '--help'],
|
|
114
|
+
commands: ['basetools read', 'basetools edit', 'basetools write', 'basetools tx', 'basetools mcp', 'basetools doctor', 'basetools capabilities', '--version', '--help'],
|
|
114
115
|
langs,
|
|
115
116
|
langsCount: langs.length,
|
|
116
117
|
limits: {
|
|
@@ -479,6 +480,105 @@ function transactionError(error) {
|
|
|
479
480
|
}
|
|
480
481
|
}
|
|
481
482
|
|
|
483
|
+
const VERIFY_OUTPUT_LIMIT = 4000
|
|
484
|
+
const tailText = (value) => {
|
|
485
|
+
const text = String(value ?? '')
|
|
486
|
+
return text.length <= VERIFY_OUTPUT_LIMIT ? text : `…${text.slice(-VERIFY_OUTPUT_LIMIT)}`
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* 写后验证:**只在调用方显式给出命令时**执行。
|
|
491
|
+
*
|
|
492
|
+
* 不是自动发现仓库脚本,也不绕过沙箱/权限;命令在 workspace 根下跑,有超时与输出上限。
|
|
493
|
+
* 失败由外层事务回滚,调用方拿到的是可判定的 `PATCH_VERIFY_FAILED`,不是“写完再说”。
|
|
494
|
+
*/
|
|
495
|
+
function runVerification(command, cwd) {
|
|
496
|
+
const timeout = Math.min(600000, Math.max(1000, Number(process.env.AURA_VERIFY_TIMEOUT_MS) || 120000))
|
|
497
|
+
const started = Date.now()
|
|
498
|
+
const res = spawnSync(command, {
|
|
499
|
+
cwd,
|
|
500
|
+
shell: true,
|
|
501
|
+
encoding: 'utf8',
|
|
502
|
+
timeout,
|
|
503
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
504
|
+
env: process.env,
|
|
505
|
+
windowsHide: true,
|
|
506
|
+
})
|
|
507
|
+
const timedOut = res.error?.code === 'ETIMEDOUT'
|
|
508
|
+
return {
|
|
509
|
+
command,
|
|
510
|
+
cwd,
|
|
511
|
+
ok: res.status === 0 && res.error === undefined,
|
|
512
|
+
exitCode: res.status,
|
|
513
|
+
signal: res.signal ?? null,
|
|
514
|
+
timedOut,
|
|
515
|
+
durationMs: Date.now() - started,
|
|
516
|
+
stdout: tailText(res.stdout),
|
|
517
|
+
stderr: tailText(res.stderr),
|
|
518
|
+
...(res.error === undefined ? {} : { error: String(res.error.message ?? res.error) }),
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async function editInTransaction(provider, request) {
|
|
523
|
+
const verifyCommand = typeof request.verify === 'string' && request.verify.trim().length > 0 ? request.verify.trim() : null
|
|
524
|
+
try {
|
|
525
|
+
return await withWorkspaceLock(provider.root, async () => {
|
|
526
|
+
const tx = openTransaction(provider.root, request.transaction)
|
|
527
|
+
const env = await editEnvelopeInner(tx.proxy(provider), request)
|
|
528
|
+
if (!env.ok) return env
|
|
529
|
+
let verification
|
|
530
|
+
if (verifyCommand !== null) {
|
|
531
|
+
verification = runVerification(verifyCommand, provider.root)
|
|
532
|
+
if (!verification.ok) {
|
|
533
|
+
const rollback = await tx.rollback(provider)
|
|
534
|
+
return {
|
|
535
|
+
ok: false,
|
|
536
|
+
code: 'PATCH_VERIFY_FAILED',
|
|
537
|
+
error: `edit verification failed (exit ${verification.exitCode ?? 'no-status'}${verification.timedOut ? ', timed out' : ''}): ${verifyCommand}`,
|
|
538
|
+
verification,
|
|
539
|
+
rollback,
|
|
540
|
+
transaction: tx.summary(),
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return { ...env, transaction: tx.summary(), ...(verification === undefined ? {} : { verification }) }
|
|
545
|
+
})
|
|
546
|
+
} catch (error) {
|
|
547
|
+
return transactionError(error)
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async function editManyInTransaction(provider, request) {
|
|
552
|
+
const verifyCommand = typeof request.verify === 'string' && request.verify.trim().length > 0 ? request.verify.trim() : null
|
|
553
|
+
try {
|
|
554
|
+
return await withWorkspaceLock(provider.root, async () => {
|
|
555
|
+
const tx = openTransaction(provider.root, request.transaction)
|
|
556
|
+
const batch = await editManyEnvelopeInner(tx.proxy(provider), request)
|
|
557
|
+
if (!batch.ok) return batch
|
|
558
|
+
let verification
|
|
559
|
+
if (verifyCommand !== null) {
|
|
560
|
+
verification = runVerification(verifyCommand, provider.root)
|
|
561
|
+
if (!verification.ok) {
|
|
562
|
+
const rollback = await tx.rollback(provider)
|
|
563
|
+
const finalBatch = writePath.rebuildAfterRollback(batch, rollback)
|
|
564
|
+
return {
|
|
565
|
+
...finalBatch,
|
|
566
|
+
ok: false,
|
|
567
|
+
code: 'PATCH_VERIFY_FAILED',
|
|
568
|
+
error: `edit verification failed (exit ${verification.exitCode ?? 'no-status'}${verification.timedOut ? ', timed out' : ''}): ${verifyCommand}`,
|
|
569
|
+
verification,
|
|
570
|
+
rollback,
|
|
571
|
+
transaction: tx.summary(),
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return { ...batch, transaction: tx.summary(), ...(verification === undefined ? {} : { verification }) }
|
|
576
|
+
})
|
|
577
|
+
} catch (error) {
|
|
578
|
+
return transactionError(error)
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
482
582
|
/**
|
|
483
583
|
* 写事务外壳:workspace 锁 + 事务日志 + 失败回滚。
|
|
484
584
|
*
|
|
@@ -492,11 +592,35 @@ export async function editEnvelope(provider, request) {
|
|
|
492
592
|
* 预演不建事务、不写盘,也不需要工作区锁;0.14.0 起事务日志只用于失败回滚。
|
|
493
593
|
*/
|
|
494
594
|
if (request.dryRun === true) return editEnvelopeInner(provider, request)
|
|
595
|
+
if (typeof request.transaction === 'string' && request.transaction.length > 0) return editInTransaction(provider, request)
|
|
596
|
+
const verifyCommand = typeof request.verify === 'string' && request.verify.trim().length > 0 ? request.verify.trim() : null
|
|
495
597
|
try {
|
|
496
598
|
return await withWorkspaceLock(provider.root, async () => {
|
|
497
599
|
const tx = createTransaction(provider.root, 'aura_basetools_edit')
|
|
498
600
|
const env = await editEnvelopeInner(tx.proxy(provider), request)
|
|
499
601
|
if (env.ok) {
|
|
602
|
+
if (verifyCommand !== null) {
|
|
603
|
+
const verification = runVerification(verifyCommand, provider.root)
|
|
604
|
+
if (!verification.ok) {
|
|
605
|
+
const rollback = await tx.rollback(provider)
|
|
606
|
+
return {
|
|
607
|
+
ok: false,
|
|
608
|
+
error: `edit verification failed (exit ${verification.exitCode ?? 'no-status'}${verification.timedOut ? ', timed out' : ''}): ${verifyCommand}`,
|
|
609
|
+
code: 'PATCH_VERIFY_FAILED',
|
|
610
|
+
verification,
|
|
611
|
+
rollback,
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
try {
|
|
615
|
+
tx.commit()
|
|
616
|
+
} catch (error) {
|
|
617
|
+
const rollback = await tx.rollback(provider)
|
|
618
|
+
error.transaction = tx.summary()
|
|
619
|
+
error.rollback = rollback
|
|
620
|
+
throw error
|
|
621
|
+
}
|
|
622
|
+
return { ...env, verification }
|
|
623
|
+
}
|
|
500
624
|
try {
|
|
501
625
|
tx.commit()
|
|
502
626
|
} catch (error) {
|
|
@@ -520,12 +644,38 @@ export async function editManyEnvelope(provider, request) {
|
|
|
520
644
|
if (provider.root === undefined) return editManyEnvelopeInner(provider, request)
|
|
521
645
|
// 与单目标同一条:预演不建事务、不写盘,不能顶掉真正的最近 committed 事务。
|
|
522
646
|
if (request.dryRun === true) return editManyEnvelopeInner(provider, request)
|
|
647
|
+
if (typeof request.transaction === 'string' && request.transaction.length > 0) return editManyInTransaction(provider, request)
|
|
648
|
+
const verifyCommand = typeof request.verify === 'string' && request.verify.trim().length > 0 ? request.verify.trim() : null
|
|
523
649
|
try {
|
|
524
650
|
return await withWorkspaceLock(provider.root, async () => {
|
|
525
651
|
const tx = createTransaction(provider.root, 'aura_basetools_edit')
|
|
526
652
|
try {
|
|
527
653
|
const batch = await editManyEnvelopeInner(tx.proxy(provider), request)
|
|
528
654
|
if (batch.ok) {
|
|
655
|
+
if (verifyCommand !== null) {
|
|
656
|
+
const verification = runVerification(verifyCommand, provider.root)
|
|
657
|
+
if (!verification.ok) {
|
|
658
|
+
const rollback = await tx.rollback(provider)
|
|
659
|
+
const finalBatch = writePath.rebuildAfterRollback(batch, rollback)
|
|
660
|
+
return {
|
|
661
|
+
...finalBatch,
|
|
662
|
+
ok: false,
|
|
663
|
+
code: 'PATCH_VERIFY_FAILED',
|
|
664
|
+
error: `edit verification failed (exit ${verification.exitCode ?? 'no-status'}${verification.timedOut ? ', timed out' : ''}): ${verifyCommand}`,
|
|
665
|
+
verification,
|
|
666
|
+
rollback,
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
try {
|
|
670
|
+
tx.commit()
|
|
671
|
+
} catch (error) {
|
|
672
|
+
const rollback = await tx.rollback(provider)
|
|
673
|
+
error.transaction = tx.summary()
|
|
674
|
+
error.rollback = rollback
|
|
675
|
+
throw error
|
|
676
|
+
}
|
|
677
|
+
return { ...batch, verification }
|
|
678
|
+
}
|
|
529
679
|
try {
|
|
530
680
|
tx.commit()
|
|
531
681
|
} catch (error) {
|
|
@@ -554,6 +704,85 @@ export async function editManyEnvelope(provider, request) {
|
|
|
554
704
|
}
|
|
555
705
|
}
|
|
556
706
|
|
|
707
|
+
/** 开一个跨调用事务;后续 `edit --tx <id>` 写在同一份日志里。 */
|
|
708
|
+
export function beginTransactionEnvelope(provider) {
|
|
709
|
+
if (provider.root === undefined) return { ok: false, error: 'transactions need a workspace root', code: 'TX_NO_ROOT' }
|
|
710
|
+
try {
|
|
711
|
+
const tx = createTransaction(provider.root, 'aura_basetools_tx')
|
|
712
|
+
return { ok: true, transaction: tx.summary() }
|
|
713
|
+
} catch (error) {
|
|
714
|
+
return transactionError(error)
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/** 提交跨调用事务:先做 afterVersion 乐观校验,再跑可选 verify;失败自动 rollback。 */
|
|
719
|
+
export async function commitTransactionEnvelope(provider, { id, verify } = {}) {
|
|
720
|
+
if (provider.root === undefined) return { ok: false, error: 'transactions need a workspace root', code: 'TX_NO_ROOT' }
|
|
721
|
+
if (typeof id !== 'string' || id.length === 0) return { ok: false, error: 'commit needs a transaction id', code: 'TX_BAD_ID' }
|
|
722
|
+
const verifyCommand = typeof verify === 'string' && verify.trim().length > 0 ? verify.trim() : null
|
|
723
|
+
try {
|
|
724
|
+
return await withWorkspaceLock(provider.root, async () => {
|
|
725
|
+
const tx = openTransaction(provider.root, id)
|
|
726
|
+
const conflicts = tx.assertFresh(provider)
|
|
727
|
+
if (conflicts.length > 0) {
|
|
728
|
+
return {
|
|
729
|
+
ok: false,
|
|
730
|
+
code: 'TX_STALE',
|
|
731
|
+
error: `transaction ${id} has ${conflicts.length} file(s) changed since the transaction wrote them`,
|
|
732
|
+
conflicts,
|
|
733
|
+
transaction: tx.summary(),
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
let verification
|
|
737
|
+
if (verifyCommand !== null) {
|
|
738
|
+
verification = runVerification(verifyCommand, provider.root)
|
|
739
|
+
if (!verification.ok) {
|
|
740
|
+
const rollback = await tx.rollback(provider)
|
|
741
|
+
return {
|
|
742
|
+
ok: false,
|
|
743
|
+
code: 'PATCH_VERIFY_FAILED',
|
|
744
|
+
error: `transaction verification failed (exit ${verification.exitCode ?? 'no-status'}${verification.timedOut ? ', timed out' : ''}): ${verifyCommand}`,
|
|
745
|
+
verification,
|
|
746
|
+
rollback,
|
|
747
|
+
transaction: tx.summary(),
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
tx.commit()
|
|
752
|
+
return { ok: true, transaction: tx.summary(), ...(verification === undefined ? {} : { verification }) }
|
|
753
|
+
})
|
|
754
|
+
} catch (error) {
|
|
755
|
+
return transactionError(error)
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/** 回滚跨调用事务:与内部失败回滚同一套 afterVersion 校验。 */
|
|
760
|
+
export async function rollbackTransactionEnvelope(provider, { id } = {}) {
|
|
761
|
+
if (provider.root === undefined) return { ok: false, error: 'transactions need a workspace root', code: 'TX_NO_ROOT' }
|
|
762
|
+
if (typeof id !== 'string' || id.length === 0) return { ok: false, error: 'rollback needs a transaction id', code: 'TX_BAD_ID' }
|
|
763
|
+
try {
|
|
764
|
+
return await withWorkspaceLock(provider.root, async () => {
|
|
765
|
+
const tx = openTransaction(provider.root, id)
|
|
766
|
+
const rollback = await tx.rollback(provider)
|
|
767
|
+
return { ok: rollback.status === 'rolled_back', rollback, transaction: tx.summary() }
|
|
768
|
+
})
|
|
769
|
+
} catch (error) {
|
|
770
|
+
return transactionError(error)
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/** 只读事务状态。 */
|
|
775
|
+
export function statusTransactionEnvelope(provider, { id } = {}) {
|
|
776
|
+
if (provider.root === undefined) return { ok: false, error: 'transactions need a workspace root', code: 'TX_NO_ROOT' }
|
|
777
|
+
if (typeof id !== 'string' || id.length === 0) return { ok: false, error: 'status needs a transaction id', code: 'TX_BAD_ID' }
|
|
778
|
+
try {
|
|
779
|
+
const meta = getTransactionMeta(provider.root, id)
|
|
780
|
+
return { ok: true, transaction: { id: meta.id, status: meta.status, files: meta.files.length, written: meta.files.filter((f) => f.written).length, dir: meta.dir } }
|
|
781
|
+
} catch (error) {
|
|
782
|
+
return transactionError(error)
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
557
786
|
/** 整文件写的紧凑回执:不把整份 old/new 文本塞进 applied(那是 edit 的形状,不是 write 的)。 */
|
|
558
787
|
function compactWriteReceipt(env, content) {
|
|
559
788
|
if (env === null || typeof env !== 'object' || env.ok !== true) return env
|
|
@@ -567,6 +796,7 @@ function compactWriteReceipt(env, content) {
|
|
|
567
796
|
const bytes = result.write_verify?.bytes ?? (dryRun && result.operation === 'create' ? Buffer.byteLength(content, 'utf8') : undefined)
|
|
568
797
|
return {
|
|
569
798
|
ok: true,
|
|
799
|
+
...(env.verification === undefined ? {} : { verification: env.verification }),
|
|
570
800
|
result: {
|
|
571
801
|
path: result.path,
|
|
572
802
|
operation: result.operation ?? 'write',
|
|
@@ -594,7 +824,7 @@ function compactWriteReceipt(env, content) {
|
|
|
594
824
|
* `preserve` 按 content 原样写(与内置 Write 的“给什么写什么”对齐),`lf` / `crlf` 强制统一。
|
|
595
825
|
* 新建文件永远按 content 原样写;显式传非 `auto` 的 eol 会被拒绝(不做静默忽略)。
|
|
596
826
|
*/
|
|
597
|
-
export async function writeEnvelope(provider, { file, content, dryRun = false, noOverwrite = false, allowUnobserved = false, eol = 'auto' } = {}) {
|
|
827
|
+
export async function writeEnvelope(provider, { file, content, dryRun = false, noOverwrite = false, allowUnobserved = false, eol = 'auto', verify, transaction } = {}) {
|
|
598
828
|
if (typeof file !== 'string' || file.length === 0) {
|
|
599
829
|
return { ok: false, error: 'write needs a file path', code: 'WRITE_BAD_ARGS' }
|
|
600
830
|
}
|
|
@@ -621,7 +851,7 @@ export async function writeEnvelope(provider, { file, content, dryRun = false, n
|
|
|
621
851
|
if (dryRun) {
|
|
622
852
|
return { ok: true, result: { path: displayPath, operation: 'create', bytes: Buffer.byteLength(content, 'utf8'), dryRun: true } }
|
|
623
853
|
}
|
|
624
|
-
return compactWriteReceipt(await editEnvelope(provider, { file, create: content }), content)
|
|
854
|
+
return compactWriteReceipt(await editEnvelope(provider, { file, create: content, verify, transaction }), content)
|
|
625
855
|
}
|
|
626
856
|
return compactWriteReceipt(
|
|
627
857
|
await editEnvelope(provider, {
|
|
@@ -632,6 +862,8 @@ export async function writeEnvelope(provider, { file, content, dryRun = false, n
|
|
|
632
862
|
requireObservation: allowUnobserved !== true,
|
|
633
863
|
skipStaleGate: allowUnobserved === true,
|
|
634
864
|
toolName: 'aura_basetools_write',
|
|
865
|
+
verify,
|
|
866
|
+
transaction,
|
|
635
867
|
}),
|
|
636
868
|
content,
|
|
637
869
|
)
|
|
@@ -117,22 +117,13 @@ function fileSummary(entry) {
|
|
|
117
117
|
* @param root - 工作区根
|
|
118
118
|
* @param tool - 产生它的工具名(仅记录)
|
|
119
119
|
*/
|
|
120
|
-
|
|
121
|
-
const id = transactionId()
|
|
122
|
-
const dir = path.join(transactionsRoot(root), id)
|
|
123
|
-
ensureDir(dir)
|
|
124
|
-
const meta = {
|
|
125
|
-
id,
|
|
126
|
-
tool,
|
|
127
|
-
root: path.resolve(root),
|
|
128
|
-
createdAt: new Date().toISOString(),
|
|
129
|
-
status: 'pending',
|
|
130
|
-
files: [],
|
|
131
|
-
dir,
|
|
132
|
-
}
|
|
133
|
-
saveMeta(meta)
|
|
120
|
+
function makeTransaction(root, meta, dir) {
|
|
134
121
|
const entries = new Map()
|
|
135
|
-
let
|
|
122
|
+
for (let i = 0; i < meta.files.length; i += 1) {
|
|
123
|
+
const saved = meta.files[i]
|
|
124
|
+
entries.set(saved.path, { ...saved, metaIndex: i })
|
|
125
|
+
}
|
|
126
|
+
let closed = meta.status !== 'pending'
|
|
136
127
|
|
|
137
128
|
const entryKey = (provider, target) => normalizeTarget(provider, target)
|
|
138
129
|
|
|
@@ -174,7 +165,7 @@ export function createTransaction(root, tool = 'aura_basetools_edit') {
|
|
|
174
165
|
}
|
|
175
166
|
|
|
176
167
|
const tx = {
|
|
177
|
-
id,
|
|
168
|
+
id: meta.id,
|
|
178
169
|
dir,
|
|
179
170
|
meta,
|
|
180
171
|
entries,
|
|
@@ -196,17 +187,29 @@ export function createTransaction(root, tool = 'aura_basetools_edit') {
|
|
|
196
187
|
}
|
|
197
188
|
return proxy
|
|
198
189
|
},
|
|
190
|
+
/** 交叉调用提交前的乐观校验:事务写过的文件必须仍停在 afterVersion。 */
|
|
191
|
+
assertFresh(provider) {
|
|
192
|
+
const conflicts = []
|
|
193
|
+
for (const entry of entries.values()) {
|
|
194
|
+
if (!entry.written) continue
|
|
195
|
+
let current
|
|
196
|
+
try { current = provider.stat(entry.path) } catch { current = undefined }
|
|
197
|
+
if (entry.afterVersion !== null && current?.version !== entry.afterVersion) {
|
|
198
|
+
conflicts.push({ path: entry.path, expected: entry.afterVersion, actual: current?.version ?? null })
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return conflicts
|
|
202
|
+
},
|
|
199
203
|
commit() {
|
|
200
204
|
if (closed) return
|
|
201
205
|
meta.status = 'committed'
|
|
202
206
|
meta.committedAt = new Date().toISOString()
|
|
203
|
-
// 只有 meta 真正落盘后才 closed;否则调用方还能 rollback 已经写出的文件。
|
|
204
207
|
saveMeta(meta)
|
|
205
208
|
closed = true
|
|
206
209
|
prune(root)
|
|
207
210
|
},
|
|
208
211
|
summary() {
|
|
209
|
-
return { id, dir, status: meta.status, files: meta.files.length }
|
|
212
|
+
return { id: meta.id, dir, status: meta.status, files: meta.files.length, written: meta.files.filter((f) => f.written).length }
|
|
210
213
|
},
|
|
211
214
|
async rollback(provider, options = {}) {
|
|
212
215
|
if (closed) return { status: meta.status, restored: 0, conflicts: [] }
|
|
@@ -243,6 +246,48 @@ export function createTransaction(root, tool = 'aura_basetools_edit') {
|
|
|
243
246
|
return tx
|
|
244
247
|
}
|
|
245
248
|
|
|
249
|
+
/**
|
|
250
|
+
* 创建一个 pending 事务。此时还没有任何文件被记录;第一次 writeText 前才记原始字节。
|
|
251
|
+
*
|
|
252
|
+
* @param root - 工作区根
|
|
253
|
+
* @param tool - 产生它的工具名(仅记录)
|
|
254
|
+
*/
|
|
255
|
+
export function createTransaction(root, tool = 'aura_basetools_edit') {
|
|
256
|
+
const id = transactionId()
|
|
257
|
+
const dir = path.join(transactionsRoot(root), id)
|
|
258
|
+
ensureDir(dir)
|
|
259
|
+
const meta = {
|
|
260
|
+
id,
|
|
261
|
+
tool,
|
|
262
|
+
root: path.resolve(root),
|
|
263
|
+
createdAt: new Date().toISOString(),
|
|
264
|
+
status: 'pending',
|
|
265
|
+
files: [],
|
|
266
|
+
dir,
|
|
267
|
+
}
|
|
268
|
+
saveMeta(meta)
|
|
269
|
+
return makeTransaction(root, meta, dir)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** 打开一个仍处于 pending 的跨调用事务。 */
|
|
273
|
+
export function openTransaction(root, id) {
|
|
274
|
+
const dir = path.join(transactionsRoot(root), id)
|
|
275
|
+
const meta = loadMeta(dir)
|
|
276
|
+
if (meta.status !== 'pending') {
|
|
277
|
+
throw Object.assign(new Error(`transaction ${id} is ${meta.status}, not open`), { code: 'TX_NOT_OPEN' })
|
|
278
|
+
}
|
|
279
|
+
return makeTransaction(root, meta, dir)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** 读一个事务的元数据(状态/文件数);不存在时抛 TX_NOT_FOUND。 */
|
|
283
|
+
export function getTransactionMeta(root, id) {
|
|
284
|
+
try {
|
|
285
|
+
return loadMeta(path.join(transactionsRoot(root), id))
|
|
286
|
+
} catch {
|
|
287
|
+
throw Object.assign(new Error(`transaction not found: ${id}`), { code: 'TX_NOT_FOUND' })
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
246
291
|
function readAllMeta(root) {
|
|
247
292
|
const dir = transactionsRoot(root)
|
|
248
293
|
let names
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aura-basetools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Byte-exact read, edit and write tools for coding agents. Standalone CLI and MCP server; no DSH required. 面向 coding agent 的逐字节读写工具;独立 CLI / MCP,可选 DSH 适配器。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|