sillyspec 3.29.4 → 3.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.claude/skills/sillyspec-auto/SKILL.md +1 -1
  2. package/.claude/skills/sillyspec-brainstorm/SKILL.md +2 -2
  3. package/.claude/skills/sillyspec-commit/SKILL.md +1 -1
  4. package/.claude/skills/sillyspec-flow/SKILL.md +57 -0
  5. package/.claude/skills/sillyspec-quick/SKILL.md +7 -105
  6. package/AGENTS.md +38 -20
  7. package/SKILL.md +9 -3
  8. package/assets/command-cards/flow.md +32 -0
  9. package/assets/command-cards/run-archive.md +26 -0
  10. package/assets/command-cards/run-brainstorm.md +33 -0
  11. package/assets/command-cards/run-execute.md +29 -0
  12. package/assets/command-cards/run-plan.md +27 -0
  13. package/assets/command-cards/run-quick.md +17 -0
  14. package/assets/command-cards/run-verify.md +29 -0
  15. package/assets/command-cards/status.md +27 -0
  16. package/package.json +2 -1
  17. package/packages/dashboard/server/executor.js +2 -1
  18. package/packages/dashboard/server/parser.js +7 -7
  19. package/r7-test-env/README-R7-TEST.md +93 -0
  20. package/r7-test-env/demo-project/lib/calc.js +6 -0
  21. package/r7-test-env/demo-project/package.json +1 -0
  22. package/r7-test-env/demo-project/test.js +10 -0
  23. package/r7-test-env/prompt-R7-SMOKE.md +41 -0
  24. package/r7-test-env/sillyspec-r7.cmd +3 -0
  25. package/round4/account.mjs +98 -0
  26. package/round4/b1-analyze-task08.mjs +40 -0
  27. package/round4/b1-b1-target-validation.md +32 -0
  28. package/round4/b3a-prompt-audit-result.md +66 -0
  29. package/round4/b3a-prompt-audit.mjs +89 -0
  30. package/round4/brief-autocompact.md +34 -0
  31. package/round4/brief-session-replay.md +50 -0
  32. package/round4/deep-read-synthesis.md +38 -0
  33. package/round4/extract-full.mjs +38 -0
  34. package/round4/extract-timeline.mjs +43 -0
  35. package/round4/find-sessions.mjs +13 -0
  36. package/round4/full-r4-l.md +4707 -0
  37. package/round4/full-r4-o-l.md +4300 -0
  38. package/round4/full-r4-os.md +1681 -0
  39. package/round4/full-r4-sf.md +4255 -0
  40. package/round4/full-r4-sq.md +1734 -0
  41. package/round4/gate-audit-relay-receipt.md +30 -0
  42. package/round4/gate-value-audit.md +52 -0
  43. package/round4/optimization-plan.md +165 -0
  44. package/round4/prereg.md +67 -0
  45. package/round4/prompt-R4-L.md +63 -0
  46. package/round4/prompt-R4-O-L.md +59 -0
  47. package/round4/prompt-R4-O-S.md +56 -0
  48. package/round4/prompt-R4-S-F.md +60 -0
  49. package/round4/prompt-R4-S-Q.md +66 -0
  50. package/round4/r4-final-report.html +347 -0
  51. package/round4/r4-os-timeline.txt +297 -0
  52. package/round4/r4-sf-timeline.txt +738 -0
  53. package/round4/r4-sq-timeline.txt +322 -0
  54. package/round4/r4l-timeline.txt +793 -0
  55. package/round4/r4ol-timeline.txt +729 -0
  56. package/round4/r4sf-timeline.txt +852 -0
  57. package/round4/r5-scenario-matrix.md +46 -0
  58. package/round4/result-S.md +171 -0
  59. package/round4/result-final.md +46 -0
  60. package/round4/result-quality.md +63 -0
  61. package/src/change-list.js +9 -0
  62. package/src/classify-change.js +6 -0
  63. package/src/command-cards.js +74 -0
  64. package/src/commit-guard.js +2 -0
  65. package/src/config-schema.js +64 -12
  66. package/src/contract-matrix.js +26 -5
  67. package/src/db.js +35 -4
  68. package/src/decision-distill.js +9 -3
  69. package/src/diagnostic-codes.js +2 -1
  70. package/src/docs-check.js +1 -1
  71. package/src/doctor-diagnostics.js +44 -2
  72. package/src/flow-draft.js +748 -0
  73. package/src/flow-parity.js +236 -0
  74. package/src/flow-review.js +184 -0
  75. package/src/flow.js +1492 -0
  76. package/src/foreign-declared.js +17 -1
  77. package/src/fr-index.js +396 -22
  78. package/src/fs-atomic.js +5 -1
  79. package/src/git-helper.js +2 -2
  80. package/src/greenfield-bootstrap.js +98 -0
  81. package/src/handoff.js +120 -7
  82. package/src/hunk-attribution.js +191 -0
  83. package/src/index.js +602 -21
  84. package/src/init.js +25 -8
  85. package/src/knowledge-classify.js +47 -1
  86. package/src/knowledge-digest.js +162 -0
  87. package/src/knowledge-quicklog.js +246 -0
  88. package/src/machine-draft.js +128 -0
  89. package/src/machine-interface.js +194 -7
  90. package/src/mcp-server.js +1 -0
  91. package/src/module-impact.js +51 -22
  92. package/src/module-resolve.js +3 -1
  93. package/src/preview-progress.js +160 -0
  94. package/src/progress/change-registry.js +11 -3
  95. package/src/progress/shared.js +3 -2
  96. package/src/progress.js +56 -13
  97. package/src/quicklog.js +44 -6
  98. package/src/redomain.js +147 -0
  99. package/src/review-material-pack.js +204 -1
  100. package/src/run/archive-distill.js +4 -2
  101. package/src/run/bg-sync.js +67 -0
  102. package/src/run/command.js +127 -10
  103. package/src/run/complete-handlers.js +363 -73
  104. package/src/run/complete.js +356 -74
  105. package/src/run/gate-snapshot-ledger.js +213 -0
  106. package/src/run/gate-snapshot.js +260 -38
  107. package/src/run/gates.js +482 -268
  108. package/src/run/green-cache.js +123 -0
  109. package/src/run/junction-rm.js +70 -0
  110. package/src/run/next.js +14 -0
  111. package/src/run/prompt.js +185 -55
  112. package/src/run/quick-audit.js +213 -14
  113. package/src/run/shared.js +123 -4
  114. package/src/run/stage.js +182 -216
  115. package/src/run/test-ledger.js +264 -0
  116. package/src/run/verify-quality-scan.js +84 -7
  117. package/src/scope-audit.js +6 -1
  118. package/src/sentinel-assertions.js +114 -0
  119. package/src/setup.js +3 -3
  120. package/src/spec-sync.js +41 -1
  121. package/src/stage-contract.js +26 -8
  122. package/src/stage-review-checklist.js +2 -0
  123. package/src/stage-review.js +7 -2
  124. package/src/stage-templates.js +157 -0
  125. package/src/stages/archive.js +2 -0
  126. package/src/stages/brainstorm.js +24 -106
  127. package/src/stages/execute.js +222 -85
  128. package/src/stages/knowledge.js +74 -1
  129. package/src/stages/plan-postcheck.js +385 -20
  130. package/src/stages/plan.js +23 -1
  131. package/src/stages/quick.js +10 -2
  132. package/src/stages/verify.js +4 -2
  133. package/src/sync.js +81 -23
  134. package/src/task-done.js +181 -0
  135. package/src/task-review.js +8 -55
  136. package/src/taskcard-frontmatter.js +57 -0
  137. package/src/test-bindings.js +549 -0
  138. package/src/timeline.js +218 -0
  139. package/src/ui-visual.js +175 -0
  140. package/src/verify-draft.js +214 -0
  141. package/src/verify-postcheck.js +919 -159
  142. package/src/verify-probes.js +103 -25
  143. package/src/watcher.js +1031 -0
  144. package/src/worktree-apply.js +44 -4
  145. package/src/worktree-deps.js +22 -8
  146. package/src/worktree-policy.js +42 -0
  147. package/src/worktree.js +102 -15
  148. package/templates/agents-instruction.md +37 -18
