sillyspec 3.32.7 → 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.
package/src/fr-index.js CHANGED
@@ -594,8 +594,17 @@ export function indexRequirements({ changeDir, knowledgeRoot, headHash = '', del
594
594
  // 落域机械改进(2026-09-27-knowledge-digest):伪域告警附交付路径推导的建议域——
595
595
  // 归档时转达人工 1 秒确认(建议器与 digest 信号卡共用 suggestDomainFromFiles 单源)
596
596
  const suggested = suggestDomainFromFiles(deliverableFiles || [], { knowledgeRoot })
597
+ // 坑 fr-redomain-advisory-and-selectivity-gap(2026-10-13):告警接专用命令 +
598
+ // --by-change 条目级过滤 + D-001 迁域不换号铁律明示(防手工迁移按序重编号断
599
+ // supersede 链)。锚定域取告警列出的伪域文件名(auto-x.md → auto-x)。
600
+ const pseudoDomains = [...new Set(pseudoHits.map((w) => String(w.file || '').replace(/\.md$/, '')))].filter(Boolean)
597
601
  console.warn(`⚠️ [FR 域路由降级] 本变更 ${pseudoHits.length} 条 FR 落伪域/unmapped(${[...new Set(pseudoHits.map((w) => w.file))].join('、')})${suggested ? `——按交付路径建议域:${suggested}(确认后可迁移;` : '('}补模块卡(docs/<项目>/modules/_module-map.yaml 登记该目录)后,新变更将自动落回真域;绿地仓可跑 sillyspec run scan 校准)`)
598
- if (suggested) console.warn(` 迁移/存量清单:sillyspec knowledge digest(伪域信号卡)`)
602
+ if (suggested) {
603
+ console.warn(` 域级整体迁移:sillyspec tests --redomain --from <伪域> --to ${suggested} --write(干跑去 --write);只迁本变更条目:加 --by-change <本变更名>`)
604
+ if (pseudoDomains.length > 0) console.warn(` 例:sillyspec tests --redomain --from ${pseudoDomains[0]} --to ${suggested} --by-change <本变更名> --write`)
605
+ console.warn(` ⚠️ 迁域不换号(D-001 铁律):条目 ID 保持原样(绑定/supersede 链按 ID 寻址)——手工搬移时勿按目标域接续重编号`)
606
+ console.warn(` 迁移/存量清单:sillyspec knowledge digest(伪域信号卡)`)
607
+ }
599
608
  }
600
609
  const unm = all.get('unmapped')
601
610
  if (unm && unm.sections.length > 50) {
package/src/index.js CHANGED
@@ -3382,17 +3382,19 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
3382
3382
  // --from <域> --to <域> [--anchor <FR-id> 可重复];干跑缺省列条目,--write 落盘。
3383
3383
  // 身份铁律:迁域不换号(绑定/supersede 链靠 ID 寻址);平台伪域卡「一键迁移」的 CLI 端。
3384
3384
  if (has('--redomain')) {
3385
- const fromD = flag('--from')
3386
- const toD = flag('--to')
3385
+ const { planRedomain, redomainFrEntries, normalizeDomainArg } = await import('./redomain.js')
3386
+ // 入参归一化(2026-10-14-redomain-domain-arg-normalize):--from/--to 容忍
3387
+ // flow done 提示语原样形态(fr/auto-backend、auto-backend.md),回显/报错统一裸域名。
3388
+ const fromD = normalizeDomainArg(flag('--from'))
3389
+ const toD = normalizeDomainArg(flag('--to'))
3387
3390
  // --by-change(坑 fr-domain-suggest-typo-and-no-split-migration 缺陷2):unmapped 等
3388
3391
  // 混合池按「变更:<名>」字段分批迁移——整域迁移会错置大部分条目,来源变更才是分批键。
3389
3392
  const byChangeD = flag('--by-change')
3390
- if (!fromD || !toD) { fail('用法: sillyspec tests --redomain --from <源域> --to <目标域> [--anchor <FR-id>] [--by-change <变更名>] [--write]'); break }
3393
+ if (!fromD || !toD) { fail('用法: sillyspec tests --redomain --from <源域> --to <目标域> [--anchor <FR-id>] [--by-change <变更名>] [--write]\n 域名可带 fr/ 前缀与 .md 后缀(fr/auto-backend ≍ auto-backend.md ≍ auto-backend)'); break }
3391
3394
  // 多 --anchor 收集
3392
3395
  const anchorList = []
3393
3396
  for (let i = 0; i < filteredArgs.length - 1; i++) if (filteredArgs[i] === '--anchor') anchorList.push(filteredArgs[i + 1])
3394
3397
  try {
3395
- const { planRedomain, redomainFrEntries } = await import('./redomain.js')
3396
3398
  if (!has('--write')) {
3397
3399
  // 预览与落盘同口径透传 byChange(坑 redomain-plan-preview-by-change-ignored,
3398
3400
  // 2026-10-02 实证:76338b4d 收编在途工作时此行漏透传——预览列整域、落盘只迁
@@ -4044,6 +4046,53 @@ SillySpec worktree — git worktree 隔离管理
4044
4046
  }
4045
4047
  break;
4046
4048
  }
4049
+ case 'deps': {
4050
+ // deps 状态显式覆写(坑 deps-gate-offline-fragile,2026-10-12 实证:离线环境
4051
+ // npm ci 必败 + lockfile 本地改动致链接快路径失效,agent 被迫手改 meta.json 绕过
4052
+ // ——未定义行为且零留痕。本命令把绕过通道 CLI 化:--set 显式值 + --reason 强制
4053
+ // 留痕(写 depsOverrideNote / depsOverrideAt),审计面可对账。合法值收敛工具
4054
+ // 认知集(n/a/linked/installed/failed)。
4055
+ const setIdx = filteredArgs.indexOf('--set')
4056
+ const setVal = setIdx !== -1 && filteredArgs[setIdx + 1] && !String(filteredArgs[setIdx + 1]).startsWith('--') ? String(filteredArgs[setIdx + 1]).trim() : null
4057
+ const rIdx = filteredArgs.indexOf('--reason')
4058
+ const reason = rIdx !== -1 && filteredArgs[rIdx + 1] ? String(filteredArgs[rIdx + 1]).trim() : null
4059
+ if (!wtName || !setVal || !reason) {
4060
+ console.error('用法: sillyspec worktree deps --change <变更名> --set <n/a|linked|installed|failed> --reason "<事实依据——必填留痕>"')
4061
+ console.error(' 离线/内网环境供给链失败时的显式覆写通道(写 meta.json + 覆写留痕);先确认 node_modules 确实可用再 set linked')
4062
+ process.exit(2)
4063
+ }
4064
+ if (!['n/a', 'linked', 'installed', 'failed'].includes(setVal)) {
4065
+ console.error(`❌ --set 值非法 "${setVal}"(合法:n/a | linked | installed | failed)`)
4066
+ process.exit(2)
4067
+ }
4068
+ try {
4069
+ const specBaseWd = resolvePlatformSpecDir(dir, specDir)
4070
+ const wtBase = join(specBaseWd, '.runtime', 'worktrees')
4071
+ const wtDir = join(wtBase, wtName)
4072
+ const metaPath = join(wtDir, 'meta.json')
4073
+ // 跨仓 worktree:主 specBase 下无实体时扫各注册仓(简化:本命令聚焦主仓 worktree,
4074
+ // 跨仓路径用 --spec-dir 指到对应 specBase;两形态 meta.json 同款)
4075
+ if (!existsSync(metaPath)) {
4076
+ console.error(`❌ 找不到 worktree meta:${metaPath}`)
4077
+ console.error(' 跨仓 worktree 用 --spec-dir 指向其 specBase 后重跑;变更名核对 sillyspec worktree list')
4078
+ process.exit(1)
4079
+ }
4080
+ const meta = JSON.parse(String(readFileSync(metaPath, 'utf8')).replace(/^\uFEFF/, ''))
4081
+ const prev = meta.depsStatus
4082
+ meta.depsStatus = setVal
4083
+ meta.depsOverrideNote = `手工覆写: ${reason}(前值 ${prev ?? 'null'})`
4084
+ meta.depsOverrideAt = new Date().toISOString()
4085
+ const { writeAtomicSync } = await import('./fs-atomic.js')
4086
+ writeAtomicSync(metaPath, JSON.stringify(meta, null, 2) + '\n')
4087
+ console.log(`✅ deps 状态已覆写:${wtName} depsStatus ${prev ?? 'null'} → ${setVal}`)
4088
+ console.log(` 留痕:${meta.depsOverrideNote}`)
4089
+ console.log(' verify/archive 对账时以 depsOverrideNote 为审计锚——事实依据不实属未定义行为')
4090
+ } catch (e) {
4091
+ console.error(`❌ 覆写失败: ${e.message}`)
4092
+ process.exit(1)
4093
+ }
4094
+ break;
4095
+ }
4047
4096
  default:
4048
4097
  console.error(`❌ 未知子命令: worktree ${wtSubCmd}`);
4049
4098
  console.log(' 运行 sillyspec worktree --help 查看帮助');
@@ -4350,7 +4399,15 @@ SillySpec platform — SillyHub 平台同步
4350
4399
  const syncChangeName = syncChangeIdx >= 0 && args[syncChangeIdx + 1] && !String(args[syncChangeIdx + 1]).startsWith("--") ? args[syncChangeIdx + 1] : null;
4351
4400
  if (syncChangeName) {
4352
4401
  // manual:用户显式同步,change_deleted 回执噪音闸旁路(显式动作的回执必须可见)
4353
- await syncModule.sync(syncChangeName, dir, { manual: true });
4402
+ // 失败必须 exit 1(2026-10-13-sync-ledger-tombstone,事故 2026-10-13-new-change-e5a27813):
4403
+ // 此前忽略返回值恒 exit 0——变更不存在/未连接平台都「假成功」,daemon RPC
4404
+ // (sillyspec.sync_change)据退出码回执,假成功掩盖行缺失。成功路径(含
4405
+ // change_deleted 预期回执、台账墓碑补推)errors 为空,exit 0 语义不变。
4406
+ const syncResult = await syncModule.sync(syncChangeName, dir, { manual: true });
4407
+ if (syncResult && Array.isArray(syncResult.errors) && syncResult.errors.length > 0) {
4408
+ console.error(`❌ platform sync 失败(${syncChangeName}): ${syncResult.errors.join(';')}`);
4409
+ process.exit(1);
4410
+ }
4354
4411
  } else {
4355
4412
  // 全树 spec 文件同步(2026-10-11-sync-conflict-tri-fix FR-03):不带 --change
4356
4413
  // 此前报「需要指定变更名称」——归档/历史变更无活跃会话标签时无从下手。现走
@@ -3,6 +3,7 @@
3
3
  // pm._changePath(persistence-core 留 facade 本体)。无共享常量依赖。
4
4
  import { existsSync, mkdirSync, readFileSync, renameSync } from 'fs';
5
5
  import { join } from 'path';
6
+ import { appendDeletedLedger } from '../deleted-ledger.js';
6
7
 
7
8
  // 所有权活跃心跳窗缺省(2026-09-14-change-ownership-guards task-02 / FR-01):owner 非本会话
8
9
  // 且 now-last_active < 窗口 → 拒绝接管;窗口外 → 自动接管。local.yaml change-ownership.
@@ -601,6 +602,9 @@ export class ChangeRegistry {
601
602
  // 本地脏度(D-013 / task-04):删除也是本地状态推进(triggerSync 据此推终态/墓碑上行)
602
603
  this.pm._touchLocalModified(cwd, changeName, now);
603
604
  });
605
+ // 删除台账(2026-10-13-sync-ledger-tombstone):DB 行被外部物理清除后墓碑链的唯一
606
+ // 终态凭证。事务提交后追加、fail-open(见 deleted-ledger.js 头注释)。
607
+ appendDeletedLedger(this.pm._getSpecDir(cwd), changeName, 'deleted');
604
608
  }
605
609
 
606
610
  /**
@@ -669,6 +673,9 @@ export class ChangeRegistry {
669
673
  // 本地脏度(D-013 / task-04):归档也是本地状态推进
670
674
  this.pm._touchLocalModified(cwd, changeName, now);
671
675
  });
676
+ // 删除台账(2026-10-13-sync-ledger-tombstone):归档终态同记(archived)——行被外部
677
+ // 清除后 sync 台账分支据此补推 archived 墓碑(平台 location='archive')。fail-open。
678
+ appendDeletedLedger(this.pm._getSpecDir(cwd), changeName, 'archived');
672
679
  }
673
680
  }
674
681
 
package/src/progress.js CHANGED
@@ -23,6 +23,10 @@ import { StepStore } from './progress/step-store.js';
23
23
  import { StageMachine } from './progress/stage-machine.js';
24
24
  import { stageRegistry } from './stages/index.js';
25
25
  import { STAGE_ORDER, MAIN_FLOW_ORDER, VALID_STAGES, STAGE_LABELS, SPEC_DIR_NAME, CURRENT_VERSION, emptyStage } from './progress/shared.js';
26
+ // 2026-10-13-thin-sync-stage:叶子模块(无内部依赖),同步投影读 thin 出身用——
27
+ // 叶子化避免本文件为读一个状态文件拉入 flow 协议整条依赖链(评审 P3 勘误:
28
+ // flow.js 静态 import 闭包实测不达本文件,非循环依赖问题)。
29
+ import { readFlowState } from './flow-state.js';
26
30
  // resolveSpecDir 单一真相源在 src/run/shared.js(含 home 拒绝守卫,坑 cwd-correction-home-collision
27
31
  // 根治:home 下 .sillyspec 恒不命中,防 smoke/临时目录污染自我延续)。此处 re-export 保持
28
32
  // 既有 import 路径兼容;run/shared.js 对 progress.js 只有动态 import,无静态循环。
@@ -600,6 +604,18 @@ export class ProgressManager {
600
604
  // → 上行恒无标题,平台只能落 change_key(「英文 key」病灶的同步链一侧)。
601
605
  title: changeRow.title ?? null,
602
606
  };
607
+ // thin 出身上行投影(2026-10-13-thin-sync-stage):tier=thin 且行 status=active 时
608
+ // current_stage 上报 'thin'——平台 StageEnum.THIN / 列表「轻量」徽章与筛选 / ingest
609
+ // 双守卫均按该值消费;本地行出生恒 'brainstorm'(thin 不走阶段机),照抄上行会让
610
+ // 平台读侧 is_thin 判定三分支全不命中(实证:进行中轻量变更无徽章)。仅覆写载荷,
611
+ // 本地 DB 行不动;归档(status=archived,行已翻 'archive')与升厚(tier=thick)不
612
+ // 覆写;flow-state 缺失/损坏 readFlowState→null 按行值上行(fail-open 现状行为)。
613
+ if (
614
+ changeRow.status === 'active' &&
615
+ readFlowState(this._changePath(cwd, changeName))?.tier === 'thin'
616
+ ) {
617
+ changeProj.current_stage = 'thin';
618
+ }
603
619
 
604
620
  // 2. project 全局单行(只投影稳定字段 name/schema_version;created_at/updated_at 是本地库元数据,
605
621
  // 同步无意义且破坏 round-trip 跨库等值——与 changes 排除 created_at 同理,B2)
@@ -1201,6 +1217,73 @@ export class ProgressManager {
1201
1217
  return this.read(cwd, changeName);
1202
1218
  }
1203
1219
 
1220
+ /**
1221
+ * 原子抢占式建变更(2026-10-13-autocreate-atomic-claim,事故 2026-10-13-new-change-e5a27813)。
1222
+ *
1223
+ * 背景:run brainstorm / run auto 无 --change 的自动建名此前是「listChanges 查零活跃 →
1224
+ * initChange 插行」两步——检查与插入非原子,并行进程都能通过检查各建一行(当日
1225
+ * daemon-log-config-view 与 new-change-e5a27813 同秒落地实证)。本方法把「零活跃检查 +
1226
+ * 建行」收敛为单条 INSERT..SELECT(NOT EXISTS active / NOT EXISTS 同名 双守卫)——
1227
+ * SQLite 单语句原子;跨进程 WAL 快照过期(BUSY_SNAPSHOT)由 db.js transaction 的
1228
+ * BUSY 整体重跑收敛:重跑后守卫见到先行者已提交的 active 行即拒绝。
1229
+ *
1230
+ * 与 initChange 的差异(调用方仅 run/command.js 自动建名两处;显式 --change / 平台
1231
+ * 工具 / 测试 fixture 继续用 initChange,语义不动):
1232
+ * - 检查+插行原子化(败者零写入);
1233
+ * - 目录创建移到抢占成功之后(败者不留 changes/<自动名>/ 空目录);
1234
+ * - 失败返回原因而非静默创建。
1235
+ *
1236
+ * @param {string} cwd 项目根
1237
+ * @param {string} changeName 变更名(自动名恒为日期前缀名;quick 会话名与 default 不走本方法)
1238
+ * @param {{title?: string|null, quicklogId?: string|null}} [meta]
1239
+ * @returns {{created: true, progress: object} | {created: false, reason: 'active_exists'|'name_exists', activeChanges?: string[]}}
1240
+ */
1241
+ initChangeIfNoActive(cwd, changeName, meta = {}) {
1242
+ if (!changeName) {
1243
+ console.warn('⚠️ initChangeIfNoActive: changeName 为空,跳过');
1244
+ return { created: false, reason: 'name_exists' };
1245
+ }
1246
+ const db = this._ensureDB(cwd);
1247
+ let claimed = false;
1248
+ db.transaction(() => {
1249
+ const sqlDb = db.getDb();
1250
+ const now = new Date().toISOString();
1251
+ // 单语句原子抢占:守卫与插入同一语句,无跨语句竞态窗口
1252
+ const claim = sqlDb.prepare(
1253
+ `INSERT INTO changes (name, current_stage, status, created_at, last_active, title, quicklog_id)
1254
+ SELECT ?, 'brainstorm', 'active', ?, ?, ?, ?
1255
+ WHERE NOT EXISTS (SELECT 1 FROM changes WHERE status = 'active')
1256
+ AND NOT EXISTS (SELECT 1 FROM changes WHERE name = ?)`
1257
+ ).run(changeName, now, now, meta.title || null, meta.quicklogId || null, changeName);
1258
+ claimed = claim.changes === 1;
1259
+ if (!claimed) return; // 败者:事务内零后续写入,提交为空事务
1260
+ // 抢到后与 initChange 同款收尾(同事务):种全部合法阶段 + 本地脏度戳
1261
+ const changeRow = sqlDb.prepare('SELECT id FROM changes WHERE name = ?').get(changeName);
1262
+ for (const stage of VALID_STAGES) {
1263
+ sqlDb.prepare(
1264
+ `INSERT OR IGNORE INTO stages (change_id, stage, status)
1265
+ VALUES (?, ?, 'pending')`
1266
+ ).run(changeRow.id, stage);
1267
+ }
1268
+ this._touchLocalModified(cwd, changeName, now);
1269
+ });
1270
+ if (!claimed) {
1271
+ const sqlDb = db.getDb();
1272
+ const activeChanges = sqlDb.prepare("SELECT name FROM changes WHERE status = 'active'").all().map((r) => r.name);
1273
+ const sameName = sqlDb.prepare('SELECT id FROM changes WHERE name = ?').get(changeName);
1274
+ // 判序:同名行存在(任意 status)优先报 name_exists(重名与自动名随机碰撞兜底);
1275
+ // 否则按 active_exists 附清单(调用方走多活跃引导)
1276
+ return sameName && !activeChanges.includes(changeName)
1277
+ ? { created: false, reason: 'name_exists' }
1278
+ : { created: false, reason: 'active_exists', activeChanges };
1279
+ }
1280
+ // 抢占成功后才建目录(败者零残留);quick/default 豁免与 initChange 同款(防辅助阶段空目录物化)
1281
+ if (!/^quick-[0-9a-f]{8}$/.test(changeName) && changeName !== 'default') {
1282
+ this._ensureChangeDir(cwd, changeName);
1283
+ }
1284
+ return { created: true, progress: this.read(cwd, changeName) };
1285
+ }
1286
+
1204
1287
  setStage(cwd, stage, changeName = null) { return this._stepStore.setStage(cwd, stage, changeName); }
1205
1288
 
1206
1289
  addStep(cwd, stage, stepName, changeName = null) { return this._stepStore.addStep(cwd, stage, stepName, changeName); }
package/src/redomain.js CHANGED
@@ -21,6 +21,27 @@ import { writeAtomicSync } from './fs-atomic.js'
21
21
  /** 条目段头(## FR-xxx-NNN / ## FR-NNN)判定与 ID 提取 */
22
22
  const FR_HEADER_RE = /^## (FR-[\w.-]+-\d+|FR-\d+)\s*(.*)$/
23
23
 
24
+ /**
25
+ * 域名入参归一化(2026-10-14-redomain-domain-arg-normalize)。
26
+ *
27
+ * flow done 伪域告警提示语(fr-index.js)给出的是 `fr/auto-backend` 形态、手工
28
+ * 输入还可能带 `.md` 后缀,而本模块按裸域名拼 `fr/${from}.md`——两种口径曾打架
29
+ * 产生 `fr/fr/` 双前缀报错。本函数把入参统一收敛为裸域名:trim → 剥前导 `fr/`
30
+ * (不区分大小写)→ 剥尾随 `.md`;裸名原样返回,幂等。可能返回空串(如只传
31
+ * `fr/`)——空值拒绝由调用方负责(报错文案需含「fr/ 前缀」用法提示)。
32
+ * 域文件物理上都在 fr/ 目录内、裸名不含 `/`,`fr/xxx` 只可能是前缀形态,无歧义。
33
+ */
34
+ export function normalizeDomainArg(v) {
35
+ return String(v ?? '').trim().replace(/^fr\//i, '').replace(/\.md$/i, '').trim()
36
+ }
37
+
38
+ /** 归一化后空值拒绝(统一文案,调用方在一切 fs IO 之前调用) */
39
+ function assertDomainArgs({ from, to }) {
40
+ if (!from || !to) {
41
+ throw new Error('源域/目标域为空:域名可带 fr/ 前缀与 .md 后缀(fr/auto-backend ≍ auto-backend.md ≍ auto-backend)')
42
+ }
43
+ }
44
+
24
45
  function frEntryId(headerLine) {
25
46
  const m = FR_HEADER_RE.exec(headerLine)
26
47
  return m ? m[1] : null
@@ -64,6 +85,9 @@ function entrySourceChange(sectionLines) {
64
85
  * @throws 源域缺席 / 无匹配条目 / anchor 未命中
65
86
  */
66
87
  export function redomainFrEntries({ knowledgeRoot, from, to, anchors = null, byChange = null }) {
88
+ from = normalizeDomainArg(from)
89
+ to = normalizeDomainArg(to)
90
+ assertDomainArgs({ from, to })
67
91
  const frDir = join(knowledgeRoot, 'fr')
68
92
  const fromPath = join(frDir, `${from}.md`)
69
93
  const toPath = join(frDir, `${to}.md`)
@@ -134,6 +158,9 @@ export function redomainFrEntries({ knowledgeRoot, from, to, anchors = null, byC
134
158
 
135
159
  /** 干跑预览:列出将迁移条目,不落盘(byChange 同执行侧过滤) */
136
160
  export function planRedomain({ knowledgeRoot, from, to, anchors = null, byChange = null }) {
161
+ from = normalizeDomainArg(from)
162
+ to = normalizeDomainArg(to)
163
+ assertDomainArgs({ from, to })
137
164
  const frDir = join(knowledgeRoot, 'fr')
138
165
  const fromPath = join(frDir, `${from}.md`)
139
166
  if (!existsSync(fromPath)) throw new Error(`源域文件不存在:fr/${from}.md`)
@@ -32,7 +32,7 @@
32
32
  * flow-state substeps 无失败计数面(执行期裁决 D-006:数据源从卡面所述「flow-state
33
33
  * substeps」改读同为既有记录面的 verify-runs 时间线,封闭面语义不变)。
34
34
  */
35
- import { existsSync, readFileSync, readdirSync, mkdirSync } from 'node:fs'
35
+ import { existsSync, readFileSync, readdirSync, mkdirSync, rmSync } from 'node:fs'
36
36
  import { createHash } from 'node:crypto'
37
37
  import { join, basename, dirname } from 'node:path'
38
38
  import { writeAtomicSync } from './fs-atomic.js'
@@ -273,4 +273,36 @@ export function readHindsightHint({ specBase }) {
273
273
  }
274
274
  }
275
275
 
276
- export default { HINDSIGHT_THRESHOLDS, snapshotBaseline, computeHindsightMetrics, markHindsight, readHindsightHint }
276
+ /**
277
+ * 标记结构化读取(2026-10-14-prestage-consent-gate):同意门需要 change/reasons 组文案,
278
+ * 提示行渲染(resume 路径)仍走 readHindsightHint。无文件/损坏 → null(读不到=无门)。
279
+ * @returns {{ change: string, marked_at?: string, metrics?: object, reasons?: string[] } | null}
280
+ */
281
+ export function readHindsightMark({ specBase }) {
282
+ try {
283
+ const raw = readFileSync(join(specBase, '.runtime', MARK_FILE), 'utf8')
284
+ const j = JSON.parse(raw)
285
+ if (!j || typeof j !== 'object' || !j.change) return null
286
+ return j
287
+ } catch {
288
+ return null
289
+ }
290
+ }
291
+
292
+ /**
293
+ * 消费标记(声明放行 --no-prestage / adopt 头脑风暴收编时由 flow.js 调用):删除标记文件。
294
+ * 一次性反馈语义——声明只豁免下一次 start,不豁免之后所有(之后再被标记会重新落盘重新拦)。
295
+ * @returns {boolean} 是否真删(无标记/删除失败 = false,幂等零副作用)
296
+ */
297
+ export function clearHindsightMark({ specBase }) {
298
+ try {
299
+ const p = join(specBase, '.runtime', MARK_FILE)
300
+ if (!existsSync(p)) return false
301
+ rmSync(p)
302
+ return true
303
+ } catch {
304
+ return false
305
+ }
306
+ }
307
+
308
+ export default { HINDSIGHT_THRESHOLDS, snapshotBaseline, computeHindsightMetrics, markHindsight, readHindsightHint, readHindsightMark, clearHindsightMark }
@@ -24,7 +24,7 @@ import { existsSync, readdirSync, mkdirSync, writeFileSync, readFileSync, rmSync
24
24
  import { randomBytes, randomUUID } from 'node:crypto'
25
25
  import { fileURLToPath } from 'node:url'
26
26
  import { writeAtomicSync } from '../fs-atomic.js'
27
- import { resolveSpecDir, countAncestorSpecDirs, ancestorSpecDirs, resolveAncestorCeiling, resolveChangeDir, triggerSync, getStageSteps, formatWaitOptions, checkApproval, warnApprovalUnknown, didYouMean, assertSafeChangeName, assertDatedChangeName, detectQuickSessionDrift, detectWorktreeSpecDrift, resolveRuntimeRoot, resolveQuickSessionsDir, writePlatformPointer, checkPlatformManaged, isSelfReferentialSpecRoot, isTempResidueSpecRoot, PLATFORM_MANAGED_FILENAME, warnSelfRefPointerOnce, readStageBurst, STAGE_BURST_STAGES } from './shared.js'
27
+ import { resolveSpecDir, countAncestorSpecDirs, ancestorSpecDirs, resolveAncestorCeiling, resolveChangeDir, triggerSync, getStageSteps, formatWaitOptions, checkApproval, warnApprovalUnknown, didYouMean, assertSafeChangeName, assertDatedChangeName, detectQuickSessionDrift, detectWorktreeSpecDrift, resolveRuntimeRoot, resolveQuickSessionsDir, writePlatformPointer, checkPlatformManaged, isSelfReferentialSpecRoot, isTempResidueSpecRoot, PLATFORM_MANAGED_FILENAME, warnSelfRefPointerOnce, readStageBurst, STAGE_BURST_STAGES, isRetiredAutoBrainstormTable, printRetiredAutoBrainstormRefusal } from './shared.js'
28
28
  import { resolveQuickLinkedChanges } from './quick-audit.js'
29
29
  import { outputStep, collectStageWaitHistory } from './prompt.js'
30
30
  import { completeStep, completeStepBurst, skipStep, waitStep, continueStep, synthesizeStepOutput } from './complete.js'
@@ -34,7 +34,7 @@ import { ProgressManager, resolveSessionIdentity } from '../progress.js'
34
34
  import { validateChangeExists, checkTransition } from '../stage-contract.js'
35
35
  import { READONLY_AUXILIARY_STAGES } from '../constants.js'
36
36
  import { stageRegistry, auxiliaryStages } from '../stages/index.js'
37
- import { definition as brainstormAutoDef } from '../stages/brainstorm-auto.js'
37
+
38
38
  import { setQuickFileNotes } from '../quicklog.js'
39
39
  import { checkQuickSessionOwner, writeQuickSessionOwner } from '../quick-session-owner.js'
40
40
  import { hasDecisionId } from '../decisions-io.js'
@@ -171,6 +171,14 @@ function resolveChangeName(cwd, progress, specDir = null) {
171
171
  export async function ensureStageSteps(progress, stageName, cwd, specDir = null) {
172
172
  if (!progress.stages) progress.stages = {}
173
173
 
174
+ // 退役自动道守门(2026-10-14-brainstorm-auto-retire):存量 brainstorm 4 步 auto 表在此明确
175
+ // 拒绝——否则下方「步骤表与当前定义漂移」分支会把 auto 表静默重种为主模式 8 步表并清零进度。
176
+ // --reset/--reopen 走 resetStage 内联重种(不经此函数)= 指引中的恢复出路。
177
+ if (stageName === 'brainstorm' && isRetiredAutoBrainstormTable(progress.stages.brainstorm?.steps)) {
178
+ printRetiredAutoBrainstormRefusal(progress?.change || null)
179
+ process.exit(2)
180
+ }
181
+
174
182
  const steps = await getStageSteps(stageName, cwd, progress, specDir)
175
183
  if (!steps) return false
176
184
 
@@ -1340,39 +1348,45 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
1340
1348
  process.exit(2) // 用法错(--done 找不到变更,未传 --change)→ exit 2
1341
1349
  }
1342
1350
  // task-03 (D-006@v1 / FR-05): 无 --change 仅当无已存在活跃变更时 auto-create;
1343
- // 多活跃变更仓强制 --change(exit 2 引导),防 B8 幽灵变更。活跃变更数用 pm.listChanges
1344
- // (与 progress show 同源,DB changes 表 status='active')。
1345
- const activeChanges = pm.listChanges(cwd)
1346
- if (activeChanges.length > 0) {
1351
+ // 多活跃变更仓强制 --change(exit 2 引导),防 B8 幽灵变更。
1352
+ // 2026-10-13-autocreate-atomic-claim:listChanges 检查 + initChange 两步非原子
1353
+ // (并行进程同过检查各建一行,2026-10-13-new-change-e5a27813 事故实证)——改
1354
+ // initChangeIfNoActive 单语句原子抢占,败者走同一引导。
1355
+ const date = new Date().toISOString().slice(0, 10)
1356
+ const autoName = `${date}-new-change-${randomBytes(4).toString('hex')}`
1357
+ const claim = pm.initChangeIfNoActive(cwd, autoName, { title: inputText ? (deriveChangeTitle(inputText) || sanitizeDesc(inputText)) : autoName })
1358
+ if (!claim.created) {
1359
+ const activeChanges = claim.activeChanges || []
1347
1360
  console.error(`❌ 已存在 ${activeChanges.length} 个活跃变更,run brainstorm 无 --change 不会自动创建新变更。`)
1348
1361
  console.error(` 活跃变更:${activeChanges.join('、')}`)
1349
1362
  console.error(' 请用 --change <变更名> 指定要操作的变更,')
1350
1363
  console.error(' 或先归档/收尾现有活跃变更后重试。')
1351
1364
  process.exit(2) // 用法错(多活跃变更未指定 --change)→ exit 2
1352
1365
  }
1353
- const date = new Date().toISOString().slice(0, 10)
1354
- const autoName = `${date}-new-change-${randomBytes(4).toString('hex')}`
1355
1366
  console.log(`🔄 自动创建变更:${autoName}`)
1356
1367
  console.log(` 提示:可以用 --change <名称> 指定自定义变更名`)
1357
1368
  console.log(` 或事后重命名:sillyspec change-rename ${autoName} <新名称>`)
1358
- // brainstorm 自动创建变更也写 title(--input 需求描述优先 / 自动名兜底),proposal 落盘后刷新。
1359
- progress = pm.initChange(cwd, autoName, { title: inputText ? (deriveChangeTitle(inputText) || sanitizeDesc(inputText)) : autoName })
1369
+ progress = claim.progress
1360
1370
  changeName = autoName
1361
1371
  } else if (stageName === 'auto') {
1362
1372
  // auto 零活跃建变更(2026-09-08-auto-driver D-003@v1,Grill P1-②):SKILL 启动命令即
1363
1373
  // run auto --input——此前此处 exit 2 是断头路。复用 brainstorm 命名(date-new-change-hex)。
1364
- // pm.read 对零/多活跃都返 null——先分流:多活跃报错列候选(不隐式选择),零活跃才建。
1365
- const activeCount = pm.listChanges(cwd)
1366
- if (activeCount.length > 1) {
1374
+ // pm.read 对零/多活跃都返 null——多活跃报错列候选(不隐式选择),零活跃才建。
1375
+ // 2026-10-13-autocreate-atomic-claim:同 brainstorm 改原子抢占;顺带收紧「恰好 1 个
1376
+ // 活跃也建第二个」的旧宽限(旧 >1 检查与其「零活跃才建」注释自相矛盾,且计数宽限
1377
+ // 正是并发双建的温床)——1 活跃现与多活跃同走 exit 2 引导。
1378
+ const date = new Date().toISOString().slice(0, 10)
1379
+ const autoName = `${date}-new-change-${randomBytes(4).toString('hex')}`
1380
+ const claim = pm.initChangeIfNoActive(cwd, autoName, { title: inputText ? (deriveChangeTitle(inputText) || sanitizeDesc(inputText)) : autoName })
1381
+ if (!claim.created) {
1382
+ const activeCount = claim.activeChanges || []
1367
1383
  console.error(`❌ 已存在 ${activeCount.length} 个活跃变更,run auto 无法自动定位目标。`)
1368
1384
  console.error(' 活跃变更:' + activeCount.join('、'))
1369
1385
  console.error(' 请用 --change <变更名> 显式指定,或先归档/收尾现有变更。')
1370
1386
  process.exit(2)
1371
1387
  }
1372
- const date = new Date().toISOString().slice(0, 10)
1373
- const autoName = `${date}-new-change-${randomBytes(4).toString('hex')}`
1374
1388
  console.log(`🔄 auto 模式自动创建变更:${autoName}(可用 --change 指定名称,或事后 change-rename)`)
1375
- progress = pm.initChange(cwd, autoName, { title: inputText ? (deriveChangeTitle(inputText) || sanitizeDesc(inputText)) : autoName })
1389
+ progress = claim.progress
1376
1390
  changeName = autoName
1377
1391
  } else {
1378
1392
  console.error('❌ 未找到进度数据,请先运行 sillyspec init 或指定 --change <变更名>')
@@ -2021,11 +2035,9 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
2021
2035
  }
2022
2036
  }
2023
2037
 
2024
- // Helper: 在 auto 模式下获取步骤定义
2038
+ // Helper: 在 auto 模式下获取步骤定义(brainstorm-auto 已删除 2026-10-14-brainstorm-auto-retire:
2039
+ // brainstorm 一律主模式定义,wait 步由 auto 驱动方按 SS-META requiresUser 自行推进)
2025
2040
  const getAutoSteps = async (stage) => {
2026
- if (stage === 'brainstorm') {
2027
- return brainstormAutoDef.steps
2028
- }
2029
2041
  return getStageSteps(stage, cwd, progress, platformOpts?.specRoot || null)
2030
2042
  }
2031
2043
 
@@ -2037,27 +2049,11 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
2037
2049
  const ensureAutoStage = async (stage) => {
2038
2050
  const stageChanged = progress.currentStage !== stage
2039
2051
  progress.currentStage = stage
2040
- // Auto 模式下 brainstorm 使用 artifact-first 步骤
2041
- if (stage === 'brainstorm') {
2042
- const existingSteps = progress.stages?.brainstorm?.steps
2043
- const isAutoModeSteps = existingSteps?.length === 4 && existingSteps?.[0]?.name === '进度确认与上下文加载'
2044
- // auto 表在位即早退(2026-09-08 E2E 实证 bug:此前仅 !isAutoModeSteps 重种后 return,
2045
- // auto 表存在时掉进下方通用 ensureStageSteps——主模式 8 步定义判 4 步 auto 表「漂移」
2046
- // 重种主模式表,与 getAutoSteps 渲染双轨互踩,进度永远回 step1)
2047
- if (isAutoModeSteps) return progress
2048
- if (!isAutoModeSteps) {
2049
- if (!progress.stages) progress.stages = {}
2050
- progress.stages.brainstorm = {
2051
- status: 'in-progress',
2052
- startedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
2053
- completedAt: null,
2054
- steps: brainstormAutoDef.steps.map(s => ({ name: s.name, status: 'pending' }))
2055
- }
2056
- pm._write(cwd, progress, changeName)
2057
- triggerSync(cwd, changeName, platformOpts)
2058
- progress = pm.read(cwd, changeName)
2059
- return progress
2060
- }
2052
+ // 退役自动道守门(2026-10-14-brainstorm-auto-retire):存量 4 步 auto 表明确拒绝(下方通用
2053
+ // ensureStageSteps 同款拒绝兜底,此处先行给带变更名的文案)
2054
+ if (stage === 'brainstorm' && isRetiredAutoBrainstormTable(progress.stages?.brainstorm?.steps)) {
2055
+ printRetiredAutoBrainstormRefusal(changeName)
2056
+ process.exit(2)
2061
2057
  }
2062
2058
  // specRoot 必传(坑 auto-mode-specroot-miss):平台模式 specRoot 是外部目录,漏传时
2063
2059
  // getStageSteps 从 cwd 推导 miss 掉 plan/execute 动态步骤(buildPlanSteps(null) 只剩
@@ -22,22 +22,19 @@ import { writeAtomicSync } from '../fs-atomic.js'
22
22
  import { gitQuiet } from '../git-helper.js'
23
23
  import { writeCloseTraceArtifacts, projectTraceFaceRows } from '../flow-parity.js'
24
24
  import { withFileLock } from '../quicklog.js'
25
- import { triggerSync, WAIT_MARKER_RE, getStageSteps, formatWaitOptions, resolveRuntimeRoot, getOrCreateMultiRepoContext, resolveChangeDir, writePlatformDocsPointer } from './shared.js'
25
+ import { triggerSync, WAIT_MARKER_RE, getStageSteps, formatWaitOptions, resolveRuntimeRoot, getOrCreateMultiRepoContext, resolveChangeDir, writePlatformDocsPointer, isRetiredAutoBrainstormTable, printRetiredAutoBrainstormRefusal } from './shared.js'
26
26
  import { isExplicitReviewWrite, collectWorktreeChangedFiles } from '../task-review.js'
27
27
 
28
- // auto 模式 brainstorm 步骤表感知解析(2026-09-08 E2E 实证 bug:--done/--wait 推进主模式 8 步表,
29
- // 与 runAutoMode getAutoSteps 的 4 步 auto 表双轨互踩——ensureAutoStage 判非 auto 表重种清零,
30
- // 进度永远回 step1)。DB 现存 brainstorm 表为 auto 形态(4 步+首名「进度确认与上下文加载」)时,
31
- // complete 路径与渲染路径同源用 brainstormAutoDef.steps。
32
- async function getStageStepsAutoAware(stageName, cwd, progress, specDir) {
33
- const steps = await getStageSteps(stageName, cwd, progress, specDir)
34
- if (stageName !== 'brainstorm') return steps
35
- const existing = progress?.stages?.brainstorm?.steps
36
- const isAutoTable = Array.isArray(existing) && existing.length === 4
37
- && existing[0]?.name === '进度确认与上下文加载'
38
- if (!isAutoTable) return steps
39
- const { definition: brainstormAutoDef } = await import('../stages/brainstorm-auto.js')
40
- return brainstormAutoDef.steps
28
+ // 退役自动道守门(2026-10-14-brainstorm-auto-retire):brainstorm-auto 4 步表已随自动道删除。
29
+ // 2026-09-08 双轨互踩 bug 的兼容面(complete 路径感知 auto 表用 auto 定义推进)就此退役——
30
+ // 存量 auto 表变更在此明确拒绝(不静默重种清零进度);--reset/--reopen 走 resetStage 内联
31
+ // 重种主模式表 = 恢复出路。
32
+ async function getStageStepsAutoAware(stageName, cwd, progress, specDir, changeName = null) {
33
+ if (stageName === 'brainstorm' && isRetiredAutoBrainstormTable(progress?.stages?.brainstorm?.steps)) {
34
+ printRetiredAutoBrainstormRefusal(changeName)
35
+ process.exit(2)
36
+ }
37
+ return getStageSteps(stageName, cwd, progress, specDir)
41
38
  }
42
39
 
43
40
  import { executeScanPreflight, executeScanPostcheck, computeScanProfile, executeScanDetectProjects, executeScanResumeCheck, executeScanFinalize } from './scan-profile.js'
@@ -337,7 +334,7 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
337
334
  }
338
335
 
339
336
  // ── requiresWait 硬门控 ──
340
- const defStepsForCurrent = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null)
337
+ const defStepsForCurrent = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null, changeName)
341
338
  // def↔DB 一致性守卫(坑 execute-step-table-drift,2026-08-20 实证):runCommand 入口已按名
342
339
  // 重播种(ensureStageSteps),但同一命令进程内 plan.md 若再次被改(或绕过 runCommand 直调
343
340
  // completeStep 的路径),def 与 DB 步数错位会让 currentIdx 的门控/prompt 施加到错误的步骤上
@@ -1028,7 +1025,7 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
1028
1025
  }
1029
1026
  }
1030
1027
 
1031
- const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null)
1028
+ const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null, changeName)
1032
1029
  console.log(`✅ Step ${currentIdx + 1}/${steps.length} 完成:${steps[currentIdx].name}\n`)
1033
1030
 
1034
1031
  // Workflow post_check(W6 Step6 抽至 complete-handlers.js handleWorkflowPostCheck)
@@ -1888,7 +1885,7 @@ export async function waitStep(pm, progress, stageName, cwd, outputText, waitRea
1888
1885
 
1889
1886
  // maxWaitRounds 硬上限:达到后拒绝继续 --wait
1890
1887
  const currentStep = stageData.steps[currentIdx]
1891
- const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null)
1888
+ const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null, changeName)
1892
1889
  const stepDef = defSteps?.[currentIdx] || {}
1893
1890
  const maxWaitRounds = currentStep.maxWaitRounds ?? stepDef.maxWaitRounds
1894
1891
  const currentWaitRound = currentStep.waitRound || 0
@@ -2038,7 +2035,7 @@ export async function continueStep(pm, progress, stageName, cwd, answer, options
2038
2035
  } else {
2039
2036
  currentIdx = waitingSteps[0].idx
2040
2037
  }
2041
- const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null)
2038
+ const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null, changeName)
2042
2039
  const currentStepDef = defSteps?.[currentIdx] || {}
2043
2040
  const currentStep = stageData.steps[currentIdx]
2044
2041
  const isRepeatableWait = currentStepDef.repeatableWait === true || currentStep.repeatableWait === true
@@ -2247,7 +2244,7 @@ export async function skipStep(pm, progress, stageName, cwd, changeName, platfor
2247
2244
  console.log(`⚠️ Step "${steps[currentIdx].name}" 此前被门控阻断(blocked),本次 --skip 将跳过该步——若门控条件已修复,更宜直接 --done 推进。`)
2248
2245
  }
2249
2246
 
2250
- const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null)
2247
+ const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null, changeName)
2251
2248
  const stepDef = defSteps ? defSteps[currentIdx] : null
2252
2249
  if (stepDef && !stepDef.optional) {
2253
2250
  console.error(`❌ 步骤 "${steps[currentIdx].name}" 不可跳过`)
package/src/run/gates.js CHANGED
@@ -224,7 +224,7 @@ export function warnMissingUiPrototype(stageName, changeName, currentStepName, s
224
224
  const design = readFileSync(designPath, 'utf8')
225
225
  const feExts = [...new Set((design.match(/\.(vue|svelte|astro|tsx|jsx|css|scss|sass|less|styl|html)\b/gi) || []).map(s => s.replace('.', '').toLowerCase()))]
226
226
  if (feExts.length === 0) return
227
- console.warn(` ⚠️ design.md 命中前端文件类型(${feExts.join('/')})但变更目录无 prototype-*.html——轻微样式改动可忽略;布局/交互流程级变化应补原型(sillyspec run brainstorm --reopen 回到「分段展示设计」,或直接让 agent 补生成)`)
227
+ console.warn(` ⚠️ design.md 命中前端文件类型(${feExts.join('/')})但变更目录无 prototype-*.html——轻微样式改动可忽略;布局/交互流程级变化应补原型(sillyspec run brainstorm --reopen 回到「分段展示设计」,或直接让 agent 补生成;已有可复跑 HTML 原型则登记复用:复制进变更目录并命名 prototype-<名称>.html,execute 按此命名注入)`)
228
228
  }
229
229
 
230
230
  /**
package/src/run/prompt.js CHANGED
@@ -2247,6 +2247,16 @@ ${maskVolatileForGuide(guideTemplate)}
2247
2247
  }
2248
2248
  console.log('<!--SS-META:' + JSON.stringify(meta) + '-->')
2249
2249
  }
2250
+
2251
+ // 尾部全文锚(2026-10-14-guide-anchor-adopt-proto,sess_fb51d988 实证):首见全量渲染可能被
2252
+ // harness 工具结果截断保尾——长动态注入段(模块欠账/知识命中等)把决策关键节(原型分级/生成
2253
+ // 要求等)顶出可见窗口,agent 只见铁律半句就临场发挥。末尾一行带 step-guides 全文路径,截断后
2254
+ // 仍能 Read 回完整规则(complete.js task-08 锚定行的补全:带路径,不只报位置)。复入分支顶部
2255
+ // 已有 📄 行,但其动态附录同样可能很长把顶部顶掉——尾部统一再锚一次。guide 关闭/--json 劫持/
2256
+ // 文件不在场时零输出(stdout 确定性测试面不变)。
2257
+ if (guideEnabled && !jsonHijacked && existsSync(guideFile)) {
2258
+ console.log(`📄 指引全文锚:${guideFile}(本输出被截断或上下文丢失时 Read 此文件拿回完整步骤规则——决策关键节都在内)`)
2259
+ }
2250
2260
  }
2251
2261
  /**
2252
2262
  * 替换 prompt 文本中的路径根占位符 {SPEC_ROOT}/{DOCS_ROOT}/{PROJECTS_ROOT}/
package/src/run/shared.js CHANGED
@@ -2308,6 +2308,25 @@ export function _clearMultiRepoCtxCache(cwd, changeName) {
2308
2308
  }
2309
2309
 
2310
2310
 
2311
+ /**
2312
+ * 退役自动道步骤表形态判定(2026-10-14-brainstorm-auto-retire):brainstorm 4 步 auto 表
2313
+ *(首名「进度确认与上下文加载」——与主模式首步「进度确认」字面可区分)。检测到时调用方必须
2314
+ * 报退役指引并退出:防止 ensureStageSteps 把 auto 表判「漂移」静默重种清零存量进度。
2315
+ * --reset/--reopen 走 resetStage 内联重种(不经此判定)——即指引中的恢复出路。
2316
+ */
2317
+ export function isRetiredAutoBrainstormTable(steps) {
2318
+ return Array.isArray(steps) && steps.length === 4
2319
+ && steps[0]?.name === '进度确认与上下文加载'
2320
+ }
2321
+
2322
+ /** 退役拒绝文案(单一真相源:ensureStageSteps / getStageStepsAutoAware / ensureAutoStage 三入口共用)。 */
2323
+ export function printRetiredAutoBrainstormRefusal(changeName) {
2324
+ console.error(`⛔ 该变更的 brainstorm 步骤表来自已退役的自动道(4 步 auto 表,brainstorm-auto 已于 2026-10-14 删除——其设计产物流程无原型分级/真码口径,且双轨步骤表机制易互踩)。`)
2325
+ console.error(` 处置:① sillyspec run brainstorm --change ${changeName || '<变更名>'} --reset(重种主模式 8 步表从头开始——brainstorm 尚未收口时适用)② sillyspec doctor(诊断后处置)。`)
2326
+ console.error(` 进度未被修改(拒绝不写盘,不静默重种)。`)
2327
+ }
2328
+
2329
+
2311
2330
  /**
2312
2331
  * 获取阶段的步骤定义(execute 需要动态构建)
2313
2332
  */