sillyspec 3.29.4 → 3.29.5

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 (56) hide show
  1. package/assets/command-cards/run-archive.md +26 -0
  2. package/assets/command-cards/run-brainstorm.md +33 -0
  3. package/assets/command-cards/run-execute.md +29 -0
  4. package/assets/command-cards/run-plan.md +27 -0
  5. package/assets/command-cards/run-quick.md +34 -0
  6. package/assets/command-cards/run-verify.md +29 -0
  7. package/assets/command-cards/status.md +27 -0
  8. package/package.json +1 -1
  9. package/round4/account.mjs +85 -0
  10. package/round4/brief-autocompact.md +34 -0
  11. package/round4/brief-session-replay.md +50 -0
  12. package/round4/deep-read-synthesis.md +38 -0
  13. package/round4/extract-full.mjs +38 -0
  14. package/round4/extract-timeline.mjs +43 -0
  15. package/round4/find-sessions.mjs +13 -0
  16. package/round4/full-r4-l.md +4707 -0
  17. package/round4/full-r4-o-l.md +4300 -0
  18. package/round4/full-r4-os.md +1681 -0
  19. package/round4/full-r4-sf.md +4255 -0
  20. package/round4/full-r4-sq.md +1734 -0
  21. package/round4/gate-audit-relay-receipt.md +30 -0
  22. package/round4/gate-value-audit.md +52 -0
  23. package/round4/prereg.md +67 -0
  24. package/round4/prompt-R4-L.md +63 -0
  25. package/round4/prompt-R4-O-L.md +59 -0
  26. package/round4/prompt-R4-O-S.md +56 -0
  27. package/round4/prompt-R4-S-F.md +60 -0
  28. package/round4/prompt-R4-S-Q.md +66 -0
  29. package/round4/r4-os-timeline.txt +297 -0
  30. package/round4/r4-sf-timeline.txt +738 -0
  31. package/round4/r4-sq-timeline.txt +322 -0
  32. package/round4/r4l-timeline.txt +793 -0
  33. package/round4/r4ol-timeline.txt +729 -0
  34. package/round4/r4sf-timeline.txt +852 -0
  35. package/round4/result-S.md +171 -0
  36. package/round4/result-final.md +46 -0
  37. package/round4/result-quality.md +63 -0
  38. package/src/command-cards.js +73 -0
  39. package/src/contract-matrix.js +26 -5
  40. package/src/diagnostic-codes.js +2 -1
  41. package/src/index.js +33 -6
  42. package/src/init.js +25 -8
  43. package/src/machine-interface.js +88 -3
  44. package/src/module-impact.js +51 -22
  45. package/src/run/command.js +4 -0
  46. package/src/run/complete-handlers.js +36 -1
  47. package/src/run/complete.js +6 -3
  48. package/src/run/gate-snapshot.js +10 -4
  49. package/src/run/gates.js +114 -37
  50. package/src/run/quick-audit.js +28 -2
  51. package/src/run/shared.js +3 -1
  52. package/src/stages/execute.js +2 -2
  53. package/src/stages/plan-postcheck.js +33 -19
  54. package/src/taskcard-frontmatter.js +57 -0
  55. package/src/verify-postcheck.js +59 -1
  56. package/src/verify-probes.js +45 -23
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: sillyspec:run-archive
3
+ description: archive 归档阶段:变更产物归档+知识蒸馏+FR/决策入库
4
+ ---
5
+ # 何时用
6
+
7
+ verify PASS 后归档:目录迁移+distill 知识沉淀+活文档同步。
8
+
9
+ # 生命周期速查
10
+
11
+ ```bash
12
+ export SILLYSPEC_SESSION_ID=<agent名+任务名> # 每条命令都带(shell 状态不持久;多会话并行的所有权判定依赖它)
13
+ sillyspec run archive --change <变更名>
14
+ sillyspec run archive --done --change <变更名> --output "<归档摘要>"
15
+ # 在隔离 worktree 内跑时(守卫默认拒绝,需双 flag):
16
+ # sillyspec <cmd> --allow-worktree-cwd --spec-dir "<worktree>/.sillyspec"
17
+ ```
18
+
19
+ # 防坑清单
20
+
21
+ - 归档是移动不是复制——活跃目录会被迁走,勿在归档中途手改。
22
+ - 跨仓同步检查(multi-repo 场景)在归档收口统一出。
23
+
24
+ # 边界声明
25
+
26
+ 本卡不含步骤内容——每步的具体指令由 `sillyspec run archive` 按当前状态实时渲染(含门禁、审查档位、产物契约)。本卡只管启动与防坑。
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: sillyspec:run-brainstorm
3
+ description: brainstorm 头脑风暴阶段:需求澄清→四件套→设计→Grill 审查
4
+ ---
5
+ # 何时用
6
+
7
+ 新能力/行为契约变更/跨模块取舍——需要落盘设计决策的变更,第一步走这里(判规模:范围明确的局部修补走 run-quick)。
8
+
9
+ # 生命周期速查
10
+
11
+ ```bash
12
+ export SILLYSPEC_SESSION_ID=<agent名+任务名> # 每条命令都带(shell 状态不持久;多会话并行的所有权判定依赖它)
13
+ sillyspec run brainstorm --change <变更名> --input "<需求一句话>"
14
+ # 每步产出后:
15
+ sillyspec run brainstorm --done --change <变更名> --output "<摘要>"
16
+ # 方案选择/设计确认等用户决策点(已拍板则一步落):
17
+ sillyspec run brainstorm --done --answer "<用户回答>" --change <变更名> --output "<摘要>"
18
+ # 预检省一轮返工:
19
+ sillyspec gate brainstorm --change <变更名>
20
+ # 在隔离 worktree 内跑时(守卫默认拒绝,需双 flag):
21
+ # sillyspec <cmd> --allow-worktree-cwd --spec-dir "<worktree>/.sillyspec"
22
+ ```
23
+
24
+ # 防坑清单
25
+
26
+ - 四件套/设计/任务卡一律用 CLI 骨架生成(fourpiece-init / design-init / taskcard)——手拼 frontmatter 会被门禁拦。
27
+ - design.md 计划新建的文件路径必须带 `NEW:` 前缀(末步存在性核验铁律)。
28
+ - Grill 审查档 S2=独立子代理单轮;审查者只读,唯一可写 review.json;FAIL 后 resume 同一审查者只验阻断项(禁全文重审)。
29
+ - 用户决策点用 --answer 落既有决策,不要空等。
30
+
31
+ # 边界声明
32
+
33
+ 本卡不含步骤内容——每步的具体指令由 `sillyspec run brainstorm` 按当前状态实时渲染(含门禁、审查档位、产物契约)。本卡只管启动与防坑。
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: sillyspec:run-execute
3
+ description: execute 执行阶段:按 Wave 逐任务实现(子代理或直做)+ 逐 task review
4
+ ---
5
+ # 何时用
6
+
7
+ plan 收口后写代码。多 agent 共享仓——开工前重读目标文件最新态。
8
+
9
+ # 生命周期速查
10
+
11
+ ```bash
12
+ export SILLYSPEC_SESSION_ID=<agent名+任务名> # 每条命令都带(shell 状态不持久;多会话并行的所有权判定依赖它)
13
+ sillyspec run execute --change <变更名>
14
+ sillyspec run execute --done --change <变更名> --output "<摘要>"
15
+ # 逐 task review.json:
16
+ sillyspec review write --change <变更名> --task task-NN --spec <verdict> --quality <verdict>
17
+ # 在隔离 worktree 内跑时(守卫默认拒绝,需双 flag):
18
+ # sillyspec <cmd> --allow-worktree-cwd --spec-dir "<worktree>/.sillyspec"
19
+ ```
20
+
21
+ # 防坑清单
22
+
23
+ - 改前重跑读最新态(多 agent 并行,Edit 前文件可能已被他侧改)。
24
+ - review 的 changedFiles 必须与本 task 卡 target_files/allowed_paths 相交(完全不相交过不了门)。
25
+ - 破坏性 git op 前先备份;禁止跳过 hook。
26
+
27
+ # 边界声明
28
+
29
+ 本卡不含步骤内容——每步的具体指令由 `sillyspec run execute` 按当前状态实时渲染(含门禁、审查档位、产物契约)。本卡只管启动与防坑。
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: sillyspec:run-plan
3
+ description: plan 计划阶段:复杂度分类→分级计划→计划审查→TaskCard→Wave 校验
4
+ ---
5
+ # 何时用
6
+
7
+ brainstorm 收口后拆任务排 Wave(plan_level=light 走轻计划,仍需 Wave 结构与任务卡)。
8
+
9
+ # 生命周期速查
10
+
11
+ ```bash
12
+ export SILLYSPEC_SESSION_ID=<agent名+任务名> # 每条命令都带(shell 状态不持久;多会话并行的所有权判定依赖它)
13
+ sillyspec run plan --change <变更名>
14
+ sillyspec run plan --done --change <变更名> --output "<摘要>"
15
+ # 在隔离 worktree 内跑时(守卫默认拒绝,需双 flag):
16
+ # sillyspec <cmd> --allow-worktree-cwd --spec-dir "<worktree>/.sillyspec"
17
+ ```
18
+
19
+ # 防坑清单
20
+
21
+ - plan.md 的 Wave 段只收纯 ID 引用行(`- task-01`),带括注长文会被 execute 契约门禁拦。
22
+ - 任务卡用 Edit 填充勿整文件重写(CRLF/漏字段回归);taskcard 骨架占位符未替换会被 --done 硬校验拦。
23
+ - 卡内 depends_on 必须与 tasks.md 注册表一致(分叉=执行序错乱)。
24
+
25
+ # 边界声明
26
+
27
+ 本卡不含步骤内容——每步的具体指令由 `sillyspec run plan` 按当前状态实时渲染(含门禁、审查档位、产物契约)。本卡只管启动与防坑。
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: sillyspec:run-quick
3
+ description: quick 轻流程:无落盘设计决策的小修补,三步到 --done
4
+ ---
5
+ # 何时用
6
+
7
+ 范围明确的局部修补(判据见 AGENTS.md 第 6 条;倒推 B 模式=代码先写好用 --done 收尾登记)。
8
+
9
+ # 生命周期速查
10
+
11
+ ```bash
12
+ export SILLYSPEC_SESSION_ID=<agent名+任务名> # 每条命令都带(shell 状态不持久;多会话并行的所有权判定依赖它)
13
+ # 启动(--input 必带,缺了占位标题/CLI 警告劝重启白跑一轮):
14
+ sillyspec run quick --input "<语义化标题>"
15
+ # 步进(每步产出后):
16
+ sillyspec run quick --done --change quick-<会话ID> --output "<摘要>"
17
+ # 末步收尾(四参数结构化落账 + 文件括注 + 关联变更):
18
+ sillyspec run quick --done --change quick-<会话ID> --req "<一句话标题>" --cause "<为什么改>" --solution "<怎么改的>" --result "<验证情况:测试数/lint/部署>" --file-notes "path::括注 || path2::括注" --linked-changes <关联的正式变更名>
19
+ # 在隔离 worktree 内跑时(守卫默认拒绝,需双 flag):
20
+ # sillyspec <cmd> --allow-worktree-cwd --spec-dir "<worktree>/.sillyspec"
21
+ # 断点恢复:
22
+ sillyspec progress show # 查进度后续跑
23
+ ```
24
+
25
+ # 防坑清单
26
+
27
+ - --done 时显式 --linked-changes 有效(会并入 guard:蒸馏尾/轻归档/QUICKLOG 关联行都吃它);'none' 清空。
28
+ - 触及 src/test 的改动 --done 门禁会亲自实测 local.yaml 的 test/lint——想省「被拦→修→重跑」一轮就先自跑。
29
+ - 纯 lint 失败重试时门禁仍会全量重测(已知损耗,勿绕门)。
30
+ - 显式 pathspec 提交(`git commit -m "..." -- 文件…`),禁 git add -A。
31
+
32
+ # 边界声明
33
+
34
+ 本卡不含步骤内容——每步的具体指令由 `sillyspec run quick` 按当前状态实时渲染(含门禁、审查档位、产物契约)。本卡只管启动与防坑。
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: sillyspec:run-verify
3
+ description: verify 验证阶段:探针核验+测试对账+验收报告
4
+ ---
5
+ # 何时用
6
+
7
+ execute 收口后对照文档验收(probe 契约+known_failures 对账+测试绿)。
8
+
9
+ # 生命周期速查
10
+
11
+ ```bash
12
+ export SILLYSPEC_SESSION_ID=<agent名+任务名> # 每条命令都带(shell 状态不持久;多会话并行的所有权判定依赖它)
13
+ sillyspec run verify --change <变更名>
14
+ sillyspec run verify --done --change <变更名> --output "<验收摘要>"
15
+ # 探针骨架缺失时:
16
+ sillyspec verify-probes --init --change <变更名> # 已存在加 --force 刷新
17
+ # 在隔离 worktree 内跑时(守卫默认拒绝,需双 flag):
18
+ # sillyspec <cmd> --allow-worktree-cwd --spec-dir "<worktree>/.sillyspec"
19
+ ```
20
+
21
+ # 防坑清单
22
+
23
+ - gate 失败明细落 .runtime/verify-runs/gate-<stage>-<change>.json(勿靠 stdout 记忆)。
24
+ - 非测试逻辑有误禁改测试来过——修逻辑。
25
+ - known_failures 豁免只在 local.yaml 声明位置复核,勿手抄。
26
+
27
+ # 边界声明
28
+
29
+ 本卡不含步骤内容——每步的具体指令由 `sillyspec run verify` 按当前状态实时渲染(含门禁、审查档位、产物契约)。本卡只管启动与防坑。
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: sillyspec:status
3
+ description: 状态查询:进度/变更清单/断点恢复入口
4
+ ---
5
+ # 何时用
6
+
7
+ 任何时刻查「我在哪、下一步做什么」——多变更并行时先看再动。
8
+
9
+ # 生命周期速查
10
+
11
+ ```bash
12
+ export SILLYSPEC_SESSION_ID=<agent名+任务名> # 每条命令都带(shell 状态不持久;多会话并行的所有权判定依赖它)
13
+ sillyspec progress show [--change <名>] [--all] [--json]
14
+ sillyspec status
15
+ # 有信号变更(冲突/滞留/疑似完成)保持详情渲染;--all 展开全部
16
+ # 在隔离 worktree 内跑时(守卫默认拒绝,需双 flag):
17
+ # sillyspec <cmd> --allow-worktree-cwd --spec-dir "<worktree>/.sillyspec"
18
+ ```
19
+
20
+ # 防坑清单
21
+
22
+ - 多会话并行:变更各自 --change 隔离,永不 reset 他人变更。
23
+ - 中途停下不靠额外命令存进度——上次 --done 已落盘。
24
+
25
+ # 边界声明
26
+
27
+ 本卡不含步骤内容——每步的具体指令由 `sillyspec run status` 按当前状态实时渲染(含门禁、审查档位、产物契约)。本卡只管启动与防坑。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.29.4",
3
+ "version": "3.29.5",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
@@ -0,0 +1,85 @@
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
+ const aggFor = (sid) => db.prepare(
37
+ `SELECT COUNT(*) req, SUM(computed_total_tokens) tok, SUM(input_tokens) itok, SUM(output_tokens) otok,
38
+ SUM(duration_ms) dur, MIN(started_at) mn, MAX(started_at) mx, SUM(tool_call_count) tools
39
+ FROM model_usage WHERE session_id = ? AND started_at >= ?`
40
+ );
41
+
42
+ const results = [];
43
+ for (const s of rows) {
44
+ const created = toMs(s.time_created);
45
+ const winStart = sinceMs != null ? Math.max(created ?? 0, sinceMs) : (created ?? 0);
46
+ const winEnd = winStart + 48 * 3600 * 1000;
47
+ // 递归滚子会话
48
+ const kids = db.prepare(
49
+ `WITH RECURSIVE tree(id, title) AS (SELECT id, title FROM session WHERE parent_id = ?
50
+ UNION ALL SELECT s.id, s.title FROM session s JOIN tree t ON s.parent_id = t.id)
51
+ SELECT id, title FROM tree`
52
+ ).all(s.id);
53
+ const self = aggFor(s.id).get(winStart > 0 ? winStart : 0);
54
+ // 请求窗口上界也钳(started_at < winEnd)
55
+ const inWin = (r) => { const a = db.prepare(
56
+ `SELECT COUNT(*) req, SUM(computed_total_tokens) tok, SUM(duration_ms) dur FROM model_usage
57
+ WHERE session_id = ? AND started_at >= ? AND started_at < ?`
58
+ ).get(r, winStart, winEnd); return a; };
59
+ const mainAgg = inWin(s.id);
60
+ let kidReq = 0, kidTok = 0, kidDur = 0;
61
+ for (const k of kids) {
62
+ const a = inWin(k.id); kidReq += a.req ?? 0; kidTok += a.tok ?? 0; kidDur += a.dur ?? 0;
63
+ }
64
+ const wall = mainAgg.req > 0 || kidReq > 0 ? null : null;
65
+ const span = db.prepare(
66
+ `SELECT MIN(started_at) mn, MAX(started_at) mx FROM model_usage WHERE session_id = ?`
67
+ ).get(s.id);
68
+ const spanMs = (toMs(span.mx) ?? 0) - (toMs(span.mn) ?? 0);
69
+ results.push({
70
+ id: s.id, title: s.title, kids: kids.length,
71
+ mainReq: mainAgg.req ?? 0, mainTok: mainAgg.tok ?? 0, mainDur: mainAgg.dur ?? 0,
72
+ kidReq, kidTok, kidDur,
73
+ totReq: (mainAgg.req ?? 0) + kidReq, totTok: ((mainAgg.tok ?? 0) + kidTok),
74
+ spanMin: spanMs / 60000,
75
+ });
76
+ }
77
+
78
+ // 3) 输出
79
+ console.log(`| 会话 | 标题 | 子代 | 主会话 req/tok/model-time | 子代 req/tok | 合计 req | 合计 tok | 活动跨度 |`);
80
+ console.log(`|---|---|---|---|---|---|---|---|`);
81
+ for (const r of results) {
82
+ 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)} |`);
83
+ }
84
+ if (results.length === 0) console.log('(无匹配会话——检查标题关键词或 --since');
85
+ 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 路径、查看内容语义、上报/归属链路零改动)。
@@ -0,0 +1,38 @@
1
+ # R4 五会话全量深读·综合判读(2026-09-21,四子代理并行精读)
2
+
3
+ ## 一、「质量差距是不是小概率」的最终答案:不是,两记实锤
4
+
5
+ **实锤 1:大任务 OpenSpec 侧存在同类缺陷。** R4-O-L 的 daemon 解析器 registry 键名(`claude-code-transcript-jsonl` 等)是**按命名惯例自造的**,未与上报侧(sillyspec CLI,禁读无法对证)核验;且仓内既有测试常量 `claude-transcript-jsonl` 已经暗示了另一种命名——若不符,解析器永不触发、静默 unsupported,**流程内无任何环节能发现**。与错键(autoCompactPreCompute)同一缺陷类:**外部契约事实未经核验即落码,自洽测试掩护其穿越全部关卡**。两个独立 OpenSpec 跑、两实例——这是系统性暴露面,不是运气。
6
+
7
+ **实锤 2(最锐利):两个 agent 生成了同一个错误先验。** R4-S-Q(sillyspec)在 [3.2m] 同样猜了 `autoCompactPrecompute`、[3.9m] 甚至专门搜了这个词——**但先验证再落码**,搜出真名 `precomputeCompactionEnabled`。同模型、同先验、同错误生成;唯一差别是有无验证步。**错误生成是确定性的,拦截是结构性的。**
8
+
9
+ B 侧错键穿越了六道关卡(proposal→spec→design→tasks→测试→archive),全绿——格式校验不校验事实,文档链越长只是把同一个错复制得更体面。
10
+
11
+ ## 二、效率面的新分解(深读修正)
12
+
13
+ **R4-L(185min)**:~118min(64%)是子代理扇出等待+后台门禁+额度事故(4 路并发派发触发额度耗尽,中断 ~17min 由用户恢复);主代理自身生成 <1/3;无效功 ~28min(CLI 旗标摩擦、review.json 两次归位、Wave6 --done 因输出 tail 截断三连跑、verify-result 槽格式学费、嵌套 worktree 下 module-docs-sync 失效、重复 pnpm install)。代码层零返工、15 commit 全净;无联网核验但用本机真实数据(38 个 cursor store.db 全量实测)做了更硬的核验。
14
+
15
+ **R4-O-L(80min)快的解剖**:零子代理(省 40-60m)+ 决策链短(20-30m)+ 只跑相关测试(10-20m)+ 无视觉验收(10-15m)+ 无独立审查(10-20m)+ 探查前置零返工。反直觉:探查占 24%(快来自读得多);最大黑洞是环境(venv/转义 ~8m);最强审查者是遗留测试套件(7 处精确 toEqual 断言逼显式化契约变更)。
16
+
17
+ **R4-S-F(171min)**:verify 92min 中 **75-80min(82%)是纯门禁运行**(7 次 --done、9 次全量门禁);agent 有效工作仅 ~15m。三方拆账:CLI ≈50%、agent ≈12%、环境 ≈6%。新发现的结构性问题:① 同一 CLI 内 `gate verify`(独立跑)PASS 与 `verify --done` FAIL 并存——**两套矛盾判定路径误导诊断 16min**;② 「越正确越被罚」——agent 正确地不动并行会话归属的债文件,快照 overlay 因此永远取 HEAD 带债版,门禁结构性奖励越权改他人文件;③ 根因(快照说明+advisory 提示)一直躺在后台日志里,晚读全文多烧 20min。**重跑地板:不改工具 ~60min;修三处(lint 前置/门禁缓存/检查前置)~45min**——全流程与 quick 的合理差距应是 ~20min 仪式成本,不是本次的 130min。
18
+
19
+ **小任务对(41.2 vs 21.5)**:19.7min 差逐项对平——两轮门禁 +19.9m(纯等待)、存量债清偿 +1.9m、联网核验 +1.2m、stash 对照 +0.6m、终态观测 +1.3m,减 B 的四件套/排障/阅读 ~6.7m。**两侧纯编码测试速度等价**(5-6m)。A 的门禁误拦反而触发正向行为(清债+全量复验拿到 mypy 963 文件级保证)。
20
+
21
+ ## 三、两侧 agent 的真实面貌(修正刻板印象)
22
+
23
+ - OpenSpec 侧 agent **探索纪律很强**(真实数据探针、搜而不信、诚实披露风险)——缺陷不在 agent 能力,在流程无核验结构。
24
+ - sillyspec 侧 R4-S-F 的排障有 15min 绕路(未早读日志全文)、两次被 gate verify 的误导 PASS 带偏。
25
+ - B 侧两个真亮点值得吸收:**前端依赖后台安装与改码并行**(墙钟并行度);**三态下拉表达「键缺省 vs 显式 false」**(语义优于 A 的不对称 checkbox)。
26
+
27
+ ## 四、修复清单增量(在已办五项 + 两缺口之外,新增)
28
+
29
+ 1. **`gate verify` 与 `verify --done` 判定路径统一**(矛盾输出,16min 级误导)
30
+ 2. 门禁失败输出 tail 截断问题(Wave6 三连跑根因)——失败时给全量日志路径提示
31
+ 3. 嵌套 worktree 下 module-docs-sync 失效(24 文件全未匹配)
32
+ 4. verify-result 槽格式旧版拒收的一次性学费(--init 提示要更早)
33
+ 5. 编排层:并发派发上限感知(4 路齐发触发额度耗尽)
34
+ 6. (对 OpenSpec 的镜像建议,供对比参考):外部契约事实表 + 来源强制校验 + vendored sdk.d.ts 的 10 秒 grep 检查——错键当场现形
35
+
36
+ ## 五、方法论沉淀
37
+
38
+ 「验证全在模型侧(自愿)」与「验证全在机器侧(盲跑)」恰好各错一半——两个流程的真正差距不是仪式多少,而是**防线是否对准缺陷类**:OpenSpec 六道格式关零事实核验;sillyspec 有事实核验结构但机器门禁 40%+ 假拦+双跑浪费。
@@ -0,0 +1,38 @@
1
+ // 全量会话导出:node extract-full.mjs <session前缀> <输出文件>
2
+ // 正文全量、reasoning 截 300、工具输入截 500(命令全文)、工具输出截 700
3
+ import { DatabaseSync } from 'node:sqlite';
4
+ import { writeFileSync } from 'node:fs';
5
+
6
+ const [prefix, outFile] = process.argv.slice(2);
7
+ const db = new DatabaseSync('C:/Users/qinyi/.zcode/cli/db/db.sqlite', { readOnly: true });
8
+ const sess = db.prepare('SELECT id, title, time_created FROM session WHERE id LIKE ?').get('sess_' + prefix + '%');
9
+ if (!sess) { console.error('not found'); process.exit(1); }
10
+
11
+ const parts = db.prepare(
12
+ `SELECT p.data, p.time_created tc, JSON_EXTRACT(m.data,'$.role') role
13
+ FROM part p JOIN message m ON p.message_id = m.id
14
+ WHERE p.session_id = ? ORDER BY p.time_created ASC, p.sequence ASC`
15
+ ).all(sess.id);
16
+
17
+ const t0 = Number(sess.time_created);
18
+ const lines = [`# ${sess.title}`, `# 会话 ${sess.id} 起点 ${new Date(t0).toISOString()}`, ''];
19
+ for (const row of parts) {
20
+ const d = JSON.parse(row.data);
21
+ const rel = ((Number(d.time?.created ?? row.tc) - t0) / 60000).toFixed(1);
22
+ const clean = (s, n) => String(s ?? '').replace(/\u001b\[[0-9;]*m/g, '').slice(0, n);
23
+ if (d.type === 'text') {
24
+ lines.push(`\n[${rel}m] ${String(row.role).toUpperCase()} 正文:\n${clean(d.text, 6000)}`);
25
+ } else if (d.type === 'reasoning') {
26
+ const r = clean(d.text ?? d.summary, 300);
27
+ if (r.trim()) lines.push(`[${rel}m] (思考) ${r}`);
28
+ } else if (d.type === 'tool') {
29
+ const inp = d.state?.input ?? {};
30
+ const key = inp.command ?? inp.file_path ?? inp.path ?? inp.pattern ?? inp.prompt ?? JSON.stringify(inp);
31
+ lines.push(`\n[${rel}m] 🛠 ${d.tool} ${d.state?.status === 'running' ? '(未完成)' : ''}\n 输入: ${clean(key, inp.command ? 2500 : 500)}`);
32
+ const out = clean(String(d.state?.output ?? ''), 700);
33
+ if (out.trim()) lines.push(` 输出: ${out.replace(/\n+/g, '\n ')}`);
34
+ }
35
+ }
36
+ writeFileSync(outFile, lines.join('\n'), 'utf8');
37
+ console.log(`wrote ${outFile}: ${lines.length} lines, ${(lines.join('\n').length / 1024).toFixed(0)}KB`);
38
+ db.close();
@@ -0,0 +1,43 @@
1
+ // 用法:node extract-timeline.mjs <session前缀> <输出文件>
2
+ // 从 zcode db.sqlite 提取会话时间线:文本节选 + 工具调用(名/输入头/输出头)
3
+ import { DatabaseSync } from 'node:sqlite';
4
+ import { writeFileSync } from 'node:fs';
5
+
6
+ const [prefix, outFile] = process.argv.slice(2);
7
+ const db = new DatabaseSync('C:/Users/qinyi/.zcode/cli/db/db.sqlite', { readOnly: true });
8
+
9
+ const sess = db.prepare('SELECT id FROM session WHERE id LIKE ?').get('sess_' + prefix + '%');
10
+ if (!sess) { console.error('session not found'); process.exit(1); }
11
+
12
+ const parts = db.prepare(
13
+ `SELECT p.data, m.time_created mt, JSON_EXTRACT(m.data,'$.role') role
14
+ FROM part p JOIN message m ON p.message_id = m.id
15
+ WHERE p.session_id = ? ORDER BY p.sequence`
16
+ ).all(sess.id);
17
+
18
+ const t0 = parts.length ? Number(JSON.parse(parts[0].data).time?.created ?? parts[0].mt) : 0;
19
+ const lines = [];
20
+ let lastTs = t0;
21
+ for (const row of parts) {
22
+ const d = JSON.parse(row.data);
23
+ const ts = Number(d.time?.created ?? row.mt);
24
+ if (ts > lastTs) lastTs = ts;
25
+ const rel = ((ts - t0) / 60000).toFixed(1);
26
+ const one = (s, n) => String(s ?? '').replace(/\s+/g, ' ').slice(0, n);
27
+ if (d.type === 'text') {
28
+ lines.push(`[${rel}m] ${row.role?.toUpperCase()} TEXT: ${one(d.text, 200)}`);
29
+ } else if (d.type === 'reasoning') {
30
+ // 只留极短摘要,标注存在性
31
+ lines.push(`[${rel}m] ${row.role?.toUpperCase()} think: ${one(d.text ?? d.summary, 90)}`);
32
+ } else if (d.type === 'tool') {
33
+ const inp = d.state?.input ?? {};
34
+ const key = inp.file_path ?? inp.command ?? inp.path ?? inp.pattern ?? JSON.stringify(inp);
35
+ const outHead = one(String(d.state?.output ?? ''), 140);
36
+ lines.push(`[${rel}m] TOOL ${d.tool}: ${one(key, 150)}`);
37
+ if (outHead) lines.push(` → ${outHead}`);
38
+ }
39
+ }
40
+ lines.push(`\n[last-part @ +${((lastTs - t0) / 60000).toFixed(1)}m]`);
41
+ writeFileSync(outFile, lines.join('\n'), 'utf8');
42
+ console.log(`wrote ${outFile}: ${lines.length} lines, span ${((lastTs - t0) / 60000).toFixed(1)}min`);
43
+ db.close();
@@ -0,0 +1,13 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ const db = new DatabaseSync('C:/Users/qinyi/.zcode/cli/db/db.sqlite', { readOnly: true });
3
+ const from = Date.parse('2026-09-20T15:00:00+08:00');
4
+ const to = Date.parse('2026-09-21T06:00:00+08:00');
5
+ console.log('window', from, '->', to, new Date(from).toISOString());
6
+ const rows = db.prepare(
7
+ 'SELECT id, title, time_created, parent_id FROM session WHERE time_created >= ? AND time_created <= ? ORDER BY time_created'
8
+ ).all(from, to);
9
+ for (const r of rows) {
10
+ const top = r.parent_id ? ' child-of-' + String(r.parent_id).slice(5, 13) : 'TOP ';
11
+ console.log(new Date(Number(r.time_created)).toISOString().slice(5, 16), top, r.id.slice(5, 13), (r.title || '').slice(0, 75));
12
+ }
13
+ db.close();