sillyspec 3.18.5 → 3.19.0
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/package.json +1 -1
- package/src/index.js +15 -0
- package/src/run.js +137 -12
- package/src/stage-contract.js +14 -1
- package/src/stages/execute.js +31 -2
- package/src/stages/verify.js +8 -0
- package/src/task-review.js +346 -0
- package/src/workflow.js +12 -8
- package/templates/workflows/scan-docs.yaml +8 -8
- package/test/cli-top-level-aliases.test.mjs +174 -0
- package/test/run-sanitize-project-name.test.mjs +51 -0
- package/test/run-scan-postcheck-fail.test.mjs +64 -0
- package/test/run-scan-project-parse.test.mjs +77 -0
- package/test/scan-docs-yaml-placeholders.test.mjs +84 -0
- package/test/scan-postcheck-project-priority.test.mjs +85 -0
- package/test/scan-workflow-anyfailed-block.test.mjs +52 -0
- package/test/stage-contract-failed-post-check.test.mjs +102 -0
- package/test/workflow-spec-base.test.mjs +142 -0
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -289,6 +289,21 @@ async function main() {
|
|
|
289
289
|
await runCommand(filteredArgs.slice(1), resolveEffectiveDir(dir), specDir)
|
|
290
290
|
break
|
|
291
291
|
}
|
|
292
|
+
// task-10: 顶层命令别名,转发 runCommand,与 case 'run': 路径行为一致
|
|
293
|
+
// help 文本(:44-46)已宣称这些 stage 可直接使用,这里补齐路由避免落 default 分支。
|
|
294
|
+
// 注意:filteredArgs[0] === command,直接透传 filteredArgs 即可让 runCommand
|
|
295
|
+
// 从 args[0] 取到 stage 名(run.js:1036)。与 case 'run': 的 filteredArgs.slice(1)
|
|
296
|
+
// 区别只在于 slice(1) 去掉的是 'run' 字面量,这里 command 本身就是 stage 名不能丢。
|
|
297
|
+
case 'doctor':
|
|
298
|
+
case 'scan':
|
|
299
|
+
case 'status':
|
|
300
|
+
case 'quick':
|
|
301
|
+
case 'explore': {
|
|
302
|
+
const { runCommand } = await import('./run.js')
|
|
303
|
+
const stageArgs = [command, ...filteredArgs.slice(1)]
|
|
304
|
+
await runCommand(stageArgs, resolveEffectiveDir(dir), specDir)
|
|
305
|
+
break
|
|
306
|
+
}
|
|
292
307
|
case 'dashboard': {
|
|
293
308
|
// Parse dashboard options
|
|
294
309
|
let port = 3456;
|
package/src/run.js
CHANGED
|
@@ -11,6 +11,22 @@ const require = createRequire(import.meta.url)
|
|
|
11
11
|
import { ProgressManager } from './progress.js'
|
|
12
12
|
import { SCAN_STATUS, POINTER_STATUS, isPointerCorrupted } from './constants.js'
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* 清洗项目名:只保留 ASCII 字母/数字/横线/下划线/点,过滤中文和特殊字符。
|
|
16
|
+
* - 必须含至少一个字母(拒绝纯数字 "0"/"7"/"07",避免 scan-projects.json 脏数据)
|
|
17
|
+
* - 长度必须 ≥ 2(拒绝单字符 "a"/"0")
|
|
18
|
+
* @param {string} name - 原始项目名候选
|
|
19
|
+
* @returns {string | null} 合法项目名或 null(拒绝)
|
|
20
|
+
*/
|
|
21
|
+
export function sanitizeProjectName(name) {
|
|
22
|
+
if (!name) return null
|
|
23
|
+
const clean = String(name).replace(/[^a-zA-Z0-9_\-.]/g, '').trim()
|
|
24
|
+
if (!clean) return null
|
|
25
|
+
if (!/[a-zA-Z]/.test(clean)) return null // 纯数字/符号拒绝("0"/"7"/"07")
|
|
26
|
+
if (clean.length < 2) return null // 单字符拒绝("a"/"0")
|
|
27
|
+
return clean
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
/**
|
|
15
31
|
* 在容器/Docker 环境下,git 可能因目录所有权不匹配报 dubious ownership。
|
|
16
32
|
* 使用 -c safe.directory= 临时参数,不污染全局 git config。
|
|
@@ -666,6 +682,12 @@ async function outputStep(stageName, stepIndex, steps, cwd, changeName, dbProjec
|
|
|
666
682
|
`2. **不允许**用 cat >、tee、heredoc 等 Bash 方式绕过 Write 工具。\n` +
|
|
667
683
|
`3. 如果 Write 和 Read 均失败,记录失败并停止当前 step。\n` +
|
|
668
684
|
`\n` +
|
|
685
|
+
`### 📍 Workflow YAML 占位符映射(task-05)\n` +
|
|
686
|
+
`读取 \`{WORKFLOWS_ROOT}/scan-docs.yaml\` 时,yaml 内的占位符按以下映射替换为绝对路径:\n` +
|
|
687
|
+
`- \`{SPEC_ROOT}\` → \`${specSillyspec}\`(规范目录根)\n` +
|
|
688
|
+
`- \`<project>\` → 当前项目名(见下方 step 提示,等于 \`${projectName}\`)\n` +
|
|
689
|
+
`- 例:\`{SPEC_ROOT}/docs/<project>/scan/ARCHITECTURE.md\` → \`${docsRoot}/scan/ARCHITECTURE.md\`\n` +
|
|
690
|
+
`\n` +
|
|
669
691
|
`创建目录: \`mkdir -p ${docsRoot}/{scan,modules,flows} ${projectsRoot} ${changesRoot}\`\n`
|
|
670
692
|
)
|
|
671
693
|
if (platformOpts.runtimeRoot) {
|
|
@@ -750,6 +772,23 @@ async function outputStep(stageName, stepIndex, steps, cwd, changeName, dbProjec
|
|
|
750
772
|
}
|
|
751
773
|
}
|
|
752
774
|
|
|
775
|
+
// Execute: 注入 currentExecuteRunId(从 runtime 标记文件读取)
|
|
776
|
+
if (stageName === 'execute' && promptText.includes('{EXECUTE_RUN_ID}')) {
|
|
777
|
+
const execSpecBase = platformOpts?.specRoot || join(cwd, '.sillyspec')
|
|
778
|
+
const runIdFile = join(execSpecBase, '.runtime', 'current-execute-run-id')
|
|
779
|
+
let runId = ''
|
|
780
|
+
try {
|
|
781
|
+
if (existsSync(runIdFile)) {
|
|
782
|
+
runId = readFileSync(runIdFile, 'utf8').trim()
|
|
783
|
+
}
|
|
784
|
+
} catch {}
|
|
785
|
+
if (!runId) {
|
|
786
|
+
const { generateExecuteRunId, getLatestExecuteRunId } = await import('./task-review.js')
|
|
787
|
+
runId = getLatestExecuteRunId(join(execSpecBase, '.runtime')) || generateExecuteRunId()
|
|
788
|
+
}
|
|
789
|
+
promptText = promptText.replace(/\{EXECUTE_RUN_ID\}/g, runId)
|
|
790
|
+
}
|
|
791
|
+
|
|
753
792
|
// 注入模块上下文(brainstorm/plan/execute 阶段,基于 Module Context Index)
|
|
754
793
|
if (['brainstorm', 'plan', 'execute'].includes(stageName) && projectName) {
|
|
755
794
|
const effectiveSpecBase = platformOpts?.specRoot || join(cwd, '.sillyspec')
|
|
@@ -1416,7 +1455,9 @@ async function runStage(pm, progress, stageName, cwd, changeName, skipApproval =
|
|
|
1416
1455
|
const specBase = platformOpts.specRoot || join(cwd, '.sillyspec')
|
|
1417
1456
|
// 状态转换校验
|
|
1418
1457
|
const prevStage = progress.currentStage || ''
|
|
1419
|
-
|
|
1458
|
+
// task-07: 提取 prevStage 的 stageData,传给 checkTransition 检测 failed_post_check 门控
|
|
1459
|
+
const fromStageData = (progress.stages && prevStage && progress.stages[prevStage]) || undefined
|
|
1460
|
+
const transition = checkTransition(prevStage, stageName, fromStageData ? { fromStageData } : {})
|
|
1420
1461
|
if (!transition.allowed) {
|
|
1421
1462
|
console.error(`❌ 阶段转换不允许: ${prevStage || '(起始)'} → ${stageName}`)
|
|
1422
1463
|
console.error(` 原因: ${transition.reason}`)
|
|
@@ -1461,6 +1502,18 @@ async function runStage(pm, progress, stageName, cwd, changeName, skipApproval =
|
|
|
1461
1502
|
}
|
|
1462
1503
|
}
|
|
1463
1504
|
|
|
1505
|
+
// ── execute 阶段启动时固定 executeRunId ──
|
|
1506
|
+
let currentExecuteRunId = null
|
|
1507
|
+
if (stageName === 'execute') {
|
|
1508
|
+
const { generateExecuteRunId, getLatestExecuteRunId } = await import('./task-review.js')
|
|
1509
|
+
const execSpecBase = platformOpts?.specRoot || join(cwd, '.sillyspec')
|
|
1510
|
+
const runtimeRoot = join(execSpecBase, '.runtime')
|
|
1511
|
+
currentExecuteRunId = getLatestExecuteRunId(runtimeRoot) || generateExecuteRunId()
|
|
1512
|
+
// 写入 runtime 标记文件,确保整个 execute 生命周期内 runId 不变
|
|
1513
|
+
mkdirSync(runtimeRoot, { recursive: true })
|
|
1514
|
+
writeFileSync(join(runtimeRoot, 'current-execute-run-id'), currentExecuteRunId + '\n')
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1464
1517
|
// 自动探测 currentChange
|
|
1465
1518
|
if (autoDetectChange(progress, cwd)) {
|
|
1466
1519
|
progress.lastActive = new Date().toLocaleString('zh-CN', { hour12: false })
|
|
@@ -2150,16 +2203,15 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
2150
2203
|
if (stageName === 'scan' && steps[currentIdx]?.name === '构建扫描项目列表') {
|
|
2151
2204
|
// 解析项目列表:从 step 2 输出提取,或回退读取 projects/*.yaml
|
|
2152
2205
|
let projectNames = []
|
|
2153
|
-
//
|
|
2154
|
-
const sanitizeProjectName = (name) => {
|
|
2155
|
-
const clean = name.replace(/[^a-zA-Z0-9_\-.]/g, '').trim()
|
|
2156
|
-
return clean || null
|
|
2157
|
-
}
|
|
2206
|
+
// sanitizeProjectName 已提取到模块顶层(含字母校验 + 长度≥2)
|
|
2158
2207
|
if (outputText) {
|
|
2159
2208
|
// 匹配方式 1: "1. project-name" 编号列表
|
|
2160
|
-
|
|
2209
|
+
// 正则收紧:token 必须以字母开头,避免误捕获纯数字 "0"/"7" 和步骤说明中英文行
|
|
2210
|
+
const numbered = outputText.match(/^\s*\d+\.\s+([a-zA-Z][\w\-.]*)/gm)
|
|
2161
2211
|
if (numbered) {
|
|
2162
|
-
|
|
2212
|
+
// task-05 B2 延伸修正:原 /[—\-:].*$/ 会把 ASCII 连字符当后缀分隔符,
|
|
2213
|
+
// 把 order-service 切成 order。现只针对中文长破折号 `—`(LLM 输出列表时常作分隔符)。
|
|
2214
|
+
const raw = numbered.map(m => m.replace(/^\s*\d+\.\s+/, '').replace(/—.*$/, '').trim())
|
|
2163
2215
|
projectNames = raw.map(sanitizeProjectName).filter(Boolean)
|
|
2164
2216
|
if (projectNames.length > 0) { stageData.scanMeta = stageData.scanMeta || {}; stageData.scanMeta.projectListParsed = true; }
|
|
2165
2217
|
}
|
|
@@ -2434,8 +2486,18 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
2434
2486
|
stageData.status = SCAN_STATUS.FAILED_POST_CHECK
|
|
2435
2487
|
stageData.completedAt = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
2436
2488
|
await pm._write(cwd, progress, changeName)
|
|
2489
|
+
triggerSync(cwd, changeName, platformOpts)
|
|
2437
2490
|
console.error(`\n❌ scan post-check 失败,状态设为 failed_post_check。不允许 clean success。`)
|
|
2438
2491
|
console.error(` 请检查上方错误信息并修复后重新 scan。`)
|
|
2492
|
+
// 平台模式:exit(1) 让 daemon/SillyHub 感知非 0 退出码(manifest.json 已落盘,不会被撤销)
|
|
2493
|
+
if (platformOpts.specRoot || platformOpts.runtimeRoot) {
|
|
2494
|
+
console.error(' 平台模式:CLI 将以 exit code 1 退出,通知 SillyHub scan 失败。')
|
|
2495
|
+
process.exit(1)
|
|
2496
|
+
}
|
|
2497
|
+
// 接口与 plan contract (run.js:2551 附近 plan 失败分支) 对齐:
|
|
2498
|
+
// 返回 { stageCompleted:false, currentIdx, nextPendingIdx: currentIdx }
|
|
2499
|
+
// 让上层 runStage 走"完成但不推进"分支,--done 被拒
|
|
2500
|
+
return { stageCompleted: false, currentIdx, nextPendingIdx: currentIdx }
|
|
2439
2501
|
} else if (postResult.status === 'completed_with_warnings') {
|
|
2440
2502
|
// 警告不阻止完成,但记录
|
|
2441
2503
|
stageData.status = 'completed'
|
|
@@ -2559,6 +2621,56 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
2559
2621
|
}
|
|
2560
2622
|
}
|
|
2561
2623
|
}
|
|
2624
|
+
|
|
2625
|
+
// ── Execute Task Review Gate:所有 task 必须有 review.json 且 verdict 通过 ──
|
|
2626
|
+
if (stageName === 'execute') {
|
|
2627
|
+
try {
|
|
2628
|
+
const { validateTaskReviews, printReviewResult, writeVerifyRequiredEvidence, getLatestExecuteRunId, generateExecuteRunId } = await import('./task-review.js')
|
|
2629
|
+
const effectiveSpecBase = platformOpts?.specRoot || specBase
|
|
2630
|
+
const planFile = resolveChangeDir(cwd, progress, platformOpts?.specRoot)
|
|
2631
|
+
const planPath = planFile ? join(planFile, 'plan.md') : null
|
|
2632
|
+
|
|
2633
|
+
if (planPath && existsSync(planPath)) {
|
|
2634
|
+
const planContent = readFileSync(planPath, 'utf8')
|
|
2635
|
+
const runtimeRoot = join(effectiveSpecBase, '.runtime')
|
|
2636
|
+
|
|
2637
|
+
// 找到 execute run id:优先从 runtime 目录找最新,否则生成新的
|
|
2638
|
+
let executeRunId = getLatestExecuteRunId(runtimeRoot)
|
|
2639
|
+
if (!executeRunId) {
|
|
2640
|
+
executeRunId = generateExecuteRunId()
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
const reviewResult = validateTaskReviews({ planContent, runtimeRoot, executeRunId })
|
|
2644
|
+
printReviewResult(reviewResult)
|
|
2645
|
+
|
|
2646
|
+
if (!reviewResult.ok) {
|
|
2647
|
+
// Task review 校验失败,阻断 execute 完成
|
|
2648
|
+
// 检查是否存在 checkbox 已勾但 review 不通过的情况
|
|
2649
|
+
const uncheckedTasks = reviewResult.errors.filter(e => e.includes('缺少 review.json'))
|
|
2650
|
+
if (uncheckedTasks.length > 0) {
|
|
2651
|
+
console.error('\n⚠️ 部分任务已在 plan.md 中勾选,但 review.json 不存在。')
|
|
2652
|
+
console.error(' 请取消勾选这些任务的 checkbox,或补充对应的 review.json。')
|
|
2653
|
+
}
|
|
2654
|
+
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
2655
|
+
await pm._write(cwd, progress, changeName)
|
|
2656
|
+
triggerSync(cwd, changeName, platformOpts)
|
|
2657
|
+
return { stageCompleted: false, currentIdx, nextPendingIdx: currentIdx }
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
// cannot_verify 的 requiredEvidence 写入 change 目录,供 verify 阶段消费
|
|
2661
|
+
if (reviewResult.requiredEvidence.length > 0) {
|
|
2662
|
+
const evidencePath = writeVerifyRequiredEvidence(join(effectiveSpecBase, 'changes', changeName), reviewResult.requiredEvidence)
|
|
2663
|
+
if (evidencePath) {
|
|
2664
|
+
console.log(`📄 verify-required-evidence.json 已写入: ${evidencePath}`)
|
|
2665
|
+
console.log(' verify 阶段必须满足这些证据要求。')
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
} catch (e) {
|
|
2670
|
+
console.warn(`⚠️ Task Review Gate 异常: ${e.message}`)
|
|
2671
|
+
// 不阻断,但记录异常
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2562
2674
|
} else if (actualCompleted < actualTotal) {
|
|
2563
2675
|
// 实际步骤未全部完成,跳过 validator(状态可能不同步)
|
|
2564
2676
|
console.log(`\n⚠️ 阶段校验跳过:${actualTotal} 步中仅 ${actualCompleted} 步标记为已完成,可能存在状态不同步。如确认阶段已完成,请运行 --status 确认。`)
|
|
@@ -2623,8 +2735,17 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
2623
2735
|
const { loadWorkflow, runPostCheck, formatCheckReport, saveWorkflowRun } = await import('./workflow.js')
|
|
2624
2736
|
const wf = loadWorkflow(cwd, 'scan-docs')
|
|
2625
2737
|
if (wf) {
|
|
2626
|
-
//
|
|
2627
|
-
|
|
2738
|
+
// 确定当前项目(优先级链):
|
|
2739
|
+
// progress.project (dbProjectName,平台模式真实项目名,与 outputStep 占位符渲染对齐)
|
|
2740
|
+
// > change?.project (变更对象的项目字段,平台模式 change 创建时传入)
|
|
2741
|
+
// > steps[idx].project (perProject 展开标记,兼容旧模式)
|
|
2742
|
+
// > steps[idx].name 正则提取 [xxx] 后缀
|
|
2743
|
+
// > null(回退检查所有项目)
|
|
2744
|
+
// task-05 修复:日志显示项目名变 frontend 是 perProject 误展开 bug,
|
|
2745
|
+
// 用 progress.project(与 outputStep 占位符渲染路径一致)修正 myaaa/frontend 分裂。
|
|
2746
|
+
const currentProjectName = progress.project
|
|
2747
|
+
|| (typeof change !== 'undefined' && change ? change.project : null)
|
|
2748
|
+
|| steps[currentIdx].project
|
|
2628
2749
|
|| (steps[currentIdx].name.match(/\[([^\]]+)\]\s*$/) || [])[1]
|
|
2629
2750
|
|| null
|
|
2630
2751
|
|
|
@@ -2644,7 +2765,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
2644
2765
|
|
|
2645
2766
|
let anyFailed = false
|
|
2646
2767
|
for (const pName of projectsToCheck) {
|
|
2647
|
-
const result = runPostCheck(wf, cwd, pName)
|
|
2768
|
+
const result = runPostCheck(wf, cwd, pName, {}, specBase)
|
|
2648
2769
|
const report = formatCheckReport(result)
|
|
2649
2770
|
console.log(report)
|
|
2650
2771
|
if (result.status === 'fail') {
|
|
@@ -2667,6 +2788,10 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
2667
2788
|
}
|
|
2668
2789
|
if (anyFailed) {
|
|
2669
2790
|
console.log(`\n⚠️ 存在检查失败项,请按上面的重试提示修复后再继续。`)
|
|
2791
|
+
// task-07: 阻断推进(与 task-06 平台模式 scan-postcheck 失败分支 return 结构对齐)
|
|
2792
|
+
// scan 深度扫描产物校验未通过时,不允许 clean success / 进入下一 step,
|
|
2793
|
+
// 让上层走"完成但不推进"分支,--done 被拒。
|
|
2794
|
+
return { stageCompleted: false, currentIdx, nextPendingIdx: currentIdx }
|
|
2670
2795
|
}
|
|
2671
2796
|
}
|
|
2672
2797
|
} catch (e) {
|
|
@@ -2682,7 +2807,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
2682
2807
|
if (wf && changeName) {
|
|
2683
2808
|
const raw = JSON.stringify(wf)
|
|
2684
2809
|
const resolved = JSON.parse(raw.replace(/<change-name>/g, changeName))
|
|
2685
|
-
const result = runPostCheck(resolved, cwd, 'sillyspec')
|
|
2810
|
+
const result = runPostCheck(resolved, cwd, 'sillyspec', {}, specBase)
|
|
2686
2811
|
// 只报告 impact-analyzer 的结果(doc-syncer 是后续步骤)
|
|
2687
2812
|
const impactResult = (result.roles || []).find(r => r.id === 'impact-analyzer')
|
|
2688
2813
|
if (impactResult) {
|
package/src/stage-contract.js
CHANGED
|
@@ -587,9 +587,11 @@ export function getContract(stageName) {
|
|
|
587
587
|
* 校验状态转换是否允许
|
|
588
588
|
* @param {string} fromStage - 当前阶段(空字符串表示变更起始)
|
|
589
589
|
* @param {string} toStage - 目标阶段
|
|
590
|
+
* @param {{ fromStageData?: { status?: string } | undefined }} [options] - 可选,从 progress.stages[prevStage] 提取
|
|
590
591
|
* @returns {{ allowed: boolean, reason?: string }}
|
|
591
592
|
*/
|
|
592
|
-
export function checkTransition(fromStage, toStage) {
|
|
593
|
+
export function checkTransition(fromStage, toStage, options = {}) {
|
|
594
|
+
const { fromStageData } = options // { status?: string } | undefined
|
|
593
595
|
const contract = contracts[toStage]
|
|
594
596
|
if (!contract) {
|
|
595
597
|
return { allowed: false, reason: `未知阶段: ${toStage}` }
|
|
@@ -605,6 +607,17 @@ export function checkTransition(fromStage, toStage) {
|
|
|
605
607
|
return { allowed: true }
|
|
606
608
|
}
|
|
607
609
|
|
|
610
|
+
// task-07: failed_post_check 门控
|
|
611
|
+
// scan post-check 未通过时,禁止进入主流程的下游阶段(brainstorm/plan/execute/verify/archive)
|
|
612
|
+
// 必须先重跑 scan 修复。toStage === 'scan' 的重跑路径已被上方 fromStage === toStage 放行。
|
|
613
|
+
// fromStageData.status 缺失(旧数据)时门控不触发(向后兼容)。
|
|
614
|
+
if (fromStage === 'scan' && fromStageData?.status === 'failed_post_check' && toStage !== 'scan') {
|
|
615
|
+
return {
|
|
616
|
+
allowed: false,
|
|
617
|
+
reason: 'scan post-check 未通过(failed_post_check),需修复后重跑 scan 再进入 ' + toStage,
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
608
621
|
// archive 特殊处理:从 verify 来的允许,从其他主流程阶段来的需要校验
|
|
609
622
|
if (toStage === 'archive') {
|
|
610
623
|
if (fromStage === 'verify') {
|
package/src/stages/execute.js
CHANGED
|
@@ -531,10 +531,40 @@ ${taskList}
|
|
|
531
531
|
- 最后一个 Wave 完成后做一次全量编译验证
|
|
532
532
|
- 用户明确要求编译时
|
|
533
533
|
4. 每个任务完成后:
|
|
534
|
-
-
|
|
534
|
+
- **先写 review.json 再勾选 checkbox**(见下方 Task Review Gate)
|
|
535
535
|
- 记录改动文件和测试结果
|
|
536
536
|
5. 遇到 BLOCKED → 记录原因,选择:重试/跳过/停止
|
|
537
537
|
|
|
538
|
+
### Task Review Gate(必须执行,不可跳过)
|
|
539
|
+
|
|
540
|
+
每个子代理完成后、勾选 checkbox **之前**,你必须创建 task review。
|
|
541
|
+
|
|
542
|
+
**操作步骤:**
|
|
543
|
+
1. 读取当前 task 的 git diff(从 task 开始到完成的变更)
|
|
544
|
+
2. 对照 plan.md 中该 task 的描述和 tasks/task-XX.md(如果存在)检查实现是否符合要求
|
|
545
|
+
3. 写入 review.json 文件
|
|
546
|
+
4. **只有 review.json 写入成功后,才允许勾选 plan.md 中的 checkbox**
|
|
547
|
+
|
|
548
|
+
**review.json 路径:**
|
|
549
|
+
|
|
550
|
+
task-XX 对应:.sillyspec/.runtime/execute-runs/{EXECUTE_RUN_ID}/tasks/task-XX/review.json
|
|
551
|
+
|
|
552
|
+
本 execute run 的固定 ID 是:{EXECUTE_RUN_ID}
|
|
553
|
+
**所有 task 的 review.json 必须使用这个 ID,不要自行创建新目录。**
|
|
554
|
+
|
|
555
|
+
**review.json 必填字段:**
|
|
556
|
+
|
|
557
|
+
{ "schemaVersion": 1, "task": "task-XX", "base": "<git-base-commit>", "head": "<git-head-commit>",
|
|
558
|
+
"changedFiles": ["src/foo.js"], "specVerdict": "pass|fail|cannot_verify",
|
|
559
|
+
"qualityVerdict": "pass|fail|cannot_verify", "reviewerNotes": "评审说明",
|
|
560
|
+
"requiredEvidence": [] }
|
|
561
|
+
|
|
562
|
+
**评审铁律:**
|
|
563
|
+
- 不信任 implementer 自报结果,对照 diff 和 task brief 验证
|
|
564
|
+
- 只看当前 task 的 diff,不做全仓库漫游审查
|
|
565
|
+
- \`cannot_verify\` 只在确实无法验证且有待补充证据时使用,且 requiredEvidence 必须非空
|
|
566
|
+
- \`sillyspec run execute --done\` 会校验所有 task 的 review.json,缺失或 fail 会阻断完成
|
|
567
|
+
|
|
538
568
|
### 完成后
|
|
539
569
|
1. 为每个后端 router task,扫描变更文件提取 API 端点 artifact:
|
|
540
570
|
- 在变更文件中搜索所有 router 注册路径(@router.get/post/put/delete)
|
|
@@ -543,7 +573,6 @@ ${taskList}
|
|
|
543
573
|
2. 运行 sillyspec run execute --done --input "用户原始反馈" --output "Wave ${waveIndex} 结果摘要"`
|
|
544
574
|
}
|
|
545
575
|
|
|
546
|
-
|
|
547
576
|
/**
|
|
548
577
|
* 动态构建 execute 步骤列表
|
|
549
578
|
* @param {string|null} planFilePath - plan 文件路径,null 则用默认 3 Wave
|
package/src/stages/verify.js
CHANGED
|
@@ -62,6 +62,14 @@ export const definition = {
|
|
|
62
62
|
5. 加载代码规范:\`cat .sillyspec/docs/<project>/scan/CONVENTIONS.md 2>/dev/null\`
|
|
63
63
|
6. 标注每个文件的存在/不存在状态
|
|
64
64
|
|
|
65
|
+
### Execute Evidence 传递检查
|
|
66
|
+
7. 检查 verify-required-evidence.json 是否存在(由 execute 阶段 Task Review Gate 写入)
|
|
67
|
+
- 路径:变更目录下的 verify-required-evidence.json
|
|
68
|
+
- 如果存在 → 读取其中的 requiredEvidence 列表,逐条验证是否已满足
|
|
69
|
+
- 每条 evidence 必须在 verify-result.md 中给出明确结论(satisfied / missing / partial)
|
|
70
|
+
- 如果有任何 evidence 为 missing → verify 结论不能为 PASS
|
|
71
|
+
- 如果文件不存在 → 表示 execute 阶段无 cannot_verify 任务,正常继续
|
|
72
|
+
|
|
65
73
|
### 模块文档加载
|
|
66
74
|
7. 读取 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`(不存在则跳过以下步骤)
|
|
67
75
|
8. 根据 design.md 的文件变更清单匹配 _module-map.yaml 中的模块
|