@thincoder/core 0.10.9 → 0.10.11

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.
Files changed (92) hide show
  1. package/CHANGELOG.md +28 -2
  2. package/advisor/messages.mjs +4 -5
  3. package/advisor/project-context.mjs +69 -44
  4. package/agent/completion.mjs +4 -1
  5. package/agent/family-tools.mjs +12 -10
  6. package/agent/helpers.mjs +19 -11
  7. package/agent/light-round.mjs +1 -4
  8. package/agent/record-results.mjs +6 -1
  9. package/agent/run-start.mjs +1 -0
  10. package/agent/setup-reminders.mjs +1 -1
  11. package/agent/turn-loop.mjs +17 -13
  12. package/agent/unbooked-pen.mjs +62 -0
  13. package/agent-tools/advisor.mjs +15 -31
  14. package/agent-tools/audit-block.mjs +16 -62
  15. package/agent-tools/batch-gate.mjs +12 -21
  16. package/agent-tools/checkpoint.mjs +11 -6
  17. package/agent-tools/context.mjs +1 -1
  18. package/agent-tools/design-token.mjs +9 -2
  19. package/agent-tools/digest-budget.mjs +2 -0
  20. package/agent-tools/doc-check.mjs +17 -23
  21. package/agent-tools/manifest.mjs +45 -14
  22. package/agent-tools/review-facts.mjs +35 -81
  23. package/agent-tools/settings.mjs +65 -5
  24. package/agent-tools/spawn-gates.mjs +68 -31
  25. package/agent-tools/subagent-async.mjs +6 -3
  26. package/agent-tools/subagent-spawn.mjs +76 -80
  27. package/agent-tools/subagent.mjs +16 -16
  28. package/agent-tools.mjs +1 -1
  29. package/browser/launch.mjs +14 -3
  30. package/browser/queue.mjs +31 -3
  31. package/browser/session.mjs +4 -0
  32. package/config-defaults.mjs +8 -0
  33. package/config-presets.mjs +1 -1
  34. package/config.mjs +60 -11
  35. package/declaration.mjs +5 -12
  36. package/doc-check/anchors.mjs +53 -60
  37. package/doc-check/index.mjs +106 -140
  38. package/doc-check/paragraph.mjs +119 -0
  39. package/fts-text.mjs +4 -1
  40. package/i18n.mjs +5 -0
  41. package/ledger-cmd.mjs +17 -4
  42. package/ledger-db.mjs +14 -8
  43. package/ledger.mjs +1 -1
  44. package/manifest-discovery.mjs +19 -10
  45. package/manifest-schema.mjs +7 -35
  46. package/manifest.mjs +21 -9
  47. package/memory/code-search.mjs +5 -14
  48. package/memory/docs.mjs +5 -14
  49. package/memory/read-gate.mjs +26 -0
  50. package/model-context.mjs +98 -0
  51. package/model-specs.mjs +110 -37
  52. package/package.json +2 -2
  53. package/permission.mjs +3 -1
  54. package/prompts/advisor-design.md +104 -38
  55. package/prompts/advisor-round1.md +72 -24
  56. package/prompts/advisor-round2.md +62 -25
  57. package/prompts/advisor-round3.md +68 -24
  58. package/prompts/common.md +214 -58
  59. package/prompts/consult-base.md +10 -3
  60. package/prompts/discipline-engineering.md +202 -54
  61. package/prompts/discipline-normal.md +95 -44
  62. package/prompts/persona-coder.md +3 -1
  63. package/prompts/persona-eng-coder.md +29 -9
  64. package/prompts/persona-eng-designer.md +101 -35
  65. package/prompts/persona-engineering.md +320 -98
  66. package/prompts/persona-explore.md +1 -1
  67. package/prompts/persona-normal.md +16 -8
  68. package/provider/anthropic.mjs +127 -4
  69. package/provider/core.mjs +16 -6
  70. package/provider/google.mjs +8 -8
  71. package/provider/list-models.mjs +37 -5
  72. package/provider/normalize.mjs +2 -2
  73. package/provider/responses.mjs +7 -7
  74. package/provider/sse.mjs +6 -5
  75. package/proxy-transport.mjs +5 -3
  76. package/proxy.mjs +2 -1
  77. package/session-guard.mjs +7 -1
  78. package/session-lifecycle.mjs +23 -8
  79. package/session-segments.mjs +1 -0
  80. package/session-slot-claims.mjs +4 -0
  81. package/session-slot-write.mjs +6 -0
  82. package/session-slots-manifest.mjs +4 -0
  83. package/token-ttl.mjs +10 -0
  84. package/tool-docs/advisor.md +24 -5
  85. package/tool-docs/doc_check.md +44 -25
  86. package/tool-docs/ledger.md +2 -2
  87. package/tool-docs/manifest-read.md +8 -0
  88. package/tool-docs/manifest.md +9 -8
  89. package/tool-docs/subagent.md +41 -16
  90. package/tools/web-deepseek.mjs +26 -11
  91. package/doc-check/version.mjs +0 -84
  92. package/doc-check/width.mjs +0 -132
