sillyspec 3.25.8 → 3.25.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 (71) hide show
  1. package/.claude/skills/sillyspec-auto/SKILL.md +8 -13
  2. package/.claude/skills/sillyspec-brainstorm/SKILL.md +8 -6
  3. package/.claude/skills/sillyspec-continue/SKILL.md +3 -3
  4. package/.claude/skills/sillyspec-doctor/SKILL.md +1 -1
  5. package/.claude/skills/sillyspec-execute/SKILL.md +10 -4
  6. package/.claude/skills/sillyspec-plan/SKILL.md +1 -1
  7. package/.claude/skills/sillyspec-verify/SKILL.md +1 -1
  8. package/README.md +9 -0
  9. package/package.json +2 -2
  10. package/src/change-list.js +11 -8
  11. package/src/change-risk-profile.js +22 -0
  12. package/src/db.js +160 -159
  13. package/src/dispatch/backends/local-agent.js +152 -0
  14. package/src/dispatch/backends/sillyhub-mcp.js +242 -0
  15. package/src/dispatch/probe.js +208 -0
  16. package/src/dispatch/strategy.js +121 -0
  17. package/src/doctor-diagnostics.js +19 -25
  18. package/src/fs-atomic.js +69 -69
  19. package/src/git-helper.js +66 -0
  20. package/src/hooks/worktree-guard.js +64 -55
  21. package/src/index.js +215 -34
  22. package/src/init.js +7 -14
  23. package/src/machine-interface.js +54 -19
  24. package/src/migrate.js +14 -4
  25. package/src/modules.js +16 -5
  26. package/src/progress/change-registry.js +72 -70
  27. package/src/progress/consistency-doctor.js +71 -14
  28. package/src/progress/shared.js +6 -3
  29. package/src/progress/stage-machine.js +61 -65
  30. package/src/progress/step-store.js +62 -67
  31. package/src/progress.js +155 -233
  32. package/src/quicklog.js +13 -5
  33. package/src/run/command.js +65 -37
  34. package/src/run/complete-handlers.js +1005 -889
  35. package/src/run/complete.js +115 -208
  36. package/src/run/concurrent-detect.js +132 -0
  37. package/src/run/gates.js +289 -17
  38. package/src/run/prompt.js +22 -68
  39. package/src/run/quick-audit.js +10 -9
  40. package/src/run/shared.js +648 -600
  41. package/src/run/stage.js +43 -24
  42. package/src/scan-postcheck.js +9 -31
  43. package/src/sillyhub-mcp/client.js +389 -0
  44. package/src/spec-dir-typo.js +71 -0
  45. package/src/stage-contract-engine.js +22 -1
  46. package/src/stage-contract-spec.js +11 -4
  47. package/src/stage-contract.js +26 -3
  48. package/src/stage-review.js +13 -9
  49. package/src/stages/archive.js +4 -6
  50. package/src/stages/brainstorm-auto.js +26 -20
  51. package/src/stages/brainstorm.js +29 -5
  52. package/src/stages/cmd-existence.js +145 -0
  53. package/src/stages/doctor.js +9 -6
  54. package/src/stages/execute.js +61 -20
  55. package/src/stages/plan-postcheck.js +152 -0
  56. package/src/stages/plan.js +16 -31
  57. package/src/stages/quick.js +1 -1
  58. package/src/stages/scan.js +9 -3
  59. package/src/stages/status.js +0 -1
  60. package/src/stages/verify.js +12 -7
  61. package/src/sync.js +4 -4
  62. package/src/task-review.js +30 -1
  63. package/src/verify-postcheck.js +933 -862
  64. package/src/version.js +19 -0
  65. package/src/worktree-apply.js +85 -68
  66. package/src/worktree-deps.js +89 -5
  67. package/src/worktree.js +213 -111
  68. package/templates/prompts/taskcard-rules.md +27 -0
  69. package/templates/prompts/verify-probes.md +3 -3
  70. package/.claude/skills/sillyspec-propose/SKILL.md +0 -87
  71. package/src/stages/propose.js +0 -181
