sillyspec 3.28.13 → 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/package.json +1 -1
- package/src/blast-surface.js +168 -0
- package/src/ceremony-tier.js +359 -0
- package/src/change-risk-profile.js +72 -199
- package/src/config-schema.js +21 -0
- package/src/decisions-io.js +32 -0
- package/src/design-facts.js +1 -0
- package/src/doctor-diagnostics.js +119 -1
- package/src/index.js +92 -5
- package/src/knowledge-stats.js +25 -0
- 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 +6 -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 +423 -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 +102 -81
- package/src/stage-review.js +26 -2
- package/src/stages/brainstorm.js +28 -15
- 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 +117 -10
- package/src/worktree.js +1 -1
- package/templates/prompts/taskcard-rules.md +1 -0
- package/templates/prompts/verify-probes.md +1 -1
|
@@ -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 命中部署/启动或跨进程关键词,必须提供对应字面证据,否则完成时被门控阻断。
|
package/src/stage-contract.js
CHANGED
|
@@ -9,7 +9,9 @@ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'fs'
|
|
|
9
9
|
import { join, basename } from 'path'
|
|
10
10
|
import { safeGit } from './git-helper.js'
|
|
11
11
|
import { nowWallClock } from './datetime.js'
|
|
12
|
-
import {
|
|
12
|
+
import { resolveChangeRisk, extractExplicitRiskLevel, checkIntegrationEvidence, VERIFICATION_NEEDS, RISK_LEVEL_CAUSES } from './change-risk-profile.js'
|
|
13
|
+
import { loadBlastDeclarationsAllProjects } from './blast-surface.js'
|
|
14
|
+
import { parseFileChangeListDetailed } from './change-list.js'
|
|
13
15
|
import { parseEvidenceSlots } from './verify-facts-schema.js'
|
|
14
16
|
import { IR_STRICT_SINCE } from './constants.js'
|
|
15
17
|
import { SCAN_REQUIRED_DOCS, AUXILIARY_STAGES } from './constants.js'
|
|
@@ -24,7 +26,6 @@ import { discoverModuleIndex } from './decision-distill.js'
|
|
|
24
26
|
import { appendKnowledgeHit } from './knowledge-hits.js'
|
|
25
27
|
// design.md 文件清单解析(2026-09-17-pass-cap-semantics task-02 事实③):change-list 是纯 fs
|
|
26
28
|
// 叶子模块(design-facts / scope-audit / verify-probes 等同款直连惯例),静态引入无环。
|
|
27
|
-
import { parseFileChangeListDetailed } from './change-list.js'
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* 校验结果
|
|
@@ -299,6 +300,21 @@ function validateScanOutputs(cwd, changeName, context = {}) {
|
|
|
299
300
|
/**
|
|
300
301
|
* brainstorm 完成校验:检查四件套规范文件是否生成
|
|
301
302
|
*/
|
|
303
|
+
/**
|
|
304
|
+
* 变更目录级声明面判级(2026-09-19-ceremony-pricing-five-cuts task-03 / D-008):
|
|
305
|
+
* design 文件变更清单(change-list 单一真相源解析,双形态标题)× blast 声明(多项目并集,
|
|
306
|
+
* 调用方无 project 语境)+ design frontmatter explicit(只压 tier 不豁免 evidence,D-009)。
|
|
307
|
+
* 词表散文判级(detectChangeRisk)退役后的 stage-contract 四消费点统一入口。
|
|
308
|
+
*/
|
|
309
|
+
function resolveChangeRiskForChangeDir(changeDir, specBase) {
|
|
310
|
+
const designPath = join(changeDir, 'design.md')
|
|
311
|
+
let designText = ''
|
|
312
|
+
try { designText = readFileSync(designPath, 'utf8') } catch { designText = '' }
|
|
313
|
+
const declaredFiles = parseFileChangeListDetailed(designPath, { keepSillyspecDocs: true }).map(e => e.path)
|
|
314
|
+
const blastDeclarations = loadBlastDeclarationsAllProjects({ specBase })
|
|
315
|
+
return resolveChangeRisk({ files: declaredFiles, blastDeclarations, explicitRiskLevel: designText ? extractExplicitRiskLevel(designText) : null })
|
|
316
|
+
}
|
|
317
|
+
|
|
302
318
|
function validateBrainstormOutputs(cwd, changeName, context = {}) {
|
|
303
319
|
const { specRoot } = context
|
|
304
320
|
const changesRoot = specRoot ? join(specRoot, 'changes') : join(cwd, '.sillyspec', 'changes')
|
|
@@ -376,23 +392,22 @@ function validateBrainstormOutputs(cwd, changeName, context = {}) {
|
|
|
376
392
|
}
|
|
377
393
|
}
|
|
378
394
|
|
|
379
|
-
// ── 风险判级提前提示(坑 risk-first-use-opaque,2026-08-24
|
|
380
|
-
// verify --done
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
//
|
|
395
|
+
// ── 风险判级提前提示(坑 risk-first-use-opaque,2026-08-24;2026-09-19-ceremony-pricing-five-cuts
|
|
396
|
+
// task-03 改声明面口径)── verify --done 的证据门既往只在流程末段暴露,首次使用者撞墙后
|
|
397
|
+
// 才知道出路。design --done 即按「design 文件清单 × 项目声明危险面」预判一次(advisory
|
|
398
|
+
// 不作定论):evidence:true 命中 → 透出证据要求 + 出路;explicit 已声明 → 透出「压档不豁免
|
|
399
|
+
// 证据」(D-009——旧显式短路连证据门一起免的洞已收口)。
|
|
384
400
|
{
|
|
385
|
-
const
|
|
386
|
-
if (
|
|
401
|
+
const riskPreview = resolveChangeRiskForChangeDir(changeDir, context.specRoot || specRoot || join(cwd, '.sillyspec'))
|
|
402
|
+
if (riskPreview.evidenceRequired && !riskPreview.explicit) {
|
|
387
403
|
warnings.push(
|
|
388
|
-
`[risk] 本次 design
|
|
389
|
-
|
|
404
|
+
`[risk] 本次 design 声明文件面命中项目声明危险面(${riskPreview.hitPrefixes.join('、')},evidence:true)——verify --done 将强制真实集成证据门控。` +
|
|
405
|
+
`若认为不该要求集成证据:改 _module-map.yaml 顶层 blast 段(git 可见);design frontmatter risk_level 只压仪式档、不豁免证据要求(D-009)。`
|
|
390
406
|
)
|
|
391
407
|
}
|
|
392
|
-
if (
|
|
408
|
+
if (riskPreview.evidenceRequired && riskPreview.explicit) {
|
|
393
409
|
warnings.push(
|
|
394
|
-
`[risk]
|
|
395
|
-
`若实际涉及对应改动,请在 design.md frontmatter 声明高危级(抑制可审计,防静默降级逃证据门控)。`
|
|
410
|
+
`[risk] 声明文件面命中 evidence:true 危险面(${riskPreview.hitPrefixes.join('、')}),且 frontmatter 已显式 risk_level——注意:显式声明压仪式档(tier=${riskPreview.tier}),但集成证据要求不被豁免(D-009)。`
|
|
396
411
|
)
|
|
397
412
|
}
|
|
398
413
|
}
|
|
@@ -655,40 +670,26 @@ function validateVerifyOutputs(cwd, changeName, context = {}) {
|
|
|
655
670
|
}
|
|
656
671
|
|
|
657
672
|
// ── P0: Change Risk Gate — 核心功能缺少真实集成验证时 FAIL ──
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
})
|
|
673
|
+
// 2026-09-19-ceremony-pricing-five-cuts task-03:声明面口径——design 文件清单 × blast 声明,
|
|
674
|
+
// evidenceRequired 驱动证据门(D-009:explicit 只压仪式档不豁免证据)。
|
|
675
|
+
const changeRiskProfile = resolveChangeRiskForChangeDir(changeDir, context.specRoot || specRoot || join(cwd, '.sillyspec'))
|
|
662
676
|
const conclusion = conclusionStr // 槽优先解析结果复用(此前同输入重复扫两遍)
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
// frontmatter risk_level 仍是双向权威覆盖通道。
|
|
667
|
-
if ((changeRiskProfile.suppressedTriggers || []).length > 0) {
|
|
668
|
-
warnings.push(
|
|
669
|
-
`[${changeRiskProfile.level}] 风险判级:关键词命中 ${changeRiskProfile.suppressedTriggers.join(', ')} 已被同句否定语境(不/未/无/避免…)抑制,未参与判级。` +
|
|
670
|
-
`若本变更实际涉及跨进程/状态机/启动入口改动,请在 design.md frontmatter 加 risk_level: integration-critical(或 deployment-critical)显式声明或提供真实集成证据——抑制可审计,不许用来静默降级。`
|
|
671
|
-
)
|
|
672
|
-
}
|
|
673
|
-
if (['integration-critical', 'deployment-critical'].includes(changeRiskProfile.level)) {
|
|
674
|
-
// 关键词误伤早期引导(坑2,FR-02):命中高危关键词且无 frontmatter risk_level 时,
|
|
675
|
-
// 无条件透出 frontmatter 覆盖指引(不依赖 conclusion / evidence),让 agent 早期即可
|
|
676
|
-
// 判断是否属误判并显式覆盖,而非撞到 evidence gate 末尾才发现出路③。含否定语境的
|
|
677
|
-
// 命中已在 detectChangeRisk 内被同句否定抑制(见 change-risk-profile.js NEGATION_CUES),
|
|
678
|
-
// 此处命中的都是无否定语境的真命中;仍误伤的走 frontmatter 显式覆盖通道。
|
|
677
|
+
if (changeRiskProfile.evidenceRequired) {
|
|
678
|
+
// 声明面命中披露 + 出路(旧关键词误伤引导随词表退役改写):命中 evidence:true 声明路径
|
|
679
|
+
// → 证据门强制;出路=改 map(git 可见)或提供真实证据;risk_level 压档不豁免证据。
|
|
679
680
|
if (!changeRiskProfile.explicit) {
|
|
680
681
|
warnings.push(
|
|
681
|
-
`[${changeRiskProfile.level}]
|
|
682
|
-
|
|
682
|
+
`[${changeRiskProfile.level}] 本次变更声明文件面命中项目声明危险面(evidence:true:${changeRiskProfile.hitPrefixes.join(', ')})。` +
|
|
683
|
+
`若认为不该要求集成证据:改 _module-map.yaml 顶层 blast 段(git 可见);design frontmatter risk_level 只压仪式档、不豁免证据要求(D-009)。`
|
|
683
684
|
)
|
|
684
685
|
}
|
|
685
|
-
// ── 显式 risk_level
|
|
686
|
-
// ① explicit
|
|
687
|
-
//
|
|
688
|
-
// ② explicit
|
|
689
|
-
//
|
|
690
|
-
//
|
|
691
|
-
// ③
|
|
686
|
+
// ── 显式 risk_level 分层(D-002@v1 延续 + D-009 修订,2026-09-19-ceremony-pricing-five-cuts)──
|
|
687
|
+
// ① explicit 与否不豁免证据(D-009):evidence:true 命中即进本块——explicit 低档声明不再
|
|
688
|
+
// 是证据逃生门(旧「豁免级免证据」洞已收口,出路=改 map);
|
|
689
|
+
// ② explicit + PASS WITH NOTES:必须携带结构化 handover(facts.handover 有效行;blocking 级
|
|
690
|
+
// 计入封顶口径同 validatePassEligibility 条件②)或齐全集成证据,二选一——无 handover 即挂
|
|
691
|
+
// 证据门(checkIntegrationEvidence 不过 → error);
|
|
692
|
+
// ③ 非显式(声明面判级)判定式一字不动(防 PASS WITH NOTES 绕证据门控)。
|
|
692
693
|
const notesExplicitCritical = conclusion === 'PASS WITH NOTES' && changeRiskProfile.explicit
|
|
693
694
|
const handoverRows = notesExplicitCritical ? countFactsHandoverItems(changeDir) : 0
|
|
694
695
|
const requiresEvidence = conclusion === 'PASS'
|
|
@@ -764,10 +765,11 @@ function validateVerifyOutputs(cwd, changeName, context = {}) {
|
|
|
764
765
|
` 缺失项(需在 verify-result.md 如实提供并满足):${evidenceCheck.errors.join('; ')}\n` +
|
|
765
766
|
` 每项要提供什么:${needs}\n` +
|
|
766
767
|
` 风险判级原因:${cause}\n` +
|
|
767
|
-
`
|
|
768
|
+
` 命中声明危险面(evidence:true):${changeRiskProfile.hitPrefixes.join(', ') || '(无)'}\n` +
|
|
768
769
|
` 出路:① 补全上述缺失的真实集成证据(真实启动 daemon/backend、集成测试、运行日志)后保持 PASS;` +
|
|
769
770
|
`或 ② 如实改结论 FAIL(承认端到端未验,留待部署后补);` +
|
|
770
|
-
`或 ③
|
|
771
|
+
`或 ③ 若认为该路径不该要求集成证据,改 _module-map.yaml 顶层 blast 段的 evidence 声明(git 可见)——` +
|
|
772
|
+
`design frontmatter risk_level 只压仪式档、不豁免证据要求(D-009)。` +
|
|
771
773
|
`仅改结论文案/措辞蹭字面关键词会被对账。`
|
|
772
774
|
)
|
|
773
775
|
}
|
|
@@ -782,11 +784,11 @@ function validateVerifyOutputs(cwd, changeName, context = {}) {
|
|
|
782
784
|
// ============ 探针 7 验收×测试覆盖矩阵门禁(2026-09-14-acceptance-test-matrix FR-02) ============
|
|
783
785
|
// 段格式与 src/verify-probes.js renderProbe7Lines 骨架渲染字面同源(禁第二套解析文法):
|
|
784
786
|
// 段标题「#### 探针 7:验收×测试覆盖矩阵」(全/半角冒号皆认);每 task 前 **task-NN** 锚行;
|
|
785
|
-
// 五列表 | acceptance 条目 | 归属测试文件 | 关键词命中 | 判定 | 证据 |;判定槽
|
|
787
|
+
// 五列表 | acceptance 条目 | 归属测试文件 | 关键词命中 | 判定 | 证据 |;判定槽 <待填:五选一>、
|
|
786
788
|
// 证据槽 <TODO>;列表防御行(卡无 acceptance…)与不适用行无槽不计。
|
|
787
789
|
|
|
788
|
-
/**
|
|
789
|
-
const MATRIX_VERDICT_WHITELIST = new Set(['covered', 'partial', 'uncovered', 'non-testable'])
|
|
790
|
+
/** 判定列五枚举白名单(骨架口径注记字面同源;covered-service=service 层承接,2026-09-19-api-matrix-service-coverage) */
|
|
791
|
+
const MATRIX_VERDICT_WHITELIST = new Set(['covered', 'covered-service', 'partial', 'uncovered', 'non-testable'])
|
|
790
792
|
/** 证据列未填占位(骨架字面同源) */
|
|
791
793
|
const MATRIX_EVIDENCE_TODO = '<TODO>'
|
|
792
794
|
|
|
@@ -823,11 +825,12 @@ function matrixEvidenceHasAnchor(evidence) {
|
|
|
823
825
|
}
|
|
824
826
|
|
|
825
827
|
/**
|
|
826
|
-
* 行级证据口径:covered/partial 须非 TODO 且含测试锚点;non-testable 须非 TODO
|
|
827
|
-
*
|
|
828
|
+
* 行级证据口径:covered/covered-service/partial 须非 TODO 且含测试锚点;non-testable 须非 TODO
|
|
829
|
+
* 且非空(一句话理由);uncovered 无证据要求;判定未填(不在需证据枚举内)的行只计 unfilled 不重复
|
|
830
|
+
* 计证据(covered-service 与 covered 同口径:证据即测试锚点三形态,2026-09-19-api-matrix-service-coverage)。
|
|
828
831
|
*/
|
|
829
832
|
function matrixEvidenceMissing(verdict, evidence) {
|
|
830
|
-
if (verdict !== 'covered' && verdict !== 'partial' && verdict !== 'non-testable') return false
|
|
833
|
+
if (verdict !== 'covered' && verdict !== 'covered-service' && verdict !== 'partial' && verdict !== 'non-testable') return false
|
|
831
834
|
const e = String(evidence || '').trim()
|
|
832
835
|
if (e === '' || e === MATRIX_EVIDENCE_TODO || e.startsWith('<待填')) return true
|
|
833
836
|
if (verdict === 'non-testable') return false // 非 TODO 且非空即合规(理由一句话)
|
|
@@ -925,15 +928,15 @@ function validateAcceptanceMatrix(cwd, changeName, context = {}) {
|
|
|
925
928
|
if (matrix.unfilled > 0) {
|
|
926
929
|
const list = matrix.rows.filter(r => r.unfilled).map(rowLabel).join(';')
|
|
927
930
|
errors.push(
|
|
928
|
-
`探针 7 验收×测试覆盖矩阵有 ${matrix.unfilled}
|
|
929
|
-
`修复:编辑 verify-result.md 探针 7 段,把
|
|
931
|
+
`探针 7 验收×测试覆盖矩阵有 ${matrix.unfilled} 行判定未填(五选一 covered/covered-service/partial/uncovered/non-testable):${list}。` +
|
|
932
|
+
`修复:编辑 verify-result.md 探针 7 段,把 <待填:五选一> 替换为判定值。`
|
|
930
933
|
)
|
|
931
934
|
}
|
|
932
935
|
if (matrix.missingEvidence > 0) {
|
|
933
936
|
const list = matrix.rows.filter(r => r.evidenceMissing).map(rowLabel).join(';')
|
|
934
937
|
errors.push(
|
|
935
938
|
`探针 7 验收×测试覆盖矩阵有 ${matrix.missingEvidence} 行证据缺失` +
|
|
936
|
-
`(covered/partial 证据须含测试锚点:\`.test.\` 文件名 / file:line / 反引号包裹的测试名;non-testable 证据须写一句理由):${list}。` +
|
|
939
|
+
`(covered/covered-service/partial 证据须含测试锚点:\`.test.\` 文件名 / file:line / 反引号包裹的测试名;non-testable 证据须写一句理由):${list}。` +
|
|
937
940
|
`修复:在证据列补测试锚点(如 \`test/foo.test.mjs\` 或 \`src/x.js:42\`)或 non-testable 理由。`
|
|
938
941
|
)
|
|
939
942
|
}
|
|
@@ -985,11 +988,13 @@ function validateAcceptanceMatrix(cwd, changeName, context = {}) {
|
|
|
985
988
|
// 与探针 7(验收×测试承接面)并排互补的独立对账门(design §4,D-009 非目标 / R-07 口径注记
|
|
986
989
|
// 互指):探针 7 管「每条 acceptance 由哪些测试承接」,本矩阵管「design 接口段每个端点由哪些
|
|
987
990
|
// 验证用例/冒烟步骤覆盖」——fail-closed 记账,机械封住「接口层零派生」的 P1 缺陷面:
|
|
988
|
-
// - covered 记账:分子=判定 covered
|
|
989
|
-
//
|
|
991
|
+
// - covered 记账:分子=判定 covered+covered-service 的端点行(covered-service 计分子——
|
|
992
|
+
// service 层承接是已完成、覆盖层不同;另有 advisory 单独计数承接面);有效分母=N−non-testable
|
|
993
|
+
// 行数(N=解析端点数或声明数,D-005);分子<有效分母或解析面有未覆盖端点 → error 逐条列缺覆盖端点
|
|
990
994
|
// - 锚点五形态(design §4 Grill #10):design接口表#<METHOD /path> 解析级(须命中
|
|
991
995
|
// facts.apiFace 解析产出集,防空指);权限矩阵[...]/契约表@.../DDL@.../载荷@... 形态级
|
|
992
|
-
// 存在即认;covered/partial 行缺锚点 → error(missingEvidence 口径同 probe7
|
|
996
|
+
// 存在即认;covered/partial 行缺锚点 → error(missingEvidence 口径同 probe7);
|
|
997
|
+
// covered-service 行只须测试锚点三形态(matrixEvidenceHasAnchor 同源,2026-09-19-api-matrix-service-coverage)
|
|
993
998
|
// - 移交联动(probe7 条件④同款形态):partial/uncovered 端点行>0 且 facts.handover 零
|
|
994
999
|
// 有效行 → error;blocking 是否封顶归 validatePassEligibility 条件②(④管有去向/②管去向级)
|
|
995
1000
|
// - 探索行(uncovered+[探索] 标记)与消费端子行(两空格缩进 ↳ 前缀)不进分母分子
|
|
@@ -1093,7 +1098,7 @@ function apiEvidenceHasAnchorForm(evidence) {
|
|
|
1093
1098
|
* facts?: object|null, // verify-facts.json(handover 联动用,缺 null)
|
|
1094
1099
|
* factsExpected?: boolean, // false(存量未跑管线)→ 兼容 ok 不误伤
|
|
1095
1100
|
* strict?: boolean, // isIrStrictVerifyChange(段缺失分层用)
|
|
1096
|
-
* riskLevel?: string|null, //
|
|
1101
|
+
* riskLevel?: string|null, // resolveChangeRisk().level(声明面判级兼容字段)
|
|
1097
1102
|
* changeName?: string,
|
|
1098
1103
|
* permSectionText?: string, // design.md 权限段(段头含 权限/角色 的段合并文本,壳内提取)
|
|
1099
1104
|
* designText?: string, // design.md 全文(写端点豁免行级检测用)
|
|
@@ -1172,13 +1177,22 @@ export function judgeApiCoverageMatrix(args) {
|
|
|
1172
1177
|
const unfilledRows = judgedRows.filter(r => r.unfilled)
|
|
1173
1178
|
if (unfilledRows.length > 0) {
|
|
1174
1179
|
errors.push(
|
|
1175
|
-
`接口验证覆盖矩阵有 ${unfilledRows.length}
|
|
1176
|
-
`修复:编辑 verify-result.md 接口验证覆盖矩阵段,把
|
|
1180
|
+
`接口验证覆盖矩阵有 ${unfilledRows.length} 行判定未填(五选一 covered/covered-service/partial/uncovered/non-testable):${unfilledRows.map(rowLabel).join(';')}。` +
|
|
1181
|
+
`修复:编辑 verify-result.md 接口验证覆盖矩阵段,把 <待填:五选一> 替换为判定值。`
|
|
1177
1182
|
)
|
|
1178
1183
|
}
|
|
1179
1184
|
const parseSet = new Set(endpoints.map(e => `${e.method} ${e.path}`))
|
|
1180
1185
|
const anchorViolations = []
|
|
1181
1186
|
for (const r of judgedRows) {
|
|
1187
|
+
if (r.verdict === 'covered-service') {
|
|
1188
|
+
// covered-service(service 层承接):证据即测试本身,只校验测试锚点三形态
|
|
1189
|
+
// (matrixEvidenceHasAnchor 同源口径,禁第二套解析文法);不做 design接口表# 解析级
|
|
1190
|
+
// 核对与五形态校验(不要求 design 侧锚点)——校验后 continue,不走 covered/partial 分支。
|
|
1191
|
+
if (!matrixEvidenceHasAnchor(r.evidence)) {
|
|
1192
|
+
anchorViolations.push(`${rowLabel(r)}:covered-service 证据缺测试锚点(\`.test.\` 文件名 / file:line / 反引号包裹的测试名)`)
|
|
1193
|
+
}
|
|
1194
|
+
continue
|
|
1195
|
+
}
|
|
1182
1196
|
if (r.verdict !== 'covered' && r.verdict !== 'partial') continue
|
|
1183
1197
|
const e = String(r.evidence || '')
|
|
1184
1198
|
// design接口表# 锚点解析级核对(防空指):提取的 METHOD /path 须全部命中解析产出集
|
|
@@ -1192,7 +1206,7 @@ export function judgeApiCoverageMatrix(args) {
|
|
|
1192
1206
|
if (anchorViolations.length > 0) {
|
|
1193
1207
|
errors.push(
|
|
1194
1208
|
`接口验证覆盖矩阵有 ${anchorViolations.length} 项证据锚点缺失/空指` +
|
|
1195
|
-
`(covered/partial 证据须含锚点五形态之一:design接口表#METHOD /path(须命中 design 接口段解析面)/ 权限矩阵[角色×动作] / 契约表@行标识 / DDL@列名 /
|
|
1209
|
+
`(covered/partial 证据须含锚点五形态之一:design接口表#METHOD /path(须命中 design 接口段解析面)/ 权限矩阵[角色×动作] / 契约表@行标识 / DDL@列名 / 载荷@构造点路径;covered-service 证据须含测试锚点三形态之一:\`.test.\` 文件名 / file:line / 反引号包裹的测试名):${anchorViolations.join(';')}。` +
|
|
1196
1210
|
`修复:在证据列补真实锚点(design接口表# 锚点须与 design.md 接口段端点一致,防编造端点)。`
|
|
1197
1211
|
)
|
|
1198
1212
|
}
|
|
@@ -1205,17 +1219,19 @@ export function judgeApiCoverageMatrix(args) {
|
|
|
1205
1219
|
)
|
|
1206
1220
|
}
|
|
1207
1221
|
|
|
1208
|
-
// ── covered 记账(D-005/D-008):分子=covered
|
|
1209
|
-
//
|
|
1210
|
-
//
|
|
1211
|
-
//
|
|
1212
|
-
//
|
|
1222
|
+
// ── covered 记账(D-005/D-008):分子=covered+covered-service 端点行(covered-service 并入
|
|
1223
|
+
// 分子满足覆盖等式,service 层承接是「已完成、覆盖层不同」非「未完成待移交」);有效分母=
|
|
1224
|
+
// N−non-testable 行数;N=解析端点数,解析零行按声明数(declaredRow 优先——矩阵实态,
|
|
1225
|
+
// facts.declared 兜底)。缺覆盖清单解析集驱动:解析面端点 − covered/covered-service 行命中
|
|
1226
|
+
// − non-testable 行命中(partial/uncovered/unfilled 端点行不在命中集即自然进清单;探索行
|
|
1227
|
+
// 不进分母分子——其 uncovered 判定使之天然不计分子,且不计入 non-testable 扣减)。──
|
|
1213
1228
|
if (!zeroFace) {
|
|
1214
1229
|
const N = endpoints.length > 0 ? endpoints.length : (declaredFromRow != null ? declaredFromRow : (declared != null ? declared : 0))
|
|
1215
1230
|
const nonTestableCount = rows.filter(r => r.verdict === 'non-testable').length
|
|
1216
1231
|
const validDenominator = Math.max(0, N - nonTestableCount)
|
|
1217
|
-
const
|
|
1218
|
-
const
|
|
1232
|
+
const serviceCoveredCount = rows.filter(r => r.verdict === 'covered-service').length
|
|
1233
|
+
const coveredCount = rows.filter(r => r.verdict === 'covered' || r.verdict === 'covered-service').length
|
|
1234
|
+
const coveredSet = new Set(rows.filter(r => (r.verdict === 'covered' || r.verdict === 'covered-service') && r.method && r.path).map(r => `${r.method} ${r.path}`))
|
|
1219
1235
|
const nonTestSet = new Set(rows.filter(r => r.verdict === 'non-testable' && r.method && r.path).map(r => `${r.method} ${r.path}`))
|
|
1220
1236
|
const missingEndpoints = endpoints.filter(e => {
|
|
1221
1237
|
const k = `${e.method} ${e.path}`
|
|
@@ -1224,10 +1240,17 @@ export function judgeApiCoverageMatrix(args) {
|
|
|
1224
1240
|
if (coveredCount < validDenominator || missingEndpoints.length > 0) {
|
|
1225
1241
|
const list = missingEndpoints.length > 0
|
|
1226
1242
|
? missingEndpoints.map(e => `${e.method} ${e.path}`).join(';')
|
|
1227
|
-
: `声明 ${N} 端点而 covered 端点行仅 ${coveredCount}(解析零行降级,无法逐条列端点——按声明行拆出每端点行填判定)`
|
|
1243
|
+
: `声明 ${N} 端点而 covered/covered-service 端点行仅 ${coveredCount}(解析零行降级,无法逐条列端点——按声明行拆出每端点行填判定)`
|
|
1228
1244
|
errors.push(
|
|
1229
|
-
`接口验证覆盖矩阵覆盖不足:有效分母 ${validDenominator}(N=${N} − non-testable ${nonTestableCount}),covered 分子 ${coveredCount}——缺覆盖端点:${list}。` +
|
|
1230
|
-
`修复:补验证用例/冒烟步骤后把端点行改 covered
|
|
1245
|
+
`接口验证覆盖矩阵覆盖不足:有效分母 ${validDenominator}(N=${N} − non-testable ${nonTestableCount}),covered+covered-service 分子 ${coveredCount}——缺覆盖端点:${list}。` +
|
|
1246
|
+
`修复:补验证用例/冒烟步骤后把端点行改 covered 并填五形态锚点;端点行为由 service 层测试锁定的改 covered-service 并填测试锚点(\`.test.\` / file:line / 反引号);不适用端点改 non-testable 并写一句理由;确未覆盖的走「## 移交项(结构化)」承载并保持 partial/uncovered(FR-04)。`
|
|
1247
|
+
)
|
|
1248
|
+
}
|
|
1249
|
+
// covered-service 承接 advisory(D-001/R-01 第一版,只进 warnings 不进 errors):端点级与
|
|
1250
|
+
// 间接覆盖在矩阵统计上可区分——先 advisory 观察滥用面,不设占比上限不阻断。
|
|
1251
|
+
if (serviceCoveredCount > 0) {
|
|
1252
|
+
warnings.push(
|
|
1253
|
+
`[advisory] ${serviceCoveredCount} 端点由 service 层测试承接(非端点级)——端点级与间接覆盖在矩阵统计上可区分,承接占比先 advisory 观察(D-001/R-01,不阻断)`
|
|
1231
1254
|
)
|
|
1232
1255
|
}
|
|
1233
1256
|
// 声明与解析并存以解析为准并注记(骨架 :1573 漂移信号同款口径,数据面 facts.apiFace)
|
|
@@ -1340,7 +1363,7 @@ function extractPermissionMatrixText(designMd) {
|
|
|
1340
1363
|
* - factsExpected=false(存量未跑管线,resolveFactsExpected 同源口径)→ no-op 零行为
|
|
1341
1364
|
* - verify-facts.json(apiFace/consumerHints/handover,缺失容 null——fail-closed 归纯函数)
|
|
1342
1365
|
* - 矩阵段 MD 槽解析(X-05 防篡改锚点:锚定当前文档实态)
|
|
1343
|
-
* -
|
|
1366
|
+
* - resolveChangeRisk 声明面判级(design 文件清单 × blast 声明 + explicit,task-03)
|
|
1344
1367
|
* - design.md 权限矩阵段提取(fs 只读,壳非纯函数)
|
|
1345
1368
|
*/
|
|
1346
1369
|
export function validateApiCoverageMatrix(cwd, changeName, context = {}) {
|
|
@@ -1358,10 +1381,9 @@ export function validateApiCoverageMatrix(cwd, changeName, context = {}) {
|
|
|
1358
1381
|
|
|
1359
1382
|
const facts = readFactsForEligibility(changeDir)
|
|
1360
1383
|
const designText = readIfExists(join(changeDir, 'design.md'))
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
})
|
|
1384
|
+
// 2026-09-19-ceremony-pricing-five-cuts task-03:声明面判级(design 文件清单 × blast 声明 +
|
|
1385
|
+
// explicit;level 为五级词兼容字段——evidenceRequired → integration-critical)
|
|
1386
|
+
const riskProfile = resolveChangeRiskForChangeDir(changeDir, context.specRoot || specRoot || join(cwd, '.sillyspec'))
|
|
1365
1387
|
return judgeApiCoverageMatrix({
|
|
1366
1388
|
matrix: extractApiCoverageMatrixSlots(readFileSync(verifyResultPath, 'utf8')),
|
|
1367
1389
|
apiFace: facts && facts.apiFace && typeof facts.apiFace === 'object' ? facts.apiFace : null,
|
|
@@ -1618,7 +1640,7 @@ function collectDbScriptCandidates({ cwd, changeName, specRoot, changeDir }) {
|
|
|
1618
1640
|
* PASS 封顶 validator 注册壳(与 validateAcceptanceMatrix 同三参签名同构,D-010:注册进
|
|
1619
1641
|
* contracts.verify.validators 即覆盖 gates / machine-interface 等全部 runValidators 调用方,
|
|
1620
1642
|
* gates.js 零改动)。壳内取数组装:verify-result.md 结论(槽优先,legacy 回退)、
|
|
1621
|
-
* verify-facts.json(缺失容 null)、
|
|
1643
|
+
* verify-facts.json(缺失容 null)、resolveChangeRisk 声明面判级、事实③文件集;判定调
|
|
1622
1644
|
* evaluatePassEligibility 纯函数。
|
|
1623
1645
|
*/
|
|
1624
1646
|
export function validatePassEligibility(cwd, changeName, context = {}) {
|
|
@@ -1638,10 +1660,9 @@ export function validatePassEligibility(cwd, changeName, context = {}) {
|
|
|
1638
1660
|
conclusion,
|
|
1639
1661
|
facts,
|
|
1640
1662
|
factsExpected: isIrStrictVerifyChange(changeDir) || facts !== null,
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
}),
|
|
1663
|
+
// 2026-09-19-ceremony-pricing-five-cuts task-03:声明面判级(level 兼容字段驱动 evaluatePassEligibility
|
|
1664
|
+
// 的 critical 判定——evidenceRequired → integration-critical,D-009 证据不被 explicit 豁免)
|
|
1665
|
+
changeRiskProfile: resolveChangeRiskForChangeDir(changeDir, context.specRoot || specRoot || join(cwd, '.sillyspec')),
|
|
1645
1666
|
changeName,
|
|
1646
1667
|
dbScriptCandidates: collectDbScriptCandidates({ cwd, changeName, specRoot, changeDir }),
|
|
1647
1668
|
})
|