sillyspec 3.26.13 → 3.26.15
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-archive/SKILL.md +2 -0
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -1
- package/.claude/skills/sillyspec-commit/SKILL.md +18 -60
- package/.claude/skills/sillyspec-continue/SKILL.md +2 -0
- package/.claude/skills/sillyspec-execute/SKILL.md +10 -4
- package/.claude/skills/sillyspec-plan/SKILL.md +10 -2
- package/.claude/skills/sillyspec-quick/SKILL.md +2 -2
- package/.claude/skills/sillyspec-resume/SKILL.md +2 -0
- package/.claude/skills/sillyspec-verify/SKILL.md +2 -0
- package/.claude/skills/sillyspec-workspace/SKILL.md +7 -1
- package/package.json +1 -1
- package/src/commit-suggest.js +202 -0
- package/src/config-cat.js +88 -0
- package/src/docs-check.js +48 -12
- package/src/doctor-diagnostics.js +49 -0
- package/src/hooks/worktree-guard.js +20 -1
- package/src/index.js +547 -21
- package/src/init.js +1 -1
- package/src/local-register.js +98 -0
- package/src/module-impact.js +146 -0
- package/src/progress/change-registry.js +43 -4
- package/src/progress/stage-machine.js +10 -5
- package/src/progress.js +18 -3
- package/src/quicklog.js +106 -1
- package/src/run/command.js +151 -38
- package/src/run/complete-handlers.js +62 -4
- package/src/run/complete.js +10 -8
- package/src/run/concurrent-detect.js +3 -1
- package/src/run/gates.js +86 -4
- package/src/run/multi-repo-context.js +3 -1
- package/src/run/next.js +128 -0
- package/src/run/prompt.js +28 -6
- package/src/run/scan-profile.js +35 -23
- package/src/run/shared.js +28 -7
- package/src/scan-postcheck.js +142 -2
- package/src/stage-contract-spec.js +1 -1
- package/src/stage-review.js +55 -7
- package/src/stages/archive.js +1 -1
- package/src/stages/brainstorm.js +26 -12
- package/src/stages/doctor.js +2 -4
- package/src/stages/execute.js +110 -26
- package/src/stages/knowledge.js +21 -5
- package/src/stages/plan-postcheck.js +24 -10
- package/src/stages/plan.js +14 -9
- package/src/stages/quick.js +14 -6
- package/src/stages/scan.js +1 -0
- package/src/stages/verify.js +7 -49
- package/src/sync.js +19 -7
- package/src/task-review.js +256 -10
- package/src/verify-probes.js +264 -0
- package/src/workspace.js +125 -0
- package/src/worktree-apply.js +15 -1
- package/src/worktree.js +58 -8
- package/templates/claude-instruction.md +1 -1
- package/templates/prompts/taskcard-rules.md +1 -1
- package/templates/prompts/verify-probes.md +13 -47
package/src/stages/brainstorm.js
CHANGED
|
@@ -45,6 +45,7 @@ export const definition = {
|
|
|
45
45
|
7. 检查全局模板:\`ls ~/.sillyspec/templates/\`
|
|
46
46
|
- 有匹配模板 → 询问是否基于模板
|
|
47
47
|
- 无相关内容 → 跳过,不输出
|
|
48
|
+
8. 查询知识库:\`cat {SPEC_ROOT}/knowledge/INDEX.md 2>/dev/null\`(无索引则跳过)——按需求关键词命中条目时读取对应知识文件;known-issues/已知坑要在方案期就规避,不要等 execute 的命中报告才发现
|
|
48
49
|
|
|
49
50
|
### 模块匹配方法
|
|
50
51
|
读取 _module-map.yaml 后,根据用户描述的需求关键词,匹配相关模块:
|
|
@@ -218,32 +219,44 @@ export const definition = {
|
|
|
218
219
|
migratedFrom: ['HTML 原型生成'],
|
|
219
220
|
requiresWait: true,
|
|
220
221
|
waitReason: '等待用户确认设计方案',
|
|
221
|
-
waitOptions: ['确认', '需要修改', '推翻重来'],
|
|
222
|
-
prompt:
|
|
222
|
+
waitOptions: ['确认', '需要修改', '补原型', '推翻重来'],
|
|
223
|
+
prompt: `展示完整设计方案供用户确认;涉及 UI 改动时按下方分级决定是否生成 HTML 原型——原型动作必须在暂停之前落定,不能"等确认后再补"。
|
|
223
224
|
|
|
224
225
|
### 操作
|
|
225
226
|
1. 简单项目:几句话整体描述
|
|
226
227
|
2. 复杂项目:按模块/Phase 分段展示,每段 200-300 字
|
|
227
228
|
3. 展示完整设计方案(不要逐段停顿,一次性展示)
|
|
228
229
|
4. 确认变更名(格式:\`YYYY-MM-DD-<简短描述>\`,例如 \`2026-05-13-user-auth\`)
|
|
229
|
-
5.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
-
|
|
235
|
-
|
|
236
|
-
|
|
230
|
+
5. 按「HTML 原型分级」生成原型或写出跳过原因(二选一必填,见下)
|
|
231
|
+
6. 暂停等待用户确认或修改意见
|
|
232
|
+
|
|
233
|
+
### HTML 原型分级(按改动程度,不是"是否碰前端文件")
|
|
234
|
+
判定对象是**改造后的界面相对现状的变化程度**:
|
|
235
|
+
- **必须生成**(布局/结构/流程级变化):
|
|
236
|
+
- 新增页面、视图、路由
|
|
237
|
+
- 页面布局变化:区域增删、骨架重排、信息层级调整(如单栏改多栏、列表改看板)
|
|
238
|
+
- 新交互流程:多步流程、跨页流转、状态驱动的界面变化
|
|
239
|
+
- 用户提供了截图/设计稿要求按其改造(原型即对照基准)
|
|
240
|
+
- **建议生成**(组件级变化、页面骨架不变):
|
|
241
|
+
- 表单重构、列表列调整、弹窗/抽屉流程等局部组件变化
|
|
242
|
+
- 拿不准时默认生成——宁多勿漏,原型在 execute 阶段可直接对照复用
|
|
243
|
+
- **跳过**(无需生成,但必须声明原因):
|
|
244
|
+
- 文案、颜色、间距、字号、图标等样式微调
|
|
245
|
+
- 纯后端逻辑/CLI/配置/文档,无任何界面变化
|
|
246
|
+
|
|
247
|
+
### 生成要求
|
|
248
|
+
生成一个独立的 HTML 文件(内联 CSS + JS),保存到:
|
|
249
|
+
\`{SPEC_ROOT}/changes/<change-name>/prototype-<名称>.html\`(目录不存在则先创建)
|
|
237
250
|
- 单文件,浏览器直接打开
|
|
238
251
|
- 高保真:布局/组件/交互流程按真实效果呈现,后续 execute 阶段可直接对照复用
|
|
239
252
|
- 与项目代码风格一致:优先复用项目现有技术栈/组件库/设计 token(先查 scan 文档与现有前端代码),不另起风格
|
|
240
|
-
展示给用户确认设计方向。不适合则跳过。
|
|
241
253
|
|
|
242
254
|
### 铁律 — 必须等待用户确认设计
|
|
243
255
|
- 展示完整设计方案后必须暂停等用户确认,**不要自己说"设计已充分确认"然后推进**(命令由 CLI 在下方注入)
|
|
256
|
+
- 原型生成或跳过声明必须在暂停**之前**完成——暂停是本步骤最后动作,用户确认后直接进入写 design.md,不会回头补
|
|
244
257
|
|
|
245
258
|
### 输出
|
|
246
|
-
完整设计方案 +
|
|
259
|
+
完整设计方案 + 变更名 + 原型文件路径 或 跳过原因(二选一必填;跳过时写明分级依据,用户可当场否决要求补)
|
|
247
260
|
|
|
248
261
|
### 注意
|
|
249
262
|
- 不要一次输出大段文字,按模块/Phase 分段
|
|
@@ -333,6 +346,7 @@ design.md 第一行标题必须用中文:# 设计文档(Design)— <变更
|
|
|
333
346
|
- design.md 含「自审」字面命中章节(自审 / Self-Review / Self-review)——「生成规范文件」步完成契约 brainstorm.design.self-review 硬要求,写在本步自审段即可
|
|
334
347
|
- 如存在 decisions.md,design.md 是否引用所有当前版本 D-xxx@vN
|
|
335
348
|
- 涉及 session/lease/agent_run/daemon/lifecycle 等关键词时,是否含「生命周期契约表」
|
|
349
|
+
- UI 原型核对:文件清单含前端文件(.vue/.tsx/.jsx/.svelte/.html/.css 等)且改动达到「必须生成/建议生成」原型级别(分级见「分段展示设计」步)时,变更目录应已有 \`prototype-*.html\`——没有则本步补生成;确不生成时把跳过原因记入 design.md 风险登记,不得静默缺位
|
|
336
350
|
5. 缺章节 → 补齐后重检;章节齐全 → 进入下一步(Design Grill 做语义层交叉审查)
|
|
337
351
|
|
|
338
352
|
### 输出
|
package/src/stages/doctor.js
CHANGED
|
@@ -73,16 +73,14 @@ const dir = '.sillyspec/changes';
|
|
|
73
73
|
const subs = fs.existsSync(dir) ? fs.readdirSync(dir).filter(f => { try { return fs.statSync(dir+'/'+f).isDirectory() && f !== 'archive'; } catch { return false; } }) : [];
|
|
74
74
|
let known;
|
|
75
75
|
try { const db = new DatabaseSync('.sillyspec/.runtime/sillyspec.db', { readOnly: true }); known = new Set(db.prepare(\"SELECT name FROM changes WHERE status='active'\").all().map(r => r.name)); db.close(); } catch (e) { console.log('⚠️ db 读取失败: ' + e.message + '(跳过孤儿判定,不做任何清理建议)'); known = null; }
|
|
76
|
+
// known 为 null(db 不可读)时只列目录不判定孤儿,后续僵尸检查同样跳过——不守卫会 TypeError 半途崩
|
|
76
77
|
if (known) subs.forEach(s => {
|
|
77
78
|
console.log(known.has(s) ? '✅ ' + s + ' — 已关联' : '⚠️ ' + s + ' — 孤儿目录(先 dump 取证再手工归位,勿直接 rm)');
|
|
78
79
|
});
|
|
79
|
-
subs.forEach(s => {
|
|
80
|
-
console.log(known.has(s) ? '✅ ' + s + ' — 已关联' : '⚠️ ' + s + ' — 孤儿目录(可清理)');
|
|
81
|
-
});
|
|
82
80
|
// 反向检查(multi-agent-review Q1):DB active 且匹配 quick-<hex> 但无目录 → 僵尸 quick 会话
|
|
83
81
|
// (quick 收尾未注销,active 行累积污染 listChanges/doctor)。新代码已修收尾,此项清理历史残留。
|
|
84
82
|
const QUICK_RE = /^quick-[0-9a-f]{8}$/;
|
|
85
|
-
const zombieQuick = [...known].filter(n => QUICK_RE.test(n) && !subs.includes(n));
|
|
83
|
+
const zombieQuick = known ? [...known].filter(n => QUICK_RE.test(n) && !subs.includes(n)) : [];
|
|
86
84
|
if (zombieQuick.length > 0) {
|
|
87
85
|
console.log('⚠️ 检测到 ' + zombieQuick.length + ' 个僵尸 quick 会话(DB active 但无目录):');
|
|
88
86
|
zombieQuick.forEach(n => console.log(' - ' + n));
|
package/src/stages/execute.js
CHANGED
|
@@ -118,7 +118,8 @@ export function validatePlanForExecute(tasksContent, planContent) {
|
|
|
118
118
|
if (count > 1) errors.push(`task id 重复: ${id} 在 tasks.md 出现 ${count} 次`)
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
// 检查 3: task id 连续性(从 1
|
|
121
|
+
// 检查 3: task id 连续性(从 1 开始;ids[0]!==1 时整体跳过 = 兼容旧变更编号不从 1 起,
|
|
122
|
+
// 契约由 test/plan-execute-contract.test.mjs Case 10 钉死,勿改)
|
|
122
123
|
const ids = allTasks.map(t => t.index).sort((a, b) => a - b)
|
|
123
124
|
if (ids.length > 0 && ids[0] === 1) {
|
|
124
125
|
for (let i = 0; i < ids.length; i++) {
|
|
@@ -213,6 +214,7 @@ const fixedPrefix = [
|
|
|
213
214
|
|
|
214
215
|
### 符号影响面扩展检查
|
|
215
216
|
11. **符号影响面扫描**(Critical — execute 前必做):
|
|
217
|
+
- **报告骨架勿手写**:先跑 \`sillyspec symbol-impact --change <change-name>\`——CLI 从 tasks.md 生成逐 task \`<!--TODO-->\` 骨架(gate 拦截时也会自动落一份);把每行占位替换为真实结论(**未替换的 TODO 占位会被 gate 拒绝**,骨架不能直接过门)
|
|
216
218
|
- 读取所有 tasks/task-NN.md,提取每个任务涉及的修改文件
|
|
217
219
|
- 对每个修改文件,检查是否涉及以下变更类型:
|
|
218
220
|
- class 构造函数参数变更(新增/删除/修改参数)
|
|
@@ -220,7 +222,7 @@ const fixedPrefix = [
|
|
|
220
222
|
- DTO / 类型定义变更
|
|
221
223
|
- API client 方法签名变更
|
|
222
224
|
- 函数/方法签名变更(参数增删改)
|
|
223
|
-
-
|
|
225
|
+
- 如果涉及上述变更类型,执行调用点搜索(用于补骨架外的调用点证据,不是生成报告的方式):
|
|
224
226
|
\`\`\`bash
|
|
225
227
|
rg "new ClassName\(" src/
|
|
226
228
|
rg "ClassName\(" src/
|
|
@@ -229,7 +231,7 @@ const fixedPrefix = [
|
|
|
229
231
|
\`\`\`
|
|
230
232
|
- 将搜索到的调用点与 plan.md 和 tasks/task-NN.md 的 allowed_paths 对比
|
|
231
233
|
- **发现调用点不在任何 task 的 allowed_paths 中 → 直接阻断 execute**
|
|
232
|
-
- **报告落盘(CLI
|
|
234
|
+
- **报告落盘(CLI 硬校验)**:结论写入 \`{SPEC_ROOT}/changes/<change-name>/symbol-impact.md\`,每个 task 一行结论(task-XX: 变更类型 + 受影响调用点 + 是否在范围内;无签名级变更也要显式写「无签名级变更」)。\`--done\` 时 CLI 校验该文件存在且覆盖 plan.md 全部 task,缺失或不覆盖会阻断完成
|
|
233
235
|
- 如果调用点不在范围内但任务明确写了"不改原因",记录但不阻断
|
|
234
236
|
|
|
235
237
|
### 输出
|
|
@@ -557,19 +559,20 @@ function resolveTaskRepo(task, changeDir) {
|
|
|
557
559
|
}
|
|
558
560
|
|
|
559
561
|
/**
|
|
560
|
-
* 把 base_commit
|
|
561
|
-
*
|
|
562
|
-
* 防止同 Wave 多 task 改同一跨仓仓时 HEAD 推进致 diff 范围漂移(design §5.3 约束① / R-01)。
|
|
562
|
+
* 把 commit 锡点(base_commit/head_commit)写入 task 卡 frontmatter(W3 task-08,D-010;
|
|
563
|
+
* head 侧 2026-08-21 审计项③ 补对称)。
|
|
563
564
|
*
|
|
564
|
-
* 策略:幂等写——frontmatter
|
|
565
|
-
*
|
|
565
|
+
* 策略:幂等写——frontmatter 有该字段行则就地替换,无则按 D-010 协议顺序插入:
|
|
566
|
+
* head_commit 跟在 base_commit 行后;base_commit 跟在 repo 行后;均无则插 frontmatter
|
|
567
|
+
* 首部。仅改 frontmatter 不动正文。已存在相同值不重复写。
|
|
566
568
|
*
|
|
567
569
|
* @param {string} taskFilePath - task-NN.md 绝对路径
|
|
568
|
-
* @param {
|
|
570
|
+
* @param {'base_commit'|'head_commit'} field - 锚点字段名
|
|
571
|
+
* @param {string} commit - commit sha
|
|
569
572
|
* @returns {boolean} 是否实际写入(false = 文件不存在 / 写失败 / 值未变)
|
|
570
573
|
*/
|
|
571
|
-
function
|
|
572
|
-
if (!taskFilePath || !
|
|
574
|
+
export function writeCommitAnchorToTaskCard(taskFilePath, field, commit) {
|
|
575
|
+
if (!taskFilePath || !commit || (field !== 'base_commit' && field !== 'head_commit')) return false
|
|
573
576
|
if (!existsSync(taskFilePath)) return false
|
|
574
577
|
let content
|
|
575
578
|
try {
|
|
@@ -577,24 +580,29 @@ function writeBaseCommitToTaskCard(taskFilePath, baseCommit) {
|
|
|
577
580
|
} catch {
|
|
578
581
|
return false
|
|
579
582
|
}
|
|
583
|
+
// CRLF 归一(坑 base-commit-crlf-frontmatter,plan-postcheck.js 同款):跨仓 task 卡被
|
|
584
|
+
// 编辑器/子代理写成 CRLF 后 `^---\n` 匹配失败 → 锚点静默不落盘,而 Wave
|
|
585
|
+
// prompt 仍声称「CLI 已落盘」。归一后按 LF 写回(taskcard 生成侧本就强制 LF)。
|
|
586
|
+
content = content.replace(/\r\n?/g, '\n')
|
|
580
587
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/)
|
|
581
588
|
if (!fmMatch) return false
|
|
582
589
|
const fm = fmMatch[1]
|
|
583
|
-
const
|
|
590
|
+
const lineRe = new RegExp(`^${field}:\\s.*$`, 'm')
|
|
584
591
|
let newFm
|
|
585
|
-
if (
|
|
586
|
-
const existing = (fm.match(
|
|
587
|
-
if (existing ===
|
|
588
|
-
newFm = fm.replace(
|
|
592
|
+
if (lineRe.test(fm)) {
|
|
593
|
+
const existing = (fm.match(lineRe) || [''])[0]
|
|
594
|
+
if (existing === `${field}: ${commit}`) return false // 值未变,幂等跳过
|
|
595
|
+
newFm = fm.replace(lineRe, `${field}: ${commit}`)
|
|
589
596
|
} else {
|
|
590
|
-
//
|
|
591
|
-
|
|
592
|
-
const anchor =
|
|
593
|
-
|
|
594
|
-
|
|
597
|
+
// 插入锚点(D-010 协议顺序 repo→base_commit→head_commit):head 跟 base 后,
|
|
598
|
+
// base 跟 repo 后,都无则 frontmatter 首字段
|
|
599
|
+
const anchor = `${field}: ${commit}`
|
|
600
|
+
const prevField = field === 'head_commit' ? 'base_commit' : 'repo'
|
|
601
|
+
const prevRe = new RegExp(`^${prevField}:\\s.*$`, 'm')
|
|
602
|
+
if (prevRe.test(fm)) {
|
|
603
|
+
newFm = fm.replace(prevRe, m => m + '\n' + anchor)
|
|
595
604
|
} else {
|
|
596
|
-
|
|
597
|
-
newFm = anchor.trimEnd() + '\n' + fm
|
|
605
|
+
newFm = anchor + '\n' + fm
|
|
598
606
|
}
|
|
599
607
|
}
|
|
600
608
|
const newContent = content.replace(/^---\n[\s\S]*?\n---/, `---\n${newFm}\n---`)
|
|
@@ -607,6 +615,75 @@ function writeBaseCommitToTaskCard(taskFilePath, baseCommit) {
|
|
|
607
615
|
}
|
|
608
616
|
}
|
|
609
617
|
|
|
618
|
+
function writeBaseCommitToTaskCard(taskFilePath, baseCommit) {
|
|
619
|
+
return writeCommitAnchorToTaskCard(taskFilePath, 'base_commit', baseCommit)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* 跨仓 task head_commit 锡点自动落盘(2026-08-21 agent-手工产出审计项③,D-010 补对称)。
|
|
624
|
+
*
|
|
625
|
+
* base_commit 已在派发时 CLI 落盘,head_commit 此前靠主 agent 按 Wave prompt 指引手跑
|
|
626
|
+
* `rev-parse` 手写(漏抄/抄错直接炸 Task Review Gate 真实性校验)。本函数在 execute
|
|
627
|
+
* --done 时机补齐另一半:扫描 task 卡,跨仓 task(repo≠main)缺 head_commit 的,实时
|
|
628
|
+
* `git -C <跨仓仓根> rev-parse HEAD` 幂等写入(**已存在不覆盖**——agent 手写的精确锚点优先)。
|
|
629
|
+
*
|
|
630
|
+
* best-effort:MultiRepoContext 构造失败 / git 不可达 → 跳过该 task 记 reason,绝不抛。
|
|
631
|
+
*
|
|
632
|
+
* @param {{ changeName: string, cwd: string, specBase?: string, platformOpts?: object }} opts
|
|
633
|
+
* @returns {Promise<{ stamped: number, stampedTasks: string[], skipped: number, reasons: string[] }>}
|
|
634
|
+
*/
|
|
635
|
+
export async function stampCrossRepoHeadCommits({ changeName, cwd, specBase, platformOpts = {} }) {
|
|
636
|
+
const base = specBase || platformOpts.specRoot || path.join(cwd, '.sillyspec')
|
|
637
|
+
const reasons = []
|
|
638
|
+
const stampedTasks = []
|
|
639
|
+
if (!changeName) {
|
|
640
|
+
return { stamped: 0, stampedTasks, skipped: 0, reasons: ['无 changeName'] }
|
|
641
|
+
}
|
|
642
|
+
const tasksDir = path.join(base, 'changes', changeName, 'tasks')
|
|
643
|
+
if (!existsSync(tasksDir)) {
|
|
644
|
+
return { stamped: 0, stampedTasks, skipped: 0, reasons: ['无 tasks/ 目录(单 task 变更,无跨仓锡点)'] }
|
|
645
|
+
}
|
|
646
|
+
let ctx = null
|
|
647
|
+
try {
|
|
648
|
+
const { getOrCreateMultiRepoContext } = await import('../run/shared.js')
|
|
649
|
+
ctx = await getOrCreateMultiRepoContext({ cwd, changeName, platformOpts })
|
|
650
|
+
} catch (e) {
|
|
651
|
+
return { stamped: 0, stampedTasks, skipped: 0, reasons: [`MultiRepoContext 构造失败: ${e.message}`] }
|
|
652
|
+
}
|
|
653
|
+
let stamped = 0
|
|
654
|
+
let skipped = 0
|
|
655
|
+
for (const f of readdirSync(tasksDir).filter(n => /^task-\d+\.md$/.test(n)).sort()) {
|
|
656
|
+
const taskFile = path.join(tasksDir, f)
|
|
657
|
+
let content
|
|
658
|
+
try {
|
|
659
|
+
content = readFileSync(taskFile, 'utf8')
|
|
660
|
+
} catch {
|
|
661
|
+
skipped++
|
|
662
|
+
continue
|
|
663
|
+
}
|
|
664
|
+
if (/^head_commit:/m.test(content)) continue // 已有(手写或前次落盘)不覆盖
|
|
665
|
+
const repoKey = parseRepo(content)
|
|
666
|
+
if (!repoKey || repoKey === 'main') continue // 主仓 task 锚 meta,无 head_commit 字段
|
|
667
|
+
const entry = ctx && ctx.resolve ? ctx.resolve(repoKey) : null
|
|
668
|
+
if (!entry || !entry.gitDir) {
|
|
669
|
+
skipped++
|
|
670
|
+
reasons.push(`${f}: repo "${repoKey}" 无法解析 gitDir(local.yaml repos 未注册?)`)
|
|
671
|
+
continue
|
|
672
|
+
}
|
|
673
|
+
const head = gitQuiet(entry.gitDir, ['rev-parse', 'HEAD'])
|
|
674
|
+
if (!head) {
|
|
675
|
+
skipped++
|
|
676
|
+
reasons.push(`${f}: git rev-parse HEAD 失败(${entry.gitDir})`)
|
|
677
|
+
continue
|
|
678
|
+
}
|
|
679
|
+
if (writeCommitAnchorToTaskCard(taskFile, 'head_commit', head)) {
|
|
680
|
+
stamped++
|
|
681
|
+
stampedTasks.push(f.replace(/\.md$/, ''))
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return { stamped, stampedTasks, skipped, reasons }
|
|
685
|
+
}
|
|
686
|
+
|
|
610
687
|
/**
|
|
611
688
|
* 为 Wave 生成 prompt(强制子代理执行)
|
|
612
689
|
*
|
|
@@ -763,9 +840,9 @@ ${crossLines}
|
|
|
763
840
|
**派发跨仓 task 前(base 锡点,CLI 已在 prompt 构造时落盘):**
|
|
764
841
|
- 跨仓 task 卡 frontmatter 的 \`base_commit\` 已由 CLI 实时 \`git -C <跨仓仓根> rev-parse HEAD\` 锁定(base 锡点,约束①)。子代理在此 HEAD 上改+commit,不受同 Wave 其他 task 推进 HEAD 影响。
|
|
765
842
|
|
|
766
|
-
**回收跨仓 task
|
|
767
|
-
-
|
|
768
|
-
- review.json 的
|
|
843
|
+
**回收跨仓 task(head 锡点,CLI 自动落盘,勿手写):**
|
|
844
|
+
- 子代理完成 commit 后,正常写 review.json(verdict/notes)并勾选 checkbox 即可。execute \`--done\` 时 CLI 自动 \`git -C <跨仓仓根> rev-parse HEAD\` 写入该 task 卡 \`head_commit:\`(幂等,已存在不覆盖——你若手写了精确锚点则以你的为准)。
|
|
845
|
+
- review.json 的 mechanics 字段(\`base\`/\`head\`/\`changedFiles\`)无需手算:\`base\` 取 task 卡 \`base_commit\`、\`head\` 取 task 卡 \`head_commit\`,写完跑 \`sillyspec backfill-reviews --change <变更名> --adopt\` 一键代填(verdict 保留)。
|
|
769
846
|
|
|
770
847
|
**跨仓 task 子代理 prompt 必须注入:**
|
|
771
848
|
> 该 task 改的是 \`<repo>\` 仓,workdir=\`<跨仓仓根>\`。**直接在该仓主干工作区改+commit(git add + git commit 到该仓主干),不经主仓 worktree、不建分支。** commit 到该仓主干即落盘,apply 阶段对跨仓 task 为 no-op(design §5.4 G1)。
|
|
@@ -958,6 +1035,13 @@ task-XX 对应:{SPEC_ROOT}/.runtime/execute-runs/{EXECUTE_RUN_ID}/tasks/task-X
|
|
|
958
1035
|
- 无 _module-map.yaml 时跳过模块匹配,仅按文件清单更新 unmapped 部分
|
|
959
1036
|
这是可选更新(不阻断 execute 完成),但保持 module-impact 与实际变更一致利于 verify 核对与 archive 终审。
|
|
960
1037
|
|
|
1038
|
+
### 知识沉淀(主代理在本 Wave 收尾,与 module-impact.md 同批)
|
|
1039
|
+
本 Wave 各 task 子代理报告里若提到**真正的项目特有坑**(跨变更可复用、未来 agent 可能再次踩到、且不是本任务专属细节),由你在 Wave 收尾统一追加到 \`{SPEC_ROOT}/knowledge/uncategorized.md\`:
|
|
1040
|
+
- 条目格式:\`## <一句话标题>\` + 一段说明(坑的来龙去脉 + 规避/解法),末尾标注来源(task-XX)
|
|
1041
|
+
- 不由各 task 子代理分别写(并行改同一文件会互相覆盖),只由主代理在 Wave 收尾统一追加——与 module-impact.md 同一收尾节奏
|
|
1042
|
+
- 不要为了完成任务而硬凑条目;纯新增/纯样式/单点 bug 修复/临时绕过不写;拿不准时不写,宁缺毋滥
|
|
1043
|
+
- execute 末步「知识库审阅」会检查待确认条目并提示用户归类
|
|
1044
|
+
|
|
961
1045
|
### 完成后
|
|
962
1046
|
1. 为每个后端 router task,扫描变更文件提取 API 端点 artifact:
|
|
963
1047
|
- 在变更文件中搜索所有 router 注册路径(@router.get/post/put/delete)
|
package/src/stages/knowledge.js
CHANGED
|
@@ -232,11 +232,12 @@ export async function cmdValidate(dir, args, opts = {}) {
|
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
// 4. uncategorized.md
|
|
235
|
+
// 4. uncategorized.md 条目数(多了提示需清理)。条目行契约是 `## <qlId> | <标题>`
|
|
236
|
+
//(quick 自动候选与手工条目同格式),h3 形态一并兼容;原 `^###` 只数 h3 恒为 0
|
|
236
237
|
const uncategorizedPath = join(knowledgeDir, 'uncategorized.md')
|
|
237
238
|
if (existsSync(uncategorizedPath)) {
|
|
238
239
|
const content = readFileSync(uncategorizedPath, 'utf8')
|
|
239
|
-
const entryCount = (content.match(
|
|
240
|
+
const entryCount = (content.match(/^#{2,3}\s+\S/gm) || []).length
|
|
240
241
|
if (entryCount >= 10) {
|
|
241
242
|
warnings.push({
|
|
242
243
|
code: 'too_many_uncategorized',
|
|
@@ -276,10 +277,25 @@ export async function cmdRefresh(dir, args, opts = {}) {
|
|
|
276
277
|
// refresh 只写 generated/ 区
|
|
277
278
|
mkdirSync(generatedDir, { recursive: true })
|
|
278
279
|
|
|
279
|
-
// 扫描 scan
|
|
280
|
+
// 扫描 scan 文档。项目名用 DB 注册名优先(坑 knowledge-basename-project-mismatch):
|
|
281
|
+
// scan 写侧 docs/<project> 的 project 来自 dbProjectName || basename(cwd),注册名 ≠ 目录名
|
|
282
|
+
// (projects yaml name/path 分离、平台模式 specRoot≠sourceRoot)时,用 basename(dir) 拼
|
|
283
|
+
// scanDir 永远找不到 → refresh 恒报 scan_docs_missing。DB 读失败回退 basename(与写侧
|
|
284
|
+
// default 同口径)。查不到注册名对应目录时再回退 basename 试一次(双形态兼容)。
|
|
280
285
|
const base = opts.specDir || join(dir, '.sillyspec')
|
|
281
|
-
|
|
282
|
-
|
|
286
|
+
let projectName = null
|
|
287
|
+
try {
|
|
288
|
+
const { DatabaseSync } = await import('node:sqlite')
|
|
289
|
+
const db = new DatabaseSync(join(base, '.runtime', 'sillyspec.db'), { readOnly: true })
|
|
290
|
+
const row = db.prepare('SELECT name FROM project LIMIT 1').get()
|
|
291
|
+
db.close()
|
|
292
|
+
if (row && row.name) projectName = row.name
|
|
293
|
+
} catch { /* db 不可读 → 走 basename 兜底 */ }
|
|
294
|
+
if (!projectName) projectName = basename(dir)
|
|
295
|
+
let scanDir = join(base, 'docs', projectName, 'scan')
|
|
296
|
+
if (!existsSync(scanDir) && projectName !== basename(dir)) {
|
|
297
|
+
scanDir = join(base, 'docs', basename(dir), 'scan')
|
|
298
|
+
}
|
|
283
299
|
|
|
284
300
|
if (!existsSync(scanDir)) {
|
|
285
301
|
output(false, {}, { code: 'scan_docs_missing', path: scanDir })
|
|
@@ -348,7 +348,10 @@ function parseTaskWavesFromPlan(planPath) {
|
|
|
348
348
|
// (旧变更读侧兼容——新格式由 validatePlanForExecute 拦旧形态,此处不重复把关)
|
|
349
349
|
const tm = line.match(/^[-*]\s+(?:\[[ x]\]\s*)?(task-\d+)\s*[::\s]*$/i)
|
|
350
350
|
|| line.match(/^[-*]\s*\[[ x]\]\s*(task-\d+)\b/i)
|
|
351
|
-
|
|
351
|
+
// id 归一补零(与卡片/注册表 task-01 口径一致,坑 wave-ref-unpadded):plan.md 写
|
|
352
|
+
// `- task-1` 时裸用捕获原文做 key,永远匹配不到卡片 id task-01 → 被判「未列入任何
|
|
353
|
+
// Wave」,同 Wave 共享文件检测静默跳过。execute.js parseWavesFromPlan 已补零,对齐。
|
|
354
|
+
if (tm && currentWave !== null) map.set(String(tm[1]).replace(/^task-(\d+)$/i, (_, n) => `task-${n.padStart(2, '0')}`), currentWave)
|
|
352
355
|
}
|
|
353
356
|
return map
|
|
354
357
|
}
|
|
@@ -614,16 +617,19 @@ export function parseLocalYamlModules(yamlText) {
|
|
|
614
617
|
const modules = {}
|
|
615
618
|
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
616
619
|
const line = lines[i]
|
|
617
|
-
const entry = line.match(/^
|
|
620
|
+
const entry = line.match(/^([ \t]+)([A-Za-z0-9_.\-]+):\s*(.*)$/)
|
|
618
621
|
if (!entry) {
|
|
619
622
|
// 遇到新的顶层 key(行首非空格且非注释)→ modules 块结束
|
|
620
623
|
if (line.length > 0 && !line.startsWith(' ') && !line.startsWith('#') && line.trim() !== '') break
|
|
621
624
|
continue
|
|
622
625
|
}
|
|
623
|
-
|
|
626
|
+
// 条目缩进放宽为「任意非零空白」(坑 modules-indent-hardcoded):此前硬编码恰 2 空格,
|
|
627
|
+
// 4 空格/Tab 缩进的条目不匹配且不触发块结束 → 被静默跳过 → modules 丢失 →
|
|
628
|
+
// validateScriptCommands 退化为只查根 package.json,monorepo 子包命令被误报不存在。
|
|
629
|
+
const rest = entry[3] || ''
|
|
624
630
|
// path 值:带引号或不带引号(flow mapping),取第一个匹配
|
|
625
631
|
const pathMatch = rest.match(/path:\s*"([^"]+)"/) || rest.match(/path:\s*([^,\s}]+)/)
|
|
626
|
-
if (pathMatch) modules[entry[
|
|
632
|
+
if (pathMatch) modules[entry[2]] = { path: pathMatch[1] }
|
|
627
633
|
}
|
|
628
634
|
return Object.keys(modules).length > 0 ? modules : null
|
|
629
635
|
}
|
|
@@ -669,9 +675,14 @@ export function validateTaskCommands(changeDir, projectRoot, modules = null) {
|
|
|
669
675
|
continue // 非法 YAML 由 feasibility 处理,不重复
|
|
670
676
|
}
|
|
671
677
|
|
|
672
|
-
// 合并 verify + implementation
|
|
673
|
-
|
|
674
|
-
|
|
678
|
+
// 合并 verify + implementation 文本(命令可能出现在任一字段)。
|
|
679
|
+
// 字段两形态都收(坑 taskcard-block-list-noop):规范格式(taskcard.js 骨架 / plan 模板)的
|
|
680
|
+
// verify/implementation 是 YAML 块列表 → js-yaml 解析为数组;此前只收 string,规范卡片
|
|
681
|
+
// 恒落空 → 「命令存在性校验」(hard-error)对合规 TaskCard 整体 no-op,只有违规写成
|
|
682
|
+
// 纯标量的卡片才被校验。数组按行 join 回文本,与标量同管道。
|
|
683
|
+
const toCmdText = (v) => Array.isArray(v) ? v.map(String).join('\n') : (typeof v === 'string' ? v : '')
|
|
684
|
+
const verifyText = toCmdText(fm.verify)
|
|
685
|
+
const implText = toCmdText(fm.implementation)
|
|
675
686
|
const text = `${verifyText}\n${implText}`
|
|
676
687
|
if (!text.trim()) continue
|
|
677
688
|
|
|
@@ -1047,8 +1058,10 @@ export function validatePlanFeasibility(changeDir, projectRoot = null) {
|
|
|
1047
1058
|
if (allTaskIds.length > 0) {
|
|
1048
1059
|
const nums = allTaskIds.map(id => {
|
|
1049
1060
|
const m = id.match(/task-(\d+)/i)
|
|
1050
|
-
return m ? parseInt(m[1]) : null
|
|
1061
|
+
return m ? parseInt(m[1], 10) : null
|
|
1051
1062
|
}).filter(n => n !== null).sort((a, b) => a - b)
|
|
1063
|
+
// nums[0]!==1 时跳过 = 兼容旧变更编号不从 1 起(契约同 validatePlanForExecute 检查 3,
|
|
1064
|
+
// test/plan-execute-contract.test.mjs Case 10 钉死,勿改)
|
|
1052
1065
|
if (nums.length > 0 && nums[0] === 1) {
|
|
1053
1066
|
for (let i = 0; i < nums.length; i++) {
|
|
1054
1067
|
if (nums[i] !== i + 1) {
|
|
@@ -1280,11 +1293,12 @@ export async function executePlanPostcheck(context) {
|
|
|
1280
1293
|
currentWaveTasks = null
|
|
1281
1294
|
continue
|
|
1282
1295
|
}
|
|
1283
|
-
// 2026-08-20-task-truth-unify:Wave 段下纯 ID 引用行(- task-NN);旧 checkbox
|
|
1296
|
+
// 2026-08-20-task-truth-unify:Wave 段下纯 ID 引用行(- task-NN);旧 checkbox 行同收。
|
|
1297
|
+
// 编号补零对齐卡片口径(坑 wave-ref-unpadded,同 parseTaskWavesFromPlan)
|
|
1284
1298
|
const tm = line.match(/^[-*]\s+(?:\[[ x]\]\s*)?task-(\d+)\s*[::\s]*$/i)
|
|
1285
1299
|
|| line.match(/^[-*]\s*\[[ x]\]\s*task-(\d+)/i)
|
|
1286
1300
|
if (tm && currentWaveTasks) {
|
|
1287
|
-
currentWaveTasks.push(`task-${tm[1]}`)
|
|
1301
|
+
currentWaveTasks.push(`task-${String(tm[1]).padStart(2, '0')}`)
|
|
1288
1302
|
}
|
|
1289
1303
|
}
|
|
1290
1304
|
if (currentWaveTasks) existingWaves.push(currentWaveTasks)
|
package/src/stages/plan.js
CHANGED
|
@@ -564,7 +564,9 @@ export const fixedSuffix = [] // postcheck 是动态生成的(需要 changeDir
|
|
|
564
564
|
*/
|
|
565
565
|
function parseTaskCount(registryContent) {
|
|
566
566
|
if (!registryContent || typeof registryContent !== 'string') return 0
|
|
567
|
-
|
|
567
|
+
// [ xX] + i(坑 taskcount-uppercase-x):parseTaskNames / gates.js extractTaskIdsFromRegistry
|
|
568
|
+
// 均收大写 [X],此处漏收 → 全大写勾选的注册表计数 0 → 误走「无任务」分支丢 coordinator 步
|
|
569
|
+
const matches = registryContent.match(/^[-*]\s*\[[ xX]\]\s*task-\d+/gim)
|
|
568
570
|
return matches ? matches.length : 0
|
|
569
571
|
}
|
|
570
572
|
|
|
@@ -624,15 +626,18 @@ export function buildPlanSteps(changeDir = null, planContent = null) {
|
|
|
624
626
|
return [...fixedPrefix, ...postcheck]
|
|
625
627
|
}
|
|
626
628
|
|
|
627
|
-
//
|
|
629
|
+
// 解析任务名(源与计数同口径,坑 plan-tasknames-empty-under-registry-contract):新契约
|
|
630
|
+
// (2026-08-20-task-truth-unify)下 plan.md Wave 段是纯 ID 引用行(无 checkbox 无任务名),
|
|
631
|
+
// 此前只从 planContent/plan.md 解析 → taskNames 恒空 → coordinator 步骤任务清单空白,
|
|
632
|
+
// 主 agent 只能自行读 tasks.md 兜底易漏派。tasks.md 优先(与 readTaskRegistryContent 同序)。
|
|
628
633
|
let taskNames = []
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
634
|
+
let namesSource = planContent || null
|
|
635
|
+
if (changeDir) {
|
|
636
|
+
const registryContent = readTaskRegistryContent(changeDir)
|
|
637
|
+
if (registryContent != null) namesSource = registryContent
|
|
638
|
+
}
|
|
639
|
+
if (namesSource) {
|
|
640
|
+
taskNames = parseTaskNames(namesSource)
|
|
636
641
|
}
|
|
637
642
|
|
|
638
643
|
// 生成协调器步骤(TaskCard 生成)+ postcheck
|
package/src/stages/quick.js
CHANGED
|
@@ -81,7 +81,14 @@ step 3 --done 审计会把 src 核心文件(CLI / 状态机 / 注入框架等
|
|
|
81
81
|
- 本步骤是最后一步,完成后 quick 会话即结束(--change 传递见首步 sessionId 说明)
|
|
82
82
|
|
|
83
83
|
### ⚠️ 结果摘要模板(必填,CLI 会校验结构)
|
|
84
|
-
|
|
84
|
+
|
|
85
|
+
🔧 **推荐四参数形式**(CLI 自动合成结构化模板,杜绝嵌套冒号/缺字段/截断规则踩坑):
|
|
86
|
+
|
|
87
|
+
\`\`\`
|
|
88
|
+
sillyspec run quick --done --change <id> --req "一句话语义化短标题(写「改了什么」)" --cause "为什么这样改(纯新增/配置/样式写「无,纯新增/纯样式」)" --solution "怎么改的" --result "验证情况(测试数 / lint / typecheck / 部署状态)"
|
|
89
|
+
\`\`\`
|
|
90
|
+
|
|
91
|
+
兼容旧形式 \`--output "需求:… 根因:… 方案:… 结果:…"\`(**必须按此结构给全四项**,逐项一句话,不可用「见前述」替代;缺任一项 \`--done\` 会被拒并提示缺失字段,补全后重跑即可不丢进度):
|
|
85
92
|
|
|
86
93
|
\`\`\`
|
|
87
94
|
需求:一句话语义化短标题(写「改了什么」)
|
|
@@ -90,15 +97,13 @@ step 3 --done 审计会把 src 核心文件(CLI / 状态机 / 注入框架等
|
|
|
90
97
|
结果:验证情况(测试数 / lint / typecheck / 部署状态)
|
|
91
98
|
\`\`\`
|
|
92
99
|
|
|
93
|
-
⚠️
|
|
94
|
-
|
|
95
|
-
缺任一项,\`--done\` 会被拒并提示缺失字段,补全后重跑即可(不丢进度)。
|
|
100
|
+
⚠️ **「需求:」(= --req)就是 QUICKLOG 条目标题**——CLI 提取该字段、截到首个标点(,。;,;)、超 80 字再截断。写成完整需求长句会被截成语义不完整的状语前半段(如「在 X 时,…」标题只剩「在 X 时」),事后还得手改。一句话说清改了什么(如「登录限流修复——INCR 计数误清」),需求背景放「根因:/方案:」字段。旧形式正文内避免嵌套全角冒号(标签后紧接嵌套冒号会被拆分判定缺字段)——这正是四参数形式消灭的事故面。
|
|
96
101
|
|
|
97
102
|
### 📎 文件括注 --file-notes(可选,省事后手改文件行)
|
|
98
103
|
若希望 QUICKLOG「文件:」行直接落盘为多行带括注(而非单行逗号路径),\`--done\` 时加 \`--file-notes\`,CLI 落盘为多行 \`- path(括注)\`,免去事后手改:
|
|
99
104
|
|
|
100
105
|
\`\`\`
|
|
101
|
-
sillyspec run quick --done --change <id> --
|
|
106
|
+
sillyspec run quick --done --change <id> --req "…" --cause "…" --solution "…" --result "…" --file-notes "src/a.js::登录端点串限流 || src/b.js::新建 INCR 计数"
|
|
102
107
|
\`\`\`
|
|
103
108
|
|
|
104
109
|
格式:\`path::括注\` 一条,\`||\` 分隔多条(括注可省略只留 \`path\`)。不传则「文件:」行回填审计到的实际改动文件单行(仍可事后手改)。
|
|
@@ -116,7 +121,10 @@ sillyspec run quick --done --change <id> --output "需求:… 根因:… 方
|
|
|
116
121
|
- 禁止使用 \`git add -A\`
|
|
117
122
|
- 不要暂存 quick 开始前就已存在的无关改动
|
|
118
123
|
3. QUICKLOG / tasks.md 记录由 CLI 在本 step 完成时自动收尾(翻「已完成」+ 勾选 task + 轮转),你无需手动建/翻骨架——**QUICKLOG 核对按需**(见下方「QUICKLOG 正文核对」;文件行可用 --file-notes 免手改)
|
|
119
|
-
4.
|
|
124
|
+
4. 只有遇到**真正的项目特有坑**(跨变更可复用、未来 agent 可能再次踩到、且不是本任务专属细节)时,才追加到 \`{SPEC_ROOT}/knowledge/uncategorized.md\`:
|
|
125
|
+
- 格式:\`## <一句话标题>\` + 一段说明(坑的来龙去脉 + 规避/解法),末尾标注来源 \`(<quicklog-id>)\`
|
|
126
|
+
- 不要为了完成任务而硬凑条目;纯新增/纯样式/单点 bug 修复/临时绕过不写
|
|
127
|
+
- 拿不准时不写,宁缺毋滥
|
|
120
128
|
5. 任务比预期复杂 → 建议用完整流程
|
|
121
129
|
|
|
122
130
|
### 模块文档同步
|
package/src/stages/scan.js
CHANGED
|
@@ -154,6 +154,7 @@ export const definition = {
|
|
|
154
154
|
generator: sillyspec-scan
|
|
155
155
|
---
|
|
156
156
|
\`\`\`
|
|
157
|
+
(\`<git-head-short>\` / \`<now-iso-datetime>\` 两占位符由 CLI 注入 prompt 时自动替换为当前 HEAD 短哈希与时间——照 prompt 里已替换的真实值抄写,勿自跑 git rev-parse;漏 header 事后 \`sillyspec scan-fix-headers\` 一键补 author/created_at)
|
|
157
158
|
- 覆盖已有 scan 文档前先读取旧 frontmatter;如果旧文档的 \`source_commit\` 与当前 HEAD 不一致,或旧文档 \`updated_at\` 晚于本次 scan 开始时间,不要覆盖。
|
|
158
159
|
- 如果用户明确传入 \`--force-rescan\`,允许覆盖,但仍需写入新的 \`source_commit\` 和 \`updated_at\`。
|
|
159
160
|
|
package/src/stages/verify.js
CHANGED
|
@@ -197,57 +197,15 @@ export const definition = {
|
|
|
197
197
|
你只需:在 verify-result.md 的「变更风险等级」section 如实记录变更性质;若变更涉及 daemon/backend 跨进程、session/lease/lifecycle 状态机、或部署启动路径,在「Runtime Evidence」section 提供真实集成证据(启动命令、daemon↔backend 调用与日志关键片段、终态断言)。
|
|
198
198
|
- **集成证据是自报告、CLI 不独立运行时核验**:「Runtime Evidence」由你如实填写,CLI 只校验其**字面存在**(是否含关键词),**不会替你启动 daemon、打真实请求或跑迁移**——它是否名副其实取决于你是否实跑过。务必实跑后据实填写,不得凭堆关键词通过门控。(测试套件对账另算:commands.test 由 CLI 真实执行,那块谎报无效。)
|
|
199
199
|
|
|
200
|
-
3.
|
|
201
|
-
4. 给出结论:PASS / PASS WITH NOTES / FAIL
|
|
200
|
+
3. **生成 verify-result.md 骨架(勿从零手写)**:先跑 \`sillyspec verify-probes --change <change-name> --init\`——一条命令生成九章节骨架(已存在不覆盖),其中**探针结果章节已机械预填**(探针 1 的 TODO/FIXME 命中清单、探针 3 的测试覆盖、探针 5 的 API 契约对账表、探针 6 的删除对账三态判定),文件落 \`.sillyspec/changes/<change-name>/verify-result.md\`。你只需把各 \`<!--TODO-->\` 占位替换为语义结论;半语义探针(2 关键词覆盖 / 4 决策追踪)与断言抽查、集成盲区标注由你补在对应 TODO 处
|
|
201
|
+
4. 给出结论:PASS / PASS WITH NOTES / FAIL(受风险门控约束)——**结论必须写明 PASS/FAIL 字样,留「待填」会被 gate 判不过**
|
|
202
202
|
5. **核对 module-impact.md**(若 changes/<change>/module-impact.md 存在):对照本次实际代码变更(git diff)与 module-impact.md 的模块影响矩阵,发现不一致(漏标受影响模块 / 影响类型错误 / 实际未触碰的模块被误标)则在 verify-result.md 标注。module-impact 由 plan 首版生成、execute 各 Wave 更新,verify 是最后一次核对机会(archive 仅终审不再生成)。这是 advisory 核对(不阻断 verify 完成),但 module-impact 与实际严重背离应记为风险。
|
|
203
203
|
|
|
204
|
-
### verify-result.md
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
PASS / PASS WITH NOTES / FAIL
|
|
210
|
-
|
|
211
|
-
## 任务完成度
|
|
212
|
-
(逐项检查任务的结果)
|
|
213
|
-
|
|
214
|
-
## 设计一致性
|
|
215
|
-
(对照 design.md 的检查结果)
|
|
216
|
-
|
|
217
|
-
## 探针结果
|
|
218
|
-
- 未实现标记扫描:...
|
|
219
|
-
- 关键词覆盖:...
|
|
220
|
-
- 测试覆盖(含断言有效性抽查):...
|
|
221
|
-
- 决策追踪覆盖:...
|
|
222
|
-
- API 契约对账:...
|
|
223
|
-
- 代码删除对账:声明修改/新增却被整文件删除 = 高风险;清单未列出的删除 = 未声明(CLI 会用 git diff --name-status HEAD advisory 复核,仅 warn 不阻断——是否 FAIL blocker 由你诚实判定)
|
|
224
|
-
|
|
225
|
-
## 决策追踪矩阵(如存在 decisions.md)
|
|
226
|
-
| 决策 ID | FR | Task | Evidence | 状态 |
|
|
227
|
-
|---|---|---|---|---|
|
|
228
|
-
| D-001@v1 | FR-01 | task-01 | test/file/path | PASS/PARTIAL/MISSING |
|
|
229
|
-
|
|
230
|
-
## 测试结果
|
|
231
|
-
(测试套件执行结果)
|
|
232
|
-
|
|
233
|
-
## 技术债务
|
|
234
|
-
(TODO/FIXME/HACK 统计)
|
|
235
|
-
|
|
236
|
-
## 变更风险等级
|
|
237
|
-
(自动检测的 change_risk_profile: doc-only / unit-sufficient / contract-required / integration-critical / deployment-critical;若 design.md frontmatter 有 risk_level 显式声明,以声明为准并在此注明「显式声明 = <等级>」+ 理由)
|
|
238
|
-
|
|
239
|
-
## Runtime Evidence(integration-critical / deployment-critical 必填;自报告,CLI 仅校验字面存在、不独立核验,须真实执行过)
|
|
240
|
-
按本次变更实际触碰的运行时组件填写(模板是通用形态——长驻进程 / 服务端点 / 生命周期状态机的证据结构;未涉及的行写「不涉及」并注明原因,勿堆砌无关关键词):
|
|
241
|
-
- 长驻进程/服务 启动命令:
|
|
242
|
-
- 服务地址(本变更触碰的端点):
|
|
243
|
-
- 触发核心路径的请求/命令(附关键响应):
|
|
244
|
-
- 进程日志关键片段(证明走了新路径、无降级/回退分支):
|
|
245
|
-
- 生命周期终态断言(本变更涉及的状态机:初始态 → 运行态 → 终态,逐个确认到达):
|
|
246
|
-
- 失败模式排除(针对本变更声明的风险,逐条说明为何未触发):
|
|
247
|
-
|
|
248
|
-
## 代码审查
|
|
249
|
-
(问题列表 + 总体评价)
|
|
250
|
-
\`\`\`
|
|
204
|
+
### verify-result.md 章节结构(骨架已含,占位替换即可)
|
|
205
|
+
|
|
206
|
+
结论(PASS / PASS WITH NOTES / FAIL)→ 任务完成度 → 设计一致性 → 探针结果(已预填)→ 测试结果 → 决策追踪矩阵(\`| 决策 ID | FR | Task | Evidence | 状态 |\`,存在 decisions.md 才留)→ 技术债务 → 变更风险等级 → Runtime Evidence → 代码审查。
|
|
207
|
+
|
|
208
|
+
**Runtime Evidence 行结构**(integration/deployment-critical 必填;按实际触碰的运行时组件写,未涉及的行写「不涉及」勿堆关键词):长驻进程启动命令 / 触碰的服务端点 / 触发核心路径的请求(附关键响应)/ 进程日志关键片段(证明走了新路径)/ 生命周期终态断言(初始态→运行态→终态)/ 失败模式排除(逐条说明为何未触发)。
|
|
251
209
|
|
|
252
210
|
### 输出
|
|
253
211
|
verify-result.md 路径 + 验证报告摘要 + 下一步命令
|