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.
- package/AGENTS.md +4 -3
- package/assets/command-cards/flow.md +8 -3
- package/package.json +1 -1
- package/src/change-list.js +13 -0
- package/src/commit-guard.js +70 -2
- package/src/cross-repo-reconcile.js +54 -1
- package/src/deleted-ledger.js +75 -0
- package/src/design-facts.js +5 -3
- package/src/dispatch/backends/local-agent.js +1 -1
- package/src/dispatch/backends/sillyhub-mcp.js +1 -1
- package/src/flow-draft.js +24 -2
- package/src/flow-parity.js +14 -0
- package/src/flow.js +353 -58
- package/src/index.js +15 -5
- package/src/progress/change-registry.js +7 -0
- package/src/progress.js +67 -0
- package/src/redomain.js +27 -0
- package/src/route-hindsight.js +34 -2
- package/src/run/command.js +37 -41
- package/src/run/complete.js +16 -19
- package/src/run/gates.js +1 -1
- package/src/run/prompt.js +10 -0
- package/src/run/shared.js +19 -0
- package/src/scope-audit.js +2 -2
- package/src/stages/brainstorm.js +19 -11
- package/src/stages/execute.js +1 -1
- package/src/stages/explore.js +3 -5
- package/src/sync.js +70 -0
- package/src/verify-postcheck.js +34 -5
- package/templates/agents-instruction.md +4 -3
- package/templates/prompts/testcase-design.md +5 -0
- package/src/stages/brainstorm-auto.js +0 -286
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
|
|
3386
|
-
|
|
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 收编在途工作时此行漏透传——预览列整域、落盘只迁
|
|
@@ -4397,7 +4399,15 @@ SillySpec platform — SillyHub 平台同步
|
|
|
4397
4399
|
const syncChangeName = syncChangeIdx >= 0 && args[syncChangeIdx + 1] && !String(args[syncChangeIdx + 1]).startsWith("--") ? args[syncChangeIdx + 1] : null;
|
|
4398
4400
|
if (syncChangeName) {
|
|
4399
4401
|
// manual:用户显式同步,change_deleted 回执噪音闸旁路(显式动作的回执必须可见)
|
|
4400
|
-
|
|
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
|
+
}
|
|
4401
4411
|
} else {
|
|
4402
4412
|
// 全树 spec 文件同步(2026-10-11-sync-conflict-tri-fix FR-03):不带 --change
|
|
4403
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
|
@@ -1217,6 +1217,73 @@ export class ProgressManager {
|
|
|
1217
1217
|
return this.read(cwd, changeName);
|
|
1218
1218
|
}
|
|
1219
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
|
+
|
|
1220
1287
|
setStage(cwd, stage, changeName = null) { return this._stepStore.setStage(cwd, stage, changeName); }
|
|
1221
1288
|
|
|
1222
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`)
|
package/src/route-hindsight.js
CHANGED
|
@@ -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
|
-
|
|
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 }
|
package/src/run/command.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
1344
|
-
//
|
|
1345
|
-
|
|
1346
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1366
|
-
|
|
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 =
|
|
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
|
-
//
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
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) 只剩
|
package/src/run/complete.js
CHANGED
|
@@ -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
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
async function getStageStepsAutoAware(stageName, cwd, progress, specDir) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
*/
|
package/src/scope-audit.js
CHANGED
|
@@ -1329,7 +1329,7 @@ async function computeFullFlowAudit({ cwd, specBase, changeName, platformOpts, c
|
|
|
1329
1329
|
* { key, repoPath, anchor, totals, degraded, degradedReason, patch?, patchSha256? };
|
|
1330
1330
|
* notes 为调用方 note 链的增量行(跨仓计数行 + 降级行)。
|
|
1331
1331
|
*/
|
|
1332
|
-
export function reconcileCrossRepoPlan({ cwd, specBase, runtimeRoot, changeName, planEntries, collectPatch = false }) {
|
|
1332
|
+
export function reconcileCrossRepoPlan({ cwd, specBase, runtimeRoot, changeName, planEntries, collectPatch = false, crossBaselines = null }) {
|
|
1333
1333
|
const rows = []
|
|
1334
1334
|
const repos = []
|
|
1335
1335
|
const notes = []
|
|
@@ -1344,7 +1344,7 @@ export function reconcileCrossRepoPlan({ cwd, specBase, runtimeRoot, changeName,
|
|
|
1344
1344
|
let repoActual = null
|
|
1345
1345
|
let repoFailure = null
|
|
1346
1346
|
try {
|
|
1347
|
-
repoActual = collectRepoActual({ repoKey, specBase, cwd, runtimeRoot, changeName })
|
|
1347
|
+
repoActual = collectRepoActual({ repoKey, specBase, cwd, runtimeRoot, changeName, crossBaselines })
|
|
1348
1348
|
} catch (e) {
|
|
1349
1349
|
repoFailure = e && e.message ? String(e.message).split('\n')[0] : String(e)
|
|
1350
1350
|
}
|
package/src/stages/brainstorm.js
CHANGED
|
@@ -67,7 +67,7 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<动机与背景>
|
|
|
67
67
|
(2 调用协议收口)。用户同意则本阶段可在此收尾(\`--done\` 并提示转轻量变更),不必继续走完整设计流程。
|
|
68
68
|
- **明显 large**(需要 Wave 并行编排/上下文分片/多阶段治理,或用户显式要求完整流程)→ 不要提示轻量变更,继续进入下一步「对话式探索与需求澄清」。
|
|
69
69
|
- **拿不准 → 继续探索**(这正是头脑风暴的职责;澄清完成后 Step 8 按同轴精判——注意:经过头脑风暴澄清的需求通常已可单上下文吞吐,出口默认收编轻量道)。
|
|
70
|
-
-
|
|
70
|
+
- 这是**粗判**,只为让明显的纯执行小变更免走设计流程;后续「生成规范文件」步骤做精判兜底(方案在澄清中缩水后按定稿重判,本步印象不作数)。
|
|
71
71
|
|
|
72
72
|
### 输出
|
|
73
73
|
项目现状理解摘要(3-5 句话,关键约定和架构决策)+ 可能涉及的模块列表 + 本次需求所属子项目 + (如命中)quick 建议
|
|
@@ -248,11 +248,15 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<动机与背景>
|
|
|
248
248
|
3. 展示完整设计方案(不要逐段停顿,一次性展示)
|
|
249
249
|
4. 分段设计含新机制时同上检索(固定动作):设计的分段内容引入新机制词(例词为举例非机制,不构成封闭清单)时,先跑 \`sillyspec knowledge search --query "<机制词>"\`,命中条目必读(尤其 status=rejected 的防复潮条目)再定稿设计
|
|
250
250
|
5. 确认变更名(格式:\`YYYY-MM-DD-<简短描述>\`,例如 \`2026-05-13-user-auth\`——CLI 已强制:新建/改名不带日期前缀直接 exit 2)
|
|
251
|
-
6. 按「HTML
|
|
251
|
+
6. 按「HTML 原型分级」三选一必填:生成原型 / 登记复用既有原型 / 写出跳过原因(见下)
|
|
252
252
|
7. 暂停等待用户确认或修改意见
|
|
253
253
|
|
|
254
254
|
### HTML 原型分级(按改动程度,不是"是否碰前端文件")
|
|
255
255
|
判定对象是**改造后的界面相对现状的变化程度**:
|
|
256
|
+
- **免生成·登记复用**(用户已提供**可复跑**的 HTML 原型且覆盖本次 UI 面——先判本档再判改动程度):
|
|
257
|
+
- 不重画。把原型文件**复制**进 \`{SPEC_ROOT}/changes/<change-name>/\` 并**必须命名为 \`prototype-<名称>.html\`**——execute 阶段按此命名扫描注入原型引用,保留中文名/原名=没登记=白复用
|
|
258
|
+
- 输出复用声明:原型来源路径 + 覆盖面(覆盖哪些页面/端);只覆盖部分 UI 面时如实声明,未覆盖部分仍按下两档生成(变更目录允许多个 prototype-*.html 共存)
|
|
259
|
+
- 截图/静态设计稿**不适用本档**(不可复跑)——仍按「必须生成」产出可跑 HTML
|
|
256
260
|
- **必须生成**(布局/结构/流程级变化):
|
|
257
261
|
- 新增页面、视图、路由
|
|
258
262
|
- 页面布局变化:区域增删、骨架重排、信息层级调整(如单栏改多栏、列表改看板)
|
|
@@ -265,19 +269,20 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<动机与背景>
|
|
|
265
269
|
- 文案、颜色、间距、字号、图标等样式微调
|
|
266
270
|
- 纯后端逻辑/CLI/配置/文档,无任何界面变化
|
|
267
271
|
|
|
268
|
-
###
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
-
|
|
272
|
-
-
|
|
273
|
-
-
|
|
272
|
+
### 生成要求(定稿原型=真码产物,不是手绘图)
|
|
273
|
+
入口文件保存到 \`{SPEC_ROOT}/changes/<change-name>/prototype-<名称>.html\`(目录不存在则先创建;该命名是 execute 注入契约,不改)。真码五要素:
|
|
274
|
+
- **与实现同方言**:用项目真实前端栈构建——组件库/设计 token/样式从项目源码或 node_modules 真实引入(例:React 项目挂真实组件而非仿制标签、Vue 项目用真实 SFC;例词非封闭清单),不手搓仿制样式
|
|
275
|
+
- **可复跑**:能被本项目工具链跑起来(dev server / 组件文档页 / 构建产物均可,入口 HTML 从本地 node_modules 引真实库也行)——先查 scan 文档与现有前端代码就近发现项目原型管线,已有则复用,没有则搭最小入口并留在项目惯例位置
|
|
276
|
+
- **高保真=组件即实现原料**:布局/组件/交互按真实效果呈现,execute 阶段直接对照搬组件,不经过"看图重新翻译"
|
|
277
|
+
- **手绘单文件(自造样式)仅限一次性粗选对比,不得作为定稿原型**(frontend-apple-style 实证:手绘定稿后每条意见都要反向翻译回真实代码);粗选定了方向,升级真码再收口
|
|
278
|
+
- **例外与降级**:项目本身即单文件 HTML 方言(纯静态站)时单文件即真码,不适用手绘限定;无法同方言真码化(如目标平台无 HTML 运行面)时显式声明降级与理由请用户裁决留痕——静默降级会被收口「UI 视觉证据」探针拦下
|
|
274
279
|
|
|
275
280
|
### 铁律 — 必须等待用户确认设计
|
|
276
281
|
- 展示完整设计方案后必须暂停等用户确认,**不要自己说"设计已充分确认"然后推进**(命令由 CLI 在下方注入)
|
|
277
|
-
-
|
|
282
|
+
- 原型生成、复用登记或跳过声明必须在暂停**之前**完成(复用登记=原型文件已按 \`prototype-<名称>.html\` 落在变更目录)——暂停是本步骤最后动作,用户确认后直接进入写 design.md,不会回头补
|
|
278
283
|
|
|
279
284
|
### 输出
|
|
280
|
-
完整设计方案 + 变更名 + 原型文件路径 或
|
|
285
|
+
完整设计方案 + 变更名 + 原型文件路径 或 复用声明(来源+覆盖面+登记后路径)或 跳过原因(三选一必填;跳过时写明分级依据,用户可当场否决要求补)
|
|
281
286
|
|
|
282
287
|
### 注意
|
|
283
288
|
- 不要一次输出大段文字,按模块/Phase 分段
|
|
@@ -531,9 +536,12 @@ status: passed | needs-user-input | blocked | skipped
|
|
|
531
536
|
按**复杂度/上下文轴**判断本次变更规模(2026-09-29-brainstorm-exit-thin-default:此前文件数轴「≤2 文件才 small」与选道表第 3 行的复杂度措辞不一致,且模糊需求探索完很少只碰 2 文件——出口几乎必然五阶段,实测坐实):
|
|
532
537
|
- **small(收编轻量道)**:单上下文可吞吐——预计无需 Wave 并行编排、无需上下文分片(单会话吃得下全部任务)、无多阶段治理/设计期人机对抗需求
|
|
533
538
|
- **large(完整五阶段)**:需要 Wave 并行编排、或单任务规模大需上下文分片、或多阶段治理、或用户显式要求完整流程
|
|
539
|
+
- **反判据(2026-10-14-scale-large-evidence,deploy-release-notes 实证:零建表定稿被早期印象判 large 走了五阶段)**:跨多端/多仓/多文件**不是** large 依据——轻量道支持多文件与跨仓交付;任务间**顺序依赖也不是**(B 等 A 的契约/产物正是轻量道任务循环的天然形态,一波等一波不构成 Wave)。Wave 证据=存在**可真并行**的任务簇(互相不等)**且单会话上下文吃不下**,两条同时成立才够 large
|
|
540
|
+
- **Wave 证据落盘**:判 large 时必须在 design.md(总体方案/Phase-Wave 处)写明——哪些任务簇可真并行、单会话上下文为何吃不下;**写不出 Wave 证据 = 改判 small**
|
|
541
|
+
- **按定稿方案重判**:Step 2 早期筛查只是路由粗判,方案在澄清中缩水(砍建表/砍迁移/砍端点)后必须按**定稿实质**重判——沿用早期筛查印象会把已缩水的方案误判厚(实证:plan 阶段自判单会话执行〔execution_mode=main〕反证 Wave 并非真需求)
|
|
534
542
|
- **拿不准 → small**(与 thin-default-flip 同哲学:预判反诱发误升厚——R16 实证;升厚留运行时证据:实测失败自动升厚+--upgrade-thick 用户决策)
|
|
535
543
|
在 design.md frontmatter 写入 \`scale: small\` 或 \`scale: large\`(frontmatter 不存在则补 \`author\`/\`created_at\`/\`scale\`)。规模决定下面的产物范围和实现路径。
|
|
536
|
-
(注:早期「加载项目上下文」步骤已做过一次粗判并可能建议过走 quick;此处基于 design
|
|
544
|
+
(注:早期「加载项目上下文」步骤已做过一次粗判并可能建议过走 quick;此处基于 design 方案实质做精判兜底——判据同轴,方案缩水后按定稿重判、早期印象不作数。)
|
|
537
545
|
|
|
538
546
|
### 操作
|
|
539
547
|
1. **按规模生成规范文件**:
|
package/src/stages/execute.js
CHANGED
|
@@ -1425,7 +1425,7 @@ ${worktreeNoticeSingle}
|
|
|
1425
1425
|
|
|
1426
1426
|
**主代理直写(execution_mode: main)——你直接实现,不派子代理。**
|
|
1427
1427
|
|
|
1428
|
-
逐任务闭环(串行,完成一个再下一个):读 task 卡(路径见下方任务摘要)→ 在 worktree 内实现(TDD
|
|
1428
|
+
逐任务闭环(串行,完成一个再下一个):读 task 卡(路径见下方任务摘要)→ 在 worktree 内实现(TDD 按层适用:纯逻辑先写测试再实现,行为契约断言用户可见行为,样式/纯配置不写——判据见「测试用例设计」)→ 跑该 task 卡 verify 命令 → \`sillyspec wt-commit --change <change-name> -- <task-files>\` 逐任务提交 → 手动勾选 tasks.md 对应 checkbox(完成=实现+测试绿+wt-commit 即勾,2026-09-26 Task Review 退役起勾选回归 agent 手动,同 thin 工作单元语义)→ 下一任务。
|
|
1429
1429
|
|
|
1430
1430
|
worktree 隔离 / 写入守卫(allowed_paths)/ verify 门禁全部保留——只换执行宿主,不换任何防线。
|
|
1431
1431
|
|