@@ -0,0 +1,71 @@
1
+ /**
2
+ * SillySpec spec 目录拼写变体检测
3
+ *
4
+ * review.json missing 时调用:检测 SPEC_ROOT 父目录下是否存在 .sillyspec 的
5
+ * 近似拼写变体目录(如 .silyspec / .sillyspc),命中则提示「路径疑似拼错」。
6
+ *
7
+ * 触发场景:agent/用户手动创建 review.json 时把 .sillyspec 拼错(少 l / 多 k 等),
8
+ * 文件落到变体目录,CLI 按规范名找不到。给一条模糊匹配提示比静默报 missing 友好。
9
+ *
10
+ * 放独立模块(而非 stage-review.js / task-review.js 内联)的原因:两者已存在
11
+ * stage-review → task-review 的常量依赖,反向 import 会形成循环依赖。
12
+ */
13
+
14
+ import { dirname, basename, join } from 'path'
15
+ import { readdirSync } from 'fs'
16
+
17
+ /**
18
+ * 计算两字符串的 Levenshtein 编辑距离(大小写不敏感比较字符)。
19
+ * @param {string} a
20
+ * @param {string} b
21
+ * @returns {number}
22
+ */
23
+ function levenshtein(a, b) {
24
+ const m = a.length
25
+ const n = b.length
26
+ if (m === 0) return n
27
+ if (n === 0) return m
28
+ let prev = Array.from({ length: n + 1 }, (_, j) => j)
29
+ let curr = new Array(n + 1)
30
+ for (let i = 1; i <= m; i++) {
31
+ curr[0] = i
32
+ for (let j = 1; j <= n; j++) {
33
+ const cost = a[i - 1].toLowerCase() === b[j - 1].toLowerCase() ? 0 : 1
34
+ curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost)
35
+ }
36
+ ;[prev, curr] = [curr, prev]
37
+ }
38
+ return prev[n]
39
+ }
40
+
41
+ /**
42
+ * 检测 spec 目录拼写变体。
43
+ * @param {string} runtimeRoot — .runtime 目录绝对路径(= <SPEC_ROOT>/.runtime)
44
+ * @returns {{ typoDir: string, canonical: string } | null} 命中返回变体目录与规范名,否则 null
45
+ */
46
+ export function detectSpecDirTypo(runtimeRoot) {
47
+ if (!runtimeRoot) return null
48
+ // runtimeRoot = <SPEC_ROOT>/.runtime → SPEC_ROOT = dirname(runtimeRoot)
49
+ const specRoot = dirname(runtimeRoot)
50
+ const parent = dirname(specRoot)
51
+ const canonical = basename(specRoot) // 期望 '.sillyspec'
52
+ if (!canonical.startsWith('.')) return null // 非隐藏目录命名,跳过(避免误报)
53
+
54
+ let siblings = []
55
+ try {
56
+ siblings = readdirSync(parent, { withFileTypes: true })
57
+ .filter((d) => d.isDirectory() && d.name.startsWith('.') && !d.name.startsWith('..') && d.name !== canonical)
58
+ .map((d) => d.name)
59
+ } catch {
60
+ return null
61
+ }
62
+ if (siblings.length === 0) return null
63
+
64
+ // 编辑距离 ≤2 视为拼写变体(覆盖少 l / 多 k / 漏字母等常见笔误)
65
+ const typo = siblings.find((name) => {
66
+ const dist = levenshtein(name, canonical)
67
+ return dist > 0 && dist <= 2
68
+ })
69
+ if (!typo) return null
70
+ return { typoDir: join(parent, typo), canonical }
71
+ }
@@ -112,13 +112,33 @@ function formatFail(failMessage, absPath) {
112
112
  : failMessage
113
113
  }
114
114
 
115
+ /**
116
+ * 规则条件判定:condition 不满足时规则跳过(同 enabled=false)。
117
+ * 用于「某规则仅在特定上下文生效」——如 brainstorm 的 proposal/requirements/tasks 仅在 scale≠small 时必需
118
+ * (小变更 scale=small 只产 design.md,见 BRAINSTORM_RULES)。
119
+ *
120
+ * condition schema: { ctxField: '<ctx 键名>', ne|eq: <值> }
121
+ * - ne: ctx[ctxField] !== ne 时条件成立(规则适用);相等则跳过
122
+ * - eq: ctx[ctxField] === eq 时条件成立;否则跳过
123
+ * fail-safe:ctx 或 ctxField 缺失 → ne 判定为「undefined !== ne 值」成立(eq 不成立),
124
+ * 即 scale 读不到时四件套仍全要求(保守默认,走重流程),不会因 condition 误放开必填项。
125
+ */
126
+ function conditionHolds(condition, ctx) {
127
+ if (!condition) return true
128
+ const actual = ctx ? ctx[condition.ctxField] : undefined
129
+ if ('ne' in condition) return actual !== condition.ne
130
+ if ('eq' in condition) return actual === condition.eq
131
+ return true // 未知操作符,放过(不阻断),避免新操作符上线时误杀
132
+ }
133
+
115
134
  // ============ 主入口 ============
116
135
 
117
136
  /**
118
137
  * 对某 stage 的全部纯 kind 规则跑判定。custom kind / target 基缺失 / enabled=false 的规则计入 skipped。
119
138
  *
120
139
  * @param {string} stage
121
- * @param {{ changeDir?: string, docsRoot?: string, archiveDir?: string, specBase?: string }} ctx
140
+ * @param {{ changeDir?: string, docsRoot?: string, archiveDir?: string, specBase?: string, scale?: string }} ctx
141
+ * 规则 target.root 解析基目录;额外字段(如 scale)供 rule.condition 按 ctxField 判定(见 conditionHolds)
122
142
  * @param {{ readFile?: Function, readDir?: Function }} [io] 默认原生 fs;plan-postcheck 可注入 CRLF-normalizing reader
123
143
  * @returns {{ ok: boolean, errors: string[], warnings: string[], applied: string[], skipped: string[] }}
124
144
  */
