aura-basetools 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/bin/aura-mcp.mjs +13 -0
- package/lib/observation-store.mjs +21 -4
- package/package.json +1 -1
- package/test/standalone-test.mjs +6 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# aura-basetools 0.
|
|
1
|
+
# aura-basetools 0.20.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.20.0
|
|
25
25
|
aura --version
|
|
26
26
|
```
|
|
27
27
|
|
|
@@ -91,6 +91,7 @@ Compared with the DSH built-in `dsh-tool-fs` tools, the differences are delibera
|
|
|
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
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
|
+
- **MCP clients get a server-side nudge.** `initialize.instructions` asks the client to prefer `aura_basetools_*` and to use `aura_basetools_tx` for multi-call changes; whether it is honored is the client's choice.
|
|
94
95
|
- **Changes can span calls, and concurrent changes merge.** `tx begin` opens a transaction; `edit` / `write` with `--tx <id>` append to it; `tx commit` three-way-merges non-overlapping external edits, refuses overlapping ones (`TX_CONFLICT`) without writing, and can run `verify`; `tx rollback` restores all of them. MCP exposes the same lifecycle as `aura_basetools_tx` plus the `transaction` field on edit/write.
|
|
95
96
|
- **Addressing is structural.** `target` / `lines` / `old` / `expect` / handles replace "copy the literal text exactly"; failure receipts include ready-to-use retry addresses.
|
|
96
97
|
- **The core is DSH-free.** `aura-basetools` has no runtime dependencies and no DSH imports; the DSH adapter is a separate optional package.
|
|
@@ -136,7 +137,7 @@ MIT.
|
|
|
136
137
|
### 安装
|
|
137
138
|
|
|
138
139
|
```bash
|
|
139
|
-
npm i -g aura-basetools@0.
|
|
140
|
+
npm i -g aura-basetools@0.20.0
|
|
140
141
|
aura --version
|
|
141
142
|
```
|
|
142
143
|
|
package/bin/aura-mcp.mjs
CHANGED
|
@@ -21,6 +21,18 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { readFileSync, realpathSync } from 'node:fs'
|
|
23
23
|
import process from 'node:process'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* MCP `initialize.instructions`: the only standard server-side nudge many MCP clients will
|
|
27
|
+
* prepend to their prompt. Keep it imperative and factual; it must not promise client behavior
|
|
28
|
+
* the client does not implement.
|
|
29
|
+
*/
|
|
30
|
+
const SERVER_INSTRUCTIONS = [
|
|
31
|
+
'Prefer the aura_basetools_* tools over built-in Read/Edit/Write for file work when they are available.',
|
|
32
|
+
'Read with aura_basetools_read; edit with aura_basetools_edit; create or overwrite with aura_basetools_write.',
|
|
33
|
+
'For changes spanning multiple calls use aura_basetools_tx begin -> edit/write with transaction -> commit; rollback on failure.',
|
|
34
|
+
'These tools preserve BOM and per-line endings, gate only the addressed region for staleness, verify writes, and keep multi-target edits atomic.',
|
|
35
|
+
].join(' ')
|
|
24
36
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
25
37
|
import {
|
|
26
38
|
openProvider,
|
|
@@ -504,6 +516,7 @@ export async function serve() {
|
|
|
504
516
|
supportedProtocolVersions: SUPPORTED_PROTOCOLS,
|
|
505
517
|
capabilities: { tools: { listChanged: false } },
|
|
506
518
|
serverInfo: { name: 'aura-basetools', version: VERSION },
|
|
519
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
507
520
|
})
|
|
508
521
|
}
|
|
509
522
|
if (method === 'ping') return reply(id, {})
|
|
@@ -204,8 +204,10 @@ export function createDiskStore(file = observationsPath()) {
|
|
|
204
204
|
* 陈旧锁(进程崩溃留下)超过 `LOCK_STALE_MS` 后清掉。拿不到锁时仍做一次尽力而为的
|
|
205
205
|
* 合并写入——缓存损坏/丢一条都不该让编辑失败,但正常情况下不会走到这条。
|
|
206
206
|
*/
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
// Runner 上 6 个 Node 进程同时启动时,2s 不够;拿不到锁仍写入会互相覆盖(实测 CI 偶发)。
|
|
208
|
+
// 锁只覆盖一次极小的 read-merge-write,15s 是安全上限,不是常态等待。
|
|
209
|
+
const LOCK_STALE_MS = 30000
|
|
210
|
+
const LOCK_WAIT_MS = 15000
|
|
209
211
|
const sleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)
|
|
210
212
|
const acquireLock = () => {
|
|
211
213
|
const lock = `${file}.lock`
|
|
@@ -221,7 +223,9 @@ export function createDiskStore(file = observationsPath()) {
|
|
|
221
223
|
try { fs.writeFileSync(fd, String(process.pid)) } finally { fs.closeSync(fd) }
|
|
222
224
|
return lock
|
|
223
225
|
} catch (error) {
|
|
224
|
-
|
|
226
|
+
// Windows 上另一个进程正在 rename/unlink 时,`wx` 可能给 EPERM/EACCES 而不是 EEXIST;
|
|
227
|
+
// 这不是“没锁可用”,而是“锁正在换手”,继续等到 deadline。
|
|
228
|
+
if (error?.code !== 'EEXIST' && error?.code !== 'EPERM' && error?.code !== 'EACCES') return null
|
|
225
229
|
try {
|
|
226
230
|
const st = fs.statSync(lock)
|
|
227
231
|
if (Date.now() - st.mtimeMs > LOCK_STALE_MS) {
|
|
@@ -254,7 +258,20 @@ export function createDiskStore(file = observationsPath()) {
|
|
|
254
258
|
if (path.sep !== '\\') {
|
|
255
259
|
try { fs.chmodSync(tmp, 0o600) } catch { /* 同上 */ }
|
|
256
260
|
}
|
|
257
|
-
|
|
261
|
+
// Windows:另一个进程正读 observations.json 时,rename 目标可能短暂 EPERM/EBUSY。
|
|
262
|
+
// 不重试就会静默丢掉这一条观测(CI 的 6 并发实测 1/5 概率)。
|
|
263
|
+
let renamed = false
|
|
264
|
+
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
265
|
+
try {
|
|
266
|
+
fs.renameSync(tmp, file)
|
|
267
|
+
renamed = true
|
|
268
|
+
break
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (!['EPERM', 'EBUSY', 'EACCES'].includes(error?.code) || attempt === 49) throw error
|
|
271
|
+
sleep(20)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (!renamed) throw new Error('observation cache rename did not complete')
|
|
258
275
|
lastStamp = fileStamp()
|
|
259
276
|
if (path.sep !== '\\') {
|
|
260
277
|
try { fs.chmodSync(file, 0o600) } catch { /* 同上 */ }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aura-basetools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.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",
|
package/test/standalone-test.mjs
CHANGED
|
@@ -375,6 +375,12 @@ say('F MCP:initialize / tools/list / tools/call')
|
|
|
375
375
|
const byId = new Map(replies.map((r) => [r.id, r]))
|
|
376
376
|
check(byId.get(1)?.result?.protocolVersion === '2025-06-18', 'initialize 回显协议版本', JSON.stringify(byId.get(1)).slice(0, 120))
|
|
377
377
|
check(byId.get(1)?.result?.serverInfo?.name === 'aura-basetools', 'serverInfo.name = aura-basetools')
|
|
378
|
+
const instructions = byId.get(1)?.result?.instructions
|
|
379
|
+
check(
|
|
380
|
+
typeof instructions === 'string' && instructions.includes('Prefer the aura_basetools_*') && instructions.includes('aura_basetools_tx'),
|
|
381
|
+
'initialize 带服务器 instructions(优先 aura + tx)',
|
|
382
|
+
String(instructions ?? '').slice(0, 120),
|
|
383
|
+
)
|
|
378
384
|
// 版本号曾经写死在 bin 里(包到 0.4.0,服务对外还报 0.2.1)—— 这里钉住它跟包清单一致
|
|
379
385
|
const manifest = (() => {
|
|
380
386
|
try {
|