@thincoder/core 0.10.2 → 0.10.4
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/CHANGELOG.md +65 -0
- package/advisor/citations.mjs +40 -4
- package/advisor/compaction.mjs +21 -2
- package/advisor/convergence.mjs +1 -1
- package/advisor/history.mjs +8 -6
- package/advisor/loop.mjs +29 -27
- package/advisor/messages.mjs +5 -4
- package/advisor/run.mjs +5 -1
- package/advisor/timeline.mjs +26 -0
- package/advisor.mjs +4 -2
- package/agent/completion.mjs +3 -1
- package/agent/digest-account.mjs +188 -0
- package/agent/dispatch-run.mjs +29 -12
- package/agent/family-tools.mjs +11 -11
- package/agent/helpers.mjs +8 -0
- package/agent/record-results.mjs +10 -1
- package/agent/run-stages.mjs +27 -4
- package/agent/run-start.mjs +49 -3
- package/agent/spawn-child.mjs +2 -1
- package/agent/subagent-event.mjs +71 -0
- package/agent/suspension.mjs +60 -12
- package/agent/turn-loop.mjs +31 -2
- package/agent/write-gate.mjs +13 -25
- package/agent-tools/advisor-async.mjs +25 -7
- package/agent-tools/advisor-settle.mjs +7 -9
- package/agent-tools/advisor.mjs +34 -33
- package/agent-tools/async-settle.mjs +29 -5
- package/agent-tools/batch-lifecycle.mjs +29 -7
- package/agent-tools/batch-paths.mjs +22 -2
- package/agent-tools/batch-read.mjs +36 -0
- package/agent-tools/batch-skeleton.mjs +59 -33
- package/agent-tools/batch.mjs +16 -6
- package/agent-tools/design-token.mjs +76 -16
- package/agent-tools/escalate-async.mjs +4 -2
- package/agent-tools/review-facts.mjs +143 -16
- package/agent-tools/subagent-actions-query.mjs +13 -6
- package/agent-tools/subagent-async.mjs +16 -4
- package/agent-tools/subagent-panel.mjs +121 -37
- package/agent-tools/subagent-spawn.mjs +1 -1
- package/agent-tools/subagent.mjs +5 -3
- package/agent-tools/verify.mjs +5 -0
- package/agent-tools/zero-write-watch.mjs +77 -0
- package/agent.mjs +1 -1
- package/auto-think.mjs +26 -2
- package/config-presets.mjs +8 -0
- package/config.mjs +30 -0
- package/conventions.mjs +26 -6
- package/declaration.mjs +26 -2
- package/embedding.mjs +35 -3
- package/escape.mjs +1 -1
- package/i18n.mjs +5 -0
- package/ledger-cmd.mjs +13 -21
- package/ledger-db.mjs +34 -1
- package/ledger-migrate.mjs +4 -3
- package/ledger-read.mjs +94 -0
- package/ledger-surface.mjs +15 -46
- package/ledger-tools.mjs +148 -85
- package/ledger-variant-notice.mjs +47 -0
- package/ledger.mjs +38 -64
- package/manifest-schema.mjs +19 -7
- package/manifest.mjs +32 -11
- package/mcp/transport-http.mjs +52 -4
- package/mcp/transport-stdio.mjs +33 -8
- package/mcp.mjs +13 -5
- package/memory/code-search.mjs +5 -3
- package/memory/core.mjs +6 -4
- package/memory/docs.mjs +5 -3
- package/memory/schema.mjs +2 -0
- package/package.json +1 -1
- package/prompts/advisor-design.md +3 -2
- package/prompts/advisor-round1.md +1 -0
- package/prompts/advisor-round2.md +8 -3
- package/prompts/advisor-round3.md +7 -2
- package/prompts/common.md +2 -2
- package/prompts/discipline-normal.md +2 -1
- package/prompts/persona-engineering.md +7 -4
- package/provider/anthropic.mjs +14 -2
- package/provider/core.mjs +10 -4
- package/provider/errors.mjs +23 -0
- package/provider/google.mjs +31 -4
- package/provider/normalize.mjs +57 -0
- package/provider/responses-request.mjs +3 -1
- package/provider/responses.mjs +38 -4
- package/provider/sse.mjs +45 -13
- package/proxy.mjs +13 -3
- package/session.mjs +13 -2
- package/shell-identity.mjs +107 -0
- package/stream-destroy.mjs +62 -0
- package/tool-docs/advisor.md +1 -1
- package/tool-docs/bash.md +1 -1
- package/tool-docs/batch.md +1 -1
- package/tool-docs/delete.md +7 -5
- package/tool-docs/ledger.md +6 -0
- package/tool-docs/subagent.md +10 -0
- package/tool-docs/verify.md +1 -1
- package/tools/bash.mjs +9 -4
- package/tools/edit-batch.mjs +7 -2
- package/tools/index.mjs +1 -4
- package/tools/patch.mjs +8 -2
- package/tools/write-path.mjs +6 -4
- package/undo-stack.mjs +23 -9
- package/tool-docs/ledger_add.md +0 -1
- package/tool-docs/ledger_close.md +0 -1
- package/tool-docs/ledger_count.md +0 -1
- package/tool-docs/ledger_query.md +0 -1
- package/tool-docs/ledger_update.md +0 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* shell-identity.mjs — bash 工具**执行器身份**探测单源(bash-executor-face 批 · 台账 #922——
|
|
3
|
+
* 设计权威 = `docs/core/design/BASH-EXECUTOR-FACE.md` §2 契约 + §3.1)。
|
|
4
|
+
*
|
|
5
|
+
* 探测源 = **spawn 同链镜像**(`tools/bash.mjs` spawn 的 `shell` 参数计算式):
|
|
6
|
+
* config.shell(trim 后非空即用)→ win32 %COMSPEC% → 兜底字面 `cmd.exe`;
|
|
7
|
+
* 非 win32 = `process.env.SHELL` → 兜底 `/bin/sh`(Node `shell:true` 兜底的事实陈述,非平台推断)。
|
|
8
|
+
* 探测值 = 实际执行器(不是环境快照);探测不到(识别表未命中知名字)⇒ `unknown` 中性句
|
|
9
|
+
* (fail-open 零猜测——用户 2026-10-04 22:48 裁定③);**零静态平台默认**(裁定①)。
|
|
10
|
+
*
|
|
11
|
+
* memo(进程生命周期 + 按配置值键控)先例 = `shell-candidates.mjs`(「shell 路径不热变化」同判据);
|
|
12
|
+
* 配置值变化 ⇒ memo 未命中重探(K2 配置变更刷新 ∥ 裁定①零静态同时成立)。
|
|
13
|
+
* 测试缝 `_setComSpecForTest` 先例 = `shell-candidates.mjs:34` `_setShellDetectForTest`。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// 识别表(raw 尾段词 → 显示名 + 语法族)。匹配大小写不敏感,尾段词先剥 `.exe` 后缀(U3——
|
|
17
|
+
// `bash.exe` ∥ `cmd.exe` ∥ `pwsh.exe` 均须命中);序 = 最特异优先:
|
|
18
|
+
// `pwsh` 先于 `powershell`;`sh` 排末(防 `fish` 尾段误命中——fish 自目在前)。
|
|
19
|
+
const IDENTITY_TABLE = Object.freeze([
|
|
20
|
+
{ word: "pwsh", name: "PowerShell (pwsh)", kind: "powershell" },
|
|
21
|
+
{ word: "powershell", name: "Windows PowerShell", kind: "powershell" },
|
|
22
|
+
{ word: "cmd", name: "cmd.exe", kind: "cmd" },
|
|
23
|
+
{ word: "bash", name: "bash", kind: "posix" },
|
|
24
|
+
{ word: "wsl", name: "wsl", kind: "posix" },
|
|
25
|
+
{ word: "fish", name: "fish", kind: "posix" },
|
|
26
|
+
{ word: "zsh", name: "zsh", kind: "posix" },
|
|
27
|
+
{ word: "sh", name: "sh", kind: "posix" },
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
/** 尾段词:raw 的最后一段非空词,剥尾 `.exe`(`C:\\...\\bash.exe` → `bash`;`pwsh` → `pwsh`)。 */
|
|
31
|
+
function lastWord(raw) {
|
|
32
|
+
const parts = String(raw).trim().split(/[\\/:]+/).filter(Boolean)
|
|
33
|
+
const tail = parts[parts.length - 1] ?? ""
|
|
34
|
+
return tail.toLowerCase().replace(/\.exe$/i, "")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 探测执行器身份。`configShell` = spawn 侧同一取值(`ctx.agent?.config?.shell ?? null`)。
|
|
39
|
+
* 纯函数(不读环境外的状态;win32 分支内读 `%COMSPEC%`);memo 按配置值键控。
|
|
40
|
+
* @param {string|null|undefined} configShell
|
|
41
|
+
* @returns {{ raw: string, name: string, kind: "cmd"|"powershell"|"posix"|"unknown", known: boolean }}
|
|
42
|
+
*/
|
|
43
|
+
export function resolveShellIdentity(configShell) {
|
|
44
|
+
const key = configShell == null ? null : String(configShell)
|
|
45
|
+
if (_memo.key === key) return _memo.value
|
|
46
|
+
|
|
47
|
+
// 链 = spawn 侧同式:config.shell trim 后非空即用 → 空/缺省时 win32 读 %COMSPEC%(兜底字面
|
|
48
|
+
// cmd.exe)∥ 非 win32 读 SHELL(兜底 /bin/sh)——探测值 = 实际执行器。
|
|
49
|
+
const trimmed = key == null ? "" : key.trim()
|
|
50
|
+
let raw
|
|
51
|
+
if (trimmed) {
|
|
52
|
+
raw = trimmed
|
|
53
|
+
} else if (process.platform === "win32") {
|
|
54
|
+
const comspec = _comSpecOverride !== null ? _comSpecOverride : process.env.ComSpec
|
|
55
|
+
raw = comspec && comspec.trim() ? comspec : "cmd.exe"
|
|
56
|
+
} else {
|
|
57
|
+
const shell = process.env.SHELL
|
|
58
|
+
raw = shell && shell.trim() ? shell : "/bin/sh"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const lw = lastWord(raw).toLowerCase()
|
|
62
|
+
// pwsh 分流总则(设计档 §2):powershell 族值面按 raw 尾段词命中 /pwsh/i 分流——
|
|
63
|
+
// 命中 ⇒ pwsh 形;未命中 ⇒ Windows PowerShell(5.x)形。kind 枚举不动(识别表单目)。
|
|
64
|
+
const pwshForm = /pwsh/i.test(lw)
|
|
65
|
+
const hit =
|
|
66
|
+
IDENTITY_TABLE.find((e) => lw === e.word) ??
|
|
67
|
+
(pwshForm ? null : IDENTITY_TABLE.find((e) => lw.endsWith(e.word)))
|
|
68
|
+
const value = hit
|
|
69
|
+
? { raw, name: hit.name, kind: hit.kind, known: true }
|
|
70
|
+
: { raw, name: raw, kind: "unknown", known: false }
|
|
71
|
+
_memo.key = key
|
|
72
|
+
_memo.value = value
|
|
73
|
+
return value
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** memo(进程生命周期 ∕ 按配置值键控——本档单点持有)。 */
|
|
77
|
+
const _memo = { key: undefined, value: null }
|
|
78
|
+
|
|
79
|
+
/** 测试缝:注入 %COMSPEC% 读数(undefined = 复位真实环境读数);注入即清 memo。 */
|
|
80
|
+
let _comSpecOverride = null
|
|
81
|
+
export function _setComSpecForTest(value) {
|
|
82
|
+
_comSpecOverride = value === undefined ? null : value
|
|
83
|
+
_memo.key = undefined
|
|
84
|
+
_memo.value = null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* kind ⇒ 注入值面(一行 Executor 声明 · 设计档 §2 值面表逐字)。powershell 族按 raw 尾段词
|
|
89
|
+
* `/pwsh/i` 分流(pwsh 形 ∥ Windows PowerShell 5.x 形);identity.kind 为 unknown ⇒ 中性句。
|
|
90
|
+
* @param {{ raw: string, name: string, kind: string, known: boolean }} identity resolveShellIdentity 出参
|
|
91
|
+
* @returns {string}
|
|
92
|
+
*/
|
|
93
|
+
export function executorLine(identity) {
|
|
94
|
+
const { raw, name, kind } = identity
|
|
95
|
+
switch (kind) {
|
|
96
|
+
case "cmd":
|
|
97
|
+
return "- Executor: the bash tool runs commands through Windows cmd.exe (%COMSPEC%). Use cmd syntax: && works; NUL not /dev/null; %VAR% not $VAR; no single-quote grouping. For complex logic prefer the execute tool (node)."
|
|
98
|
+
case "powershell":
|
|
99
|
+
return /pwsh/i.test(lastWord(raw))
|
|
100
|
+
? `- Executor: the bash tool runs commands through PowerShell (pwsh; ${raw}). && and || work; %VAR% does not expand (use $VAR); cmdlets differ (Remove-Item, Copy-Item). For complex logic prefer the execute tool (node).`
|
|
101
|
+
: `- Executor: the bash tool runs commands through Windows PowerShell (${raw}). PS 5.1 has NO &&/|| (use ; or separate calls); redirection differs (2>/dev/null is invalid); cmdlets differ (Remove-Item, Copy-Item). For complex logic prefer the execute tool (node).`
|
|
102
|
+
case "posix":
|
|
103
|
+
return `- Executor: the bash tool runs commands through a POSIX shell (${raw}). Standard POSIX syntax applies ($(...), $VAR, ;, >/dev/null).`
|
|
104
|
+
default:
|
|
105
|
+
return `- Executor: the bash tool shell could not be identified (raw: ${raw}) — write portable commands and trust the command's own error output.`
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stream-destroy.mjs — response body 终止守卫(单一权威源——PROXY.md §2 ∥ §7 D-PX7)
|
|
3
|
+
*
|
|
4
|
+
* 无监听者瞬间的 `destroy(err)` 会 emit 未处理 'error' ⇒ uncaughtException ⇒ 整个进程被杀
|
|
5
|
+
* (2026-09-22/23 三份 crash-report 签名 = `Response body timeout (idle)`,栈 = proxy.mjs
|
|
6
|
+
* 看门狗;GitHub #16)。`destroyBody(body, err)` 在终止前先挂**永久** no-op `'error'`
|
|
7
|
+
* 监听者——pipe 内部监听者触发即自摘后的重发同样被兜住;消费面语义零变(在场 ∥ 迟到
|
|
8
|
+
* `for-await` 消费者仍收原错误;`stream.errored` 保留原错误对象)。
|
|
9
|
+
*
|
|
10
|
+
* 契约四则(PROXY.md §2):
|
|
11
|
+
* ① body 空 ∥ 非 destroy 形态(web `ReadableStream`)∥ 已 `destroyed` ⇒ 显式返回(no-op);
|
|
12
|
+
* ② 首次调用挂永久 no-op 'error' 监听者(一次性标记防重复挂);
|
|
13
|
+
* ③ 再 `destroy(err)`——原错误对象直传;
|
|
14
|
+
* ④ 禁 `listenerCount('error') === 0` 预检(pipe 监听者使计数失真——触发即自摘后重发)。
|
|
15
|
+
*
|
|
16
|
+
* 内部 abort 通道(2026-10-04 · #878 D-PX8):直连 fetch 的 body = web `ReadableStream`(无
|
|
17
|
+
* `destroy`——本档形态①的 no-op 面)。断流改经 `IDLE_ABORT` 符号挂载的 `AbortController`:
|
|
18
|
+
* `terminateBody(response, err)` 对挂通道者 `controller.abort(err)`(body 随之中止);无通道回落
|
|
19
|
+
* `destroyBody`(原契约零变)。建设点 = `proxy.mjs` `proxyFetch` 直连分支单点。
|
|
20
|
+
*
|
|
21
|
+
* 零 import——proxy ∥ provider 消费点共用(无环)。
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** 一次性标记(模块私有 Symbol——不占 body 公开面)。 */
|
|
25
|
+
const GUARDED = Symbol("thincoder.destroyBody.guarded")
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 终止 body,且未处理 'error' 不逃逸到进程(契约四则见档头)。
|
|
29
|
+
* @param {import("node:stream").Stream} body — response body(PassThrough);
|
|
30
|
+
* web `ReadableStream`(无 destroy)⇒ 显式 no-op
|
|
31
|
+
* @param {Error} err — 原错误对象(直传 destroy)
|
|
32
|
+
*/
|
|
33
|
+
export function destroyBody(body, err) {
|
|
34
|
+
if (!body || typeof body.destroy !== "function" || body.destroyed) return
|
|
35
|
+
if (!body[GUARDED]) {
|
|
36
|
+
body[GUARDED] = true
|
|
37
|
+
body.on("error", () => {})
|
|
38
|
+
}
|
|
39
|
+
body.destroy(err)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 内部 abort 通道符号(#878 D-PX8——直连 fetch 响应的断流通道;module-private 语义面)。 */
|
|
43
|
+
export const IDLE_ABORT = Symbol("thincoder.idleAbort")
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* body 终止分流单点(#878):
|
|
47
|
+
* - response 挂有 `IDLE_ABORT`(直连 fetch——web `ReadableStream`)⇒ `controller.abort(err)`
|
|
48
|
+
* (读循环随之终结;原错误对象即 abort reason——undici 以该对象拒读);
|
|
49
|
+
* - 否则回落 `destroyBody(response.body, err)`(proxy PassThrough ∥ 无通道 web 流——原契约零变)。
|
|
50
|
+
* response 缺位 / 无 body ⇒ no-op 不抛(幂等——二次调用对 AbortController 原生安全)。
|
|
51
|
+
* @param {Response|{body?: import("node:stream").Stream}} response
|
|
52
|
+
* @param {Error} err — 原错误对象(abort reason ∥ destroy 直传)
|
|
53
|
+
*/
|
|
54
|
+
export function terminateBody(response, err) {
|
|
55
|
+
if (!response) return
|
|
56
|
+
const controller = response[IDLE_ABORT]
|
|
57
|
+
if (controller) {
|
|
58
|
+
controller.abort(err)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
destroyBody(response.body, err)
|
|
62
|
+
}
|
package/tool-docs/advisor.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Run an independent review of your work. `type` is REQUIRED — one of exactly two values: 'design' reviews design / requirement documents BEFORE implementation (documents=[...] with the explicit doc paths; plus batchDoc when a batch record is in flight); 'code' reviews the code you changed AFTER implementation (paths=[...] to scope files/directories; documents=[...] adds acceptance-criteria context). A call without a type (or with a conflicting object.type) is refused — no default, no silent fallback.
|
|
1
|
+
Run an independent review of your work. `type` is REQUIRED — one of exactly two values: 'design' reviews design / requirement documents BEFORE implementation (documents=[...] with the explicit doc paths — relative paths resolve against the session cwd first, then candidate project roots (project-root-relative e.g. <repo>/docs/…, or absolute; relative forms are also tried against the session cwd's ancestor directories); plus batchDoc when a batch record is in flight); 'code' reviews the code you changed AFTER implementation (paths=[...] to scope files/directories; documents=[...] adds acceptance-criteria context). A call without a type (or with a conflicting object.type) is refused — no default, no silent fallback.
|
|
2
2
|
|
|
3
3
|
The advisor is an independent read-only sub-agent (explores the codebase, reads files, traces callers via grep/lsp). Code review converges: round 1 full review → round 2 verifies the agent's fix claims → round 3+ re-checks only the fix claims. Design review is a single pass against methodology and requirements. Criteria come from .thincoder/advisor.md (if present) or sensible defaults. After the review you MUST produce a response table (format = discipline rules); on all-clear, call verify.
|
|
4
4
|
|
package/tool-docs/bash.md
CHANGED
|
@@ -30,7 +30,7 @@ Notes:
|
|
|
30
30
|
- The environment sets GIT_PAGER=cat, PAGER=cat, EDITOR=true, TERM=dumb — but still always use non-interactive flags
|
|
31
31
|
- Output is capped at ~200K chars; if you need more, redirect to a file and read it. Truncated output ends with a `[... truncated: N chars omitted]` marker — the missing tail may contain errors, so read the saved log file's tail before trusting success. Use `filter` to narrow instead of hand-piping.
|
|
32
32
|
- Check `[stderr]` for error messages, warnings, and diagnostic output — it is separated from `[stdout]` so you can quickly identify problems.
|
|
33
|
-
-
|
|
33
|
+
- Shell identity: the bash tool's shell is NOT fixed — it depends on configuration and environment. The current executor is declared in the Executor line above; write commands for THAT shell, and when a command fails read its error output to identify the actual shell.
|
|
34
34
|
- Never use bash to read, copy, or transmit secret files (.env, keys, tokens)
|
|
35
35
|
- Do NOT run destructive commands (rm -rf, force-push, drop table) without explicit user confirmation
|
|
36
36
|
- After commands that change files (git checkout, npm install, etc.), repo_outline and code_search may be stale — re-run them to get current results.
|
package/tool-docs/batch.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Batch-record lifecycle tool (一段一作者 — six append-only sections, one author each). actions: create (depth-0 only — write a new six-section skeleton record; pass path + topic + source) · append (add text to the END of YOUR section — existing lines are never rewritten or deleted) · status (update YOUR section's `**状态行**:` line — the value must carry exactly ONE legal keyword of your section's STATUS_WORDS entry, decorated only by surrounding symbols / a trailing ISO date / whitespace; prose goes to the separate `note` field, which lands as a parenthetical) · close (depth-0 only — freeze the record: §1 → 「已收口 <date>」, every further write is refused). Identity fixes what you may write (段白名单): main agent (depth 0) — append §1/§4/§6, status §1 only, create/close; eng-designer — §2; eng-coder — §5; design review (review binding) — §3. A write outside your own section is refused. Skeleton placeholders (dead literals — `#<编号>` / `<板块>` and the older `<BATCH-ID>` / `<讨论来源>`) in the record header
|
|
1
|
+
Batch-record lifecycle tool (一段一作者 — six append-only sections, one author each). actions: create (depth-0 only — write a new six-section skeleton record; pass path + topic + source, plus optional ledger / board that fill the record's 台账 header line at creation) · append (add text to the END of YOUR section — existing lines are never rewritten or deleted) · status (update YOUR section's `**状态行**:` line — the value must carry exactly ONE legal keyword of your section's STATUS_WORDS entry, decorated only by surrounding symbols / a trailing ISO date / whitespace; prose goes to the separate `note` field, which lands as a parenthetical) · close (depth-0 only — freeze the record: §1 → 「已收口 <date>」, every further write is refused). Identity fixes what you may write (段白名单): main agent (depth 0) — append §1/§4/§6, status §1 only, create/close; eng-designer — §2; eng-coder — §5; design review (review binding) — §3. A write outside your own section is refused. Skeleton placeholders (dead literals — `#<编号>` / `<板块>` and the older `<BATCH-ID>` / `<讨论来源>`) in the record header's structural lines block append/status until filled: create with ledger / board fills them at creation; if omitted, the main agent fills the header by file edit right after create (citing these literals in a section's prose is fine — only the header is checked); `<§N 模板占位:…>` template lines are legal and never blocked. Target record: sub-agents and design reviews have NO path parameter in practice — the record arrives via the spawn binding / the review instance key (passing path is refused); depth-0 MAY pass path (若传则须可读), omitting it picks the unique in-flight record (0 or ≥2 in flight ⇒ refuse — pass path). A design review's append is stamped by the tool with a `### 轮次 N(评审子代理)` heading — N is tool-counted; do not write your own heading (it would be dropped). Credential values are stripped mechanically before writing (never write a token or designId value). Failures are hard and visible (no silent fallback): if a write is refused or fails, say so in your report — “§× 未写入”.
|
package/tool-docs/delete.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
Delete a file. Use when the agent created a temporary or junk file that should be cleaned up, or when the user explicitly asks to delete something.
|
|
1
|
+
Delete a file, or an empty directory. Use when the agent created a temporary or junk file or empty directory that should be cleaned up, or when the user explicitly asks to delete something.
|
|
2
|
+
Refuses to delete git-tracked files as a safety measure — tracked files should be edited or removed via bash with explicit user confirmation.
|
|
2
3
|
|
|
3
|
-
**Route to delete instead of bash:** `del file` / `rm file` → delete (single files)
|
|
4
|
+
**Route to delete instead of bash:** `del file` / `rm file` → delete (single files); an empty directory → delete (empty directories only). Non-empty directories have no delete path — stop and report to the user instead of hand-rolling a recursive shell delete.
|
|
4
5
|
|
|
5
6
|
Parameters:
|
|
6
|
-
- path (required): File path, relative to cwd or absolute
|
|
7
|
+
- path (required): File or empty-directory path, relative to cwd or absolute
|
|
7
8
|
- force: Allow deleting git-tracked files (default false)
|
|
8
9
|
|
|
9
10
|
Notes:
|
|
10
11
|
- Untracked or non-git files are deleted immediately
|
|
11
12
|
- Tracked files require force=true (user must confirm separately)
|
|
12
|
-
- Directories
|
|
13
|
-
-
|
|
13
|
+
- Directories: empty ones only — a non-empty directory is refused (a natural fuse; nothing recursive is ever removed); delete nested empty trees bottom-up
|
|
14
|
+
- Symbolic links are removed as links — the link itself is deleted, never followed (its target is untouched)
|
|
15
|
+
- Returns `Deleted <path>` (or `Error: ...` on failure / tracked / non-empty refusal).
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
台账统一入口——action = add / update / close / query / count。query / count 只读全角色;add / update / close 写仅主 agent。
|
|
2
|
+
- add:新增条目入待讨论。kind = requirement(需求池)/ tech_todo(技术待办);trigger = 归批 / 条件 / 认账不排期(可空)。
|
|
3
|
+
- update:按六态允许表迁移:待讨论→待设计 / 待设计→在途 / 在途→待核销 / 待核销→已核销 / 任意态→已废弃;表外拒。status 缺省 = 仅更新字段。
|
|
4
|
+
- close:勾销(待核销 → 已核销);追认核销(待讨论 / 待设计 → 已核销,须有 evidence);在途 → 已核销 不可跳;撤回(任意态 → 已废弃)。软删除(写 closed_at,行保留)。
|
|
5
|
+
- query:SQLite 行集,过滤缺省 = 全部行。六态 = 待讨论 / 待设计 / 在途 / 待核销 / 已核销 / 已废弃。
|
|
6
|
+
- count:未决四态(待讨论 / 待设计 / 在途 / 待核销)COUNT(*)。
|
package/tool-docs/subagent.md
CHANGED
|
@@ -18,6 +18,16 @@ Available roles (exposed per mode; the role is REQUIRED for action:'spawn' — a
|
|
|
18
18
|
- eng-coder — engineering mode only (replaces coder): same toolset plus the design-driven methodology overlay; its delivery protocol (implementation → audit → self-fix → advisor re-review → converged delivery) runs fully inside the child. REQUIRES a valid designToken from a passed advisor(type='design') review (its reply also echoes a designId — pass it: required to pick between concurrent designs, optional for one; echo it back in the delivery report). REQUIRES a batchDoc — the batch record path, the task book this spawn implements (refused without it or when it does not resolve to a readable file).
|
|
19
19
|
- eng-designer — engineering mode only: the SOLE author of the requirements/design documents and of the batch record's design section. Writes no implementation code, does not edit prompts, does not fire reviews, needs NO designToken. Surveys on its own; may only spawn read-only explore children (sync, ≤6 per batch). REQUIRES a batchDoc (same gate as eng-coder).
|
|
20
20
|
|
|
21
|
+
Task book (eng-coder / eng-designer spawns — six mandatory fields): 目标与理由 (goal & why), round (initial|fix — the structured parameter), 已知事实 (known facts), 设计要点与禁止范围 (design points & forbidden scope), 验收标准 (acceptance criteria), 交付报告格式 (delivery-report format). Keep the labels (Chinese or the English equivalents) — the gate matches these markers; a missing field is a dispatch defect and the spawn is refused.
|
|
22
|
+
|
|
23
|
+
Copy this skeleton verbatim into every task book — fill the blanks, do not relabel the six lines:
|
|
24
|
+
**目标与理由**:…
|
|
25
|
+
**round**:initial | fix(此字段=结构化参数——spawn 时同时传 `round`)
|
|
26
|
+
**已知事实**:…
|
|
27
|
+
**设计要点与禁止范围**:…
|
|
28
|
+
**验收标准**:…
|
|
29
|
+
**交付报告格式**:…
|
|
30
|
+
|
|
21
31
|
Mode filtering: normal mode exposes explore/plan/coder; engineering mode exposes explore/eng-designer/eng-coder — off-mode roles are refused mechanically (enums filtered at assembly; the spawn gate re-checks).
|
|
22
32
|
|
|
23
33
|
Async spawn: top-level spawns are async by default — never pass async:false at depth 0 (the spawn returns {id, status:"running"} immediately; the report arrives automatically; if your next step needs it, end the turn and let the digest deliver it — do not poll; the explicit async:false is a mechanism parameter, not for model use). Inside subagents (depth>0) spawns are always synchronous. Async spawns are pooled per role domain — 4 concurrent eng-coders plus 4 concurrent other-role spawns by default (agent.poolLimits overrides both); a full domain queues further spawns with a position while the other keeps starting.
|
package/tool-docs/verify.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Generic pre-completion verification gate — language/framework/project agnostic. verify does NOT run any test/verification command for you and does NOT require a test per change. You DECLARE your verification status via the verification argument ({ status: 'passed' | 'failed' | 'skipped', command?, summary? } — effects per parameter): the declaration is self-reported evidence — verify only mechanically gates on it ('passed' → allowed; 'failed' → blocked; 'skipped' → allowed but needs a reason). Whether/how to verify is described in natural language in the project's AGENTS.md and is decided + executed by you. The report also carries the changed files (git diff), an advisory node --check hint on changed .js/.mjs (not gating), the task list, and a self-review checklist. Call this BEFORE declaring a coding task complete — do not say 'done' until verify passes.
|
|
1
|
+
Generic pre-completion verification gate — language/framework/project agnostic. verify does NOT run any test/verification command for you and does NOT require a test per change. You DECLARE your verification status via the verification argument ({ status: 'passed' | 'failed' | 'skipped', command?, summary?, basis? } — effects per parameter): the declaration is self-reported evidence — verify only mechanically gates on it ('passed' → allowed; 'failed' → blocked; 'skipped' → allowed but needs a reason). 'basis' (optional) is the 'what it stands on' dimension of the declaration — a judgment source, a read file:line coordinate, or an explicit 'unverified' (declaration-only: no format is enforced or machine-checked); a passed / skipped declaration with no basis gets one advisory hint line in the report — never blocking and never changing the gate — while the failed face gets no basis hint (already blocked). Whether/how to verify is described in natural language in the project's AGENTS.md and is decided + executed by you. The report also carries the changed files (git diff), an advisory node --check hint on changed .js/.mjs (not gating), the task list, and a self-review checklist. Call this BEFORE declaring a coding task complete — do not say 'done' until verify passes.
|
package/tools/bash.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { spawn } from "node:child_process";
|
|
|
9
9
|
// 树杀单源(台账 #208② · TOOLS.md §6.14 落位表行 2):本地副本删除——改用 `./process-tree.mjs`
|
|
10
10
|
// (行为逐字同:win32 `taskkill /T /F` · POSIX 组杀 + 直杀兜底)。
|
|
11
11
|
import { killProcessTree } from "./process-tree.mjs";
|
|
12
|
+
import { resolveShellIdentity } from "../shell-identity.mjs";
|
|
12
13
|
|
|
13
14
|
/** Maximum buffer size per stream (stdout / stderr) before truncation */
|
|
14
15
|
const MAX_STREAM_BUF = 2_000_000
|
|
@@ -24,8 +25,10 @@ function applyLineFilter(output, filter) {
|
|
|
24
25
|
|
|
25
26
|
/** POSIX-only constructs that cmd.exe reads literally (and thus breaks). Detected
|
|
26
27
|
* so the agent is told IMMEDIATELY instead of chasing a confusing failure — warning
|
|
27
|
-
* only, never a block (the approval layer is the real gate, same as destructive-command policy).
|
|
28
|
-
|
|
28
|
+
* only, never a block (the approval layer is the real gate, same as destructive-command policy).
|
|
29
|
+
* 身份门(bash-executor-face 批):kind === cmd 才产出——PS/POSIX 执行器不再喊 cmd.exe
|
|
30
|
+
* (态 4 实锤:hint 无身份门 = 同族缺陷);提示语引用探测名(非死写)。 */
|
|
31
|
+
function posixSyntaxHint(command, shellName) {
|
|
29
32
|
const hits = []
|
|
30
33
|
if (/\$\([^)]*\)/.test(command)) hits.push("$(...)")
|
|
31
34
|
if (command.includes("`")) hits.push("backtick")
|
|
@@ -34,7 +37,7 @@ function posixSyntaxHint(command) {
|
|
|
34
37
|
if (/'.*'/.test(command)) hits.push("single quotes (cmd.exe doesn't group)")
|
|
35
38
|
if (/\$\{[A-Za-z_]/.test(command)) hits.push("${VAR} (use %VAR%)")
|
|
36
39
|
if (!hits.length) return ""
|
|
37
|
-
return "[hint: POSIX-only construct(s) detected — " + hits.join(", ") + ". Current shell is
|
|
40
|
+
return "[hint: POSIX-only construct(s) detected — " + hits.join(", ") + ". Current shell is " + shellName + "; these will NOT work. Use && / NUL / %VAR%, or use the execute tool (node) for complex logic]"
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
|
|
@@ -256,7 +259,9 @@ export const bashTool = {
|
|
|
256
259
|
// the wide matcher also covers variants like `git checkout HEAD -- .`).
|
|
257
260
|
// 前置两件(guard ∥ hint)同步 ∥ 后台两径同保留(#9)。
|
|
258
261
|
const guard = await gitGuardSnapshot(args.command, ctx.cwd)
|
|
259
|
-
|
|
262
|
+
// hint 身份门:只有 cmd 执行器产出 POSIX-only 提示(PS/POSIX 下该提示误导——态 4)。
|
|
263
|
+
const identity = resolveShellIdentity(ctx.agent?.config?.shell ?? null)
|
|
264
|
+
const hint = identity.kind === "cmd" ? posixSyntaxHint(args.command, identity.name) : ""
|
|
260
265
|
if (args.async === true) {
|
|
261
266
|
// depth 第二道(schema 主门 = agent/helpers.mjs excludeSubagentTools 删参——异步面只到
|
|
262
267
|
// 子代理实装亦拒:留一条显式文案,不静默转同步)。
|
package/tools/edit-batch.mjs
CHANGED
|
@@ -23,7 +23,8 @@ import { assertEditArgsExclusive, assertEditsContainer, assertEditEntries, valid
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Apply the `edits` array form: multi-file atomic replacement. Throws on any
|
|
26
|
-
* failure (atomic — nothing written). Returns the per-entry result text (joined)
|
|
26
|
+
* failure (atomic — nothing written). Returns the per-entry result text (joined)
|
|
27
|
+
* + the constant batch summary line at the end (#796 — EDIT.md §5).
|
|
27
28
|
*/
|
|
28
29
|
export async function applyEditBatch(args, ctx) {
|
|
29
30
|
assertEditsContainer(args.edits)
|
|
@@ -105,7 +106,11 @@ export async function applyEditBatch(args, ctx) {
|
|
|
105
106
|
note: p.note,
|
|
106
107
|
}))
|
|
107
108
|
}
|
|
108
|
-
|
|
109
|
+
// #796(核面小修批——EDIT.md §5 批量回执汇总行 ∥ §8 D-9):末行**恒为**汇总行——大回执
|
|
110
|
+
// (>64K)落盘后双端 preview 保尾(TOOL-OUTPUT-LIMITS.md §2)⇒ 汇总结论恒可见;N = 条目数
|
|
111
|
+
// (全判后原子写 ⇒ 成功恒 N/N);路径 = 条目原样形、去重保序首现序(groups 插入序即首现序)。
|
|
112
|
+
const summary = `edit batch: ${args.edits.length}/${args.edits.length} entries — ${[...groups.values()].map((g) => g.path).join(", ")}`
|
|
113
|
+
return [...results, summary].join("\n")
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
// ---------------------------------------------------------------------------
|
package/tools/index.mjs
CHANGED
|
@@ -60,7 +60,6 @@ export async function assembleBuiltinTools({ memory, cwd, projectDir = null, aut
|
|
|
60
60
|
const { repoOutlineTool } = await import("./repomap.mjs");
|
|
61
61
|
const { settingsTool } = await import("../agent-tools/settings.mjs");
|
|
62
62
|
const { peerInstancesTool } = await import("../peer-instances.mjs");
|
|
63
|
-
const { ledgerQueryTool, ledgerCountTool } = await import("../ledger.mjs"); // 动态 import(ledger 链静态达 node:sqlite——W8 契约②)
|
|
64
63
|
const imageOk = Boolean(specForModel(model)?.multimodal);
|
|
65
64
|
return [
|
|
66
65
|
...builtinTools,
|
|
@@ -72,8 +71,6 @@ export async function assembleBuiltinTools({ memory, cwd, projectDir = null, aut
|
|
|
72
71
|
repoOutlineTool(memory?.db, cwd),
|
|
73
72
|
settingsTool(),
|
|
74
73
|
peerInstancesTool,
|
|
75
|
-
//
|
|
76
|
-
ledgerQueryTool,
|
|
77
|
-
ledgerCountTool,
|
|
74
|
+
// 台账工具面(统一入口 `ledger`——含读二)随家族段 `agent/family-tools.mjs`(2026-10-05 统一入口批:本基集清零)
|
|
78
75
|
];
|
|
79
76
|
}
|
package/tools/patch.mjs
CHANGED
|
@@ -254,7 +254,7 @@ export const deleteTool = {
|
|
|
254
254
|
parameters: {
|
|
255
255
|
type: "object",
|
|
256
256
|
properties: {
|
|
257
|
-
path: { type: "string", description: "File path (relative to cwd or absolute)" },
|
|
257
|
+
path: { type: "string", description: "File or empty-directory path (relative to cwd or absolute)" },
|
|
258
258
|
force: { type: "boolean", description: "Allow deleting git-tracked files (default false)" },
|
|
259
259
|
},
|
|
260
260
|
required: ["path"],
|
|
@@ -269,7 +269,13 @@ export const deleteTool = {
|
|
|
269
269
|
} catch {
|
|
270
270
|
throw new Error(`File not found: ${args.path}`)
|
|
271
271
|
}
|
|
272
|
-
if (s.isDirectory())
|
|
272
|
+
if (s.isDirectory()) {
|
|
273
|
+
try { await writeThroughPath(abs, null, { op: "rmdir" }) } catch (e) {
|
|
274
|
+
if (e?.code !== "ENOTEMPTY" && e?.code !== "EEXIST") throw e
|
|
275
|
+
throw new Error(`"${args.path}" is a directory and is not empty — only empty directories can be deleted (nothing recursive is ever removed). Remove the contents first (delete bottom-up for nested empty directories).`)
|
|
276
|
+
}
|
|
277
|
+
return `Deleted ${args.path}`
|
|
278
|
+
}
|
|
273
279
|
// git-tracked files: refuse direct deletion (safety net); untracked: allow
|
|
274
280
|
// Use resolved relative path (normalized forward slashes) to prevent backslash/unusual paths from bypassing ls-files matching
|
|
275
281
|
const rel = relative(ctx.cwd, abs).replace(/\\/g, "/")
|
package/tools/write-path.mjs
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* 记账**(两段式全部成功后由调用方一次 `markDirty`——批级语义,与改前逐字同)。`file.mjs` 只做
|
|
20
20
|
* 再导出,保既有 import 面稳定(模块拆分写优先纪律)。
|
|
21
21
|
*/
|
|
22
|
-
import { writeFile, unlink, rename, cp, rm } from "node:fs/promises"
|
|
22
|
+
import { writeFile, unlink, rename, cp, rm, rmdir } from "node:fs/promises"
|
|
23
23
|
|
|
24
24
|
/** apply_patch 两段式的暂存后缀(单点定义——阶段一落暂存档、阶段二提交、失败清理均用它)。 */
|
|
25
25
|
export const TMP_SUFFIX = ".thincoder-tmp"
|
|
@@ -106,7 +106,7 @@ async function tryInjectedPath(abs, content, meta) {
|
|
|
106
106
|
if ((op === "move" || op === "rename" || op === "copy") && typeof meta.dest === "string" && meta.dest.length > 0) {
|
|
107
107
|
gateOpenDoc(impl, meta.dest) // dest 面过门禁(覆盖目标档——与 src 同源拒写判据)
|
|
108
108
|
}
|
|
109
|
-
// 编辑器径只承载**内容写点**(op="write");delete / 路径操作(copy/move/rename)与
|
|
109
|
+
// 编辑器径只承载**内容写点**(op="write");delete / rmdir / 路径操作(copy/move/rename)与
|
|
110
110
|
// commit(默认径两段式的收尾)无端侧载体 ⇒ 门禁过后回默认径。
|
|
111
111
|
if (!doc || op !== "write") return null
|
|
112
112
|
if (typeof impl.applyEdit !== "function") return null
|
|
@@ -120,7 +120,7 @@ async function tryInjectedPath(abs, content, meta) {
|
|
|
120
120
|
* 默认径(无注入面 / 注入面未处理)——**CLI 语义,零行为变**。
|
|
121
121
|
*
|
|
122
122
|
* meta:
|
|
123
|
-
* · `op` "write"(默认)| "delete" | "copy" | "move" | "rename" | "commit"
|
|
123
|
+
* · `op` "write"(默认)| "delete" | "rmdir" | "copy" | "move" | "rename" | "commit"
|
|
124
124
|
* · `record` op="write":写后记账快照(recordWrite 第二参——受影响区)
|
|
125
125
|
* · `dest` op ∈ {copy, move, rename}:目标绝对路径
|
|
126
126
|
* · `stage` op="write":落到 `<abs>.thincoder-tmp`(**不提交、不记账**)——apply_patch
|
|
@@ -149,6 +149,8 @@ async function defaultPath(abs, content, meta) {
|
|
|
149
149
|
markDirty(abs)
|
|
150
150
|
return { written: true, via: "fs" }
|
|
151
151
|
}
|
|
152
|
+
// #943:空目录移除(非空 ENOTEMPTY 上抛——调用面译拒);不记 dirty(目录无行号语义)。
|
|
153
|
+
case "rmdir": { await rmdir(abs); return { written: true, via: "fs" } }
|
|
152
154
|
case "copy": {
|
|
153
155
|
await cp(abs, meta.dest, { recursive: true, force: true })
|
|
154
156
|
return { written: true, via: "fs" }
|
|
@@ -174,7 +176,7 @@ async function defaultPath(abs, content, meta) {
|
|
|
174
176
|
* 核内**单一写路径点**——全部工具写点经此落盘。
|
|
175
177
|
*
|
|
176
178
|
* @param {string} abs 目标绝对路径
|
|
177
|
-
* @param {string|null} content 写入内容(op="write" 必填;delete / copy / move / rename 传 null)
|
|
179
|
+
* @param {string|null} content 写入内容(op="write" 必填;delete / rmdir / copy / move / rename 传 null)
|
|
178
180
|
* @param {object} [meta] 见 defaultPath 的字段表
|
|
179
181
|
* @returns {Promise<{written: boolean, via: "editor"|"fs"|"staged"|"committed"}>}
|
|
180
182
|
* `via` = 实际落盘径("editor" = 注入面已提交;"staged" = 已落暂存待 `op="commit"`);
|
package/undo-stack.mjs
CHANGED
|
@@ -6,13 +6,23 @@
|
|
|
6
6
|
* 属核内面,函数本体零 TUI 依赖 ⇒ 按核内结构归位到核;`/undo` 命令面 `handleUndoCommand`
|
|
7
7
|
* 留 CLI 壳——S2 由壳 import 本档)。
|
|
8
8
|
*
|
|
9
|
+
* 双上界(#863 · TUI-COMMANDS.md §5.3):单条备份超 `MAX_UNDO_BYTES` ⇒ **oversize 占位**条目
|
|
10
|
+
* (`oversize: true` · `backup: null`——列表可见 · 不可回退;与「文件创建」态靠 `oversize` 字段分判,
|
|
11
|
+
* 消费面 = `cmd-undo.mjs` 先判该字段再判 `backup === null`);总量超 `MAX_UNDO_TOTAL_BYTES`
|
|
12
|
+
* ⇒ 逐最旧驱逐(条数上限 `MAX_UNDO` 保留)。
|
|
13
|
+
*
|
|
9
14
|
* Tracks write/edit/delete/hashline_edit/apply_patch operations in agent._undoStack.
|
|
10
15
|
*/
|
|
11
16
|
|
|
12
|
-
import { existsSync, readFileSync } from "node:fs"
|
|
17
|
+
import { existsSync, readFileSync, statSync } from "node:fs"
|
|
13
18
|
import { isAbsolute, join } from "node:path"
|
|
14
19
|
|
|
15
20
|
const MAX_UNDO = 50
|
|
21
|
+
const MAX_UNDO_BYTES = 10_000_000 // 10 MB — 单条备份上界(= tools/file.mjs 大档 read 守卫同值)
|
|
22
|
+
const MAX_UNDO_TOTAL_BYTES = 64_000_000 // 64 MB — 备份总预算(超限逐最旧驱逐)
|
|
23
|
+
|
|
24
|
+
/** 条目预算字节数(oversize 占位 ∥ 文件创建态(backup = null)不占预算)。 */
|
|
25
|
+
const backupBytes = (e) => (typeof e?.backup === "string" ? Buffer.byteLength(e.backup, "utf8") : 0)
|
|
16
26
|
|
|
17
27
|
/**
|
|
18
28
|
* Snapshot a file before a side-effect tool modifies it.
|
|
@@ -26,22 +36,26 @@ export function snapshotForUndo(agent, toolName, args, cwd) {
|
|
|
26
36
|
// 绝对路径直接用(path.join 对绝对段不重置——Windows 反斜杠路径也不按 "/" 切分);相对路径整段 join(cwd)。
|
|
27
37
|
const abs = isAbsolute(path) ? path : join(cwd, path)
|
|
28
38
|
let backup = null
|
|
39
|
+
let oversize = false
|
|
29
40
|
try {
|
|
30
41
|
if (existsSync(abs)) {
|
|
31
|
-
|
|
42
|
+
if (statSync(abs).size > MAX_UNDO_BYTES) oversize = true // 大档不入内存——占位条目(与「文件创建」态分判)
|
|
43
|
+
else backup = readFileSync(abs, "utf8")
|
|
32
44
|
}
|
|
33
45
|
} catch {
|
|
34
46
|
// can't read — maybe binary, skip
|
|
35
47
|
return
|
|
36
48
|
}
|
|
37
49
|
|
|
38
|
-
agent._undoStack.push(
|
|
39
|
-
tool: toolName,
|
|
40
|
-
path,
|
|
41
|
-
backup,
|
|
42
|
-
timestamp: Date.now(),
|
|
43
|
-
})
|
|
50
|
+
agent._undoStack.push(oversize
|
|
51
|
+
? { tool: toolName, path, backup: null, oversize: true, timestamp: Date.now() }
|
|
52
|
+
: { tool: toolName, path, backup, timestamp: Date.now() })
|
|
44
53
|
if (agent._undoStack.length > MAX_UNDO) agent._undoStack.shift()
|
|
54
|
+
// 总量上界:超限逐最旧驱逐(按字节预算计——条数上界保留)。
|
|
55
|
+
let total = agent._undoStack.reduce((n, e) => n + backupBytes(e), 0)
|
|
56
|
+
while (total > MAX_UNDO_TOTAL_BYTES && agent._undoStack.length > 0) {
|
|
57
|
+
total -= backupBytes(agent._undoStack.shift())
|
|
58
|
+
}
|
|
45
59
|
}
|
|
46
60
|
|
|
47
|
-
export { MAX_UNDO }
|
|
61
|
+
export { MAX_UNDO, MAX_UNDO_BYTES, MAX_UNDO_TOTAL_BYTES }
|
package/tool-docs/ledger_add.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
台账新增条目(写命令,仅主 agent)——入待讨论(六态状态机入口)。kind = requirement(需求池)/ tech_todo(技术待办);trigger = 归批 / 条件 / 认账不排期(可空)。
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
台账收口(写命令,仅主 agent)——勾销:待核销 → 已核销;追认核销:待讨论 / 待设计 → 已核销(须已有 evidence,缺则拒);在途 → 已核销 不可跳(仍经 待核销);撤回:任意态 → 已废弃。归档 = 软删除(写 closed_at,行保留)。
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
台账计数单源:未决四态(待讨论 / 待设计 / 在途 / 待核销)COUNT(*),只读、全角色可用。
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
台账查询(需求池 / 技术待办)——SQLite 行集,只读、全角色可用。未决四态 = 待讨论 / 待设计 / 在途 / 待核销;已核销 / 已废弃 = 归档态(软删除,行保留)。过滤参数缺省 = 全部行。
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
台账更新条目(写命令,仅主 agent)——状态迁移按六态允许迁移表:待讨论→待设计 / 待设计→在途 / 在途→待核销 / 待核销→已核销 / 任意态→已废弃;表外迁移被拒。status 缺省 = 仅更新字段。
|