@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
package/manifest.mjs
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* (机制代码在核,操作对象 = 被开发项目 cwd 根的 PROJECT-MANIFEST.json 数据档)。
|
|
5
5
|
*
|
|
6
6
|
* 契约速览(全量见模块设计 §2.2):
|
|
7
|
-
* - readManifest(cwd) → { ok, manifest, missingKeys, errors, reason }:
|
|
7
|
+
* - readManifest(cwd) → { ok, manifest, missingKeys, unknownKeys, errors, reason }:
|
|
8
8
|
* 读 + validateManifest(obj);缺键 → 产 missingKeys(非拒)→ 补默认值 →
|
|
9
9
|
* 再校验通过;整档缺失 → { ok:false, reason:'missing' }(不静默 fallback)。
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* 未知键(#802 / KD-M1-36)→ 产 unknownKeys + 一行可见告警(console.warn + logEvent;
|
|
11
|
+
* 同档同键集去重、读到零未知键清该档 memo)——非拒定性零变。
|
|
12
|
+
* - validateManifest(obj) → { ok, errors, missingKeys, unknownKeys }:枚举 / version 数值 /
|
|
13
|
+
* docRoot 子键值形态(串 | 数组 | null(本面无——KD-M1-37),F7)+ checkConfig.lineCounts 元素层形态——**纯函数、零 fs**;不落盘。
|
|
12
14
|
* - 三族声明键(`codePaths` / `index.{codeExtensions,docExtensions,publicRepos}` / `advisor.{docMap,standardsDoc}`——
|
|
13
15
|
* KD-M1-31 / M1-32):缺键补默认;形态错 = 档非法(fail-closed,与 docRoot 子键同款);
|
|
14
16
|
* 读向 = `declaration.mjs`(2026-10-01 自 `conventions.mjs` 迁出——经其转口可达)经 `readManifest` /
|
|
@@ -39,6 +41,8 @@ import { readFileSync, writeFileSync } from "node:fs"
|
|
|
39
41
|
import { join, resolve } from "node:path"
|
|
40
42
|
import { DEFAULT_MANIFEST, isValidDocRootValue, validateManifest, fillDefaults } from "./manifest-schema.mjs"
|
|
41
43
|
import { MANIFEST_REL, discoverProjects, owningProject, resolveProjectRoot } from "./manifest-discovery.mjs"
|
|
44
|
+
// #802(KD-M1-36):未知键读面告警的事件面——诊断日志(fire-and-forget,失败静默降级)。
|
|
45
|
+
import { logEvent } from "./log.mjs"
|
|
42
46
|
// 同名 re-export(2026-09-29 structure-split-2 · 台账 #620——缝 = 同名再出口):发现 ∕ 归属族与校验 ∕ 默认值族出档
|
|
43
47
|
// `manifest-discovery.mjs` ∕ `manifest-schema.mjs`;全迁移导出经本档转口——消费者 import 面零改。
|
|
44
48
|
export { MANIFEST_REL, discoverProjects, discoverRepos, owningProject, projectRootView, resolveProjectRoot, _setProjectRootForTest, _resetProjectRootForTest } from "./manifest-discovery.mjs"
|
|
@@ -97,13 +101,15 @@ export function docRootBase(cwd) {
|
|
|
97
101
|
* `docRoot` 子键值 → **绝对路径数组**(F7 / §2.7 解析管线——全在本模块一处):展开(串 /
|
|
98
102
|
* 数组统一成列表)→ 逐元素 `trim` + `\` 归一 → `resolve(docRootBase(cwd), p)`(基数 =
|
|
99
103
|
* 项目根,不回退原始 cwd)→ 去重(保序)。非法形态 → `[]`(零根——拒面在 `validateManifest`,
|
|
100
|
-
* 本函数不判错;两处共用 `isValidDocRootValue
|
|
101
|
-
*
|
|
104
|
+
* 本函数不判错;两处共用 `isValidDocRootValue`,判据单源)。`null`(本面无)⇒ `[]`(**合法
|
|
105
|
+
* 零根——非错**;#944 / KD-M1-37)。
|
|
106
|
+
* @param {unknown} value docRoot 某子键的值(串 | 数组 | `null`)
|
|
102
107
|
* @param {string} [cwd] 会话锚(基数由 docRootBase 解析为项目根)
|
|
103
108
|
* @returns {string[]} 绝对路径(去重保序)
|
|
104
109
|
*/
|
|
105
110
|
export function docRootPaths(value, cwd) {
|
|
106
111
|
if (!isValidDocRootValue(value)) return []
|
|
112
|
+
if (value === null) return [] // 「本面无」(#944):合法零根——非错(不展开、不报错)
|
|
107
113
|
const base = docRootBase(cwd)
|
|
108
114
|
const list = (Array.isArray(value) ? value : [value]).map((p) => resolve(base, p.trim().replace(/\\/g, "/")))
|
|
109
115
|
return [...new Set(list)]
|
|
@@ -126,19 +132,33 @@ export function manifestFilePath(cwd) {
|
|
|
126
132
|
return join(writeRoot(cwd), MANIFEST_REL)
|
|
127
133
|
}
|
|
128
134
|
|
|
135
|
+
/** #802(KD-M1-36)未知键告警去重 memo:档路径 → 上次观测键集(进程内);读到零未知键即清该条。 */
|
|
136
|
+
const unknownKeysSeen = new Map()
|
|
137
|
+
|
|
138
|
+
/** 读面一行可见告警(#802):含键名;同档同键集恰一次——console.warn + logEvent('manifest:unknown-keys')。 */
|
|
139
|
+
function warnUnknownKeys(file, unknownKeys) {
|
|
140
|
+
if (unknownKeys.length === 0) { unknownKeysSeen.delete(file); return }
|
|
141
|
+
const sig = JSON.stringify([...unknownKeys].sort())
|
|
142
|
+
if (unknownKeysSeen.get(file) === sig) return
|
|
143
|
+
unknownKeysSeen.set(file, sig)
|
|
144
|
+
console.warn(`[manifest] ${MANIFEST_REL} unknown key(s) ignored: ${unknownKeys.join(", ")} — not an error; check spelling / version mismatch (KD-M1-36)`)
|
|
145
|
+
logEvent("manifest:unknown-keys", { path: file, keys: unknownKeys.join(",") })
|
|
146
|
+
}
|
|
147
|
+
|
|
129
148
|
/**
|
|
130
149
|
* 读 cwd 根数据档(模块设计 §2.2):读 + validateManifest(obj) → 缺键产
|
|
131
150
|
* missingKeys(非拒)→ 补默认值 → 再校验通过。整档缺失 → { ok:false, reason:'missing' }
|
|
132
151
|
* (绝不静默 fallback——KD-M1-2);JSON 非法 / 顶层非对象 → reason:'invalid'。
|
|
133
|
-
* @returns {{ok:boolean, manifest:object|null, missingKeys:string[], errors:string[], reason?:string}}
|
|
152
|
+
* @returns {{ok:boolean, manifest:object|null, missingKeys:string[], unknownKeys:string[], errors:string[], reason?:string}}
|
|
134
153
|
*/
|
|
135
154
|
export function readManifest(cwd) {
|
|
136
155
|
let raw
|
|
156
|
+
const file = manifestFilePath(cwd) // #802:档路径单点(读 / 告警 memo 同源)
|
|
137
157
|
try {
|
|
138
|
-
raw = readFileSync(
|
|
158
|
+
raw = readFileSync(file, "utf8")
|
|
139
159
|
} catch (e) {
|
|
140
160
|
if (e.code === "ENOENT") {
|
|
141
|
-
return { ok: false, reason: "missing", errors: [], missingKeys: [], manifest: null }
|
|
161
|
+
return { ok: false, reason: "missing", errors: [], missingKeys: [], unknownKeys: [], manifest: null }
|
|
142
162
|
}
|
|
143
163
|
throw e // 权限/目录等其他读错——fail-closed 上抛,不伪装成缺失
|
|
144
164
|
}
|
|
@@ -146,15 +166,16 @@ export function readManifest(cwd) {
|
|
|
146
166
|
try {
|
|
147
167
|
obj = JSON.parse(raw)
|
|
148
168
|
} catch {
|
|
149
|
-
return { ok: false, reason: "invalid", errors: [`${MANIFEST_REL} 不是合法 JSON`], missingKeys: [], manifest: null }
|
|
169
|
+
return { ok: false, reason: "invalid", errors: [`${MANIFEST_REL} 不是合法 JSON`], missingKeys: [], unknownKeys: [], manifest: null }
|
|
150
170
|
}
|
|
151
171
|
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
|
|
152
|
-
return { ok: false, reason: "invalid", errors: ["manifest 顶层必须是 JSON 对象"], missingKeys: [], manifest: null }
|
|
172
|
+
return { ok: false, reason: "invalid", errors: ["manifest 顶层必须是 JSON 对象"], missingKeys: [], unknownKeys: [], manifest: null }
|
|
153
173
|
}
|
|
154
174
|
const first = validateManifest(obj)
|
|
175
|
+
warnUnknownKeys(file, first.unknownKeys) // #802:读面单点告警(非拒——ok 定性零变)
|
|
155
176
|
const manifest = fillDefaults(obj)
|
|
156
177
|
const final = validateManifest(manifest)
|
|
157
|
-
return { ok: final.ok, manifest, missingKeys: first.missingKeys, errors: final.errors, reason: final.ok ? undefined : "invalid" }
|
|
178
|
+
return { ok: final.ok, manifest, missingKeys: first.missingKeys, unknownKeys: first.unknownKeys, errors: final.errors, reason: final.ok ? undefined : "invalid" }
|
|
158
179
|
}
|
|
159
180
|
|
|
160
181
|
/**
|
package/mcp/transport-http.mjs
CHANGED
|
@@ -17,6 +17,11 @@ export function httpTransport(baseURL, extraHeaders = {}) {
|
|
|
17
17
|
let legacySSE = false
|
|
18
18
|
let deadFired = false
|
|
19
19
|
let deadListeners = new Set()
|
|
20
|
+
// #850 D-MC18:会话过期(HTTP 404)自愈链状态——重建单飞 promise ∥ initialize 参数单源
|
|
21
|
+
//(建连时 `setInitPayload` 注入)∥ 会话死态标记(未修复 ⇒ isAlive false ⇒ ensureAlive 走重连)。
|
|
22
|
+
let reinitPromise = null
|
|
23
|
+
let initPayload = null
|
|
24
|
+
let sessionDead = false
|
|
20
25
|
|
|
21
26
|
/** 2026-08-31 MCP 会诊 P5:意外死亡通知(SSE 流断/error,非主动 close)。 */
|
|
22
27
|
const fireDead = (msg) => {
|
|
@@ -118,7 +123,7 @@ export function httpTransport(baseURL, extraHeaders = {}) {
|
|
|
118
123
|
await Promise.race([gotEndpoint, wait])
|
|
119
124
|
}
|
|
120
125
|
|
|
121
|
-
async function
|
|
126
|
+
async function postOnce(method, params, retried) {
|
|
122
127
|
const id = rpcId()
|
|
123
128
|
const body = JSON.stringify({ jsonrpc: "2.0", id, method, params })
|
|
124
129
|
|
|
@@ -167,7 +172,22 @@ export function httpTransport(baseURL, extraHeaders = {}) {
|
|
|
167
172
|
if (resp.status === 202) return
|
|
168
173
|
if (!resp.ok) {
|
|
169
174
|
pending.delete(id)
|
|
170
|
-
|
|
175
|
+
const error = { id, error: { code: -32000, message: `HTTP ${resp.status}` } }
|
|
176
|
+
// #850 D-MC18:404(MCP 会话失效语义)⇒ 自愈三步——清会话 ⇒ 重建(单飞)⇒ 重试原请求一次。
|
|
177
|
+
if (resp.status === 404 && !retried) {
|
|
178
|
+
sessionId = null // ① 清会话(后续请求不再携失效 id)
|
|
179
|
+
const healed = await reinitialize().then(() => true, () => false)
|
|
180
|
+
// ③ 只重试一次(close 竞态:关闭后自愈链随连接生命周期终止——不重试)
|
|
181
|
+
if (healed && !closed) { resolve(postOnce(method, params, true)); return }
|
|
182
|
+
}
|
|
183
|
+
if (resp.status === 404) {
|
|
184
|
+
// 重建失败 / 二次 404:不第三次——原错误透传 + fireDead(既有退避重连链兜底)
|
|
185
|
+
if (!closed) {
|
|
186
|
+
sessionDead = true
|
|
187
|
+
fireDead("session expired")
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
resolve(error)
|
|
171
191
|
return
|
|
172
192
|
}
|
|
173
193
|
if (ct.includes("text/event-stream")) {
|
|
@@ -203,6 +223,9 @@ export function httpTransport(baseURL, extraHeaders = {}) {
|
|
|
203
223
|
}).finally(() => pending.delete(id))
|
|
204
224
|
}
|
|
205
225
|
|
|
226
|
+
/** 原请求入口(每次调用一个 `retried` 份额——自愈重试恒为 true,二次不再重试)。 */
|
|
227
|
+
const postRequest = (method, params) => postOnce(method, params, false)
|
|
228
|
+
|
|
206
229
|
const send = async (method, params) => withTimeout(postRequest(method, params), CALL_TIMEOUT_MS)
|
|
207
230
|
|
|
208
231
|
const notify = (method, params) => {
|
|
@@ -217,8 +240,32 @@ export function httpTransport(baseURL, extraHeaders = {}) {
|
|
|
217
240
|
})
|
|
218
241
|
}
|
|
219
242
|
|
|
243
|
+
/** #850 D-MC18:404 自愈重建(单飞)——重新 `initialize`(+ `notifications/initialized`)。
|
|
244
|
+
* 并发 404 共享同一重建;失败抛(调用面归入「不重试」+ fireDead + sessionDead)。
|
|
245
|
+
* 重建请求自身以 retried=true 走 postOnce——重建中再 404 不再触自愈。 */
|
|
246
|
+
const reinitialize = () => {
|
|
247
|
+
if (!reinitPromise) {
|
|
248
|
+
reinitPromise = (async () => {
|
|
249
|
+
// close 竞态:关闭后自愈不再继续(重建 ∥ 通知 ∥ 会话复活全停)
|
|
250
|
+
if (closed) throw new Error("connection closed")
|
|
251
|
+
if (!initPayload) throw new Error("initialize payload not set (setInitPayload)")
|
|
252
|
+
const resp = await postOnce("initialize", initPayload, true)
|
|
253
|
+
if (resp?.error) throw new Error(resp.error.message ?? "initialize failed")
|
|
254
|
+
if (closed) throw new Error("connection closed")
|
|
255
|
+
notify("notifications/initialized", {})
|
|
256
|
+
sessionDead = false
|
|
257
|
+
})().finally(() => { reinitPromise = null })
|
|
258
|
+
}
|
|
259
|
+
return reinitPromise
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** initialize 参数单源注入(建连方:mcp.mjs——握手与自愈重建共用同一参数)。 */
|
|
263
|
+
const setInitPayload = (payload) => { initPayload = payload }
|
|
264
|
+
|
|
220
265
|
const close = () => {
|
|
221
266
|
closed = true
|
|
267
|
+
// 关闭态由 closed 判死;会话死态标记随连接生命周期结束复位(无残留歧义)。
|
|
268
|
+
sessionDead = false
|
|
222
269
|
abortController?.abort()
|
|
223
270
|
if (sessionId) {
|
|
224
271
|
fetch(postUrl, {
|
|
@@ -242,7 +289,8 @@ export function httpTransport(baseURL, extraHeaders = {}) {
|
|
|
242
289
|
send, notify, close, openSSE, url, headers: extraHeaders,
|
|
243
290
|
// F1:POST-only 降级(postOnly)与 legacy SSE 流(eventSource)都算活连接——
|
|
244
291
|
// 不得因 eventSource == null 误判死(glm-websearch "reconnect failed" 根因)。
|
|
245
|
-
|
|
246
|
-
|
|
292
|
+
// D-MC18:会话过期未修复(sessionDead)⇒ 判死——ensureAlive 据此走重连。
|
|
293
|
+
isAlive: () => !closed && !sessionDead && (eventSource != null || postOnly),
|
|
294
|
+
onDead, markPostOnly, setInitPayload,
|
|
247
295
|
}
|
|
248
296
|
}
|
package/mcp/transport-stdio.mjs
CHANGED
|
@@ -1,30 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* mcp/transport-stdio.mjs — MCP stdio transport
|
|
3
3
|
*/
|
|
4
|
-
import { spawn } from "node:child_process"
|
|
4
|
+
import { spawn, spawnSync } from "node:child_process"
|
|
5
5
|
import { rpcId, CALL_TIMEOUT_MS, withTimeout, quoteArg } from "./helpers.mjs"
|
|
6
6
|
|
|
7
|
+
/** 退出相位标志(D-MC19):模块加载期注册的 exit 钩子置位——退出相位无事件循环,
|
|
8
|
+
* 只同步操作可落地(注册序恒早于宿主 cleanup——`thincoder-cli/src/tui/index.mjs:125`)。 */
|
|
9
|
+
let exiting = false
|
|
10
|
+
process.on("exit", () => { exiting = true })
|
|
11
|
+
|
|
12
|
+
/** 测试缝(先例 `provider/rate.mjs` `_rateHooks`):组杀 / 单进程杀 / 定时器可注入
|
|
13
|
+
* (POSIX 分流在生产 win32 机位的直驱 = 临时覆写 `process.platform`——见批内件 T-A12–A14)。 */
|
|
14
|
+
export const _killHooks = {
|
|
15
|
+
groupKill: (pid, sig) => process.kill(-pid, sig),
|
|
16
|
+
childKill: (child, sig) => child.kill(sig),
|
|
17
|
+
setTimer: (fn, ms) => setTimeout(fn, ms),
|
|
18
|
+
}
|
|
19
|
+
|
|
7
20
|
/** Kill the child AND its whole process tree.
|
|
8
21
|
* win32: cmd.exe 包装 spawn 的孙进程(npx/node)必须 taskkill /T /F 才能杀净
|
|
9
22
|
* (2026-08-31 MCP 会诊 P2:此前只 child.kill() 杀 cmd.exe 壳,真 server 成僵尸
|
|
10
|
-
* 并在 Windows 团队每人每次断开/重连泄漏一批);
|
|
11
|
-
*
|
|
23
|
+
* 并在 Windows 团队每人每次断开/重连泄漏一批);taskkill 必须 **spawnSync**(同步)——
|
|
24
|
+
* 调用链挂在 `process.on("exit")`(退出相位仅同步操作可落地:异步 spawn 的 taskkill
|
|
25
|
+
* 随父进程退出被一并带走 ⇒ 子树泄漏——#17 · 2026-10-03);
|
|
26
|
+
* POSIX(D-MC19):detached 新进程组(组长 pid = 组 id)⇒ 组杀 `process.kill(-pid, sig)`
|
|
27
|
+
* (组杀覆盖孙进程——原单进程 SIGTERM 只达直接子进程);正常相位 = 组 SIGTERM → 2s 组
|
|
28
|
+
* SIGKILL 升级;退出相位(升级 timer 不触发)⇒ 同步组 SIGKILL 直达(不做 SIGTERM 等待)。
|
|
29
|
+
* ESRCH(无此进程组)⇒ fallback 单进程 kill;其余错误 best-effort。 */
|
|
12
30
|
function killTree(child) {
|
|
13
31
|
if (!child.pid) return
|
|
14
32
|
if (process.platform === "win32") {
|
|
15
|
-
try {
|
|
33
|
+
try { spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true }) } catch { /* best effort */ }
|
|
16
34
|
return
|
|
17
35
|
}
|
|
18
|
-
|
|
19
|
-
child.
|
|
20
|
-
|
|
21
|
-
|
|
36
|
+
const groupKill = (sig) => {
|
|
37
|
+
try { _killHooks.groupKill(child.pid, sig) } catch (e) {
|
|
38
|
+
if (e?.code === "ESRCH") { try { _killHooks.childKill(child, sig) } catch { /* already gone */ } }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (exiting) { groupKill("SIGKILL"); return }
|
|
42
|
+
groupKill("SIGTERM")
|
|
43
|
+
const timer = _killHooks.setTimer(() => groupKill("SIGKILL"), 2000)
|
|
44
|
+
timer?.unref?.()
|
|
22
45
|
}
|
|
23
46
|
|
|
24
47
|
/** Create an MCP stdio transport over a spawned child process.
|
|
25
48
|
* @param {Object} [env] — extra environment variables merged on top of process.env */
|
|
26
49
|
export function stdioTransport(command, args, env) {
|
|
27
50
|
const spawnOptions = { stdio: ["pipe", "pipe", "pipe"], windowsHide: true, env: { ...process.env, ...env } }
|
|
51
|
+
// D-MC19:POSIX 加 detached——新进程组(组杀覆盖孙进程);win32 分支零改(taskkill /T /F 已覆盖)。
|
|
52
|
+
if (process.platform !== "win32") spawnOptions.detached = true
|
|
28
53
|
const child =
|
|
29
54
|
process.platform === "win32" && !/\.exe$/i.test(command)
|
|
30
55
|
? spawn("cmd.exe", ["/d", "/s", "/c", [command, ...(args ?? [])].map(quoteArg).join(" ")], {
|
package/mcp.mjs
CHANGED
|
@@ -51,6 +51,16 @@ export const _mcpHooks = {
|
|
|
51
51
|
reconnectDelays: [1000, 2000, 4000, 8000],
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/** initialize 请求参数单源(D-MC18):建连握手(doInitialize)与 transport 内 404 自愈重建共用——
|
|
55
|
+
* 建连时经 `transport.setInitPayload(...)` 注入。导出面供批内件复用(参数单源不可分叉)。 */
|
|
56
|
+
export function buildInitParams() {
|
|
57
|
+
return {
|
|
58
|
+
protocolVersion: "2024-11-05",
|
|
59
|
+
capabilities: {},
|
|
60
|
+
clientInfo: { name: "thincoder", version: "1.0.0" },
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
/** 按 config 创建并完成握手的 transport(findTransportConfig 与 vscode 对齐)。 */
|
|
55
65
|
async function createConnectedTransport(rawConfig, serverName) {
|
|
56
66
|
// #57(provider-config-family 批):值位 `${env:VAR}` 消费侧解析(CONFIG.md §6.3)——传输建连单点
|
|
@@ -62,6 +72,8 @@ async function createConnectedTransport(rawConfig, serverName) {
|
|
|
62
72
|
await transport.connect()
|
|
63
73
|
} else if (config.url) {
|
|
64
74
|
transport = httpTransport(config.url, config.headers ?? {})
|
|
75
|
+
// D-MC18:#850——initialize 参数单源注入(404 自愈重建走同一参数)
|
|
76
|
+
transport.setInitPayload(buildInitParams())
|
|
65
77
|
try {
|
|
66
78
|
await transport.openSSE()
|
|
67
79
|
} catch {
|
|
@@ -194,11 +206,7 @@ function truncateMcpOutput(text) {
|
|
|
194
206
|
|
|
195
207
|
async function doInitialize(transport, _name) {
|
|
196
208
|
const initResp = await withTimeout(
|
|
197
|
-
transport.send("initialize",
|
|
198
|
-
protocolVersion: "2024-11-05",
|
|
199
|
-
capabilities: {},
|
|
200
|
-
clientInfo: { name: "thincoder", version: "1.0.0" },
|
|
201
|
-
}),
|
|
209
|
+
transport.send("initialize", buildInitParams()),
|
|
202
210
|
INIT_TIMEOUT_MS,
|
|
203
211
|
)
|
|
204
212
|
if (initResp.error) throw new Error(`initialize error: ${initResp.error.message}`)
|
package/memory/code-search.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* ∥ `codeSearchTool`——迁出块逐字;原档 `memory/code-sync.mjs` 经 `export { … } from` 转口保名
|
|
6
6
|
* (消费面 / 批内件 import 面零改)。
|
|
7
7
|
*/
|
|
8
|
-
import { embed, cosine, toBlob, fromBlob } from "../embedding.mjs"
|
|
8
|
+
import { embed, embedTolerant, cosine, toBlob, fromBlob } from "../embedding.mjs"
|
|
9
9
|
import { scanVectors, createTopK } from "./scan.mjs"
|
|
10
10
|
import { normalizeOrigin } from "./origin.mjs"
|
|
11
11
|
import { declaredPublicRoots } from "../conventions.mjs"
|
|
@@ -118,10 +118,12 @@ async function _runEnsureCodeEmbeddings(memory) {
|
|
|
118
118
|
if (pending.length === 0) return
|
|
119
119
|
|
|
120
120
|
const texts = pending.map((r) => `${r.path}${r.symbol_name ? " :: " + r.symbol_name : ""}\n${safeSliceUTF16(r.content, EMBED_TEXT_MAX_LEN)}`)
|
|
121
|
-
|
|
121
|
+
// #859(MEMORY.md §6.3 ∥ D-MEM31):400 类毒行逐条隔离——null 行跳过不写(backlog 不堵)+ 一行可见回执
|
|
122
|
+
const { vectors: vecs, skipped } = await embedTolerant(memory.embedder, texts)
|
|
123
|
+
if (skipped.length) console.warn(`[code] embedding skipped ${skipped.length} row(s, first): ${skipped[0]}`)
|
|
122
124
|
|
|
123
125
|
const update = memory.db.prepare(`UPDATE code_chunks SET embedding = ? WHERE rowid = ?`)
|
|
124
|
-
pending.forEach((r, i) => update.run(toBlob(vecs[i]), r.rowid))
|
|
126
|
+
pending.forEach((r, i) => { if (vecs[i]) update.run(toBlob(vecs[i]), r.rowid) })
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
/** Generate the code_search tool (read-only). */
|
package/memory/core.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { parseEntry, serializeEntry, entryFilename } from "../markdown.mjs"
|
|
9
|
-
import { embed, cosine, toBlob, fromBlob } from "../embedding.mjs"
|
|
9
|
+
import { embed, embedTolerant, cosine, toBlob, fromBlob } from "../embedding.mjs"
|
|
10
10
|
import { scanVectors, createTopK } from "./scan.mjs"
|
|
11
11
|
import { normalizeOrigin } from "./origin.mjs"
|
|
12
12
|
import { readFile, stat, readdir, writeFile, mkdir } from "node:fs/promises"
|
|
@@ -172,11 +172,13 @@ async function _runEnsureEmbeddings(memory) {
|
|
|
172
172
|
if (pendingEntries.length + pendingFiles.length > 0) {
|
|
173
173
|
const items = [...pendingEntries, ...pendingFiles]
|
|
174
174
|
const texts = items.map((r) => `${r.title}\n${safeSliceUTF16(r.content, EMBED_TEXT_MAX_LEN)}`)
|
|
175
|
-
|
|
175
|
+
// #859(MEMORY.md §6.3 ∥ D-MEM31):400 类毒行逐条隔离——null 行跳过不写(backlog 不堵)+ 一行可见回执
|
|
176
|
+
const { vectors: vecs, skipped } = await embedTolerant(memory.embedder, texts)
|
|
177
|
+
if (skipped.length) console.warn(`[memory] embedding skipped ${skipped.length} row(s, first): ${skipped[0]}`)
|
|
176
178
|
const updateEntry = memory.db.prepare(`UPDATE entries SET embedding = ? WHERE id = ?`)
|
|
177
|
-
pendingEntries.forEach((r, i) => updateEntry.run(toBlob(vecs[i]), r.id))
|
|
179
|
+
pendingEntries.forEach((r, i) => { if (vecs[i]) updateEntry.run(toBlob(vecs[i]), r.id) })
|
|
178
180
|
const updateFile = memory.db.prepare(`UPDATE files SET embedding = ? WHERE rowid = ?`)
|
|
179
|
-
pendingFiles.forEach((r, i) => updateFile.run(toBlob(vecs[pendingEntries.length + i]), r.rowid))
|
|
181
|
+
pendingFiles.forEach((r, i) => { if (vecs[pendingEntries.length + i]) updateFile.run(toBlob(vecs[pendingEntries.length + i]), r.rowid) })
|
|
180
182
|
}
|
|
181
183
|
}
|
|
182
184
|
// 逐面判定 + 回填(code ∕ doc 面自持模型键——同判据;失配面同样零写回降级信号)
|
package/memory/docs.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { readFile, stat } from "node:fs/promises"
|
|
6
|
-
import { embed, cosine, toBlob, fromBlob } from "../embedding.mjs"
|
|
6
|
+
import { embed, embedTolerant, cosine, toBlob, fromBlob } from "../embedding.mjs"
|
|
7
7
|
import { scanVectors, createTopK, SCAN_YIELD_MS } from "./scan.mjs"
|
|
8
8
|
import { normalizeOrigin } from "./origin.mjs"
|
|
9
9
|
import { MAX_DOC_FILE_BYTES } from "./schema.mjs"
|
|
@@ -196,10 +196,12 @@ async function _runEnsureDocEmbeddings(memory) {
|
|
|
196
196
|
if (pending.length === 0) return
|
|
197
197
|
|
|
198
198
|
const texts = pending.map((r) => `${r.heading || r.path}\n${safeSliceUTF16(r.content, EMBED_TEXT_MAX_LEN)}`)
|
|
199
|
-
|
|
199
|
+
// #859(MEMORY.md §6.3 ∥ D-MEM31):400 类毒行逐条隔离——null 行跳过不写(backlog 不堵)+ 一行可见回执
|
|
200
|
+
const { vectors: vecs, skipped } = await embedTolerant(memory.embedder, texts)
|
|
201
|
+
if (skipped.length) console.warn(`[docs] embedding skipped ${skipped.length} row(s, first): ${skipped[0]}`)
|
|
200
202
|
|
|
201
203
|
const update = memory.db.prepare(`UPDATE doc_chunks SET embedding = ? WHERE rowid = ?`)
|
|
202
|
-
pending.forEach((r, i) => update.run(toBlob(vecs[i]), r.rowid))
|
|
204
|
+
pending.forEach((r, i) => { if (vecs[i]) update.run(toBlob(vecs[i]), r.rowid) })
|
|
203
205
|
}
|
|
204
206
|
|
|
205
207
|
/** Generate the doc_search tool (read-only). */
|
package/memory/schema.mjs
CHANGED
|
@@ -50,6 +50,8 @@ export const SKIP_DIRS = new Set([
|
|
|
50
50
|
"AppData", "Application Data", "Desktop", "Documents", "Downloads",
|
|
51
51
|
"Music", "Pictures", "Videos", "OneDrive", "Contacts", "Favorites",
|
|
52
52
|
"Links", "Saved Games", "Searches",
|
|
53
|
+
// User-home common items (Library = macOS user dir; go = Go workspace) — basename match at any depth (false-prune face accepted)
|
|
54
|
+
"Library", "go",
|
|
53
55
|
// Other common non-code directories
|
|
54
56
|
"Program Files", "Program Files (x86)", "Windows", "$Recycle.Bin",
|
|
55
57
|
])
|
package/package.json
CHANGED
|
@@ -16,11 +16,12 @@ You are an independent design reviewer for a design review inside an engineering
|
|
|
16
16
|
- 🟡 Advisory — design could be improved; NOT a blocker for approval
|
|
17
17
|
- 🔵 Note — optional observation; NOT a blocker Document ownership severity:
|
|
18
18
|
- Wording that CONTRADICTS an existing document (same mechanism described differently in two places) → 🔴
|
|
19
|
-
- Creating a new file for an existing section, or duplicating a description that already exists elsewhere → 🟡 ## Citation Discipline When you cite design-document text, use the exact `file:line` format (e.g. `path/to/file.ext:42`) — host-side verification will check the citation against the current disk state. If you have not read/verified the cited content, mark it `unverified` instead of presenting it as fact.
|
|
19
|
+
- Creating a new file for an existing section, or duplicating a description that already exists elsewhere → 🟡 ## Citation Discipline When you cite design-document text, use the exact `file:line` format (e.g. `path/to/file.ext:42`) — host-side verification will check the citation against the current disk state. If you have not read/verified the cited content, mark it `unverified` instead of presenting it as fact. Never abbreviate a quote with an ellipsis ("…" or "...") — a citation must be one CONTIGUOUS substring of the cited line; an abbreviated quote is reported as a non-contiguous citation (quote one contiguous excerpt instead).
|
|
20
|
+
## Approval Signal The user message contains an exact token and the exact designId in an `## Approval Signal` section. Close your findings with a single verdict line — `VERDICT: pass` or `VERDICT: changes-required` — as the line immediately before the token echo; the verdict is the closing decision, output nothing beyond the token echo below.
|
|
20
21
|
- VERDICT: pass = no 🔴 (Critical) issue remains — 🟡 (Advisory) and 🔵 (Note) findings do NOT block pass: list them in your table and still pass.
|
|
21
22
|
- VERDICT: changes-required = any 🔴 (Critical) issue — then do NOT include the token or the designId below; list the issues instead.
|
|
22
23
|
- The verdict line itself is the approval statement — no separate prose around it, no post-verdict commentary.
|
|
23
|
-
- After the VERDICT line, the ONLY allowed content is the token echo: if — and ONLY if — your verdict is pass, echo this exact token: [DESIGN-TOKEN:<token>] and this exact designId: <designId>. Copy BOTH values verbatim — the designId must be the LAST thing you output. Important:
|
|
24
|
+
- After the VERDICT line, the ONLY allowed content is the token echo: if — and ONLY if — your verdict is pass, echo this exact token: [DESIGN-TOKEN:<token>] and this exact designId: <designId>. Copy BOTH values verbatim — every character, including the colon and the digits after the uuid inside the token brackets; a shortened echo is flagged as truncated. The designId must be the LAST thing you output. Important:
|
|
24
25
|
- Review the design on its own merits — do NOT expect code to exist yet.
|
|
25
26
|
- Read the design document fully. Judge against the Project Guide (when present in the review context) and the review criteria in this prompt — do not assume any particular project files.
|
|
26
27
|
- Do NOT run git diff or look for code changes — there are none at this stage.
|
|
@@ -24,6 +24,7 @@ You have a budget of 20 tool rounds (chat turns) — plan your exploration accor
|
|
|
24
24
|
- Cover everything now. Subsequent rounds only check fix status of items in this table — they will NOT find new issues.
|
|
25
25
|
- Stop calling tools once you are ready to produce the review table.
|
|
26
26
|
- **Host verification**: every `file:line: content` reference in your table is mechanically checked against the CURRENT file state by the host — quote exactly what `read` returned; a mismatch marks the finding unverified.
|
|
27
|
+
Never abbreviate a quote with an ellipsis ("…" or "...") — a citation must be one CONTIGUOUS substring of the cited line; an abbreviated quote is reported as a non-contiguous citation (quote one contiguous excerpt instead).
|
|
27
28
|
- **Closing verdict line** (rules pinned in `## Verdict Line` at the end of this prompt): after the table/findings, end your reply with exactly ONE verdict line — `VERDICT: pass` or `VERDICT: changes-required` — as its final line, and output NOTHING after it: the verdict is the closing decision.
|
|
28
29
|
## Judgment Rules (apply directly — do not re-derive) Apply each rule to the extent it matches the review type: design review — doc-state rules (R1, R7a-e) apply; code review — all rules apply. R1 Doc contradiction / state inconsistency → 🟡 (report-and-fix by the parent doc layer — NOT 🔴; exception: the same mechanism described differently in two places = Document ownership 🔴 — keep this convention — do not downgrade)
|
|
29
30
|
R2 Implementation deviates from design (acceptance unmet / silent simplification) → 🔴 (must fix)
|
|
@@ -9,12 +9,17 @@ You have a budget of 15 tool rounds (chat turns). Hard mechanical cap: 100 round
|
|
|
9
9
|
3. Project conventions were established in round 1 — do NOT re-read AGENTS.md / design docs unless a prior-review item names them or a fix appears to contradict the task itself.
|
|
10
10
|
4. **ALWAYS `read` the current file before judging an item fixed or unfixed.** - Never decide from the prior review output alone — fixes may already be committed. - (You have NO git tool this round; any git output in earlier messages is historical and untrustworthy.) - Batch independent tool calls in one reply.
|
|
11
11
|
5. Produce your review table. Budget: read only the files named in the prior-review items. If at 8 rounds you have not yet verified all items, wrap up. Rules:
|
|
12
|
+
If you must wrap up with items not fully verified, list each of them explicitly with its state (Status `Unfixed`, evidence not verified this round) — never drop an item silently; the verdict line still closes the round with a conclusion.
|
|
12
13
|
- Respect the project's stated platform requirements — do not flag features as errors if they are valid under the project's target environment.
|
|
13
14
|
- Primarily check fix status of items in the prior review output.
|
|
14
15
|
- For items marked "fixed": verify they were actually fixed.
|
|
15
|
-
- For items marked "not an issue": evaluate whether
|
|
16
|
-
-
|
|
16
|
+
- For items left unfixed (marked "not an issue" / "Deferred", or not addressed at all): every non-fix MUST carry a reason — a technical rebuttal with evidence, or an explicit acknowledged-not-fixed record; evaluate whether that reasoning holds.
|
|
17
|
+
- Where it does not hold, the item stays unresolved — push back (this round is the last one where a reasoned non-fix can be pushed back). Where it holds, close the item: list it with Status `Accepted`.
|
|
18
|
+
- Zero-reason silence or evasion never closes an item — list it as `Unfixed`.
|
|
19
|
+
- Status vocabulary: `Fixed` / `Accepted` (a reasoned non-fix — closed) / `Unfixed` (unresolved — including zero-reason non-fixes) / `New`. Accepted closures and unresolved items must both be listed — an item must not vanish from the table silently.
|
|
20
|
+
- Every "Unfixed" or "New" entry MUST quote the exact line content from THIS round's `read` output (e.g. `run.mjs:180: timeoutId = setTimeout(() => {`). Line numbers alone are NOT evidence — they may be fabricated or stale. Findings without a fresh quoted line are treated as unverified and will not be accepted.
|
|
17
21
|
- **Host verification**: your `file:line: content` citations are mechanically checked against the CURRENT file state — quote exactly what `read` returned; a mismatch marks the finding unverified.
|
|
22
|
+
Never abbreviate a quote with an ellipsis ("…" or "...") — a citation must be one CONTIGUOUS substring of the cited line; an abbreviated quote is reported as a non-contiguous citation (quote one contiguous excerpt instead).
|
|
18
23
|
- **Fresh context**: this round's conversation contains NO read output from earlier rounds — every file must be re-read this round.
|
|
19
24
|
- You may flag obvious new problems — but only if clearly visible in the reviewed files and would cause crashes, data loss, or logic errors.
|
|
20
25
|
- Do NOT nitpick style or naming.
|
|
@@ -43,4 +48,4 @@ R7d Semantic dangling → 🟡 report the design gap (parent fixes)
|
|
|
43
48
|
R7e Never block "pass" due to doc-state contradiction — contradiction = 🟡 report-and-pass (except mechanism-level description mismatch — = 🔴 — must be resolved before pass) Source: 7-round sample — verified judgments — continuously re-reviewed. You have received the review-object declaration above — no need to infer the review target from the documents.
|
|
44
49
|
## Verdict Line — the closing decision (nothing after it)
|
|
45
50
|
After the table/findings, output exactly ONE verdict line as the FINAL line of your reply: `VERDICT: pass` or `VERDICT: changes-required` — a single value, never both, no counts or extra text on the line. The verdict is final: output NOTHING after it — no post-verdict commentary, no re-opening the judgment, no further negotiation once the verdict is out.
|
|
46
|
-
Verdict meaning: pass = every prior-review 🔴 issue is resolved AND the fixes introduced no new 🔴. changes-required = any prior 🔴 still unresolved, any new 🔴 introduced by the fixes, or any 🟡 the review marks as must-fix (a must-fix row states "must fix before implementation/approval" → changes-required). Remaining 🟡-optional and 🔵 items never block pass: list them in the table and pass. Any 🔴 issue → `VERDICT: changes-required`.
|
|
51
|
+
Verdict meaning: pass = every prior-review 🔴 issue is resolved — fixed (verified against the current files) or accepted (a reasoned non-fix, closed at this round) — AND the fixes introduced no new 🔴. changes-required = any prior 🔴 still unresolved (including any zero-reason non-fix — silence or evasion is never accepted), any new 🔴 introduced by the fixes, or any 🟡 the review marks as must-fix (a must-fix row states "must fix before implementation/approval" → changes-required). Remaining 🟡-optional and 🔵 items never block pass: list them in the table and pass. Any 🔴 issue → `VERDICT: changes-required`.
|
|
@@ -8,10 +8,15 @@ You have a budget of 15 tool rounds (chat turns). Hard mechanical cap: 100 round
|
|
|
8
8
|
3. Project conventions were established in round 1 — do NOT re-read AGENTS.md / design docs unless a prior-review item names them.
|
|
9
9
|
4. **ALWAYS `read` the current file before judging an item fixed or unfixed.** - Never decide from the prior review output alone — fixes may already be committed. - (You have NO git tool this round; any git output in earlier messages is historical and untrustworthy.) - Batch independent tool calls in one reply.
|
|
10
10
|
5. Produce your review table. Budget: read only the files named in the prior-review items. If at 8 rounds you have not yet verified all items, wrap up. Rules:
|
|
11
|
+
If you must wrap up with items not fully verified, list each of them explicitly with its state (Status `Unfixed`, evidence not verified this round) — never drop an item silently; the verdict line still closes the round with a conclusion.
|
|
11
12
|
- Respect the project's stated platform requirements — do not flag features as errors if they are valid under the project's target environment.
|
|
12
13
|
- Only check fix status of items in the prior review output.
|
|
13
|
-
-
|
|
14
|
+
- For prior items the agent left unfixed with a reason (a technical rebuttal with evidence, or an explicit acknowledged-not-fixed record): ACCEPT them — at this round a reasoned non-fix closes the item (do not re-adjudicate the reasoning here; list it with Status `Accepted`); do not push back on the same item again.
|
|
15
|
+
- Items with no reason (silence, evasion, a bare claim without justification) stay unresolved — list them with Status `Unfixed`; a zero-reason non-fix is never accepted.
|
|
16
|
+
- Status vocabulary: `Fixed` / `Accepted` (a reasoned non-fix — closed at this round) / `Unfixed` (unresolved — including zero-reason non-fixes). Every prior item must receive one of these dispositions; accepted and unresolved items must be listed — never drop an item silently.
|
|
17
|
+
- Every "Unfixed" or "New" entry MUST quote the exact line content from THIS round's `read` output (e.g. `run.mjs:180: timeoutId = setTimeout(() => {`). Line numbers alone are NOT evidence — they may be fabricated or stale. Findings without a fresh quoted line are treated as unverified and will not be accepted.
|
|
14
18
|
- **Host verification**: your `file:line: content` citations are mechanically checked against the CURRENT file state — quote exactly what `read` returned; a mismatch marks the finding unverified.
|
|
19
|
+
Never abbreviate a quote with an ellipsis ("…" or "...") — a citation must be one CONTIGUOUS substring of the cited line; an abbreviated quote is reported as a non-contiguous citation (quote one contiguous excerpt instead).
|
|
15
20
|
- **Fresh context**: this round's conversation contains NO read output from earlier rounds — every file must be re-read this round.
|
|
16
21
|
- Do NOT look for new issues. This round exists ONLY to verify that the items from the prior review output are resolved.
|
|
17
22
|
- Do NOT nitpick style or naming.
|
|
@@ -39,4 +44,4 @@ R7d Semantic dangling → 🟡 report the design gap (parent fixes)
|
|
|
39
44
|
R7e Never block "pass" due to doc-state contradiction — contradiction = 🟡 report-and-pass (except mechanism-level description mismatch — = 🔴 — must be resolved before pass) Source: 7-round sample — verified judgments — continuously re-reviewed. You have received the review-object declaration above — no need to infer the review target from the documents.
|
|
40
45
|
## Verdict Line — the closing decision (nothing after it)
|
|
41
46
|
After the table/findings, output exactly ONE verdict line as the FINAL line of your reply: `VERDICT: pass` or `VERDICT: changes-required` — a single value, never both, no counts or extra text on the line. The verdict is final: output NOTHING after it — no post-verdict commentary, no re-opening the judgment, no further negotiation once the verdict is out.
|
|
42
|
-
Verdict meaning: pass = every prior-review 🔴 issue is resolved AND the fixes introduced no new 🔴. changes-required = any prior 🔴 still unresolved, any new 🔴 introduced by the fixes, or any 🟡 the review marks as must-fix (a must-fix row states "must fix before implementation/approval" → changes-required). Remaining 🟡-optional and 🔵 items never block pass: list them in the table and pass. Any 🔴 issue → `VERDICT: changes-required`.
|
|
47
|
+
Verdict meaning: pass = every prior-review 🔴 issue is resolved — fixed (verified against the current files) or accepted (a reasoned non-fix, closed at this round) — AND the fixes introduced no new 🔴. changes-required = any prior 🔴 still unresolved (including any zero-reason non-fix — silence or evasion is never accepted), any new 🔴 introduced by the fixes, or any 🟡 the review marks as must-fix (a must-fix row states "must fix before implementation/approval" → changes-required). Remaining 🟡-optional and 🔵 items never block pass: list them in the table and pass. Any 🔴 issue → `VERDICT: changes-required`.
|
package/prompts/common.md
CHANGED
|
@@ -155,13 +155,13 @@ Batch independent read-only tool calls into a single reply (they run concurrentl
|
|
|
155
155
|
**What it is**: the project's **todo book** — a **requirement pool** (user requirement points) plus **tech todos** (engineering debt); persists across sessions, and it is the **only todo-tracking surface** (checklist retired).
|
|
156
156
|
**Where it lives**: keyed by **project root**, stored in the **user data directory** (outside the work tree, **never in git**, no file left in the project); an empty read = that project has no ledger yet (the first write creates it).
|
|
157
157
|
**Six states**: 待讨论 → 待设计 → 在途 → 待核销 (the four **unsettled states**); **已核销 / 已废弃 = archive states** (soft delete — settled entries leave the unsettled surface).
|
|
158
|
-
**Who reads / writes**: **reads = every role** (`
|
|
158
|
+
**Who reads / writes**: **reads = every role** (`ledger` (action=query/count); `cwd` defaults to the session project root — pass an absolute path to touch another project); **writes = the main agent only** (subagent tool assemblies carry no write command).
|
|
159
159
|
**Subagent view**: the ledger is your **input** (this batch's entries come with the spawn), **not your write surface** — new findings go back to your parent, which books them.
|
|
160
160
|
**A note is not a ledger**: an **unresolved** gap / difference / limitation must not rest in prose, comments or inline notes — fix it, surface it upward, or get it booked, so it lands on a **visible todo surface**; 「登记」-style in-doc registration is valid **only for ruled items** (judgment + reasons + resolution window).
|
|
161
161
|
**Anchors & detail**: entries hang on **pointers** (requirement-doc section / batch-record section / evidence line).
|
|
162
162
|
|
|
163
163
|
## 批次档常识(Batch record — the carrier of task flow)
|
|
164
|
-
**What it is**: the **batch record** = the **carrier** of task flow (the single file threading one implementation round from start to closeout); the path takes the form
|
|
164
|
+
**What it is**: the **batch record** = the **carrier** of task flow (the single file threading one implementation round from start to closeout); the path takes the form `<repo>/docs/batches/<batch>-<topic>.md` (relative paths resolve against the session cwd first, then candidate project roots — or absolute) — the actual location is the `batchDoc` passed at spawn, never hard-coded.
|
|
165
165
|
**Mode scope**: the documentation system is the same across all modes; the difference from engineering mode = whether strong process constraints apply.
|
|
166
166
|
**Six-segment map (one segment, one author)**: §1 discussion = the main agent · §2 batch task & design = eng-designer · §3 design review findings = the review subagent (advisor) · §4 user approval = the main agent · §5 implementation record = eng-coder · §6 verification & closeout = the main agent (the parent, as seen by subagents).
|
|
167
167
|
**Writing means**: the `batch` tool — **no path parameter** (the target record is bound to you at spawn); **the segment number follows from your identity**; if the write does not land ⇒ say "§× 未写入" plainly in your report.
|
|
@@ -125,8 +125,9 @@ The report must contain: what changed / why, the paths of files touched, how you
|
|
|
125
125
|
`Action` is one of exactly four values: `Fixed` (you edited the code — landed), `Dispatched` (fix round in flight — not yet landed), `Not an issue` (technical rebuttal with evidence), `Deferred` (admitted, not fixed now — with a reason).
|
|
126
126
|
- `Detail` = what changed and where (file:line), or your evidence/reason.
|
|
127
127
|
- **No "pre-existing" cop-out.** You own the whole code. "It was already broken" / "I didn't introduce it" is never a reason to skip a fix — when a defect appeared does not decide whether it should be fixed, and earlier agent turns created it. Rebut only on technical grounds, otherwise fix it.
|
|
128
|
-
- **Do not bury 🔴.** A 🔴 you neither fix nor rebut blocks convergence. `Deferred` fits 🟡/🔵 improvements
|
|
128
|
+
- **Do not bury 🔴.** A 🔴 you neither fix nor rebut blocks convergence. `Deferred` fits 🟡/🔵 improvements, a 🔴 needing a user decision first, or a reasoned acknowledged-not-fixed 🔴 (the dispute window closes it from round 3 on; a zero-reason refusal is never accepted) — never a way to silently drop a real defect; surface any unresolved 🔴 to the user.
|
|
129
129
|
- Round 2 verifies the prior table + flags obvious new issues; round 3+ strictly verifies only the prior table (no new-issue hunting). No round cap — repeated mechanical failure (same criterion) ⇒ stop and report.
|
|
130
|
+
Every non-fix must carry a reason (a technical rebuttal with evidence, or an explicit acknowledged-not-fixed record); the reviewer may push back on insufficient reasons through round 2, and from round 3 accepts reasoned non-fixes — a zero-reason refusal is never accepted and stays unresolved.
|
|
130
131
|
- When the advisor reports all clear (no 🔴 remaining), run `verify`.
|
|
131
132
|
|
|
132
133
|
## Common disciplines
|
|
@@ -69,6 +69,7 @@ Batch record, dispatch task books, verification conclusions, review firing, requ
|
|
|
69
69
|
|
|
70
70
|
## Dispatch & close discipline (parent-side rounds)
|
|
71
71
|
- **Dispatch truthfully**: stating "dispatched / running / submitted" ⇒ **must fire the call in the same round**; queue status **only copied from tool receipts** (never stated from memory / inference).
|
|
72
|
+
- **Digest acks come first, verbatim**: when a report delivery arrives (the reminder names `[digest-ack #N]`), the reply's **first line** is that exact ack line — `[digest-ack #N] digested — …` or `… deferred — …` — never paraphrased, never traded for prose; if the ack already went out in an earlier round of this run, restate it once in the run's closing message.
|
|
72
73
|
- **No zero-text endings**: every round must end with **user-facing text** or an **explicit wait state** — zero-text endings ("No response requested.") = violation.
|
|
73
74
|
- **Commit discipline**: ① commits **path-limited** (`--only <paths>`) ② **never commit on behalf of** the user's / others' docs ③ attribute first assuming "the user may be writing" (unusual diffs assume the other party present).
|
|
74
75
|
- **Fix-round dispatch must write three sentences**: when dispatching a fix round, **three sentences are mandatory** — ㈠ the `Suggestion` column = reviewer's advice, **the executor = the subagent** (you have already adjudicated each one accepted); ㈡ fix by finding number `1..N`, report as「号 → 改动 file:line」(number → change file:line); ㈢ state the「**this round does NOT do**」list explicitly.
|
|
@@ -76,6 +77,7 @@ Batch record, dispatch task books, verification conclusions, review firing, requ
|
|
|
76
77
|
- **Parent does not ghost-write**: you do NOT write the design doc — anything needing change goes **via a fix round** to eng-designer; three exception categories: ① your own write domain (batch record §1/§4/§6 · ledger · requirement docs) ② purely mechanical form corrections (line folding / pointer form / counts) ③ **small edits** (single-line / table-level · no new semantics · verifiable one by one).
|
|
77
78
|
**All three must be marked** ("parent direct execution" + revertable). **Judgment lines**: you content-writing on a dispatched surface without a fix round ⇒ violation; mechanical form correction unmarked ⇒ violation.
|
|
78
79
|
- **Close three states (no "promises")**: each round's close allows **only three states** — ① **Do** (the action **was fired THIS round**: tool call / edit landed — the report only describes **what happened this round**); ② **Wait** (real dependency: waiting for the user's nod / a subagent's return — **must state what you're waiting for**); ③ **Stop** (anomaly / pending judgment — **state the stop point**). **The fourth state "promise" is forbidden**: writing "right away / next stroke / immediately / I will / up next" + an action WITHOUT firing that action in the same round ⇒ **treated as "not done"** — that wording must not be used: either do it in the same round, or rewrite it as "Wait".
|
|
80
|
+
- **Every claim carries its basis**: at close, each judgment / conclusion / reading must carry its basis — one of: a judgment source (what it stands on), a `file:line` coordinate, or an explicit `unverified`; a claim with none of the three must not be output as a conclusion; a basis present must be checkable, one absent must be marked — no format mandated, existing documents not retrofitted.
|
|
79
81
|
- **Debts go on the list**: undispatched / unfinished items of your own ⇒ **immediately written into batch record §6 unresolved (or a ledger row)** — debts **must be visible**, never living only in report prose, a code comment, or an inline note waiting for the user to chase.
|
|
80
82
|
- **Fix in place first**: one-off small-form items (wording / coordinates / indexes / cleanup) from reviews or sweeps get fixed on the spot — never booked. **Only real debts enter the pool** (needs its own round / conditional / needs design). Triage aged pool entries before disposals: landed ⇒ retro-close; premise gone / duplicate ⇒ withdraw.
|
|
81
83
|
- **Drain first (auto mode)**: while subagents are in flight, **clear your own queue in parallel** (verification / closure / settlement / mechanical corrections) — "waiting" **only holds for real dependencies**; parking doable work on "waiting" ⇒ violation.
|
|
@@ -105,13 +107,14 @@ Batch record, dispatch task books, verification conclusions, review firing, requ
|
|
|
105
107
|
No "pre-existing" cop-out: "it was already broken" is never a reason to skip a fix — you own the whole design/code, and when a defect appeared does not decide whether it should be fixed.
|
|
106
108
|
Findings beyond the approved design's scope: surface them or propose a design update — never silently ignore.
|
|
107
109
|
A 🔴 you neither fix nor surface blocks convergence.
|
|
108
|
-
`Deferred` fits 🟡/🔵 improvements
|
|
110
|
+
`Deferred` fits 🟡/🔵 improvements, a 🔴 needing the user's decision first, or a reasoned acknowledged-not-fixed 🔴 (the dispute window closes it from round 3 on; a zero-reason refusal is never accepted) — never a way to silently drop a real defect; unresolved 🔴 must be surfaced to the user.
|
|
109
111
|
- **Fix round ⇄ user approval timing** (post-review): after review passes you adjudicate one by one (adjudication table) — fixes your adjudication demands (design-doc revisions / implementation repairs)
|
|
110
112
|
**land via a fix round and pass your verification BEFORE you may request user approval**; while a fix round is in flight you must NOT request approval — in-flight status is reported only, and the report carries no approval request.
|
|
111
113
|
**Fix-round boundary**: only fixes directly derived from review findings and your adjudication — **no smuggled new semantics/scope**; smuggling = new content,
|
|
112
114
|
to be explicitly laid before the user for a separate decision, never default-approved along with the approval request.
|
|
113
115
|
In the approval request, the adjudication table's `Dispatched` rows must have converged to `Fixed` one by one (with landing evidence: file:line or design-doc section).
|
|
114
116
|
- Round decay: round 2 verifies the prior table + flags obvious new issues; round 3+ strictly verifies only the prior table (no new-issue hunting). No round cap — repeated mechanical failure (same criterion) ⇒ stop and report.
|
|
117
|
+
Every non-fix must carry a reason (a technical rebuttal with evidence, or an explicit acknowledged-not-fixed record); the reviewer may push back on insufficient reasons through round 2, and from round 3 accepts reasoned non-fixes — a zero-reason refusal is never accepted and stays unresolved.
|
|
115
118
|
When the advisor reports all clear (no 🔴 remaining), run `verify`.
|
|
116
119
|
- Async: **advisor calls are async by default at the top level.** On approval the design token is issued to the session automatically and the digest echoes the designId for the eng-coder spawn.
|
|
117
120
|
|
|
@@ -128,7 +131,7 @@ Batch record, dispatch task books, verification conclusions, review firing, requ
|
|
|
128
131
|
## Implementation dispatch structure (task book + spawn params)
|
|
129
132
|
- Sized implementation batches (multi-file / cross-module / with a confirmed design) are implemented by a coder subagent by default — async spawn with the design doc as the task book; small / exploratory / interactive changes stay in the main session.
|
|
130
133
|
In engineering mode the spawn looks like: `subagent(role="eng-coder", designId=<id>, designToken=<token>, batchDoc=<batch record path>, task=...)`
|
|
131
|
-
——**`batchDoc` is mandatory** (the batch record path, e.g.
|
|
134
|
+
——**`batchDoc` is mandatory** (the batch record path, e.g. `<repo>/docs/batches/<batch>-<topic>.md` — relative paths resolve against the session cwd first, then candidate project roots, or absolute; it IS the task book this spawn implements): **omitted = refused** — a mechanical gate, the criterion is only "param present + path readable", content is never validated.
|
|
132
135
|
Do not implement sized batches yourself just because you can — the isolated context is what breaks the self-review blind spot.
|
|
133
136
|
- Every dispatch carries a task book with:
|
|
134
137
|
**Goal & why**
|
|
@@ -162,7 +165,7 @@ The single-point pipeline's fixed cost is ~40 minutes — borne alone by one req
|
|
|
162
165
|
|
|
163
166
|
**Lifecycle (event → action — act on the hit, don't wait for the user to push)**:
|
|
164
167
|
1. **Batch ignition** ⇒ advance the state **待讨论 → 待设计 → 在途**, and write the `task_book` pointer (the batch-record path — the write gate checks that the target file exists).
|
|
165
|
-
2. **Implementation verification passed** ⇒ **在途 → 待核销 → 已核销** (two-step migration; `
|
|
168
|
+
2. **Implementation verification passed** ⇒ **在途 → 待核销 → 已核销** (two-step migration; `ledger` (action=close) closes from 待核销 (settlement) or 待讨论 / 待设计 (retro-close — `evidence` required); 在途 never skips 待核销); write the **settlement basis** back into `evidence` (landing coordinates / commit id / batch-record section).
|
|
166
169
|
3. **Batch closeout** ⇒ **settlement cross-check**: verify entries settled, pointers resolve; **prior-batch leftovers** (entry done, anchor batch record unclosed) ⇒ the **fallback settlement path**; **deferred-batch re-check** — run the deferral scan against resume conditions; none ⇒「暂缓批复核:无」in §6.
|
|
167
170
|
4. **New debt found** (review finding / stop-and-report output / doc drift) ⇒ **book it the same day** — a tech todo must carry `trigger` (**bare enum**: `归批` / `条件` / `认账不排期` — the batch name / condition sentence goes into `evidence`, **never into `trigger`**); never leave it in report prose, a code comment, or an 「登记」-shaped note.
|
|
168
171
|
5. **Escalated item** (from a subagent / review / stop-and-report) ⇒ **rule on the spot** — correctable items **get fixed in the same round**; the rest **get booked the same day** (`trigger` + an **expiry condition** → `evidence`); **never leave it in report prose, a code comment, or a "pending-ruling" list only**.
|
|
@@ -174,7 +177,7 @@ Requirement pool and tech todos share one iron law (pointer-ized, no task-detail
|
|
|
174
177
|
|
|
175
178
|
## Parallel dispatch & multi-task
|
|
176
179
|
General parallel rules (stages run in parallel — parallelize aggressively) — see discipline-engineering.
|
|
177
|
-
- **Token isolation**: each design's review pass issues its own designId + token pair (the advisor echoes both in the Approved reply). Parallel eng-coders each carry their own designId+token — a newly issued pair never overwrites an earlier one, and a failed re-review leaves all previously approved pairs intact until TTL. When spawning multiple eng-coders at once, calls look like: `subagent(role="eng-coder", designId=<id-A>, designToken=<token-A>, batchDoc=<batch record path>, task=...)` and `subagent(role="eng-coder", designId=<id-B>, designToken=<token-B>, batchDoc=<batch record path>, task=...)` — one call per design, all in the same reply. `batchDoc` is mandatory on every eng-coder spawn — the batch record path (e.g.
|
|
180
|
+
- **Token isolation**: each design's review pass issues its own designId + token pair (the advisor echoes both in the Approved reply). Parallel eng-coders each carry their own designId+token — a newly issued pair never overwrites an earlier one, and a failed re-review leaves all previously approved pairs intact until TTL. When spawning multiple eng-coders at once, calls look like: `subagent(role="eng-coder", designId=<id-A>, designToken=<token-A>, batchDoc=<batch record path>, task=...)` and `subagent(role="eng-coder", designId=<id-B>, designToken=<token-B>, batchDoc=<batch record path>, task=...)` — one call per design, all in the same reply. `batchDoc` is mandatory on every eng-coder spawn — the batch record path (e.g. `<repo>/docs/batches/<batch>-<topic>.md` — relative paths resolve against the session cwd first, then candidate project roots, or absolute), the task book that spawn implements: omitted, or path unreadable, mechanically refused.
|
|
178
181
|
- **Declare spawn scheduling metadata in the task book**: spawns carry `files` (write domain) and `dependsOn` (prior async ids) — the scheduler gates admission: async spawns overlapping in-flight/queued files queue until the blocker settles; sync spawns conflicting on files error out (no queueing); dependency chains order automatically. Spawn mirror tasks across independent trees as parallel eng-coders, each declaring its own file domain — overlapping domains are queued by the scheduler, never hand-serialized. **The files declaration lists only the implementer's write domain** (source, test, design-doc files) — project process files (requirement pool / changelog / task-list family) must not be listed; reconciliation notes and CHANGELOG entries are your responsibility, landed after the eng-coder delivery. files must be file-level paths (one per file). Directory declarations are NOT supported — they bypass the conflict detector and are rejected with an error.
|
|
179
182
|
- **Submit and go — queuing is the mechanism's job**: spawns always carry `files`/`dependsOn` and are submitted directly — domain conflicts are queued by the scheduler (returns `queued` + position), pool-full is queued by the pool; never hand-keep queues, never release file by file, never delay submission for conflicts/pool-full. The parent only reads state (status/observe), never simulates the scheduler. **Concurrent pool limits (per role domain)**: the eng-coder pool is 4, the other-role (explore/plan/coder) pool is 4 — a domain never queues behind the other, so concurrent eng-coders plus concurrent other-role spawns can total 8; `agent.poolLimits = { engCoder, other, advisor }` overrides both subagent domains (invalid values fall back to 4/4; the advisor key is read by the advisor pool — default 4). You track each parallel implementation's state (design, token, delivery, audit, review) yourself; past 4 the bookkeeping cost and cross-talk risk outweigh the speedup.
|
|
180
183
|
- **User interactions stay one at a time** (clarifications, approvals) — but you MAY fire several review/approval follow-ups in a single response once the user has answered.
|