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/AGENTS.md +4 -3
- package/assets/command-cards/flow.md +8 -3
- package/package.json +1 -1
- package/src/change-list.js +43 -7
- 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 +90 -6
- package/src/flow-parity.js +14 -0
- package/src/flow-state.js +21 -0
- package/src/flow.js +503 -86
- package/src/fr-index.js +10 -1
- package/src/index.js +62 -5
- package/src/progress/change-registry.js +7 -0
- package/src/progress.js +83 -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/sentinel-assertions.js +13 -7
- package/src/stage-review.js +3 -0
- package/src/stages/brainstorm.js +19 -11
- package/src/stages/execute.js +1 -1
- package/src/stages/explore.js +3 -5
- package/src/stages/knowledge.js +22 -0
- package/src/stages/plan-postcheck.js +42 -7
- 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/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)
|
|
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
|
|
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 收编在途工作时此行漏透传——预览列整域、落盘只迁
|
|
@@ -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
|
-
|
|
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`)
|
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
|
*/
|