@thincoder/core 0.10.8 → 0.10.9
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 +6 -0
- package/agent/family-tools.mjs +2 -2
- package/agent/helpers.mjs +6 -6
- package/agent/post-turn.mjs +34 -0
- package/agent/run-stages.mjs +43 -9
- package/agent/setup.mjs +10 -2
- package/agent/turn-loop.mjs +6 -0
- package/agent-tools/audit-block.mjs +4 -2
- package/agent-tools/batch-gate.mjs +198 -0
- package/agent-tools/batch-lifecycle.mjs +216 -36
- package/agent-tools/batch-skeleton.mjs +87 -0
- package/agent-tools/batch.mjs +11 -3
- package/agent-tools/context.mjs +28 -5
- package/agent-tools/design-slots.mjs +7 -33
- package/agent-tools/doc-check.mjs +44 -6
- package/agent-tools/spawn-gates.mjs +100 -41
- package/agent-tools/ssh-session.mjs +108 -61
- package/agent-tools/ssh-transport.mjs +17 -4
- package/agent-tools/ssh-vault.mjs +291 -0
- package/agent-tools/subagent-spawn.mjs +18 -11
- package/agent-tools/subagent.mjs +13 -7
- package/agent-tools/timer.mjs +2 -2
- package/config-defaults.mjs +92 -0
- package/config-normalize.mjs +109 -0
- package/config.mjs +88 -197
- package/doc-check/anchors.mjs +54 -12
- package/doc-check/index.mjs +161 -12
- package/doc-check/version.mjs +84 -0
- package/doc-check/width.mjs +5 -4
- package/i18n.mjs +2 -0
- package/ledger-cmd.mjs +314 -61
- package/ledger-db.mjs +22 -10
- package/ledger-migrate.mjs +15 -9
- package/ledger-tools.mjs +115 -69
- package/ledger.mjs +13 -10
- package/log.mjs +2 -2
- package/manifest-schema.mjs +15 -1
- package/package.json +1 -1
- package/permission.mjs +7 -1
- package/prompts/advisor-design.md +21 -21
- package/prompts/advisor-round1.md +18 -18
- package/prompts/advisor-round2.md +22 -22
- package/prompts/advisor-round3.md +24 -24
- package/prompts/common.md +108 -93
- package/prompts/consult-base.md +8 -8
- package/prompts/discipline-engineering.md +68 -65
- package/prompts/discipline-normal.md +69 -68
- package/prompts/persona-coder.md +6 -6
- package/prompts/persona-eng-coder.md +19 -18
- package/prompts/persona-eng-designer.md +36 -32
- package/prompts/persona-engineering.md +114 -98
- package/prompts/persona-explore.md +2 -2
- package/prompts/persona-normal.md +16 -16
- package/prompts/persona-plan.md +7 -7
- package/prompts/subagent-base.md +4 -4
- package/provider/anthropic.mjs +4 -2
- package/provider/core.mjs +4 -2
- package/provider/google.mjs +5 -1
- package/provider/responses-request.mjs +3 -2
- package/session-lifecycle.mjs +6 -6
- package/team.mjs +2 -1
- package/token-window.mjs +1 -1
- package/tool-docs/batch.md +1 -1
- package/tool-docs/doc_check.md +30 -7
- package/tool-docs/fetch.md +3 -2
- package/tool-docs/ledger.md +7 -5
- package/tool-docs/ssh.md +23 -6
- package/tool-docs/subagent.md +6 -11
- package/tool-docs/websearch.md +3 -1
- package/tools/ssh.mjs +24 -11
- package/tools/web-deepseek.mjs +172 -0
- package/tools/web.mjs +29 -4
package/ledger-tools.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 接线:族出口 = `ledger.mjs`(re-export——消费面 agent/family-tools.mjs;旧读二装配面已清零);
|
|
11
11
|
* 消费侧一律动态 import(`ledger-db.mjs` 静态 import node:sqlite ⇒ W8 契约②)。
|
|
12
12
|
*/
|
|
13
|
-
import { ledgerAdd, ledgerClose, ledgerCount, ledgerQuery, ledgerUpdate } from "./ledger-cmd.mjs"
|
|
13
|
+
import { ledgerAdd, ledgerClose, ledgerCloseBatch, ledgerCount, ledgerQuery, ledgerUpdate, ledgerUpdateBatch } from "./ledger-cmd.mjs"
|
|
14
14
|
import { resolveLedgerRoot } from "./ledger-db.mjs"
|
|
15
15
|
import { DESC } from "./tools/shared.mjs" // #15 描述外置:文本单点 = tool-docs/ledger.md(DESC 单解析面)
|
|
16
16
|
|
|
@@ -37,37 +37,47 @@ const argPreview = (v) => {
|
|
|
37
37
|
/** 枚举取值域文本(P3 / P5 文案同源)。 */
|
|
38
38
|
const enumText = (values) => `{${values.join(", ")}}`
|
|
39
39
|
|
|
40
|
-
/** P3 后缀(声明派生):枚举字段带取值域 / 非枚举必填串带「非空字符串」/
|
|
41
|
-
|
|
40
|
+
/** P3 后缀(声明派生):枚举字段带取值域 / 非枚举必填串带「非空字符串」/ 数字字段仅「必填」
|
|
41
|
+
* (`domain` = 按形条件判据给出的取值域——`whenPresent.enum` 优先于基本形)。 */
|
|
42
|
+
const requiredSuffix = (spec, domain) => (domain ? `;取值 ∈ ${enumText(domain)}` : spec.type === "string" ? ";非空字符串" : "")
|
|
43
|
+
|
|
44
|
+
/** 值非空字段(P6 字段级:空串 / 全空白 ⇒ 拒——声明面不表达值级判据,与 `title` 同源一处)。 */
|
|
45
|
+
const NON_EMPTY_FIELDS = ["title", "resume_when"]
|
|
42
46
|
|
|
43
47
|
/**
|
|
44
|
-
* 工具层参数守卫(设计档 §3.2 · Gitee #IKIQGK / 台账 #472):判序 P1–
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* ⇒ `{}`)。非法 ⇒ throw——判于核函数之前(零写、零库动作)。
|
|
48
|
+
* 工具层参数守卫(设计档 §3.2 · Gitee #IKIQGK / 台账 #472):判序 P1–P7,文案逐字 = §3.2 模板;
|
|
49
|
+
* 消费工具自身声明派生(`required` / `type` / `enum` / `oneOfRequired` / `whenPresent`——枚举 / 必填 /
|
|
50
|
+
* 类型 / 组判据不在守卫内复写);只判不改(零缺省填充 / 零类型转换 / 零文本改写),返回入参对象
|
|
51
|
+
* (唯一归一 = 判序① 的缺省 / `null` ⇒ `{}`)。非法 ⇒ throw——判于核函数之前(零写、零库动作)。
|
|
52
|
+
* 按形条件判据(`whenPresent`):`when` 字段在本笔入参**在场**(非 `null`)⇒ 该条生效(`required` 按
|
|
53
|
+
* P3 判、`enum` 按 P5 判并取代基本形取值域);不在场 ⇒ 本条不生效、按基本形判。
|
|
48
54
|
*/
|
|
49
55
|
function assertToolArgs(tool, args) {
|
|
50
56
|
const name = tool.name
|
|
51
57
|
const decl = tool.parameters ?? {}
|
|
52
58
|
const props = decl.properties ?? {}
|
|
53
59
|
const fields = Object.keys(props)
|
|
60
|
+
// 组判据两键居声明条目顶层(不入 `parameters`——自定义键不随声明交模型 / provider)
|
|
61
|
+
const conditional = tool.whenPresent ?? []
|
|
54
62
|
// P1:入参缺省 / `null` ⇒ 视作 `{}`;非对象(数组 / 标量)⇒ 拒
|
|
55
63
|
if (args == null) args = {}
|
|
56
64
|
else if (typeof args !== "object" || Array.isArray(args)) throw new Error(`${name}:参数须为对象(收到 ${argPreview(args)})`)
|
|
57
65
|
// P2:未知键 ⇒ 拒(判序先于缺参——typo 键先给可用参数集)
|
|
58
66
|
const unknown = Object.keys(args).filter((k) => !Object.hasOwn(props, k))
|
|
59
67
|
if (unknown.length > 0) throw new Error(`${name}:未知参数:${unknown.join(" / ")}(可用参数 = ${fields.join(" / ")})`)
|
|
60
|
-
// P3–P6:逐字段(声明序)——缺失(必填;显式 `null` 同判)→ 类型 / 枚举 →
|
|
68
|
+
// P3–P6:逐字段(声明序)——缺失(必填;显式 `null` 同判)→ 类型 / 枚举 → 值非空
|
|
61
69
|
for (const field of fields) {
|
|
62
70
|
const spec = props[field]
|
|
63
71
|
const value = args[field]
|
|
64
|
-
const
|
|
72
|
+
const rule = conditional.find((c) => c.field === field && args[c.when] != null)
|
|
73
|
+
const required = rule?.required ?? (decl.required ?? []).includes(field)
|
|
74
|
+
const domain = rule?.enum ?? spec.enum
|
|
65
75
|
if (value === undefined || value === null) {
|
|
66
|
-
if (required) throw new Error(`${name}:${field} 缺失(必填${requiredSuffix(spec)})`)
|
|
76
|
+
if (required) throw new Error(`${name}:${field} 缺失(必填${requiredSuffix(spec, domain)})`)
|
|
67
77
|
continue // 可空字段 `null` ⇒ 放行(≡ 略去——与 `patch.x ?? 行值` 既有语义同源)
|
|
68
78
|
}
|
|
69
|
-
if (
|
|
70
|
-
if (!
|
|
79
|
+
if (domain) {
|
|
80
|
+
if (!domain.includes(value)) throw new Error(`${name}:${field} 非法:${argPreview(value)}(取值 ∈ ${enumText(domain)})`)
|
|
71
81
|
} else if (spec.type === "string" && typeof value !== "string") {
|
|
72
82
|
throw new Error(`${name}:${field} 非法:${argPreview(value)}(应为字符串)`)
|
|
73
83
|
} else if (spec.type === "number" && typeof value !== "number") {
|
|
@@ -75,10 +85,16 @@ function assertToolArgs(tool, args) {
|
|
|
75
85
|
} else if (spec.type === "boolean" && typeof value !== "boolean") {
|
|
76
86
|
throw new Error(`${name}:${field} 非法:${argPreview(value)}(应为布尔)`)
|
|
77
87
|
}
|
|
78
|
-
if (field
|
|
88
|
+
if (NON_EMPTY_FIELDS.includes(field) && typeof value === "string" && value.trim() === "") {
|
|
79
89
|
throw new Error(`${name}:${field} 为空(${required ? "必填;" : ""}非空字符串)`)
|
|
80
90
|
}
|
|
81
91
|
}
|
|
92
|
+
// P7:组判据(声明派生 `oneOfRequired`)——组内恰携其一 ⇒ 过;同携 / 双缺 ⇒ 拒
|
|
93
|
+
for (const group of tool.oneOfRequired ?? []) {
|
|
94
|
+
if (group.filter((f) => args[f] != null).length === 1) continue
|
|
95
|
+
const received = Object.fromEntries(group.map((f) => [f, args[f] ?? null]))
|
|
96
|
+
throw new Error(`${name}:${group.join(" ∥ ")} 须恰携其一(收到 ${argPreview(received)})`)
|
|
97
|
+
}
|
|
82
98
|
return args
|
|
83
99
|
}
|
|
84
100
|
|
|
@@ -91,68 +107,90 @@ const READ_ACTIONS = ["query", "count"]
|
|
|
91
107
|
/** cwd 参数描述(五前身声明同文——文本单点)。 */
|
|
92
108
|
const CWD_DESC = "本次动作的目标路径——项目根由它推导:沿祖先链最近的项目目录(带 PROJECT-MANIFEST.json 或已有台账库);容器目录下收敛到子项目。台账按项目根关联存储于用户数据目录(工作树外、不进 git)。相对路径按当前工作目录解析;查/写别的项目请给绝对路径。缺省 = 当前会话锚"
|
|
93
109
|
|
|
94
|
-
/** 各 action 声明条目(= 前身旧工具 `parameters` 逐字;伪工具对象守卫 + 弃用壳共用单源)。
|
|
110
|
+
/** 各 action 声明条目(= 前身旧工具 `parameters` 逐字;伪工具对象守卫 + 弃用壳共用单源)。
|
|
111
|
+
* `parameters` 内的键 = 交模型 / provider 的那一份声明;**组判据两键(`oneOfRequired` / `whenPresent`)
|
|
112
|
+
* 居本条条目顶层、不入 `parameters`**——自定义键不随声明外泄(provider 面零风险),守卫仍只读声明。 */
|
|
95
113
|
const ACTION_SPECS = {
|
|
96
114
|
add: {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
115
|
+
parameters: {
|
|
116
|
+
type: "object",
|
|
117
|
+
required: ["kind", "title"],
|
|
118
|
+
properties: {
|
|
119
|
+
cwd: { type: "string", description: CWD_DESC },
|
|
120
|
+
kind: { type: "string", enum: ["requirement", "tech_todo"], description: "类别" },
|
|
121
|
+
title: { type: "string", description: "条目标题" },
|
|
122
|
+
board: { type: "string", description: "归属板块(需求档文档名,可空)" },
|
|
123
|
+
req_doc: { type: "string", description: "需求档指针(可空)" },
|
|
124
|
+
task_book: { type: "string", description: "任务书指针(可空;在途 / 待核销必填——咬合 CHECK)" },
|
|
125
|
+
evidence: { type: "string", description: "最小证据行(可空)" },
|
|
126
|
+
trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "技术待办触发(可空)" },
|
|
127
|
+
resume_when: { type: "string", description: "复启条件句(可空;`trigger=条件` 行的一句话条件 + 可选锚 `@YYYY-MM-DD` ∥ `@事件件名`)——值非空:空串 / 全空白 ⇒ 拒" },
|
|
128
|
+
},
|
|
129
|
+
additionalProperties: false,
|
|
108
130
|
},
|
|
109
|
-
additionalProperties: false,
|
|
110
131
|
},
|
|
111
132
|
update: {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
133
|
+
parameters: {
|
|
134
|
+
type: "object",
|
|
135
|
+
properties: {
|
|
136
|
+
cwd: { type: "string", description: CWD_DESC },
|
|
137
|
+
id: { type: "number", description: "条目 id(与 batch 恰携其一)" },
|
|
138
|
+
batch: { type: "string", description: "批档指针(与 id 恰携其一)——批级批量:一次结算该批的行;携 status ⇒ 逐行按迁移表重判,不携 status ⇒ 纯字段补丁(status 零写)" },
|
|
139
|
+
status: { type: "string", enum: ["待讨论", "待设计", "在途", "待核销", "已核销", "已废弃"], description: "目标状态(六态之一,缺省 = 不变)" },
|
|
140
|
+
title: { type: "string", description: "标题(缺省 = 不变)" },
|
|
141
|
+
board: { type: "string", description: "归属板块(缺省 = 不变)" },
|
|
142
|
+
req_doc: { type: "string", description: "需求档指针(缺省 = 不变)" },
|
|
143
|
+
task_book: { type: "string", description: "任务书指针(缺省 = 不变)" },
|
|
144
|
+
evidence: { type: "string", description: "证据(缺省 = 不变;传入 = 追加——旧值非空 ⇒ 拼 `旧|新`(「|」零空格)、旧空直写;新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传)" },
|
|
145
|
+
evidenceReplace: { type: "boolean", description: "覆盖旗(布尔):与 evidence 同传 = 整段替换(旧文零保留);缺省 / false = 追加;旗独传(无新文)⇒ 拒" },
|
|
146
|
+
trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "触发(缺省 = 不变)" },
|
|
147
|
+
resume_when: { type: "string", description: "复启条件句(缺省 = 不变;值非空——空串 / 全空白 ⇒ 拒)" },
|
|
148
|
+
executor: { type: "string", description: "执行者 sessionId(可选——接手改写归属用;缺省 = 按状态迁移语义:进入待设计 / 在途自动写本会话;出在途 / 撤回清空)" },
|
|
149
|
+
},
|
|
150
|
+
additionalProperties: false,
|
|
126
151
|
},
|
|
127
|
-
|
|
152
|
+
oneOfRequired: [["id", "batch"]],
|
|
128
153
|
},
|
|
129
154
|
close: {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
155
|
+
parameters: {
|
|
156
|
+
type: "object",
|
|
157
|
+
required: ["status"],
|
|
158
|
+
properties: {
|
|
159
|
+
cwd: { type: "string", description: CWD_DESC },
|
|
160
|
+
id: { type: "number", description: "条目 id(与 batch 恰携其一)" },
|
|
161
|
+
batch: { type: "string", description: "批档指针(与 id 恰携其一)——批级批量结算:只销已到 待核销 的行;待讨论 / 待设计 点名跳过(追认核销不入批量面)" },
|
|
162
|
+
status: { type: "string", enum: ["已核销", "已废弃"], description: "目标态(勾销 / 追认核销 / 撤回)——携 batch 时可不给(缺省 = 勾销单值),给了只能是 已核销" },
|
|
163
|
+
evidence: { type: "string", description: "最小证据行(可选;传入 = 追加缺省——旧值非空 ⇒ 拼 `旧|新`(「|」零空格)、旧空直写;新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传。追认核销面判结果值须非空:缺 / 全空白 ⇒ 拒)" },
|
|
164
|
+
evidenceReplace: { type: "boolean", description: "覆盖旗(布尔):与 evidence 同传 = 整段替换(旧文零保留);缺省 / false = 追加;旗独传(无新文)⇒ 拒" },
|
|
165
|
+
},
|
|
166
|
+
additionalProperties: false,
|
|
138
167
|
},
|
|
139
|
-
|
|
168
|
+
oneOfRequired: [["id", "batch"]],
|
|
169
|
+
whenPresent: [{ when: "batch", field: "status", required: false, enum: ["已核销"] }],
|
|
140
170
|
},
|
|
141
171
|
query: {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
172
|
+
parameters: {
|
|
173
|
+
type: "object",
|
|
174
|
+
properties: {
|
|
175
|
+
cwd: { type: "string", description: CWD_DESC },
|
|
176
|
+
status: { type: "string", enum: ["待讨论", "待设计", "在途", "待核销", "已核销", "已废弃"], description: "按状态过滤(六态之一,缺省 = 不过滤)" },
|
|
177
|
+
kind: { type: "string", enum: ["requirement", "tech_todo"], description: "按类别过滤" },
|
|
178
|
+
board: { type: "string", description: "按归属板块过滤(需求档文档名)" },
|
|
179
|
+
trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "按技术待办触发过滤(三枚举,缺省 = 不过滤——无触发(NULL)不过滤)" },
|
|
180
|
+
task_book: { type: "string", description: "按任务书指针过滤(原值等值——批→行反查;缺省 = 不过滤)" },
|
|
181
|
+
},
|
|
182
|
+
additionalProperties: false,
|
|
149
183
|
},
|
|
150
|
-
additionalProperties: false,
|
|
151
184
|
},
|
|
152
185
|
count: {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
186
|
+
parameters: {
|
|
187
|
+
type: "object",
|
|
188
|
+
properties: {
|
|
189
|
+
cwd: { type: "string", description: CWD_DESC },
|
|
190
|
+
task_book: { type: "string", description: "按任务书指针过滤(原值等值)" },
|
|
191
|
+
},
|
|
192
|
+
additionalProperties: false,
|
|
193
|
+
},
|
|
156
194
|
},
|
|
157
195
|
}
|
|
158
196
|
|
|
@@ -171,8 +209,10 @@ const UNION_PARAMETERS = {
|
|
|
171
209
|
evidence: { type: "string", description: "最小证据行(add 可空;update / close 传入 = 追加缺省——旧空直写、旧非空拼 `旧|新`(「|」零空格)、新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传;update 缺省 = 不变 / close 缺省 = 判行值;追认核销须非空)" },
|
|
172
210
|
evidenceReplace: { type: "boolean", description: "覆盖旗(布尔):与 evidence 同传 = 整段替换;缺省 / false = 追加;旗独传(无新文)⇒ 拒(update / close)" },
|
|
173
211
|
trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "技术待办触发(add / update 可空 / query 过滤)" },
|
|
174
|
-
|
|
175
|
-
|
|
212
|
+
resume_when: { type: "string", description: "复启条件句(add / update 可空;值非空——空串 / 全空白 ⇒ 拒;`trigger=条件` 行的一句话条件 + 可选锚 `@YYYY-MM-DD` ∥ `@事件件名`)" },
|
|
213
|
+
id: { type: "number", description: "条目 id(update / close:与 batch 恰携其一)" },
|
|
214
|
+
batch: { type: "string", description: "批档指针(update / close:与 id 恰携其一)——批级批量:一次结算该批的行(update 携 status ⇒ 逐行迁移表重判;不携 status ⇒ 纯字段补丁;close 只销 待核销 行、status 可空)" },
|
|
215
|
+
status: { type: "string", enum: ["待讨论", "待设计", "在途", "待核销", "已核销", "已废弃"], description: "状态(单行 close 必填 ∈ {已核销, 已废弃};close 携 batch 时可空、给了只能是 已核销;update 迁移 / query 过滤——六态之一)" },
|
|
176
216
|
executor: { type: "string", description: "执行者 sessionId(update 可选——接手改写归属;缺省 = 按状态迁移语义:进入待设计 / 在途自动写本会话;出在途 / 撤回清空)" },
|
|
177
217
|
},
|
|
178
218
|
additionalProperties: false,
|
|
@@ -210,8 +250,10 @@ function makeLedgerTool(actions, { readonly = false, writeFace = false } = {}) {
|
|
|
210
250
|
// ② 入口级 action 判(P3 缺失 / P5 非法——消费 action;取值域 = 本装配面枚举)
|
|
211
251
|
const { action, ...rest0 } = args
|
|
212
252
|
assertToolArgs(entryTool, { action })
|
|
213
|
-
// ③ 余键喂该 action 伪工具对象(声明源 = 前身工具 parameters 逐字——`action` 键不入 P2
|
|
214
|
-
|
|
253
|
+
// ③ 余键喂该 action 伪工具对象(声明源 = 前身工具 parameters 逐字——`action` 键不入 P2 域;
|
|
254
|
+
// 组判据两键在条目顶层,与 `parameters` 同送守卫)
|
|
255
|
+
const spec = ACTION_SPECS[action]
|
|
256
|
+
const rest = assertToolArgs({ name: `ledger(${action})`, ...spec }, rest0)
|
|
215
257
|
// ④ 路由既有核函数(包装细节携自原工具 execute 逐字保留:cwd 缺省 / JSON 缩进 / getSessionId 动态 import)
|
|
216
258
|
switch (action) {
|
|
217
259
|
case "add": {
|
|
@@ -221,7 +263,7 @@ function makeLedgerTool(actions, { readonly = false, writeFace = false } = {}) {
|
|
|
221
263
|
return JSON.stringify({ id, status: "待讨论", ...reportedRoot(target) })
|
|
222
264
|
}
|
|
223
265
|
case "update": {
|
|
224
|
-
const { cwd, id, ...patch } = rest
|
|
266
|
+
const { cwd, id, batch, ...patch } = rest
|
|
225
267
|
const target = cwd ?? cwdOf(ctx)
|
|
226
268
|
let executorSessionId
|
|
227
269
|
if (patch.executor == null) {
|
|
@@ -229,17 +271,21 @@ function makeLedgerTool(actions, { readonly = false, writeFace = false } = {}) {
|
|
|
229
271
|
const { getSessionId } = await import("./session-slots.mjs")
|
|
230
272
|
executorSessionId = getSessionId()
|
|
231
273
|
}
|
|
274
|
+
// 批级批量(§3.3)——`batch` 与 `id` 恰携其一(守卫 P7 已先裁)
|
|
275
|
+
if (batch != null) return JSON.stringify({ ...ledgerUpdateBatch({ cwd: target, batch, patch, executorSessionId }), ...reportedRoot(target) })
|
|
232
276
|
return JSON.stringify({ ...ledgerUpdate({ cwd: target, id, patch, executorSessionId }), ...reportedRoot(target) })
|
|
233
277
|
}
|
|
234
278
|
case "close": {
|
|
235
279
|
const target = rest.cwd ?? cwdOf(ctx)
|
|
280
|
+
// 批级批量结算(§3.3——勾销径)
|
|
281
|
+
if (rest.batch != null) return JSON.stringify({ ...ledgerCloseBatch({ cwd: target, batch: rest.batch, evidence: rest.evidence, evidenceReplace: rest.evidenceReplace }), ...reportedRoot(target) })
|
|
236
282
|
return JSON.stringify({ ...ledgerClose({ cwd: target, id: rest.id, status: rest.status, evidence: rest.evidence, evidenceReplace: rest.evidenceReplace }), ...reportedRoot(target) })
|
|
237
283
|
}
|
|
238
284
|
case "query":
|
|
239
|
-
return JSON.stringify(ledgerQuery({ cwd: rest.cwd ?? cwdOf(ctx), status: rest.status, kind: rest.kind, board: rest.board, trigger: rest.trigger }), null, 2)
|
|
285
|
+
return JSON.stringify(ledgerQuery({ cwd: rest.cwd ?? cwdOf(ctx), status: rest.status, kind: rest.kind, board: rest.board, trigger: rest.trigger, task_book: rest.task_book }), null, 2)
|
|
240
286
|
case "count": {
|
|
241
287
|
const target = rest.cwd ?? cwdOf(ctx)
|
|
242
|
-
return JSON.stringify({
|
|
288
|
+
return JSON.stringify({ ...ledgerCount({ cwd: target, task_book: rest.task_book }), ...reportedRoot(target) })
|
|
243
289
|
}
|
|
244
290
|
default:
|
|
245
291
|
// 不可达(② 枚举守门已先拒)——fail-loud 兜底(防未来变体增量 drift)
|
|
@@ -262,7 +308,7 @@ const deprecatedLine = (old, action) => `${old} 已退役(本名不再装配
|
|
|
262
308
|
const deprecatedShell = (old, action, readonly) => ({
|
|
263
309
|
name: old,
|
|
264
310
|
description: deprecatedLine(old, action),
|
|
265
|
-
parameters: ACTION_SPECS[action],
|
|
311
|
+
parameters: ACTION_SPECS[action].parameters,
|
|
266
312
|
readonly,
|
|
267
313
|
async execute() { throw new Error(deprecatedLine(old, action)) },
|
|
268
314
|
})
|
package/ledger.mjs
CHANGED
|
@@ -65,9 +65,11 @@ export function discoverFamily(anchor) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/** scan 组装(SQLite 行集 + 计数单源 → scan 对象)——形状契约(设计档 §2.2):pool/tech/aged/
|
|
68
|
-
* agedKeys/agedTitles/thresholdReached/boards/actionable/root/name/ledger。行龄 = 时间戳
|
|
68
|
+
* agedKeys/agedTitles/overdue/thresholdReached/boards/actionable/root/name/ledger。行龄 = 时间戳
|
|
69
69
|
* (updated_at ?? created_at 距今 > days;未知不计——零假阳降级,同 v1 行龄未知口径)。
|
|
70
70
|
* `now`/`days` = 注入面(确定性用例——替代 v1 ageOf git 注入面)。
|
|
71
|
+
* **`days` 只作用于展示面 `aged`**(本函数局部阈值);`overdue`(出口态)恒按核常量 `AGING_DAYS`
|
|
72
|
+
* (`overdueOf` 单源)——故 `days` 调小时 `aged` 会先于 `overdue` 涨(两数口径不同,非矛盾)。
|
|
71
73
|
* **根推导**(设计档 §3.8):`root` / `name` / `ledger` = `resolveLedgerRoot(anchor)` 推导所得项目根(非锚
|
|
72
74
|
* ——容器 ∥ 工作区锚取收敛序之根)——与行集读取同一单源;无根 ⇒ `ledger` = `null`(不落兜底根——
|
|
73
75
|
* 该态下 `root` / `name` 回落锚、行集为空,且该 scan 不进族枚举(`discoverFamily` 容器支只列已注册子目录))。
|
|
@@ -77,7 +79,7 @@ export function buildScan({ cwd, days = AGING_DAYS, now = Date.now() } = {}) {
|
|
|
77
79
|
const anchor = resolve(cwd)
|
|
78
80
|
const derived = resolveLedgerRoot(anchor)
|
|
79
81
|
const root = derived ? derived.root : anchor
|
|
80
|
-
const rows = ledgerQuery({ cwd: anchor })
|
|
82
|
+
const rows = ledgerQuery({ cwd: anchor, now }) // `now` 透传(两读数同时钟——`overdue` 的行键由核同一 `now` 算)
|
|
81
83
|
const pending = rows.filter((r) => PENDING_STATUSES.includes(r.status))
|
|
82
84
|
const poolEntries = pending.filter((r) => r.kind === "requirement")
|
|
83
85
|
const techEntries = pending.filter((r) => r.kind === "tech_todo")
|
|
@@ -98,6 +100,7 @@ export function buildScan({ cwd, days = AGING_DAYS, now = Date.now() } = {}) {
|
|
|
98
100
|
return {
|
|
99
101
|
name: basename(root), root, ledger: derived ? ledgerDbPath(root) : null,
|
|
100
102
|
pool: poolEntries.length, tech: techEntries.length, aged: aged.length,
|
|
103
|
+
overdue: pending.filter((r) => r.overdue != null).length, // 待处置(§3.7——机判单源 = 行集 `overdue` 键,行值与 `now` 均同源)
|
|
101
104
|
agedKeys: aged.map((r) => normalizeEntry(r.title)), agedTitles: aged.map((r) => entryTitle(r.title)),
|
|
102
105
|
thresholdReached, boards, actionable: aged.length > 0 || thresholdReached,
|
|
103
106
|
inflightExecutors,
|
|
@@ -113,8 +116,8 @@ export function formatMarker(scan) {
|
|
|
113
116
|
* `root` = `family.current.root` 的 scan(命中但不在 `scans`——不可读 ⇒ 空范围,不掺兄弟);
|
|
114
117
|
* `current` 缺席(容器根锚)⇒ 范围 = `scans` 全体(族内已读项目——构建期不可读已跳过)。
|
|
115
118
|
* 空范围 ⇒ `{ marker: null, warn: false }`(不落 `0·0`);范围非空 ⇒ `marker` = `formatMarker`
|
|
116
|
-
* 逐字(两池分列求和——F1);`warn` = 范围内任一项 `
|
|
117
|
-
*
|
|
119
|
+
* 逐字(两池分列求和——F1);`warn` = 范围内任一项 `overdue>0 ∨ deadExecutors>0`(§3.7——`aged ⊂
|
|
120
|
+
* overdue` ⇒ 警示色不缩;端零重算)。消费面 = 核拍面(`ledger-surface.mjs`)∥ VSC item。 */
|
|
118
121
|
export function scopeMarkerOf(scans, family) {
|
|
119
122
|
const list = Array.isArray(scans) ? scans : []
|
|
120
123
|
const currentRoot = family?.current?.root ?? null
|
|
@@ -126,15 +129,15 @@ export function scopeMarkerOf(scans, family) {
|
|
|
126
129
|
for (const s of range) {
|
|
127
130
|
pool += s.pool
|
|
128
131
|
tech += s.tech
|
|
129
|
-
if (s.
|
|
132
|
+
if (s.overdue > 0 || s.deadExecutors > 0) warn = true
|
|
130
133
|
}
|
|
131
134
|
return { marker: formatMarker({ pool, tech }), warn }
|
|
132
135
|
}
|
|
133
136
|
|
|
134
|
-
/** L2 明细行(逐字——`(老化 <n
|
|
135
|
-
*
|
|
137
|
+
/** L2 明细行(逐字——`(老化 <n> · 待处置 <m>)` 两数恒显(含 0——§3.7 出口面恒显位扩);
|
|
138
|
+
* 阈值达成加 ` — 可开批`;判活尾段 = executorTail 三态——在途 executor 缺席时尾段空串)。 */
|
|
136
139
|
export function formatDetailLine(scan) {
|
|
137
|
-
return `台账 ${scan.name}:需求池 ${scan.pool} · 技术待办 ${scan.tech}(老化 ${scan.aged})${scan.thresholdReached ? " — 可开批" : ""}${executorTail(scan)}`
|
|
140
|
+
return `台账 ${scan.name}:需求池 ${scan.pool} · 技术待办 ${scan.tech}(老化 ${scan.aged} · 待处置 ${scan.overdue})${scan.thresholdReached ? " — 可开批" : ""}${executorTail(scan)}`
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
/** 明细行集 = {current} ∪ {可动作项目}(发现序——current 在前,其余按目录名升序;同项去重)。 */
|
|
@@ -148,8 +151,8 @@ export function detailScans(scans, current) {
|
|
|
148
151
|
// ── re-export(拆分件接口——命令面接线 = 动态 import 本档,KD-M2-3;`AGING_DAYS` 常量单源 = `ledger-db.mjs`
|
|
149
152
|
// ——2026-10-07 批 ledger-tool 自本档迁入,公共面零变;`ledgerChildren` / `MAX_SIBLING_SCAN` / `resolveLedgerRoot`
|
|
150
153
|
// ——2026-10-16 批 ledger-root-derivation 随迁 / 新增,公共面形零改) ──
|
|
151
|
-
export { AGING_DAYS, ALLOWED_MIGRATIONS, MAX_SIBLING_SCAN, ledgerChildren, ledgerDbPath, ledgerKey, ledgerDirPath, openLedger, PENDING_STATUSES, resolveLedgerRoot, _setLedgerDirForTest, _resetLedgerDirForTest, ensureExecutorColumn } from "./ledger-db.mjs"
|
|
152
|
-
export { ledgerAdd, ledgerClose, ledgerCount, ledgerQuery, ledgerUpdate } from "./ledger-cmd.mjs"
|
|
154
|
+
export { AGING_DAYS, ALLOWED_MIGRATIONS, EVIDENCE_SOFT_LIMIT, MAX_SIBLING_SCAN, ledgerChildren, ledgerDbPath, ledgerKey, ledgerDirPath, openLedger, PENDING_STATUSES, resolveLedgerRoot, _setLedgerDirForTest, _resetLedgerDirForTest, ensureColumn, ensureExecutorColumn } from "./ledger-db.mjs"
|
|
155
|
+
export { ledgerAdd, ledgerBatchRows, ledgerClose, ledgerCloseBatch, ledgerCount, ledgerEvidenceOversize, ledgerQuery, ledgerReanchorBatch, ledgerUpdate, ledgerUpdateBatch, overdueOf } from "./ledger-cmd.mjs"
|
|
153
156
|
export { ledgerTool, ledgerReadTool, ledgerAddTool, ledgerCloseTool, ledgerCountTool, ledgerQueryTool, ledgerUpdateTool } from "./ledger-tools.mjs"
|
|
154
157
|
export { runLedgerAudit, runLedgerMigrate } from "./ledger-migrate.mjs"
|
|
155
158
|
export { resolveExecutorStates, executorTail, _setExecutorProbeTtlForTest } from "./ledger-executors.mjs"
|
package/log.mjs
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
* → 整个字段丢弃;
|
|
19
19
|
* ② 内容只扫密钥形态(sk-xxx / Bearer xxx / key=… / token 值形等)→ 截断到形态之前
|
|
20
20
|
* ——宁可丢信息不漏密钥。
|
|
21
|
-
* token 值形(`uuid:epoch`——2026-10-14
|
|
22
|
-
*
|
|
21
|
+
* token 值形(`uuid:epoch`——2026-10-14 批并入):工具结果经 `tool:done` head 落盘
|
|
22
|
+
* 时在形态前截断、不落值(与 batch.mjs 凭据第三形同判据)。
|
|
23
23
|
* 工具事件不记 args;URL 不入事件(llm/tool 事件从不携带 URL;err 文本经形态扫描)。
|
|
24
24
|
* - 摘要截断(B 方案,2026-09-03 用户裁定):截断处带 "…" 标记(截后仍 ≤上限)。
|
|
25
25
|
* - 测试隔离:node --test 进程(NODE_TEST_CONTEXT)默认不写盘——防测试事件污染真实
|
package/manifest-schema.mjs
CHANGED
|
@@ -25,6 +25,20 @@ export function isValidDocRootValue(value) {
|
|
|
25
25
|
return value.every((p) => typeof p === "string" && p.trim() !== "")
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** `checkConfig.anchors.roots` **单元素值形判据**(判据单源——解析根 = 相对项目根的目录):
|
|
29
|
+
* 合法 ⇒ `null`;非法 ⇒ 理由句(非空字符串 ∥ 相对路径 ∥ 无 `..` 越界段)。
|
|
30
|
+
* 消费面两处:产品引擎 `doc-check/anchors.mjs`(报告态逐元素弃用)∥ `doc-check/index.mjs`
|
|
31
|
+
* (gate 面 `configError` 拒,fail-closed)。
|
|
32
|
+
* @param {unknown} value 解析根元素
|
|
33
|
+
* @returns {string|null}
|
|
34
|
+
*/
|
|
35
|
+
export function anchorRootError(value) {
|
|
36
|
+
if (typeof value !== "string" || value.trim() === "") return "元素须为非空字符串"
|
|
37
|
+
if (/^(?:[A-Za-z]:[\\/]|[\\/])/.test(value)) return "元素须为相对项目根的路径(不得为绝对路径)"
|
|
38
|
+
if (value.split(/[\\/]/).includes("..")) return "元素不得含 `..` 越界段"
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
/** 默认八键(架构 §2.3 E1 五键 + 三族声明键——KD-M1-31)——整档初始化的写源与缺键 fallback 的补源。 */
|
|
29
43
|
export const DEFAULT_MANIFEST = Object.freeze({
|
|
30
44
|
version: 1,
|
|
@@ -41,7 +55,7 @@ export const DEFAULT_MANIFEST = Object.freeze({
|
|
|
41
55
|
scanDirs: Object.freeze(["docs"]),
|
|
42
56
|
lineWidth: 300,
|
|
43
57
|
widthExemptZones: Object.freeze([]),
|
|
44
|
-
anchors: Object.freeze({ domain: "docs", exclude: Object.freeze(["_archive", "batches"]) }),
|
|
58
|
+
anchors: Object.freeze({ domain: "docs", exclude: Object.freeze(["_archive", "batches"]), roots: Object.freeze([]) }),
|
|
45
59
|
exemptions: Object.freeze([]),
|
|
46
60
|
}),
|
|
47
61
|
// 项目声明三族(2026-09-27 声明载体唯一化批并入——键名 / 形态逐字承前;来源档已退役)。
|
package/package.json
CHANGED
package/permission.mjs
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { createInterface } from "node:readline"
|
|
16
16
|
import { lastPageUrl } from "./browser/session.mjs"
|
|
17
|
+
import { composeTaskBookBrief } from "./agent-tools/spawn-gates.mjs"
|
|
17
18
|
|
|
18
19
|
/** CLI tool arg summary (truncated long JSON) */
|
|
19
20
|
export function summarize(toolArgs) {
|
|
@@ -34,7 +35,7 @@ export function formatPermission(name, args) {
|
|
|
34
35
|
return `${args.path}\n${oldLines}\n ↓\n${newLines}`
|
|
35
36
|
}
|
|
36
37
|
if (base === "delete") return `${args.path}${args.force ? "(force:跟踪文件也删)" : ""}`
|
|
37
|
-
if (base === "subagent") return cap(args.task
|
|
38
|
+
if (base === "subagent") return cap(composeTaskBookBrief(args, args.role) || args.task || "", 500)
|
|
38
39
|
if (base === "memory") {
|
|
39
40
|
// §6 action-routed preview: put shows content, batch delete/clear show the gate args
|
|
40
41
|
const action = String(args.action ?? "")
|
|
@@ -86,6 +87,11 @@ export function formatPermission(name, args) {
|
|
|
86
87
|
}
|
|
87
88
|
if (action === "send") return `${sid} — 将发送:\n${cap(String(args.data ?? ""), 500)}`
|
|
88
89
|
if (action === "secret_input") return `${sid} — 将发送:••••••(携密——值不回显)${args.label ? `(${args.label})` : ""}`
|
|
90
|
+
if (action === "forget") {
|
|
91
|
+
// ⑫ 删除面:明示目标主机(无值——保存件不含可展示的凭据值)
|
|
92
|
+
const target = args.host ? `${args.host}${args.port ? `:${args.port}` : ""}` : "(missing host)"
|
|
93
|
+
return cap(`${target} — 删除已保存的 ssh 凭据`, 300)
|
|
94
|
+
}
|
|
89
95
|
if (action === "close") return `${sid} — 关闭会话`
|
|
90
96
|
return cap(`${action} ${sid}`, 300)
|
|
91
97
|
}
|
|
@@ -3,10 +3,10 @@ You are an independent design reviewer for a design review inside an engineering
|
|
|
3
3
|
|
|
4
4
|
## Your role (identity — read before the criteria)
|
|
5
5
|
You are an INDEPENDENT REVIEWER — authority in judgment, not in decisions.
|
|
6
|
-
1. **Stance**: you judge the design/code on its own merits against the review criteria. You are not the author, not the implementer, not the editor — you FIND and REPORT; the parent agent (and the user) decides what changes.
|
|
7
|
-
2. **Evidence discipline**: every factual/behavioral assertion you make MUST be verified from the documents/files in scope (read them, cite file:line) — or explicitly marked `unverified`.
|
|
8
|
-
3. **Boundary**: your review target = the review-object declaration (type / target / status / reason / exclude) + the documents in the review scope
|
|
9
|
-
4. **Neutrality**:
|
|
6
|
+
1. **Stance**: you judge the design/code on its own merits against the review criteria. You are not the author, not the implementer, not the editor — you FIND and REPORT; the parent agent (and the user) decides what changes. Writing replacement text or patch code in your findings would make you the author of what you judge, so the suggestion column stays advisory guidance — you evidence and recommend, and the rewrite belongs to the parent agent.
|
|
7
|
+
2. **Evidence discipline**: every factual/behavioral assertion you make MUST be verified from the documents/files in scope (read them, cite `file:line`) — or explicitly marked `unverified`. An assertion has two sources only: what you read (cite `file:line`) or an explicit `unverified` — "Known behavior…" and "I'm confident…" rest on memory, which has a cut-off and misremembers, so no one can check a remembered API answer; where the source is readable, a behavioral question is an EVIDENCE question, not a reasoning question.
|
|
8
|
+
3. **Boundary**: your review target = the review-object declaration (type / target / status / reason / exclude) + the documents in the review scope; a finding outside that surface has nobody in this round to dispose of it and dilutes the object, so the target does not expand. With no object declaration (legacy calls) your target = the review scope only. Out-of-scope: not looked at; report it only when it **blocks the judgment** — that is the **task being blocked**, not a finding.
|
|
9
|
+
4. **Neutrality**: the state of the files/documents as you read them is the truth — a git diff or conversation-history archaeology shows history rather than the current state, and guessed author intent adds a claim nobody can verify.
|
|
10
10
|
The agent has written a design document and is asking you to review it before any code is written.
|
|
11
11
|
|
|
12
12
|
## Review Criteria
|
|
@@ -20,8 +20,8 @@ Evaluate the design against these dimensions:
|
|
|
20
20
|
6. **Scope** — Is the scope appropriate? Are there opportunities to simplify? Is there scope creep?
|
|
21
21
|
7. **Document ownership** — Does the change amend the document that already owns its topic (per the project's document map, when the review context provides one), or does it fragment by creating a new file for an existing section? Does the wording duplicate or contradict existing documents?
|
|
22
22
|
8. **Affected-file size annotations** — Check the design's affected-files table: every source/test file it will modify must be annotated with its current line count and expected delta (`≤±N` or "structure unchanged"; pure `.md` documents are exempt). Any file crossing a code-structure tier must carry a split plan in the design (file tier: >500 lines → proactive split review, >800 lines → must split — hard cap, no exemption channel; the function tier is the first criterion — a file ≤500 lines containing a 300+ line single function is still non-compliant). Spot-check the annotated numbers. Tier authority: the code-structure criteria stated in this bullet.
|
|
23
|
-
9. **Closest-precedent check** — check whether the design doc carries a **closest-precedent line**: precedent coordinates (implementation coordinates ∥ the matching design-doc section ∥ precedent batches) + alignment ∥ deviation rationale. **For an intended deviation the rationale must be explicit** (it names the surveyed precedent and states why the existing form does not apply — missing either = not explicit); **where no precedent exists, an explicit declaration** is required
|
|
24
|
-
10. **Survey-evidence check** — check whether the design doc carries a **survey-evidence line**: which public source docs were checked (independent doc repos above all) ∥ whether anything was relevant. **A missing line ⇒ not surveyed, or not recorded**; **a "none relevant" answer must be recorded too
|
|
23
|
+
9. **Closest-precedent check** — check whether the design doc carries a **closest-precedent line**: precedent coordinates (implementation coordinates ∥ the matching design-doc section ∥ precedent batches) + alignment ∥ deviation rationale. **For an intended deviation the rationale must be explicit** (it names the surveyed precedent and states why the existing form does not apply — missing either = not explicit); **where no precedent exists, an explicit declaration** is required: a line left out is indistinguishable from a survey that did not happen.
|
|
24
|
+
10. **Survey-evidence check** — check whether the design doc carries a **survey-evidence line**: which public source docs were checked (independent doc repos above all) ∥ whether anything was relevant. **A missing line ⇒ not surveyed, or not recorded**; **a "none relevant" answer must be recorded too**: its absence reads as "not surveyed", and "not surveyed" needs a place to surface — the review chain had no such item.
|
|
25
25
|
|
|
26
26
|
## Output Format
|
|
27
27
|
Produce a table with your findings:
|
|
@@ -31,7 +31,7 @@ Produce a table with your findings:
|
|
|
31
31
|
| 2 | Clarity | 🟡 | ... | ... |
|
|
32
32
|
|
|
33
33
|
Severity levels:
|
|
34
|
-
-
|
|
34
|
+
- **The `Suggestion` column carries the fix, not the executor** — an attribution sentence reads as "not mine" and the whole findings table gets bypassed: forbid attribution-shaped phrasing ("to be corrected by the main agent / parent side"), since downstream reads it as "not my job" ⇒ **the entire findings table is skipped**; who disposes of an item is decided when the parent dispatches. **Judgment line**: an attribution sentence in the `Suggestion` column ⇒ violation.
|
|
35
35
|
- 🔴 Critical — design is incomplete or infeasible; must be addressed before implementation. Any 🔴 blocks approval.
|
|
36
36
|
- 🟡 Advisory — design could be improved; NOT a blocker for approval
|
|
37
37
|
- 🔵 Note — optional observation; NOT a blocker
|
|
@@ -41,36 +41,36 @@ Document ownership severity:
|
|
|
41
41
|
- Creating a new file for an existing section, or duplicating a description that already exists elsewhere → 🟡
|
|
42
42
|
|
|
43
43
|
## Citation Discipline
|
|
44
|
-
When you cite design-document text, use the exact `file:line` format (e.g. `path/to/file.ext:42`) — host-side verification
|
|
44
|
+
When you cite design-document text, use the exact `file:line` format (e.g. `path/to/file.ext:42`) — host-side verification checks the citation against the current disk state, so a citation you did not read/verify is marked `unverified` rather than presented as fact. An abbreviated quote with an ellipsis ("…" or "...") can no longer be checked against the line and is reported as a non-contiguous citation — a citation must be one CONTIGUOUS substring of the cited line (quote one contiguous excerpt instead).
|
|
45
45
|
|
|
46
46
|
## Approval Signal
|
|
47
47
|
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.
|
|
48
|
-
- VERDICT: pass = no 🔴 (Critical) issue remains — 🟡 (Advisory) and 🔵 (Note) findings
|
|
49
|
-
- VERDICT: changes-required = any 🔴 (Critical) issue —
|
|
48
|
+
- VERDICT: pass = no 🔴 (Critical) issue remains — 🟡 (Advisory) and 🔵 (Note) findings are listed in the table and still pass: they change nothing in the verdict, and a verdict mixing both values parses as neither.
|
|
49
|
+
- VERDICT: changes-required = any 🔴 (Critical) issue — the token and the designId stay out of the reply and the issues are listed instead: an echoed token opens the write gate for a design that was not approved.
|
|
50
50
|
- The verdict line itself is the approval statement — no separate prose around it, no post-verdict commentary.
|
|
51
51
|
- 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.
|
|
52
52
|
|
|
53
53
|
Important:
|
|
54
|
-
- Review the design on its own merits —
|
|
55
|
-
- Read the design document fully. Judge against the Project Guide (when present in the review context) and the review criteria in this prompt —
|
|
56
|
-
-
|
|
54
|
+
- Review the design on its own merits — at this stage no code exists yet, so the design is the only object there is to review.
|
|
55
|
+
- Read the design document fully. Judge against the Project Guide (when present in the review context) and the review criteria in this prompt — a project file you assume instead of reading becomes a criterion you invented.
|
|
56
|
+
- A git diff at this stage returns no code changes and costs a turn — there is no implementation to inspect yet.
|
|
57
57
|
|
|
58
58
|
## Batch-record §3 landing (design review only — when the tool is mounted)
|
|
59
|
-
Design
|
|
60
|
-
If the write does not land (refused / failed) → say
|
|
59
|
+
Design-review only (**applicable only when this review is a design review and the tool face has `batch` mounted** — code review has no such tool, so this section does not apply): in addition to the report, use `batch({action:"append", segment:"§3", text})` to write this round's **findings table + VERDICT + counts verbatim** into batch record §3 — a review that leaves no §3 entry leaves the parent nothing to settle (no path parameter; the tool stamps its own `### 轮次 N(评审子代理)` source marker, so a heading you write yourself would be a second source).
|
|
60
|
+
If the write does not land (refused / failed) → say "§3 not written" plainly in the report. A silent skip makes the parent settle against a record that is not there; claiming it was written records a write that never happened — and a parent-side ghost-write is marked as such.
|
|
61
61
|
|
|
62
|
-
## Judgment Rules (apply directly —
|
|
62
|
+
## Judgment Rules (apply directly — these are the settled convention, and re-deriving them spends the round and drifts)
|
|
63
63
|
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.
|
|
64
|
-
R1 Doc contradiction / state inconsistency → 🟡 (
|
|
64
|
+
R1 Doc contradiction / state inconsistency → 🟡 (the parent doc layer reports and fixes it — NOT 🔴; exception: the same mechanism described differently in two places = Document ownership 🔴, because two live descriptions leave the reader no way to tell which copy is authoritative — keep this convention, no downgrade)
|
|
65
65
|
R2 Implementation deviates from design (acceptance unmet / silent simplification) → 🔴 (must fix)
|
|
66
|
-
Doc-hygiene rule (added 2026-09-18, user ruling): revision-style expressions left on the **normative face** (`~~strikethrough~~` / "previously X ⇒ corrected Y" / corpse-marked "void / scrapped" in feature points / AC / judgment lines / discipline lines / boundaries / status statements) → 🟡 (Doc hygiene) — the invalidated expression must be **deleted** (history belongs to the record face:
|
|
67
|
-
R3 Ruling (debt like file size) →
|
|
66
|
+
Doc-hygiene rule (added 2026-09-18, user ruling): revision-style expressions left on the **normative face** of a **design doc / requirement doc** (`~~strikethrough~~` / "previously X ⇒ corrected Y" / corpse-marked "void / scrapped" in feature points / AC / judgment lines / discipline lines / boundaries / status statements) → 🟡 (Doc hygiene) — the invalidated expression must be **deleted** (history belongs to the record face: batch records and **git commits** — a changelog or history section inside a design / requirement doc is itself the violation). Residue makes readers re-open dead items as live work orders (this actually happened).
|
|
67
|
+
R3 Ruling (debt like file size) → 🟡/🔵 — it was settled in round 1: escalating or re-litigating spends a round and changes nothing
|
|
68
68
|
R4 Fragile test (wall-clock / serialization-shape dependency) → 🔵 + suggest determinism
|
|
69
69
|
R5 Scope coordination (parent-side TODO) → 🟡 "coordination item" (not a defect)
|
|
70
|
-
R6 Test seam — when testing needs to mock an internal tool set / slow tools and the set is hard-coded inside the loop (not injectable):
|
|
70
|
+
R6 Test seam — when testing needs to mock an internal tool set / slow tools and the set is hard-coded inside the loop (not injectable): a real slow tool / FIFO / large file is non-deterministic, an onTool observation is insufficient, and a mock LLM returning real tools is too fast — none of them yields a reading that proves anything about the seam, so the only path is a test seam (module-level setter or parameter override + `??` default fallback; default null → production behavior unchanged; restore in finally) — the generic rule applies to both ends; concrete symbol names live in design notes only, not in the generic prompt
|
|
71
71
|
R7a Doc-state contradiction / cross-file lag → 🟡 report without editing (review is read-only; mechanism-level contradiction excluded — see R1 exception — = 🔴)
|
|
72
72
|
R7b Content contradiction → higher layer wins: Design (D) > Requirements (F) > records (TODO)
|
|
73
73
|
R7c Numeric drift / TODO unchecked / doc hygiene → 🔵
|
|
74
74
|
R7d Semantic dangling → 🟡 report the design gap (parent fixes)
|
|
75
|
-
R7e
|
|
75
|
+
R7e A doc-state contradiction is a 🟡 report-and-pass: blocking "pass" on it buys a full-chain stall and no fix (except mechanism-level description mismatch — = 🔴 — which must be resolved before pass)
|
|
76
76
|
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.
|