sillyspec 3.25.5 → 3.25.6
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-brainstorm/SKILL.md +1 -1
- package/.claude/skills/sillyspec-scan/SKILL.md +12 -7
- package/.claude/skills/sillyspec-verify/SKILL.md +2 -2
- package/package.json +1 -1
- package/src/change-list.js +100 -12
- package/src/constants.js +11 -0
- package/src/index.js +1 -1
- package/src/run/command.js +57 -2
- package/src/run/complete-handlers.js +5 -1
- package/src/run/complete.js +20 -1
- package/src/run/gates.js +37 -0
- package/src/run/quick-audit.js +8 -1
- package/src/run/scan-profile.js +29 -10
- package/src/run/shared.js +161 -4
- package/src/run/stage.js +4 -0
- package/src/scan-postcheck.js +25 -11
- package/src/stage-contract.js +6 -1
- package/src/stage-review.js +10 -4
- package/src/stages/brainstorm.js +9 -14
- package/src/stages/plan-postcheck.js +1 -1
- package/src/stages/plan.js +6 -0
- package/src/stages/quick.js +31 -31
- package/src/stages/scan.js +2 -1
- package/src/stages/verify.js +6 -3
- package/src/task-review.js +159 -0
- package/src/verify-postcheck.js +148 -3
- package/src/worktree-apply.js +34 -29
- package/templates/prompts/verify-probes.md +13 -2
package/src/run/shared.js
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* - triggerSync 的动态 import './sync.js' → '../sync.js'(src/sync.js)
|
|
10
10
|
* - safeGit 原用顶层 require('child_process'),改静态 import execSync(更 ESM-native)
|
|
11
11
|
*/
|
|
12
|
-
import { basename, join, resolve, dirname } from 'node:path'
|
|
13
|
-
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
12
|
+
import { basename, join, resolve, dirname, sep } from 'node:path'
|
|
13
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
14
14
|
import { execSync } from 'node:child_process'
|
|
15
15
|
import { fileURLToPath } from 'node:url'
|
|
16
16
|
|
|
@@ -104,6 +104,156 @@ export function resolveSpecDir(cwd, opts = {}) {
|
|
|
104
104
|
return join(resolve(cwd), '.sillyspec')
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* 枚举 cwd 祖先链(含自身)上所有 .sillyspec 目录的绝对路径,上界 = git root。
|
|
109
|
+
*
|
|
110
|
+
* 返回顺序:从 cwd 最近开始向上到 git root(含两端命中项)。单实例项目任意子目录恒返回 [自身]。
|
|
111
|
+
*
|
|
112
|
+
* 上界 = git root(monorepo 天然边界)。必须有限上界:一路数到文件系统根会撞 home 等
|
|
113
|
+
* 无关祖先的孤立 .sillyspec(home 下任何项目都被误报多实例)。非 git 仓库:不向上数,
|
|
114
|
+
* 只看 cwd 自身(≤1,永不误报)。
|
|
115
|
+
*
|
|
116
|
+
* 被 countAncestorSpecDirs(漂移提醒 warn)与 locateQuickSessionGuard(quick 漂移 fail-fast
|
|
117
|
+
* 守卫,坑 quick-cwd-drift-splits-specdir)共用 —— 单一真相源,避免两处各自重写祖先枚举漂移。
|
|
118
|
+
*/
|
|
119
|
+
export function ancestorSpecDirs(cwd) {
|
|
120
|
+
const resolved = resolve(cwd)
|
|
121
|
+
// 上界 = git root。但 linked worktree 内 --show-toplevel 返回 worktree 根(非主仓根),
|
|
122
|
+
// 祖先链到不了主仓 .sillyspec → 漂移提醒/quick 守卫全哑(坑 worktree-execute-spec-drift)。
|
|
123
|
+
// 复刻 worktree.js _resolveMainRepoRoot:--git-common-dir 的 dirname 才是主仓根,取更靠上者作 ceiling。
|
|
124
|
+
// (git 可能返回相对路径,须 resolve(cwd, commonDir) 绝对化,否则相对 process.cwd 误解析。)
|
|
125
|
+
const topLevel = safeGit(resolved, ['rev-parse', '--show-toplevel']).value
|
|
126
|
+
let ceiling = topLevel ? resolve(topLevel) : null
|
|
127
|
+
const commonDir = safeGit(resolved, ['rev-parse', '--git-common-dir']).value
|
|
128
|
+
if (commonDir) {
|
|
129
|
+
const absCommonDir = resolve(resolved, commonDir)
|
|
130
|
+
if (existsSync(absCommonDir) && statSync(absCommonDir).isDirectory()) {
|
|
131
|
+
const mainRoot = dirname(absCommonDir)
|
|
132
|
+
// 主仓根是 worktree 根的祖先时取它(更靠上);monorepo/单仓两者相等不替换,行为不变
|
|
133
|
+
if (!ceiling || resolve(ceiling).startsWith(resolve(mainRoot) + sep)) ceiling = mainRoot
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const dirs = []
|
|
137
|
+
let dir = resolved
|
|
138
|
+
while (true) {
|
|
139
|
+
if (existsSync(join(dir, '.sillyspec'))) dirs.push(join(dir, '.sillyspec'))
|
|
140
|
+
if (!ceiling || resolve(dir) === resolve(ceiling)) break
|
|
141
|
+
const parent = dirname(dir)
|
|
142
|
+
if (parent === dir) break
|
|
143
|
+
dir = parent
|
|
144
|
+
}
|
|
145
|
+
return dirs
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 统计 cwd 祖先链上有多少个 .sillyspec 目录(= ancestorSpecDirs(cwd).length)。
|
|
150
|
+
*
|
|
151
|
+
* ≥2 个 = monorepo 多实例(子项目独立 .sillyspec + 根 .sillyspec 并存),resolveSpecDir 命中的
|
|
152
|
+
* 「最近」实例未必是用户意图的项目(如 cd 进被独立 scan 的子项目跑测试后忘回根)→ 漂移风险。
|
|
153
|
+
* 单实例项目在任意子目录工作恒返回 1,不误报。
|
|
154
|
+
*/
|
|
155
|
+
export function countAncestorSpecDirs(cwd) {
|
|
156
|
+
return ancestorSpecDirs(cwd).length
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 在 cwd 祖先链的各 specBase 中查找 quick 会话 guard.json(坑 quick-cwd-drift-splits-specdir)。
|
|
161
|
+
*
|
|
162
|
+
* sessionId(quick-<uuid8>)全局唯一,但 guard.json 按 specBase 分散落盘
|
|
163
|
+
* (<specBase>/.runtime/quick-sessions/<sessionId>/guard.json)。cd 漂移到子项目后,当前 specBase
|
|
164
|
+
* 下读不到本 session 的 guard,但祖先链别处的 specBase(创建 session 的根)仍有它 → 据此定位
|
|
165
|
+
* session 真正归属,供 detectQuickSessionDrift 的 fail-fast 守卫判断。
|
|
166
|
+
*
|
|
167
|
+
* 遍历顺序 = ancestorSpecDirs(cwd 最近 → git root),返回第一个命中。
|
|
168
|
+
*
|
|
169
|
+
* @param {string} cwd
|
|
170
|
+
* @param {string} sessionId - quick-<uuid8>
|
|
171
|
+
* @returns {{ specBase: string, guard: object } | null}
|
|
172
|
+
*/
|
|
173
|
+
export function locateQuickSessionGuard(cwd, sessionId) {
|
|
174
|
+
if (!sessionId) return null
|
|
175
|
+
for (const specBase of ancestorSpecDirs(cwd)) {
|
|
176
|
+
const guardFile = join(specBase, '.runtime', 'quick-sessions', sessionId, 'guard.json')
|
|
177
|
+
if (existsSync(guardFile)) {
|
|
178
|
+
try {
|
|
179
|
+
const guard = JSON.parse(readFileSync(guardFile, 'utf8'))
|
|
180
|
+
return { specBase, guard }
|
|
181
|
+
} catch {
|
|
182
|
+
// guard.json 损坏:当作不存在,继续找下一个(不阻断主流程)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 检测 quick 会话是否跨 specDir 漂移(坑 quick-cwd-drift-splits-specdir)。
|
|
191
|
+
*
|
|
192
|
+
* quick 被 validateChangeExists 的 sessionId 豁免(quick-<8hex> 不在 changes/ 下),故 quick
|
|
193
|
+
* 漂移除 countAncestorSpecDirs 的 warn 外无硬守卫 → 无声分裂(progress/artifact/QUICKLOG 落
|
|
194
|
+
* 子项目、根 session 停滞)。本函数补这个口:
|
|
195
|
+
* - 当前 specBase 已有本 session guard → null(同一 specDir,无漂移)
|
|
196
|
+
* - 当前 specBase 没有,但祖先链别处有同 sessionId guard → 判漂移,返回引导信息供调用方 fail-fast
|
|
197
|
+
* - 别处也没有 → null(真·新会话首次启动,guard 尚未建,放行 —— 不误伤子项目主动启新 quick)
|
|
198
|
+
*
|
|
199
|
+
* 风格仿 validateChangeExists:null = 通过,对象 = 失败(含 message)。
|
|
200
|
+
*
|
|
201
|
+
* @param {string} cwd
|
|
202
|
+
* @param {string} currentSpecBase - 当前命中的 specBase
|
|
203
|
+
* @param {string} sessionId - quick-<uuid8>
|
|
204
|
+
* @returns {{ realSpecBase: string, message: string } | null}
|
|
205
|
+
*/
|
|
206
|
+
export function detectQuickSessionDrift(cwd, currentSpecBase, sessionId) {
|
|
207
|
+
if (!sessionId || !currentSpecBase) return null
|
|
208
|
+
// 当前 specBase 已有本 session guard → 同一 specDir,无漂移
|
|
209
|
+
const currentGuard = join(currentSpecBase, '.runtime', 'quick-sessions', sessionId, 'guard.json')
|
|
210
|
+
if (existsSync(currentGuard)) return null
|
|
211
|
+
// 当前没有 → 看祖先链别处有没有同 sessionId 的 guard
|
|
212
|
+
const located = locateQuickSessionGuard(cwd, sessionId)
|
|
213
|
+
if (!located) return null // 别处也没有 = 真·新会话首次启动,放行
|
|
214
|
+
return {
|
|
215
|
+
realSpecBase: located.specBase,
|
|
216
|
+
message: `quick 会话 "${sessionId}" 创建于 ${located.specBase},当前 cwd 解析到 ${currentSpecBase}(跨 specDir 漂移)。` +
|
|
217
|
+
`继续会把 progress/artifact/QUICKLOG 写到错误的 spec,与原会话分裂。` +
|
|
218
|
+
`排查:① cd 回 ${dirname(located.specBase)}(原 spec 所属项目根)再跑;② 或显式 --spec-dir ${located.specBase} 指定原 spec。`,
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 检测 specBase 是否命中 worktree 内 checkout 出来的 .sillyspec 副本(坑 worktree-execute-spec-drift)。
|
|
224
|
+
*
|
|
225
|
+
* worktree 根 = <mainRepo>/.sillyspec/.runtime/worktrees/<changeName>/(worktree.js WORKTREES_REL)。
|
|
226
|
+
* worktree 是主仓完整 checkout,若 .sillyspec/changes/ 被跟踪 → 副本里 change 目录真实存在,
|
|
227
|
+
* validateChangeExists 被骗放行 → 进度/产出写进副本,与主仓 .sillyspec 分裂(副本随工作树清理丢失)。
|
|
228
|
+
*
|
|
229
|
+
* 判据:specBase 路径含 `.sillyspec/.runtime/worktrees/<seg>/.sillyspec`。主仓 specBase=<mainRepo>/.sillyspec
|
|
230
|
+
* 无此段 → null,零误伤。返回 null=通过;对象=漂移(含 mainSpecBase/changeName/message),风格仿
|
|
231
|
+
* validateChangeExists。覆盖 plan/execute/verify/archive(同 Set)——都要求 change 已存在、都会被副本骗过。
|
|
232
|
+
* 平台模式/--spec-dir 已明确指定,由调用方跳过。
|
|
233
|
+
*/
|
|
234
|
+
export function detectWorktreeSpecDrift(specBase) {
|
|
235
|
+
if (!specBase) return null
|
|
236
|
+
const seg = resolve(specBase).split(sep)
|
|
237
|
+
// specBase 恒以 .sillyspec 结尾(command.js specBase=join(cwd,'.sillyspec'))。worktree 副本形如
|
|
238
|
+
// <mainRepo>/.sillyspec/.runtime/worktrees/<change>/.sillyspec —— 尾段也须是 .sillyspec,
|
|
239
|
+
// 否则 worktree 根目录本身(<.../worktrees/<change>)会被误判(它不是 spec,不会作 specBase 传入)。
|
|
240
|
+
if (seg[seg.length - 1] !== '.sillyspec') return null
|
|
241
|
+
for (let i = 0; i + 3 < seg.length; i++) {
|
|
242
|
+
if (seg[i] === '.sillyspec' && seg[i + 1] === '.runtime' && seg[i + 2] === 'worktrees') {
|
|
243
|
+
const changeName = seg[i + 3]
|
|
244
|
+
const mainSpec = seg.slice(0, i + 1).join(sep) // <mainRepo>/.sillyspec
|
|
245
|
+
return {
|
|
246
|
+
changeName,
|
|
247
|
+
mainSpecBase: mainSpec,
|
|
248
|
+
message: `当前 spec 命中 worktree 副本(${specBase})——这是 ${changeName} 隔离工作树内 checkout 出来的 .sillyspec,不是主仓 spec。\n` +
|
|
249
|
+
` 在此跑 plan/execute/verify/archive 会把进度与产出写到副本,与主仓 .sillyspec 分裂(副本随工作树清理而丢失)。\n` +
|
|
250
|
+
` 排查:① cd 回 ${dirname(mainSpec)}(主仓根)再跑;② 或 --spec-dir ${mainSpec} 显式指定主仓 spec。`,
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return null
|
|
255
|
+
}
|
|
256
|
+
|
|
107
257
|
/**
|
|
108
258
|
* 统一查找变更目录(与 progress.js 的变更检测逻辑一致)。
|
|
109
259
|
*/
|
|
@@ -222,12 +372,15 @@ export function isQuickMetadata(p, linkedChanges = []) {
|
|
|
222
372
|
|
|
223
373
|
/**
|
|
224
374
|
* quick 完成审计:对比 baseline 与实际变更。
|
|
225
|
-
* @returns {{ status: 'safe'|'warning'|'blocked', reasons: string[], changedFiles: string[], newFiles: string[], deletedFiles: string[], baselineHit: string[] }}
|
|
375
|
+
* @returns {{ status: 'safe'|'warning'|'blocked', reasons: string[], changedFiles: string[], newFiles: string[], deletedFiles: string[], baselineHit: string[], stagedTotal: number }}
|
|
226
376
|
*/
|
|
227
377
|
export async function auditQuickCompletion(cwd, guard, options = {}) {
|
|
228
378
|
const { baselineFiles, allowedFiles = [], allowNew = false, forceBaseline = false } = guard
|
|
229
379
|
const { isConfirm } = options
|
|
230
|
-
|
|
380
|
+
// stagedTotal:当前所有非 quick 元数据的未提交条目(含前序 baseline 残留)。
|
|
381
|
+
// 与 changedFiles(扣 baseline 后的本轮新增)区分,供审计文案同时展示「本轮新增 vs 累计暂存」,
|
|
382
|
+
// 避免叠加 quick 会话时把前序会话未提交文件误读为「本会话只动了 N 个」。
|
|
383
|
+
const result = { status: 'safe', reasons: [], changedFiles: [], newFiles: [], deletedFiles: [], baselineHit: [], stagedTotal: 0 }
|
|
231
384
|
|
|
232
385
|
try {
|
|
233
386
|
const gitStatus = execSync('git status --porcelain', { cwd, encoding: 'utf8', timeout: 10000 })
|
|
@@ -268,6 +421,10 @@ export async function auditQuickCompletion(cwd, guard, options = {}) {
|
|
|
268
421
|
const file = parsePorcelainPath(entry) // 已去引号/处理 rename/归一化,修正首行丢首字符
|
|
269
422
|
if (!file) continue
|
|
270
423
|
|
|
424
|
+
// 累计暂存计数:所有非 quick 元数据的未提交条目(含下方将被 baseline 跳过的前序残留)。
|
|
425
|
+
// 放在 isBaselineFile 跳过之前,确保 baseline 内文件也计入「累计暂存」。
|
|
426
|
+
if (!isQuickMetadata(file, guard.linkedChanges)) result.stagedTotal++
|
|
427
|
+
|
|
271
428
|
// 预存脏文件:step1 baseline 已记录,非本次 quick 产生,跳过审计(含折叠目录前缀匹配)
|
|
272
429
|
if (isBaselineFile(file)) continue
|
|
273
430
|
|
package/src/run/stage.js
CHANGED
|
@@ -246,6 +246,10 @@ export async function runStage(pm, progress, stageName, cwd, changeName, skipApp
|
|
|
246
246
|
})
|
|
247
247
|
progress.quickGuard = {
|
|
248
248
|
sessionId: changeName,
|
|
249
|
+
// 锚定创建时的 specBase(坑 quick-cwd-drift-splits-specdir):sessionId 全局复用但
|
|
250
|
+
// guard 按 specDir 落盘,记下归属让漂移可自证(detectQuickSessionDrift 靠目录定位即可,
|
|
251
|
+
// 此字段供诊断/未来一致性校验)。
|
|
252
|
+
specDir: specBase,
|
|
249
253
|
name_zh: '快速任务守卫',
|
|
250
254
|
baselineCommit: safeGit(cwd, ['rev-parse', 'HEAD']).value,
|
|
251
255
|
baselineFiles,
|
package/src/scan-postcheck.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync } from 'fs'
|
|
9
9
|
import { join, basename } from 'path'
|
|
10
|
-
import { SCAN_STATUS, CHECK_SEVERITY, SCAN_REQUIRED_DOCS } from './constants.js'
|
|
10
|
+
import { SCAN_STATUS, CHECK_SEVERITY, SCAN_REQUIRED_DOCS, SCAN_REQUIRED_DOCS_QUICK } from './constants.js'
|
|
11
11
|
|
|
12
12
|
const REQUIRED_SCAN_DOCS = SCAN_REQUIRED_DOCS
|
|
13
13
|
|
|
@@ -22,8 +22,12 @@ const REQUIRED_SCAN_DOCS = SCAN_REQUIRED_DOCS
|
|
|
22
22
|
* @param {number} [opts.scanMeta.projectCount] - 实际展开的项目数量
|
|
23
23
|
* @returns {{ status: 'success'|'completed_with_warnings'|'failed_post_check', checks: Array<{name, severity, detail}> }}
|
|
24
24
|
*/
|
|
25
|
-
export function runScanPostCheck({ cwd, specDir, outputText = '', scanMeta = {} } ) {
|
|
25
|
+
export function runScanPostCheck({ cwd, specDir, outputText = '', scanMeta = {}, scanProfile = null } ) {
|
|
26
26
|
const isPlatform = !!specDir
|
|
27
|
+
// profile 感知:quick 档只要求 4 份核心文档,否则要求完整 7 份。
|
|
28
|
+
// 否则 quick 扫描(--quick 显式 或 小项目自动判定)在平台模式会因缺 INTEGRATIONS/TESTING/CONCERNS 直接判 failed。
|
|
29
|
+
const mode = scanProfile?.mode || null
|
|
30
|
+
const requiredDocs = mode === 'quick' ? SCAN_REQUIRED_DOCS_QUICK : SCAN_REQUIRED_DOCS
|
|
27
31
|
const checks = []
|
|
28
32
|
|
|
29
33
|
if (!isPlatform) {
|
|
@@ -31,10 +35,13 @@ export function runScanPostCheck({ cwd, specDir, outputText = '', scanMeta = {}
|
|
|
31
35
|
const localSpec = join(cwd, '.sillyspec')
|
|
32
36
|
const scanDir = join(localSpec, 'docs', basename(cwd), 'scan')
|
|
33
37
|
|
|
34
|
-
//
|
|
35
|
-
const missing =
|
|
38
|
+
// 检查必需文档是否存在(quick profile 按 4 份清单,否则 7 份)
|
|
39
|
+
const missing = requiredDocs.filter(f => !existsSync(join(scanDir, f)))
|
|
36
40
|
if (missing.length > 0) {
|
|
37
|
-
checks.push({ name: 'missing_docs', severity: CHECK_SEVERITY.WARNING, detail: `缺少 ${missing.length} 份 scan
|
|
41
|
+
checks.push({ name: 'missing_docs', severity: CHECK_SEVERITY.WARNING, detail: `缺少 ${missing.length} 份 scan 文档(${mode || 'full'} profile): ${missing.join(', ')}` })
|
|
42
|
+
}
|
|
43
|
+
if (mode === 'quick') {
|
|
44
|
+
checks.push({ name: 'quick_profile_notice', severity: CHECK_SEVERITY.WARNING, detail: 'quick scan:仅生成 4 份核心文档(PROJECT/ARCHITECTURE/CONVENTIONS/STRUCTURE),深度扫描待补齐' })
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
const hasWarning = checks.some(c => c.severity === 'warning')
|
|
@@ -74,21 +81,24 @@ export function runScanPostCheck({ cwd, specDir, outputText = '', scanMeta = {}
|
|
|
74
81
|
}
|
|
75
82
|
}
|
|
76
83
|
|
|
77
|
-
// 2. spec_root
|
|
84
|
+
// 2. spec_root 检查必需文档(quick profile 按 4 份清单,否则 7 份)
|
|
78
85
|
const specScanDir = join(specDir, 'docs', projectName, 'scan')
|
|
79
|
-
const missingDocs =
|
|
86
|
+
const missingDocs = requiredDocs.filter(f => !existsSync(join(specScanDir, f)))
|
|
80
87
|
if (missingDocs.length > 0) {
|
|
81
88
|
checks.push({
|
|
82
|
-
name: missingDocs.length ===
|
|
89
|
+
name: missingDocs.length === requiredDocs.length ? 'all_docs_missing' : 'partial_docs_missing',
|
|
83
90
|
severity: CHECK_SEVERITY.FAILED,
|
|
84
|
-
detail: missingDocs.length ===
|
|
91
|
+
detail: missingDocs.length === requiredDocs.length
|
|
85
92
|
? `spec_root 下无任何 scan 文档(${specScanDir}/),扫描可能未执行`
|
|
86
|
-
: `spec_root 缺少必需文档: ${missingDocs.join(', ')}
|
|
93
|
+
: `spec_root 缺少必需文档: ${missingDocs.join(', ')}(${mode === 'quick' ? 'quick profile 要求 4 份核心文档' : '7 份 scan 文档均为 required'})`
|
|
87
94
|
})
|
|
88
95
|
}
|
|
96
|
+
if (mode === 'quick') {
|
|
97
|
+
checks.push({ name: 'quick_profile_notice', severity: CHECK_SEVERITY.WARNING, detail: 'quick scan:仅生成 4 份核心文档(PROJECT/ARCHITECTURE/CONVENTIONS/STRUCTURE),深度扫描待补齐' })
|
|
98
|
+
}
|
|
89
99
|
|
|
90
100
|
// 3. 检查文档 header(author / created_at)— 只看文件头部,避免正文出现同名词被误判
|
|
91
|
-
const existingDocs =
|
|
101
|
+
const existingDocs = requiredDocs.filter(f => existsSync(join(specScanDir, f)))
|
|
92
102
|
const docsMissingHeader = []
|
|
93
103
|
for (const doc of existingDocs) {
|
|
94
104
|
const content = readFileSync(join(specScanDir, doc), 'utf8')
|
|
@@ -310,6 +320,10 @@ export function formatStructuredResult(result, meta = {}) {
|
|
|
310
320
|
else if (check.name === 'docs_missing_header') {
|
|
311
321
|
structured.failure_categories.quality_warnings.push(entry)
|
|
312
322
|
}
|
|
323
|
+
// quick profile 说明(informational warning,非缺陷)
|
|
324
|
+
else if (check.name === 'quick_profile_notice') {
|
|
325
|
+
structured.failure_categories.quality_warnings.push(entry)
|
|
326
|
+
}
|
|
313
327
|
// 兜底:归入 violations
|
|
314
328
|
else {
|
|
315
329
|
structured.failure_categories.violations.push(entry)
|
package/src/stage-contract.js
CHANGED
|
@@ -66,9 +66,14 @@ export function validateChangeExists(specBase, stageName, changeName) {
|
|
|
66
66
|
if (stageName === 'archive') {
|
|
67
67
|
const archiveRoot = join(specBase, 'changes', 'archive')
|
|
68
68
|
if (existsSync(archiveRoot)) {
|
|
69
|
+
// 归档目录名恒为 <归档日>-<纯描述>(archiveDestDirName 写入时已 strip 源名前导日期)。
|
|
70
|
+
// 校验侧同样 strip changeName 前导日期再比 —— 否则「写 strip、读拿完整源名」语义不一致,
|
|
71
|
+
// 会让带日期前缀的源名(brainstorm 约定,如 2026-07-29-sidebar-menu-restructure)在 step5
|
|
72
|
+
// 被前置校验误拦(坑 archive-step5-leading-date-change-name)。
|
|
73
|
+
const stripped = String(changeName).replace(/^\d{4}-\d{2}-\d{2}-/, '')
|
|
69
74
|
const archived = readdirSync(archiveRoot).find(d => {
|
|
70
75
|
const m = d.match(/^\d{4}-\d{2}-\d{2}-(.+)$/)
|
|
71
|
-
return m && m[1] ===
|
|
76
|
+
return m && m[1] === stripped
|
|
72
77
|
})
|
|
73
78
|
if (archived) return null
|
|
74
79
|
}
|
package/src/stage-review.js
CHANGED
|
@@ -195,10 +195,16 @@ export function verifyStageReviewDocHash(review, searchDirs) {
|
|
|
195
195
|
|
|
196
196
|
for (const base of searchDirs || []) {
|
|
197
197
|
const abs = join(base, primaryRel)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
//
|
|
201
|
-
|
|
198
|
+
if (!existsSync(abs)) continue
|
|
199
|
+
// 找到主文档,比对 hash。容忍「原始字节」与「LF 规范化」两种口径:Windows 下 CRLF/LF 在
|
|
200
|
+
// git add / eol 规范化前后字节会漂移,同一文件 sha256 偶发不一致(坑 worktree-execute-apply-friction
|
|
201
|
+
// 坑3)——agent 写的 docHash(原始 sha256sum)与 gate 重算只要任一口径匹配即认可,消除 CRLF
|
|
202
|
+
// 漂移导致的反复「疑似伪造」误报。两口径都对不上仍判不匹配(防伪造能力不降级)。
|
|
203
|
+
const content = readFileSync(abs, 'utf8')
|
|
204
|
+
const actualRaw = createHash('sha256').update(content).digest('hex')
|
|
205
|
+
const actualLf = createHash('sha256').update(content.replace(/\r\n/g, '\n')).digest('hex')
|
|
206
|
+
const claimed = String(review.docHash).toLowerCase()
|
|
207
|
+
if (claimed !== actualRaw.toLowerCase() && claimed !== actualLf.toLowerCase()) {
|
|
202
208
|
errors.push(
|
|
203
209
|
`docHash 与主审查文档 ${primaryRel} 的实际内容不匹配。` +
|
|
204
210
|
`若刚改过该文档(如 design 修订后忘重算),重算并更新 review.json 的 docHash 字段:` +
|
package/src/stages/brainstorm.js
CHANGED
|
@@ -60,7 +60,7 @@ export const definition = {
|
|
|
60
60
|
加载上下文后,用需求描述 + 模块上下文**粗判**本次变更规模:
|
|
61
61
|
- **明显 small**(满足:预计改动 ≤ 2 个文件、单模块、无 schema/API/状态机/权限变更;或属于改文案/修 bug/样式调整/配置微调等纯执行类)→ 输出:「此变更规模较小,建议直接走 quick 流程」+ 一句依据,给出建议命令 \`sillyspec run quick "<需求>"\`。用户同意则本阶段可在此收尾(\`--done\` 并提示转 quick),不必继续走完整设计流程。
|
|
62
62
|
- **拿不准或明显 large**(涉及多模块、schema、状态流转、新架构等)→ 不要提示 quick,继续进入下一步「对话式探索与需求澄清」。
|
|
63
|
-
-
|
|
63
|
+
- 这是**粗判**,只为让明显的小变更免走完整设计流程;不确定就继续,后续「生成规范文件」步骤会基于 design.md 文件清单做精判兜底。
|
|
64
64
|
|
|
65
65
|
### 输出
|
|
66
66
|
项目现状理解摘要(3-5 句话,关键约定和架构决策)+ 可能涉及的模块列表 + 本次需求所属子项目 + (如命中)quick 建议
|
|
@@ -442,11 +442,10 @@ status: passed | needs-user-input | blocked | skipped
|
|
|
442
442
|
optional: false
|
|
443
443
|
},
|
|
444
444
|
{
|
|
445
|
-
name: '
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
prompt: `用户确认设计方案,按变更规模生成规范文件并给出实现路径建议。
|
|
445
|
+
name: '生成规范文件',
|
|
446
|
+
// 重命名自「用户确认并生成规范文件」(去确认门控:设计已在「分段展示设计」步确认过,末步再问纯冗余),老进度 completed 由迁移逻辑承接
|
|
447
|
+
migratedFrom: ['用户确认并生成规范文件'],
|
|
448
|
+
prompt: `按变更规模生成规范文件并给出实现路径建议(设计已在前置步骤确认过,本步不再暂停等确认,生成后展示摘要即可)。
|
|
450
449
|
|
|
451
450
|
### 规模评估(展示前先做)
|
|
452
451
|
读取 design.md 的「文件变更清单」,判断本次变更规模:
|
|
@@ -456,13 +455,13 @@ status: passed | needs-user-input | blocked | skipped
|
|
|
456
455
|
(注:早期「加载项目上下文」步骤已做过一次粗判并可能建议过走 quick;此处基于 design.md 文件清单做精判兜底。)
|
|
457
456
|
|
|
458
457
|
### 操作
|
|
459
|
-
1.
|
|
460
|
-
2. 暂停等待用户选择:✅ 确认 / ✏️ 修改 / ❌ 推翻重来
|
|
461
|
-
3. 确认后,**按规模生成规范文件**:
|
|
458
|
+
1. **按规模生成规范文件**:
|
|
462
459
|
- **scale=large**:在 \`{SPEC_ROOT}/changes/<change-name>/\` 下生成完整四件套(design.md / decisions.md 可选 / proposal.md / requirements.md / tasks.md),实现路径 → \`sillyspec run plan --change <变更名>\`
|
|
463
460
|
- **scale=small**:只生成/补全 design.md(proposal/requirements/tasks 对 quick 无用,不生成),实现路径 → \`sillyspec run quick --linked-changes <变更名>\`
|
|
464
461
|
- 两种规模都执行 \`git add .sillyspec/\` — 暂存规范文件(不要 commit,由用户通过统一提交工具处理)。**平台模式跳过 git add**(specRoot 不在 sourceRoot 的 git repo 内)
|
|
465
462
|
|
|
463
|
+
2. 生成完成后展示 design.md 摘要 + **规模评估结果(small/large + 一句依据)** + 实现路径建议,告知用户“如有异议直接说,可修改文件、改 scale 或 --reopen 回退”(不暂停,展示完直接 --done)
|
|
464
|
+
|
|
466
465
|
所有规范文件头部必须包含 YAML frontmatter:
|
|
467
466
|
\`\`\`yaml
|
|
468
467
|
---
|
|
@@ -553,15 +552,11 @@ Then 期望结果
|
|
|
553
552
|
4. \`git add .sillyspec/\` — 暂存所有新增文件(不要 commit,由用户通过统一提交工具处理)。**平台模式跳过 git add**(specRoot 不在 sourceRoot 的 git repo 内)
|
|
554
553
|
5. 后续变更包的骨架文件同样必须包含 \`author: <git-user>\` 和 \`created_at: <now-datetime>\`
|
|
555
554
|
|
|
556
|
-
### 铁律 — 必须等待用户最终确认
|
|
557
|
-
- 展示 design.md 摘要后暂停等用户确认,**不要自己说"用户已确认"然后生成文件**(命令由 CLI 在下方注入)
|
|
558
|
-
- 只有用户明确确认(--answer "确认")后才生成规范文件**
|
|
559
|
-
|
|
560
555
|
### 输出
|
|
561
556
|
所有规范文件路径(含后续变更包目录列表)
|
|
562
557
|
|
|
563
558
|
### 注意
|
|
564
|
-
-
|
|
559
|
+
- 本步不再等待用户确认,生成完成后直接 --done;用户事后提出修改/推翻时,修改对应文件或用 --reopen 回退重做
|
|
565
560
|
- 禁止自动 commit
|
|
566
561
|
- 推翻重来回到「对话式探索与需求澄清」步骤
|
|
567
562
|
- 表名/字段名/类名必须来自真实代码或标注"新增"
|
|
@@ -64,7 +64,7 @@ function parseTaskId(content, filename) {
|
|
|
64
64
|
* @param {string} content
|
|
65
65
|
* @returns {string[]}
|
|
66
66
|
*/
|
|
67
|
-
function parseAllowedPaths(content) {
|
|
67
|
+
export function parseAllowedPaths(content) {
|
|
68
68
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/)
|
|
69
69
|
if (!fmMatch) return []
|
|
70
70
|
const fm = fmMatch[1]
|
package/src/stages/plan.js
CHANGED
|
@@ -322,6 +322,7 @@ tier: {REVIEW_TIER}({REVIEW_TIER_REASON})
|
|
|
322
322
|
- [ ] 不存在 P0/P1 unresolved blocker 残留
|
|
323
323
|
- [ ] 没有实现细节泄漏到 plan.md(接口签名/代码示例应在 tasks/task-NN.md)
|
|
324
324
|
- [ ] 关键路径与 Wave 依赖合理(无循环依赖、无遗漏前置)
|
|
325
|
+
- [ ] 连带测试归属:改共享/被多 task 依赖源文件的 task 是否在 related_tests 声明了会失效的既有测试、且路径在 allowed_paths 内(或由独立测试 task 覆盖)?(漏声明 = execute 阶段测试债、主代理事后兜底)
|
|
325
326
|
|
|
326
327
|
### tier=independent 时:启动 plan-review 子代理
|
|
327
328
|
用 Agent tool 启动子代理(subagent_type: general),prompt 要点:
|
|
@@ -401,6 +402,9 @@ verify:
|
|
|
401
402
|
constraints:
|
|
402
403
|
- 边界约束 1(如:不加测试)
|
|
403
404
|
- 边界约束 2(如:不修改传入参数)
|
|
405
|
+
related_tests: # 可选。仅当本 task 改共享/被多 task 依赖的源文件时填
|
|
406
|
+
- path: frontend/src/lib/errors.test.ts # 因本次改动断言失效的既有测试文件
|
|
407
|
+
reason: 旧断言假设单例,改归属键后需同步
|
|
404
408
|
---
|
|
405
409
|
|
|
406
410
|
TaskCard 格式规则(必须严格遵守):
|
|
@@ -422,6 +426,7 @@ TaskCard 格式规则(必须严格遵守):
|
|
|
422
426
|
- provides / expects_from 是可选字段:仅当跨 task 契约(一个 task 的接口/DTO/响应被另一个 task 消费)时才填,单 task 或无对外接口场景留空即可
|
|
423
427
|
- 填写后 plan-postcheck 会做硬对账:consumer 的每个 expects_from[provider].needs 字段必须在对应 provider 的 provides.fields 里,否则 plan 阶段阻断(不进入 execute)
|
|
424
428
|
- 不要把内部实现字段塞进 provides;只暴露给其他 task 的对外契约形状
|
|
429
|
+
- related_tests 是可选字段:仅当本 task 改共享源文件(被既有测试引用、或被多 task 依赖)时填,列出会因本次改动断言失效的既有测试文件 + 为何失效。关键:这些测试路径必须同时写进本 task 的 allowed_paths(否则子代理被 allowed_paths 铁律锁死、改不了测试 → 退化成主代理事后兜底);若测试由独立的测试 task 负责,则在该测试 task 的 allowed_paths 覆盖、本字段留空
|
|
425
430
|
- 如果存在 decisions.md,无法覆盖的 D-xxx@vN 在 constraints 中标注
|
|
426
431
|
- **保存前格式自检**(plan-postcheck 会硬校验,不通过到 Step 4 会批量报错,先在这里逐条自查):
|
|
427
432
|
- frontmatter 字段齐全:id、title、title_zh、author、created_at、priority、depends_on、blocks、allowed_paths、goal、implementation、acceptance、verify、constraints
|
|
@@ -464,6 +469,7 @@ ${subagentPrompts}
|
|
|
464
469
|
- allowed_paths 有无冲突
|
|
465
470
|
- depends_on 与 plan.md Wave 分组是否一致
|
|
466
471
|
- provides/expects_from 契约自洽:每个 expects_from[provider].needs 字段都在该 provider task 的 provides.fields 里(plan-postcheck 会硬校验,这里提前自查)
|
|
472
|
+
- related_tests(若填)的测试路径是否都在本 task 或某 task 的 allowed_paths 内(否则子代理无权改 → execute 测试债、主代理事后兜底)
|
|
467
473
|
- 如发现矛盾,列出问题清单,不要自动修复`
|
|
468
474
|
|
|
469
475
|
return {
|
package/src/stages/quick.js
CHANGED
|
@@ -8,8 +8,8 @@ export const definition = {
|
|
|
8
8
|
name: '理解任务',
|
|
9
9
|
prompt: `解析任务参数,加载项目上下文。
|
|
10
10
|
|
|
11
|
-
### 📌 本 quick 会话 sessionId: <quick-session-id>
|
|
12
|
-
- CLI 是短进程,run 与 done 是独立进程,sessionId 靠 --change 跨进程传递
|
|
11
|
+
### 📌 本 quick 会话 sessionId: <quick-session-id>
|
|
12
|
+
- CLI 是短进程,run 与 done 是独立进程,sessionId 靠 --change 跨进程传递
|
|
13
13
|
- **完成每个 step 必须带 --change <quick-session-id>**(命令由 CLI 在下方注入);多会话并发时不带会命中他者会话状态(不带时 fallback 读 current-quick-run-id,单会话兼容;多会话不可靠)
|
|
14
14
|
|
|
15
15
|
### 操作
|
|
@@ -26,15 +26,15 @@ export const definition = {
|
|
|
26
26
|
9. 根据任务描述初步判断可能涉及的模块
|
|
27
27
|
10. 读取匹配到的 \`{SPEC_ROOT}/docs/<project>/modules/<module>.md\`
|
|
28
28
|
|
|
29
|
-
### QUICKLOG 记录(CLI 已接管,无需你手动创建)
|
|
30
|
-
本 quick 会话的 ql-ID 由 CLI 在启动时分配,已注入为 \`<quicklog-id>\`。CLI 同时已自动:
|
|
31
|
-
- 在 \`{SPEC_ROOT}/quicklog/QUICKLOG-<git-user>.md\` 写入「进行中」条目(含关联变更与预估文件)
|
|
32
|
-
- 对每个关联变更 \`<c>\`:在 \`{SPEC_ROOT}/changes/<c>/tasks.md\` 追加未勾选 task \`- [ ] <quicklog-id> <任务描述>\`
|
|
33
|
-
|
|
34
|
-
**你不要创建或修改任何 QUICKLOG 的条目骨架 / tasks.md 记录**——ql-ID 分配、「进行中」条目、状态翻转、task 勾选、轮转全由 CLI 接管,你无需手写。<quicklog-id> 可用于 design.md / plan.md / archive / 模块变更索引引用。
|
|
35
|
-
|
|
36
|
-
> ⚠️ 例外:step 3 --done 之后,CLI 落盘的 QUICKLOG 只是简版骨架,**你必须手动精修正文成丰富格式**(标题语义化 + 文件多行带括注 + 四段充实),详见 step 3「QUICKLOG 正文精修」。这是 quick 交付质量的一部分,不可跳过。
|
|
37
|
-
|
|
29
|
+
### QUICKLOG 记录(CLI 已接管,无需你手动创建)
|
|
30
|
+
本 quick 会话的 ql-ID 由 CLI 在启动时分配,已注入为 \`<quicklog-id>\`。CLI 同时已自动:
|
|
31
|
+
- 在 \`{SPEC_ROOT}/quicklog/QUICKLOG-<git-user>.md\` 写入「进行中」条目(含关联变更与预估文件)
|
|
32
|
+
- 对每个关联变更 \`<c>\`:在 \`{SPEC_ROOT}/changes/<c>/tasks.md\` 追加未勾选 task \`- [ ] <quicklog-id> <任务描述>\`
|
|
33
|
+
|
|
34
|
+
**你不要创建或修改任何 QUICKLOG 的条目骨架 / tasks.md 记录**——ql-ID 分配、「进行中」条目、状态翻转、task 勾选、轮转全由 CLI 接管,你无需手写。<quicklog-id> 可用于 design.md / plan.md / archive / 模块变更索引引用。
|
|
35
|
+
|
|
36
|
+
> ⚠️ 例外:step 3 --done 之后,CLI 落盘的 QUICKLOG 只是简版骨架,**你必须手动精修正文成丰富格式**(标题语义化 + 文件多行带括注 + 四段充实),详见 step 3「QUICKLOG 正文精修」。这是 quick 交付质量的一部分,不可跳过。
|
|
37
|
+
|
|
38
38
|
### 输出
|
|
39
39
|
任务理解 + 上下文摘要`,
|
|
40
40
|
outputHint: '任务理解',
|
|
@@ -42,16 +42,16 @@ export const definition = {
|
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
name: '实现并验证',
|
|
45
|
-
prompt: `直接在主工作区实现任务。
|
|
46
|
-
|
|
47
|
-
### 边界声明(quick 不校验 design.md)
|
|
48
|
-
关联变更的 design.md 仅供理解意图,**不作为验收基准**——quick 不 enforce design 一致性。需要 design 一致性保证 → 走完整流程(plan + verify)。发现 design 本身有错时,可 Reverse Sync 回写(见下方铁律)。
|
|
49
|
-
|
|
45
|
+
prompt: `直接在主工作区实现任务。
|
|
46
|
+
|
|
47
|
+
### 边界声明(quick 不校验 design.md)
|
|
48
|
+
关联变更的 design.md 仅供理解意图,**不作为验收基准**——quick 不 enforce design 一致性。需要 design 一致性保证 → 走完整流程(plan + verify)。发现 design 本身有错时,可 Reverse Sync 回写(见下方铁律)。
|
|
49
|
+
|
|
50
50
|
### 操作
|
|
51
51
|
1. 先读后写:调用已有方法前 \`cat\` 源文件确认签名,\`grep\` 确认方法存在
|
|
52
52
|
2. 写代码完成任务
|
|
53
53
|
3. 如涉及逻辑变更,建议写单元测试验证(不强制,纯配置/文档/小改动可跳过)
|
|
54
|
-
4.
|
|
54
|
+
4. **不要做无谓的全量编译/构建。** 但——跑针对性的单元测试始终允许且鼓励(见 step3);若本任务就是修测试/构建/基础设施本身,跑测试/构建即验收,应当跑,不算"编译"
|
|
55
55
|
|
|
56
56
|
### 输出
|
|
57
57
|
实现摘要 + 修改文件列表
|
|
@@ -67,7 +67,7 @@ export const definition = {
|
|
|
67
67
|
name: '暂存和更新记录',
|
|
68
68
|
prompt: `Git 暂存并更新任务记录。
|
|
69
69
|
|
|
70
|
-
### 📌 收尾确认
|
|
70
|
+
### 📌 收尾确认
|
|
71
71
|
- 本步骤是最后一步,完成后 quick 会话即结束(--change 传递见首步 sessionId 说明)
|
|
72
72
|
|
|
73
73
|
### ⚠️ 结果摘要模板(必填,CLI 会校验结构)
|
|
@@ -102,19 +102,19 @@ export const definition = {
|
|
|
102
102
|
- 使用 \`git add -- <module-doc>\` 暂存更新的模块文件
|
|
103
103
|
9. 未命中任何模块 → 跳过,不做额外操作
|
|
104
104
|
|
|
105
|
-
### 输出
|
|
106
|
-
暂存确认 + 记录路径 + 模块文档同步结果(如有)
|
|
107
|
-
|
|
108
|
-
### 📝 QUICKLOG 正文精修(--done 之后必做,不可跳过)
|
|
109
|
-
sillyspec run quick --done 成功后,CLI 已在 {SPEC_ROOT}/quicklog/QUICKLOG-<git-user>.md 落盘本次 <quicklog-id> 条目的简版骨架:状态翻「已完成」、「文件:」是单行逗号路径、正文是 --output 的四字段。骨架是机械产物不达标,你必须立即编辑该条目手动精修成丰富格式,三项必改:
|
|
110
|
-
|
|
111
|
-
1. 标题:把 ## <quicklog-id> | <时间> | <标题> 中的 <标题> 改为真实需求摘要(如「登录加 IP 限流(5 次/分)+ 失败 3 次滑块验证」)。禁止保留 CLI 兜底的 (quick 任务) 或一句话笼统描述。
|
|
112
|
-
2. 文件:把 CLI 写的单行「文件:a.js, b.js」改写为多行 bullet,每个文件带关键改动括注;禁止「(见实际改动)」或无括注纯路径单行。示例:
|
|
113
|
-
文件:
|
|
114
|
-
- backend/app/modules/auth/router.py(登录端点串 check_rate_limit→assert_captcha_if_needed)
|
|
115
|
-
- backend/app/modules/auth/captcha_service.py(新建:限流 INCR + 失败计数 + Pillow 滑块生成/校验)
|
|
116
|
-
3. 正文:核对 需求: 根因: 方案: 结果: 四段逐项充实(--output 已写、CLI 原样插入,按需扩写),不允许只留一段「结果:」。
|
|
117
|
-
|
|
105
|
+
### 输出
|
|
106
|
+
暂存确认 + 记录路径 + 模块文档同步结果(如有)
|
|
107
|
+
|
|
108
|
+
### 📝 QUICKLOG 正文精修(--done 之后必做,不可跳过)
|
|
109
|
+
sillyspec run quick --done 成功后,CLI 已在 {SPEC_ROOT}/quicklog/QUICKLOG-<git-user>.md 落盘本次 <quicklog-id> 条目的简版骨架:状态翻「已完成」、「文件:」是单行逗号路径、正文是 --output 的四字段。骨架是机械产物不达标,你必须立即编辑该条目手动精修成丰富格式,三项必改:
|
|
110
|
+
|
|
111
|
+
1. 标题:把 ## <quicklog-id> | <时间> | <标题> 中的 <标题> 改为真实需求摘要(如「登录加 IP 限流(5 次/分)+ 失败 3 次滑块验证」)。禁止保留 CLI 兜底的 (quick 任务) 或一句话笼统描述。
|
|
112
|
+
2. 文件:把 CLI 写的单行「文件:a.js, b.js」改写为多行 bullet,每个文件带关键改动括注;禁止「(见实际改动)」或无括注纯路径单行。示例:
|
|
113
|
+
文件:
|
|
114
|
+
- backend/app/modules/auth/router.py(登录端点串 check_rate_limit→assert_captcha_if_needed)
|
|
115
|
+
- backend/app/modules/auth/captcha_service.py(新建:限流 INCR + 失败计数 + Pillow 滑块生成/校验)
|
|
116
|
+
3. 正文:核对 需求: 根因: 方案: 结果: 四段逐项充实(--output 已写、CLI 原样插入,按需扩写),不允许只留一段「结果:」。
|
|
117
|
+
|
|
118
118
|
一条 quick = 一条独立 ql 条目;精修只动本次 <quicklog-id> 条目,不追加到旧条目。QUICKLOG 在 .sillyspec/(gitignore),精修不影响 --done 已通过的边界审计。`,
|
|
119
119
|
outputHint: '暂存和记录确认',
|
|
120
120
|
optional: false
|
package/src/stages/scan.js
CHANGED
|
@@ -139,7 +139,8 @@ export const definition = {
|
|
|
139
139
|
对每个项目分别执行(将 \`<project>\` 替换为实际项目名)
|
|
140
140
|
4. 如果检查报告有失败项,按报告中的角色和文件重试失败的部分
|
|
141
141
|
|
|
142
|
-
### 覆盖保护
|
|
142
|
+
### 覆盖保护
|
|
143
|
+
- **scan_depth: quick 的浅层文档允许覆盖升级**:读取旧 frontmatter 时,若旧文档含 scan_depth: quick(由 --quick 快速接入生成的浅层版本),即使其 source_commit 与当前 HEAD 一致、updated_at 未变,也允许覆盖重写——深度扫描的目的就是把它升级为完整文档。
|
|
143
144
|
- 生成每份 scan 文档时,frontmatter 必须包含:
|
|
144
145
|
\`\`\`yaml
|
|
145
146
|
---
|
package/src/stages/verify.js
CHANGED
|
@@ -118,8 +118,8 @@ export const definition = {
|
|
|
118
118
|
{{include: verify-probes}}
|
|
119
119
|
|
|
120
120
|
### 探针结果处理
|
|
121
|
-
-
|
|
122
|
-
-
|
|
121
|
+
- 将六个探针的结果汇总为「探针报告」
|
|
122
|
+
- 如果探针发现问题(未实现标记、关键词缺失、测试缺失、决策未闭环、API 契约缺口、代码删除对账),在最终验证报告中明确标注
|
|
123
123
|
- 探针发现的问题不等同于验证失败,但必须在报告中列出
|
|
124
124
|
|
|
125
125
|
### 设计一致性检查
|
|
@@ -191,6 +191,7 @@ export const definition = {
|
|
|
191
191
|
- **误判时的诚实出路(豁免级)**:在 design.md 顶部 frontmatter 加一行 \`risk_level: <真实等级>\`(doc-only / unit-sufficient / contract-required / integration-critical / deployment-critical),CLI 会以声明为准覆盖关键词判级。声明后若是 unit-sufficient 等豁免级,PASS WITH NOTES 不再被强制拦;但结论为 PASS 仍需对应证据。
|
|
192
192
|
- **留痕要求(防逃逸)**:用了显式声明,必须在本报告「变更风险等级」section 写明「risk_level 由 design frontmatter 显式声明 = <等级>(覆盖关键词判级)」+ 一句话理由,让豁免可审计。
|
|
193
193
|
你只需:在 verify-result.md 的「变更风险等级」section 如实记录变更性质;若变更涉及 daemon/backend 跨进程、session/lease/lifecycle 状态机、或部署启动路径,在「Runtime Evidence」section 提供真实集成证据(启动命令、daemon↔backend 调用与日志关键片段、终态断言)。
|
|
194
|
+
- **集成证据是自报告、CLI 不独立运行时核验**:「Runtime Evidence」由你如实填写,CLI 只校验其**字面存在**(是否含关键词),**不会替你启动 daemon、打真实请求或跑迁移**——它是否名副其实取决于你是否实跑过。务必实跑后据实填写,不得凭堆关键词通过门控。(测试套件对账另算:commands.test 由 CLI 真实执行,那块谎报无效。)
|
|
194
195
|
|
|
195
196
|
3. 生成 verify-result.md 文件,保存到 \`.sillyspec/changes/<change-name>/verify-result.md\`
|
|
196
197
|
4. 给出结论:PASS / PASS WITH NOTES / FAIL(受风险门控约束)
|
|
@@ -213,6 +214,8 @@ PASS / PASS WITH NOTES / FAIL
|
|
|
213
214
|
- 关键词覆盖:...
|
|
214
215
|
- 测试覆盖:...
|
|
215
216
|
- 决策追踪覆盖:...
|
|
217
|
+
- API 契约对账:...
|
|
218
|
+
- 代码删除对账:声明修改/新增却被整文件删除 = 高风险;清单未列出的删除 = 未声明(CLI 会用 git diff --name-status HEAD 独立复核)
|
|
216
219
|
|
|
217
220
|
## 决策追踪矩阵(如存在 decisions.md)
|
|
218
221
|
| 决策 ID | FR | Task | Evidence | 状态 |
|
|
@@ -228,7 +231,7 @@ PASS / PASS WITH NOTES / FAIL
|
|
|
228
231
|
## 变更风险等级
|
|
229
232
|
(自动检测的 change_risk_profile: doc-only / unit-sufficient / contract-required / integration-critical / deployment-critical;若 design.md frontmatter 有 risk_level 显式声明,以声明为准并在此注明「显式声明 = <等级>」+ 理由)
|
|
230
233
|
|
|
231
|
-
## Runtime Evidence(integration-critical / deployment-critical
|
|
234
|
+
## Runtime Evidence(integration-critical / deployment-critical 必填;自报告,CLI 仅校验字面存在、不独立核验,须真实执行过)
|
|
232
235
|
- daemon 启动命令:
|
|
233
236
|
- backend 地址:
|
|
234
237
|
- 创建 session / 调用核心 API 的请求:
|