aura-basetools 0.16.0 → 0.18.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 +5 -5
- package/bin/aura-mcp.mjs +31 -0
- package/lib/ops.mjs +11 -6
- package/lib/transaction-store.mjs +113 -1
- package/package.json +1 -1
- package/test/standalone-test.mjs +1 -1
- package/test/write-test.mjs +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# aura-basetools 0.
|
|
1
|
+
# aura-basetools 0.18.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.18.0
|
|
25
25
|
aura --version
|
|
26
26
|
```
|
|
27
27
|
|
|
@@ -91,7 +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
|
-
- **Changes can span calls.** `tx begin` opens a transaction; `edit` / `write` with `--tx <id>` append to it; `tx commit`
|
|
94
|
+
- **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
95
|
- **Addressing is structural.** `target` / `lines` / `old` / `expect` / handles replace "copy the literal text exactly"; failure receipts include ready-to-use retry addresses.
|
|
96
96
|
- **The core is DSH-free.** `aura-basetools` has no runtime dependencies and no DSH imports; the DSH adapter is a separate optional package.
|
|
97
97
|
|
|
@@ -136,7 +136,7 @@ MIT.
|
|
|
136
136
|
### 安装
|
|
137
137
|
|
|
138
138
|
```bash
|
|
139
|
-
npm i -g aura-basetools@0.
|
|
139
|
+
npm i -g aura-basetools@0.18.0
|
|
140
140
|
aura --version
|
|
141
141
|
```
|
|
142
142
|
|
|
@@ -206,7 +206,7 @@ npx -y --package aura-basetools aura basetools mcp
|
|
|
206
206
|
- **依据过期就拒绝。** 区域级陈旧闸门在“授权读取之后该区域被改过”时拒绝,并指出重读哪几行;`expect` 让目标原文显式化。
|
|
207
207
|
- **写完要验,不靠相信。** 每次写盘走临时文件、flush、rename,再做逐字节回读;多目标先全量规划,任一失败则一个字节都不写。
|
|
208
208
|
- **变更可以自证。** `edit` / `write` 接受显式 `verify` 命令;非零退出或超时会把整个事务回滚,回执带命令输出与回滚状态。不自动发现仓库脚本。
|
|
209
|
-
-
|
|
209
|
+
- **变更可以跨调用,也可以并发合并。** `tx begin` 开事务;`edit` / `write --tx <id>` 追加;`tx commit` 对外部非重叠改动做三方合并,重叠则 `TX_CONFLICT` 且零写入,可带 `verify`;`tx rollback` 恢复全部。MCP 用同一个生命周期:`aura_basetools_tx` + edit/write 的 `transaction` 字段。
|
|
210
210
|
- **寻址是结构化的。** `target` / `lines` / `old` / `expect` / 句柄替代“把原文一字不差抄对”;失败回执带可直接用的重试地址。
|
|
211
211
|
- **核心不依赖 DSH。** `aura-basetools` 零运行时依赖、无 DSH import;DSH 适配器是独立可选包。
|
|
212
212
|
|
package/bin/aura-mcp.mjs
CHANGED
|
@@ -30,6 +30,10 @@ import {
|
|
|
30
30
|
// 多目标(fs-contract-v2)的引擎绑定:与单目标同一处(`lib/ops.mjs`),bin 里不另找引擎。
|
|
31
31
|
readManyWith,
|
|
32
32
|
editManyEnvelope,
|
|
33
|
+
beginTransactionEnvelope,
|
|
34
|
+
commitTransactionEnvelope,
|
|
35
|
+
rollbackTransactionEnvelope,
|
|
36
|
+
statusTransactionEnvelope,
|
|
33
37
|
assertReadTargets,
|
|
34
38
|
assertEditTargets,
|
|
35
39
|
assertTargetShape,
|
|
@@ -204,6 +208,7 @@ const TOOLS = [
|
|
|
204
208
|
type: 'string',
|
|
205
209
|
description: '写后验证命令(显式给出才执行;在 workspace 根下、受超时/输出上限约束)。非零退出 ⇒ 自动回滚并返回 PATCH_VERIFY_FAILED。不自动发现/执行仓库脚本。',
|
|
206
210
|
},
|
|
211
|
+
transaction: { type: 'string', description: '跨调用事务 id(tx begin 返回);本次修改追加到该事务,不立即 commit' },
|
|
207
212
|
create: { type: 'string', description: '文件不存在时用它创建(整份内容)' },
|
|
208
213
|
targets: TARGETS_SCHEMA,
|
|
209
214
|
},
|
|
@@ -245,10 +250,25 @@ const TOOLS = [
|
|
|
245
250
|
type: 'string',
|
|
246
251
|
description: '写后验证命令(显式给出才执行;非零退出 ⇒ 自动回滚并返回 PATCH_VERIFY_FAILED)。',
|
|
247
252
|
},
|
|
253
|
+
transaction: { type: 'string', description: '跨调用事务 id(tx begin 返回);本次修改追加到该事务,不立即 commit' },
|
|
248
254
|
},
|
|
249
255
|
required: ['file_path', 'content'],
|
|
250
256
|
},
|
|
251
257
|
},
|
|
258
|
+
{
|
|
259
|
+
name: 'aura_basetools_tx',
|
|
260
|
+
description:
|
|
261
|
+
'Begin, commit, rollback, or inspect a cross-call transaction. Begin returns an id; pass it to edit/write `transaction` to append changes; commit checks each written file was not changed externally and can run `verify`; rollback restores all files.',
|
|
262
|
+
inputSchema: {
|
|
263
|
+
type: 'object',
|
|
264
|
+
properties: {
|
|
265
|
+
action: { type: 'string', enum: ['begin', 'commit', 'rollback', 'status'] },
|
|
266
|
+
transaction: { type: 'string', description: 'tx begin 返回的事务 id(begin 时不需要)' },
|
|
267
|
+
verify: { type: 'string', description: 'commit 时的写后验证命令;失败 ⇒ 整个事务回滚' },
|
|
268
|
+
},
|
|
269
|
+
required: ['action'],
|
|
270
|
+
},
|
|
271
|
+
},
|
|
252
272
|
]
|
|
253
273
|
|
|
254
274
|
/**
|
|
@@ -333,6 +353,7 @@ async function editTargetsCall(provider, args) {
|
|
|
333
353
|
dryRun: args.dry_run === true,
|
|
334
354
|
...(args.require_observation === true ? { requireObservation: true } : {}),
|
|
335
355
|
...(typeof args.verify === 'string' ? { verify: args.verify } : {}),
|
|
356
|
+
...(typeof args.transaction === 'string' ? { transaction: args.transaction } : {}),
|
|
336
357
|
})
|
|
337
358
|
}
|
|
338
359
|
|
|
@@ -383,6 +404,14 @@ async function callTool(provider, name, args = {}) {
|
|
|
383
404
|
}
|
|
384
405
|
}
|
|
385
406
|
}
|
|
407
|
+
if (name === 'aura_basetools_tx') {
|
|
408
|
+
const action = args.action
|
|
409
|
+
if (action === 'begin') return beginTransactionEnvelope(provider)
|
|
410
|
+
if (action === 'commit') return await commitTransactionEnvelope(provider, { id: args.transaction, verify: args.verify })
|
|
411
|
+
if (action === 'rollback') return await rollbackTransactionEnvelope(provider, { id: args.transaction })
|
|
412
|
+
if (action === 'status') return statusTransactionEnvelope(provider, { id: args.transaction })
|
|
413
|
+
return { ok: false, error: 'aura_basetools_tx needs action = begin | commit | rollback | status', code: 'BAD_ARGS' }
|
|
414
|
+
}
|
|
386
415
|
if (name === 'aura_basetools_read') {
|
|
387
416
|
if (multi) return await readTargetsCall(provider, args)
|
|
388
417
|
const noShape = noShapeRefusal(args, 'aura_basetools_read')
|
|
@@ -418,6 +447,7 @@ async function callTool(provider, name, args = {}) {
|
|
|
418
447
|
create: creating ? args.create : undefined,
|
|
419
448
|
...(args.require_observation === true ? { requireObservation: true } : {}),
|
|
420
449
|
...(typeof args.verify === 'string' ? { verify: args.verify } : {}),
|
|
450
|
+
...(typeof args.transaction === 'string' ? { transaction: args.transaction } : {}),
|
|
421
451
|
})
|
|
422
452
|
}
|
|
423
453
|
if (name === 'aura_basetools_write') {
|
|
@@ -429,6 +459,7 @@ async function callTool(provider, name, args = {}) {
|
|
|
429
459
|
allowUnobserved: args.allow_unobserved === true,
|
|
430
460
|
...(typeof args.eol === 'string' ? { eol: args.eol } : {}),
|
|
431
461
|
...(typeof args.verify === 'string' ? { verify: args.verify } : {}),
|
|
462
|
+
...(typeof args.transaction === 'string' ? { transaction: args.transaction } : {}),
|
|
432
463
|
})
|
|
433
464
|
}
|
|
434
465
|
return { ok: false, error: `unknown tool ${JSON.stringify(name)}`, code: 'UNKNOWN_TOOL' }
|
package/lib/ops.mjs
CHANGED
|
@@ -99,6 +99,11 @@ export function capabilitiesEnvelope() {
|
|
|
99
99
|
dsh: ['stale-region-gate', 'byte-verify'],
|
|
100
100
|
},
|
|
101
101
|
},
|
|
102
|
+
{
|
|
103
|
+
name: 'aura_basetools_tx',
|
|
104
|
+
actions: ['begin', 'commit', 'rollback', 'status'],
|
|
105
|
+
options: ['verify'],
|
|
106
|
+
},
|
|
102
107
|
{
|
|
103
108
|
name: 'aura_basetools_write',
|
|
104
109
|
operations: ['create', 'overwrite'],
|
|
@@ -723,13 +728,13 @@ export async function commitTransactionEnvelope(provider, { id, verify } = {}) {
|
|
|
723
728
|
try {
|
|
724
729
|
return await withWorkspaceLock(provider.root, async () => {
|
|
725
730
|
const tx = openTransaction(provider.root, id)
|
|
726
|
-
const
|
|
727
|
-
if (conflicts.length > 0) {
|
|
731
|
+
const merge = tx.mergeOnCommit(provider)
|
|
732
|
+
if (merge.conflicts.length > 0) {
|
|
728
733
|
return {
|
|
729
734
|
ok: false,
|
|
730
|
-
code: '
|
|
731
|
-
error: `transaction ${id} has ${conflicts.length} file(s)
|
|
732
|
-
conflicts,
|
|
735
|
+
code: 'TX_CONFLICT',
|
|
736
|
+
error: `transaction ${id} has ${merge.conflicts.length} file(s) that cannot be merged; nothing was written`,
|
|
737
|
+
conflicts: merge.conflicts,
|
|
733
738
|
transaction: tx.summary(),
|
|
734
739
|
}
|
|
735
740
|
}
|
|
@@ -749,7 +754,7 @@ export async function commitTransactionEnvelope(provider, { id, verify } = {}) {
|
|
|
749
754
|
}
|
|
750
755
|
}
|
|
751
756
|
tx.commit()
|
|
752
|
-
return { ok: true, transaction: tx.summary(), ...(verification === undefined ? {} : { verification }) }
|
|
757
|
+
return { ok: true, transaction: tx.summary(), merges: merge.merged, ...(verification === undefined ? {} : { verification }) }
|
|
753
758
|
})
|
|
754
759
|
} catch (error) {
|
|
755
760
|
return transactionError(error)
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* 这不是安全边界;它只保证遵守本协议的进程不会静默覆盖并发写。
|
|
15
15
|
*/
|
|
16
16
|
import fs from 'node:fs'
|
|
17
|
+
import { spawnSync } from 'node:child_process'
|
|
17
18
|
import os from 'node:os'
|
|
18
19
|
import path from 'node:path'
|
|
19
20
|
import crypto from 'node:crypto'
|
|
@@ -108,6 +109,8 @@ function fileSummary(entry) {
|
|
|
108
109
|
written: entry.written,
|
|
109
110
|
afterVersion: entry.afterVersion ?? null,
|
|
110
111
|
backup: entry.backup ?? null,
|
|
112
|
+
writtenBackup: entry.writtenBackup ?? null,
|
|
113
|
+
rollbackBackup: entry.rollbackBackup ?? null,
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -117,6 +120,53 @@ function fileSummary(entry) {
|
|
|
117
120
|
* @param root - 工作区根
|
|
118
121
|
* @param tool - 产生它的工具名(仅记录)
|
|
119
122
|
*/
|
|
123
|
+
function utf8Text(buf) {
|
|
124
|
+
const text = buf.toString('utf8')
|
|
125
|
+
if (!Buffer.from(text, 'utf8').equals(buf)) return null
|
|
126
|
+
if (text.includes('\u0000')) return null
|
|
127
|
+
return text
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 2-way fast path + `git merge-file` 的三方合并。
|
|
132
|
+
*
|
|
133
|
+
* 文字层合并,不碰二进制;一方没变时直接取另一方,避免为无关文件启动 git。
|
|
134
|
+
* Git 不可用或冲突时返回 conflict,调用方拒绝提交,不写任何文件。
|
|
135
|
+
*/
|
|
136
|
+
function mergeText(baseBuf, oursBuf, theirsBuf, tmpDir) {
|
|
137
|
+
const base = utf8Text(baseBuf)
|
|
138
|
+
const ours = utf8Text(oursBuf)
|
|
139
|
+
const theirs = utf8Text(theirsBuf)
|
|
140
|
+
if (base === null || ours === null || theirs === null) return { ok: false, reason: 'not-utf8' }
|
|
141
|
+
if (ours === base) return { ok: true, bytes: theirsBuf }
|
|
142
|
+
if (theirs === base) return { ok: true, bytes: oursBuf }
|
|
143
|
+
ensureDir(tmpDir)
|
|
144
|
+
const stem = `${process.pid}-${crypto.randomBytes(4).toString('hex')}`
|
|
145
|
+
const baseFile = path.join(tmpDir, `${stem}.base`)
|
|
146
|
+
const oursFile = path.join(tmpDir, `${stem}.ours`)
|
|
147
|
+
const theirsFile = path.join(tmpDir, `${stem}.theirs`)
|
|
148
|
+
try {
|
|
149
|
+
fs.writeFileSync(baseFile, baseBuf)
|
|
150
|
+
fs.writeFileSync(oursFile, oursBuf)
|
|
151
|
+
fs.writeFileSync(theirsFile, theirsBuf)
|
|
152
|
+
const res = spawnSync('git', ['merge-file', '-p', '--diff3', oursFile, baseFile, theirsFile], {
|
|
153
|
+
encoding: null,
|
|
154
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
155
|
+
windowsHide: true,
|
|
156
|
+
})
|
|
157
|
+
if (res.error?.code === 'ENOENT') return { ok: false, reason: 'git-unavailable' }
|
|
158
|
+
if (res.status === 0) return { ok: true, bytes: Buffer.from(res.stdout) }
|
|
159
|
+
if (res.status === 1) {
|
|
160
|
+
return { ok: false, reason: 'overlap', conflictText: Buffer.from(res.stdout).toString('utf8').slice(0, 4000) }
|
|
161
|
+
}
|
|
162
|
+
return { ok: false, reason: 'git-error', exitCode: res.status, stderr: String(res.stderr ?? '').slice(0, 500) }
|
|
163
|
+
} finally {
|
|
164
|
+
for (const file of [baseFile, oursFile, theirsFile]) {
|
|
165
|
+
try { fs.rmSync(file, { force: true }) } catch { /* 临时文件清不掉不影响判定 */ }
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
120
170
|
function makeTransaction(root, meta, dir) {
|
|
121
171
|
const entries = new Map()
|
|
122
172
|
for (let i = 0; i < meta.files.length; i += 1) {
|
|
@@ -177,16 +227,77 @@ function makeTransaction(root, meta, dir) {
|
|
|
177
227
|
const result = await provider.writeText(target, content, expected)
|
|
178
228
|
entry.written = true
|
|
179
229
|
entry.afterVersion = result?.version ?? null
|
|
230
|
+
const writtenPath = path.join('written', `${entry.metaIndex}.bin`)
|
|
231
|
+
writeBytesAtomic(path.join(dir, writtenPath), fs.readFileSync(entry.realPath), 0o600)
|
|
232
|
+
entry.writtenBackup = writtenPath
|
|
180
233
|
const summary = meta.files[entry.metaIndex]
|
|
181
234
|
if (summary !== undefined) {
|
|
182
235
|
summary.written = true
|
|
183
236
|
summary.afterVersion = entry.afterVersion
|
|
237
|
+
summary.writtenBackup = entry.writtenBackup
|
|
184
238
|
}
|
|
185
239
|
saveMeta(meta)
|
|
186
240
|
return result
|
|
187
241
|
}
|
|
188
242
|
return proxy
|
|
189
243
|
},
|
|
244
|
+
/**
|
|
245
|
+
* 提交前的并发合并:事务写过的文件若被外部改过,尝试把我们的 diff 三方合并到当前内容。
|
|
246
|
+
* **先规划所有文件,全部可合并才写;任一冲突 ⇒ 零写入**,返回 conflicts 供调用方拒绝。
|
|
247
|
+
*/
|
|
248
|
+
mergeOnCommit(provider) {
|
|
249
|
+
const plans = []
|
|
250
|
+
const conflicts = []
|
|
251
|
+
for (const entry of entries.values()) {
|
|
252
|
+
if (!entry.written) continue
|
|
253
|
+
let current
|
|
254
|
+
try { current = provider.stat(entry.path) } catch { current = undefined }
|
|
255
|
+
if (entry.afterVersion !== null && current?.version === entry.afterVersion) continue
|
|
256
|
+
if (!entry.writtenBackup || !entry.backup) {
|
|
257
|
+
conflicts.push({ path: entry.path, reason: 'no-merge-base' })
|
|
258
|
+
continue
|
|
259
|
+
}
|
|
260
|
+
let baseBytes
|
|
261
|
+
let oursBytes
|
|
262
|
+
let theirsBytes
|
|
263
|
+
try {
|
|
264
|
+
baseBytes = fs.readFileSync(path.join(dir, entry.backup))
|
|
265
|
+
oursBytes = fs.readFileSync(path.join(dir, entry.writtenBackup))
|
|
266
|
+
theirsBytes = fs.readFileSync(entry.realPath)
|
|
267
|
+
} catch (error) {
|
|
268
|
+
conflicts.push({ path: entry.path, reason: String(error?.message ?? error) })
|
|
269
|
+
continue
|
|
270
|
+
}
|
|
271
|
+
const merged = mergeText(baseBytes, oursBytes, theirsBytes, path.join(dir, 'merge-tmp'))
|
|
272
|
+
if (!merged.ok) {
|
|
273
|
+
conflicts.push({ path: entry.path, reason: merged.reason, ...(merged.conflictText === undefined ? {} : { conflictText: merged.conflictText }), ...(merged.exitCode === undefined ? {} : { exitCode: merged.exitCode }) })
|
|
274
|
+
continue
|
|
275
|
+
}
|
|
276
|
+
plans.push({ entry, theirsBytes, bytes: merged.bytes })
|
|
277
|
+
}
|
|
278
|
+
if (conflicts.length > 0) return { merged: [], conflicts }
|
|
279
|
+
const merged = []
|
|
280
|
+
for (const plan of plans) {
|
|
281
|
+
const { entry } = plan
|
|
282
|
+
const rollbackBackup = path.join('rollback', `${entry.metaIndex}.bin`)
|
|
283
|
+
writeBytesAtomic(path.join(dir, rollbackBackup), plan.theirsBytes, 0o600)
|
|
284
|
+
entry.rollbackBackup = rollbackBackup
|
|
285
|
+
writeBytesAtomic(entry.realPath, plan.bytes, entry.mode ?? 0o600)
|
|
286
|
+
const writtenBackup = path.join('written', `${entry.metaIndex}.bin`)
|
|
287
|
+
writeBytesAtomic(path.join(dir, writtenBackup), plan.bytes, 0o600)
|
|
288
|
+
entry.writtenBackup = writtenBackup
|
|
289
|
+
entry.afterVersion = provider.stat(entry.path)?.version ?? null
|
|
290
|
+
const summary = meta.files[entry.metaIndex]
|
|
291
|
+
if (summary !== undefined) {
|
|
292
|
+
summary.afterVersion = entry.afterVersion
|
|
293
|
+
summary.writtenBackup = entry.writtenBackup
|
|
294
|
+
summary.rollbackBackup = entry.rollbackBackup
|
|
295
|
+
}
|
|
296
|
+
merged.push({ path: entry.path, merged: true })
|
|
297
|
+
}
|
|
298
|
+
if (merged.length > 0) saveMeta(meta)
|
|
299
|
+
return { merged, conflicts: [] }
|
|
300
|
+
},
|
|
190
301
|
/** 交叉调用提交前的乐观校验:事务写过的文件必须仍停在 afterVersion。 */
|
|
191
302
|
assertFresh(provider) {
|
|
192
303
|
const conflicts = []
|
|
@@ -227,7 +338,8 @@ function makeTransaction(root, meta, dir) {
|
|
|
227
338
|
}
|
|
228
339
|
try {
|
|
229
340
|
if (entry.existed) {
|
|
230
|
-
|
|
341
|
+
const backup = entry.rollbackBackup ?? entry.backup
|
|
342
|
+
writeBytesAtomic(entry.realPath, fs.readFileSync(path.join(dir, backup)), entry.mode ?? 0o600)
|
|
231
343
|
} else {
|
|
232
344
|
fs.rmSync(entry.realPath, { force: true })
|
|
233
345
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aura-basetools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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
|
@@ -385,7 +385,7 @@ say('F MCP:initialize / tools/list / tools/call')
|
|
|
385
385
|
})()
|
|
386
386
|
if (manifest !== null) check(byId.get(1)?.result?.serverInfo?.version === manifest.version, `serverInfo.version 跟包一致(${manifest.version})`, String(byId.get(1)?.result?.serverInfo?.version))
|
|
387
387
|
const tools = byId.get(2)?.result?.tools ?? []
|
|
388
|
-
check(tools.length ===
|
|
388
|
+
check(tools.length === 4 && tools.some((t) => t.name === 'aura_basetools_read') && tools.some((t) => t.name === 'aura_basetools_edit') && tools.some((t) => t.name === 'aura_basetools_write') && tools.some((t) => t.name === 'aura_basetools_tx'), `tools/list 四个工具(${tools.map((t) => t.name).join(',')}`)
|
|
389
389
|
const readEnv = JSON.parse(byId.get(3)?.result?.content?.[0]?.text ?? '{}')
|
|
390
390
|
check(readEnv.ok === true && Array.isArray(readEnv.symbols ?? []), 'aura_basetools_read 经 MCP 可用', JSON.stringify(readEnv).slice(0, 120))
|
|
391
391
|
const editEnv = JSON.parse(byId.get(4)?.result?.content?.[0]?.text ?? '{}')
|
package/test/write-test.mjs
CHANGED
|
@@ -210,7 +210,7 @@ say('C MCP:aura_basetools_write 声明面与调用面')
|
|
|
210
210
|
|
|
211
211
|
const tools = byId.get(2)?.result?.tools ?? []
|
|
212
212
|
const writeTool = tools.find((tool) => tool.name === 'aura_basetools_write')
|
|
213
|
-
check(tools.length ===
|
|
213
|
+
check(tools.length === 4 && writeTool !== undefined, `C1 tools/list 四个工具且含 aura_basetools_write(${tools.map((tool) => tool.name).join(',')})`)
|
|
214
214
|
check(JSON.stringify(writeTool?.inputSchema?.required) === JSON.stringify(['file_path', 'content']), 'C2 aura_basetools_write schema 必填 file_path + content', JSON.stringify(writeTool?.inputSchema?.required))
|
|
215
215
|
check(envOf(3).ok === true && envOf(3).result?.operation === 'create', 'C3 经 MCP 新建成功', JSON.stringify(envOf(3)).slice(0, 160))
|
|
216
216
|
check(fs.readFileSync(file('mcp-created.txt'), 'utf8') === 'mcp\ncreate\n', 'C4 MCP 新建的内容真的落盘')
|