@@ -132,6 +152,7 @@ export function evaluateRules(stage, ctx, io, opts = {}) {
132
152
 
133
153
  for (const rule of getRulesFor(stage, { source: opts.source })) {
134
154
  if (rule.enabled === false) { skipped.push(rule.id); continue }
155
+ if (rule.condition && !conditionHolds(rule.condition, ctx)) { skipped.push(rule.id); continue }
135
156
  if (isCustomKind(rule.kind)) { skipped.push(rule.id); continue }
136
157
  const absPath = resolveTargetPath(rule.target, ctx)
137
158
  if (absPath == null) { skipped.push(rule.id); continue }
@@ -20,6 +20,9 @@
20
20
  * failMessage 失败报错(逐字 == 现有报错文案;支持 ${path} 占位 → 引擎替换为解析后的绝对路径)
21
21
  * hint? 可选出路提示(renderStageContract 附在 spec 后)
22
22
  * enabled? 默认 true,置 false 灰度停用
23
+ * condition? 可选条件对象 { ctxField, ne|eq };不满足时规则跳过(同 enabled=false)。
24
+ * 供「仅在特定上下文生效」的规则,如 brainstorm proposal/requirements/tasks 仅 scale≠small 时必需
25
+ * (小变更只产 design.md)。判定见 stage-contract-engine.js conditionHolds(fail-safe:ctx 字段缺失时 ne 成立)。
23
26
  *
24
27
  * 纯 kind(stage-contract-engine.dispatchPure 直接判定):
25
28
  * file-exists / dir-exists / literal-any / literal-all / regex / contains-section /
@@ -67,7 +70,7 @@ const BRAINSTORM_RULES = [
67
70
  stage: 'brainstorm', source: 'validateBrainstormOutputs', severity: 'error', kind: 'file-exists',
68
71
  target: { root: 'change', path: 'design.md' },
69
72
  data: {},
70
- spec: '产出 `design.md`(四件套之一;brainstorm 四件套齐全才能完成)',
73
+ spec: '产出 `design.md`(brainstorm 必产;scale=small 时这是唯一必需产物,proposal/requirements/tasks 豁免)',
71
74
  failMessage: 'brainstorm 产物缺失: ${path}',
72
75
  },
73
76
  {
@@ -75,7 +78,9 @@ const BRAINSTORM_RULES = [
75
78
  stage: 'brainstorm', source: 'validateBrainstormOutputs', severity: 'error', kind: 'file-exists',
76
79
  target: { root: 'change', path: 'proposal.md' },
77
80
  data: {},
78
- spec: '产出 `proposal.md`(四件套之一)',
81
+ // scale=small(小变更)豁免:brainstorm 末步指引 small 只产 design.md 后转 quick,四件套不全不应阻断
82
+ condition: { ctxField: 'scale', ne: 'small' },
83
+ spec: '产出 `proposal.md`(四件套之一;scale=small 时豁免——小变更只需 design.md)',
79
84
  failMessage: 'brainstorm 产物缺失: ${path}',
80
85
  },
81
86
  {
@@ -83,7 +88,8 @@ const BRAINSTORM_RULES = [
83
88
  stage: 'brainstorm', source: 'validateBrainstormOutputs', severity: 'error', kind: 'file-exists',
84
89
  target: { root: 'change', path: 'requirements.md' },
85
90
  data: {},
86
- spec: '产出 `requirements.md`(四件套之一)',
91
+ condition: { ctxField: 'scale', ne: 'small' },
92
+ spec: '产出 `requirements.md`(四件套之一;scale=small 时豁免——小变更只需 design.md)',
87
93
  failMessage: 'brainstorm 产物缺失: ${path}',
88
94
  },
89
95
  {
@@ -91,7 +97,8 @@ const BRAINSTORM_RULES = [
91
97
  stage: 'brainstorm', source: 'validateBrainstormOutputs', severity: 'error', kind: 'file-exists',
92
98
  target: { root: 'change', path: 'tasks.md' },
93
99
  data: {},
94
- spec: '产出 `tasks.md`(四件套之一)',
100
+ condition: { ctxField: 'scale', ne: 'small' },
101
+ spec: '产出 `tasks.md`(四件套之一;scale=small 时豁免——小变更只需 design.md)',
95
102
  failMessage: 'brainstorm 产物缺失: ${path}',
96
103
  },
97
104
  // 内容章节(warning)—— 逐字对齐 validateBrainstormOutputs 内容校验段
@@ -257,15 +257,28 @@ function validateBrainstormOutputs(cwd, changeName, context = {}) {
257
257
  return { ok: false, errors: [`平台模式 specRoot 缺少 changes 目录: ${changesRoot}`], warnings: [] }
258
258
  }
259
259
  const changeDir = resolveChangeDir(cwd, changeName, specRoot)
260
+ // 读 design.md frontmatter scale(若有)传入引擎 ctx,供 BRAINSTORM_RULES 的 condition 判定:
261
+ // scale=small → proposal/requirements/tasks 三规则跳过(小变更只产 design.md,与末步 prompt 一致,
262
+ // 避免"照 Step8 small 指引只写 design.md 后 --done 必撞四件套 error墙")。
263
+ // fail-safe:design.md 不存在或无 scale → null → condition(ne 'small')成立 → 四件套全要求(保守走重流程)。
264
+ const designPath = join(changeDir, 'design.md')
265
+ let scale = null
266
+ let designContent = null
267
+ if (existsSync(designPath)) {
268
+ designContent = readFileSync(designPath, 'utf8')
269
+ const fm = designContent.match(/^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n/)
270
+ const sm = fm && fm[1].match(/^scale:[ \t]*['"]?(\w+)/m)
271
+ if (sm) scale = sm[1]
272
+ }
260
273
  // 纯 kind 规则(四件套存在性 + proposal/requirements/design 章节 + tasks 列表)由引擎消费
261
274
  // stage-contract-spec.js manifest,与报错文案 + prompt 事前契约严格同源(事前给的 == 事后查的)。
262
275
  // custom kind(lifecycle / decisions)保留下方旧逻辑,数据/文案在 Batch 3 迁入 manifest。
263
- const engineResult = evaluateRules('brainstorm', { changeDir })
276
+ const engineResult = evaluateRules('brainstorm', { changeDir, scale })
264
277
  const errors = [...engineResult.errors]
265
278
  const warnings = [...engineResult.warnings]
266
279
 
267
- if (existsSync(join(changeDir, 'design.md'))) {
268
- const content = readFileSync(join(changeDir, 'design.md'), 'utf8')
280
+ if (designContent) {
281
+ const content = designContent
269
282
 
270
283
  // lifecycle-exemption(custom kind):判定算法保留(trigger/exemption/table 三段短路),
271
284
  // data + failMessage/exemptionPassedMessage 从 stage-contract-spec.js manifest 同源。
@@ -446,6 +459,16 @@ function validateVerifyOutputs(cwd, changeName, context = {}) {
446
459
  })
447
460
  const conclusion = extractVerifyConclusion(verify)
448
461
  if (['integration-critical', 'deployment-critical'].includes(changeRiskProfile.level)) {
462
+ // 关键词误伤早期引导(坑2,FR-02):命中高危关键词且无 frontmatter risk_level 时,
463
+ // 无条件透出 frontmatter 覆盖指引(不依赖 conclusion / evidence),让 agent 早期即可
464
+ // 判断是否属误判并显式覆盖,而非撞到 evidence gate 末尾才发现出路③。遵 6417a27:不做
465
+ // body 否定语境扫描,只走 frontmatter 显式覆盖通道。
466
+ if (!changeRiskProfile.explicit) {
467
+ warnings.push(
468
+ `[${changeRiskProfile.level}] 本次变更被关键词判级(命中:${changeRiskProfile.triggers.join(', ')})。` +
469
+ `若属关键词误伤(实际未触碰 daemon/session/启动入口/跨进程),可在 design.md frontmatter 加 risk_level: <真实等级>(如 unit-sufficient)显式覆盖后重跑。`
470
+ )
471
+ }
449
472
  // 显式 risk_level 声明(design frontmatter,非关键词误判)下,PASS WITH NOTES 视为对残留项的
450
473
  // 诚实声明,不强求全量集成证据——豁免本就来自 design 的明确判断。自动关键词判级维持严格:
451
474
  // PASS WITH NOTES 仍要求证据齐全,防 agent 用 PASS WITH NOTES 绕证据门控。
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 与 task-review.js 的关系:
5
5
  * task-review 校验 execute 每个 task 的 review.json(git 代码 diff 证据:base/head)。
6
- * stage-review 校验 brainstorm/plan/propose/execute-acceptance 的阶段级 review.json
6
+ * stage-review 校验 brainstorm/plan/execute-acceptance 的阶段级 review.json
7
7
  * (文档证据:reviewedFiles + docHash)。两者 verdict 语义一致(pass/fail/cannot_verify
8
8
  * 三态 + cannot_verify 必须带 requiredEvidence 的反逃逸规则),复用 task-review 导出的
9
9
  * VALID_VERDICTS / REVIEW_SCHEMA_VERSION 常量;task-review.js 本身不改、execute 路径零风险。
@@ -17,6 +17,7 @@ import { existsSync, readFileSync, mkdirSync, readdirSync } from 'fs'
17
17
  import { join } from 'path'
18
18
  import { createHash } from 'crypto'
19
19
  import { VALID_VERDICTS, REVIEW_SCHEMA_VERSION } from './task-review.js'
20
+ import { detectSpecDirTypo } from './spec-dir-typo.js'
20
21
 
21
22
  // 文档型 stage review 的合法 reviewType
22
23
  export const STAGE_REVIEW_TYPES = ['design', 'plan', 'proposal', 'code', 'acceptance']
@@ -24,10 +25,10 @@ export const STAGE_REVIEW_TYPES = ['design', 'plan', 'proposal', 'code', 'accept
24
25
  // checklist 每项 result 的合法值
25
26
  const CHECKLIST_RESULTS = ['pass', 'gap', 'fail']
26
27
 
27
- // stage → 主审查文档(brainstorm/execute 审 design.md;plan 审 plan.md;propose 审 proposal.md)
28
- const STAGE_MAIN_DOC = { brainstorm: 'design.md', plan: 'plan.md', propose: 'proposal.md', execute: 'design.md' }
28
+ // stage → 主审查文档(brainstorm/execute 审 design.md;plan 审 plan.md)
29
+ const STAGE_MAIN_DOC = { brainstorm: 'design.md', plan: 'plan.md', execute: 'design.md' }
29
30
  // stage → reviewType(对齐 gates.js Stage Review Gate 的映射)
30
- const STAGE_REVIEW_TYPE = { brainstorm: 'design', plan: 'plan', propose: 'proposal', execute: 'acceptance' }
31
+ const STAGE_REVIEW_TYPE = { brainstorm: 'design', plan: 'plan', execute: 'acceptance' }
31
32
 
32
33
  /**
33
34
  * 渲染 review.json 产物契约(markdown)给 review 子代理事前看 —— schema 表 + 完整 JSON 示例 +
@@ -242,7 +243,7 @@ export function generateStageReviewRunId() {
242
243
  * (同一 stage 不同变更的 review 各自独立 marker);changeName 缺失时退化为按 stage 分。
243
244
  *
244
245
  * @param {string} runtimeRoot - .runtime 绝对路径
245
- * @param {string} stage - brainstorm|plan|propose|execute
246
+ * @param {string} stage - brainstorm|plan|execute
246
247
  * @param {string} [changeName] - 变更名(防多 change 串台)
247
248
  * @returns {string} marker 文件绝对路径
248
249
  */
@@ -260,7 +261,7 @@ export function stageReviewMarkerPath(runtimeRoot, stage, changeName) {
260
261
  * stage-reviews/<stage>-review-* 目录取字典序最新(向后兼容无 marker 旧数据)。
261
262
  *
262
263
  * @param {string} runtimeRoot - .runtime 绝对路径
263
- * @param {string} stage - brainstorm|plan|propose|execute
264
+ * @param {string} stage - brainstorm|plan|execute
264
265
  * @param {string} [changeName] - 变更名(读对应 marker,防多 change 串台)
265
266
  * @returns {string|null} run id(如 'review-2026-07-16-143000'),无则 null
266
267
  */
@@ -313,7 +314,7 @@ export function getLatestStageReviewRunId(runtimeRoot, stage, changeName) {
313
314
  }
314
315
 
315
316
  /**
316
- * stage-level review 总校验(brainstorm/plan/propose/execute-acceptance 的 done gate)
317
+ * stage-level review 总校验(brainstorm/plan/execute-acceptance 的 done gate)
317
318
  *
318
319
  * 规则(与 task-review.validateTaskReviews 对称):
319
320
  * - review.json 缺失 → error 阻断
@@ -328,7 +329,7 @@ export function getLatestStageReviewRunId(runtimeRoot, stage, changeName) {
328
329
  * tier=independent 才调用。
329
330
  *
330
331
  * @param {object} opts
331
- * @param {string} opts.stage - brainstorm|plan|propose|execute
332
+ * @param {string} opts.stage - brainstorm|plan|execute
332
333
  * @param {string} opts.reviewType - design|plan|proposal|code|acceptance
333
334
  * @param {string} opts.runtimeRoot - .runtime 绝对路径(平台模式为 runtimeRoot)
334
335
  * @param {string} opts.reviewRunId - stage review run id(review-<ts>)
@@ -345,9 +346,12 @@ export function validateStageReview(opts) {
345
346
  const reviewPath = join(reviewDir, 'review.json')
346
347
 
347
348
  if (!reviewRunId || !existsSync(reviewPath)) {
349
+ const errs = [`缺少 ${stage} 阶段的 stage review.json — tier=independent 要求独立审查子代理产出(期望路径:${reviewPath})`]
350
+ const typo = detectSpecDirTypo(runtimeRoot)
351
+ if (typo) errs.push(`💡 路径疑似拼错:发现 ${typo.typoDir} 目录(应为 ${typo.canonical}),review.json 可能误存于此——检查是否把 .sillyspec 拼成了变体`)
348
352
  return {
349
353
  ok: false,
350
- errors: [`缺少 ${stage} 阶段的 stage review.json — tier=independent 要求独立审查子代理产出(期望路径:${reviewPath})`],
354
+ errors: errs,
351
355
  warnings,
352
356
  review: null,
353
357
  }
@@ -51,9 +51,7 @@ module-impact.md 第一行标题必须用中文:# 模块影响分析(Module
51
51
  needs_review:如果影响无法完全确定,标记为 true
52
52
 
53
53
  8. 未匹配到任何模块的文件归入"未匹配文件"表格
54
- 9. 生成 \`.sillyspec/changes/<change-name>/module-impact.md\`
55
- 10. 完成后运行 workflow 检查:
56
- \`node -e "import('./src/workflow.js').then(w => { /* 用 loadWorkflow 加载 archive-impact,用 runPostCheck 检查 */ })\``,
54
+ 9. 生成 \`.sillyspec/changes/<change-name>/module-impact.md\``,
57
55
  outputHint: 'module-impact.md 路径 + 影响摘要',
58
56
  optional: false
59
57
  },
@@ -128,7 +126,7 @@ module_id: <module-id>
128
126
  6. 用户拒绝时,不写入,但提示"module-impact.md 已保留,可稍后手动同步"
129
127
  7. 回填 module-impact.md 的"更新结果"表格,区分目标:
130
128
  - 目标列写 "\`_module-map.yaml: <module-id>\`" 或 "\`modules/<module-id>.md\`"
131
- 8. **同步完成后**,运行 \`sillyspec modules rebuild\` 刷新索引(如果需要),或手动更新 dependencies.md
129
+ 8. **同步完成后**,如需刷新 \`_module-map.yaml\` 索引:\`sillyspec modules rebuild --force\`(默认 dry-run 只预览不写;\`--force\` 才覆盖,但会清空 tags/entrypoints/main_symbols/depends_on/used_by 等手动维护字段——与 archive-impact 的「人工备注保护」约束冲突,仅当手动字段已并入骨架或可接受覆盖时用;优先手动更新 dependencies.md / 模块卡片)
132
130
 
133
131
  ### 输出
134
132
  已更新的文件路径列表 + 用户确认状态`,
@@ -157,8 +155,8 @@ module_id: <module-id>
157
155
 
158
156
  ### 操作
159
157
  1. 如果 \`.sillyspec/ROADMAP.md\` 存在,标记对应 Phase 为已完成
160
- 2. \`git add .sillyspec/changes/\` — 暂存归档结果(不要 commit,由用户通过统一提交工具处理)
161
- 3. \`git add .sillyspec/docs/\` — 暂存模块文档更新(如有)
158
+ 2. \`git add .sillyspec/changes/archive/\` — 暂存归档结果(archive/ 下仅本次归档新增,不会裹挟 changes/ 下其他活跃变更;不要 commit,由用户通过统一提交工具处理)
159
+ 3. \`git add .sillyspec/docs/<project>/modules/\` — 暂存模块文档更新(如有;精确到本次同步的模块文档,勿 add 整个 .sillyspec/docs/)
162
160
  4. 确认 sillyspec.db 中该变更已不再 active(确认归档步骤由 CLI 调用 unregisterChange)
163
161
 
164
162
  ### 输出
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 与 brainstorm.js 的区别:
5
5
  * 1. artifact-first:直接写文件,对话只输出摘要
6
- * 2. 自动决策:基于 AC-001~AC-010 checklist,不频繁请示
6
+ * 2. 自动决策:基于 AC-001~AC-011 checklist,不频繁请示
7
7
  * 3. next-action.json:结构化产物,驱动下游推进
8
8
  * 4. 只有 blocking questions 才 wait 用户
9
9
  * 5. 步骤从 ~13 步精简为 4 步
@@ -56,7 +56,12 @@ export const definition = {
56
56
  },
57
57
  {
58
58
  name: '需求分析与方案设计',
59
- requiresWait: true,
59
+ // conditionalWait(非 requiresWait):与主模式 Step3 对齐。prompt 明确「目标明确→跳过追问,
60
+ // 直接进入方案设计」「checklist 全 ✅ → AUTO_DECIDED」——这两种场景 AI 无需追问,必须能直接 --done。
61
+ // 若用 requiresWait,CLI 硬门强制要 --answer(complete.js:223),AI 只能伪造 --answer「无需追问」
62
+ // (正是 Grill 第三病)或 --wait 一个不存在的问题。conditionalWait 让「无需追问」路径直接 --done,
63
+ // 只在真正需要用户决策(方案选择/拆分确认)时才走 --wait(multi-agent-review B1)。
64
+ conditionalWait: true,
60
65
  repeatableWait: true,
61
66
  maxWaitRounds: 5,
62
67
  waitReason: '等待用户回答需求问题',
@@ -92,7 +97,7 @@ export const definition = {
92
97
 
93
98
  #### D. 方案设计(自动决策优先)
94
99
  1. 基于需求理解和上下文,提出 1-3 种实现方案
95
- 2. **使用自动决策 checklist(AC-001~AC-010)判断是否需要用户选择:**
100
+ 2. **使用自动决策 checklist(AC-001~AC-011)判断是否需要用户选择:**
96
101
 
97
102
  \`\`\`
98
103
  AC-001: 未修改公共 API
@@ -105,12 +110,13 @@ AC-007: 已有项目约定可复用
105
110
  AC-008: 不影响向后兼容
106
111
  AC-009: 不涉及数据迁移
107
112
  AC-010: 单模块范围内可完成
113
+ AC-011: 不涉及业务规则/产品范围/默认行为/用户可见行为变更
108
114
  \`\`\`
109
115
 
110
116
  3. **自动决策判定:**
111
117
  - 只有一个明显合理方案 + checklist 全部 ✅ → **自动选择,标记 AUTO_DECIDED**
112
118
  - 有多个方案但影响不大(checklist 全部 ✅)→ **自动选推荐方案,标记 AUTO_DECIDED**
113
- - 有多个方案且 checklist 有 ❌(影响架构/数据/接口/权限/兼容性)→ **只有这一种情况才暂停等用户选择**
119
+ - 有多个方案且 checklist 有 ❌(影响架构/数据/接口/权限/兼容性/业务)→ **只有这一种情况才暂停等用户选择**
114
120
 
115
121
  4. 如果需要用户选择方案:
116
122
  - 调用:\`sillyspec run brainstorm --wait --reason "等待用户选择方案" --options "方案A,方案B,方案C" --output "方案对比摘要"\`
@@ -136,15 +142,16 @@ AC-010: 单模块范围内可完成
136
142
  prompt: `将设计方案写入文件(artifact-first),不回显正文。
137
143
 
138
144
  ### 操作
139
- 1. 确保变更目录存在:\`mkdir -p .sillyspec/changes/<change-name>/brainstorm\`
145
+ 1. 确保变更目录存在:\`mkdir -p .sillyspec/changes/<change-name>\`
140
146
  2. **直接将设计方案写入文件,不要先在对话中输出完整内容再写文件。**
147
+ 3. 产物写入**变更根目录**(\`.sillyspec/changes/<change-name>/\`),不要建 \`brainstorm/\` 子目录——stage validator 只认根目录,写进子目录会在 \`--done\` 校验时被判产物缺失。
141
148
 
142
149
  ### 产物文件
143
150
 
144
- #### design.md(必填,写入 \`brainstorm/design.md\`)
151
+ #### design.md(必填,写入变更根目录 \`design.md\`)
145
152
  包含:背景、设计目标、非目标、总体方案、文件变更清单、接口定义、数据模型(如涉及)、兼容策略(brownfield)、风险登记、决策追踪。
146
153
 
147
- #### decisions.md(必填,写入 \`brainstorm/decisions.md\`)
154
+ #### decisions.md(必填,写入变更根目录 \`decisions.md\`)
148
155
  记录所有决策:
149
156
  \`\`\`markdown
150
157
  ## D-001@v1: 决策短标题
@@ -160,13 +167,13 @@ AC-010: 单模块范围内可完成
160
167
  - evidence: 代码/文档路径或用户回答轮次
161
168
  \`\`\`
162
169
 
163
- #### gaps.md(必填,写入 \`brainstorm/gaps.md\`)
170
+ #### gaps.md(必填,写入变更根目录 \`gaps.md\`)
164
171
  记录已识别的缺口。status=BLOCKER 的缺口会在 plan-postcheck 中触发回退。
165
172
 
166
- #### assumptions.md(必填,写入 \`brainstorm/assumptions.md\`)
173
+ #### assumptions.md(必填,写入变更根目录 \`assumptions.md\`)
167
174
  记录隐含假设和验证方法。
168
175
 
169
- #### next-action.json(必填,写入 \`brainstorm/next-action.json\`)
176
+ #### next-action.json(必填,写入变更根目录 \`next-action.json\`)
170
177
  \`\`\`json
171
178
  {
172
179
  "status": "ready_for_plan" | "waiting_for_user",
@@ -189,12 +196,12 @@ AC-010: 单模块范围内可完成
189
196
  ### 输出(artifact-first)
190
197
  只输出摘要:
191
198
  \`\`\`
192
- 已生成设计产物(5 个文件):
193
- - brainstorm/design.md — 采用 xxx 方案,涉及 N 个文件变更
194
- - brainstorm/decisions.md — M 个决策,K 个 AUTO_DECIDED
195
- - brainstorm/gaps.md — J 个缺口,0 个 BLOCKER
196
- - brainstorm/assumptions.md — L 个假设
197
- - brainstorm/next-action.json — ready_for_plan / waiting_for_user
199
+ 已生成设计产物(5 个文件,均在变更根目录):
200
+ - design.md — 采用 xxx 方案,涉及 N 个文件变更
201
+ - decisions.md — M 个决策,K 个 AUTO_DECIDED
202
+ - gaps.md — J 个缺口,0 个 BLOCKER
203
+ - assumptions.md — L 个假设
204
+ - next-action.json — ready_for_plan / waiting_for_user
198
205
  \`\`\``,
199
206
  outputHint: '产物摘要',
200
207
  optional: false
@@ -207,13 +214,12 @@ AC-010: 单模块范围内可完成
207
214
  prompt: `生成 proposal.md / requirements.md / tasks.md,让用户确认。
208
215
 
209
216
  ### 操作
210
- 1. 基于 brainstorm/ 下的设计产物,生成规范文件(直接写文件):
217
+ 1. 基于变更根目录下的设计产物(design.md / decisions.md / gaps.md / assumptions.md / next-action.json),生成规范文件(直接写入变更根目录):
211
218
  - **proposal.md**:动机、关键问题、变更范围、不在范围内、成功标准
212
219
  - **requirements.md**:角色表 + FR 编号需求 + Given/When/Then + 非功能需求
213
220
  - **tasks.md**:任务列表(只列名称,细节在 plan 阶段展开)
214
- 2. 如果 brainstorm/decisions.md 有 AUTO_DECIDED 决策,在变更根目录也写一份 decisions.md
215
- 3. 所有规范文件头部包含 YAML frontmatter
216
- 4. \`git add .sillyspec/\` — 暂存规范文件(不要 commit)
221
+ 2. 所有规范文件头部包含 YAML frontmatter
222
+ 3. \`git add {SPEC_ROOT}/changes/<change-name>/\` — 暂存本变更的规范文件(勿用 .sillyspec/ 整目录——会裹挟其他活跃变更;不要 commit)
217
223
 
218
224
  ### 输出(摘要)
219
225
  规范文件路径列表(各一句话说明)
@@ -79,7 +79,7 @@ export const definition = {
79
79
  maxWaitRounds: 8,
80
80
  waitReason: '等待用户回答需求问题或澄清',
81
81
  waitOptions: ['回答见--answer', '信息够了,进入方案讨论'],
82
- prompt: `通过对话探索需求细节、分析原型(如有)、判断拆分/批量、并对歧义点做需求澄清 Grill。本步骤合并了原有的对话探索、原型分析、需求范围评估、需求澄清 Grill 四个环节——按下方 A→D 顺序按需执行,能一次问清的不要拆成多轮。
82
+ prompt: `通过对话探索需求细节、分析原型(如有)、判断拆分/批量、并对歧义点做需求澄清 Grill。本步骤合并了原有的对话探索、原型分析、需求范围评估、需求澄清 Grill 四个环节——按下方 A→D 顺序按需执行。提问遵守「一次只问一个」(见 D 节追问策略):必须问的问题逐个问,能查代码/文档解决的不问;不要把多个歧义打包成一屏清单一次抛给用户(用户记不住、答不全,反而漏掉关键约束);也不要为凑数把单个问题拖成多轮。
83
83
 
84
84
  ### A. 原型/设计图分析(如用户提供)
85
85
  如果用户提供了截图、图片或 HTML 原型,先分析提取:
@@ -176,7 +176,7 @@ export const definition = {
176
176
  ### 铁律 — 不要自问自答 / 等待用户
177
177
  - 这是人机协作步骤:提出问题后必须暂停等用户回答,**不要在输出里模拟用户回答然后说"需求已明确"**(命令由 CLI 在下方注入)
178
178
  - 需要拆分或批量模式时:列出方案并暂停等用户确认
179
- - 不需要追问、无歧义、需求已清晰时:可正常完成本步骤(不必强行 --wait)
179
+ - 不需要追问、无歧义、需求已清晰时:可正常完成本步骤(不必强行 --wait)。但「正常完成」的前提是**输出一份可否决的确认清单**——在输出里明确列出「已确认需求点清单」(逐条),让用户能一眼看到并喊停;未与用户确认过任何需求点、纯凭自身判断的场景,不得仅凭「我觉得清晰」直接放行(这是把默认共识伪装成共识的逃逸口)
180
180
  - 2-3 轮问答即进入方案讨论;达到 maxWaitRounds=8 后必须总结已确认内容与剩余风险,不要无限追问
181
181
 
182
182
  ### 输出
@@ -272,6 +272,10 @@ design.md 第一行标题必须用中文:# 设计文档(Design)— <变更
272
272
  | 修改 | src/xxx/ExistingFile.java | 新增 xx 方法 |
273
273
  | 删除 | src/xxx/OldFile.java | 已被 xx 替代 |
274
274
 
275
+ **字段数据流标注**(避免新增字段到 execute 才发现没透传):当清单含「新增/修改对外字段、接口、DTO、响应体、事件 payload、配置键」时,对应行「说明」列必须交代 producer→consumer 数据流——产出方(producer)→ 每跳流转/归一化点(序列化/反序列化/字段映射/默认值兜底)→ 消费方(consumer)。漏标 = 字段在某跳 dormant(声明了却没透传),到 execute/verify 才暴露,属设计层缺口。
276
+ - 示例:「修改 daemon.ts:新增 budget_tokens,producer=backend/api-types.ts → ws_hub envelope 透传 → daemon.ts normalizeUsage 归一化(snake→camel)→ consumer=runtime/lease.ts 写 lease」
277
+ - 仅改内部实现、无对外字段变动时,说明列照常写「新增 xx 方法」即可,无需数据流。
278
+
275
279
  7. **接口定义**:方法签名、数据结构(代码类任务必填)
276
280
  7.5. **生命周期契约表**(涉及以下关键词时必填,否则可省略):
277
281
 
@@ -458,7 +462,7 @@ status: passed | needs-user-input | blocked | skipped
458
462
  1. **按规模生成规范文件**:
459
463
  - **scale=large**:在 \`{SPEC_ROOT}/changes/<change-name>/\` 下生成完整四件套(design.md / decisions.md 可选 / proposal.md / requirements.md / tasks.md),实现路径 → \`sillyspec run plan --change <变更名>\`
460
464
  - **scale=small**:只生成/补全 design.md(proposal/requirements/tasks 对 quick 无用,不生成),实现路径 → \`sillyspec run quick --linked-changes <变更名>\`
461
- - 两种规模都执行 \`git add .sillyspec/\` — 暂存规范文件(不要 commit,由用户通过统一提交工具处理)。**平台模式跳过 git add**(specRoot 不在 sourceRoot 的 git repo 内)
465
+ - 两种规模都执行 \`git add {SPEC_ROOT}/changes/<change-name>/\` — 暂存本变更的规范文件(精确到变更目录,勿用 .sillyspec/ 整目录——会裹挟其他活跃变更;不要 commit,由用户通过统一提交工具处理)。**平台模式跳过 git add**(specRoot 不在 sourceRoot 的 git repo 内)
462
466
 
463
467
  2. 生成完成后展示 design.md 摘要 + **规模评估结果(small/large + 一句依据)** + 实现路径建议,告知用户“如有异议直接说,可修改文件、改 scale 或 --reopen 回退”(不暂停,展示完直接 --done)
464
468
 
@@ -472,6 +476,10 @@ created_at: <now-datetime>
472
476
 
473
477
  ### proposal.md 格式要求
474
478
  \`\`\`markdown
479
+ ---
480
+ author: <git-user>
481
+ created_at: <now-datetime>
482
+ ---
475
483
  # 提案书(Proposal)
476
484
 
477
485
  ## 动机
@@ -495,6 +503,10 @@ created_at: <now-datetime>
495
503
 
496
504
  ### requirements.md 格式要求
497
505
  \`\`\`markdown
506
+ ---
507
+ author: <git-user>
508
+ created_at: <now-datetime>
509
+ ---
498
510
  # 需求规格(Requirements)
499
511
 
500
512
  ## 角色
@@ -523,6 +535,18 @@ Then 期望结果
523
535
  | D-001@v1 | FR-01 | ... |
524
536
  \`\`\`
525
537
 
538
+ ### tasks.md 格式要求(scale=large 时生成骨架,plan 阶段展开)
539
+ \`\`\`markdown
540
+ ---
541
+ author: <git-user>
542
+ created_at: <now-datetime>
543
+ ---
544
+ # 任务清单(Tasks)
545
+
546
+ - [ ] task-01: <任务名>
547
+ - [ ] task-02: <任务名>
548
+ \`\`\`
549
+
526
550
  ### decisions.md 格式要求(仅在有 Grill/重大决策时生成)
527
551
  \`\`\`markdown
528
552
  # 决策记录(Decisions)
@@ -549,7 +573,7 @@ Then 期望结果
549
573
  - \`design.md\`:从 MASTER.md 中提取该包的职责描述(标记为「待设计 - 本包 design 在该包进入 brainstorm 时完善」)
550
574
  - \`requirements.md\`:从 MASTER.md 中提取该包的需求范围(标记为「待完善」)
551
575
  - \`tasks.md\`:创建空任务列表,标记为「待 plan 阶段展开」
552
- 4. \`git add .sillyspec/\` — 暂存所有新增文件(不要 commit,由用户通过统一提交工具处理)。**平台模式跳过 git add**(specRoot 不在 sourceRoot 的 git repo 内)
576
+ 4. \`git add {SPEC_ROOT}/changes/<本步骤创建的各变更包名>/\` — 逐个暂存本步骤新建的变更包(勿用 .sillyspec/ 整目录——会裹挟其他活跃变更;不要 commit,由用户通过统一提交工具处理)。**平台模式跳过 git add**(specRoot 不在 sourceRoot 的 git repo 内)
553
577
  5. 后续变更包的骨架文件同样必须包含 \`author: <git-user>\` 和 \`created_at: <now-datetime>\`
554
578
 
555
579
  ### 输出
@@ -563,7 +587,7 @@ Then 期望结果
563
587
  - 如果存在 decisions.md,requirements.md 必须引用全部当前版本 D-xxx@vN;没有覆盖的 D-xxx@vN 必须标注为剩余风险
564
588
  - 如果 Design Grill 产生 P0/P1 unresolved blocker,必须回到 design 修正,不能进入 plan/quick
565
589
  - tasks.md 只列任务名,细节在 plan 阶段展开
566
- - **规范 md 文件第一行标题用中文**(sillyhub 平台解析识别用):tasks.md 第一行用 # 任务清单(Tasks)(proposal/design/requirements/decisions 见各自模板,均已含中文标题)`,
590
+ - **规范 md 文件标题用中文**(sillyhub 平台解析识别用,frontmatter 在标题之前):tasks.md 标题用 # 任务清单(Tasks)(proposal/requirements/tasks/decisions 见各自模板 + design 见「写设计文档并自审」步;均含 frontmatter + 中文标题)`,
567
591
 
568
592
  }
569
593
  ]