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 CHANGED
@@ -1,4 +1,4 @@
1
- # aura-basetools 0.19.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.19.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.19.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
- const LOCK_STALE_MS = 5000
208
- const LOCK_WAIT_MS = 2000
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
- if (error?.code !== 'EEXIST') return null
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
- fs.renameSync(tmp, file)
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.19.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",
@@ -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 {