sillyspec 3.32.8 → 3.32.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,12 +25,10 @@ export const definition = {
25
25
  6. 讨论 UI/交互方案且用户明确要求原型时,按下方「HTML 原型生成」执行
26
26
 
27
27
  ### HTML 原型生成(仅用户明确要求时)
28
- 探索保持只读姿态:不主动写原型文件;UI 讨论成形时可以提议("要不要出个原型看效果"),用户同意后再生成。生成要求与 brainstorm 阶段的原型对齐:
29
- - 单文件 HTML(内联 CSS + JS),浏览器直接打开
30
- - 高保真呈现讨论中的布局/组件/交互流程——用户看到的是"这个方案做出来长什么样",不是示意图
31
- - 与项目现有界面风格一致:先读 scan 文档与现有前端代码,复用现有组件库/设计 token 的观感,不另起风格
28
+ 探索保持只读姿态:不主动写原型文件;UI 讨论成形时可以提议("要不要出个原型看效果"),用户同意后再生成。探索期原型是**粗选对比件**(求快不求真,手绘单文件含内联 CSS+JS 在探索期合法,浏览器直接打开):
29
+ - 呈现讨论中的布局/组件/交互流程——用户看到的是"这个方案做出来长什么样",不是示意图;能用项目真实组件库/设计 token 观感的尽量用(先读 scan 文档与现有前端代码),降低与真实效果的偏差
32
30
  - 保存到 \`{SPEC_ROOT}/explore/prototype-<名称>.html\`(目录不存在则先创建;用户指定了其他位置则从其指定)
33
- - 原型是探索讨论工具、不是交付物,也不是实现代码——不要顺手把原型接进项目源码或构建;话题后续进入 brainstorm 时,把原型文件复制到 \`{SPEC_ROOT}/changes/<变更名>/\` 可直接复用为该变更的原型
31
+ - 原型是探索讨论工具、不是交付物,也不是实现代码——不要顺手把原型接进项目源码或构建;话题后续进入 brainstorm 时分两路:已是可复跑真码的原型复制到 \`{SPEC_ROOT}/changes/<变更名>/\`(**保留 \`prototype-<名称>.html\` 命名**——execute 按此命名注入,改名=不注入)走登记复用;手绘粗选件只是方向输入,brainstorm 定稿须按其「生成要求」升级为真码(同方言/可复跑),不得把手绘件直接当定稿登记
34
32
 
35
33
  ### 输出
36
34
  探索结论、选项对比、风险清单或 ASCII 图;如生成了原型,附原型文件路径
package/src/sync.js CHANGED
@@ -18,6 +18,7 @@ import { safeGit } from './git-helper.js';
18
18
  import { openDatabase } from './db-engine.js';
19
19
  import { PLATFORM_MANAGED_FILENAME, QUICK_SID_RE } from './run/shared.js';
20
20
  import { syncSpecTree } from './spec-sync.js';
21
+ import { lookupDeletedLedger } from './deleted-ledger.js';
21
22
  import { bindSyncNoiseFromCwd, syncConnectionWarn, isConnectionClassStatus, noteSyncSuccess, syncChangeDeletedLog, syncChangeDeletedWarn, noteChangeDeletedResolved, syncSelfHealWarn } from './sync-noise.js';
22
23
 
23
24
  // sync 是 best-effort(网络失败只 warn):平台指针失效时不抛,跳过平台、回退本地。
@@ -747,6 +748,18 @@ export class SyncManager {
747
748
  return { synced: 0, errors: [`读取 progress 失败: ${err.message}`] };
748
749
  }
749
750
  if (progressData === null) {
751
+ // 删除台账兜底(2026-10-13-sync-ledger-tombstone,事故 2026-10-13-new-change-e5a27813):
752
+ // DB 行被外部物理清除(rm + 裸 SQL)后 serializeForSync 恒 null、X1 墓碑链失源,
753
+ // 平台行永挂 active。台账里「存在过且已达终态」时合成最小载荷补推墓碑,让平台
754
+ // _apply_cli_tombstone 收敛 location。台账无此名(从未存在,如 --change 打错字)
755
+ // 不发——误发会把平台同名真变更软删。quick 会话豁免(与 tombstoneDue 同口径)。
756
+ if (!QUICK_SID_RE.test(changeName)) {
757
+ const ledger = lookupDeletedLedger(safePlatformSpecDir(this.cwd) || join(this.cwd, '.sillyspec'), changeName);
758
+ if (ledger && (ledger.status === 'deleted' || ledger.status === 'archived')) {
759
+ const pushed = await this._pushLedgerTombstone(changeName, ledger, { signal: opts.signal });
760
+ return { synced: 0, errors: [], ledgerTombstone: pushed ? 'pushed' : 'best-effort-failed' };
761
+ }
762
+ }
750
763
  console.warn(`[sync] 变更无进度数据 (无活跃进度): ${changeName}`);
751
764
  return { synced: 0, errors: [`变更无进度数据: ${changeName}`] };
752
765
  }
@@ -1098,6 +1111,63 @@ export class SyncManager {
1098
1111
  return { synced: 0 };
1099
1112
  }
1100
1113
 
1114
+ /**
1115
+ * 台账墓碑(2026-10-13-sync-ledger-tombstone):DB 行已被外部物理清除、终态凭证只剩
1116
+ * 删除台账时的合成墓碑上行。与 _pushTombstone 的差异:不读 DB(无行可读),载荷由
1117
+ * 台账条目合成——changes[0] 携带台账 status(平台 _apply_cli_tombstone 据此收敛
1118
+ * location)+ current_stage='archive',其余五表空数组(平台端解析容错,字段缺失即空集)。
1119
+ * base_ts 不设:平台契约「base_ts 缺失无条件接受」(platform_sync/service.py 分支 1),
1120
+ * 不与既有 last_pushed_at 比较,无 409 面。best-effort:失败 debugLog 不抛(与 X1 链
1121
+ * 语义一致——平台 7 天 reparse 兜底仍在)。
1122
+ * @param {string} changeName
1123
+ * @param {{name: string, status: string, at: string}} ledger 台账终态条目
1124
+ * @param {{signal?: AbortSignal}} [opts]
1125
+ * @returns {Promise<boolean>} 是否推送成功
1126
+ */
1127
+ async _pushLedgerTombstone(changeName, ledger, opts = {}) {
1128
+ const platform = this._getPlatform();
1129
+ if (!platform) return false; // 未连接平台:台账墓碑无从上行,行缺失也无处可回填,静默
1130
+ const payload = {
1131
+ changes: [{
1132
+ name: changeName,
1133
+ status: ledger.status,
1134
+ current_stage: 'archive',
1135
+ created_at: ledger.at,
1136
+ last_active: ledger.at,
1137
+ title: changeName,
1138
+ }],
1139
+ stages: [],
1140
+ steps: [],
1141
+ approvals: [],
1142
+ batch_progress: [],
1143
+ };
1144
+ const headers = {
1145
+ 'Content-Type': 'application/json',
1146
+ Authorization: `Bearer ${platform.token}`,
1147
+ 'X-SillySpec-Pushed-At': new Date().toISOString(),
1148
+ };
1149
+ const pushUser = platform.user || resolvePlatformUser(this.cwd) || null;
1150
+ if (pushUser) headers['X-SillySpec-User'] = pushUser;
1151
+ const syncUrl = `${platform.url}/api/changes/${encodeURIComponent(changeName)}/progress`;
1152
+ try {
1153
+ const res = await fetchJsonWithStatus(syncUrl, {
1154
+ method: 'POST',
1155
+ headers,
1156
+ body: JSON.stringify(payload),
1157
+ signal: opts.signal,
1158
+ });
1159
+ if (res.ok) {
1160
+ console.log(`[sync] 已上行台账墓碑(行已清除,status=${ledger.status}): ${changeName}`);
1161
+ return true;
1162
+ }
1163
+ debugLog(`[sync] 台账墓碑上行未成功: ${changeName} (status=${res.status},平台 reparse 兜底)`);
1164
+ return false;
1165
+ } catch (err) {
1166
+ debugLog(`[sync] 台账墓碑上行失败: ${changeName}: ${(err && err.message) || err}`);
1167
+ return false;
1168
+ }
1169
+ }
1170
+
1101
1171
  /**
1102
1172
  * 自竞态判定辅助:fresh 读本地 DB 的 last_synced_platform_ts(base_ts),判断它是否已覆盖
1103
1173
  * 平台 409 回执的 last_pushed_at(字符串字典序比较,契约 §4.2——ISO 8601 字典序 == 时间序)。
@@ -2867,17 +2867,34 @@ function buildDepsBatches({ deps, changedFiles = [], hits = [], cwd = null, prio
2867
2867
  // cd 前缀保留(2026-09-25-deps-cwd-prefix,R15/R16 实证):剥前缀后从 worktree 根跑 uv,根上无
2868
2868
  // pyproject.toml → uv 解析到无 dev extras 的错环境 → aiobotocore 假红(两轮 known_failures 顶着)。
2869
2869
  // 前缀在则批次文件路径按该 dir 重定基(模块命令的文件参即 dir 相对口径,如 app/modules/...)。
2870
+ // py 按包根分组(坑 flow-dynamic-subset-root-scripts-path,2026-10-13 实证:主仓根
2871
+ // scripts/test_x.py 被拼进 backend 的 cd 批——相对路径在 backend cwd 下必然 not found,
2872
+ // 整批 exit 2 假败)。每个 .py 文件按最近 pyproject/uv.lock 祖先归组(与 runner 推断
2873
+ // 同款就近原则——monorepo 子项目各自独立环境);组内共用该包根的 runner 命令与重定基。
2874
+ // 显式命中命令串(hits)优先且整批共用(用户显式配置语义优先,不拆组)。
2870
2875
  let pyRunner = null
2871
2876
  for (const h of hits || []) {
2872
2877
  const cmd = String(h.test || '')
2873
2878
  const m = /^((?:cd\s+[^&|;]+&&\s*)*[^&|;]*?pytest)/.exec(cmd) || /(?:^|&&|;|\|\|)\s*([^&|;]*pytest)/.exec(cmd)
2874
2879
  if (m) { pyRunner = m[1].trim(); break }
2875
2880
  }
2876
- if (!pyRunner && cwd && pyRun.length > 0) {
2877
- const inferred = inferPyRunner(cwd, pyRun[0])
2878
- pyRunner = inferred.command
2881
+ // pyGroups: Map<dirKey, { files, command }>;dirKey null = 主仓根(无 cd 前缀)
2882
+ const pyGroups = new Map()
2883
+ const pyGroupOf = (f) => {
2884
+ if (pyRunner) return null // 显式命中命令:整批一组(既有语义)
2885
+ if (!cwd) return null
2886
+ const inferred = inferPyRunner(cwd, f)
2887
+ return inferred.dir || null
2888
+ }
2889
+ for (const f of pyRun) {
2890
+ const key = pyGroupOf(f)
2891
+ if (!pyGroups.has(key)) {
2892
+ const command = pyRunner || (cwd ? inferPyRunner(cwd, f).command : 'python -m pytest')
2893
+ pyGroups.set(key, { files: [], command })
2894
+ }
2895
+ pyGroups.get(key).files.push(f)
2879
2896
  }
2880
- if (!pyRunner) pyRunner = 'python -m pytest'
2897
+ if (!pyRunner && pyGroups.size > 0) pyRunner = pyGroups.values().next().value.command
2881
2898
  // JSX 运行器推断(2026-09-26-residual-runner-parity 起 + 结构推断源):.tsx/.jsx 是 node
2882
2899
  // 原生 type stripping 跑不了的(node --test 直跑恒败——R18 verify 段 ~11 次尝试的坑),
2883
2900
  // ① 命中命令串提取 vitest/jest(旧路径)② 无命中命令时最近含 vitest/jest 的 package.json;
@@ -2928,7 +2945,19 @@ function buildDepsBatches({ deps, changedFiles = [], hits = [], cwd = null, prio
2928
2945
  // 批计数分列(2026-10-06-fr-regress-cap-drop):count=实跑总数、prioCount=实跑中优先面数、
2929
2946
  // dropped 只计普通依赖弃置、prioDropped=防御路径计数(构造上恒 0——quotaFill 保证优先面零弃置,
2930
2947
  // >0 即组卷逻辑被破坏,消费面 loud 披露)。js 批经 jsNative/jsProject 内容分流后计数随分流面。
2931
- if (pyRun.length > 0) batches.push({ name: 'deps(auto-py)', short: 'py', command: `${pyRunner} ${pyRun.map(rebase).join(' ')}`, count: pyRun.length, prioCount: pyQ.prioCount, dropped: pyQ.dropped, prioDropped: pyQ.prioDropped, files: pyRun })
2948
+ // py 多批(坑 flow-dynamic-subset-root-scripts-path):按包根分组逐批 push——每组
2949
+ // 重定基按本组 dir(跨组文件不再共享单一 cdDir);组序稳定(Map 插入序=pyRun 排序序)。
2950
+ {
2951
+ let gi = 0
2952
+ for (const [dirKey, grp] of pyGroups) {
2953
+ // hits 命中形态 dirKey=null——从本组命令的 cd 前缀提取重定基目录(坑 deps-cwd-prefix
2954
+ // ① 用例:显式配置 'cd backend && uv run pytest …' 时路径须按 backend/ 重定基)
2955
+ const cmdDir = dirKey || /(?:^|\s)cd\s+(\S+)\s*&&/.exec(grp.command)?.[1] || null
2956
+ const rebasePy = (f) => (cmdDir && f.startsWith(cmdDir + '/')) ? f.slice(cmdDir.length + 1) : f
2957
+ batches.push({ name: pyGroups.size > 1 ? `deps(auto-py:${dirKey || 'root'})` : 'deps(auto-py)', short: 'py', command: `${grp.command} ${grp.files.map(rebasePy).join(' ')}`, count: grp.files.length, prioCount: grp.files.filter(f => prio(f) === 0).length, dropped: pyQ.dropped, prioDropped: pyQ.prioDropped, tap: true, files: grp.files, pyRoot: dirKey || null })
2958
+ gi++
2959
+ }
2960
+ }
2932
2961
  if (jsNative.length > 0) batches.push({ name: 'deps(auto-js)', short: 'js', command: `node --test --test-reporter=spec --test-reporter-destination=stderr --test-reporter=tap --test-reporter-destination=stdout ${jsNative.join(' ')}`, count: jsNative.length, prioCount: jsNative.filter(f => prio(f) === 0).length, dropped: jsTest.length - jsRun.length, prioDropped: jsQ.prioDropped, tap: true, files: jsNative })
2933
2962
  if (jsMeta.length > 0) batches.push({ name: 'deps(auto-js-meta-skip)', short: 'js-meta-skip', command: null, count: jsMeta.length, dropped: 0, skip: true, files: jsMeta, reason: '套件编排器(run-tests.mjs=npm test 组卷入口)非单测文件——内嵌执行=递归全量套件(嵌套 runner 污染假败);全量门由收口实测/CI 的 npm test 承担' })
2934
2963
 
@@ -6,8 +6,9 @@
6
6
  ## 选道(动手前先对号入座)
7
7
  | 需求形态 | 走法 |
8
8
  |---|---|
9
- | 需求已含决策(改什么、成功标准说得清) | **轻量变更(默认快道)**:`flow start` → 直接干活 → `flow done`(2 次协议调用;操作细节见 `/sillyspec:flow` skill 与 flow start 输出) |
10
- | 需求不清晰 / 需要方案探索 | 头脑风暴预段:`run brainstorm --change <名>` → 完成后 `flow start --change <名>` 收编续跑(design 以头脑风暴版为准) |
9
+ | 需求已含决策(改什么、成功标准说得清——成功标准须来自**用户原话或既有文档转写**;要靠你推断拟出的标准=需求不清晰) | **轻量变更(默认快道)**:`flow start` → 直接干活 → `flow done`(2 次协议调用;操作细节见 `/sillyspec:flow` skill 与 flow start 输出) |
10
+ | 用户显式要求极速(「极速/别验证/只要 spec 和代码」类原话)且需求已含决策 | **轻量变更极速版**:同上行加 `--turbo`(`flow start --change <名> --input "<...>" --turbo`)——**仅用户显式触发,禁止你自行启用**;收口跳过实测与重仪式门(hunk/dirty/ui 探针/评审),spec 结构校验/tasks 全勾/ledger/patch 冻结/distill/归档照常(跳的是验证不是证据);升厚即失效恢复全量仪式;收口输出显式标注「不含测试客观核验」。预段组合极速:头脑风暴预段走轻量跑判定+单轮合并问询+burst 一次 --done,收编时按用户要求带 --turbo |
11
+ | 需求不清晰 / 需要方案探索(含「成功标准要你替用户拟」的形态——自己拟标准再自答是把默认共识伪装成共识) | 头脑风暴预段:`run brainstorm --change <名>` → 完成后 `flow start --change <名>` 收编续跑(design 以头脑风暴版为准)。上个轻量变更被 route-hindsight 标记时全新 start 会被同意门拦下要求显式选道:确认已含决策带 `--no-prestage` 声明留痕,否则先走本行预段 |
11
12
  | 大改动:跨模块取舍 / Wave 计划编排 / 多阶段治理 / 设计期人机对抗 | 完整流程五阶段:`run brainstorm → plan → execute → verify → archive`(每阶段一次渲染 + 一次 --done 收口;细节见各阶段 skill 与 CLI 输出) |
12
13
  | 代码已先写好(倒推收尾) | 不回头补流程装样子:`flow start --change <YYYY-MM-DD-名> --input` 按下方过门格式写(动机=已做改动的描述,成功标准=已验证达标的行为)→ `flow done` 一步收口 |
13
14
 
@@ -30,7 +31,7 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<动机与背景>
30
31
  ## 核心规则
31
32
  1. **禁止绕过本文件规则和 SillySpec 流程**。所有变更走 sillyspec 流程,不裸改裸提交。
32
33
  2. **改代码前必须先说明依据**——依据的文档路径(design.md / 模块文档 / file-lifecycle.md(如有))或现有代码依据,无依据不改。
33
- 3. **执行顺序**:文档 → 读代码 → 写测试 → 写实现 → 跑测试 → 验收 → 更新文档。
34
+ 3. **执行顺序**:文档 → 读代码 → 写测试 → 写实现 → 跑测试 → 验收 → 更新文档。测试按层适用:纯逻辑先写测试,行为契约断言用户可见行为,视觉样式与纯配置/文档不写测试(记「不适用:理由」,验收靠规范与人工)。
34
35
  4. **实证核验再收口**:触及 `src`/`test` 的改动,CLI 会在收口时亲自实测(实测失败阻断收口;纯 doc/配置自动跳过)。测试面按变更动态推断(本变更测试 ∪ FR 关联回归 ∪ import 依赖,runner 自项目结构推断——无需配置);lint 走 `.sillyspec/local.yaml` 的 `commands.lint`。以落盘文件与测试结果为准,不信口头"已完成"。
35
36
  5. **非测试逻辑本身有误时,禁止改测试来"通过"**——修逻辑,不修测试。
36
37
  6. **git hook 拦截提交时禁止跳过**(如 `.husky/pre-push`),修复问题后再提交。
@@ -3,6 +3,11 @@
3
3
  写测试是为了拦住回归,不是"有测试文件"。
4
4
  **基础习惯照做、不展开**:FIRST(快/独立/可重复/自校验/及时)、金字塔分层(单元为主/集成少量/E2E 只冒烟)、AAA 结构、测试名描述场景、测试数据轻量构造(Object Mother/Builder)、覆盖率不追数字。
5
5
 
6
+ **先判该不该写:按层适用**(一句话判据——能写成「当 X 发生时,用户应看到/发生 Y」→ 写测试;只能描述成「看起来更精致/更美观」→ 不写,靠规范与人工验收):
7
+ - **纯逻辑**(校验规则/数据变换/状态规则/重试策略)→ 严格 test-first:先写测试再实现。
8
+ - **行为契约**(用户做 X 后应看到/发生 Y 的关键交互,组件/API 皆然)→ 断言用户可见行为;主干断言可先行,mock 细节允许实现后补;不为样式类/className 写断言。
9
+ - **视觉样式与纯配置**(颜色/布局/间距/动效;配置/文档/CI)→ 不写测试——断言样式是负资产,改一次样式碎一片;测试绑定记「不适用:<理由>」。
10
+
6
11
  逐条对照检查:
7
12
  1. **覆盖**:正常路径 + 边界 + 异常/非法输入,不只写业务正例;单测全绿 ≠ 集成正确。
8
13
  2. **断言**:断言真实输出/副作用,禁止只"不抛错"的空断言;不 mock 被测方法自身。
@@ -1,286 +0,0 @@
1
- /**
2
- * brainstorm-auto.js — auto/full 模式使用的 brainstorm 步骤定义
3
- *
4
- * 与 brainstorm.js 的区别:
5
- * 1. artifact-first:直接写文件,对话只输出摘要
6
- * 2. 自动决策:基于 AC-001~AC-011 checklist,不频繁请示
7
- * 3. next-action.json:结构化产物,驱动下游推进
8
- * 4. 只有 blocking questions 才 wait 用户
9
- * 5. 步骤从 ~13 步精简为 4 步
10
- */
11
-
12
- export const definition = {
13
- name: 'brainstorm',
14
- title: '头脑风暴(自动模式)',
15
- description: '探索需求、分析技术方案、识别风险 — artifact-first,自动决策优先',
16
-
17
- steps: [
18
- {
19
- name: '进度确认与上下文加载',
20
- migratedFrom: ['状态检查与上下文加载'],
21
- prompt: `检查进度、加载项目上下文、匹配模块。用 \`sillyspec progress show\` 查流程进度,不要用 \`sillyspec status\`(项目级快照,不推进流程)。
22
-
23
- ### 操作
24
- 1. 运行 \`sillyspec progress show\`,确认 currentStage 为 "brainstorm"
25
- 2. 如果未初始化,提示先运行 sillyspec init
26
- 3. **检查变更名称**:如果当前变更名是自动生成的(如 \`2026-06-02-new-change-a3f2b7c1\`),直接重命名为有意义名称,然后运行 \`sillyspec change-rename <旧名> <新名>\`
27
-
28
- ### 加载上下文
29
- 4. 读取项目总览 \`.sillyspec/docs/<project>/scan/PROJECT.md\`(如存在)+ 共享规范 + 子项目上下文
30
- 5. 加载项目信息:\`cat .sillyspec/projects/*.yaml 2>/dev/null\`
31
- 6. 构建命令(CLI 自 local.yaml 注入,勿再读文件):
32
- {LOCAL_COMMANDS}
33
- 7. 棕地项目:读取 scan 下的 STRUCTURE.md、CONVENTIONS.md、ARCHITECTURE.md
34
- 8. 加载模块索引:读取 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`(如存在)
35
- 9. 查看进行中的变更:\`ls .sillyspec/changes/ | grep -v archive\`
36
- 10. 检查同名变更或可复用模板
37
-
38
- ### 模块匹配
39
- 根据用户描述的需求关键词,匹配相关模块(用 _module-map.yaml 的 tags/aliases/paths)。
40
-
41
- ### 子项目判定
42
- - 单项目:直接确认
43
- - 多项目且用户已指定:直接确认
44
- - 多项目且用户未指定:列出项目列表,需要用户确认
45
-
46
- ### 如果有用户提供的原型/截图
47
- 分析提取:页面结构、表单字段、交互流程、业务规则。
48
-
49
- ### 输出
50
- 状态摘要 + 项目现状理解(3-5 句)+ 涉及模块列表 + 子项目 + 原型分析(如有)
51
-
52
- ### 注意
53
- - 以 CLI 返回为准,不要自行推断阶段
54
- - 不要用 mv 命令重命名变更目录,必须用 \`sillyspec change-rename\``,
55
- outputHint: '状态摘要 + 上下文 + 模块匹配',
56
- optional: false
57
- },
58
- {
59
- name: '需求分析与方案设计',
60
- // conditionalWait(非 requiresWait):与主模式 Step3 对齐。prompt 明确「目标明确→跳过追问,
61
- // 直接进入方案设计」「checklist 全 ✅ → AUTO_DECIDED」——这两种场景 AI 无需追问,必须能直接 --done。
62
- // 若用 requiresWait,CLI 硬门强制要 --answer(complete.js:223),AI 只能伪造 --answer「无需追问」
63
- // (正是 Grill 第三病)或 --wait 一个不存在的问题。conditionalWait 让「无需追问」路径直接 --done,
64
- // 只在真正需要用户决策(方案选择/拆分确认)时才走 --wait(multi-agent-review B1)。
65
- conditionalWait: true,
66
- repeatableWait: true,
67
- maxWaitRounds: 5,
68
- waitReason: '等待用户回答需求问题',
69
- waitOptions: ['回答见--answer', '信息够了,进入设计'],
70
- prompt: `分析需求,必要时追问,然后设计方案。
71
-
72
- ### 操作
73
-
74
- #### A. 需求评估
75
- 1. 汇总上一步加载的上下文和用户需求
76
- 2. 判断需求是否清晰:
77
- - 目标明确、范围清楚、无歧义 → 跳过追问,直接进入方案设计
78
- - 有歧义或不清楚 → 挑**一个最关键的**问题追问
79
-
80
- #### B. 追问规则(只在需要时)
81
- - 一次只问一个问题,不要一次性列出多个问题
82
- - 能从代码/文档确认的不要问用户
83
- - 多选题优于开放式问题
84
- - YAGNI — 砍掉不需要的功能
85
- - 2-3 轮问答就应进入方案设计
86
- - 优先追问影响架构/数据/接口的问题,实现细节不要问
87
-
88
- **如果需要追问:**调用 \`sillyspec run brainstorm --wait --reason "等待用户回答需求问题" --options "回答见--answer,信息够了,进入设计" --output "你的单个问题"\`
89
-
90
- #### C. 复杂度评估(追问结束后或不需要追问时)
91
- 1. 判断是否需要拆分或走批量模式:
92
- - 3+ 个可独立交付的功能模块 → 建议拆分
93
- - 任务 > 10 且有重复模式 → 建议批量模式
94
- - 简单 CRUD → 不拆
95
- 2. 如果需要拆分/批量模式:暂停等用户确认
96
- - 调用:\`sillyspec run brainstorm --wait --reason "等待用户确认拆分方案" --options "同意拆分,不需要拆分,走批量模式" --output "拆分方案摘要"\`
97
- 3. 不需要拆分 → 继续
98
-
99
- #### D. 方案设计(自动决策优先)
100
- 1. 基于需求理解和上下文,提出 1-3 种实现方案
101
- 2. **使用自动决策 checklist(AC-001~AC-011)判断是否需要用户选择:**
102
-
103
- \`\`\`
104
- AC-001: 未修改公共 API
105
- AC-002: 未修改数据库 schema
106
- AC-003: 未涉及鉴权/权限
107
- AC-004: 未扩大 allowed_paths
108
- AC-005: 未引入新外部依赖
109
- AC-006: 未修改核心模块(模块索引中被多方依赖或承担关键流程的模块)
110
- AC-007: 已有项目约定可复用
111
- AC-008: 不影响向后兼容
112
- AC-009: 不涉及数据迁移
113
- AC-010: 单模块范围内可完成
114
- AC-011: 不涉及业务规则/产品范围/默认行为/用户可见行为变更
115
- \`\`\`
116
-
117
- 3. **自动决策判定:**
118
- - 只有一个明显合理方案 + checklist 全部 ✅ → **自动选择,标记 AUTO_DECIDED**
119
- - 有多个方案但影响不大(checklist 全部 ✅)→ **自动选推荐方案,标记 AUTO_DECIDED**
120
- - 有多个方案且 checklist 有 ❌(影响架构/数据/接口/权限/兼容性/业务)→ **只有这一种情况才暂停等用户选择**
121
-
122
- 4. 如果需要用户选择方案:
123
- - 调用:\`sillyspec run brainstorm --wait --reason "等待用户选择方案" --options "方案A,方案B,方案C" --output "方案对比摘要"\`
124
-
125
- #### E. Design Grill(轻量交叉审查)
126
- 对方案做快速交叉审查:
127
- - 检查方案与 ARCHITECTURE.md / CONVENTIONS.md 的一致性
128
- - 检查是否有术语歧义、边界遗漏
129
- - 能自动解决的直接修正,只有需要业务判断的才问用户
130
- - 跳过条件:单模块、无状态流转、< 3 个文件变更
131
-
132
- ### 输出
133
- 需求理解摘要 + 复杂度评估 + 方案决策(AUTO_DECIDED 或用户选择)
134
-
135
- ### 注意
136
- - **不要自问自答。** 不要在自己输出中模拟用户回答然后说"需求已明确"
137
- - checklist 判定结果必须在 decisions.md 中记录依据`,
138
- outputHint: '需求理解 + 方案决策',
139
- optional: false
140
- },
141
- {
142
- name: '生成设计产物',
143
- prompt: `将设计方案写入文件(artifact-first),不回显正文。
144
-
145
- ### 操作
146
- 1. 确保变更目录存在:\`mkdir -p .sillyspec/changes/<change-name>\`
147
- 2. **直接将设计方案写入文件,不要先在对话中输出完整内容再写文件。**
148
- 3. 产物写入**变更根目录**(\`.sillyspec/changes/<change-name>/\`),不要建 \`brainstorm/\` 子目录——stage validator 只认根目录,写进子目录会在 \`--done\` 校验时被判产物缺失。
149
-
150
- ### 产物文件
151
-
152
- #### design.md(必填,写入变更根目录 \`design.md\`)
153
- 包含:背景、设计目标、非目标、总体方案、文件变更清单、接口定义、数据模型(如涉及)、兼容策略(brownfield)、风险登记、决策追踪、自审。
154
- **章节骨架必须按下述字面格式写**(校验器按字面短语匹配,措辞偏离会连环卡门——坑 literal-template-trap,2026-08-24 用户反馈五期②):
155
- \`\`\`markdown
156
- # 设计文档(Design)— <变更简述>
157
-
158
- ## 背景
159
- <为什么做、解决什么问题>
160
-
161
- ## 设计目标
162
- <要达成什么>
163
-
164
- ## 非目标
165
- <明确不做的事——标题必须字面含「非目标」(Non-Goals 也认,但大小写须完全一致)>
166
-
167
- ## 总体方案
168
- <技术方案>
169
-
170
- ## 文件变更清单
171
- | 操作 | 文件路径 | 说明 |
172
- |---|---|---|
173
- | 新增 | src/xxx/NewFile.ts | ... |
174
- | 修改 | src/xxx/ExistingFile.ts | ... |
175
-
176
- ## 生命周期契约表
177
- <仅当涉及 session/lease/agent_run/daemon/lifecycle/state_transition/claim/heartbeat 关键词时必填;
178
- 若不涉及,删掉本节或在正文写紧邻豁免短语:「不涉及生命周期契约」或「生命周期契约:无/N/A」
179
- ——「无需 lifecycle 事件」这类宽写法不被识别(否定词必须紧邻「生命周期契约/lifecycle contract」)>
180
-
181
- | 事件 | 发起方 | 接收方 | 必需字段 | 状态变化 |
182
- |---|---|---|---|---|
183
- | <事件名> | <发起方> | <接收方> | <字段清单> | <初始态 → 终态> |
184
-
185
- ## 风险登记
186
- | 编号 | 风险 | 等级 | 应对策略 |
187
- |---|---|---|---|
188
- | R-01 | ... | P0/P1/P2 | ... |
189
-
190
- ## 决策追踪
191
- <列出 D-xxx@vN 决策 ID 及覆盖关系;无决策可写「无」>
192
-
193
- ## 自审
194
- <对自身设计的校验——标题必须字面含「自审」或「Self-Review」(自我审查等宽写法不识别)>
195
- \`\`\`
196
-
197
- #### decisions.md(必填,写入变更根目录 \`decisions.md\`)
198
- 记录所有决策:
199
- \`\`\`markdown
200
- ---
201
- author: <git-user>
202
- created_at: <now-datetime>
203
- ---
204
-
205
- # 决策记录(Decisions)
206
-
207
- ## D-001@v1: 决策短标题
208
- - type: architecture | boundary | compatibility | ...
209
- - priority: P0 | P1 | P2
210
- - status: AUTO_DECIDED | NEEDS_REVIEW | USER_DECIDED
211
- - source: user | code | docs
212
- - question: 被解决的问题
213
- - answer: 选择或结论
214
- - checklist: AC-001 ✅, AC-007 ✅(AUTO_DECIDED 时必须列出)
215
- - normalized_requirement: 可测试约束
216
- - impacts: [FR-01, task-01]
217
- - evidence: 代码/文档路径或用户回答轮次
218
- \`\`\`
219
-
220
- #### gaps.md(必填,写入变更根目录 \`gaps.md\`)
221
- 记录已识别的缺口。status=BLOCKER 的缺口会在 plan-postcheck 中触发回退。
222
-
223
- #### assumptions.md(必填,写入变更根目录 \`assumptions.md\`)
224
- 记录隐含假设和验证方法。
225
-
226
- #### next-action.json(必填,写入变更根目录 \`next-action.json\`)
227
- \`\`\`json
228
- {
229
- "status": "ready_for_plan" | "waiting_for_user",
230
- "decision_level": "high" | "medium" | "low",
231
- "has_blocking_questions": true | false,
232
- "blocking_reasons": [],
233
- "questions": [],
234
- "auto_decisions": [
235
- { "id": "D-001", "decision": "方案描述", "reason": "AC-007 ✅" }
236
- ]
237
- }
238
- \`\`\`
239
-
240
- ### 自审
241
- 对 design.md 执行自审(需求覆盖、文件变更清单具体性、验收标准可测试、非目标清晰、兼容策略)。发现问题直接修改文件。
242
-
243
- ### 如果有 blocking questions
244
- 将问题写入 next-action.json.questions,在对话中输出问题列表和推荐选项,然后 --wait。
245
-
246
- ### 输出(artifact-first)
247
- 只输出摘要:
248
- \`\`\`
249
- 已生成设计产物(5 个文件,均在变更根目录):
250
- - design.md — 采用 xxx 方案,涉及 N 个文件变更
251
- - decisions.md — M 个决策,K 个 AUTO_DECIDED
252
- - gaps.md — J 个缺口,0 个 BLOCKER
253
- - assumptions.md — L 个假设
254
- - next-action.json — ready_for_plan / waiting_for_user
255
- \`\`\``,
256
- outputHint: '产物摘要',
257
- optional: false
258
- },
259
- {
260
- name: '生成规范文件',
261
- requiresWait: true,
262
- waitReason: '等待用户最终确认',
263
- waitOptions: ['确认', '需要修改', '推翻重来'],
264
- prompt: `生成 proposal.md / requirements.md / tasks.md,让用户确认。
265
-
266
- ### 操作
267
- 1. 基于变更根目录下的设计产物(design.md / decisions.md / gaps.md / assumptions.md / next-action.json),生成规范文件(直接写入变更根目录):
268
- - **proposal.md**:动机、关键问题、变更范围、不在范围内、成功标准
269
- - **requirements.md**:角色表 + FR 编号需求 + Given/When/Then + 非功能需求
270
- - **tasks.md**:任务列表骨架(只列名称;任务清单唯一真相,plan 阶段展开细节并写回本文件——execute 勾选与 verify 对照都在本文件)
271
- 2. 所有规范文件头部包含 YAML frontmatter
272
- 3. \`git add {SPEC_ROOT}/changes/<change-name>/\` — 暂存本变更的规范文件(勿用 .sillyspec/ 整目录——会裹挟其他活跃变更;不要 commit)
273
-
274
- ### 输出(摘要)
275
- 规范文件路径列表(各一句话说明)
276
-
277
- ### 铁律
278
- - **直接写文件,不在对话中输出完整内容**
279
- - 暂停等待用户确认:\`sillyspec run brainstorm --wait --reason "等待用户最终确认" --options "确认,需要修改,推翻重来" --output "规范文件摘要"\`
280
- - 禁止自动 commit
281
- - 禁止在确认前推进到后续阶段`,
282
- outputHint: '规范文件摘要',
283
- optional: false
284
- }
285
- ]
286
- }