sillyspec 3.28.3 → 3.28.5
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-auto/SKILL.md +54 -54
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -114
- package/.claude/skills/sillyspec-continue/SKILL.md +17 -17
- package/.claude/skills/sillyspec-execute/SKILL.md +222 -222
- package/.claude/skills/sillyspec-knowledge/SKILL.md +40 -40
- package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
- package/.claude/skills/sillyspec-propose/SKILL.md +16 -16
- package/.claude/skills/sillyspec-resume/SKILL.md +21 -21
- package/.claude/skills/sillyspec-state/SKILL.md +25 -25
- package/.claude/skills/sillyspec-workspace/SKILL.md +33 -33
- package/AGENTS.md +25 -0
- package/INSTRUCTIONS.md +12 -0
- package/package.json +1 -1
- package/results-review-tier.md +18 -0
- package/src/agent-session-log.js +4 -3
- package/src/archive-delta.js +82 -0
- package/src/change-delete.js +2 -1
- package/src/change-list.js +18 -3
- package/src/change-risk-profile.js +77 -6
- package/src/commit-suggest.js +52 -1
- package/src/config-cat.js +17 -1
- package/src/constants.js +3 -1
- package/src/decision-distill.js +45 -5
- package/src/decisions-io.js +96 -0
- package/src/design-facts.js +3 -0
- package/src/dispatch/probe.js +31 -8
- package/src/docs-check.js +102 -3
- package/src/doctor-diagnostics.js +441 -5
- package/src/foreign-declared.js +53 -2
- package/src/index.js +606 -22
- package/src/mcp-server.js +144 -0
- package/src/module-impact.js +84 -12
- package/src/modules.js +46 -34
- package/src/plan-adopt-waves.js +13 -5
- package/src/progress/change-registry.js +34 -0
- package/src/progress.js +75 -2
- package/src/quick-recommend.js +6 -1
- package/src/quicklog.js +70 -8
- package/src/review-dispatch.js +377 -0
- package/src/run/archive-distill.js +48 -0
- package/src/run/command.js +48 -13
- package/src/run/complete-handlers.js +215 -9
- package/src/run/complete.js +184 -2
- package/src/run/gate-snapshot.js +88 -0
- package/src/run/gates.js +206 -58
- package/src/run/prompt.js +150 -14
- package/src/run/quick-audit.js +38 -13
- package/src/run/shared.js +183 -12
- package/src/run/stage.js +52 -2
- package/src/run/verify-quality-scan.js +336 -0
- package/src/runtime-hygiene.js +84 -0
- package/src/scope-audit.js +735 -0
- package/src/sillyhub-mcp/client.js +58 -3
- package/src/spec-sync.js +63 -8
- package/src/stage-contract.js +83 -3
- package/src/stage-review-checklist.js +44 -0
- package/src/stage-review.js +125 -4
- package/src/stages/archive.js +60 -167
- package/src/stages/brainstorm.js +7 -17
- package/src/stages/doctor.js +41 -515
- package/src/stages/execute.js +13 -10
- package/src/stages/plan-postcheck.js +191 -7
- package/src/stages/plan.js +7 -35
- package/src/stages/quick.js +11 -11
- package/src/stages/verify.js +24 -39
- package/src/sync-noise.js +158 -0
- package/src/sync.js +93 -27
- package/src/task-review.js +133 -17
- package/src/taskcard.js +46 -3
- package/src/validate-artifacts.js +163 -0
- package/src/verify-facts-schema.js +141 -0
- package/src/verify-postcheck.js +347 -29
- package/src/verify-probes.js +275 -24
- package/src/workflow.js +6 -0
- package/src/worktree-apply.js +167 -24
- package/src/worktree.js +5 -1
- package/templates/agents-instruction.md +1 -0
package/src/stages/execute.js
CHANGED
|
@@ -6,6 +6,7 @@ import { renderDispatchInstruction } from '../dispatch/strategy.js'
|
|
|
6
6
|
import { isPathASupported } from '../dispatch/backends/sillyhub-mcp.js'
|
|
7
7
|
import { readMcpConfig } from '../sillyhub-mcp/config.js'
|
|
8
8
|
import { gitQuiet } from '../git-helper.js'
|
|
9
|
+
import { REVIEW_CHECKLISTS } from '../stage-review-checklist.js'
|
|
9
10
|
import { parseRepo } from './plan-postcheck.js'
|
|
10
11
|
// 模块卡分级解析(token 成本优化 P0a)。注意:module-resolve.js 反向 import 本文件的
|
|
11
12
|
// parseTaskRegistry(注册表同口径)——双向引用均为函数声明 + 调用时解引(无模块初始化期
|
|
@@ -323,6 +324,7 @@ worktree 路径 + 分支名 + 模式
|
|
|
323
324
|
|
|
324
325
|
如上为 CLI 用 git 事实算出的本变更触及模块文档欠账(无输出=无欠账或归属数据缺失)。
|
|
325
326
|
欠账处理:Wave 收尾时顺手同步对应模块卡,不必为此停下。变更索引类条目追加到卡同目录 \`<module>.changelog.md\` sidecar(无则创建;可先跑 \`sillyspec modules split-changelog\` 迁出历史段)——勿把历史条目堆回模块卡正文(卡是子代理的读取税)。
|
|
327
|
+
⚠️ **铁律:模块卡 / sidecar 一律写主仓绝对路径 \`{SPEC_ROOT}/docs/<project>/modules/\`(CLI 已替换为绝对路径)**——在 worktree 内执行时**绝不**写 worktree checkout 出来的 \`.sillyspec\` 副本(该副本随 worktree cleanup 整目录蒸发,主仓永远读不到;2026-09-10 实证 task-13 子代理写副本、归档阶段被迫 checkout 补救)。
|
|
326
328
|
|
|
327
329
|
### 铁律
|
|
328
330
|
- **不要询问用户确认频率**,默认 wave 模式;用户已明确口头指定时遵从其指定`,
|
|
@@ -342,13 +344,12 @@ const acceptanceSteps = [
|
|
|
342
344
|
tier: {REVIEW_TIER}({REVIEW_TIER_REASON})
|
|
343
345
|
- tier=self:当前 agent 汇总执行(对照 design.md 逐项检查 + 偏差说明)
|
|
344
346
|
- tier=independent:必须用 Agent tool 启动一个独立的 QA 子代理(独立上下文,不共享实现者的分析),子代理对照 design.md 逐项检查实现一致性并输出 review.json。review.json 产物契约(CLI Stage Review Gate 将硬校验,schema + 完整示例 + docHash 算法如下,照抄改值;reviewedFiles 除主文档 design.md 外可追加 git diff 涉及的源码文件):
|
|
347
|
+
宿主环境无 Agent tool 可用(调用报 Unknown agent / Available agents: none)→ 不卡死:主代理切换为审查者角色自审替代,reviewerNotes 首行记录「降级:环境无子代理可用」,逐条结论附源码锚点(file:line 或 grep/read 证据)补偿独立性。
|
|
345
348
|
{REVIEW_JSON_CONTRACT}
|
|
346
349
|
该 acceptance review 同时覆盖"代码审查"视角(风格/bug/安全/冗余),后续代码审查步骤仅需轻量复审。
|
|
347
350
|
|
|
348
351
|
**审查范围分级(省重复消耗,task review 已覆盖的不全量重审)**:每个 task 在 Task Review Gate 已产出 review.json({SPEC_ROOT}/.runtime/execute-runs/{EXECUTE_RUN_ID}/tasks/task-XX/review.json,specVerdict/qualityVerdict 双 pass)。QA 子代理按它分层:双 pass 的 task 只**抽查**(读 1-2 个核心 diff 文件抽验 reviewerNotes 与实际改动相符,不必逐文件重审);未双 pass(fail/cannot_verify/缺失)的 task 必须全量重审。无论抽查还是全量,以下三项始终必查——task review 铁律是"只看当前 task 的 diff",这三项是它覆盖不到、只有 stage review 能兜住的:
|
|
349
|
-
1
|
|
350
|
-
2. design.md 整体对照(最终实现拼起来是否仍符合设计意图,而非仅各 task 局部合规)
|
|
351
|
-
3. 组装行为(全量测试/构建/启动通过——单 task 测试全绿 ≠ 组装正确)
|
|
352
|
+
${REVIEW_CHECKLISTS.execute.map((item, index) => ' ' + (index + 1) + '. ' + item).join('\n')}
|
|
352
353
|
|
|
353
354
|
**产物唯一化(省重复消耗)**:本步逐项对照结论**只落盘一份**——直接写进 review.json 的 \`checklist\` 数组(item=设计要点/FR/决策,note=实现状态 ✅/⚠️/❌ + 偏差说明 + commit 锚点),reviewerNotes 写汇总。**不要**另写独立的 design-check.md 长文(同一份 design×diff 二次消费;2026-08-22 实测该重复一遍 ≈8 分钟全量重读)。
|
|
354
355
|
**gate 重试修复**:review.json 落盘后若 design.md 又有改版,gate 会**自动机械重算 docHash 放行**(verdict/checklist 保留,结论是否仍适用于新文档需人工确认)——**不要重做审查**(重做=同一材料第三遍);主文档路径错/缺失不会被自动修复,按 gate 报错修正 reviewedFiles[0],或跑 \`sillyspec register-stage-review --change <变更名> --stage execute --refresh-hash\`。
|
|
@@ -1061,7 +1062,7 @@ ${indexLines}
|
|
|
1061
1062
|
${crossLines}${crossWorktreeSection}${crossLegacySection}
|
|
1062
1063
|
|
|
1063
1064
|
**回收跨仓 task(head 锡点,CLI 自动落盘,勿手写):**
|
|
1064
|
-
- 子代理完成 commit 后,正常写 review.json(verdict/notes
|
|
1065
|
+
- 子代理完成 commit 后,正常写 review.json(verdict/notes)即可——checkbox 由 CLI 按 review verdict 自动勾选(review write 落盘即勾)。execute \`--done\` 时 CLI 自动对跨仓仓(worktree 模式=该仓 worktree,legacy=仓根)\`git rev-parse HEAD\` 写入该 task 卡 \`head_commit:\`(幂等,已存在不覆盖——你若手写了精确锚点则以你的为准)。
|
|
1065
1066
|
- review.json 的 mechanics 字段(\`base\`/\`head\`/\`changedFiles\`/\`diffPaths\`)无需手算:\`base\` 取 task 卡 \`base_commit\`、\`head\` 取 task 卡 \`head_commit\`,写完跑 \`sillyspec backfill-reviews --change <变更名> --adopt\` 一键代填(verdict 保留)。
|
|
1066
1067
|
`
|
|
1067
1068
|
}
|
|
@@ -1111,7 +1112,9 @@ ${workdirLines}
|
|
|
1111
1112
|
\`\`\`
|
|
1112
1113
|
|
|
1113
1114
|
### 注意
|
|
1114
|
-
蓝图文件(tasks.md / design.md / proposal.md / requirements.md)在主工作区 {SPEC_ROOT}/changes/<change>/ 下(CLI 已替换为主仓绝对路径),它们可能不在 worktree 中。读取蓝图时使用主工作区路径,不要拼接到 worktree
|
|
1115
|
+
蓝图文件(tasks.md / design.md / proposal.md / requirements.md)在主工作区 {SPEC_ROOT}/changes/<change>/ 下(CLI 已替换为主仓绝对路径),它们可能不在 worktree 中。读取蓝图时使用主工作区路径,不要拼接到 worktree 路径下。
|
|
1116
|
+
|
|
1117
|
+
⚠️ **铁律:spec 流程产物只写主仓 {SPEC_ROOT},绝不写进 worktree 副本**——包括 module-impact.md / knowledge 条目 / 模块卡与 \`<module>.changelog.md\` sidecar。在 worktree 内发现 \`.sillyspec/\` 目录是 checkout 副本,写进去的任何内容都会随 worktree cleanup 整目录蒸发(2026-09-10 实证:模块文档写副本、归档被迫 checkout 补救)。子代理 prompt 中涉及此类产物时,必须原样带上主仓绝对路径。
|
|
1115
1118
|
`
|
|
1116
1119
|
: ''
|
|
1117
1120
|
}
|
|
@@ -1158,7 +1161,7 @@ ${workdirLines}
|
|
|
1158
1161
|
你的角色是调度者 + 审查者(batch 只合并实现、不合并审查):
|
|
1159
1162
|
1. 为每个任务启动一个子代理(Agent tool),或按上述三条件把多个任务合并为一个 batch 子代理,同 Wave 内可并行
|
|
1160
1163
|
2. 子代理完成后审查结果——batch 子代理只做实现与自验,task 审查、review.json 产出与 checkbox 勾选仍归你(主 agent),在子代理返回后逐 task 进行;审查 batch 报告时逐 task 对照 allowed_paths 检查改动文件清单有无越权
|
|
1161
|
-
3. 勾选 tasks.md
|
|
1164
|
+
3. checkbox 由 CLI 自动勾选(review write 落盘即按 verdict 勾选 tasks.md;勿手动勾选)
|
|
1162
1165
|
4. 记录改动文件和测试结果
|
|
1163
1166
|
|
|
1164
1167
|
${worktreeSection}${crossRepoCommitSection}${dispatchSection}
|
|
@@ -1211,21 +1214,21 @@ ${taskList}
|
|
|
1211
1214
|
- 用户明确要求编译时
|
|
1212
1215
|
4. 每个任务完成后:
|
|
1213
1216
|
- **在 worktree 内 git add -A && git commit -m "<task-NN 摘要>"**(坑 subagent-uncommitted-newfile-apply3way,2026-08-22 实证:纯新增文件不 commit 时 apply 的 git apply --3way 报 "does not exist in index" 直接炸——未 commit 的新文件不在 base commit 也不在 index,patch 生成取不到。commit 后 base..HEAD diff 完整、apply 顺畅,review.json 的 head 也有真实锚点)
|
|
1214
|
-
-
|
|
1215
|
-
-
|
|
1217
|
+
- **写 review.json 即可**(checkbox 由 CLI 自动勾选,见下方 Task Review Gate)
|
|
1218
|
+
- **任务边界上报(每任务一次,主仓根目录)**:review write 落盘(CLI 已自动勾选)后跑一次 \`sillyspec platform sync --change <change-name>\`——以任务粒度把「最后信号」(last_pushed_at)与 tasks.md 勾选状态推上平台(变更中心「进行中」可见性);未连接平台时该命令静默跳过,无需先检查连接状态
|
|
1216
1219
|
- **既跑 lint check 也跑 formatter**:凡变更涉及的源码跑项目的 lint 检查 **和** 格式化(如 \`ruff format\` / \`prettier --write\`),不要只跑 check——只 check 不 format 会把格式问题留到 commit 时被 pre-commit hook 拦截(worktree 内二进制可能缺失,先 \`which <bin>\` 确认,缺则 \`uv tool install\` / \`uv sync\`)
|
|
1217
1220
|
- 记录改动文件和测试结果
|
|
1218
1221
|
5. 遇到 BLOCKED → 记录原因,选择:重试/跳过/停止
|
|
1219
1222
|
|
|
1220
1223
|
### Task Review Gate
|
|
1221
1224
|
|
|
1222
|
-
|
|
1225
|
+
每个子代理完成后,你必须创建 task review(review write 落盘后 CLI 自动勾选 checkbox——P2-f 起勾选唯一写入者是 CLI)。
|
|
1223
1226
|
|
|
1224
1227
|
**操作步骤:**
|
|
1225
1228
|
1. 读取当前 task 的 git diff(从 task 开始到完成的变更)
|
|
1226
1229
|
2. 对照 plan.md 中该 task 的描述和 tasks/task-XX.md(如果存在)检查实现是否符合要求
|
|
1227
1230
|
3. 写入 review.json 文件
|
|
1228
|
-
4.
|
|
1231
|
+
4. **禁止手动勾选 tasks.md 的 checkbox**(P2-f task 真源归一:review.json verdict 是唯一真源,tasks.md 勾选是它的显示态,唯一写入者是 CLI——review write 落盘即勾、execute --done 时 autoCheckPlanFromReviews 兜底;agent 手勾与机器勾的漂移面就此退役)
|
|
1229
1232
|
|
|
1230
1233
|
**review.json 路径:**
|
|
1231
1234
|
|
|
@@ -1379,6 +1379,148 @@ export function validatePlanArtifacts(changeDir) {
|
|
|
1379
1379
|
// 执行入口(从 run.js 迁移)
|
|
1380
1380
|
// ═══════════════════════════════════════════════════════════════
|
|
1381
1381
|
|
|
1382
|
+
/**
|
|
1383
|
+
* plan --done 自动生成 module-impact.md 首版(刀②,2026-09-08 轮次经济学)。
|
|
1384
|
+
*
|
|
1385
|
+
* 文件×模块归属是机械分类(module-map 前缀匹配),此前 plan 审查步 prompt 让 agent 手写
|
|
1386
|
+
* 首版——章节标题写变体会被 archive contains_sections 硬拦返工(prompt 自认「agent 只能从
|
|
1387
|
+
* gate 报错反推格式」)。改为 --done 时 CLI 用 design 文件变更清单(main 段,代码未写、
|
|
1388
|
+
* 无 diff 可取)生成骨架,影响类型列留 <!--TODO--> 由 execute/verify 按实际 diff 回填。
|
|
1389
|
+
*
|
|
1390
|
+
* 边界:已存在不覆盖;scale=small 豁免(与 stage-contract plan.module-impact.exists 的
|
|
1391
|
+
* condition 同判法);生成失败(无 module-map / 清单为空)不阻断——与旧手写路径行为一致,
|
|
1392
|
+
* 由 check 1e 的 validatePlanOutputs 报缺失,agent 按 gate 提示兜底手写。
|
|
1393
|
+
*
|
|
1394
|
+
* @param {string} changeDir - 变更目录
|
|
1395
|
+
* @param {string} cwd - 仓库根
|
|
1396
|
+
* @param {string|null} [specRoot] - 规范根(平台模式显式传入)
|
|
1397
|
+
* @returns {Promise<{status: 'generated'|'skipped', reason?: string, matchedCount?: number, unmatchedCount?: number}>}
|
|
1398
|
+
*/
|
|
1399
|
+
export async function generatePlanModuleImpactFirstVersion(changeDir, cwd, specRoot = null) {
|
|
1400
|
+
const designPath = pJoin(changeDir, 'design.md')
|
|
1401
|
+
const impactPath = pJoin(changeDir, 'module-impact.md')
|
|
1402
|
+
if (!existsSync(designPath)) return { status: 'skipped', reason: 'no-design' }
|
|
1403
|
+
if (existsSync(impactPath)) return { status: 'skipped', reason: 'exists' }
|
|
1404
|
+
|
|
1405
|
+
const scale = parseFrontmatterScalar(readFileSync(designPath, 'utf8'), 'scale')
|
|
1406
|
+
if (scale === 'small') return { status: 'skipped', reason: 'scale-small' }
|
|
1407
|
+
|
|
1408
|
+
// 声明来源:design 文件变更清单 main 段(跨仓其他段的路径相对各仓根,不入主仓 module-map 归类)
|
|
1409
|
+
const { byRepo } = parseDesignCoverageByRepo(designPath)
|
|
1410
|
+
const mainFiles = [...(byRepo.get('main') || [])]
|
|
1411
|
+
if (mainFiles.length === 0) return { status: 'skipped', reason: 'no-declared-files' }
|
|
1412
|
+
|
|
1413
|
+
const { generateModuleImpactSkeleton } = await import('../module-impact.js')
|
|
1414
|
+
const result = generateModuleImpactSkeleton({
|
|
1415
|
+
cwd,
|
|
1416
|
+
changeName: basename(changeDir),
|
|
1417
|
+
specDir: specRoot,
|
|
1418
|
+
sourceFiles: mainFiles,
|
|
1419
|
+
origin: 'plan --done CLI(design 声明清单 × module-map 前缀匹配)',
|
|
1420
|
+
})
|
|
1421
|
+
if (!result) return { status: 'skipped', reason: 'no-module-map' }
|
|
1422
|
+
|
|
1423
|
+
writeFileSync(impactPath, result.markdown)
|
|
1424
|
+
return { status: 'generated', matchedCount: result.matchedCount, unmatchedCount: result.unmatchedCount }
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
/**
|
|
1428
|
+
* Wave 提案验证(2026-09-09-plan-derived FR-01,提案-验证-落盘三段式的验证段):
|
|
1429
|
+
* ①蓝图一致性(validateBlueprintConsistency 经 opts.waves 注入提案分组——读侧注入不改 plan.md)
|
|
1430
|
+
* ②逐 Wave allowed_paths 两两交集(topo 只看依赖看不见文件重叠;合并手工安全串行可能收敛出
|
|
1431
|
+
* 同 Wave 共享路径冲突——提案必须比它要替换的违规布局更干净才可落盘)。
|
|
1432
|
+
* @returns {string[]} 冲突明细(空数组 = 提案干净可写)
|
|
1433
|
+
*/
|
|
1434
|
+
function validateWaveProposal(changeDir, waves, repoRegistry = null) {
|
|
1435
|
+
const conflicts = []
|
|
1436
|
+
try {
|
|
1437
|
+
const consistency = validateBlueprintConsistency(changeDir, { repoRegistry, waves })
|
|
1438
|
+
for (const e of consistency.errors || []) conflicts.push(`蓝图一致性:${e}`)
|
|
1439
|
+
} catch (e) {
|
|
1440
|
+
conflicts.push(`蓝图一致性复跑异常:${e?.message || e}`)
|
|
1441
|
+
}
|
|
1442
|
+
// 逐 Wave allowed_paths 两两交集
|
|
1443
|
+
try {
|
|
1444
|
+
const tasksDir = pJoin(changeDir, 'tasks')
|
|
1445
|
+
if (existsSync(tasksDir)) {
|
|
1446
|
+
for (const taskFiles of (waves || [])) {
|
|
1447
|
+
const pathsPerTask = []
|
|
1448
|
+
for (const tf of taskFiles) {
|
|
1449
|
+
const card = readFileSync(pJoin(tasksDir, `${tf}.md`), 'utf8')
|
|
1450
|
+
pathsPerTask.push(new Set(parseAllowedPaths(card)))
|
|
1451
|
+
}
|
|
1452
|
+
for (let i = 0; i < pathsPerTask.length; i++) {
|
|
1453
|
+
for (let j = i + 1; j < pathsPerTask.length; j++) {
|
|
1454
|
+
const overlap = [...pathsPerTask[i]].filter(x => pathsPerTask[j].has(x))
|
|
1455
|
+
if (overlap.length > 0) conflicts.push(`同 Wave ${taskFiles[i]} × ${taskFiles[j]} 共享路径:${overlap.slice(0, 3).join('、')}`)
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
} catch { /* 卡片读取失败按无重叠证据 → 冲突留一致性段兜底 */ }
|
|
1461
|
+
return conflicts
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
/** plan_level 客观复核阈值单点(2026-09-09-plan-derived FR-03,D-003@v1) */
|
|
1465
|
+
const PLAN_LEVEL_SIGNALS = { files: 8, modules: 2 }
|
|
1466
|
+
|
|
1467
|
+
/**
|
|
1468
|
+
* plan_level 第二把尺子(不接管,仅 warning):design 文件清单数 / 模块跨度 / task 数
|
|
1469
|
+
* 与声明档位比对。fail-open(design/map 读取失败 → null 零输出)。
|
|
1470
|
+
* @returns {string|null} warning 文案
|
|
1471
|
+
*/
|
|
1472
|
+
async function reviewPlanLevelSignal(changeDir, planLevel) {
|
|
1473
|
+
try {
|
|
1474
|
+
const designPath = pJoin(changeDir, 'design.md')
|
|
1475
|
+
if (!existsSync(designPath) || !planLevel) return null
|
|
1476
|
+
const { byRepo } = parseDesignCoverageByRepo(designPath)
|
|
1477
|
+
const fileList = [...(byRepo.get('main') || [])]
|
|
1478
|
+
// 模块跨度:module-map 前缀命中去重(未命中文件不计——防新模块假信号)。
|
|
1479
|
+
// map 来源 = parseModuleMapPaths(module-impact.js 同款扫描 docs/<p>/modules/_module-map.yaml,
|
|
1480
|
+
// 免 project 名——loadModuleMap(specRoot, null) 会因第二参 null 短路恒返 null,勿用)。
|
|
1481
|
+
let moduleIds = new Set()
|
|
1482
|
+
try {
|
|
1483
|
+
const { parseModuleMapPaths } = await import('../module-impact.js')
|
|
1484
|
+
const specRoot = pJoin(changeDir, '..', '..')
|
|
1485
|
+
let mapText = null
|
|
1486
|
+
try {
|
|
1487
|
+
for (const d of readdirSync(pJoin(specRoot, 'docs'), { withFileTypes: true })) {
|
|
1488
|
+
if (!d.isDirectory()) continue
|
|
1489
|
+
const cand = pJoin(specRoot, 'docs', d.name, 'modules', '_module-map.yaml')
|
|
1490
|
+
if (existsSync(cand)) { mapText = readFileSync(cand, 'utf8'); break }
|
|
1491
|
+
}
|
|
1492
|
+
} catch { /* 无 docs/ → 只用文件数信号 */ }
|
|
1493
|
+
if (mapText) {
|
|
1494
|
+
const prefixMap = parseModuleMapPaths(mapText)
|
|
1495
|
+
for (const f of fileList) {
|
|
1496
|
+
const hit = deriveModuleIdFor(f, prefixMap)
|
|
1497
|
+
if (hit) moduleIds.add(hit)
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
} catch { /* map 不可得 → 只用文件数信号 */ }
|
|
1501
|
+
const tasksDir = pJoin(changeDir, 'tasks')
|
|
1502
|
+
const taskCount = existsSync(tasksDir) ? readdirSync(tasksDir).filter(f => /^task-\d+\.md$/.test(f)).length : 0
|
|
1503
|
+
const big = fileList.length > PLAN_LEVEL_SIGNALS.files || moduleIds.size > PLAN_LEVEL_SIGNALS.modules
|
|
1504
|
+
const small = fileList.length <= 2 && moduleIds.size <= 1
|
|
1505
|
+
if (planLevel !== 'full' && big) {
|
|
1506
|
+
return `plan_level 声明 ${planLevel} 但客观规模信号偏大(design 清单 ${fileList.length} 文件 / ${moduleIds.size} 模块 / ${taskCount} task)——若确属轻量请在 plan.md 附一行理由,否则建议 full(tier=independent 独立审查)`
|
|
1507
|
+
}
|
|
1508
|
+
if (planLevel === 'full' && small && taskCount <= 2) {
|
|
1509
|
+
return `plan_level 声明 full 但信号偏小(${fileList.length} 文件 / ${moduleIds.size} 模块)——可考虑降档 light/none 省审查仪式(advisory)`
|
|
1510
|
+
}
|
|
1511
|
+
return null
|
|
1512
|
+
} catch { return null }
|
|
1513
|
+
}
|
|
1514
|
+
function deriveModuleIdFor(filePath, mapYaml) {
|
|
1515
|
+
const posix = String(filePath).replace(/\\/g, '/')
|
|
1516
|
+
for (const [id, paths] of mapYaml) {
|
|
1517
|
+
for (const p of paths) {
|
|
1518
|
+
if (p.endsWith('/') ? posix.startsWith(p) : (posix === p || posix.startsWith(p + '/'))) return id
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
return null
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1382
1524
|
/**
|
|
1383
1525
|
* Plan postcheck 主函数:Wave 重排 + 一致性校验 + 产物确认
|
|
1384
1526
|
*
|
|
@@ -1490,6 +1632,14 @@ export async function executePlanPostcheck(context) {
|
|
|
1490
1632
|
console.log(` ✅ design.md ${coverage.designFiles.length} 个文件全部被 task allowed_paths 覆盖`)
|
|
1491
1633
|
}
|
|
1492
1634
|
|
|
1635
|
+
// ── 1e-0. module-impact.md 首版 CLI 自动生成(刀②)──
|
|
1636
|
+
// 在 1e 契约校验(plan.module-impact.exists)之前生成,使 scale≠small 的变更
|
|
1637
|
+
// 天然通过;生成失败不报错(缺失由 1e 统一报,口径不变)。
|
|
1638
|
+
const miGen = await generatePlanModuleImpactFirstVersion(changeDir, cwd, specRoot)
|
|
1639
|
+
if (miGen.status === 'generated') {
|
|
1640
|
+
console.log(` ✅ module-impact.md 首版已由 CLI 自动生成(归类 ${miGen.matchedCount} 个文件,未匹配 ${miGen.unmatchedCount} 个;影响类型列由 execute/verify 回填)`)
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1493
1643
|
// ── 1e. 阶段完成产物校验(plan stage contract)──
|
|
1494
1644
|
// plan 完成 gate 还会跑 stage-contract.validatePlanOutputs;为免「postcheck 通过 →
|
|
1495
1645
|
// stage gate 又报 module-impact 缺失/entry-point-wiring 未覆盖」的修一层撞一层,
|
|
@@ -1526,6 +1676,16 @@ export async function executePlanPostcheck(context) {
|
|
|
1526
1676
|
}
|
|
1527
1677
|
printSectionWarnings('target_files 声明', targetFiles.warnings)
|
|
1528
1678
|
|
|
1679
|
+
// ── 1g. plan_level 客观复核(第二把尺子,warning 不阻断——2026-09-09-plan-derived FR-03)──
|
|
1680
|
+
try {
|
|
1681
|
+
const planFm = readFileSync(pJoin(changeDir, 'plan.md'), 'utf8').match(/^plan_level:\s*(\w+)/m)
|
|
1682
|
+
if (planFm) {
|
|
1683
|
+
const levelWarn = await reviewPlanLevelSignal(changeDir, planFm[1])
|
|
1684
|
+
if (levelWarn) console.warn(`
|
|
1685
|
+
⚠️ ${levelWarn}`)
|
|
1686
|
+
}
|
|
1687
|
+
} catch { /* fail-open */ }
|
|
1688
|
+
|
|
1529
1689
|
// ── 聚合输出:一轮 --done 暴露全部失败项(坑6③)──
|
|
1530
1690
|
if (failures.length > 0) {
|
|
1531
1691
|
console.error(`\n❌ plan postcheck 失败(${failures.length} 类问题,已全部列出——一次修复后重跑,无需逐个迭代):`)
|
|
@@ -1607,10 +1767,35 @@ export async function executePlanPostcheck(context) {
|
|
|
1607
1767
|
}
|
|
1608
1768
|
}
|
|
1609
1769
|
if (directionViolations.length > 0) {
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1770
|
+
// ── 2026-09-09-plan-derived FR-01:违规自动修复(提案-验证-落盘三段式)──
|
|
1771
|
+
// 拓扑布局方向必然合法 → 产提案(不落盘)→ 双验证(蓝图一致性含 repoRegistry +
|
|
1772
|
+
// 逐 Wave allowed_paths 两两交集——topo 只看依赖看不见文件重叠)→ 干净才写。
|
|
1773
|
+
// 不干净保留手排原文照旧 throw(真冲突需人裁决:拆 Wave 或补 depends_on)。
|
|
1774
|
+
// 提案任何异常回落原 throw 路径(零新阻断面)。
|
|
1775
|
+
try {
|
|
1776
|
+
const { adoptPlanWaves } = await import('../plan-adopt-waves.js')
|
|
1777
|
+
const proposal = adoptPlanWaves({ changeDir, mode: 'proposal' })
|
|
1778
|
+
const conflicts = validateWaveProposal(changeDir, proposal.waves, repoRegistry)
|
|
1779
|
+
if (proposal.ok && proposal.rewritten && conflicts.length === 0) {
|
|
1780
|
+
writeFileSync(planPath, proposal.planMdDraft, 'utf8')
|
|
1781
|
+
console.log(`\n 🔧 Wave 依赖方向违规已按 depends_on 拓扑自动修复(${directionViolations.length} 处违规 → ${proposal.waves.length} 段重排,W 列同步 ${proposal.tableRowsUpdated} 行):`)
|
|
1782
|
+
for (const v of directionViolations) console.log(` · ${v}`)
|
|
1783
|
+
console.log(' 提案验证通过(蓝图一致性 + 同 Wave 文件面无交集);原文未被采纳的手工分组已按拓扑归一')
|
|
1784
|
+
} else {
|
|
1785
|
+
console.error(`\n❌ Wave 依赖方向违规(${directionViolations.length} 处),拓扑提案不可自动采纳(${!proposal.ok ? proposal.error : conflicts.length > 0 ? '同 Wave 文件面重叠' : '提案与现状等价'}):`)
|
|
1786
|
+
for (const v of directionViolations) console.error(` ${v}`)
|
|
1787
|
+
if (conflicts.length > 0) for (const c of conflicts) console.error(` · 提案冲突:${c}`)
|
|
1788
|
+
console.error(' 解法:把共享文件的任务拆到不同 Wave,或补 depends_on 使拓扑合法;sillyspec plan-adopt-waves 可预览拓扑分组')
|
|
1789
|
+
throw new Error('planPostcheck: Wave 依赖方向违规(depends_on 同 Wave / 后置 Wave)——' + directionViolations.join(';'))
|
|
1790
|
+
}
|
|
1791
|
+
} catch (e) {
|
|
1792
|
+
if (e && e.message && e.message.startsWith('planPostcheck:')) throw e
|
|
1793
|
+
// 提案链路异常 → 回落原硬拦(零新阻断面)
|
|
1794
|
+
console.error(`\n❌ Wave 依赖方向违规(${directionViolations.length} 处,自动修复提案链路异常:${e?.message || e}):`)
|
|
1795
|
+
for (const v of directionViolations) console.error(` ${v}`)
|
|
1796
|
+
console.error(' 解法:sillyspec plan-adopt-waves --change <变更名> 一键按 depends_on 拓扑重排 plan.md Wave 段,或手动调整分组')
|
|
1797
|
+
throw new Error('planPostcheck: Wave 依赖方向违规(depends_on 同 Wave / 后置 Wave)——' + directionViolations.join(';'))
|
|
1798
|
+
}
|
|
1614
1799
|
}
|
|
1615
1800
|
|
|
1616
1801
|
const sameStructure = waves.length === existingWaves.length &&
|
|
@@ -1622,10 +1807,9 @@ export async function executePlanPostcheck(context) {
|
|
|
1622
1807
|
|
|
1623
1808
|
if (sameStructure) {
|
|
1624
1809
|
console.log(' ✅ Wave 分组与拓扑排序一致,无需更新 plan.md')
|
|
1625
|
-
} else {
|
|
1626
|
-
console.log(' ⚠️ Wave 分组与拓扑排序不一致(依赖方向已校验合法——手工比拓扑更细的串行化是安全做法,可保持现状)')
|
|
1627
|
-
console.log(' 标准分组见上方 📊;如需对齐:sillyspec plan-adopt-waves --change <变更名>(重排 plan.md Wave 段并同步任务总表 W 列)')
|
|
1628
1810
|
}
|
|
1811
|
+
// 结构不一致但方向合法(手工保守串行)→ 静默放行(2026-09-09-plan-derived:
|
|
1812
|
+
// 合法安全模式消每轮提示噪音;手动对齐出口保留 sillyspec plan-adopt-waves)
|
|
1629
1813
|
}
|
|
1630
1814
|
}
|
|
1631
1815
|
}
|
package/src/stages/plan.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from 'fs'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import { getRule } from '../stage-contract-spec.js'
|
|
4
|
+
import { REVIEW_CHECKLISTS } from '../stage-review-checklist.js'
|
|
4
5
|
|
|
5
6
|
// 从 plan-postcheck.js 重导出(保持向后兼容)
|
|
6
7
|
export {
|
|
@@ -331,17 +332,10 @@ plan.md 审查通过后、进入 execute 前,若 plan_level=full(跨模块/
|
|
|
331
332
|
tier: {REVIEW_TIER}({REVIEW_TIER_REASON})
|
|
332
333
|
- tier=self:当前 agent 直接执行下方审查清单(小变更,独立审查仪式成本 > 收益)
|
|
333
334
|
- tier=independent:必须用 Agent tool 启动一个独立的计划审查子代理(独立上下文,不共享你生成 plan 时的分析与倾向),由子代理执行下方审查清单并输出 review.json
|
|
335
|
+
宿主环境无 Agent tool 可用(调用报 Unknown agent / Available agents: none)→ 不卡死:主代理切换为审查者角色自审替代,reviewerNotes 首行记录「降级:环境无子代理可用」,逐条结论附源码锚点(file:line 或 grep/read 证据)补偿独立性。
|
|
334
336
|
|
|
335
337
|
### 审查清单(读取 plan.md 的 plan_level,逐条核对)
|
|
336
|
-
- [ ]
|
|
337
|
-
- [ ] plan_level 档位与实际复杂度匹配(none/light/full 没选错)
|
|
338
|
-
- [ ] 跨任务契约:task-A 的产出(接口/DTO/响应)被 task-B 消费时,consumer 是否在 TaskCard expects_from 声明所需字段、provider 是否在 provides 承诺、两边字段一致?(plan-postcheck 会硬校验,此处是独立视角复查)
|
|
339
|
-
- [ ] 文件覆盖:design.md 文件变更清单中的每个源码文件,是否都被至少一个 task 的 allowed_paths 覆盖?(漏覆盖 = execute 必然漏改。跨仓变更对账口径:design 清单按「## <repo-key> 仓变更」分段、路径相对各仓根,task 卡 repo: + 同口径相对路径匹配——allowed_paths 带仓库名前缀或绝对路径 = 永不命中、对账不上)
|
|
340
|
-
- [ ] 不存在 P0/P1 unresolved blocker 残留
|
|
341
|
-
- [ ] 没有实现细节泄漏到 plan.md(接口签名/代码示例应在 tasks/task-NN.md)
|
|
342
|
-
- [ ] 关键路径与 Wave 依赖合理(无循环依赖、无遗漏前置)
|
|
343
|
-
- [ ] 连带测试归属:本批改动是否会导致既有测试断言失效(改共享/被多 task 依赖源文件、改被测试精确匹配的值如 UI 文案/按钮文本/错误信息/常量/枚举字面量、改函数签名或返回结构等单文件场景)?此类 task 是否在 related_tests 声明了失效测试、且路径在 allowed_paths 内(或由独立测试 task 覆盖)?(漏声明 = execute 阶段测试债、主代理事后兜底)
|
|
344
|
-
- [ ] acceptance 字段对照实际 schema/类型源文件核验存在性与形态,不凭 design.md 文字臆断(plan-postcheck best-effort grep 会给 allowed_paths 源文件未命中的 snake_case/camelCase 标识符提 warning,此处是语义层复查;臆断 = execute 阶段返工)
|
|
338
|
+
${REVIEW_CHECKLISTS.plan.map((item) => '- [ ] ' + item).join('\n')}
|
|
345
339
|
|
|
346
340
|
### tier=independent 时:启动 plan-review 子代理
|
|
347
341
|
用 Agent tool 启动子代理(subagent_type: general),prompt 要点:
|
|
@@ -351,37 +345,15 @@ tier: {REVIEW_TIER}({REVIEW_TIER_REASON})
|
|
|
351
345
|
{REVIEW_JSON_CONTRACT}
|
|
352
346
|
4. verdict=fail 时在 reviewerNotes 写明阻断项
|
|
353
347
|
|
|
354
|
-
###
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
输入(此时 TaskCard/allowed_paths 尚未生成——在下一步「生成 TaskCard」才产出,故用以下两项作输入,粒度与 archive 现状一致):
|
|
358
|
-
- design.md 的「文件变更清单」(本次计划改哪些源码文件)
|
|
359
|
-
- plan.md 的任务列表(每个 task 改的范围)
|
|
360
|
-
|
|
361
|
-
步骤(module-impact 的生成口径与 archive 终审一致):
|
|
362
|
-
1. 读 {SPEC_ROOT}/docs/<project>/modules/_module-map.yaml(模块→文件路径映射)。**不存在 → 降级**:生成只含 unmapped 部分的 module-impact.md + 提示「建议运行 scan 生成模块映射」,不阻断
|
|
363
|
-
2. 对照 design 文件变更清单 + plan 任务列表,逐文件匹配所属模块(命中的归 mapped,未命中的归「未匹配文件」章节)
|
|
364
|
-
3. 落盘 {SPEC_ROOT}/changes/<change>/module-impact.md,**两个章节标题逐字固定**(archive-impact.yaml contains_sections 机械校验,写成「影响矩阵」等变体会被 archive 硬拦返工):
|
|
365
|
-
- 「## 模块影响矩阵」:模块 × 影响类型[新增/修改/删除/依赖变更] × 说明
|
|
366
|
-
- 「## 未匹配文件」:unmatched 文件 × 处置说明(无未匹配文件也保留空章节,写「无」)
|
|
367
|
-
4. 首行标题必须用中文:# 模块影响分析(Module Impact)— <变更简述>
|
|
368
|
-
5. **必须含「更新结果」表骨架**(本表是 verify/archive 死信门控的收口目标——CLI 硬校验表内无 pending/待办行,漏写此表则 agent 只能从 gate 报错反推格式):
|
|
369
|
-
\`\`\`markdown
|
|
370
|
-
## 更新结果
|
|
371
|
-
|
|
372
|
-
| 目标 | 操作 | 状态 |
|
|
373
|
-
|------|------|------|
|
|
374
|
-
| \`modules/<id>.md\` | 更新<模块名>模块卡(本次变更涉及) | pending |
|
|
375
|
-
| \`_module-map.yaml\` | 无变化(未增删模块) | skipped |
|
|
376
|
-
\`\`\`
|
|
377
|
-
规则:每个受影响模块文档一行,状态列初始化 pending;无变化的行直接写 skipped。execute/verify 完成文档同步后把对应行回填 done;确定不同步的行改 skipped 并在操作列写明原因。**末列是状态列**(done/skipped/pending),不要在表尾追加其他列。
|
|
348
|
+
### module-impact.md 首版(scale≠small 时:CLI 自动生成,审查步勿手写)
|
|
349
|
+
module-impact.md 首版**由 CLI 在本阶段 --done 时自动生成**——文件×模块归属按 _module-map.yaml 前缀匹配机械预填,章节含「## 模块影响矩阵」「## 未匹配文件」「## 更新结果」表骨架(每受影响模块一行 pending),影响类型列留 <!--TODO--> 由 execute/verify 按实际 diff 回填。已存在不覆盖。手写整份首版是历史返工根源(章节标题变体会被 archive contains_sections 硬拦),**本步不要手写**。
|
|
350
|
+
仅当 --done 报 module-impact 缺失时(如无 module-map 的绿地项目生成降级失败),按 gate 报错提示手写兜底:两章节标题逐字用「## 模块影响矩阵」「## 未匹配文件」(未命中的文件归「未匹配文件」章节),并含「## 更新结果」表骨架(行:\`modules/<id>.md\`、\`_module-map.yaml\`,状态列 pending/skipped,末列是状态列,勿追加其他列)。
|
|
378
351
|
|
|
379
352
|
execute/verify 阶段会按实际代码变更更新此文档;archive 阶段会最终确认它。
|
|
380
353
|
|
|
381
354
|
### 输出
|
|
382
355
|
- tier=self:审查清单结果(每条状态 + 偏差说明)
|
|
383
|
-
- tier=independent:子代理产出的 review.json 路径 + verdict
|
|
384
|
-
- scale≠small:附 module-impact.md 路径 + 影响摘要`,
|
|
356
|
+
- tier=independent:子代理产出的 review.json 路径 + verdict 摘要`,
|
|
385
357
|
outputHint: 'plan 审查结果(self=清单 / independent=review.json 路径+verdict)',
|
|
386
358
|
optional: false
|
|
387
359
|
}
|
package/src/stages/quick.js
CHANGED
|
@@ -20,18 +20,18 @@ step 3 --done 审计会把 src 核心文件(CLI / 状态机 / 注入框架等
|
|
|
20
20
|
|
|
21
21
|
### 操作
|
|
22
22
|
1. 检查关联变更(\`<linked-changes>\`,逗号分隔的变更名列表;显示「(无)」= 不关联变更),确定记录方式
|
|
23
|
-
2.
|
|
24
|
-
3.
|
|
25
|
-
4.
|
|
26
|
-
5. 构建命令(CLI 自 local.yaml 注入,勿再读文件):
|
|
23
|
+
2. 理解任务:模糊则问一个问题确认(项目/约定/模块上下文已由 CLI 注入下方与开头,勿再 cat)
|
|
24
|
+
3. 阅读「项目上下文注入」段(CLI 已代读 projects/*.yaml 摘要 + CONVENTIONS.md 开头)——注入标注截断且确需全文时才读原文件
|
|
25
|
+
4. 构建命令(CLI 自 local.yaml 注入,勿再读文件):
|
|
27
26
|
{LOCAL_COMMANDS}
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
5. 若有关联变更,加载每个变更的设计文档:\`cat {SPEC_ROOT}/changes/<c>/design.md 2>/dev/null\`(理解设计意图)
|
|
28
|
+
6. 如有需要,查询知识库:\`cat {SPEC_ROOT}/knowledge/INDEX.md 2>/dev/null\`
|
|
30
29
|
|
|
31
|
-
###
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
### 模块上下文(CLI 按任务描述匹配注入)
|
|
31
|
+
按本会话任务描述从 _module-map.yaml 匹配的模块已注入本 prompt 开头「📦 模块上下文」段(职责/核心文件/风险/最近变更)——**不要再 cat _module-map.yaml 挑卡**;开头无该段 = 无命中模块,需要跨模块全局视野时才读 \`{SPEC_ROOT}/docs/<project>/modules/_module-map.yaml\` 自查。
|
|
32
|
+
|
|
33
|
+
### 项目上下文注入(CLI 代读,刀①)
|
|
34
|
+
{QUICK_CONTEXT_DIGEST}
|
|
35
35
|
|
|
36
36
|
### QUICKLOG 记录(CLI 已接管,无需你手动创建)
|
|
37
37
|
本 quick 会话的 ql-ID 由 CLI 在启动时分配,已注入为 \`<quicklog-id>\`。CLI 同时已自动:
|
|
@@ -101,7 +101,7 @@ sillyspec run quick --done --change <id> --req "一句话语义化短标题(
|
|
|
101
101
|
结果:验证情况(测试数 / lint / typecheck / 部署状态)
|
|
102
102
|
\`\`\`
|
|
103
103
|
|
|
104
|
-
⚠️ **「需求:」(= --req)就是 QUICKLOG 条目标题**——CLI
|
|
104
|
+
⚠️ **「需求:」(= --req)就是 QUICKLOG 条目标题**——CLI 提取该字段原样作标题(标点随意,仅超 80 字才在就近标点/空格处截断,2026-09-08 起;旧口径「截到首个标点」已废)。仍建议一句话说清改了什么(如「登录限流修复——INCR 计数误清」),需求背景放「根因:/方案:」字段——标题行太长影响 QUICKLOG 可扫读性。单行旧形式的风险仅在「正文里再次出现顶层标签字样」——需求:/根因:/方案:/结果: 字样前导恰是空白或句末标点(。;!?)时,会被边界扫描误当真实字段边界(错位/缺字段被拒)——这正是四参数形式消灭的事故面。注意区分:根因块内以列表行形态出现的嵌套子字段(\`- 现象:\` \`- 根因:\` \`- 护栏:\` \`- 证据:\`)是**合法形态**——「- 」前缀不构成顶层标签、不参与拆分判定,顶层四字段边界不受影响(postmortem 用法见下方可选提示)。
|
|
105
105
|
|
|
106
106
|
> 💡 **可选(非必填)**——根因字段属 postmortem 场景(踩坑修复、值得留档复盘)时,可改用多行 \`--output\` 形式,在根因块内按列表行补四子字段:\`根因:\` 行后换行接 \`- 现象:…\`(出了什么问题)、\`- 根因:…\`(深层原因)、\`- 护栏:…\`(防再犯措施)、\`- 证据:…\`(可追溯路径,如 sillyspec agent-log --json 输出的会话日志 jsonl 路径、review.json、verify-result.md)。该列表行形态为合法格式(顶层标签边界不受影响,详见上方说明);常规场景仍推荐四参数形式。
|
|
107
107
|
|
package/src/stages/verify.js
CHANGED
|
@@ -75,10 +75,12 @@ export const definition = {
|
|
|
75
75
|
- 路径:变更目录下的 verify-required-evidence.json
|
|
76
76
|
- 文件 schema:\`{ items: [{ task, verdict, evidence: string[] }] }\`——顶层是 \`items\`,每项的 \`evidence\` 是字符串数组(不是 \`requiredEvidence\` 键)
|
|
77
77
|
- 如果存在 → 逐项读取 \`items\`,对每个 cannot_verify 任务逐条核对其 \`evidence\` 数组是否已满足
|
|
78
|
-
- 每条 evidence
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
78
|
+
- 每条 evidence 的结论写进 verify-result.md「## 证据账(cannot_verify 任务)」槽段(2026-09-08-ir-verify-facts v2 起为硬门):逐 task 一行 \`- task-NN: <satisfied|missing|partial> | verifiedFiles: <精确路径,逗号分隔>\`
|
|
79
|
+
- verifiedFiles 是 CLI 核验对象:代码/测试类路径核 存在×mtime×git diff 交集(不在本变更 diff 内的文件会被拦);日志/文档类(.runtime/、*.log、docs/)豁免 diff 只核存在×mtime
|
|
80
|
+
- satisfied 必填 verifiedFiles;确实无法验证 → 填 missing 并加(豁免:<一句话理由>)后缀;部分满足 → partial + 已核验路径
|
|
81
|
+
- 存在未豁免 missing 或 satisfied 核验不过 → CLI 阻断 verify 完成(rollback,明细逐文件列出)
|
|
82
|
+
- 如果文件不存在 → 表示 execute 阶段无 cannot_verify 任务,证据账槽段写「无」,正常继续
|
|
83
|
+
- 旧格式(无槽段)报告走 legacy 子串对账(warning 不阻断),新变更一律用 --init 骨架的槽格式
|
|
82
84
|
|
|
83
85
|
### 模块文档加载
|
|
84
86
|
8. 读取 \`{SPEC_ROOT}/docs/<project>/modules/_module-map.yaml\`(不存在则跳过以下步骤)
|
|
@@ -96,7 +98,7 @@ export const definition = {
|
|
|
96
98
|
},
|
|
97
99
|
{
|
|
98
100
|
name: '逐项检查任务',
|
|
99
|
-
prompt: `对照 tasks.md
|
|
101
|
+
prompt: `对照 tasks.md(任务注册表唯一真相)检查每个任务完成状态。勾选唯一写入者是 CLI(review write 落盘即按 review.json verdict 勾选,execute --done 时 autoCheckPlanFromReviews 兜底;P2-f 起 agent 不再手勾),本阶段只读对照、不改勾。
|
|
100
102
|
|
|
101
103
|
### 勾选状态(CLI 注入,勿手数)
|
|
102
104
|
{TASKS_CHECKBOX}
|
|
@@ -167,36 +169,17 @@ export const definition = {
|
|
|
167
169
|
},
|
|
168
170
|
{
|
|
169
171
|
name: '运行测试和质量扫描',
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
4. 搜索技术债务:grep TODO/FIXME/HACK/XXX(仅限变更文件)
|
|
182
|
-
|
|
183
|
-
### 注意
|
|
184
|
-
- **CLI 对账机制**:verify 阶段最终 --done 时,CLI 会亲自执行 local.yaml 的 commands.test(同步,耗时可能较长);实测失败会直接阻断 verify 完成,谎报测试结果没有意义。commands.lint 同样会被 CLI 实测对账(advisory)
|
|
185
|
-
- 冒烟测试非必需:全量/模块实测结果以 CLI 对账为准,本步跑的结果仅供你提前发现问题并写入验证报告
|
|
186
|
-
|
|
187
|
-
### 检查选择指引(按变更影响面收窄,本地聚焦;FR-12)
|
|
188
|
-
按本次变更的实际影响面选择检查组合,避免无差别全量:
|
|
189
|
-
- **行为类改动**(源码逻辑/数据结构/接口/调用关系/配置)→ 聚焦测试:只跑变更命中模块的子集(local.yaml 配 \`test_strategy: module\` + modules 映射),CLI --done 对账同口径收窄
|
|
190
|
-
- **文档/prompt 类改动**(*.md、docs/** 等)→ 文档检查(\`sillyspec docs check\` 口径),不跑代码测试
|
|
191
|
-
- **门禁/契约类改动**(gate/contract 相关文件、对外接口契约)→ 契约对账(\`sillyspec gate\` 口径)优先于跑测试
|
|
192
|
-
- **全量**仅在用户明确要求、或仓库级不可分变更(横切基础设施/全仓重命名等无法按模块拆分)时执行——本地聚焦,全量留给 CI 或明确要求
|
|
193
|
-
|
|
194
|
-
### 测试策略推荐(CLI 注入;test_strategy: evidence-auto 时非空,其余策略为空)
|
|
195
|
-
{EVIDENCE_AUTO_RECOMMENDATION}
|
|
196
|
-
|
|
197
|
-
### 输出
|
|
198
|
-
质量扫描结果 + 技术债务标记`,
|
|
199
|
-
outputHint: '质量扫描结果 + 技术债务',
|
|
172
|
+
// noAI 化(P0-1,docs/sillyspec/noai-ir-roadmap.md §3):test/lint 实测提前到本步由 CLI
|
|
173
|
+
// 亲自执行(run/verify-quality-scan.js executeVerifyQualityScan)——本步此前的 agent 角色
|
|
174
|
+
// 是「可选冒烟 + lint 转述」纯中继(旧 prompt 自述「CLI 会在 --done 统一跑,实测 198s×2」)。
|
|
175
|
+
// 结果按代码指纹落盘,--done 对账指纹匹配即复用免重跑(前置一);失败打印输出 + 并行 WIP
|
|
176
|
+
// 归因提示后不盖 completed,agent 修复后 run verify 复入本步(前置二)。步骤名不变
|
|
177
|
+
//(存量进度库按名匹配迁移);test_strategy 收窄(module/evidence-auto/skip)由实测函数
|
|
178
|
+
// 自行解析,检查选择指引随之退役。
|
|
179
|
+
noAI: true,
|
|
180
|
+
_cliAction: 'verifyRunQualityScan',
|
|
181
|
+
prompt: '',
|
|
182
|
+
outputHint: '质量扫描结果(CLI 实测输出 + 指纹复用记录)',
|
|
200
183
|
optional: false
|
|
201
184
|
},
|
|
202
185
|
{
|
|
@@ -215,20 +198,22 @@ export const definition = {
|
|
|
215
198
|
3. **生成 verify-result.md 骨架(勿从零手写)**:先跑 \`sillyspec verify-probes --change <change-name> --init\`——一条命令生成十章节骨架(已存在不覆盖),其中**探针结果章节已机械预填**(探针 1 的 TODO/FIXME 命中清单、探针 3 的测试覆盖、探针 5 的 API 契约对账表、探针 6 的删除对账三态判定),文件落 \`{SPEC_ROOT}/changes/<change-name>/verify-result.md\`(CLI 替换出的**主仓绝对路径**;若当前 cwd 在 worktree 内也绝不落 worktree 副本——CLI 校验读主仓,副本随 worktree 清理蒸发)。你只需把各 \`<!--TODO-->\` 占位替换为语义结论;半语义探针(2 关键词覆盖 / 4 决策追踪)与断言抽查、集成盲区标注由你补在对应 TODO 处
|
|
216
199
|
4. **预填探针段不可篡改或删除**:verify \`--done\` gate 会重跑探针对比 verify-result.md 正文,不符即拦(ERROR);对探针结果有异议只能在预填段旁追加说明,不得改写原文
|
|
217
200
|
5. **verify-facts.json 为机器底稿,勿手改**:防篡改对比的基准是正文,改底稿无意义
|
|
218
|
-
6. 给出结论:PASS / PASS WITH NOTES / FAIL
|
|
201
|
+
6. 给出结论:PASS / PASS WITH NOTES / FAIL(受风险门控约束)——**结论只认骨架「结论枚举:」槽行(行首锚定,把 <待填:三选一> 整体替换为枚举值);槽留待填会被 gate 判不过,正文其他位置的 PASS/FAIL 字样不参与判定**
|
|
219
202
|
7. **核对 module-impact.md**(若 \`{SPEC_ROOT}/changes/<change>/module-impact.md\` 存在):对照本次实际代码变更(git diff)与 module-impact.md 的模块影响矩阵,发现不一致(漏标受影响模块 / 影响类型错误 / 实际未触碰的模块被误标)则在 verify-result.md 标注。module-impact 由 plan 首版生成、execute 各 Wave 更新,verify 是最后一次核对机会(archive 仅终审不再生成)。这是 advisory 核对(不阻断 verify 完成),但 module-impact 与实际严重背离应记为风险。
|
|
220
203
|
|
|
221
204
|
### verify-result.md 章节结构(骨架已含,占位替换即可)
|
|
222
205
|
|
|
223
|
-
结论(PASS / PASS WITH NOTES / FAIL)→ 任务完成度 → 设计一致性 → 探针结果(已预填)→ 测试结果 → 决策追踪矩阵(\`| 决策 ID | FR | Task | Evidence | 状态 |\`,存在 decisions.md 才留)→ 技术债务 → 变更风险等级 → Runtime Evidence → 代码审查。
|
|
206
|
+
结论(PASS / PASS WITH NOTES / FAIL)→ 证据账(cannot_verify 任务,槽段)→ 集成验证回执(槽段)→ 任务完成度 → 设计一致性 → 探针结果(已预填)→ 测试结果 → 决策追踪矩阵(\`| 决策 ID | FR | Task | Evidence | 状态 |\`,存在 decisions.md 才留)→ 技术债务 → 变更风险等级 → Runtime Evidence → 代码审查。
|
|
207
|
+
|
|
208
|
+
**「## 集成验证回执」槽行结构**(integration/deployment-critical 变更必填;其余写「无」):\`- claim: <一句话> | command: <命令> | exit: <0 或非 0> | log: <日志路径>\`——CLI 一致性校验四条件:log 存在 × mtime 在 verify 窗口内 × 日志尾无失败签名(error/exception/traceback/fatal 行首,剔除「0 errors」类良性行)× exit 0;全绿才算在场证据,字面措辞不再参与判定(v2 起 literals 仅存量回退)。CLI 不代跑集成进程——回执必须来自你真实执行过的命令。
|
|
224
209
|
|
|
225
210
|
**Runtime Evidence 行结构**(integration/deployment-critical 必填;按实际触碰的运行时组件写,未涉及的行写「不涉及」勿堆关键词):长驻进程启动命令 / 触碰的服务端点 / 触发核心路径的请求(附关键响应)/ 进程日志关键片段(证明走了新路径)/ 生命周期终态断言(初始态→运行态→终态)/ 失败模式排除(逐条说明为何未触发)。
|
|
226
211
|
|
|
227
212
|
### 🧹 服务进程登记与自动回收(真实启动验证必读,坑 verify-service-process-leak)
|
|
228
213
|
「真实启动一次」起的长驻服务(uvicorn/node server 等)**必须登记 PID,CLI 在 verify 收尾自动回收**——不登记的进程会挂死机器(实测 uvicorn 漏挂一天多):
|
|
229
|
-
1. 后台启动服务并取 PID(PowerShell:\`Start-Process -PassThru\` 取 \`.Id\`;bash:\`cmd & echo
|
|
214
|
+
1. 后台启动服务并取 PID(PowerShell:\`Start-Process -PassThru\` 取 \`.Id\`;bash:\`cmd & echo $!\`)。**优先登记服务本体(叶子)PID**——经 shell 包装启动(\`sh -c\` / \`cmd /c\` / uvicorn --reload 的 reloader)时 CLI 虽已按进程树击杀(2026-09-10 起连带子进程),但叶子 PID 最稳:包装 shell 退出后子进程 PID 不漂移、跨平台一致
|
|
230
215
|
2. 每行一个 PID 追加到 \`{SPEC_ROOT}/.runtime/verify-services-<change-name>.pids\`(\`Add-Content\` / \`echo >>\`;**按变更名分片**——多会话并发 verify 时各自的 --done 只回收自己的服务,互不误杀)
|
|
231
|
-
3. verify \`--done\` 时 CLI
|
|
216
|
+
3. verify \`--done\` 时 CLI 读该文件**逐进程树击杀**(包装进程连同子进程)并清空——你在 verify-result.md 的「生命周期终态断言」里写「PID 已登记,CLI 收尾回收」即可
|
|
232
217
|
登记是硬要求:没有 PID 登记的「真实启动」证据在 deployment-critical 门控下视为不完整(进程无法证明被回收)。
|
|
233
218
|
|
|
234
219
|
### 输出
|