sillyspec 3.28.12 → 3.29.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/.claude/skills/sillyspec-execute/SKILL.md +222 -223
- package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
- package/.claude/skills/sillyspec-quick/SKILL.md +8 -8
- package/.claude/skills/sillyspec-verify/SKILL.md +87 -91
- package/package.json +1 -1
- package/src/blast-surface.js +168 -0
- package/src/ceremony-tier.js +359 -0
- package/src/change-risk-profile.js +75 -202
- package/src/config-schema.js +21 -0
- package/src/decision-distill.js +32 -25
- package/src/decisions-io.js +32 -0
- package/src/design-facts.js +1 -0
- package/src/doctor-diagnostics.js +148 -1
- package/src/fr-index.js +359 -0
- package/src/index.js +92 -5
- package/src/knowledge-stats.js +124 -2
- package/src/modules.js +28 -0
- package/src/prefill.js +491 -0
- package/src/probe7-anchor-check.js +8 -6
- package/src/quick-gate-profile.js +8 -4
- package/src/quick-session-owner.js +63 -0
- package/src/review-dispatch.js +473 -1
- package/src/review-material-pack.js +265 -0
- package/src/review-tier.js +185 -41
- package/src/run/archive-distill.js +35 -0
- package/src/run/command.js +158 -87
- package/src/run/complete-handlers.js +82 -1
- package/src/run/complete.js +37 -1
- package/src/run/gates.js +301 -19
- package/src/run/prompt.js +454 -21
- package/src/run/shared.js +36 -0
- package/src/run/verify-quality-scan.js +14 -6
- package/src/scope-audit.js +8 -1
- package/src/span-risk-surface.js +127 -0
- package/src/stage-contract-spec.js +2 -2
- package/src/stage-contract.js +172 -98
- package/src/stage-review.js +26 -2
- package/src/stages/brainstorm.js +34 -16
- package/src/stages/execute.js +10 -2
- package/src/stages/plan.js +29 -12
- package/src/stages/verify.js +7 -7
- package/src/taskcard.js +3 -0
- package/src/verify-postcheck.js +375 -20
- package/src/verify-probes.js +964 -30
- package/src/worktree.js +1 -1
- package/templates/prompts/taskcard-rules.md +1 -0
- package/templates/prompts/verify-probes.md +1 -1
package/src/run/shared.js
CHANGED
|
@@ -23,6 +23,7 @@ import { createHash } from 'node:crypto'
|
|
|
23
23
|
// 分级门禁画像信号层(2026-09-14-quick-exit-tiered-gates task-01 / FR-02):纯函数零 IO,
|
|
24
24
|
// 仅依赖 change-risk-profile,无环——auditQuickCompletion 静态 import 安全(task-02 接线)。
|
|
25
25
|
import { computeGateProfile, resolveGateThresholds } from '../quick-gate-profile.js'
|
|
26
|
+
import { loadSpanRiskPatterns } from '../span-risk-surface.js'
|
|
26
27
|
export { safeGit, unquoteGitPath }
|
|
27
28
|
|
|
28
29
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
@@ -1771,10 +1772,15 @@ export async function auditQuickCompletion(cwd, guard, options = {}) {
|
|
|
1771
1772
|
} catch { /* 坏 YAML → 纯默认阈值 */ }
|
|
1772
1773
|
}
|
|
1773
1774
|
const gateFiles = result.changedFiles.filter(f => !isQuicklogFileLineNoise(f, guard.linkedChanges))
|
|
1775
|
+
// span 声明面装载(2026-09-19-span-risk-pattern-migration task-03):与 loadQuickModuleIndex 同
|
|
1776
|
+
// specBase/projectName 上下文(_module-map.yaml 顶层 span_risk 段)→ gateOpts.riskTable;
|
|
1777
|
+
// 无声明项目 → 空表(riskTable 已默认 [],此处显式装载与其他声明面入参同风格,D-003)
|
|
1778
|
+
const riskTable = loadSpanRiskPatterns({ specBase, project: projectName })
|
|
1774
1779
|
const gateOpts = {
|
|
1775
1780
|
...(thresholds ? { thresholds } : {}),
|
|
1776
1781
|
...(noDocs ? { noDocs: true } : {}),
|
|
1777
1782
|
...(Array.isArray(fileNotes) && fileNotes.length > 0 ? { fileNotes } : {}),
|
|
1783
|
+
riskTable,
|
|
1778
1784
|
}
|
|
1779
1785
|
const moduleIndex = await loadQuickModuleIndex(specBase, projectName)
|
|
1780
1786
|
result.gateProfile = computeGateProfile(gateFiles, moduleIndex, gateOpts)
|
|
@@ -1783,6 +1789,36 @@ export async function auditQuickCompletion(cwd, guard, options = {}) {
|
|
|
1783
1789
|
const attrGateFiles = gateFiles.filter(f => !undeclaredNorm.has(String(f).replace(/\\/g, '/')))
|
|
1784
1790
|
result.gateProfile.checks.docClaim = computeGateProfile(attrGateFiles, moduleIndex, gateOpts).checks.docClaim
|
|
1785
1791
|
}
|
|
1792
|
+
// ── FR 写面腐烂 suspect 遥测(钩子 #1,L3 裁决数据,advisory 零阻断)──
|
|
1793
|
+
// quick 永不归档 → FR 索引对 quick 改动无感知(L1 显式划出覆盖面),改掉 FR 覆盖行为后
|
|
1794
|
+
// 条目仍标 active——账本从 quick 路径腐烂且今天完全不可见。此钩子把它量出来:changedFiles
|
|
1795
|
+
// 经 moduleIndex 路径匹配得触达域(与 computeGateProfile 同 join),域内有 active FR 即记
|
|
1796
|
+
// fr-rot-suspect 事件+一行 warn(只提醒不阻断——看得见约束≠声明取代,L3 才有裁决权)。
|
|
1797
|
+
// 盲区预登(评审要求):①unmapped 文件静默漏 join(新路径未录 map 即无信号)②map 文件
|
|
1798
|
+
// 清单自身漂移——**无信号 ≠ 无腐烂**。fail-open:异常跳过不碰审计结论。
|
|
1799
|
+
try {
|
|
1800
|
+
const { readActiveFrDigest } = await import('../fr-index.js')
|
|
1801
|
+
const { appendKnowledgeHit } = await import('../knowledge-hits.js')
|
|
1802
|
+
const gateFilesNorm = gateFiles.map(f => String(f).replace(/\\/g, '/'))
|
|
1803
|
+
const touchedDomains = new Set()
|
|
1804
|
+
for (const [modId, mod] of Object.entries(moduleIndex || {})) {
|
|
1805
|
+
const prefixes = [...(mod && Array.isArray(mod.paths) ? mod.paths : []), ...(mod && Array.isArray(mod.core_files) ? mod.core_files : [])]
|
|
1806
|
+
for (const raw of prefixes) {
|
|
1807
|
+
const p = String(raw).replace(/\\/g, '/').replace(/\/+$/, '')
|
|
1808
|
+
if (p && gateFilesNorm.some(f => f === p || f.startsWith(p + '/'))) { touchedDomains.add(modId); break }
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
if (touchedDomains.size > 0) {
|
|
1812
|
+
const frs = readActiveFrDigest(join(specBase, 'knowledge'), [...touchedDomains])
|
|
1813
|
+
if (frs.length > 0) {
|
|
1814
|
+
appendKnowledgeHit(join(specBase, '.runtime'), {
|
|
1815
|
+
type: 'fr-rot-suspect', change: guard.changeName || null,
|
|
1816
|
+
domains: [...touchedDomains], count: frs.length, source: 'quick-done',
|
|
1817
|
+
})
|
|
1818
|
+
console.warn(`⚠️ [FR 腐烂 suspect·advisory] 本次 quick 触达 ${[...touchedDomains].join('、')} 域的 ${frs.length} 条 active FR——quick 不走归档,若改动影响这些行为,索引不会自动 supersede(L3 声明义务;盲区:unmapped 文件/map 漂移不报警)`)
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
} catch { /* fail-open:遥测异常不影响审计 */ }
|
|
1786
1822
|
} catch { /* 画像 fail-open:异常只跳过(gateProfile 保持 null,D-003 不碰 status) */ }
|
|
1787
1823
|
|
|
1788
1824
|
// --confirm 模式:展示 diff 并等待确认
|
|
@@ -33,7 +33,9 @@ import { join, dirname } from 'node:path'
|
|
|
33
33
|
import { writeAtomicSync } from '../fs-atomic.js'
|
|
34
34
|
import { gitQuiet } from '../git-helper.js'
|
|
35
35
|
import { resolveRuntimeRoot, parsePorcelainPath } from './shared.js'
|
|
36
|
-
import {
|
|
36
|
+
import { resolveChangeRisk, extractExplicitRiskLevel } from '../change-risk-profile.js'
|
|
37
|
+
import { loadBlastDeclarationsAllProjects } from '../blast-surface.js'
|
|
38
|
+
import { parseFileChangeListDetailed } from '../change-list.js'
|
|
37
39
|
import { recordFrictionEvent } from '../friction-tally.js'
|
|
38
40
|
|
|
39
41
|
const RECORD_SCHEMA_VERSION = 1
|
|
@@ -524,13 +526,19 @@ export function evaluateConclusionDraft({ cwd, specBase, changeName, changeDir,
|
|
|
524
526
|
const p6 = (probes.probe6 && probes.probe6.deletions) || []
|
|
525
527
|
if (p6.length > 0) reasons.push(`探针 6 删除对账 ${p6.length} 项(待人工判定)`)
|
|
526
528
|
try {
|
|
529
|
+
// 2026-09-19-ceremony-pricing-five-cuts task-03:声明面判级(design 文件清单 × blast 声明 + explicit;
|
|
530
|
+
// evidenceRequired → 集成证据链不预填——level 兼容字段为 integration-critical)
|
|
527
531
|
const designPath = join(changeDir || '', 'design.md')
|
|
528
|
-
const planPath = join(changeDir || '', 'plan.md')
|
|
529
532
|
const designContent = existsSync(designPath) ? readFileSync(designPath, 'utf8') : ''
|
|
530
|
-
const
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
533
|
+
const declaredFiles = parseFileChangeListDetailed(designPath, { keepSillyspecDocs: true }).map(e => e.path)
|
|
534
|
+
const blastDeclarations = loadBlastDeclarationsAllProjects({ specBase: specBase || dirname(dirname(dirname(designPath))) })
|
|
535
|
+
const risk = resolveChangeRisk({ files: declaredFiles, blastDeclarations, explicitRiskLevel: designContent ? extractExplicitRiskLevel(designContent) : null })
|
|
536
|
+
// 否决面=evidence:true 命中 或 agent 显式自报 critical(诚实承认集成风险 → 不代笔 PASS 草稿;
|
|
537
|
+
// explicit 低档声明不豁免 evidence 命中,D-009)
|
|
538
|
+
if (risk && (risk.evidenceRequired || (risk.explicit && risk.tier === 'S3'))) {
|
|
539
|
+
reasons.push(risk.evidenceRequired
|
|
540
|
+
? `命中 evidence:true 声明危险面(${risk.hitPrefixes.join('、')})——需集成证据链(回执槽 + Runtime Evidence),不预填`
|
|
541
|
+
: `显式自报 risk_level=integration-critical(tier=${risk.tier})——需集成证据链(回执槽 + Runtime Evidence),不预填`)
|
|
534
542
|
}
|
|
535
543
|
} catch (e) {
|
|
536
544
|
reasons.push(`风险判级异常(${e && e.message ? e.message : e})——保守不预填`)
|
package/src/scope-audit.js
CHANGED
|
@@ -29,6 +29,7 @@ import { createHash } from 'node:crypto'
|
|
|
29
29
|
import { join, dirname } from 'node:path'
|
|
30
30
|
import { locateQuickSessionGuard, auditQuickCompletion, resolveRuntimeRoot, collectOtherQuickSessionDeclarations, ancestorSpecDirs, isQuickMetadata } from './run/shared.js'
|
|
31
31
|
import { computeGateProfile, resolveGateThresholds } from './quick-gate-profile.js'
|
|
32
|
+
import { loadSpanRiskPatternsAllProjects } from './span-risk-surface.js'
|
|
32
33
|
import { safeGit } from './git-helper.js'
|
|
33
34
|
import { parseFileChangeListDetailed, pathMatches } from './change-list.js'
|
|
34
35
|
import { classifyToolScaffold } from './worktree-apply.js'
|
|
@@ -432,10 +433,16 @@ async function buildQuickRecordResult(rec, requestedName) {
|
|
|
432
433
|
.filter(Boolean)
|
|
433
434
|
if (rowsPaths.length > 0) {
|
|
434
435
|
const thresholds = await resolveReplayGateThresholds(rec.specBase)
|
|
436
|
+
// span 声明面装载(2026-09-19-span-risk-pattern-migration task-03):重放态 rec 只有
|
|
437
|
+
// specBase 无 projectName 语境 → AllProjects 并集口径(与 verify-postcheck 双跑事实面
|
|
438
|
+
// 同款;单项目仓与实时态 auditQuickCompletion 的 project 装载逐字相同);无声明 → 空表。
|
|
439
|
+
// :517 pickModuleMapProject 内的 computeGateProfile 唯一消费 unmappedFiles.length,
|
|
440
|
+
// riskTable 不参与——不接线(Grill X-7 避免死接线)
|
|
441
|
+
const riskTable = loadSpanRiskPatternsAllProjects({ specBase: rec.specBase })
|
|
435
442
|
gateProfile = computeGateProfile(
|
|
436
443
|
rowsPaths,
|
|
437
444
|
await loadGateModuleIndex(rec.specBase, rowsPaths),
|
|
438
|
-
thresholds ? { thresholds } : {},
|
|
445
|
+
{ ...(thresholds ? { thresholds } : {}), riskTable },
|
|
439
446
|
)
|
|
440
447
|
}
|
|
441
448
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* span-risk-surface.js — 项目声明风险路径面(span 轴路径模式唯一输入源,2026-09-19-span-risk-pattern-migration D-003)
|
|
3
|
+
*
|
|
4
|
+
* 口径真相源:knowledge/conventions.md「判级/定价/门禁输入必须项目声明」条——src/
|
|
5
|
+
* change-risk-profile.js 的旧六域硬编码表(auth/permission/billing/migration/lock/
|
|
6
|
+
* scheduling 全宇宙路径模式,2026-09-19-span-risk-pattern-migration 已退役删除)是
|
|
7
|
+
* 判级/定价域最后一张违反该口径的表,本模块按 D-003 口径取而代之(表退役与消费面切换
|
|
8
|
+
* 见本变更 design.md)。
|
|
9
|
+
*
|
|
10
|
+
* 主声明 = `_module-map.yaml` 顶层 `span_risk:` 段(token 扁平字符串列表,进 git 手工
|
|
11
|
+
* 维护;modules rebuild --force 按未知顶层段原样回插保留——D-008@v2 机制)。token 只做
|
|
12
|
+
* 字面量编译(零新文法:无通配/正则语法,防 YAML 里写正则的转义与评审不可读问题),
|
|
13
|
+
* 编译口径与旧表逐字相同:前界 `(?:^|[/_-])` + 后界 `(?=[/._-]|$)` + `/i`——段边界锚定
|
|
14
|
+
* 防 author/booking/lockfile 类子串假阳(旧表 R-03 口径原样继承)。
|
|
15
|
+
* 未配置/坏声明项目 → 空表(span 模式维度关闭),禁止回退内置表——与 blast「未配置禁
|
|
16
|
+
* 回退」(2026-09-19-ceremony-pricing-five-cuts D-008 先例)同款取舍。
|
|
17
|
+
*
|
|
18
|
+
* 零依赖纯函数(compile/match)+ 两个 IO 装载器(仅 fs/path/js-yaml,与 blast-surface.js
|
|
19
|
+
* 同栈);无 CLI/DB/锁副作用。装载容错立场同 parseModuleMapSimple / loadBlastDeclarations:
|
|
20
|
+
* 坏数据不缺省不拦截(map 缺失/坏 YAML/段非数组 → 空表,条目脏值逐条跳过)。
|
|
21
|
+
*/
|
|
22
|
+
import { readFileSync, existsSync, readdirSync } from 'fs'
|
|
23
|
+
import { join } from 'path'
|
|
24
|
+
import jsYaml from 'js-yaml'
|
|
25
|
+
|
|
26
|
+
/** 正则字面量转义(先例:knowledge-classify.js escapeRegExp——token 只做字面量编译,防特殊字符注入文法) */
|
|
27
|
+
function escapeRegExp(s) {
|
|
28
|
+
return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* token 编译(纯函数):token 字符串数组 → 边界锚定正则表。
|
|
33
|
+
* 逐 token:非字符串跳过(design 兼容策略:条目非字符串逐条跳过);trim 后空串跳过;
|
|
34
|
+
* 重复 token 去重(首个保留,按 trim 后字面量判重)。产物 { pattern, re } 与旧六域
|
|
35
|
+
* 硬编码表条目同形(两消费面现有循环零适配消费)——pattern 为 token
|
|
36
|
+
* 原始字面量(审计标签,ceremony reasons / quick riskHits 输出用),re 编译为
|
|
37
|
+
* `(?:^|[/_-])<token>(?=[/._-]|$)` /i。
|
|
38
|
+
* @param {string[]} tokens
|
|
39
|
+
* @returns {Array<{pattern: string, re: RegExp}>}
|
|
40
|
+
*/
|
|
41
|
+
export function compileSpanRiskPatterns(tokens) {
|
|
42
|
+
if (!Array.isArray(tokens)) return []
|
|
43
|
+
const seen = new Set()
|
|
44
|
+
const out = []
|
|
45
|
+
for (const raw of tokens) {
|
|
46
|
+
if (typeof raw !== 'string') continue // 条目非字符串逐条跳过(手工维护数据容错)
|
|
47
|
+
const token = String(raw).trim()
|
|
48
|
+
if (token === '') continue // 空/纯空白 token 跳过
|
|
49
|
+
if (seen.has(token)) continue // 重复 token 去重(首个保留)
|
|
50
|
+
seen.add(token)
|
|
51
|
+
out.push({ pattern: token, re: new RegExp('(?:^|[/_-])' + escapeRegExp(token) + '(?=[/._-]|$)', 'i') })
|
|
52
|
+
}
|
|
53
|
+
return out
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 共享命中语义(纯函数):files × patterns → `[{ pattern, file }]` 扁平命中数组
|
|
58
|
+
* (两消费面 span 命中的单一语义)。files 逐条反斜杠归一 POSIX(Windows 兼容)并过滤
|
|
59
|
+
* 空串;patterns 逐条防御(null/非对象/re 非 RegExp 跳过);global 正则每次 test 前归零
|
|
60
|
+
* lastIndex——调用方多次调用语义安全(先例:src/ceremony-tier.js span 命中循环同款写法)。
|
|
61
|
+
* @param {string[]} files 变更文件面
|
|
62
|
+
* @param {Array<{pattern: string, re: RegExp}>} patterns compileSpanRiskPatterns 产物
|
|
63
|
+
* @returns {Array<{pattern: string, file: string}>} file 为归一后(POSIX)路径
|
|
64
|
+
*/
|
|
65
|
+
export function matchSpanRiskPatterns(files, patterns) {
|
|
66
|
+
const norm = (Array.isArray(files) ? files : []).map((f) => String(f).replace(/\\/g, '/')).filter(Boolean)
|
|
67
|
+
const list = Array.isArray(patterns) ? patterns : []
|
|
68
|
+
const hits = []
|
|
69
|
+
for (const file of norm) {
|
|
70
|
+
for (const entry of list) {
|
|
71
|
+
if (entry == null || typeof entry !== 'object') continue
|
|
72
|
+
const re = entry.re
|
|
73
|
+
if (!(re instanceof RegExp)) continue
|
|
74
|
+
if (re.global) re.lastIndex = 0 // 防调用方传入 /g 正则跨文件携带 lastIndex 状态
|
|
75
|
+
if (re.test(file)) hits.push({ pattern: entry.pattern, file })
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return hits
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 声明面装载(IO,project 域):`docs/<project>/modules/_module-map.yaml` 顶层 `span_risk:`
|
|
83
|
+
* 段(token 字符串数组)→ 编译产物。容错逐款对照 loadBlastDeclarations(src/blast-surface.js):
|
|
84
|
+
* map 缺失/坏 YAML/顶层非对象/段非数组 → 空表(不缺省不拦截);条目脏值经
|
|
85
|
+
* compileSpanRiskPatterns 逐条跳过。无声明 = span 模式维度关闭(禁回退内置表)。
|
|
86
|
+
* @param {{ specBase: string, project?: string|null }} opts specBase=.sillyspec 根;project=子项目名
|
|
87
|
+
* @returns {Array<{pattern: string, re: RegExp}>}
|
|
88
|
+
*/
|
|
89
|
+
export function loadSpanRiskPatterns({ specBase, project = null } = {}) {
|
|
90
|
+
if (!specBase || !project) return []
|
|
91
|
+
const mapPath = join(specBase, 'docs', project, 'modules', '_module-map.yaml')
|
|
92
|
+
try {
|
|
93
|
+
if (existsSync(mapPath)) {
|
|
94
|
+
const doc = jsYaml.load(readFileSync(mapPath, 'utf8'))
|
|
95
|
+
const spanRisk = doc && typeof doc === 'object' ? doc.span_risk : null
|
|
96
|
+
if (Array.isArray(spanRisk)) return compileSpanRiskPatterns(spanRisk)
|
|
97
|
+
}
|
|
98
|
+
} catch { /* 坏 map → 空表(手工数据容错,不拦截判级) */ }
|
|
99
|
+
return []
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 多项目并集装载(事实面用——调用方无 project 语境时扫各 docs/<project>/modules/
|
|
104
|
+
* _module-map.yaml 取全部项目声明并集):跨项目 token 按 trim 后字面量去重(并集语义,
|
|
105
|
+
* 防重复 pattern 在消费面重复计命中条数);任一项目装载异常 → 该项目按空表跳过
|
|
106
|
+
* (loadSpanRiskPatterns 内建 catch;形态对照 loadBlastDeclarationsAllProjects)。
|
|
107
|
+
* @param {{ specBase: string }} opts
|
|
108
|
+
* @returns {Array<{pattern: string, re: RegExp}>}
|
|
109
|
+
*/
|
|
110
|
+
export function loadSpanRiskPatternsAllProjects({ specBase } = {}) {
|
|
111
|
+
const all = []
|
|
112
|
+
if (!specBase) return all
|
|
113
|
+
try {
|
|
114
|
+
const docsDir = join(specBase, 'docs')
|
|
115
|
+
if (!existsSync(docsDir)) return all
|
|
116
|
+
const seen = new Set()
|
|
117
|
+
for (const entry of readdirSync(docsDir, { withFileTypes: true })) {
|
|
118
|
+
if (!entry.isDirectory()) continue
|
|
119
|
+
for (const p of loadSpanRiskPatterns({ specBase, project: entry.name })) {
|
|
120
|
+
if (seen.has(p.pattern)) continue
|
|
121
|
+
seen.add(p.pattern)
|
|
122
|
+
all.push(p)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch { /* docs 目录不可读 → 空表(手工数据容错) */ }
|
|
126
|
+
return all
|
|
127
|
+
}
|
|
@@ -52,7 +52,7 @@ const CUSTOM_KINDS = new Set([
|
|
|
52
52
|
'task-card-fields', // task-NN.md 卡片字段存在性(validateBlueprintConsistency + validatePlanFeasibility)
|
|
53
53
|
'verify-conclusion-gate', // verify-result.md 结论 PASS/FAIL 门控
|
|
54
54
|
'integration-evidence', // verify-result.md 集成证据(change-risk-profile.checkIntegrationEvidence)
|
|
55
|
-
'change-risk-gate', // 变更风险分级门控(
|
|
55
|
+
'change-risk-gate', // 变更风险分级门控(resolveChangeRisk)
|
|
56
56
|
])
|
|
57
57
|
|
|
58
58
|
export function isCustomKind(kind) {
|
|
@@ -226,7 +226,7 @@ const VERIFY_RULES = [
|
|
|
226
226
|
failMessage: 'verify-result.md 结论为 FAIL — 验证未通过,不能标记 verify 完成;请修复后重新运行验证。⚠️ 重验成本预告:修复后 verify --done 时 CLI 会重新执行 local.yaml 的 commands.test 全量对账(同步执行,长套件可达数分钟、中途无输出属正常;可按 local.yaml test_strategy: module + modules 收窄命中模块),请在修复时一并评估测试范围',
|
|
227
227
|
noConclusionWarning: 'verify-result.md 未识别到结论章节(含 结论/Conclusion/Result/结果 的二级标题,后跟 PASS / PASS WITH NOTES / FAIL)',
|
|
228
228
|
},
|
|
229
|
-
// integration-evidence(custom,D10 事前契约):风险门控。判定算法
|
|
229
|
+
// integration-evidence(custom,D10 事前契约):风险门控。判定算法 resolveChangeRisk +
|
|
230
230
|
// checkIntegrationEvidence 留 change-risk-profile.js(需运行时读 design/plan 内容,引擎无法纯判定);
|
|
231
231
|
// 此处只作事前提示——renderStageContract 把 spec/hint 注入 verify step0,让 agent 写 verify-result.md
|
|
232
232
|
// 前就知道:若 design/plan 命中部署/启动或跨进程关键词,必须提供对应字面证据,否则完成时被门控阻断。
|