@@ -0,0 +1,98 @@
1
+ /**
2
+ * model-context.mjs — 模型上下文解析的运行时事实面(`PROVIDER.md` §6.15 · 五级链 L1/L3 两层来源)。
3
+ *
4
+ * 组成三件:模块级 `baseURL → (归一 id → 条目表)` 只读缓存 · 键归一(末段 + 小写)· 值域校验
5
+ * (`positiveInt`)。零本地 import(只碰运行时内建)⇒ 零 import 环。
6
+ *
7
+ * - L1 `providers[].modelContext[模型]`(人工声明,**K** 单位)→ `manualContextK`(返 K)
8
+ * - L3 `/models` 自报窗口(**token**)→ `listedContextTokens`(返 token)
9
+ * 单位进函数名:乘点唯一在 `model-specs.mjs`(L1 ∥ L2 ×1024,L3 不乘)——读函数名即知单位。
10
+ *
11
+ * 缓存触发 = 既有列表拉取(写面只在 `provider/list-models.mjs` 成功返回时调 `recordListedContext`):
12
+ * 六个消费面本来就要拉列表,顺带把窗口留在核里,**零新增请求**(D2)。读面是同步纯查表:未命中返
13
+ * `null`、调用侧落下一层,永不抛、永不等待(R28)。
14
+ *
15
+ * 键归一 = 末段 + 小写(规则单源 = `PROVIDER.md` §6.9):`qwen/ZHIPU/GLM-5.3` ⇒ `glm-5.3`。
16
+ * **本面算子 = 末段相等**(不是前缀):用户键 `glm-5.3` 不命中模型 `glm-5.3-flashx`(R29③)。
17
+ * 同侧两条键归一后相撞 = 同一模型的两种写法 ⇒ **命中即取**(对象键序 ∥ 列表序第一条,不排序、
18
+ * 不比较;R29④)——不拒、不告警、不落下一层。
19
+ */
20
+
21
+ /** 值域校验单点(L1 K ∥ L3 token 共用谓词):正整数才收,其余一律 `null`(= 未提供)。
22
+ * 只认 `number`——写面交互输入是字符串,须先 `Number(String(v).trim())` 再判(调用侧责任)。 */
23
+ export function positiveInt(v) {
24
+ return typeof v === "number" && Number.isInteger(v) && v > 0 ? v : null
25
+ }
26
+
27
+ /** 模型名归一(末段 + 小写——§6.9 三面同一把尺子)。非串归一等价空串(脏载不抛)。 */
28
+ export function normalizeModelKey(name) {
29
+ const m = (typeof name === "string" ? name : "").toLowerCase()
30
+ const slash = m.lastIndexOf("/")
31
+ return slash >= 0 ? m.slice(slash + 1) : m
32
+ }
33
+
34
+ function normalizeBaseURL(baseURL) {
35
+ return (typeof baseURL === "string" ? baseURL : "").replace(/\/+$/, "")
36
+ }
37
+
38
+ /** 告警一次(模块级去重键)——本模块唯一告警出口;去重键决定「一次」的射程。 */
39
+ const warned = new Set()
40
+ export function warnContextOnce(key, message) {
41
+ if (warned.has(key)) return
42
+ warned.add(key)
43
+ console.warn(message)
44
+ }
45
+
46
+ // ─── L3 缓存:归一 baseURL → (归一 id → 条目表) ───────────────────────────────────
47
+ // 键 = 归一后的 baseURL(列表是端点的事实,不是本地别名的事实——探针目标常缺 name)。
48
+ // 写 = 每次成功拉取**整条替换**(不合并):列表不再报某模型 ⇒ 该模型回到 L4;拉取失败不写、
49
+ // 不清空上次成功值(上次自报比静态表更接近事实)。归一索引在写时建好 ⇒ 读面是一次 `Map` 查表
50
+ // (同键多条目 = 末段相撞,读面取列表序第一条——R29④)。
51
+ const listedByEndpoint = new Map()
52
+
53
+ /** L3 写面(唯一入口):记录一个端点的列表自报窗口表。`entries` = `[id, tokens]` 可迭代
54
+ * (Map ∥ 数组);非法值当场丢弃(= 该模型列表未报)。空表也写入——「这次一条都没报」是事实。 */
55
+ export function recordListedContext(baseURL, entries) {
56
+ const base = normalizeBaseURL(baseURL)
57
+ if (!base) return
58
+ const index = new Map()
59
+ for (const [id, tokens] of entries ?? []) {
60
+ if (typeof id !== "string" || !id) continue
61
+ const value = positiveInt(tokens)
62
+ if (value === null) continue
63
+ const key = normalizeModelKey(id)
64
+ if (!key) continue
65
+ const bucket = index.get(key)
66
+ if (bucket) bucket.push({ id, tokens: value })
67
+ else index.set(key, [{ id, tokens: value }])
68
+ }
69
+ listedByEndpoint.set(base, index)
70
+ }
71
+
72
+ /** L3 读面:列表自报窗口(token,不乘)。未报 ⇒ `null`(落下一层);同端点两 id 末段相撞
73
+ * ⇒ 取列表序第一条即用(同一模型,R29④——不排序、不比较、不告警)。 */
74
+ export function listedContextTokens(provider) {
75
+ const base = normalizeBaseURL(provider?.baseURL)
76
+ const target = normalizeModelKey(provider?.model)
77
+ if (!base || !target) return null
78
+ const bucket = listedByEndpoint.get(base)?.get(target)
79
+ return bucket ? positiveInt(bucket[0].tokens) : null
80
+ }
81
+
82
+ /** L1 读面:`providers[].modelContext[模型]` 人工声明(K 单位)。未声明 ∥ 值非法 ⇒ `null`
83
+ * (落下一层);两条键归一后相同 = 同一模型的两种写法 ⇒ 取对象键序第一条即用(R29④——
84
+ * 不排序、不比较、不告警)。值非法在装载期已告警一次(`config.mjs`),此处只判不告(热路径)。 */
85
+ export function manualContextK(provider) {
86
+ const table = provider?.modelContext
87
+ if (!table || typeof table !== "object" || Array.isArray(table)) return null
88
+ const target = normalizeModelKey(provider?.model)
89
+ if (!target) return null
90
+ for (const [key, value] of Object.entries(table)) if (normalizeModelKey(key) === target) return positiveInt(value)
91
+ return null
92
+ }
93
+
94
+ /** 测试缝:清空 L3 缓存 + 告警去重(单元档用;生产零调用)。 */
95
+ export function _resetModelContextForTest() {
96
+ listedByEndpoint.clear()
97
+ warned.clear()
98
+ }
package/model-specs.mjs CHANGED
@@ -7,8 +7,10 @@
7
7
  * config-presets.mjs — the core's single preset face, re-exported by config.mjs.)
