sillyspec 3.33.1 → 3.33.2
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/.claude/skills/sillyspec-flow/SKILL.md +17 -9
- package/AGENTS.md +2 -2
- package/README.md +2 -2
- package/assets/command-cards/flow.md +8 -4
- package/package.json +1 -1
- package/src/cross-repo-reconcile.js +4 -1
- package/src/decision-distill.js +22 -8
- package/src/decision-values.js +34 -0
- package/src/decisions-io.js +5 -2
- package/src/flow-draft.js +15 -9
- package/src/flow-parity.js +93 -5
- package/src/flow-review-pack.js +183 -0
- package/src/flow-review.js +45 -3
- package/src/flow.js +254 -47
- package/src/index.js +21 -14
- package/src/init.js +7 -30
- package/src/run/command.js +1 -1
- package/src/run/gate-snapshot.js +47 -14
- package/src/run/gates.js +1 -1
- package/src/scope-audit.js +6 -1
- package/src/stage-templates.js +3 -2
- package/src/stages/brainstorm.js +5 -4
- package/src/task-tick.js +22 -0
- package/templates/agents-instruction.md +2 -2
package/src/run/gate-snapshot.js
CHANGED
|
@@ -494,7 +494,7 @@ export function applyGateSnapshotCopy(cwd, snapshotRoot) {
|
|
|
494
494
|
* @param {{ cwd: string, files: string[] }} opts cwd=主仓根;files=本会话变更文件(仓库根相对 POSIX)
|
|
495
495
|
* @returns {{ snapshotRoot: string, cleanup: () => void, reason?: string }|null} 失败返回 null(调用方回退主仓)
|
|
496
496
|
*/
|
|
497
|
-
export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSmoke = false, mergeBase = null, runtimeRoot = null, failRef = null }) {
|
|
497
|
+
export function createGateSnapshot({ cwd, files, carriedFiles = [], sourceRoot = null, skipImportSmoke = false, mergeBase = null, runtimeRoot = null, failRef = null }) {
|
|
498
498
|
// failRef 可选出参(坑 snapshot-fallback-reason-blind,2026-10-16 下游实证):所有失败分支
|
|
499
499
|
// 把结构化原因写进 failRef.reason——调用方(verify --done 实测门 / noAI 质量扫描)消费它
|
|
500
500
|
// 替换硬编码笼统文案,降级原因可直读(pnpm 布局拒建 ≠ 文件集为空 ≠ junction 失败)。
|
|
@@ -543,7 +543,6 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
543
543
|
// 是共享运行时面:.runtime/(sqlite 锁/在途 marker)与 quicklog/(跨会话共享账本);
|
|
544
544
|
// local.yaml 由下方 cfg 段显式复制(单一来源,不经 overlay)。
|
|
545
545
|
const overlayRoot = sourceRoot || cwd
|
|
546
|
-
let overlaid = 0
|
|
547
546
|
// 双写一致性(2026-09-20 红线机检变更实证:主 agent 在主仓直写而 execute 已建 worktree 时,
|
|
548
547
|
// overlay 源=worktree 拿陈旧内容盖掉快照 HEAD 的新版——快照内 import 找不到新导出→门禁假红)。
|
|
549
548
|
// 祖先判据(fr-index-l2 二次实证升级):基线优先取 merge-base 的每文件祖先内容
|
|
@@ -568,9 +567,12 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
568
567
|
}
|
|
569
568
|
try { return cmpText(readFileSync(join(snapshotRoot, rel))) } catch { return null }
|
|
570
569
|
}
|
|
571
|
-
|
|
570
|
+
// 单文件 overlay(判定面 files 与携带面 carriedFiles 共用,2026-10-20-gate-snapshot-
|
|
571
|
+
// carried-deps 提取):源=overlayRoot;worktree 模式经三态判定(陈旧取主仓/正常取
|
|
572
|
+
// worktree/分叉取 worktree 血统 M2);源侧不存在返回 false(已删/不在场不拷)。
|
|
573
|
+
const overlayOne = (f) => {
|
|
572
574
|
const isSillyspecRuntime = typeof f === 'string' && (f.startsWith('.sillyspec/.runtime/') || f.startsWith('.sillyspec/quicklog/') || f === '.sillyspec/local.yaml' || f === '.sillyspec/.sillyspec-platform.json')
|
|
573
|
-
if (typeof f !== 'string' || f.includes('..') || f.startsWith('/') || isSillyspecRuntime)
|
|
575
|
+
if (typeof f !== 'string' || f.includes('..') || f.startsWith('/') || isSillyspecRuntime) return false
|
|
574
576
|
let src = join(overlayRoot, f)
|
|
575
577
|
if (sourceRoot && sourceRoot !== cwd) {
|
|
576
578
|
const wtPath = join(sourceRoot, f), cwdPath = join(cwd, f)
|
|
@@ -595,11 +597,26 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
595
597
|
}
|
|
596
598
|
}
|
|
597
599
|
}
|
|
598
|
-
if (!existsSync(src))
|
|
600
|
+
if (!existsSync(src)) return false
|
|
599
601
|
const dst = join(snapshotRoot, f)
|
|
600
602
|
mkdirSync(dirname(dst), { recursive: true })
|
|
601
603
|
copyFileSync(src, dst)
|
|
602
|
-
|
|
604
|
+
return true
|
|
605
|
+
}
|
|
606
|
+
let overlaid = 0
|
|
607
|
+
for (const f of files) { if (overlayOne(f)) overlaid++ }
|
|
608
|
+
|
|
609
|
+
// 携带面 overlay(2026-10-20-gate-snapshot-carried-deps,坑 verify-gate-…-three-defects
|
|
610
|
+
// 缺陷二):他者声明文件从判定面剔除但物理携带进快照——快照复现本变更实测时真实的依赖
|
|
611
|
+
// 环境(worktree 模式≈worktree 自洽态;in-place≈主仓混合树)。坑文场景:本变更文件 import
|
|
612
|
+
// 他者未提交导出(quick 的 stripPathPrefix)→ 原快照装 HEAD 旧版必 TypeError 假红,携带后
|
|
613
|
+
// 自洽。判定面(files/overlaid)不含携带件——归属语义不变,假红可经控制台清单归因到他者。
|
|
614
|
+
const carriedList = []
|
|
615
|
+
if (Array.isArray(carriedFiles) && carriedFiles.length > 0) {
|
|
616
|
+
for (const f of carriedFiles) {
|
|
617
|
+
if (files.includes(f)) continue // 判定面已含(own 声明优先级高于他者)——不重复拷
|
|
618
|
+
if (overlayOne(f)) carriedList.push(f)
|
|
619
|
+
}
|
|
603
620
|
}
|
|
604
621
|
|
|
605
622
|
// 环境目录链接(坑 gate-snapshot-env-mismatch,2026-09-12 驾驭第十三批③,用户实证:
|
|
@@ -706,7 +723,7 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
706
723
|
// HEAD 健版保证 import 图闭合。新增文件(HEAD 无版)无法回退 → 醒目 warn 保留。
|
|
707
724
|
// 全链 fail-open:找不到解释器/基建异常只 warn 不作废快照(主场景是 Python 项目,
|
|
708
725
|
// JS/TS 的 vitest load 冒烟成本高留后续)。
|
|
709
|
-
const overlaidPyFiles = files.filter(f => typeof f === 'string' && f.endsWith('.py')
|
|
726
|
+
const overlaidPyFiles = [...files, ...carriedList].filter(f => typeof f === 'string' && f.endsWith('.py')
|
|
710
727
|
&& !f.startsWith('.sillyspec/')
|
|
711
728
|
&& existsSync(join(snapshotRoot, f)))
|
|
712
729
|
if (overlaidPyFiles.length > 0 && !skipImportSmoke) {
|
|
@@ -727,7 +744,7 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
727
744
|
if (dirRemoved && worktreeCleaned) unregisterGateSnapshot({ runtimeRoot, snapshotRoot })
|
|
728
745
|
} catch { /* 双失败已由 cleanupSnapshot 吞掉;条目保留 */ }
|
|
729
746
|
}
|
|
730
|
-
return { snapshotRoot, cleanup, overlaid, pinnedCacheLinks }
|
|
747
|
+
return { snapshotRoot, cleanup, overlaid, pinnedCacheLinks, carriedFiles: carriedList, carried: carriedList.length }
|
|
731
748
|
} catch (e) {
|
|
732
749
|
// 基建失败(worktree add 拒绝/磁盘满/…):尽力清理后回退主仓;销账同走双清确认
|
|
733
750
|
// (D-004@v2:清不掉的条目保留待回收——它不是悬空条目,是待回收记录)。
|
|
@@ -866,8 +883,15 @@ export function envDirsLinked(cwd, snapshotRoot) {
|
|
|
866
883
|
*/
|
|
867
884
|
export function printSnapshotFailureHint(snapInfo, { offEnv = 'SILLYSPEC_VERIFY_GATE_SNAPSHOT_OFF' } = {}) {
|
|
868
885
|
if (!snapInfo || !snapInfo.snapshotRoot) return
|
|
869
|
-
console.error(` 🔬 本次实测执行于隔离快照:${snapInfo.snapshotRoot}(HEAD + 本变更 ${snapInfo.changeFileCount ?? '?'} 个文件${snapInfo.sourceRoot ? ',overlay 自 worktree' : ''})`)
|
|
870
|
-
|
|
886
|
+
console.error(` 🔬 本次实测执行于隔离快照:${snapInfo.snapshotRoot}(HEAD + 本变更 ${snapInfo.changeFileCount ?? '?'} 个文件${snapInfo.sourceRoot ? ',overlay 自 worktree' : ''}${snapInfo.carriedFiles && snapInfo.carriedFiles.length > 0 ? `,携带 ${snapInfo.carriedFiles.length} 个他者声明文件(依赖自洽面)` : ''})`)
|
|
887
|
+
// 2026-10-20-gate-snapshot-carried-deps 评审 F3 清偿:携带面在场时他者文件**在**疑点内
|
|
888
|
+
// (原「并行污染不在疑点内」只对未声明脏文件成立——携带件是声明过的他者内容)。
|
|
889
|
+
if (snapInfo.carriedFiles && snapInfo.carriedFiles.length > 0) {
|
|
890
|
+
const preview = snapInfo.carriedFiles.slice(0, 5).join('、')
|
|
891
|
+
console.error(` 失败疑点排查顺序:① 本变更文件自身的真实失败(最常见)→ 修代码;② 携带的他者声明文件坏半态(${snapInfo.carriedFiles.length > 5 ? `${preview} 等` : preview}——他者变更在途内容,非本变更面)→ 核对他者变更状态或 SNAPSHOT_OFF 对照;③ 快照环境差异(monorepo 依赖布局/环境目录链接缺失)→ 设 ${offEnv}=1 回退主仓复跑对照——主仓过而快照挂即环境问题。`)
|
|
892
|
+
} else {
|
|
893
|
+
console.error(` 失败疑点排查顺序:① 本变更文件自身的真实失败(最常见)→ 修代码;② 快照环境差异(monorepo 依赖布局/环境目录链接缺失)→ 设 ${offEnv}=1 回退主仓复跑对照——主仓过而快照挂即环境问题;③ 并行污染已被快照隔离,不在疑点内。`)
|
|
894
|
+
}
|
|
871
895
|
}
|
|
872
896
|
|
|
873
897
|
/**
|
|
@@ -875,8 +899,9 @@ export function printSnapshotFailureHint(snapInfo, { offEnv = 'SILLYSPEC_VERIFY_
|
|
|
875
899
|
* main 工作区,多会话并发任何人的 WIP 都能弄红别人的门)。等价「--worktree 定向跑」:
|
|
876
900
|
* overlay 文件集 = resolveVerifyChangedFiles(worktree-aware:含 merge-base 补齐与
|
|
877
901
|
* working-tree),overlay 源 = 本变更 worktree 根(meta 感知;in-place 退主仓 cwd)——
|
|
878
|
-
*
|
|
879
|
-
*
|
|
902
|
+
* 并行会话**未声明**的在途脏文件物理不进快照(隔离核心);他者**显式声明**的文件以携带面
|
|
903
|
+
* 物理进快照(2026-10-20-gate-snapshot-carried-deps:依赖自洽——判定/归因面仍剔除)。
|
|
904
|
+
* 变更文档(changes/<name>/**,test_strategy 的 module-impact.md 等消费)从主仓 specBase 随快照复制。
|
|
880
905
|
* @param {{ cwd: string, changeName: string, specBase: string, platformOpts?: object }} opts
|
|
881
906
|
* @returns {Promise<{snapshotRoot: string, cleanup: () => void, overlaid: number, changeFileCount: number}|null>}
|
|
882
907
|
*/
|
|
@@ -905,11 +930,15 @@ export async function createVerifyGateSnapshot({ cwd, changeName, specBase, plat
|
|
|
905
930
|
}
|
|
906
931
|
// 他者声明归属过滤(坑 verify-reconcile-foreign-wip 同款):in-place 模式 working-tree
|
|
907
932
|
// 并入后主仓全部在途文件进场——他者活跃变更显式声明的文件(quick --files / 他者 design
|
|
908
|
-
//
|
|
933
|
+
// 清单)从**判定面**剔除,只留本变更相关面(无主文件保留,fail-closed 口径与 probe6 一致)。
|
|
934
|
+
// 2026-10-20-gate-snapshot-carried-deps(坑 verify-gate-…-three-defects 缺陷二):被剔
|
|
935
|
+
// 文件改以「携带面」物理进快照(carriedFiles)——快照依赖自洽,判定/归属语义不变。
|
|
936
|
+
let carriedForeignFiles = []
|
|
909
937
|
try {
|
|
910
938
|
const { foreign } = splitOwnVsForeignDiffFiles(cwd, changeName, changeFiles, { specBase, runtimeRoot: verifyRuntimeRoot })
|
|
911
939
|
if (foreign.length > 0) {
|
|
912
940
|
const foreignSet = new Set(foreign.map(x => x.file))
|
|
941
|
+
carriedForeignFiles = changeFiles.filter(f => foreignSet.has(f))
|
|
913
942
|
changeFiles = changeFiles.filter(f => !foreignSet.has(f))
|
|
914
943
|
if (changeFiles.length === 0) {
|
|
915
944
|
setFail(`变更文件集为空(${foreign.length} 个 diff 文件全部被他者声明过滤——并行会话声明面覆盖了本变更全部在途文件)`)
|
|
@@ -943,12 +972,16 @@ export async function createVerifyGateSnapshot({ cwd, changeName, specBase, plat
|
|
|
943
972
|
}
|
|
944
973
|
} catch { /* meta 读取失败 → 主仓 cwd 源(files 已含本变更 working-tree 改动) */ }
|
|
945
974
|
|
|
946
|
-
const snap = createGateSnapshot({ cwd, files: changeFiles, sourceRoot, mergeBase: snapMergeBase, runtimeRoot: verifyRuntimeRoot, failRef })
|
|
975
|
+
const snap = createGateSnapshot({ cwd, files: changeFiles, carriedFiles: carriedForeignFiles, sourceRoot, mergeBase: snapMergeBase, runtimeRoot: verifyRuntimeRoot, failRef })
|
|
947
976
|
if (!snap) {
|
|
948
977
|
// createGateSnapshot 已把具体原因写进同一 failRef;无 reason(旧形态兜底)补笼统口径
|
|
949
978
|
if (failRef && typeof failRef === 'object' && !failRef.reason) setFail('快照基建不可用(createGateSnapshot 返回 null,无更细原因)')
|
|
950
979
|
return null
|
|
951
980
|
}
|
|
981
|
+
if (snap.carriedFiles && snap.carriedFiles.length > 0) {
|
|
982
|
+
const preview = snap.carriedFiles.slice(0, 5).join('、')
|
|
983
|
+
console.log(`📦 快照携带 ${snap.carriedFiles.length} 个他者声明文件(依赖自洽面,不进判定/归因面;假红先核对他者变更)${snap.carriedFiles.length > 5 ? `:${preview} 等` : `:${preview}`}`)
|
|
984
|
+
}
|
|
952
985
|
|
|
953
986
|
// 变更文档随快照:module-impact.md(test_strategy=evidence-auto 消费)/tasks.md 等
|
|
954
987
|
// 在 .sillyspec(gitignore)不进 HEAD,从主仓 specBase 复制 changes/<name>/**
|
package/src/run/gates.js
CHANGED
|
@@ -1108,7 +1108,7 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1108
1108
|
snapshotFailRef = {}
|
|
1109
1109
|
verifyGateSnap = await createVerifyGateSnapshot({ cwd, changeName, specBase, platformOpts, failRef: snapshotFailRef })
|
|
1110
1110
|
if (verifyGateSnap) {
|
|
1111
|
-
console.log(`🧪 Verify 实测隔离快照(根:${verifyGateSnap.snapshotRoot}):HEAD + 本变更 ${verifyGateSnap.changeFileCount} 个文件${verifyGateSnap.sourceRoot ? '(overlay 自 worktree——本变更分支内容定向跑)' : ''}
|
|
1111
|
+
console.log(`🧪 Verify 实测隔离快照(根:${verifyGateSnap.snapshotRoot}):HEAD + 本变更 ${verifyGateSnap.changeFileCount} 个文件${verifyGateSnap.sourceRoot ? '(overlay 自 worktree——本变更分支内容定向跑)' : ''}${verifyGateSnap.carriedFiles && verifyGateSnap.carriedFiles.length > 0 ? ` + 携带 ${verifyGateSnap.carriedFiles.length} 个他者声明文件(依赖自洽,不进判定面)` : ''}(并行会话未声明脏文件不参与门判定)`)
|
|
1112
1112
|
} else {
|
|
1113
1113
|
// FR-02(2026-10-09-verify-reuse-friction):null 回退与异常回退同等可见 + 记账;
|
|
1114
1114
|
// FR-04(2026-10-16-fix-downstream-tool-defects):reason 消费 failRef 结构化原因
|
package/src/scope-audit.js
CHANGED
|
@@ -41,6 +41,7 @@ import { loadSpanRiskPatternsAllProjects } from './span-risk-surface.js'
|
|
|
41
41
|
import { safeGit } from './git-helper.js'
|
|
42
42
|
import { parseFileChangeListDetailed, pathMatches } from './change-list.js'
|
|
43
43
|
import { classifyToolScaffold, filterDeliverableFiles } from './worktree-apply.js'
|
|
44
|
+
import { isToolGovernancePath } from './flow-parity.js'
|
|
44
45
|
import { parseRepoRegistry } from './stages/plan-postcheck.js'
|
|
45
46
|
// 跨仓 per-repo 采集共享内核(2026-09-20 scope-audit-cross-repo task-02 消费 task-01 产物)。
|
|
46
47
|
// 无环确认:cross-repo-reconcile 的静态依赖链(git-helper/plan-postcheck/worktree-apply/
|
|
@@ -1373,7 +1374,11 @@ export function reconcileCrossRepoPlan({ cwd, specBase, runtimeRoot, changeName,
|
|
|
1373
1374
|
}
|
|
1374
1375
|
// 真实三态:该仓 actual(filterDeliverableFiles 过滤,主仓口径)× 该组声明面 pathMatches
|
|
1375
1376
|
// 双向容差(glob/目录前缀兼容,design 清单可写 glob)→ planned / unplanned / untouched
|
|
1376
|
-
|
|
1377
|
+
// 工具治理模板同源剔除(2026-10-19-cross-repo-face-tool-filter):跨仓面(rows/totals/
|
|
1378
|
+
// patch)与主仓冻结面同口径,不让 .claude/.codex 等注入面刷屏跨仓交付。
|
|
1379
|
+
const repoFiles = [...new Set(filterDeliverableFiles(repoActual.files).filter(Boolean))]
|
|
1380
|
+
.filter((f) => !isToolGovernancePath(f))
|
|
1381
|
+
.map(toPosix).sort()
|
|
1377
1382
|
const repoStats = collectNumstatByPath(repoActual.repoPath, repoFiles, { baseRef: repoActual.anchor.base })
|
|
1378
1383
|
const repoMatched = new Set()
|
|
1379
1384
|
for (const f of repoFiles) {
|
package/src/stage-templates.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
18
18
|
import { join } from 'path'
|
|
19
|
+
import { DECISION_STATUSES, DECISION_TYPES } from './decision-values.js'
|
|
19
20
|
|
|
20
21
|
/** brainstorm 四件套产物格式模板(自 stages/brainstorm.js「生成规范文件」步逐字迁移——
|
|
21
22
|
* 与 prompt 指针版本同源;decisions 九字段与可选字段说明一并迁入)。 */
|
|
@@ -110,9 +111,9 @@ created_at: <now-datetime>
|
|
|
110
111
|
# 决策记录(Decisions)
|
|
111
112
|
|
|
112
113
|
## D-001@v1: 决策短标题
|
|
113
|
-
- type:
|
|
114
|
+
- type: ${DECISION_TYPES.join(' | ')}
|
|
114
115
|
- priority: P0 | P1 | P2
|
|
115
|
-
- status:
|
|
116
|
+
- status: ${DECISION_STATUSES.join(' | ')}
|
|
116
117
|
- supersedes:
|
|
117
118
|
- source: user | code | docs
|
|
118
119
|
- question: 被解决的问题
|
package/src/stages/brainstorm.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { REVIEW_CHECKLISTS } from '../stage-review-checklist.js'
|
|
2
|
+
import { DECISION_STATUSES, DECISION_TYPES } from '../decision-values.js'
|
|
2
3
|
|
|
3
4
|
export const definition = {
|
|
4
5
|
name: 'brainstorm',
|
|
@@ -192,9 +193,9 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句 10-30 字中文
|
|
|
192
193
|
|
|
193
194
|
\`\`\`markdown
|
|
194
195
|
## D-001@v1: <短标题>
|
|
195
|
-
- type:
|
|
196
|
+
- type: ${DECISION_TYPES.join(' | ')}
|
|
196
197
|
- priority: P0 | P1 | P2
|
|
197
|
-
- status:
|
|
198
|
+
- status: ${DECISION_STATUSES.join(' | ')}
|
|
198
199
|
- source: user | code | docs
|
|
199
200
|
- question: <被解决的问题>
|
|
200
201
|
- answer: <用户确认或代码查证结果>
|
|
@@ -525,9 +526,9 @@ ${REVIEW_CHECKLISTS.brainstorm.slice(3).map((item) => '- ' + item).join('\n')}
|
|
|
525
526
|
### decisions.md 版本规则
|
|
526
527
|
\`\`\`markdown
|
|
527
528
|
## D-001@v2: 缓存异常时的 fallback 语义
|
|
528
|
-
- type:
|
|
529
|
+
- type: ${DECISION_TYPES.join(' | ')}
|
|
529
530
|
- priority: P0 | P1 | P2
|
|
530
|
-
- status:
|
|
531
|
+
- status: ${DECISION_STATUSES.join(' | ')}
|
|
531
532
|
- supersedes: D-001@v1
|
|
532
533
|
- source: design-grill
|
|
533
534
|
- question: §3 数据流与 §7 容错策略冲突时以哪个为准?
|
package/src/task-tick.js
CHANGED
|
@@ -190,6 +190,28 @@ export async function runTaskTick({ changeName, cwd, taskId, specBase, evidence
|
|
|
190
190
|
}
|
|
191
191
|
} catch { /* 教学面 best-effort:失败不影响翻格 */ }
|
|
192
192
|
}
|
|
193
|
+
// 顺序正典现场提醒(2026-10-19-approve-gate-tick-nudge FR-04,advisory):勾选是「正在干活」
|
|
194
|
+
// 的最高频时刻——spec 面未齐或未过断点① 时给一行警示(不阻断、不改既有行;读取失败静默,
|
|
195
|
+
// 回显退化为现状)。判定与 flow status 同源(designSectionsFilled 单一源)。
|
|
196
|
+
try {
|
|
197
|
+
const dText = readFileSync(join(changesRoot, changeName, 'design.md'), 'utf8')
|
|
198
|
+
const { designSectionsFilled } = await import('./flow-review.js')
|
|
199
|
+
const unanswered = !designSectionsFilled(dText)
|
|
200
|
+
const fsText = readFileSync(join(changesRoot, changeName, 'flow-state.yaml'), 'utf8')
|
|
201
|
+
const unapproved = !/^spec_approved:\s*true/m.test(fsText) && !/^autopilot:\s*true/m.test(fsText)
|
|
202
|
+
if (unanswered || unapproved) {
|
|
203
|
+
const parts = []
|
|
204
|
+
if (unanswered) parts.push('design 四节未答')
|
|
205
|
+
if (unapproved) parts.push('未过断点①(flow approve)')
|
|
206
|
+
console.log(`⚠️ spec 面未就位(${parts.join('、')})——顺序正典:先答 spec 面并过批准,再继续勾任务`)
|
|
207
|
+
}
|
|
208
|
+
} catch { /* 提醒 best-effort:读取失败静默,不影响勾选回显与 exit code */ }
|
|
209
|
+
// 执行期用户决策落盘提示(2026-10-20-thin-user-decision-record,advisory):tick 是执行期
|
|
210
|
+
// 对 agent 最高频触点——decisions.md 尚未落盘时提一句(用户中途给了实现影响方向可当场
|
|
211
|
+
// decisions add 记录;缺席合法,纯提示不阻断;存在性判定即时读盘,在场分支零新增行)。
|
|
212
|
+
if (!existsSync(join(changesRoot, changeName, 'decisions.md'))) {
|
|
213
|
+
console.log(`💡 本变更 decisions.md 尚未落盘——执行期用户给过实现影响方向的话,当场 sillyspec decisions add --change ${changeName} --id D-001 --title "…" 记录(收口随蒸馏进知识防复潮;纯提示不阻断)`)
|
|
214
|
+
}
|
|
193
215
|
return r
|
|
194
216
|
}
|
|
195
217
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
## 选道(动手前先对号入座)
|
|
7
7
|
| 需求形态 | 走法 |
|
|
8
8
|
|---|---|
|
|
9
|
-
| 需求已含决策(改什么、成功标准说得清——成功标准须来自**用户原话或既有文档转写**;要靠你推断拟出的标准=需求不清晰) | **轻量变更(默认快道)**:`flow start` →
|
|
9
|
+
| 需求已含决策(改什么、成功标准说得清——成功标准须来自**用户原话或既有文档转写**;要靠你推断拟出的标准=需求不清晰) | **轻量变更(默认快道)**:`flow start` → 补 spec 面(FR 绑定+design 四节)→ 批准(`flow approve` / 用户显式 `--autopilot`)→ 干活 → `flow done`(协议调用仍 2 次,批准由用户运行;操作细节见 `/sillyspec:flow` skill 与 flow start 输出) |
|
|
10
10
|
| 用户显式要求极速(「极速/别验证/只要 spec 和代码」类原话)且需求已含决策 | **轻量变更极速版**:同上行加 `--turbo`(`flow start --change <名> --input "<...>" --turbo`)——**仅用户显式触发,禁止你自行启用**;收口跳过实测与重仪式门(hunk/dirty/ui 探针/评审),spec 结构校验/tasks 全勾/ledger/patch 冻结/distill/归档照常(跳的是验证不是证据);升厚即失效恢复全量仪式;收口输出显式标注「不含测试客观核验」。预段组合极速:头脑风暴预段走轻量跑判定+单轮合并问询+burst 一次 --done,收编时按用户要求带 --turbo |
|
|
11
11
|
| 需求不清晰 / 需要方案探索(含「成功标准要你替用户拟」的形态——自己拟标准再自答是把默认共识伪装成共识) | 头脑风暴预段:`run brainstorm --change <名>` → 完成后 `flow start --change <名>` 收编续跑(design 以头脑风暴版为准)。上个轻量变更被 route-hindsight 标记时全新 start 会被同意门拦下要求显式选道:确认已含决策带 `--no-prestage` 声明留痕,否则先走本行预段 |
|
|
12
12
|
| 大改动:跨模块取舍 / Wave 计划编排 / 多阶段治理 / 设计期人机对抗 | 完整流程五阶段:`run brainstorm → plan → execute → verify → archive`(每阶段一次渲染 + 一次 --done 收口;细节见各阶段 skill 与 CLI 输出) |
|
|
@@ -31,7 +31,7 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句 10-30 字中文
|
|
|
31
31
|
## 核心规则
|
|
32
32
|
1. **禁止绕过本文件规则和 SillySpec 流程**。所有变更走 sillyspec 流程,不裸改裸提交。
|
|
33
33
|
2. **改代码前必须先说明依据**——依据的文档路径(design.md / 模块文档 / file-lifecycle.md(如有))或现有代码依据,无依据不改。
|
|
34
|
-
3. **执行顺序**:文档 → 读代码 → 写测试 → 写实现 → 跑测试 → 验收 →
|
|
34
|
+
3. **执行顺序**:文档 → 读代码 → 写测试 → 写实现 → 跑测试 → 验收 → 更新文档(轻量道即:FR 绑定+design 四节+用户批准先于动码,读代码探究不算动码)。测试按层适用:纯逻辑先写测试,行为契约断言用户可见行为,视觉样式与纯配置/文档不写测试(记「不适用:理由」,验收靠规范与人工)。
|
|
35
35
|
4. **实证核验再收口**:触及 `src`/`test` 的改动,CLI 会在收口时亲自实测(实测失败阻断收口;纯 doc/配置自动跳过)。测试面按变更动态推断(本变更测试 ∪ FR 关联回归 ∪ import 依赖,runner 自项目结构推断——无需配置);lint 走 `.sillyspec/local.yaml` 的 `commands.lint`。以落盘文件与测试结果为准,不信口头"已完成"。
|
|
36
36
|
5. **非测试逻辑本身有误时,禁止改测试来"通过"**——修逻辑,不修测试。
|
|
37
37
|
6. **git hook 拦截提交时禁止跳过**(如 `.husky/pre-push`),修复问题后再提交。
|