sillyspec 3.29.3 → 3.29.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/asset-audit-2026-09-20/README.md +42 -0
- package/asset-audit-2026-09-20/asset-audit.cjs +253 -0
- package/asset-audit-2026-09-20/extract-candidates.cjs +37 -0
- package/asset-audit-2026-09-20/platform-archive.csv +294 -0
- package/asset-audit-2026-09-20/platform-fr.csv +12 -0
- package/asset-audit-2026-09-20/platform-gotchas.csv +6 -0
- package/asset-audit-2026-09-20/platform-knownissues.csv +23 -0
- package/asset-audit-2026-09-20/platform-modules.csv +222 -0
- package/asset-audit-2026-09-20/platform-quicklog.csv +1076 -0
- package/asset-audit-2026-09-20/platform-troubleshooting-candidates.csv +88 -0
- package/asset-audit-2026-09-20/platform-troubleshooting.csv +1 -0
- package/asset-audit-2026-09-20/sillyspec-archive.csv +108 -0
- package/asset-audit-2026-09-20/sillyspec-fr.csv +51 -0
- package/asset-audit-2026-09-20/sillyspec-gotchas.csv +4 -0
- package/asset-audit-2026-09-20/sillyspec-knownissues.csv +34 -0
- package/asset-audit-2026-09-20/sillyspec-modules.csv +30 -0
- package/asset-audit-2026-09-20/sillyspec-quicklog.csv +460 -0
- package/asset-audit-2026-09-20/sillyspec-troubleshooting.csv +69 -0
- package/asset-audit-2026-09-20/summary.json +241 -0
- package/assets/command-cards/run-archive.md +26 -0
- package/assets/command-cards/run-brainstorm.md +33 -0
- package/assets/command-cards/run-execute.md +29 -0
- package/assets/command-cards/run-plan.md +27 -0
- package/assets/command-cards/run-quick.md +34 -0
- package/assets/command-cards/run-verify.md +29 -0
- package/assets/command-cards/status.md +27 -0
- package/package.json +1 -1
- package/round4/account.mjs +85 -0
- package/round4/brief-autocompact.md +34 -0
- package/round4/brief-session-replay.md +50 -0
- package/round4/deep-read-synthesis.md +38 -0
- package/round4/extract-full.mjs +38 -0
- package/round4/extract-timeline.mjs +43 -0
- package/round4/find-sessions.mjs +13 -0
- package/round4/full-r4-l.md +4707 -0
- package/round4/full-r4-o-l.md +4300 -0
- package/round4/full-r4-os.md +1681 -0
- package/round4/full-r4-sf.md +4255 -0
- package/round4/full-r4-sq.md +1734 -0
- package/round4/gate-audit-relay-receipt.md +30 -0
- package/round4/gate-value-audit.md +52 -0
- package/round4/prereg.md +67 -0
- package/round4/prompt-R4-L.md +63 -0
- package/round4/prompt-R4-O-L.md +59 -0
- package/round4/prompt-R4-O-S.md +56 -0
- package/round4/prompt-R4-S-F.md +60 -0
- package/round4/prompt-R4-S-Q.md +66 -0
- package/round4/r4-os-timeline.txt +297 -0
- package/round4/r4-sf-timeline.txt +738 -0
- package/round4/r4-sq-timeline.txt +322 -0
- package/round4/r4l-timeline.txt +793 -0
- package/round4/r4ol-timeline.txt +729 -0
- package/round4/r4sf-timeline.txt +852 -0
- package/round4/result-S.md +171 -0
- package/round4/result-final.md +46 -0
- package/round4/result-quality.md +63 -0
- package/src/command-cards.js +73 -0
- package/src/contract-matrix.js +26 -5
- package/src/cross-repo-reconcile.js +232 -46
- package/src/diagnostic-codes.js +2 -1
- package/src/fr-index.js +58 -2
- package/src/index.js +35 -8
- package/src/init.js +25 -8
- package/src/machine-interface.js +88 -3
- package/src/module-impact.js +51 -22
- package/src/run/bg-sync.js +231 -0
- package/src/run/command.js +4 -0
- package/src/run/complete-handlers.js +244 -6
- package/src/run/complete.js +6 -3
- package/src/run/gate-snapshot.js +10 -4
- package/src/run/gates.js +124 -42
- package/src/run/prompt.js +1 -1
- package/src/run/quick-audit.js +28 -2
- package/src/run/shared.js +35 -1
- package/src/scope-audit.js +303 -20
- package/src/stages/execute.js +2 -2
- package/src/stages/plan-postcheck.js +33 -19
- package/src/stages/quick.js +2 -0
- package/src/sync.js +12 -0
- package/src/taskcard-frontmatter.js +57 -0
- package/src/verify-postcheck.js +68 -6
- package/src/verify-probes.js +45 -23
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* taskcard-frontmatter.js — task 卡 frontmatter 单一解析源(2026-09-20-taskcard-yaml-hardgate)
|
|
3
|
+
*
|
|
4
|
+
* 背景:坏 YAML 曾被三个消费点各自静默吞——plan-postcheck.js parseTaskContracts 的 catch 返回
|
|
5
|
+
* 空对象冒充「无契约字段」→ 契约门禁空真过门;verify-probes.js parseTaskAcceptance 的 catch
|
|
6
|
+
* 返回空数组 → 探针 7 假防御文案;且两侧 frontmatter 提取口径不一致(一侧 \r 容错一侧不容)。
|
|
7
|
+
* 本模块把「提取 + jsYaml 解析 + 错误定位(文件行:列)」收敛为一处,plan 与 verify 两侧共同消费。
|
|
8
|
+
*
|
|
9
|
+
* 防环铁律:本模块除 js-yaml 外零依赖(不 import 仓内任何模块)——plan-postcheck 与
|
|
10
|
+
* worktree-apply 存在既有依赖边(knowledge patterns),共享逻辑放新模块两侧 import。
|
|
11
|
+
*/
|
|
12
|
+
import jsYaml from 'js-yaml'
|
|
13
|
+
|
|
14
|
+
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 提取 task 卡 frontmatter。界定口径与 verify-probes.js parseTaskAcceptance 原实现同款
|
|
18
|
+
* (首行 --- 起、闭合 --- 行止,\r\n 容错——Windows 编辑器文本模式写卡不炸提取)。
|
|
19
|
+
* @param {string} content task 卡全文
|
|
20
|
+
* @returns {{ has: boolean, yamlText: string|null, yamlStartLine: number }}
|
|
21
|
+
* yamlStartLine = YAML 内容在文件中的起始行号(1 基):首行是 ---,YAML 自第 2 行起,恒为 2
|
|
22
|
+
*/
|
|
23
|
+
export function splitFrontmatter(content) {
|
|
24
|
+
const m = String(content ?? '').match(FRONTMATTER_RE)
|
|
25
|
+
if (!m) return { has: false, yamlText: null, yamlStartLine: 2 }
|
|
26
|
+
return { has: true, yamlText: m[1], yamlStartLine: 2 }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 解析 task 卡 frontmatter(单一真相源)。
|
|
31
|
+
* @param {string} content task 卡全文
|
|
32
|
+
* @returns {{ ok: boolean, hasFrontmatter: boolean, fm: object|null,
|
|
33
|
+
* error: { message: string, line: number, column: number }|null }}
|
|
34
|
+
* - 无 frontmatter:{ ok: true, hasFrontmatter: false, fm: null, error: null }——不是错误,
|
|
35
|
+
* 「无 frontmatter」由各消费方按自身语义处理(plan-postcheck 结构检查另有拦截)
|
|
36
|
+
* - jsYaml 抛错:{ ok: false, hasFrontmatter: true, fm: null, error: {...} }。
|
|
37
|
+
* error.line = e.mark.line + yamlStartLine:js-yaml mark.line 是 YAML 文本内 0 基行号,
|
|
38
|
+
* +2 后即文件 1 基行号(首行 ---,YAML 第 1 行 = 文件第 2 行);mark 缺席回退 1。
|
|
39
|
+
* error.column = e.mark.column + 1(0 基 → 1 基;mark 缺席回退 1)。
|
|
40
|
+
* error.message 取异常首行(js-yaml 原始 message 含多行上下文,单行化便于门禁文案)。
|
|
41
|
+
* - 合法:{ ok: true, hasFrontmatter: true, fm, error: null }(空文档回退 {})
|
|
42
|
+
*/
|
|
43
|
+
export function parseTaskFrontmatter(content) {
|
|
44
|
+
const { has, yamlText, yamlStartLine } = splitFrontmatter(content)
|
|
45
|
+
if (!has) return { ok: true, hasFrontmatter: false, fm: null, error: null }
|
|
46
|
+
let fm
|
|
47
|
+
try {
|
|
48
|
+
fm = jsYaml.load(yamlText) || {}
|
|
49
|
+
} catch (e) {
|
|
50
|
+
const mark = e && e.mark
|
|
51
|
+
const line = mark && typeof mark.line === 'number' ? mark.line + yamlStartLine : 1
|
|
52
|
+
const column = mark && typeof mark.column === 'number' ? mark.column + 1 : 1
|
|
53
|
+
const message = String((e && e.message) || e).split('\n')[0]
|
|
54
|
+
return { ok: false, hasFrontmatter: true, fm: null, error: { message, line, column } }
|
|
55
|
+
}
|
|
56
|
+
return { ok: true, hasFrontmatter: true, fm, error: null }
|
|
57
|
+
}
|
package/src/verify-postcheck.js
CHANGED
|
@@ -135,6 +135,44 @@ function extractLintCommand(yamlText) {
|
|
|
135
135
|
return null
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* lint 失败输出中提及的文件路径提取(lint 归属鉴定·R4-S-F 实证:verify 侧 lint 硬门 7 轮
|
|
140
|
+
* 全是 HEAD 存量债——快照含基线债文件时恒败,逼 agent 范围外清偿或逃生口)。
|
|
141
|
+
* 口径:按空白切 token,剥引号/行号尾缀(ruff/eslint 的 path:line:col 形态),保留带路径
|
|
142
|
+
* 分隔符且以代码扩展名结尾的 token(不带分隔符的普通单词不收,防误报)。去重保序。
|
|
143
|
+
*/
|
|
144
|
+
export function extractLintFailureFiles(output) {
|
|
145
|
+
if (!output || typeof output !== 'string') return []
|
|
146
|
+
const LINT_FILE_EXT_RE = /\.(?:js|mjs|cjs|ts|tsx|jsx|vue|svelte|py|pyw|go|rs|java|kt|kts|rb|php|cs|c|h|cpp|cc|hpp|swift|scala|css|scss)$/
|
|
147
|
+
const seen = new Set()
|
|
148
|
+
for (const raw of output.split(/\s+/)) {
|
|
149
|
+
let t = raw.replace(/^["'`(\[]+/, '').replace(/["'`),;]+$/, '')
|
|
150
|
+
t = t.replace(/:(?:\d+)(?::\d+)*$/, '') // 剥 :line / :line:col
|
|
151
|
+
if (!LINT_FILE_EXT_RE.test(t)) continue
|
|
152
|
+
if (!/[\\/]/.test(t)) continue
|
|
153
|
+
const norm = t.replace(/\\/g, '/').replace(/^\.\//, '')
|
|
154
|
+
if (norm.length > 1) seen.add(norm)
|
|
155
|
+
}
|
|
156
|
+
return [...seen]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* lint 失败归属判定(纯函数):失败输出提及的文件 × 本变更文件集。
|
|
161
|
+
* - owned:有交集 → 失败可能由本变更引入 → 维持硬拦(保守)。
|
|
162
|
+
* - pre-existing:零交集 → 全是 HEAD 存量债 → 调用方降档 advisory(债不是本变更的错)。
|
|
163
|
+
* - unattributable:失败文件提取为空(输出无可识别路径,如纯配置错误)→ 维持硬拦(无法
|
|
164
|
+
* 排除本变更责任,宁可拦不可漏)。
|
|
165
|
+
* 匹配宽松双向 endsWith(输出可能是绝对路径/仓相对路径,两侧口径不一定同源)。
|
|
166
|
+
*/
|
|
167
|
+
export function triageLintOwnership({ failureFiles = [], changeFiles = [] } = {}) {
|
|
168
|
+
const norm = (p) => String(p).replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase()
|
|
169
|
+
const ffs = (Array.isArray(failureFiles) ? failureFiles : []).map(norm).filter(Boolean)
|
|
170
|
+
if (ffs.length === 0) return { verdict: 'unattributable', overlap: [] }
|
|
171
|
+
const cfs = (Array.isArray(changeFiles) ? changeFiles : []).map(norm).filter(Boolean)
|
|
172
|
+
const overlap = ffs.filter(f => cfs.some(c => c === f || c.endsWith('/' + f) || f.endsWith('/' + c)))
|
|
173
|
+
return overlap.length > 0 ? { verdict: 'owned', overlap } : { verdict: 'pre-existing', overlap: [] }
|
|
174
|
+
}
|
|
175
|
+
|
|
138
176
|
/**
|
|
139
177
|
* lint advisory 观察期计数(刀③,2026-09-08):advisory 失败不阻断,但「观察期后升级硬门」
|
|
140
178
|
* 需要失败率数据支撑(更硬 vs 更吵的决策依据——硬门误伤纯文档/跨平台路径会制造新噪音)。
|
|
@@ -226,6 +264,7 @@ export function runVerifyLintCheck({ cwd, specBase, timeoutMs } = {}) {
|
|
|
226
264
|
outputTail,
|
|
227
265
|
reason: finalReason,
|
|
228
266
|
tally, // { failedRuns, totalRuns } | null——观察期失败率,升级硬门的决策依据
|
|
267
|
+
failureFiles: status === 'failed' ? extractLintFailureFiles(output) : [], // 归属鉴定输入(全文口径,outputTail 有截断盲区)
|
|
229
268
|
}
|
|
230
269
|
}
|
|
231
270
|
|
|
@@ -266,7 +305,7 @@ export function printVerifyLintCheck(result) {
|
|
|
266
305
|
console.error(`\n⚠️ Verify lint 实测失败(advisory 档,不阻断本次完成):\`${result.command}\` — ${result.reason}`)
|
|
267
306
|
console.error(' agent 的 lint 自报告与实测不符时以实测为准;请修复后重跑,避免格式债推迟到 commit 被 pre-commit hook 拦截。')
|
|
268
307
|
if (result.tally) {
|
|
269
|
-
console.error(` 📊 lint advisory
|
|
308
|
+
console.error(` 📊 lint advisory 观察期:实测 ${result.tally.totalRuns} 次中失败 ${result.tally.failedRuns} 次(数据落 .runtime/verify-lint-tally.json)。`)
|
|
270
309
|
}
|
|
271
310
|
}
|
|
272
311
|
if (result.outputTail) {
|
|
@@ -1027,6 +1066,22 @@ export function classifyTestFailureArtifact(result) {
|
|
|
1027
1066
|
* - 其余(full / module 无块 / module git 不可用 hitCount=-1)→ 'full'
|
|
1028
1067
|
* @returns {'skip'|'module-subset'|'module-zero-hit-skip'|'full'}
|
|
1029
1068
|
*/
|
|
1069
|
+
/**
|
|
1070
|
+
* 纯超时失败判定(R4 门禁价值考古 2026-09-21:227 跑 106 败里 36 次(34%)是 600s 帽杀的
|
|
1071
|
+
* 纯超时假拦——超时=未完成非测试失败,硬拦只是浪费一轮门禁)。所有失败单元的 reason 都
|
|
1072
|
+
* 含「超时」→ true(调用方降档 advisory);任一失败单元是真实退出码/挂测 → false(维持硬拦)。
|
|
1073
|
+
* 单元来源:module 模式读 result.modules(失败单元过滤),full 模式整体 reason。跨仓合并结果
|
|
1074
|
+
* 语义同 full(整体 reason 聚合)。
|
|
1075
|
+
*/
|
|
1076
|
+
export function isTimeoutOnlyTestFailure(testCheck) {
|
|
1077
|
+
if (!testCheck || testCheck.status !== 'failed') return false
|
|
1078
|
+
const units = Array.isArray(testCheck.modules) && testCheck.modules.length > 0
|
|
1079
|
+
? testCheck.modules.filter(u => u && u.status === 'failed')
|
|
1080
|
+
: [testCheck]
|
|
1081
|
+
if (units.length === 0) return false
|
|
1082
|
+
return units.every(u => /超时/.test(String(u.reason || '')))
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1030
1085
|
export function decideVerifyTestAction({ strategy, modulesPresent, hitCount }) {
|
|
1031
1086
|
if (strategy === 'skip') return 'skip'
|
|
1032
1087
|
if (strategy === 'module' && modulesPresent) {
|
|
@@ -2034,6 +2089,9 @@ function runModuleSubset({ cwd, specBase, changeName, hits, knownFailures = [],
|
|
|
2034
2089
|
// 每模块台账另见 extra.modules[].failure_remaining(带模块归属)
|
|
2035
2090
|
failureRemaining: perModule.flatMap(r => r.failureRemaining || []),
|
|
2036
2091
|
failureExempted: perModule.flatMap(r => r.failureExempted || []),
|
|
2092
|
+
// 逐模块结论(isTimeoutOnlyTestFailure 的单元口径;writeRunResult extra 之外再随结果
|
|
2093
|
+
// 返回,调用方可精确分单元判超时/真挂,不依赖聚合 reason 文本)
|
|
2094
|
+
modules: perModule.map(r => ({ name: r.name, status: r.status, reason: r.reason, durationMs: r.durationMs })),
|
|
2037
2095
|
}
|
|
2038
2096
|
|
|
2039
2097
|
writeRunResult({
|
|
@@ -2909,21 +2967,25 @@ export function reconcileTargetFiles({ cwd, specBase = null, changeName = null,
|
|
|
2909
2967
|
const decl = collectDeclaredTargetFiles(sb, changeName)
|
|
2910
2968
|
// —— 跨仓 per-repo 对账(坑 cross-repo-reconcile-blindness,2026-09-15 复盘兑现 D-004 分期:
|
|
2911
2969
|
// 此前 22 个跨仓文件在归档表全标「计划未动」、跨仓全靠人工到对应仓解释)——advisory 不阻断:
|
|
2912
|
-
//
|
|
2913
|
-
//
|
|
2914
|
-
//
|
|
2970
|
+
// 锚点档经内核分级(A reviews 锡点区间 > B HEAD~1..HEAD 窗口 > C 未提交窗口,runtimeRoot/
|
|
2971
|
+
// changeName 传参贯通喂 A 档 reviews 解析——评审 G2;锚点脆弱期会产②类假信号),只报告不
|
|
2972
|
+
// 翻转主仓状态。结果经 crossRepo 字段(gates 渲染明细)与 notes(摘要随各状态出口流动)
|
|
2973
|
+
// 双通道出口。
|
|
2915
2974
|
let crossRepo = []
|
|
2916
2975
|
if (decl.crossRepoCards.length > 0) {
|
|
2917
2976
|
try {
|
|
2918
|
-
crossRepo = reconcileCrossRepoDeclarations({ specBase: sb, cwd, declarationsByRepo: decl.crossRepoDeclarations })
|
|
2977
|
+
crossRepo = reconcileCrossRepoDeclarations({ specBase: sb, cwd, declarationsByRepo: decl.crossRepoDeclarations, runtimeRoot: rt, changeName })
|
|
2919
2978
|
} catch { crossRepo = [] /* 跨仓对账异常不拖垮主仓链(advisory 定位) */ }
|
|
2920
2979
|
for (const label of decl.crossRepoCards) {
|
|
2921
2980
|
notes.push(`跨仓 task 卡 ${label} 不进主仓对账(主仓 actual 对不上跨仓声明,D-004)——其声明已转 per-repo 对账(见下方跨仓对账段)`)
|
|
2922
2981
|
}
|
|
2923
2982
|
for (const r of crossRepo) {
|
|
2983
|
+
// 锚点档动态化(task-03):优先内核透传的 anchor.label(A/B/C/degraded 四档人类可读描述);
|
|
2984
|
+
// 旧形态无 anchor 字段 → 防御性回退原文案(该仓最近提交窗口)
|
|
2985
|
+
const anchorTxt = r.anchor && typeof r.anchor.label === 'string' && r.anchor.label ? r.anchor.label : '该仓最近提交窗口'
|
|
2924
2986
|
notes.push(r.degradedReason
|
|
2925
2987
|
? `跨仓 ${r.repo}:${r.degradedReason}`
|
|
2926
|
-
: `跨仓 ${r.repo} 对账:声明 ${r.declaredCount} / 实测 ${r.actualCount} / 对上 ${r.matched.length} / ②缺 ${r.missing.length} / ③多 ${r.undeclared.length}${r.scaffoldCount > 0 ? `(另有 ${r.scaffoldCount} 脚手架已聚合)` : ''}——advisory
|
|
2988
|
+
: `跨仓 ${r.repo} 对账:声明 ${r.declaredCount} / 实测 ${r.actualCount} / 对上 ${r.matched.length} / ②缺 ${r.missing.length} / ③多 ${r.undeclared.length}${r.scaffoldCount > 0 ? `(另有 ${r.scaffoldCount} 脚手架已聚合)` : ''}——advisory(锚点=${anchorTxt})`)
|
|
2927
2989
|
}
|
|
2928
2990
|
}
|
|
2929
2991
|
if (decl.cardCount === 0) {
|
package/src/verify-probes.js
CHANGED
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'fs'
|
|
28
28
|
import { join, dirname, basename, resolve, isAbsolute, relative } from 'path'
|
|
29
|
-
import jsYaml from 'js-yaml'
|
|
30
29
|
import { gitQuiet, unquoteGitPath } from './git-helper.js'
|
|
31
30
|
import { hasUnconfirmedPrefill } from './prefill.js'
|
|
32
31
|
import {
|
|
@@ -36,6 +35,7 @@ import { parseFileChangeListDetailed } from './change-list.js'
|
|
|
36
35
|
import { parseDecisions } from './decision-distill.js'
|
|
37
36
|
import { parseAllowedPaths, parseRepo, parseRepoRegistry } from './stages/plan-postcheck.js'
|
|
38
37
|
import { verifyApiParity, _readWorktreeMeta } from './contract-matrix.js'
|
|
38
|
+
import { parseTaskFrontmatter } from './taskcard-frontmatter.js'
|
|
39
39
|
import { RECEIPT_SOURCE_CROSS_LAYER_RE as RECEIPT_CROSS_LAYER_RE, RECEIPT_SOURCE_UNIT_RE as RECEIPT_UNIT_RE, CLI_SMOKE_SOURCE_MARK as SMOKE_RECEIPT_SOURCE_MARK } from './change-risk-profile.js'
|
|
40
40
|
import { splitOwnVsForeignDiffFiles } from './foreign-declared.js'
|
|
41
41
|
import { resolveSpecDir, resolveRuntimeRoot, detectWorktreeSpecDrift } from './run/shared.js'
|
|
@@ -1073,7 +1073,7 @@ function designHasSubmitEndpoint(text) {
|
|
|
1073
1073
|
// sectionHint(命中的接口段头列表,审计注记)。
|
|
1074
1074
|
// 落盘面(数据通道=落盘即消费,stage-contract 零 import 纪律):backfillFactsFromMdAndTests
|
|
1075
1075
|
// 主路径写 facts.apiFace / facts.consumerHints,task-05 validator 经 facts 消费不经函数调用。
|
|
1076
|
-
const API_FACE_SECTION_RE =
|
|
1076
|
+
const API_FACE_SECTION_RE = /接口|端点|\bapi\b|\brest\b/i // quick-B 词边界:OpenAPI 标题(api 前邻字母)不再误收进 sectionHint
|
|
1077
1077
|
const API_FACE_METHOD_RE = /(?:^|[^A-Za-z])(GET|POST|PUT|DELETE|PATCH)(?![A-Za-z])/
|
|
1078
1078
|
// 路径样式 token:/ 起始、前邻非字母数字(日期 2026/09/17 的 /09 前是数字段不认),
|
|
1079
1079
|
// 首字符字母数字/_/-/{,后续段字符含 {}/: 模板形态(/orders/{id}、/orders/:id 全串命中)
|
|
@@ -1713,26 +1713,34 @@ const PROBE7_NONTESTABLE_RE = /文档|部署|doc|deploy|manual|config/i
|
|
|
1713
1713
|
|
|
1714
1714
|
/**
|
|
1715
1715
|
* 解析 task-NN.md frontmatter 的 acceptance(string/array 双形态归一为数组)。
|
|
1716
|
-
*
|
|
1717
|
-
*
|
|
1716
|
+
* 解析归一 taskcard-frontmatter.js 单一源(2026-09-20-taskcard-yaml-hardgate:此前坏 YAML
|
|
1717
|
+
* catch 返回 [] 冒充「无 acceptance」,探针 7 渲染假防御文案)——status 三态让调用方区分
|
|
1718
|
+
* 「真无 acceptance」与「frontmatter 非法 YAML」。口径锚 src/stages/plan-postcheck.js
|
|
1719
|
+
* acceptance best-effort 段(string → 原文单条、array → 逐条)。
|
|
1718
1720
|
* @param {string} content task 卡全文
|
|
1719
|
-
* @returns {
|
|
1720
|
-
*
|
|
1721
|
+
* @returns {{ status: 'no-frontmatter'|'invalid-yaml'|'ok', acceptance: string[],
|
|
1722
|
+
* error: {message: string, line: number, column: number}|null }}
|
|
1723
|
+
* no-frontmatter = 无 frontmatter(调用方跳过该卡);invalid-yaml = frontmatter 非法 YAML
|
|
1724
|
+
* (acceptance 不可读,error 带文件 1 基行:列);ok = 合法解析,acceptance 归一数组
|
|
1725
|
+
* (frontmatter 在场但无 acceptance → [],此时防御行为真——plan-postcheck 已拦缺失)
|
|
1721
1726
|
*/
|
|
1722
1727
|
export function parseTaskAcceptance(content) {
|
|
1723
|
-
const
|
|
1724
|
-
if (!
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1728
|
+
const parsed = parseTaskFrontmatter(content)
|
|
1729
|
+
if (!parsed.hasFrontmatter) return { status: 'no-frontmatter', acceptance: [], error: null }
|
|
1730
|
+
if (!parsed.ok) return { status: 'invalid-yaml', acceptance: [], error: parsed.error }
|
|
1731
|
+
const fmObj = parsed.fm || {}
|
|
1732
|
+
if (typeof fmObj.acceptance === 'string') {
|
|
1733
|
+
const t = fmObj.acceptance.trim()
|
|
1734
|
+
return { status: 'ok', acceptance: t ? [t] : [], error: null }
|
|
1735
|
+
}
|
|
1736
|
+
if (Array.isArray(fmObj.acceptance)) {
|
|
1737
|
+
return {
|
|
1738
|
+
status: 'ok',
|
|
1739
|
+
acceptance: fmObj.acceptance.filter(x => typeof x === 'string' && x.trim() !== '').map(x => x.trim()),
|
|
1740
|
+
error: null,
|
|
1733
1741
|
}
|
|
1734
|
-
}
|
|
1735
|
-
return []
|
|
1742
|
+
}
|
|
1743
|
+
return { status: 'ok', acceptance: [], error: null }
|
|
1736
1744
|
}
|
|
1737
1745
|
|
|
1738
1746
|
/**
|
|
@@ -1939,6 +1947,12 @@ function renderProbe7Lines(p7) {
|
|
|
1939
1947
|
for (const t of (p7.tasks || [])) {
|
|
1940
1948
|
L.push('')
|
|
1941
1949
|
L.push(`**${t.task}**`)
|
|
1950
|
+
if (t.fmError) {
|
|
1951
|
+
// 坏 YAML 如实陈述(2026-09-20-taskcard-yaml-hardgate):非「无 acceptance」防御——
|
|
1952
|
+
// plan 门禁 0b 硬校验应已拦截;已过 plan 门仍见此行 = 门禁失效信号(可反馈工具缺陷)
|
|
1953
|
+
L.push(`- ⚠️ frontmatter 非法 YAML(${t.task}.md:${t.fmError.line}:${t.fmError.column} ${mdEscapeCell(t.fmError.message, 160)})——acceptance 不可读,本行非「无 acceptance」防御;plan 门禁 frontmatter 硬校验应已拦截,若已过 plan 门仍见此行即门禁失效信号`)
|
|
1954
|
+
continue
|
|
1955
|
+
}
|
|
1942
1956
|
if (!Array.isArray(t.acceptance) || t.acceptance.length === 0) {
|
|
1943
1957
|
L.push('- (卡无 acceptance——防御,plan-postcheck 已拦)')
|
|
1944
1958
|
continue
|
|
@@ -2138,10 +2152,17 @@ export function runVerifyProbes({ cwd, changeName, specDir = null }) {
|
|
|
2138
2152
|
try {
|
|
2139
2153
|
for (const f of readdirSync(tasksDir).filter(n => /^task-\d+\.md$/.test(n)).sort()) {
|
|
2140
2154
|
const raw = readFileSync(join(tasksDir, f), 'utf8')
|
|
2141
|
-
|
|
2142
|
-
|
|
2155
|
+
// 三态分流(2026-09-20-taskcard-yaml-hardgate):no-frontmatter 跳卡(原 null 语义);
|
|
2156
|
+
// invalid-yaml 挂 fmError 进条目——渲染层区分「坏 YAML」与「真无 acceptance」防御行
|
|
2157
|
+
const parsed = parseTaskAcceptance(raw)
|
|
2158
|
+
if (parsed.status === 'no-frontmatter') continue // 无 frontmatter → 跳过(plan-postcheck 已拦)
|
|
2143
2159
|
const fmId = (raw.match(/^id:\s*(\S+)/m) || [])[1]
|
|
2144
|
-
cards.push({
|
|
2160
|
+
cards.push({
|
|
2161
|
+
task: fmId || f.replace(/\.md$/, ''),
|
|
2162
|
+
acceptance: parsed.acceptance,
|
|
2163
|
+
fmError: parsed.status === 'invalid-yaml' ? parsed.error : null,
|
|
2164
|
+
raw,
|
|
2165
|
+
})
|
|
2145
2166
|
}
|
|
2146
2167
|
} catch { /* tasks 目录不可读 → applicable 维持 false */ }
|
|
2147
2168
|
probe7.applicable = cards.length > 0
|
|
@@ -2196,6 +2217,7 @@ export function runVerifyProbes({ cwd, changeName, specDir = null }) {
|
|
|
2196
2217
|
probe7.tasks.push({
|
|
2197
2218
|
task: card.task,
|
|
2198
2219
|
acceptance: card.acceptance,
|
|
2220
|
+
fmError: card.fmError || null,
|
|
2199
2221
|
testFiles,
|
|
2200
2222
|
hints: buildAcceptanceHints(card.acceptance, testFiles, cwd, wtRoot, probe7CrossRoots),
|
|
2201
2223
|
})
|
|
@@ -2568,7 +2590,7 @@ function renderApiCoverageMatrixLines(apiFace) {
|
|
|
2568
2590
|
L.push('<!-- 预填说明:端点行由 CLI 机械预填,判定/用例依据 ID/结果/证据由 agent 逐格填写——用例依据 ID 锚点五形态:design接口表#METHOD /path、权限矩阵[角色×动作]、契约表@行标识、DDL@列名、载荷@构造点路径(须真实命中对应表/段,防空指)。 -->')
|
|
2569
2591
|
L.push('<!-- 文法注释:子行 = 端点行下一行、两空格缩进、以「↳ <消费端>:」前缀书写(消费端细分承接面,不计矩阵行账);探索行 = 判定 uncovered 且证据列含 [探索] 标记(探索性验证不算覆盖)。 -->')
|
|
2570
2592
|
if (endpoints.length === 0 && declared === null) {
|
|
2571
|
-
L.push('- 无接口面(design 接口段解析零端点且无「本变更接口面:N 端点」声明行)——本变更若实际触碰接口,先补 design 接口段表格或声明行,再重跑 `verify-probes --init`
|
|
2593
|
+
L.push('- 无接口面(design 接口段解析零端点且无「本变更接口面:N 端点」声明行)——本变更若实际触碰接口,先补 design 接口段表格或声明行,再重跑 `verify-probes --change <变更名> --init --force` 重生成本段(⚠️ 全骨架重生成,手填结论会重置——先备份;quick-B 起 --force 通道存在);判级 critical 的零面拦截归 validator')
|
|
2572
2594
|
return L
|
|
2573
2595
|
}
|
|
2574
2596
|
if (endpoints.length > 0 && declared !== null && declared !== endpoints.length) {
|
|
@@ -2582,7 +2604,7 @@ function renderApiCoverageMatrixLines(apiFace) {
|
|
|
2582
2604
|
}
|
|
2583
2605
|
} else {
|
|
2584
2606
|
L.push(`| 本变更接口面:${declared} 端点(agent 声明) | <待填:五选一> | <待填:用例 ID> | <待填> | <待填:锚点> |`)
|
|
2585
|
-
L.push('<!-- 解析零行降级(D-005):接口面以 agent 声明为准(对账分母=声明数);声明与实际不符时补 design 接口段表格后重跑 --init
|
|
2607
|
+
L.push('<!-- 解析零行降级(D-005):接口面以 agent 声明为准(对账分母=声明数);声明与实际不符时补 design 接口段表格后重跑 --init --force 重生成本段(quick-B:--force 才有刷新通道,手填内容会重置先备份) -->')
|
|
2586
2608
|
}
|
|
2587
2609
|
L.push('<!-- advisory 尾注(warning 计算归 validator,本段只留位):有消费端未填子行的端点将列于此(advisory——消费端归类=design 清单启发式,数据面 facts.consumerHints);写端点(POST/PUT/DELETE/PATCH)未在权限矩阵段声明的将列于此(advisory——补行或显式豁免「无权限约束」,数据面 facts.apiFace.writeEndpoints;表缺行会让派生框架继承你的洞) -->')
|
|
2588
2610
|
return L
|