8
8
  */
9
9
 
10
+ import { listedContextTokens, manualContextK, warnContextOnce } from "./model-context.mjs"
11
+
10
12
  /**
11
- * Known model capability spec table (prefix match, longer first).
13
+ * Known model capability spec table (prefix match within the model's last "/"-segment, longer first).
12
14
  * Used for compaction threshold derivation, continuation protocol selection, and capability-aware optimization.
13
15
  *
14
16
  * context: context window (tokens)
@@ -42,8 +44,6 @@ const MODEL_SPECS = [
42
44
  ["deepseek-v4-flash-vision-exp", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2], multimodal: true }],
43
45
  // Kimi series
44
46
  ["kimi-k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
45
- // Qwen router prefixes model IDs with provider namespace: kimi/kimi-k3 → kimi-k3 (IK7K4V)
46
- ["kimi/kimi-k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
47
47
  // Kimi For Coding endpoint uses the short model ID "k3" (same specs as kimi-k3) — IK5VGJ
48
48
  ["k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
49
49
  // kimi-for-coding / k3-256k / kimi-for-coding-highspeed(Kimi For Coding 端点三名 · 2026-09-25 批 §14.2 #7-9 · 活测 = §1.4 服务端 200 /models):
@@ -249,36 +249,32 @@ const MODEL_SPECS = [
249
249
  ]
250
250
  const DEFAULT_SPEC = { context: 128_000, maxOutput: 32_000 }
251
251
 
252
- /** Look up spec by model name prefix (case-insensitive), conservative default for unknown models.
252
+ /** 查表规则(单源 = PROVIDER.md §6.9 · 用户 2026-10-23 17:34 裁定「匹配 / 拆分以后的最后一段」):
253
253
  *
254
- * Vendor-namespace stripping (2026-09-04;末段形 2026-10-10——用户裁定「只取最后一段」):第三方
255
- * token 市场(roapi/new-api/one-api/aiproxy 聚合网关)惯例在模型名前加厂商前缀(zhipu/glm-5.3、
256
- * openai/gpt-4o),链式网关下可叠多段(测试服务器 qwen/ZHIPU/GLM-5.3)。完整名未命中且含 "/" 时,
257
- * 取最后一个 "/" 之后的段再按前缀匹配一次——单段 ZHIPU/GLM-5.3 → glm-5.3(两法等价)、多段
258
- * qwen/ZHIPU/GLM-5.3 → glm-5.3 均命中真实规格,不再降级 128K 默认。kimi/kimi-k3 的显式 alias
259
- * 行保留为文档锚(发送路径 provider.core isRouter 依赖含 "/" 判定),通用机制已覆盖同类。 */
254
+ * 模型名匹配 = **末段唯一**:按 "/" 拆开只拿最后一段查表(单段名末段 = 自身),段内仍按前缀匹配
255
+ * (`glm-5.3-flashx` 命中 `glm-5.3` 行)。完整名**不再先扫**——旧法把 `glm-5.3/zzz` 误判成
256
+ * `glm-5.3`(1M 上下文 ⇒ 压缩永不触发 ⇒ 溢出);新法取末段 `zzz` ⇒ 未命中 ⇒ DEFAULT_SPEC + 告警。
257
+ * 聚合网关惯例 `vendor/model`(roapi/new-api/one-api/aiproxy)因此天然命中:`zhipu/glm-5.3` /
258
+ * 链式 `qwen/ZHIPU/GLM-5.3` ⇒ `glm-5.3`。末段为空(`foo/`)⇒ 未命中(空串不做匹配)。 */
260
259
  const warnedModels = new Set() // warn once per model name — spec lookup runs on every request (hot path)
261
260
  // Pre-sorted once at module scope — spec lookup runs on every request (agent, provider core,
262
261
  // context, auto-think, TUI rendering); re-sorting per call was wasteful.
263
262
  const SORTED_SPECS = [...MODEL_SPECS].sort((a, b) => b[0].length - a[0].length)
264
263
 