@@ -130,13 +130,13 @@ export function parseProjectOverview(projectPath) {
130
130
 
131
131
  // --- Git info ---
132
132
  try {
133
- result.git.lastCommit = execSync('git log -1 --format=%s', { cwd: projectPath, encoding: 'utf-8' }).trim()
133
+ result.git.lastCommit = execSync('git log -1 --format=%s', { cwd: projectPath, encoding: 'utf-8', windowsHide: true }).trim()
134
134
  } catch {}
135
135
  try {
136
- result.git.branch = execSync('git branch --show-current', { cwd: projectPath, encoding: 'utf-8' }).trim()
136
+ result.git.branch = execSync('git branch --show-current', { cwd: projectPath, encoding: 'utf-8', windowsHide: true }).trim()
137
137
  } catch {}
138
138
  try {
139
- result.git.dirtyCount = parseInt(execSync('git status --porcelain', { cwd: projectPath, encoding: 'utf-8' }).trim().split('\n').filter(Boolean).length, 10) || 0
139
+ result.git.dirtyCount = parseInt(execSync('git status --porcelain', { cwd: projectPath, encoding: 'utf-8', windowsHide: true }).trim().split('\n').filter(Boolean).length, 10) || 0
140
140
  } catch {}
141
141
 
142
142
  return result
@@ -145,17 +145,17 @@ export function parseProjectOverview(projectPath) {
145
145
  export function parseGitDetail(projectPath) {
146
146
  const result = { branch: '', commits: [], untracked: [] }
147
147
  try {
148
- result.branch = execSync('git branch --show-current', { cwd: projectPath, encoding: 'utf-8' }).trim()
148
+ result.branch = execSync('git branch --show-current', { cwd: projectPath, encoding: 'utf-8', windowsHide: true }).trim()
149
149
  } catch {}
150
150
  try {
151
- const log = execSync('git log -5 --format=%h|%s|%an|%aI', { cwd: projectPath, encoding: 'utf-8' }).trim()
151
+ const log = execSync('git log -5 --format=%h|%s|%an|%aI', { cwd: projectPath, encoding: 'utf-8', windowsHide: true }).trim()
152
152
  result.commits = log.split('\n').filter(Boolean).map(line => {
153
153
  const [hash, message, author, date] = line.split('|')
154
154
  return { hash, message, author, date }
155
155
  })
156
156
  } catch {}
157
157
  try {
158
- const status = execSync('git status --porcelain', { cwd: projectPath, encoding: 'utf-8' }).trim()
158
+ const status = execSync('git status --porcelain', { cwd: projectPath, encoding: 'utf-8', windowsHide: true }).trim()
159
159
  result.untracked = status.split('\n').filter(Boolean).map(line => ({
160
160
  status: line.slice(0, 2).trim(),
161
161
  file: line.slice(3)
@@ -453,7 +453,7 @@ export function parseProjectState(projectPath) {
453
453
  // Use CLI to read current stage from SQLite
454
454
  try {
455
455
  const output = execSync('sillyspec progress show 2>/dev/null', {
456
- cwd: projectPath, encoding: 'utf-8', timeout: 5000
456
+ cwd: projectPath, encoding: 'utf-8', timeout: 5000, windowsHide: true
457
457
  })
458
458
  const stageMatch = output.match(/当前阶段:\s*(\S+)/)
459
459
  if (stageMatch) currentStage = stageMatch[1]
@@ -0,0 +1,93 @@
1
+ # R7 测试环境说明书(sillyspec 2-调用协议 · watcher · 机器起草 · 编辑距离路由)
2
+
3
+ > 环境组成:`sillyspec-r7.cmd`(wrapper,直跑主仓检出——**不动全局 3.29.6,不影响并行会话**)
4
+ > + `demo-project/`(ESM 小项目:lib/calc.js + test.js,测试命令 `node test.js`,已基线提交)。
5
+ > 全部命令在 `demo-project/` 目录内执行;Git Bash 里用 `cmd //c ..\sillyspec-r7.cmd ...` 或直接
6
+ > `node C:\Users\qinyi\IdeaProjects\sillyspec\src\index.js ...`。
7
+
8
+ ## 场景 A · 薄跑道 happy path(协议必需调用=2)
9
+
10
+ ```
11
+ ..\sillyspec-r7.cmd flow start --change my-first-thin --input "给 calc 加乘法。成功标准:
12
+ - mul(2,3)=6 有测试
13
+ - 既有 add 测试不回归"
14
+ ```
15
+ 看点:输出「协议调用 1/2」+材料路径清单;`.sillyspec/changes/my-first-thin/` 自动落
16
+ proposal/requirements/tasks 三件**机器稿**(MACHINE-DRAFT sha256 指纹标记+`<!--AGENT:槽-->`)
17
+ +flow-state.yaml;watcher 同步拉起。
18
+
19
+ 干活(正常开发):改 `lib/calc.js` 加 `export function mul(a,b){return a*b}`、test.js 补
20
+ `assert.equal(mul(2,3),6)`,然后 **git add -A && git commit -m "feat: mul"**(先提交——
21
+ 门禁快照以 HEAD+会话文件为基,含新文件的未提交态在快照内可能缺 package.json 类配置)。
22
+
23
+ ```
24
+ ..\sillyspec-r7.cmd flow done --change my-first-thin
25
+ ```
26
+ 看点:六子步逐个 done、测试门真跑 `node test.js`、末行「flow done 完成(2/2 协议调用收口)」
27
+ exit 0;change 目录移入 `changes/archive/`。**全程只有这 2 次协议调用。**
28
+
29
+ ## 场景 B · watcher 观测(事件与协议解耦)
30
+
31
+ 另开一个终端:
32
+ ```
33
+ powershell -NoProfile -Command "Get-Content -Wait .sillyspec\.runtime\watcher-events-my-first-thin.jsonl"
34
+ ```
35
+ 事件三类源:产物签名(proposal.md 出现/变更、任务卡 checkbox 翻格)/git 提交(HEAD 前进)/
36
+ 工件(质量扫描记录、archived 终态)。每行恒带 `"provisional":true`——平台只展示不判定。
37
+ 墙钟拆账:`.sillyspec/.runtime/watcher-stage-timing-my-first-thin.json`。
38
+ 已知边界:flow done 的 events 收口行读文件时 watcher 可能差一拍(3s 轮询)——收口显示
39
+ 「无事件文件」但事后文件在,属观测旁路 best-effort 语义。
40
+
41
+ ## 场景 C · 机器稿篡改拒收(验收侧守卫)
42
+
43
+ 新开 change 后,直接编辑 proposal.md 里机器段正文(改「任务原话转写」几个字)→
44
+ `flow done` → **三态拒收**:内容哈希失配 exit 1(不归档)。删掉整段 MACHINE-DRAFT 标记再试
45
+ → 标记缺失拒收。AGENT 槽(`<!--AGENT:槽N` 注释行下方)随便写 → 放行。
46
+
47
+ ## 场景 D · amend 留痕通道 + 编辑距离路由
48
+
49
+ 改机器段正文后不走 flow done,走:
50
+ ```
51
+ ..\sillyspec-r7.cmd flow amend-draft --change X
52
+ ```
53
+ 看点:重锚+ledger amendment 审计+**editRatio**(首版原文为基准,改写过半 → `route_hint: thick`
54
+ 提示——advisory 定案:测绿仍可薄过)。之后 `flow done` 正常收口,末尾醒目打印 route_hint,
55
+ 遥测落 `.sillyspec/.runtime/flow-telemetry.jsonl`(protocolCalls=2/editRatio/routeHint)。
56
+
57
+ ## 场景 E · 失败升厚 + 断点重入(fail-closed 三句)
58
+
59
+ 把 test.js 改坏(如断言 add(1,2)=4)→ `flow done`:
60
+ - 实测失败=**整单 FAIL exit≠0**,不继续 distill/归档
61
+ - 自动升厚 `tier: thick` + upgrade_reason 落 flow-state(born_face=thin 保归档不死锁)
62
+ - 修好 test.js 再跑同一条 `flow done` → 已完成子步 `artifacts(skip)` 幂等跳过,断点续到归档
63
+
64
+ ## 场景 F · legacy 一行回滚
65
+
66
+ `.sillyspec/local.yaml` 末尾加:
67
+ ```
68
+ flow:
69
+ mode: legacy
70
+ ```
71
+ `flow start` → 拒跑 exit 2 指路 `run <stage>`(既有全流程逐字不动);删掉这三行即回 thin。
72
+
73
+ ## 场景 G · 恢复简报
74
+
75
+ 干活干一半(有 dirty 文件未提交)时再跑 `flow start --change X` → 输出恢复简报:
76
+ 任务勾选/提交数/dirty 数/P2 记录/六子步标记 → 做到哪、剩什么、下一步。
77
+
78
+ ## 速查
79
+
80
+ | 目的 | 命令/文件 |
81
+ |---|---|
82
+ | 事件流 | `.sillyspec/.runtime/watcher-events-<change>.jsonl` |
83
+ | 墙钟拆账 | `.sillyspec/.runtime/watcher-stage-timing-<change>.json` |
84
+ | watcher 日志/锁 | `.sillyspec/.runtime/watcher.log` / `watcher.lock`(心跳租约 30s) |
85
+ | 机器稿台账 | `.sillyspec/.runtime/draft-ledger-<change>.json`(首版原文永存) |
86
+ | 协议遥测 | `.sillyspec/.runtime/flow-telemetry.jsonl` |
87
+ | flow 状态 | `.sillyspec/changes/<X>/flow-state.yaml`(tier/born_face/baseline_commit/子步标记) |
88
+ | 关 watcher | env `SILLYSPEC_WATCHER=0`(测试自救阀;自灭三闸:归档即退/git 仓蒸发退/12h 硬帽) |
89
+ | 重置 demo | `git reset --hard 7cc3615 && rm -rf .sillyspec/.runtime .sillyspec/changes` |
90
+
91
+ 显式档:`flow start --thick`(起始即厚档)/ `--with-tasks`(薄协议+生成任务卡,中间自愿
92
+ task done,收尾仍 flow done)。混跑回退:thin change 上跑 `run <stage>` → flow-state 落
93
+ legacy_fallback,flow done 拒裁并指路厚档。
@@ -0,0 +1,6 @@
1
+ export function add(a, b) { return a + b }
2
+ export function sub(a, b) { return a - b }
3
+ export function div(a, b) {
4
+ if (b === 0) throw new Error('div by zero')
5
+ return a / b
6
+ }
@@ -0,0 +1 @@
1
+ {"type":"module","name":"r7-demo","version":"1.0.0"}
@@ -0,0 +1,10 @@
1
+ import assert from 'node:assert/strict'
2
+ import { add, sub, div } from './lib/calc.js'
3
+ assert.equal(add(1, 2), 3)
4
+ console.log('test ok: add(1,2)=3')
5
+ assert.equal(sub(5, 3), 2)
6
+ console.log('test ok: sub(5,3)=2')
7
+ assert.equal(div(6, 3), 2)
8
+ console.log('test ok: div(6,3)=2')
9
+ assert.throws(() => div(1, 0), { message: 'div by zero' })
10
+ console.log('test ok: div(1,0) throws div by zero')
@@ -0,0 +1,41 @@
1
+ # R7 薄跑烟测提示词(贴给一个全新 agent 会话;30min 级小任务)
2
+
3
+ 你是一个开发会话,任务:在下面的演示项目里完成一个小功能。本项目接入了一套新的
4
+ 「2-调用」开发协议(flow),你是它的第一批真实用户——按协议干活即可,协议没让你做的事
5
+ 就不要做。
6
+
7
+ 【环境】
8
+ - 工作目录:C:\Users\qinyi\IdeaProjects\sillyspec\r7-test-env\demo-project(所有命令在此执行)
9
+ - CLI 入口(R7 版):cmd //c ..\sillyspec-r7.cmd <命令...>
10
+ (Git Bash 形态;PowerShell/cmd 形态:..\sillyspec-r7.cmd <命令...>)
11
+ - 会话标识:先执行 export SILLYSPEC_SESSION_ID=r7-smoke-1
12
+ - 项目现状:lib/calc.js 有 add;test.js 用断言风格测试;测试命令 node test.js
13
+
14
+ 【任务】
15
+ 给 calc 库增加两个函数并配套测试:
16
+ 1. sub(a,b) 减法——成功标准:sub(5,3)=2 有测试
17
+ 2. div(a,b) 除法,除零抛 Error('div by zero')——成功标准:div(6,3)=2 与除零抛错都有测试
18
+ 既有 add 测试不得回归。
19
+
20
+ 【协议契约——只有 2 次协议调用】
21
+ 1. 第一次:cmd //c ..\sillyspec-r7.cmd flow start --change r7-smoke-add-sub-div --input "<把上面任务原话贴进去>"
22
+ ——它会告诉你材料在哪、怎么干活。治理文档(提案/需求/任务清单)由 CLI 自动起草,你不要手写。
23
+ 2. 中间:正常开发。改代码、写测试、git 提交(git add -A && git commit -m "...")。
24
+ 不要跑任何 sillyspec run <stage> 命令;progress show 等只读命令也不必用——保持最小。
25
+ 如果你觉得自动起草的文档里机器写的部分说错了,唯一合法修改通道是 flow amend-draft
26
+ (命令形态在上面输出里找);不想改就直接忽略,不影响验收。
27
+ 3. 第二次:干完并提交后 cmd //c ..\sillyspec-r7.cmd flow done --change r7-smoke-add-sub-div
28
+ ——它是唯一裁决点:会真跑测试,失败会整单 FAIL(修好重跑同一条命令即可,断点自动续)。
29
+
30
+ 【边界】
31
+ - 全程协议类调用只许上述 2 次(这是被测量的数字)。
32
+ - 不要读 sillyspec 的源码或它的变更文档(.sillyspec/changes/archive/**)——你是用户不是评审。
33
+ - .sillyspec/ 目录里不要手写任何文件(这也是被测量的面)。
34
+ - 测试必须真实通过;node test.js 自己先跑绿再 flow done。
35
+
36
+ 【完成后汇报(务必按此格式,这是测试数据)】
37
+ 1. 协议调用清单:每条 sillyspec 命令原文+次数
38
+ 2. .sillyspec 写入清单:你亲手写过的文件(应为零;若用了 amend-draft 如实列出)
39
+ 3. 摩擦记录:协议该给你而没给的信息/你想做但被拦的事,逐条列(没有写「无」)
40
+ 4. flow done 是否一次通过;若失败过,失败原因与重入体验
41
+ 5. 一句话:这个流程 compared 于你平时用的开发流程,最不习惯的一点是什么
@@ -0,0 +1,3 @@
1
+ @echo off
2
+ rem R7 test build of sillyspec - runs repo checkout directly (no global install, parallel sessions on 3.29.6 unaffected)
3
+ node "C:\Users\qinyi\IdeaProjects\sillyspec\src\index.js" %*
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+ // R4 对照实验·核算脚本 —— zcode db 会话级 token/请求/时长归账
3
+ // 用法:node account.mjs [--arm R4] [--title 关键词] [--since 2026-09-19]
4
+ // 注意(第2/3轮各踩一次的坑):model_usage.started_at / session.time_created 均按
5
+ // 数值 epoch-ms 处理;若遇 ISO 字符串自动 Date.parse 兜底,绝不字符串比较。
6
+ import { DatabaseSync } from 'node:sqlite';
7
+
8
+ const DB = 'C:/Users/qinyi/.zcode/cli/db/db.sqlite';
9
+ const args = process.argv.slice(2);
10
+ const flag = (k) => { const i = args.indexOf(k); return i >= 0 ? args[i + 1] : null; };
11
+
12
+ const toMs = (v) => {
13
+ if (v === null || v === undefined) return null;
14
+ if (typeof v === 'number') return v;
15
+ const s = String(v);
16
+ if (/^\d+$/.test(s)) return Number(s);
17
+ const p = Date.parse(s.includes('T') ? s : s.replace(' ', 'T') + '+08:00');
18
+ return Number.isNaN(p) ? null : p;
19
+ };
20
+ const fmtMin = (ms) => (ms == null ? '?' : (ms / 60000).toFixed(1) + 'min');
21
+ const fmtTok = (t) => (t == null ? '?' : (t / 1e6).toFixed(2) + 'M');
22
+
23
+ const db = new DatabaseSync(DB, { readOnly: true });
24
+
25
+ // 1) 定位会话:标题关键词(可多个,逗号分隔),可加 --since YYYY-MM-DD 下限
26
+ const titleKw = (flag('--title') ?? 'R4').split(',').map(s => s.trim()).filter(Boolean);
27
+ const sinceRaw = flag('--since');
28
+ const sinceMs = sinceRaw ? toMs(sinceRaw + 'T00:00:00+08:00') : null;
29
+
30
+ const like = titleKw.map(() => 'title LIKE ?').join(' OR ');
31
+ const rows = db.prepare(
32
+ `SELECT id, parent_id, title, time_created FROM session WHERE (${like}) ORDER BY time_created`
33
+ ).all(...titleKw.map(k => '%' + k + '%'));
34
+
35
+ // 2) 每会话聚合(主会话 + 递归子会话),窗口=会话创建起 48h 防 spill
36
+ // v2(2026-09-21 R5 准备):三分扩展——新鲜 input(=input−cache_read)、缓存读、输出,
37
+ // 及账单当量两界(方案 v3.2 口径:新入 1× / 缓存 0.1× / 输出 3–5×,R5 验收判法依赖)
38
+ const aggFor = (sid) => db.prepare(
39
+ `SELECT COUNT(*) req, SUM(computed_total_tokens) tok, SUM(input_tokens) itok, SUM(output_tokens) otok,
40
+ SUM(cache_read_input_tokens) cr,
41
+ SUM(duration_ms) dur, MIN(started_at) mn, MAX(started_at) mx, SUM(tool_call_count) tools
42
+ FROM model_usage WHERE session_id = ? AND started_at >= ?`
43
+ );
44
+
45
+ const results = [];
46
+ for (const s of rows) {
47
+ const created = toMs(s.time_created);
48
+ const winStart = sinceMs != null ? Math.max(created ?? 0, sinceMs) : (created ?? 0);
49
+ const winEnd = winStart + 48 * 3600 * 1000;
50
+ // 递归滚子会话
51
+ const kids = db.prepare(
52
+ `WITH RECURSIVE tree(id, title) AS (SELECT id, title FROM session WHERE parent_id = ?
53
+ UNION ALL SELECT s.id, s.title FROM session s JOIN tree t ON s.parent_id = t.id)
54
+ SELECT id, title FROM tree`
55
+ ).all(s.id);
56
+ const self = aggFor(s.id).get(winStart > 0 ? winStart : 0);
57
+ // 请求窗口上界也钳(started_at < winEnd)
58
+ const inWin = (r) => { const a = db.prepare(
59
+ `SELECT COUNT(*) req, SUM(computed_total_tokens) tok, SUM(duration_ms) dur,
60
+ SUM(input_tokens) itok, SUM(output_tokens) otok, SUM(cache_read_input_tokens) cr
61
+ FROM model_usage
62
+ WHERE session_id = ? AND started_at >= ? AND started_at < ?`
63
+ ).get(r, winStart, winEnd); return a; };
64
+ const mainAgg = inWin(s.id);
65
+ let kidReq = 0, kidTok = 0, kidDur = 0, kidIt = 0, kidCr = 0, kidOt = 0;
66
+ for (const k of kids) {
67
+ const a = inWin(k.id); kidReq += a.req ?? 0; kidTok += a.tok ?? 0; kidDur += a.dur ?? 0;
68
+ kidIt += a.itok ?? 0; kidCr += a.cr ?? 0; kidOt += a.otok ?? 0;
69
+ }
70
+ const wall = mainAgg.req > 0 || kidReq > 0 ? null : null;
71
+ const span = db.prepare(
72
+ `SELECT MIN(started_at) mn, MAX(started_at) mx FROM model_usage WHERE session_id = ?`
73
+ ).get(s.id);
74
+ const spanMs = (toMs(span.mx) ?? 0) - (toMs(span.mn) ?? 0);
75
+ // 三分与当量(新鲜 = input − cache_read,钳 ≥0 防口径倒挂;当量两界 = 输出 3×/5×)
76
+ const mIt = mainAgg.itok ?? 0, mCr = mainAgg.cr ?? 0, mOt = mainAgg.otok ?? 0;
77
+ const tIt = mIt + kidIt, tCr = mCr + kidCr, tOt = mOt + kidOt;
78
+ const fresh = Math.max(0, tIt - tCr);
79
+ const eq3 = fresh + 0.1 * tCr + 3 * tOt;
80
+ const eq5 = fresh + 0.1 * tCr + 5 * tOt;
81
+ results.push({
82
+ id: s.id, title: s.title, kids: kids.length,
83
+ mainReq: mainAgg.req ?? 0, mainTok: mainAgg.tok ?? 0, mainDur: mainAgg.dur ?? 0,
84
+ kidReq, kidTok, kidDur,
85
+ totReq: (mainAgg.req ?? 0) + kidReq, totTok: ((mainAgg.tok ?? 0) + kidTok),
86
+ spanMin: spanMs / 60000,
87
+ fresh, cacheR: tCr, out: tOt, eq3, eq5,
88
+ });
89
+ }
90
+
91
+ // 3) 输出
92
+ console.log(`| 会话 | 标题 | 子代 | 主会话 req/tok/model-time | 子代 req/tok | 合计 req | 合计 tok | 活动跨度 | 新鲜in | 缓存R | 输出 | 当量@3× | 当量@5× |`);
93
+ console.log(`|---|---|---|---|---|---|---|---|---|---|---|---|---|`);
94
+ for (const r of results) {
95
+ console.log(`| ${r.id} | ${(r.title ?? '').slice(0, 40).replace(/\|/g, '/')} | ${r.kids} | ${r.mainReq} / ${fmtTok(r.mainTok)} / ${fmtMin(r.mainDur)} | ${r.kidReq} / ${fmtTok(r.kidTok)} | ${r.totReq} | ${fmtTok(r.totTok)} | ${fmtMin(r.spanMs ?? r.spanMin * 60000)} | ${fmtTok(r.fresh)} | ${fmtTok(r.cacheR)} | ${fmtTok(r.out)} | ${fmtTok(r.eq3)} | ${fmtTok(r.eq5)} |`);
96
+ }
97
+ if (results.length === 0) console.log('(无匹配会话——检查标题关键词或 --since');
98
+ db.close();
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ // B-① 靶点验证:解剖 R4-L 的 8.6M 实现子代理——读了什么、多大、是否越界
3
+ // 用法:node b1-analyze-task08.mjs
4
+ import { DatabaseSync } from 'node:sqlite';
5
+ const db = new DatabaseSync('C:/Users/qinyi/.zcode/cli/db/db.sqlite', { readOnly: true });
6
+ const sid = 'sess_subagent_agent_d60364e3-c378-49b8-a715-6ac5b9d9aa4f';
7
+ const WT = 'C:\\Users\\qinyi\\IdeaProjects\\multi-agent-platform\\.sillyspec\\.runtime\\worktrees\\2026-09-20-r4-session-replay';
8
+
9
+ const parts = db.prepare(
10
+ 'SELECT data, time_created tc FROM part WHERE session_id=? ORDER BY time_created ASC, sequence ASC'
11
+ ).all(sid);
12
+ const t0 = Number(parts[0].tc);
13
+ const rows = [];
14
+ for (const row of parts) {
15
+ const d = JSON.parse(row.data);
16
+ if (d.type !== 'tool') continue;
17
+ const rel = ((Number(d.time?.created ?? row.tc) - t0) / 60000).toFixed(0);
18
+ const inp = d.state?.input ?? {};
19
+ const out = String(d.state?.output ?? '');
20
+ const kb = Math.round(out.length / 1024);
21
+ const short = (p) => String(p).split(WT + '\\').pop()?.replaceAll('\\', '/') ?? p;
22
+ let detail = '';
23
+ if (d.tool === 'Read') detail = short(inp.file_path ?? '?');
24
+ else if (d.tool === 'Grep') detail = 'q=' + String(inp.pattern ?? inp.query ?? '').slice(0, 40);
25
+ else if (d.tool === 'Bash') detail = String(inp.command ?? '').slice(0, 70);
26
+ else detail = JSON.stringify(inp).slice(0, 60);
27
+ rows.push({ rel, tool: d.tool, detail, kb, status: d.state?.status });
28
+ }
29
+ const byTool = {};
30
+ for (const r of rows) byTool[r.tool] = (byTool[r.tool] ?? 0) + r.kb;
31
+ console.log('tool calls:', rows.length, '| 按工具 KB:', JSON.stringify(byTool));
32
+ console.log('--- 全部调用(时间 | 工具 | 输出KB | 详情)---');
33
+ for (const r of rows) console.log(`${r.rel}m ${r.tool.padEnd(7)} ${String(r.kb).padStart(6)}KB ${r.detail}`);
34
+ console.log('--- Read 同文件累计 ---');
35
+ const byPath = {};
36
+ for (const r of rows) if (r.tool === 'Read') (byPath[r.detail] ??= []).push(r.kb);
37
+ const s = (a) => a.reduce((x, y) => x + y, 0);
38
+ for (const [p, arr] of Object.entries(byPath).sort((a, b) => s(b[1]) - s(a[1])))
39
+ console.log(`${String(s(arr)).padStart(6)}KB ×${arr.length} ${p}`);
40
+ db.close();
@@ -0,0 +1,32 @@
1
+ # B-① 靶点验证报告(2026-09-21,第 1 批执行产物)
2
+
3
+ > 预设判定规则(optimization-plan.md B-①):「靶子不实不立项」。
4
+ > 数据源:zcode db(C:/Users/qinyi/.zcode/cli/db/db.sqlite)sess_5cb66a7e(R4-L 主会话)及其 19 个子会话;工具 b1-analyze-task08.mjs。
5
+
6
+ ## 判定:靶子不实——「越界读」零发生,B-① 实现项按预案砍掉
7
+
8
+ ### 证据一:8.6M 的构成不是「读了 8.6M 新内容」
9
+
10
+ task-08 子代理(sess_subagent_agent_d60364e3,58 请求)账面:名义输入 8.57M / 缓存读 8.39M(98%)/ 输出 0.07M / **新鲜输入(名义−缓存)仅 186K**。
11
+
12
+ 全部 62 次工具调用的输出总量 ≈335KB(Read 297KB + Bash 34KB)——与 186K 新鲜输入同量级自洽。8.6M 名义体量 = **58 轮 × 轮均 148K 上下文的累积重发**,是轮数乘上下文的机械积,不是读取行为。
13
+
14
+ ### 证据二:读取内容全部在任务契约链内,无一次越界
15
+
16
+ 62 次调用的读取对象逐条核过:task-08.md 任务卡、CLAUDE.md、turn-timeline.tsx(被适配组件)/ agent-log-card / turn-segment-views / session-log-assembler / runtime-session-helpers(契约链组件)、agent-log-card.test.tsx(测试先例)、session-panel-page(接线单点)、api-types / query-keys / agent-logs / round-divider(类型与工具链)、design.md + prototype-agent-replay.html(变更文档)。**没有一次读声明面之外的源码**,没有 node_modules / daemon / backend 深潜。重复读是「首次全读 + 后续定向切片」模式(如 turn-timeline 62KB 全读 + 16KB 切片),本身是省 token 的正确行为。
17
+
18
+ ### 证据三:全扇出面同构——新鲜内容只占 3.2%
19
+
20
+ 19 个子代理合计:名义输入 41.77M,**新鲜输入 1.34M(3.2%)**,输出 0.49M。各角色轮均上下文 35K–92K,唯 task-08 达 148K(契约链文件大:组件 78KB + 测试先例 37KB + design 15KB + prototype 14KB 全量进上下文)。
21
+
22
+ ## 处置(按预案预设规则)
23
+
24
+ 1. **砍掉第 2 批的「B-① 读清单注入」实现项**。读上界就是 1.34M 新鲜内容,其中「本可不读」的部分即使砍半也只有 ~0.7M 名义,对 42.3M 扇出是零头;allowed_paths 工作目录约束(子代理 prompt 已有)维持现状即够。B-① 前置验证本身完成使命。
25
+ 2. **真正杠杆换位:轮数 × 上下文双乘数**。42.3M ≈ Σ(轮数 × 轮均上下文),两个乘数各有对策:
26
+ - **上下文**:B-④ 材料包治这个——task-08 型大契约链任务(design/prototype 通读 + 大组件全量进上下文)正是材料包的靶子,且比原估计更实(148K vs 同侪 45–92K 的差额就是可压空间)。
27
+ - **轮数**:**新增候选 B-⑥ 轮数纪律**(提示词级)——task-08 的 58 轮里 Edit×7(相邻同文件逐个改)+ TodoWrite×4(每条占一轮重发 148K)+ 测试 Bash 循环多次;合并相邻 Edit、TodoWrite 降频、测试合并跑,砍 1/3 轮 ≈ 砍 1/3 名义体量。此项原方案未覆盖,性价比高于已砍的 B-①。
28
+ 3. **R5 预期路径修正**:扇出 42.3M → 28–32M 的驱动从「①②③④ 并用」改为「**B-④ 上下文压缩 + B-⑥ 轮数纪律**」双轮驱动;账单当量口径下缓存重发本就计 0.1×,硬门(扇出 ≤32M)以名义口径计不受影响。
29
+
30
+ ## 复核方式
31
+
32
+ `node round4/b1-analyze-task08.mjs` 重跑可复现证据一/二;证据三的聚合查询见本报告数据源行(account.mjs 同库同表 model_usage,input−cache_read 即新鲜口径)。
@@ -0,0 +1,66 @@
1
+ # B3a prompt 审计——db 全量口径(2026-09-21,v3 修正版)
2
+
3
+ ## A. CLI 渲染体量(R4-L 主会话,129 次 sillyspec Bash,合计 204KB ≈ 60K tokens)
4
+
5
+ | 子命令 | 次数 | 累计KB | 最大单次KB |
6
+ |---|---|---|---|
7
+ | run | 53 | 98.4 | 6.7 |
8
+ | ? | 45 | 70.8 | 5.2 |
9
+ | verify-probes | 4 | 10.2 | 7.9 |
10
+ | show | 1 | 9.9 | 9.9 |
11
+ | register-stage-review | 5 | 3.1 | 1.8 |
12
+ | review | 7 | 2.8 | 0.6 |
13
+ | taskcard | 1 | 2.7 | 2.7 |
14
+ | progress | 3 | 2.4 | 1.5 |
15
+ | gate | 2 | 1.7 | 1.2 |
16
+ | module-docs-sync | 2 | 0.5 | 0.3 |
17
+ | endpoints | 1 | 0.4 | 0.4 |
18
+ | design-init | 1 | 0.3 | 0.3 |
19
+ | brainstorm | 1 | 0.3 | 0.3 |
20
+ | git | 1 | 0.2 | 0.2 |
21
+ | module-impact | 1 | 0.1 | 0.1 |
22
+ | worktree | 1 | 0.0 | 0.0 |
23
+
24
+ ## B. 近重复渲染(头部签名相同出现 >1 次——重复注入的直接瘦身候选)
25
+
26
+ | 次数 | 单次体量 | 头部签名 |
27
+ |---|---|---|
28
+ | ×2 | ~2.6KB | --- stage: brainstorm step: 7/8 stepName: Design Grill 交叉审查 project: project change: 2026- |
29
+ | ×2 | ~0.3KB | ⚠ 未匹配模块的文件 24 个(不入 sidecar): backend/app/modules/platform_sync/router.py, backend/app/modu |
30
+ | ×2 | ~0.0KB | (Bash completed with no output) |
31
+
32
+ 近重复渲染累计 ≈6KB(占 CLI 渲染总量 3%)。
33
+
34
+ ## C. 实跑措辞规则转发率(execute.js buildWavePrompt 口径 → 编排者组装的子代理 prompt)
35
+
36
+ | 规则(实跑措辞) | CLI 渲染 | 转发进子代理 prompt |
37
+ |---|---|---|
38
+ | workdir 参数是强制必传 | 2 | 0 |
39
+ | 破坏隔离 | 2 | 0 |
40
+ | 调度者 + 审查者 | 2 | 0 |
41
+ | 你不要自己写代码 | 4 | 0 |
42
+ | 禁止读 | 0 | 16 |
43
+ | 禁止 push | 0 | 12 |
44
+ | 工作目录(强制 | 0 | 12 |
45
+ | allowed_paths | 30 | 0 |
46
+ | TDD | 0 | 0 |
47
+ | 先读后写 | 0 | 0 |
48
+
49
+ Agent 派发 19 次,prompt 合计 49.8KB(均值 2.6KB,最大 5KB)。
50
+
51
+ ## D. 解读与瘦身候选(B3a 结论,2026-09-21)
52
+
53
+ **总口径**:R4-L 主会话 CLI 渲染 204KB ≈ 60K tokens(占主会话名义 56.8M 的 ~0.1%,占峰值的次要面)——CLI 渲染不是 377K 峰值的主因(主因是会话累积),但每次渲染常驻上下文参与后续所有轮次的重发,瘦身仍有正收益。
54
+
55
+ **证据性发现**:
56
+ 1. **近重复渲染只占 3%(~6KB)**——「同一指令反复整体重渲染」不是成本大头,各步渲染因进度态不同而不同。去重渲染**不是**瘦身抓手,预期收益上限低。
57
+ 2. **run 子命令是渲染大头**:53 次 / 98.4KB(步骤指令渲染,单次最大 6.7KB)。同一阶段的步骤指令在 --done 循环中被多次近似重渲染(内容 90% 相同、状态行不同)。
58
+ 3. **编排者对派发规则是「提取规范、丢弃论述」模式**:CLI 渲染的 workdir 论述段(强制必传 + 破坏隔离后果 + json 范例)转发进子代理 prompt 的字面率为 0,编排者自写成紧凑隔离块(工作目录(强制 ×12 / 禁止读 ×16 / 禁止 push ×12,每条 ~50 字)。行为合规(worktree 隔离全程成立),说明**论述段被读过但未被复述**——其价值存疑但不能仅凭字面率为 0 判死(合规可能正来自读过论述)。
59
+ 4. allowed_paths 渲染 30 次、转发 0——但审查环节逐 task 对照 allowed_paths 检查(行为消费),属「CLI ↔ 门禁」消费而非 prompt 转发,保留。
60
+
61
+ **瘦身候选(供 B3b 依审计删减,全部低风险渐进)**:
62
+ - **C1(首选)run 步骤渲染增量化**:同阶段同步骤的重复渲染只出「状态 delta + 指令不变声明」,首渲染出全量。预计砍 run 渲染体量 30–50%(≈30–50KB/大任务)。机制级(CLI 渲染器小改),不是删规则——**不动规则内容**。
63
+ - **C2(次选,低置信)派发指令论述段压缩**:workdir/隔离的后果论述段压成一行规范 + 一行后果(保留 json 范例与 workdir 值)。省 ~1–2KB/次渲染。风险:论述可能支撑合规——R5 若有隔离事故即回滚。
64
+ - **不建议动**:四律请求钳(H3 实证 PASS 承重)、隔离规范行、allowed_paths、testcase-design 段(TDD 引导,taskcard 证据链)。
65
+
66
+ **方法论修正记录**:v1 transcript 口径因提取稿 700 字截断致规则渲染面全盲(17 签名 15 个零命中假象);v2 分类正则漏多 flag 前缀;v3 修正后数据可信。B3b 删任何规则前须按本 v3 口径复跑 + 行为级合规核查(字面转发率 ≠ 遵守率,本审计已实证两者背离)。
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ // B3a prompt 审计 v3(只读,db 全量口径):
3
+ // A. R4-L 主会话 CLI 渲染体量按子命令归因(修正 v2 分类正则)
4
+ // B. 近重复渲染检测(同头签名多次渲染 = 瘦身/缓存首要候选)
5
+ // C. 实跑措辞的规则转发率(execute.js buildWavePrompt 口径)
6
+ // 用法:node b3a-prompt-audit.mjs
7
+ import { DatabaseSync } from 'node:sqlite';
8
+ import { writeFileSync } from 'node:fs';
9
+
10
+ const db = new DatabaseSync('C:/Users/qinyi/.zcode/cli/db/db.sqlite', { readOnly: true });
11
+ const sid = 'sess_5cb66a7e-2ba6-44f9-9916-226fc553778b';
12
+ const parts = db.prepare(
13
+ 'SELECT data, time_created tc FROM part WHERE session_id=? ORDER BY time_created ASC, sequence ASC'
14
+ ).all(sid);
15
+ const t0 = Number(parts[0].tc);
16
+
17
+ const calls = [];
18
+ const agentPrompts = [];
19
+ for (const row of parts) {
20
+ const d = JSON.parse(row.data);
21
+ if (d.type !== 'tool') continue;
22
+ const rel = ((Number(d.time?.created ?? row.tc) - t0) / 60000).toFixed(0);
23
+ const inp = d.state?.input ?? {};
24
+ if (d.tool === 'Bash') {
25
+ const cmd = String(inp.command ?? '');
26
+ if (!/sillyspec/.test(cmd)) continue;
27
+ // 子命令 = sillyspec 后跳过所有 --flag(含带引号值)后的第一个词
28
+ const m = cmd.match(/sillyspec\s+(?:--[\w-]+(?:\s+(?:"[^"]*"|\S+))?\s+)*([\w-]+)/);
29
+ calls.push({ rel, sub: m ? m[1] : '?', chars: String(d.state?.output ?? '').length, out: String(d.state?.output ?? '') });
30
+ }
31
+ if (d.tool === 'Agent' || d.tool === 'Task')
32
+ agentPrompts.push({ rel, prompt: String(inp.prompt ?? '') });
33
+ }
34
+
35
+ const by = {};
36
+ for (const c of calls) {
37
+ by[c.sub] ??= { n: 0, chars: 0, max: 0 };
38
+ by[c.sub].n++; by[c.sub].chars += c.chars; by[c.sub].max = Math.max(by[c.sub].max, c.chars);
39
+ }
40
+ const total = Object.values(by).reduce((s, v) => s + v.chars, 0);
41
+
42
+ // 近重复:输出头部 200 字(压缩空白)做签名
43
+ const sig = {};
44
+ for (const c of calls) {
45
+ const s = c.out.slice(0, 200).replace(/\s+/g, ' ').replace(/\|/g, '/');
46
+ (sig[s] ??= []).push(c);
47
+ }
48
+ const dups = Object.entries(sig).filter(([, v]) => v.length > 1).sort((a, b) => b[1].length - a[1].length);
49
+
50
+ // 实跑措辞规则转发率
51
+ const SIGS = [
52
+ 'workdir 参数是强制必传', '破坏隔离', '调度者 + 审查者', '你不要自己写代码',
53
+ '禁止读', '禁止 push', '工作目录(强制', 'allowed_paths', 'TDD', '先读后写',
54
+ ];
55
+ const cliAll = calls.map(c => c.out).join('\n');
56
+ const agentAll = agentPrompts.map(a => a.prompt).join('\n');
57
+
58
+ const L = [];
59
+ L.push('# B3a prompt 审计——db 全量口径(2026-09-21,v3 修正版)');
60
+ L.push('');
61
+ L.push(`## A. CLI 渲染体量(R4-L 主会话,${calls.length} 次 sillyspec Bash,合计 ${(total / 1024).toFixed(0)}KB ≈ ${Math.round(total / 3.5 / 1000)}K tokens)`);
62
+ L.push('');
63
+ L.push('| 子命令 | 次数 | 累计KB | 最大单次KB |');
64
+ L.push('|---|---|---|---|');
65
+ for (const [k, v] of Object.entries(by).sort((a, b) => b[1].chars - a[1].chars))
66
+ L.push(`| ${k} | ${v.n} | ${(v.chars / 1024).toFixed(1)} | ${(v.max / 1024).toFixed(1)} |`);
67
+ L.push('');
68
+ L.push('## B. 近重复渲染(头部签名相同出现 >1 次——重复注入的直接瘦身候选)');
69
+ L.push('');
70
+ L.push('| 次数 | 单次体量 | 头部签名 |');
71
+ L.push('|---|---|---|');
72
+ for (const [k, v] of dups.slice(0, 10))
73
+ L.push(`| ×${v.length} | ~${(v[0].chars / 1024).toFixed(1)}KB | ${k.slice(0, 90)} |`);
74
+ const dupChars = dups.reduce((s, [, v]) => s + v.length * v[0].chars, 0);
75
+ L.push('');
76
+ L.push(`近重复渲染累计 ≈${(dupChars / 1024).toFixed(0)}KB(占 CLI 渲染总量 ${((dupChars / total) * 100).toFixed(0)}%)。`);
77
+ L.push('');
78
+ L.push('## C. 实跑措辞规则转发率(execute.js buildWavePrompt 口径 → 编排者组装的子代理 prompt)');
79
+ L.push('');
80
+ L.push('| 规则(实跑措辞) | CLI 渲染 | 转发进子代理 prompt |');
81
+ L.push('|---|---|---|');
82
+ for (const s of SIGS)
83
+ L.push(`| ${s} | ${cliAll.split(s).length - 1} | ${agentAll.split(s).length - 1} |`);
84
+ L.push('');
85
+ L.push(`Agent 派发 ${agentPrompts.length} 次,prompt 合计 ${(agentPrompts.reduce((s, a) => s + a.prompt.length, 0) / 1024).toFixed(1)}KB(均值 ${(agentPrompts.reduce((s, a) => s + a.prompt.length, 0) / agentPrompts.length / 1024).toFixed(1)}KB,最大 ${Math.max(...agentPrompts.map(a => a.prompt.length)) / 1024 | 0}KB)。`);
86
+ const outPath = new URL('b3a-prompt-audit-result.md', import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1');
87
+ writeFileSync(outPath, L.join('\n') + '\n', 'utf8');
88
+ console.log(L.join('\n'));
89
+ db.close();
@@ -0,0 +1,34 @@
1
+ ---
2
+ author: qinyi
3
+ created_at: 2026-09-20 17:19:12
4
+ ---
5
+ # 提案书(Proposal)— claude 引擎 autocompact 配置(provider 级)
6
+
7
+ ## 动机
8
+
9
+ claude 引擎长会话在 ~160K tokens 触发引擎自动压缩(默认 believed limit 200K × ~80% 设计点),生产会话(6e213eb3,113 轮)频繁被压。平台无干预手段,用户希望按 provider 配置压缩行为(更晚压缩/关闭/预计算)。
10
+
11
+ ## 关键问题
12
+
13
+ 1. 平台管道已有 `settings_config → lease 透传 → claude-settings 白名单 → settings.json` 机制(attribution 等键先例),但白名单不含任何 autocompact 键——配置写了也不生效。
14
+ 2. 前端 provider 表单无 autocompact 入口,用户只能手改数据库 JSON。
15
+ 3. window 配置超过模型实际窗口会撞硬限报错而非压缩——需要风险提示承载(用户自担的配置面)。
16
+
17
+ ## 变更范围
18
+
19
+ - daemon:claude-settings.ts 白名单加三键(值守护:window 正整数/两开关布尔)。
20
+ - 前端:llm-provider-form claude 分支「引擎自动压缩」区(三键 + 风险提示)。
21
+ - 文档:daemon.md 增量段。
22
+
23
+ ## 不在范围内(显式清单)
24
+
25
+ - profile 级配置(D-001 否决)
26
+ - pi/codex 引擎压缩配置
27
+ - 阈值比例可配(SDK 无可写项)
28
+
29
+ ## 成功标准(可验证)
30
+
31
+ - settings_config 配 `{"autoCompactWindow": 230000}` 的 claude provider 会话,daemon spawn 后 `$CLAUDE_CONFIG_DIR/settings.json` 含该键(单测断言)
32
+ - 三键值守护:window=0/负数/非整数、开关非布尔 → 不写入(单测)
33
+ - 前端表单 claude 分支渲染三键控件并正确提交进 settings_config;非 claude 不渲染(组件测试)
34
+ - 既有 attribution/env 白名单行为零回归(既有用例全绿)
@@ -0,0 +1,50 @@
1
+ ---
2
+ author: qinyi
3
+ created_at: 2026-09-19 20:55:00
4
+ ---
5
+ # 提案书(Proposal)
6
+
7
+ ## 动机
8
+
9
+ SillySpec CLI 自动上报创建的会话(origin=tool_report 且未继续过对话)打开后看到的
10
+ 是**日志元数据卡列表**,不是用户预期的会话内容。用户明确要求:「应该直接按 会话
11
+ 样式展示,只是数据来源不一样而已」「token 信息也要能获取到」「不光 zcode 其他
12
+ agent 也要核对能正确获取与正确显示」。
13
+
14
+ ## 关键问题
15
+
16
+ 1. **形态错位**:对话内容藏在每条日志卡的「查看内容」320px 嵌套小窗里,主对话被
17
+ 埋在元数据与子代理日志中间(实证会话 137ddfff:1 主日志 + 5 条子代理日志),
18
+ 没有「会话」的样子。
19
+ 2. **内容错位**:自主运行日志的 user 角色消息多为系统注入(task-notification),
20
+ 现渲染当用户气泡处理,直接「内容不对」;轮次边界(zcode turnId)与 token 用量
21
+ (response.usage 五项)都在日志里但解析层全部丢弃。
22
+ 3. **harness 覆盖断层**:claude-code 格式规整且含 usage 但无解析器(只能看原文);
23
+ cursor-agent transcript 干净可解析但整条上报链路缺失;cursor IDE 二进制格式点开
24
+ 是 409 死胡同。
25
+
26
+ ## 变更范围
27
+
28
+ - 前端:回放主体组件 AgentReplayBody(适配器 → TurnTimeline 真组件复用)、系统事件
29
+ 中性行、工作会话折叠条、不可用三态、token 徽标与累计显示。
30
+ - daemon:NormalizedLogMessage 扩展(usage/turnId/model/durationMs/sender)+ zcode
31
+ 补字段 + 新增 claude-code、cursor-agent 两个解析器 + RPC totalUsage。
32
+ - backend:messages 端点 schema 可选新字段透传 + gen:types(零表结构改动)。
33
+ - docs:cursor-agent 上报链路跨仓跟进记录。
34
+
35
+ ## 不在范围内(显式清单)
36
+
37
+ - 不做解析产物落库(L3 持久化,单独决策)
38
+ - 不做 cursor-agent 作为 provider 接入(运行时 token 捕获,另行立项)
39
+ - 不做 cursor IDE store.db 对话化(blob 库 + 无 token)
40
+ - 不动已激活 tool_report 会话路径与 chat 会话
41
+ - 不改 sillyspec 仓(cursor-agent 扫描上报仅记录跨仓跟进)
42
+
43
+ ## 成功标准(可验证)
44
+
45
+ - 打开纯 tool_report 会话:主体即会话时间线(对话/全部视图可用),主日志为正文、
46
+ 子代理在「工作会话」折叠条,系统事件显示为中性行非用户气泡。
47
+ - zcode/claude-code 回放可见每轮 token(输入/输出/上下文)与会话累计;数据源无
48
+ token(cursor-agent)时显示「未知」。
49
+ - 老 daemon / 机器离线 / 格式不支持 / 文件缺失时回退到显式中文提示,不弹错框。
50
+ - 全量既有测试不受影响(activated 路径、查看内容语义、上报/归属链路零改动)。