265
- /** Single table lookup shared by specForModel / specMatch — prefix match (case-insensitive)
266
- * with vendor-namespace stripping. Returns null on a miss (DEFAULT_SPEC is the caller-side
267
- * fallback and is deliberately NOT returned here — `matched` needs the miss itself).
264
+ /** Single table lookup shared by specForModel / specMatch — prefix match (case-insensitive) on the
265
+ * model's LAST "/"-segment only (末段唯一 · PROVIDER.md §6.9). The full name is NOT scanned first:
266
+ * `glm-5.3/zzz` takes `zzz` and misses (the old full-name-first scan mis-hit `glm-5.3`).
267
+ * Returns null on a miss (DEFAULT_SPEC is the caller-side fallback and is deliberately NOT
268
+ * returned here — `matched` needs the miss itself).
268
269
  * 非串 model(外端脏载 / 手工档)归一等价空串 ⇒ 未命中 ⇒ 调用侧 DEFAULT_SPEC(#638 查表全性:
269
270
  * 下游无类型门——`.toLowerCase` 只认串,非串归一后本函数恒 total,同 providerSpec 文档句)。 */
270
271
  function lookupSpec(model) {
271
272
  const m = (typeof model === "string" ? model : "").toLowerCase()
272
- for (const [prefix, spec] of SORTED_SPECS) {
273
- if (m.startsWith(prefix.toLowerCase())) return spec
274
- }
275
- // Vendor-namespace strip: take the LAST "/"-separated segment — retry the prefix match on it.
276
273
  const slash = m.lastIndexOf("/")
277
- if (slash > 0) {
278
- const bare = m.slice(slash + 1)
279
- for (const [prefix, spec] of SORTED_SPECS) {
280
- if (bare.startsWith(prefix.toLowerCase())) return spec
281
- }
274
+ const bare = slash >= 0 ? m.slice(slash + 1) : m // 末段唯一;单段名 = 自身
275
+ if (!bare) return null // 末段为空 ⇒ 未命中(空串不做前缀匹配)
276
+ for (const [prefix, spec] of SORTED_SPECS) {
277
+ if (bare.startsWith(prefix.toLowerCase())) return spec
282
278
  }
283
279
  return null
284
280
  }
@@ -312,27 +308,104 @@ export function specMatch(model) {
312
308
  return { spec: DEFAULT_SPEC, matched: false }
313
309
  }
314
310
 
311
+ /** Context window 展示串,**收 token**(核单源——三端读面同一格式器出词,R31④):`128K` ∥ `1M`。
312
+ * 本批由 CLI 私有 `fmtContextK` 上提——三端各自拼串会漂。服务链上 L3/L4 值(token)的展示;
313
+ * 人工声明(K)的展示走 `formatDeclaredContextK`,一个换算方向一个入口,不混用。 */
314
+ export function formatContextK(tokens) {
315
+ if (tokens >= 1_000_000) return `${Math.round(tokens / 1_048_576)}M`
316
+ return `${Math.round(tokens / 1024)}K`
317
+ }
318
+
319
+ /** 人工声明上下文的展示串,**收 K**(`modelContext` ∥ 旧键 `context` 的存储单位):`128` ⇒ `128K`、
320
+ * `1024` ⇒ `1M`。K→token 的展示换算**只此一处**(内部转 token 再交给 `formatContextK`)——三端读面
321
+ * 把已声明原值直接交进来,不自己换算、不自己拼 K 串(PROVIDER.md §6.15「读面回显」· R31④)。
322
+ * 前提:`k` 是先过 `positiveInt` 的正整数(与读链同一谓词)——非法值本函数不复判。 */
323
+ export function formatDeclaredContextK(k) {
324
+ return formatContextK(k * 1024)
325
+ }
326
+
315
327
  /**
316
- * providerSpec(provider) — spec with a provider-level context override (PROVIDER.md §15, 2026-09-02).
328
+ * providerSpec(provider) — spec with the context resolution chain (PROVIDER.md §6.15, 2026-10-23
329
+ * 五级链). `providerSpecResolution` = the single traversal; `providerSpec` = `resolution.spec`.
330
+ *
331
+ * Chain (first available wins) — 单位差只在这里乘一次,且**解析乘点**全仓唯一(本函数内 L1 ∥ L2 共用;
332
+ * 展示面的另一次在 `formatDeclaredContextK`,不回写解析值):
333
+ * L1 `providers[].modelContext[模型]`(人工声明,K) → ×1024
334
+ * L2 `providers[].context`(旧键,K;仅未被 L1 点名的模型) → ×1024(同一乘点)
335
+ * L3 `/models` 自报(三字段任一,token) → 不乘
336
+ * L4 `MODEL_SPECS` 静态表(token) → 不乘
337
+ * L5 `DEFAULT_SPEC`(128K,token) → 不乘
338
+ *
339
+ * L2 位于 L3 之上:旧键也是人工声明(2026-10-23 17:30 裁定「人工声明 > 列表」),只是粒度不对、
340
+ * 单位事故出过两次;`modelContext` 点名的模型一律走 L1,L2 不参与。L2 实际被用到时告警一次
341
+ * (provider 名去重——装载期告警会在 L1 已完全覆盖时误报)。
317
342
  *
318
- * providers[].context is configured in K units (128 = 128K = 131072 tokens) and overrides the
319
- * MODEL_SPECS value for THIS provider only — the same model can have different real context
320
- * windows on different endpoints (official vs local deployment). The ×1024 conversion happens
321
- * HERE and nowhere else.
343
+ * 量级守卫(R32):L1 ∥ L2 的 K 值换算后 > 静态表 ×4(表无行时 = 4 × 128K)⇒ 照用 + 告警一次,
344
+ * 不截断、不拒写——与 L3 的 4× 守卫对称。守卫住在核里:值可能来自手改 JSON(不经任何界面),
345
+ * 端上守不到;手改 ∥ CLI ∥ VSC ∥ 桌面四条来源在这里同判。
322
346
  *
323
- * Returns a COPY ({ ...spec, context }) — the shared spec object from the SORTED_SPECS lookup
324
- * must never be mutated, or the override would leak across providers (T-C1).
347
+ * 同步纯函数、零 IO:L3 读 `model-context.mjs` 的模块级只读缓存(写面在 listModels 成功拉取时),
348
+ * 未命中即落下一层——providerSpec 在请求热路径上(每请求算预算),async 等于把 IO 塞进请求路径。
325
349
  *
326
- * Validation is defensive (pure function): absent/invalid context falls back to the plain spec
327
- * (config.mjs loadConfig already warns + strips invalid values; this guard covers direct callers
328
- * and keeps the function total). specForModel stays a pure table lookup — callers without a
329
- * provider keep using it.
350
+ * Returns a COPY ({ ...spec, context }) when L1 / L2 / L3 supplies the window — the shared spec
351
+ * object from SORTED_SPECS ∥ DEFAULT_SPEC must never be mutated, or the override would leak across
352
+ * providers (T-C1). No L1-L3 value ⇒ the shared spec object itself (zero-copy hot path). `maxOutput`
353
+ * 与其余能力位仍取静态表 ∥ DEFAULT_SPEC(需求边界:不改 maxOutput 来源)。
354
+ *
355
+ * `source` 闭集五值(`windowSource` 的唯一来源,不并类):`modelContext` ∥ `provider-override` ∥
356
+ * `list` ∥ `spec` ∥ `default`。`warnUnknownModel` 只在链落到 L4/L5 时触发(L1-L3 供值时 128K 告警为假)。
330
357
  */
358
+ export function providerSpecResolution(provider) {
359
+ const model = provider?.model ?? ""
360
+ const hit = lookupSpec(model)
361
+ const base = hit ?? DEFAULT_SPEC
362
+ // L1 — 人工声明(按模型,K 单位);L2 — 旧键(渠道级,K 单位)。两种「人工声明」粒度在这里合流:
363
+ // **解析面的 ×1024 只出现在合流后的那一次换算上**(L3 起是 token,不乘)——加第二个解析乘点 = 下一次单位事故。
364
+ // (展示面的 K→token 换算另在 `formatDeclaredContextK`,只出词、不回写解析值。)
365
+ const manualK = manualContextK(provider)
366
+ const legacyRaw = Number(provider?.context) // Number() 接受数字字符串("128")——旧键既有口径(code review #1)
367
+ const legacyK = Number.isInteger(legacyRaw) && legacyRaw > 0 ? legacyRaw : null
368
+ const kUnit = manualK ?? legacyK
369
+ if (kUnit !== null) {
370
+ if (manualK === null) {
371
+ warnContextOnce(
372
+ `legacy-context\0${provider?.name ?? ""}`,
373
+ `[config] provider "${provider?.name}" uses the legacy context override (K units) for models not listed in modelContext — migrate to modelContext: { "${model}": ${legacyK} } (K units, same as this key)`,
374
+ )
375
+ }
376
+ // R32 — 人工声明的量级守卫(L1 ∥ L2,与 L3 的守卫对称):换算后 > 静态表 ×4 ⇒ 照用 + 告警一次,
377
+ // **不截断、不拒写**(异常值也可能为真;截断 = 静默改写真事实)。守卫住核——值可能来自手改 JSON
378
+ // (不经任何界面),端上守不到;四条来源(手改 / CLI / VSC / 桌面)在这里同判。去重键 = 渠道 + 模型 + K 值。
379
+ const declaredTokens = kUnit * 1024
380
+ if (declaredTokens > base.context * 4) {
381
+ warnContextOnce(
382
+ `declared-over-4x\0${provider?.name ?? ""}\0${model}\0${kUnit}`,
383
+ `[config] model "${model}" on provider "${provider?.name ?? ""}" declares context ${kUnit}K (= ${declaredTokens} tokens), over 4x the spec value ${base.context} — using the declared value as-is (not truncated)`,
384
+ )
385
+ }
386
+ return { spec: { ...base, context: declaredTokens }, source: manualK !== null ? "modelContext" : "provider-override" }
387
+ }
388
+ // L3 — 列表自报(token,不乘);异常大值照用 + 告警一次(R27——截断 = 静默改写真事实)
389
+ const listedTokens = listedContextTokens(provider)
390
+ if (listedTokens !== null) {
391
+ const staticValue = base.context
392
+ if (listedTokens > staticValue * 4) {
393
+ const endpoint = String(provider?.baseURL ?? "").replace(/\/+$/, "")
394
+ warnContextOnce(
395
+ `listed-over-4x\0${endpoint}\0${model}\0${listedTokens}`,
396
+ `[config] model "${model}" on ${endpoint} reports context ${listedTokens}, over 4x the spec value ${staticValue} — using the reported value as-is (not truncated)`,
397
+ )
398
+ }
399
+ return { spec: { ...base, context: listedTokens }, source: "list" }
400
+ }
401
+ // L4 — 静态表命中;L5 — DEFAULT_SPEC 兜底(未知模型告警只在这里)
402
+ if (hit) return { spec: hit, source: "spec" }
403
+ warnUnknownModel(model)
404
+ return { spec: DEFAULT_SPEC, source: "default" }
405
+ }
406
+
331
407
  export function providerSpec(provider) {
332
- const spec = specForModel(provider?.model ?? "")
333
- const k = Number(provider?.context) // Number() 接受数字字符串("128")——两端语义统一(code review #1)
334
- if (Number.isInteger(k) && k > 0) return { ...spec, context: k * 1024 }
335
- return spec
408
+ return providerSpecResolution(provider).spec
336
409
  }
337
410
 
338
411
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thincoder/core",
3
- "version": "0.10.9",
3
+ "version": "0.10.11",
4
4
  "description": "ThinCoder shared core — shared mechanism modules + shared prompt content (CLI ↔ VS Code extension).",
5
5
  "keywords": [
6
6
  "ai",
@@ -13,7 +13,7 @@
13
13
  "type": "module",
14
14
  "scripts": {
15
15
  "test": "node test/run.mjs",
16
- "prepublishOnly": "npm test"
16
+ "prepublishOnly": "node scripts/check-syntax.mjs"
17
17
  },
18
18
  "license": "MIT",
19
19
  "author": "liwei <liwei@51marine.com> (上海新舶)",
package/permission.mjs CHANGED
@@ -35,7 +35,9 @@ export function formatPermission(name, args) {
35
35
  return `${args.path}\n${oldLines}\n ↓\n${newLines}`
36
36
  }
37
37
  if (base === "delete") return `${args.path}${args.force ? "(force:跟踪文件也删)" : ""}`
38
- if (base === "subagent") return cap(composeTaskBookBrief(args, args.role) || args.task || "", 500)
38
+ // 工程角色无任务书文本参数(任务书 = 批次档 §2):拼装空 ⇒ 回退到 §2 路径句,人审面不空白
39
+ // (AGENT-LOOP-SUBAGENT.md §6.22.8 P11)。
40
+ if (base === "subagent") return cap(composeTaskBookBrief(args, args.role) || (args.batchDoc ? `task book: batch record §2 (${args.batchDoc})` : "") || args.task || "", 500)
39
41
  if (base === "memory") {
40
42
  // §6 action-routed preview: put shows content, batch delete/clear show the gate args
41
43
  const action = String(args.action ?? "")
@@ -3,25 +3,63 @@ 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. 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.
6
+ 1. **Stance**: you judge the design/code on its own merits against the review criteria.
7
+ **You find and report — you do not author**: you are not the author, not the implementer, not the editor; the parent agent (and the user) decides what changes.
8
+ **The suggestion column stays advisory guidance**: you evidence and recommend, and the rewrite belongs to the parent agent.
9
+ **Why**: writing replacement text or patch code in your findings would make you the author of what you judge.
10
+ 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`.
11
+ **An assertion has two sources only**: what you read (cite `file:line`) or an explicit `unverified`.
12
+ **Where the source is readable**, a behavioral question is an EVIDENCE question, not a reasoning question.
13
+ **Why**: "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.
14
+ 3. **Boundary**: your review target = the review-object declaration (type / target / status / reason / exclude) + the documents in the review scope.
15
+ **With no object declaration (legacy calls)**: your target = the review scope only.
16
+ **Out-of-scope is not looked at**: report it only when it blocks the judgment — that is the task being blocked, not a finding.
17
+ **Why**: the target does not expand — a finding outside that surface has nobody in this round to dispose of it and dilutes the object.
9
18
  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
19
  The agent has written a design document and is asking you to review it before any code is written.
11
20
 
12
21
  ## Review Criteria
13
22
  Evaluate the design against these dimensions:
14
- 1. **Requirements coverage** — Does the design address every requirement? Are there gaps?
15
-
16
- 2. **Feasibility** — Given the project's architecture and constraints, can this design be implemented? Are there obvious blockers?
17
- 3. **Methodology compliance** — Does it follow the project's document norms and the 4-step workflow? **Is the design doc itself present** (at any tier) — no doc ⇒ 🔴.
18
- 4. **Clarity** — Is the design specific enough to implement? Are the affected files identified?
19
- 5. **Acceptance criteria** — Are they verifiable? Do they cover normal paths, edge cases, and error conditions?
20
- 6. **Scope** — Is the scope appropriate? Are there opportunities to simplify? Is there scope creep?
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
- 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: 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.
23
+ 1. **Requirements coverage**: Does the design address every requirement? Are there gaps?
24
+ 2. **Feasibility**: Given the project's architecture and constraints, can this design be implemented? Are there obvious blockers?
25
+ 3. **Methodology compliance**: Does it follow the project's document norms and the 4-step workflow? Is the design doc itself present (at any tier) — no doc ⇒ 🔴.
26
+ 4. **Clarity**: Is the design specific enough to implement? Are the affected files identified?
27
+ 5. **Acceptance criteria**: Are they verifiable? Do they cover normal paths, edge cases, and error conditions?
28
+ 6. **Scope**: Is the scope appropriate? Are there opportunities to simplify? Is there scope creep?
29
+ 6b. **Branch provenance**: every branch the design adds for an input ∥ state names where its scene comes from — a real observation (a data reading ∥ a log line ∥ a user report, quoted) or a reproducible failure path (the command that reproduces it).
30
+ **Neither given** ⇒ the branch guards a scene that has not happened ⇒ **🔴**.
31
+ **Why**: a branch brings four companions of its own — an alert, copy, cross-end consistency and test material; with no scene none of the four can be verified, and nobody later dares delete the branch.
32
+ **A real defence passes**: give the reproduction path and it walks — this kills the branch that cannot name its scene, not defence itself.
33
+ 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?
34
+ 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").
35
+ **Pure `.md` documents are exempt** from that annotation.
36
+ **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).
37
+ **The function tier is the first criterion**: a file ≤500 lines containing a 300+ line single function is still non-compliant.
38
+ **Tier authority**: the code-structure criteria stated in this bullet.
39
+ **Spot-check**: sample the annotated numbers and verify them against the files.
40
+ 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.
41
+ **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.
42
+ **No precedent**: an explicit declaration is required.
43
+ **Why**: a line left out is indistinguishable from a survey that did not happen.
44
+ 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.
45
+ **A missing line**: not surveyed, or not recorded.
46
+ **A "none relevant" answer**: record it too.
47
+ **Why**: its absence reads as "not surveyed", and "not surveyed" needs a place to surface — the review chain had no such item.
48
+ 11. **Existing-piece check**: where the requirement set requires it — each new name ∥ component ∥ endpoint ∥ label text carries a three-question answer (where it lives · name, verbatim · who).
49
+ **The answer takes one of three forms**: `existing: …` · `none found: …` · `existing but unusable: …`.
50
+ **Unused existing piece**: an existing piece left unused with no reason it is unusable ⇒ 🔴.
51
+ **A missing line** stays 🟡.
52
+ 12. **Integration-test design**: the design document must contain an integration-test design section that starts from `test/RTM.md` — the RTM: one section per business story, one row per integration case.
53
+ The section answers as a delta against it — which rows this change breaks, which are redundant, which are missing;
54
+ each row names the **interaction between components** each evaluates, the business result each asserts, the project's entry (where a user enters the business — a business-observable point), and the business requirement each traces back to.
55
+ **An integration section that starts from a blank "which scenarios do we need" list, with no RTM read, ⇒ 🔴.**
56
+ A missing row's number is fixed in the RTM before its case title is written — `test('1-1 …')`, the same number verbatim.
57
+ **A design missing that section ⇒ 🔴 (design incomplete)**; a change that touches neither an interaction between components nor a business-observable result an existing case asserts states so, with its reason, in that section.
58
+ **Only one reason stands: this change touches neither the interaction between components nor a business-observable result an existing case asserts. Anything else — e.g. the 50–100 case window being full — is still 🔴.**
59
+ **Why**: no other acceptance run evaluates the interaction between components, and the batch's unit-test files are deleted at closeout — a design that never names those scenarios leaves that interaction with no acceptance at all.
60
+ **A design's acceptance naming a full-suite run ⇒ 🔴 (the release gate written as a round cost)**: the criterion is the changed-face unit test file or files (`node --test <file> [<file> ...]`), or — when the change has no runnable test — the check you actually ran and its result;
61
+ the integration half is reported in the closeout line, not run here.
62
+ Why: acceptance is what the implementing round runs — a suite there turns the release gate into a round cost, and a repeat run proves nothing new.
25
63
 
26
64
  ## Output Format
27
65
  Produce a table with your findings:
@@ -31,24 +69,37 @@ Produce a table with your findings:
31
69
  | 2 | Clarity | 🟡 | ... | ... |
32
70
 
33
71
  Severity levels:
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.
72
+ - **The `Suggestion` column carries the fix, not the executor**: forbid attribution-shaped phrasing ("to be corrected by the main agent / parent side") — who disposes of an item is decided when the parent dispatches.
73
+ **Judgment line**: an attribution sentence in the `Suggestion` column ⇒ violation.
74
+ **Why**: an attribution sentence reads as "not mine"; downstream reads it as "not my job" ⇒ the entire findings table is skipped.
35
75
  - 🔴 Critical — design is incomplete or infeasible; must be addressed before implementation. Any 🔴 blocks approval.
36
- - 🟡 Advisory — design could be improved; NOT a blocker for approval
37
- - 🔵 Note — optional observation; NOT a blocker
76
+ - 🟡 Advisory — design could be improved; NOT a blocker for approval.
77
+ - 🔵 Note — optional observation; NOT a blocker.
38
78
 
39
79
  Document ownership severity:
40
- - Wording that CONTRADICTS an existing document (same mechanism described differently in two places) → 🔴
41
- - Creating a new file for an existing section, or duplicating a description that already exists elsewhere → 🟡
80
+ - Wording that CONTRADICTS an existing document (same mechanism described differently in two places) → 🔴.
81
+ - Creating a new file for an existing section, or duplicating a description that already exists elsewhere → 🟡.
42
82
 
43
83
  ## 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 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).
84
+ **Cite with the exact `file:line` format** (e.g. `path/to/file.ext:42`) when you cite design-document text.
85
+ **A citation you did not read/verify is marked `unverified`**, not presented as fact.
86
+ **Why**: host-side verification checks the citation against the current disk state.
87
+ **A citation must be one CONTIGUOUS substring of the cited line** (quote one contiguous excerpt).
88
+ **Why**: an abbreviated quote with an ellipsis ("…" or "...") can no longer be checked against the line, so it is reported as a non-contiguous citation.
45
89
 
46
90
  ## Approval Signal
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 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
- - The verdict line itself is the approval statement — no separate prose around it, no post-verdict commentary.
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.
91
+ The user message contains an exact token and the exact designId in an `## Approval Signal` section.
92
+ **Close your findings with a single verdict line** — `VERDICT: pass` or `VERDICT: changes-required` — as the line immediately before the token echo.
93
+ **The verdict is the closing decision**: output nothing beyond the token echo.
94
+ **Verdict meaning — pass**: no 🔴 (Critical) issue remains — 🟡 (Advisory) and 🔵 (Note) findings are listed in the table and still pass, and they change nothing in the verdict.
95
+ **Verdict meaning — changes-required**: any 🔴 (Critical) issue — the token and the designId stay out of the reply, and the issues are listed instead.
96
+ **Any unresolved 🔴 issue** → `VERDICT: changes-required`.
97
+ **A verdict mixing both values parses as neither**: the line is parsed as a whole downstream, so it carries exactly one value.
98
+ **The verdict line itself is the approval statement**: no separate prose around it, no post-verdict commentary.
99
+ **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>.
100
+ **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.
101
+ **Output order**: the designId must be the LAST thing you output.
102
+ **Why**: an echoed token opens the write gate for a design that was not approved.
52
103
 
53
104
  Important:
54
105
  - 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.
@@ -56,21 +107,36 @@ Important:
56
107
  - A git diff at this stage returns no code changes and costs a turn — there is no implementation to inspect yet.
57
108
 
58
109
  ## Batch-record §3 landing (design review only — when the tool is mounted)
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).
110
+ **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.
111
+ **In addition to the report, write §3 with the tool**: use `batch({action:"append", segment:"§3", text})` to write this round's findings table + VERDICT + counts verbatim into batch record §3.
112
+ **No path parameter**: the tool stamps its own `### 轮次 N(评审子代理)` source marker.
113
+ **Why**: a heading you write yourself would be a second source.
114
+ **A review that leaves no §3 entry** leaves the parent nothing to settle.
60
115
  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
116
 
62
117
  ## Judgment Rules (apply directly — these are the settled convention, and re-deriving them spends the round and drifts)
63
118
  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 → 🟡 (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
- 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** 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
- R4 Fragile test (wall-clock / serialization-shape dependency) → 🔵 + suggest determinism
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): 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
- R7a Doc-state contradiction / cross-file lag → 🟡 report without editing (review is read-only; mechanism-level contradiction excluded — see R1 exception — = 🔴)
72
- R7b Content contradiction → higher layer wins: Design (D) > Requirements (F) > records (TODO)
73
- R7c Numeric drift / TODO unchecked / doc hygiene → 🔵
74
- R7d Semantic dangling → 🟡 report the design gap (parent fixes)
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)
119
+ R1 **Doc contradiction / state inconsistency → 🟡**: the parent doc layer reports and fixes it — NOT 🔴.
120
+ **Exception**: the same mechanism described differently in two places = Document ownership 🔴.
121
+ **No downgrade of this rule**: a doc contradiction stays 🟡 unless it is the same mechanism described twice.
122
+ **Why**: two live descriptions leave the reader no way to tell which copy is authoritative.
123
+ R2 Implementation deviates from design (acceptance unmet / silent simplification) → 🔴 (must fix).
124
+ **Doc-hygiene rule**: revision-style expressions left on the normative face of a design doc / requirement doc → 🟡 (Doc hygiene) — the invalidated expression must be deleted.
125
+ **What counts as revision-style**: `~~strikethrough~~` / "previously X ⇒ corrected Y" / corpse-marked "void / scrapped" in feature points / AC / judgment lines / discipline lines / boundaries / status statements.
126
+ **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.
127
+ **Why**: residue makes readers re-open dead items as live work orders (this actually happened).
128
+ R3 Ruling (debt like file size) → 🟡/🔵 — it was settled in round 1: escalating or re-litigating spends a round and changes nothing.
129
+ R4 Fragile test (wall-clock / serialization-shape dependency) → 🔵 + suggest determinism.
130
+ R5 Scope coordination (parent-side TODO) → 🟡 "coordination item" (not a defect).
131
+ 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).
132
+ **The only path is a test seam**: module-level setter or parameter override + `??` default fallback; default null → production behavior unchanged; restore in finally.
133
+ **The generic rule applies to both ends** (design review and code review).
134
+ **Concrete symbol names live in design notes only**, not in the generic prompt.
135
+ **Why**: a stand-in does not work — 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.
136
+ R7a Doc-state contradiction / cross-file lag → 🟡 report without editing (review is read-only; mechanism-level contradiction excluded — see R1 exception — = 🔴).
137
+ R7b Content contradiction → higher layer wins: Design (D) > Requirements (F) > records (TODO).
138
+ R7c Numeric drift / TODO unchecked / doc hygiene → 🔵.
139
+ R7d Semantic dangling → 🟡 report the design gap (parent fixes).
140
+ R7e A doc-state contradiction is a 🟡 report-and-pass (except mechanism-level description mismatch — = 🔴 — which must be resolved before pass).
141
+ **Why**: blocking "pass" on it buys a full-chain stall and no fix.
76
142
  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.