sillyspec 3.27.2 → 3.27.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/sillyspec-execute/SKILL.md +8 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +2 -1
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-archive/SKILL.md +87 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-auto/SKILL.md +93 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-commit/SKILL.md +67 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-continue/SKILL.md +47 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-execute/SKILL.md +214 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-explore/SKILL.md +102 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-export/SKILL.md +57 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-init/SKILL.md +177 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-plan/SKILL.md +156 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-quick/SKILL.md +116 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-resume/SKILL.md +70 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-scan/SKILL.md +79 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-state/SKILL.md +64 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-status/SKILL.md +54 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-verify/SKILL.md +87 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
- package/.claude/worktrees/sillyspec-data-model-research/.gitattributes +2 -0
- package/.claude/worktrees/sillyspec-data-model-research/.husky/pre-push +20 -0
- package/.claude/worktrees/sillyspec-data-model-research/README.md +203 -0
- package/.claude/worktrees/sillyspec-data-model-research/SKILL.md +91 -0
- package/.claude/worktrees/sillyspec-data-model-research/bin/sillyspec.js +2 -0
- package/.claude/worktrees/sillyspec-data-model-research/logo.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/package-lock.json +977 -0
- package/.claude/worktrees/sillyspec-data-model-research/package.json +39 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/favicon.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/index.html +16 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/logo.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-overview.html +256 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/package.json +25 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/executor.js +95 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/index.js +679 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/parser.js +526 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/watcher.js +357 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/agent-session-log.js +803 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/change-list.js +290 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/change-risk-profile.js +270 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/check-primitives.js +65 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/classify-change.js +109 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/commit-suggest.js +202 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/config-cat.js +88 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/config-schema.js +328 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/constants.js +97 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/contract-matrix.js +480 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/datetime.js +20 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/db-engine.js +55 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/db.js +381 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/decision-distill.js +517 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/local-agent.js +155 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/sillyhub-mcp.js +245 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/probe.js +246 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/strategy.js +121 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-check.js +825 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-debt.js +393 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-gate.js +107 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/doctor-diagnostics.js +1099 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/endpoint-extractor.js +420 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/foreign-declared.js +78 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/fs-atomic.js +73 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/git-helper.js +150 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/hooks/claude-pre-tool-use.cjs +130 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/hooks/worktree-guard.js +892 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/index.js +2798 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/init.js +626 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/knowledge-match.js +231 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/local-detect.js +128 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/local-register.js +104 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/machine-interface.js +543 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/migrate.js +127 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/module-impact.js +166 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/module-resolve.js +222 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/modules.js +580 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/change-registry.js +336 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/consistency-doctor.js +355 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/shared.js +36 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/stage-machine.js +594 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/step-store.js +265 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress.js +1304 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/quick-recommend.js +115 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/quicklog.js +852 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/review-tier.js +77 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/command.js +1595 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/complete-handlers.js +1552 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/complete.js +1259 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/concurrent-detect.js +156 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/gates.js +1084 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/multi-repo-context.js +208 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/next.js +128 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/prompt.js +843 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/quick-audit.js +186 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/scan-profile.js +269 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/shared.js +1562 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/stage.js +584 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run.js +23 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-diff.js +388 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-postcheck.js +512 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-staleness.js +95 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/setup.js +414 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/client.js +597 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/config.js +62 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/spec-dir-typo.js +50 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/spec-sync.js +358 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-engine.js +171 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-spec.js +550 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract.js +889 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-review.js +597 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/archive.js +199 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm-auto.js +236 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm.js +617 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/cmd-existence.js +145 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/doctor.js +540 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/execute.js +1275 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/explore.js +43 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/index.js +29 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/knowledge.js +514 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan-postcheck.js +1344 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan.js +650 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/quick.js +167 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/scan.js +608 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/status.js +64 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/verify.js +246 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sync.js +1470 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/task-review.js +1498 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/taskcard.js +210 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/verify-postcheck.js +1718 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/verify-probes.js +277 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/version.js +19 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/workflow.js +600 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/workspace.js +125 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree-apply.js +1446 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree-deps.js +576 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree.js +1715 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/claude-instruction.md +23 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/taskcard-rules.md +28 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/testcase-design.md +12 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/verify-probes.md +32 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/archive-impact.yaml +79 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/scan-docs.yaml +132 -0
- package/eval/README.md +86 -0
- package/eval/eval.config.json +28 -0
- package/eval/lib/adapters/cli.mjs +63 -0
- package/eval/lib/adapters/dryrun.mjs +27 -0
- package/eval/lib/config.mjs +82 -0
- package/eval/lib/db.mjs +76 -0
- package/eval/lib/report.mjs +120 -0
- package/eval/lib/runner.mjs +157 -0
- package/eval/lib/tasks.mjs +73 -0
- package/eval/lib/util.mjs +107 -0
- package/eval/lib/workdir.mjs +30 -0
- package/eval/run.mjs +83 -0
- package/eval/tasks/001-slugify/files/package.json +6 -0
- package/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
- package/eval/tasks/001-slugify/task.json +6 -0
- package/eval/tasks/001-slugify/verify.mjs +28 -0
- package/eval/tasks/002-each-day/files/package.json +6 -0
- package/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
- package/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
- package/eval/tasks/002-each-day/task.json +6 -0
- package/eval/tasks/002-each-day/verify.mjs +31 -0
- package/eval/tasks/003-word-report/files/data/notes.txt +5 -0
- package/eval/tasks/003-word-report/files/package.json +6 -0
- package/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
- package/eval/tasks/003-word-report/task.json +6 -0
- package/eval/tasks/003-word-report/verify.mjs +38 -0
- package/package.json +4 -2
- package/src/agent-session-log.js +45 -5
- package/src/change-risk-profile.js +83 -14
- package/src/config-schema.js +18 -3
- package/src/contract-matrix.js +90 -29
- package/src/datetime.js +20 -0
- package/src/decision-distill.js +517 -0
- package/src/dispatch/backends/local-agent.js +4 -4
- package/src/docs-check.js +227 -0
- package/src/docs-debt.js +187 -24
- package/src/doctor-diagnostics.js +102 -2
- package/src/foreign-declared.js +78 -0
- package/src/index.js +89 -9
- package/src/init.js +9 -7
- package/src/knowledge-match.js +107 -6
- package/src/local-register.js +7 -1
- package/src/module-resolve.js +12 -1
- package/src/modules.js +2 -1
- package/src/plan-adopt-waves.js +126 -0
- package/src/quicklog.js +50 -4
- package/src/run/command.js +122 -43
- package/src/run/complete-handlers.js +48 -19
- package/src/run/complete.js +5 -2
- package/src/run/gates.js +87 -22
- package/src/run/prompt.js +84 -42
- package/src/run/quick-audit.js +11 -0
- package/src/run/shared.js +152 -10
- package/src/run.js +2 -2
- package/src/scan-postcheck.js +30 -17
- package/src/stage-contract-spec.js +2 -1
- package/src/stage-contract.js +68 -4
- package/src/stages/archive.js +53 -12
- package/src/stages/brainstorm-auto.js +7 -0
- package/src/stages/brainstorm.js +11 -0
- package/src/stages/doctor.js +157 -1
- package/src/stages/execute.js +32 -6
- package/src/stages/plan-postcheck.js +76 -17
- package/src/stages/plan.js +1 -0
- package/src/stages/quick.js +6 -1
- package/src/stages/verify.js +31 -14
- package/src/sync.js +41 -3
- package/src/task-review.js +2 -2
- package/src/taskcard-placeholders.js +21 -0
- package/src/taskcard.js +10 -1
- package/src/verify-postcheck.js +315 -13
- package/src/verify-probes.js +46 -4
- package/src/worktree-apply.js +184 -7
- package/src/worktree-deps.js +57 -0
- package/src/worktree.js +201 -58
- package/templates/prompts/taskcard-rules.md +1 -0
package/src/verify-postcheck.js
CHANGED
|
@@ -9,15 +9,22 @@
|
|
|
9
9
|
* 未配置 commands.test(或标记 unavailable)时降级为 warning 不阻断,
|
|
10
10
|
* 兼容无测试项目。
|
|
11
11
|
*
|
|
12
|
-
* test_strategy 支持(D-002@v1):
|
|
12
|
+
* test_strategy 支持(D-002@v1;D-005@v2 扩 skip/evidence-auto):
|
|
13
13
|
* - full(默认):整跑 commands.test(brownfield 行为不变)
|
|
14
14
|
* - module:按 local.yaml modules 映射,仅跑 git diff 命中的模块子集
|
|
15
15
|
* 测试,避免 monorepo 全量测试超 gate timeout。
|
|
16
|
+
* - skip:真跳过测试(不再回退全量——兑现声明语义,verify 输出显式
|
|
17
|
+
* 标注留审计痕迹,R-07;既有 skipped 状态机承载)。
|
|
18
|
+
* - evidence-auto:按变更目录 module-impact.md 影响类型推荐检查组合
|
|
19
|
+
* (行为→module 聚焦测试、文档/prompt→docs-check、门禁
|
|
20
|
+
* 契约→gate;缺失/不可解析降级 module 并注记),推荐结果
|
|
21
|
+
* 由 resolveTestStrategy 统一产出(契约 test_strategy_resolution,
|
|
22
|
+
* task-12 经 run/prompt.js 注入 prompt 供用户否决)。
|
|
16
23
|
*/
|
|
17
24
|
|
|
18
25
|
import { execSync } from 'child_process'
|
|
19
26
|
import { gitQuiet } from './git-helper.js'
|
|
20
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
27
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs'
|
|
21
28
|
import { join } from 'path'
|
|
22
29
|
import { verifyApiParity } from './contract-matrix.js'
|
|
23
30
|
import { parseFileChangeListDetailed, pathMatches } from './change-list.js'
|
|
@@ -162,16 +169,22 @@ export function printVerifyLintCheck(result) {
|
|
|
162
169
|
* 从 local.yaml 文本提取顶层 test_strategy。
|
|
163
170
|
* 轻量正则(与 extractTestCommand 同风格,不引 yaml 依赖)。
|
|
164
171
|
*
|
|
172
|
+
* D-005@v2:识别 skip(真跳过)与 evidence-auto(按 module-impact 推荐)两新值;
|
|
173
|
+
* 未知值仍回 null(缺省全量口径不动)。
|
|
174
|
+
*
|
|
165
175
|
* @param {string} yamlText
|
|
166
|
-
* @returns {'full'|'module'|null} - 解析到的策略;缺省/无法解析返回 null(调用方按 full 处理)
|
|
176
|
+
* @returns {'full'|'module'|'skip'|'evidence-auto'|null} - 解析到的策略;缺省/无法解析返回 null(调用方按 full 处理)
|
|
167
177
|
*/
|
|
168
178
|
export function extractTestStrategy(yamlText) {
|
|
169
179
|
if (!yamlText) return null
|
|
170
|
-
|
|
180
|
+
// 值字符集含 '-'(evidence-auto);不含 '.'/空格,与顶层标量枚举写法一致
|
|
181
|
+
const m = yamlText.match(/^\s*test_strategy:\s*([A-Za-z_-]+)\s*(?:#.*)?$/m)
|
|
171
182
|
if (!m || !m[1]) return null
|
|
172
183
|
const v = m[1].trim().toLowerCase()
|
|
173
184
|
if (v === 'module') return 'module'
|
|
174
185
|
if (v === 'full') return 'full'
|
|
186
|
+
if (v === 'skip') return 'skip'
|
|
187
|
+
if (v === 'evidence-auto') return 'evidence-auto'
|
|
175
188
|
return null
|
|
176
189
|
}
|
|
177
190
|
|
|
@@ -192,6 +205,10 @@ export function extractTestStrategy(yamlText) {
|
|
|
192
205
|
* - strategy==='module' 有块且命中(hitCount>0)→ null(走子集)
|
|
193
206
|
* - strategy===null(缺省)→ hint(默认全量)
|
|
194
207
|
*
|
|
208
|
+
* 注:strategy 仅接收 full/module/null——skip 短路在其上游(runVerifyTestCheck 的
|
|
209
|
+
* skip 分支)、evidence-auto 经 resolveTestStrategy 解析为生效策略后才进本函数,
|
|
210
|
+
* 两者的 hint 语义由各自分支承载,本函数行为不变(D-005@v2)。
|
|
211
|
+
*
|
|
195
212
|
* @param {object} ctx
|
|
196
213
|
* @param {'full'|'module'|null} ctx.strategy
|
|
197
214
|
* @param {boolean} ctx.modulesPresent - extractModules 是否返回有效映射
|
|
@@ -216,6 +233,199 @@ export function computeFullFallbackReason({ strategy, modulesPresent, hitCount }
|
|
|
216
233
|
return 'local.yaml 未配置 test_strategy(默认全量 commands.test,未按变更范围收窄)'
|
|
217
234
|
}
|
|
218
235
|
|
|
236
|
+
// ── evidence-auto 推荐逻辑(D-005@v2 / task-11,契约 test_strategy_resolution)──
|
|
237
|
+
// 按 W3 检查选择口径把 module-impact.md 的影响面映射为检查组合:
|
|
238
|
+
// 行为类(源码/逻辑/数据结构/接口/调用关系/配置/新增/修改)→ module 聚焦测试
|
|
239
|
+
// 文档/prompt 类(*.md、docs/** 等)→ docs-check
|
|
240
|
+
// 门禁/契约类(路径含 gate/contract)→ sillyspec gate
|
|
241
|
+
// module-impact.md 缺失/不可解析 → 降级 module 策略并注记(多测不漏测)
|
|
242
|
+
|
|
243
|
+
/** 行为类影响信号词(module-impact.js 骨架「影响类型说明」词表 + 通用「修改/删除」)。 */
|
|
244
|
+
const IMPACT_BEHAVIORAL_TOKENS = ['逻辑变更', '数据结构变更', '接口变更', '调用关系变更', '配置变更', '新增', '修改', '删除']
|
|
245
|
+
|
|
246
|
+
/** 文档/prompt 面:md 系扩展名或 docs/ 前缀(.sillyspec/docs 同入)。 */
|
|
247
|
+
function isDocsImpactPath(p) {
|
|
248
|
+
const n = String(p).replace(/\\/g, '/')
|
|
249
|
+
return /\.(mdx?|markdown|txt)$/i.test(n) || n.startsWith('docs/') || n.startsWith('doc/') || n.startsWith('.sillyspec/docs/')
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** 门禁/契约面:路径含 gate/contract(stage-contract*.js / docs-gate.js / contract-matrix.js 等)。 */
|
|
253
|
+
function isGateImpactPath(p) {
|
|
254
|
+
return /(gate|contract)/i.test(String(p))
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** 从行文本提取路径样 token:优先反引号包裹(骨架表格用),再裸路径(扩展名必须字母开头)。 */
|
|
258
|
+
function collectPathTokens(text) {
|
|
259
|
+
const tokens = new Set()
|
|
260
|
+
for (const m of String(text).matchAll(/`([^`]+)`/g)) {
|
|
261
|
+
const t = m[1].trim()
|
|
262
|
+
if (t) tokens.add(t)
|
|
263
|
+
}
|
|
264
|
+
const stripped = String(text).replace(/`[^`]*`/g, ' ')
|
|
265
|
+
for (const m of stripped.matchAll(/[A-Za-z_][\w./\\-]*\.[A-Za-z][\w]{0,9}/g)) {
|
|
266
|
+
tokens.add(m[0])
|
|
267
|
+
}
|
|
268
|
+
return [...tokens]
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 解析 module-impact.md 的「模块影响矩阵」+「未匹配文件」两节,按检查面归类影响证据。
|
|
273
|
+
* 纯机械启发式(确定性:同输入同输出);矩阵行 <!--TODO--> 未回填 → 保守计行为类
|
|
274
|
+
* (影响面未知时多测不漏测)。矩阵节缺失 / 无数据行 → parseable=false(调用方降级)。
|
|
275
|
+
*
|
|
276
|
+
* @param {string|null} mdText
|
|
277
|
+
* @returns {{ parseable: boolean, behavioral: string[], docs: string[], gate: string[] }}
|
|
278
|
+
*/
|
|
279
|
+
function classifyModuleImpactEvidence(mdText) {
|
|
280
|
+
const out = { parseable: false, behavioral: [], docs: [], gate: [] }
|
|
281
|
+
if (!mdText || !String(mdText).trim()) return out
|
|
282
|
+
const lines = normalizeLineEndings(String(mdText)).split('\n')
|
|
283
|
+
let section = null // 'matrix' | 'unmatched' | null
|
|
284
|
+
let matrixRows = 0
|
|
285
|
+
const consumeRow = (rowText) => {
|
|
286
|
+
const tokens = collectPathTokens(rowText)
|
|
287
|
+
let classified = 0
|
|
288
|
+
for (const t of tokens) {
|
|
289
|
+
if (isDocsImpactPath(t)) { out.docs.push(t); classified++ }
|
|
290
|
+
else if (isGateImpactPath(t)) { out.gate.push(t); classified++ }
|
|
291
|
+
else { out.behavioral.push(t); classified++ }
|
|
292
|
+
}
|
|
293
|
+
// 无路径 token 的行:落回影响类型信号词(如「| setup | 修改 | 枚举与新键 |」)
|
|
294
|
+
if (classified === 0) {
|
|
295
|
+
const tok = IMPACT_BEHAVIORAL_TOKENS.find((w) => rowText.includes(w))
|
|
296
|
+
if (tok) { out.behavioral.push(`(影响类型:${tok})`); classified++ }
|
|
297
|
+
}
|
|
298
|
+
// 矩阵行 <!--TODO--> 未回填 → 影响面未知,保守计行为类
|
|
299
|
+
if (String(rowText).includes('<!--TODO')) { out.behavioral.push('<!--TODO--> 未回填行'); classified++ }
|
|
300
|
+
return classified
|
|
301
|
+
}
|
|
302
|
+
for (const line of lines) {
|
|
303
|
+
const h = line.match(/^##+\s*(.+?)\s*$/)
|
|
304
|
+
if (h) {
|
|
305
|
+
const t = h[1]
|
|
306
|
+
section = t.includes('模块影响矩阵') ? 'matrix' : t.includes('未匹配文件') ? 'unmatched' : null
|
|
307
|
+
continue
|
|
308
|
+
}
|
|
309
|
+
if (!section) continue
|
|
310
|
+
const trimmed = line.trim()
|
|
311
|
+
if (section === 'matrix' && trimmed.startsWith('|')) {
|
|
312
|
+
const cells = line.split('|').slice(1, -1).map((c) => c.trim())
|
|
313
|
+
if (cells.length === 0) continue
|
|
314
|
+
if (cells.every((c) => /^[-:\s]*$/.test(c))) continue // 分隔行 |---|---|
|
|
315
|
+
if (cells[0] === '模块' || cells.includes('变更文件') || cells.includes('影响类型')) continue // 表头
|
|
316
|
+
matrixRows++
|
|
317
|
+
consumeRow(trimmed)
|
|
318
|
+
} else if (section === 'unmatched') {
|
|
319
|
+
// 骨架产出 `- \`path\` <!--TODO-->` 列表行;手写形态可能是 `| 文件 | 处置说明 |` 表行
|
|
320
|
+
const bullet = trimmed.match(/^-\s+(.+)$/)
|
|
321
|
+
if (bullet) { consumeRow(bullet[1]); continue }
|
|
322
|
+
if (trimmed.startsWith('|')) {
|
|
323
|
+
const cells = line.split('|').slice(1, -1).map((c) => c.trim())
|
|
324
|
+
if (cells.length === 0) continue
|
|
325
|
+
if (cells.every((c) => /^[-:\s]*$/.test(c))) continue
|
|
326
|
+
if (cells[0] === '文件') continue
|
|
327
|
+
consumeRow(trimmed)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
out.parseable = matrixRows > 0 && (out.behavioral.length + out.docs.length + out.gate.length) > 0
|
|
332
|
+
return out
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* resolveTestStrategy — test_strategy 解析 + evidence-auto 推荐统一入口
|
|
337
|
+
* (契约 test_strategy_resolution,2026-08-23 adopt-harness-practices task-11)。
|
|
338
|
+
*
|
|
339
|
+
* 非 evidence-auto(full/module/skip/未配置 null)→ 原样透传 strategy 且
|
|
340
|
+
* evidence_auto_recommendation=null(full/module/缺省消费路径逐字不变);
|
|
341
|
+
* evidence-auto → 按 module-impact.md 影响面解析生效策略 + 推荐检查组合:
|
|
342
|
+
* - 含行为类影响 → strategy='module'(聚焦测试)
|
|
343
|
+
* - 纯文档/prompt、门禁/契约影响(无行为类)→ strategy='skip'(测试不在推荐组合内)
|
|
344
|
+
* - module-impact.md 缺失/不可解析 → 降级 strategy='module' 且 degraded 注记
|
|
345
|
+
*
|
|
346
|
+
* @param {object} opts
|
|
347
|
+
* @param {string|null} opts.yamlText - local.yaml 全文(与 extractTestStrategy 同源)
|
|
348
|
+
* @param {string|null} [opts.changeDir] - 变更目录(定位 module-impact.md;evidence-auto 时缺省 → 降级)
|
|
349
|
+
* @param {string|null} [opts.moduleImpactText] - module-impact.md 文本(测试注入用;提供则不读盘)
|
|
350
|
+
* @returns {{ strategy: 'full'|'module'|'skip'|'evidence-auto'|null, evidence_auto_recommendation: object|null }}
|
|
351
|
+
* 下游契约:task-12 消费 evidence_auto_recommendation(run/prompt.js 注入),
|
|
352
|
+
* task-13 消费 strategy(语义回归锁定)。两字段为 provides 契约的全部内容。
|
|
353
|
+
*/
|
|
354
|
+
export function resolveTestStrategy({ yamlText, changeDir = null, moduleImpactText = null }) {
|
|
355
|
+
const configured = extractTestStrategy(yamlText ?? null)
|
|
356
|
+
if (configured !== 'evidence-auto') {
|
|
357
|
+
// full / module / skip / 未配置(null)——原样透传,不掺推荐语义
|
|
358
|
+
return { strategy: configured, evidence_auto_recommendation: null }
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let mdText = moduleImpactText
|
|
362
|
+
if (mdText == null && changeDir) {
|
|
363
|
+
const p = join(changeDir, 'module-impact.md')
|
|
364
|
+
try {
|
|
365
|
+
mdText = existsSync(p) ? readFileSync(p, 'utf8') : null
|
|
366
|
+
} catch { mdText = null }
|
|
367
|
+
}
|
|
368
|
+
const ev = classifyModuleImpactEvidence(mdText)
|
|
369
|
+
const sample = (arr) => arr.slice(0, 5).join('、') + (arr.length > 5 ? ` 等 ${arr.length} 处` : '')
|
|
370
|
+
|
|
371
|
+
if (!ev.parseable) {
|
|
372
|
+
const degradedReason = changeDir == null && moduleImpactText == null
|
|
373
|
+
? '未提供变更目录(changeDir),无法定位 module-impact.md'
|
|
374
|
+
: 'module-impact.md 缺失或不可解析(无「模块影响矩阵」数据行)'
|
|
375
|
+
const summary = [
|
|
376
|
+
'test_strategy: evidence-auto 推荐:',
|
|
377
|
+
`- ⚠️ 降级注记:${degradedReason}——已降级 module 策略(多测不漏测),推荐结果不可信时请显式设 test_strategy: full/module。`,
|
|
378
|
+
'- 可在 verify-result.md 否决本推荐并改跑全量(显式设 test_strategy: full)。',
|
|
379
|
+
].join('\n')
|
|
380
|
+
return {
|
|
381
|
+
strategy: 'module',
|
|
382
|
+
evidence_auto_recommendation: {
|
|
383
|
+
configured_strategy: 'evidence-auto',
|
|
384
|
+
resolved_strategy: 'module',
|
|
385
|
+
degraded: true,
|
|
386
|
+
degraded_reason: degradedReason,
|
|
387
|
+
impact: { behavioral: [], docs: [], gate: [] },
|
|
388
|
+
checks: [
|
|
389
|
+
{ kind: 'module-tests', reason: `${degradedReason} → 降级 test_strategy=module(多测不漏测)` },
|
|
390
|
+
],
|
|
391
|
+
summary,
|
|
392
|
+
},
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const checks = []
|
|
397
|
+
if (ev.behavioral.length > 0) {
|
|
398
|
+
checks.push({ kind: 'module-tests', reason: `行为类影响 ${ev.behavioral.length} 处(${sample(ev.behavioral)})→ test_strategy=module 聚焦测试` })
|
|
399
|
+
}
|
|
400
|
+
if (ev.docs.length > 0) {
|
|
401
|
+
checks.push({ kind: 'docs-check', reason: `文档/prompt 类影响 ${ev.docs.length} 处(${sample(ev.docs)})→ sillyspec docs check` })
|
|
402
|
+
}
|
|
403
|
+
if (ev.gate.length > 0) {
|
|
404
|
+
checks.push({ kind: 'gate', reason: `门禁/契约类影响 ${ev.gate.length} 处(${sample(ev.gate)})→ sillyspec gate` })
|
|
405
|
+
}
|
|
406
|
+
// 生效测试策略:有行为类影响 → module;纯文档/门禁面 → skip(测试不在推荐组合内)
|
|
407
|
+
const resolved = ev.behavioral.length > 0 ? 'module' : 'skip'
|
|
408
|
+
const summaryLines = ['test_strategy: evidence-auto 推荐(依据变更目录 module-impact.md 影响面):']
|
|
409
|
+
for (const c of checks) summaryLines.push(`- ${c.reason}`)
|
|
410
|
+
if (resolved === 'skip') {
|
|
411
|
+
summaryLines.push('- 测试不在推荐组合内(module-impact.md 无行为类影响)——CLI 实测将跳过;如需全量实测,可在 verify-result.md 否决本推荐并显式设 test_strategy: full。')
|
|
412
|
+
} else {
|
|
413
|
+
summaryLines.push('- 可在 verify-result.md 否决本推荐并改跑全量(显式设 test_strategy: full)。')
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
strategy: resolved,
|
|
417
|
+
evidence_auto_recommendation: {
|
|
418
|
+
configured_strategy: 'evidence-auto',
|
|
419
|
+
resolved_strategy: resolved,
|
|
420
|
+
degraded: false,
|
|
421
|
+
degraded_reason: null,
|
|
422
|
+
impact: { behavioral: ev.behavioral, docs: ev.docs, gate: ev.gate },
|
|
423
|
+
checks,
|
|
424
|
+
summary: summaryLines.join('\n'),
|
|
425
|
+
},
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
219
429
|
/**
|
|
220
430
|
* 从 local.yaml 文本解析 modules 映射块。
|
|
221
431
|
* 设计约定(见 change 2026-07-10-tooling-followups design.md #2 方案 A):
|
|
@@ -450,12 +660,15 @@ export function judgeWithKnownFailures(exitCode, output, baseReason, knownFailur
|
|
|
450
660
|
|
|
451
661
|
/**
|
|
452
662
|
* 决定 verify 实测的执行动作(纯函数,便于测试;坑 verify-worktree-... 修复方向 3)。
|
|
663
|
+
* - skip → 'skip'(真跳过,D-005@v2——不落 full 兜底;evidence-auto 经
|
|
664
|
+
* resolveTestStrategy 解析出生效 skip 时同走此动作)
|
|
453
665
|
* - module + 命中模块 → 'module-subset'
|
|
454
666
|
* - module + 0 命中 → 'module-zero-hit-skip'(不静默回退注定超时/预存失败的全量)
|
|
455
667
|
* - 其余(full / module 无块 / module git 不可用 hitCount=-1)→ 'full'
|
|
456
|
-
* @returns {'module-subset'|'module-zero-hit-skip'|'full'}
|
|
668
|
+
* @returns {'skip'|'module-subset'|'module-zero-hit-skip'|'full'}
|
|
457
669
|
*/
|
|
458
670
|
export function decideVerifyTestAction({ strategy, modulesPresent, hitCount }) {
|
|
671
|
+
if (strategy === 'skip') return 'skip'
|
|
459
672
|
if (strategy === 'module' && modulesPresent) {
|
|
460
673
|
if (hitCount > 0) return 'module-subset'
|
|
461
674
|
if (hitCount === 0) return 'module-zero-hit-skip'
|
|
@@ -563,6 +776,12 @@ function gitChangedFiles(cwd) {
|
|
|
563
776
|
return runGitDiffNameOnly(cwd, '')
|
|
564
777
|
}
|
|
565
778
|
|
|
779
|
+
// 他者声明归属过滤(坑 verify-reconcile-foreign-wip):实现在零环模块 foreign-declared.js
|
|
780
|
+
// (contract-matrix 被 verify-postcheck import,parity 侧不能反向 import 本模块),此处
|
|
781
|
+
// re-export 保持既有消费方路径不变
|
|
782
|
+
export { collectForeignDeclaredFiles, splitOwnVsForeignDiffFiles } from './foreign-declared.js'
|
|
783
|
+
import { splitOwnVsForeignDiffFiles } from './foreign-declared.js'
|
|
784
|
+
|
|
566
785
|
/**
|
|
567
786
|
* 解析 verify 对账用的变更文件集(worktree-aware)。
|
|
568
787
|
*
|
|
@@ -611,7 +830,7 @@ export function resolveVerifyChangedFiles(cwd, changeName, ctx = null, opts = {}
|
|
|
611
830
|
const wtGitDir = (meta.worktreePath && meta.mode !== 'in-place-fallback' && existsSync(meta.worktreePath))
|
|
612
831
|
? meta.worktreePath
|
|
613
832
|
: cwd
|
|
614
|
-
const wtStatus = gitQuiet(wtGitDir, ['status', '--porcelain'], { timeout: 30000, trim: false })
|
|
833
|
+
const wtStatus = gitQuiet(wtGitDir, ['status', '--porcelain', '--untracked-files=all'], { timeout: 30000, trim: false })
|
|
615
834
|
const wtFiles = String(wtStatus || '').split('\n')
|
|
616
835
|
.map(l => l.slice(3).trim().split(' -> ').pop() || '')
|
|
617
836
|
.map(p => p.replace(/^"|"$/g, '').replace(/\\/g, '/'))
|
|
@@ -678,7 +897,18 @@ function resolveMainChangedFiles(cwd, changeName) {
|
|
|
678
897
|
}
|
|
679
898
|
}
|
|
680
899
|
}
|
|
681
|
-
|
|
900
|
+
const fallback = gitChangedFiles(cwd)
|
|
901
|
+
// 无 meta 回退(主仓 HEAD diff 全量)撞并行会话在途 WIP 的归属过滤(坑
|
|
902
|
+
// verify-reconcile-foreign-wip):他者显式声明(quick --files / 他者 design 清单)的
|
|
903
|
+
// 文件剔除归他者——不混入本变更 module 命中。无主文件保留(fail-closed)。
|
|
904
|
+
if (fallback !== null && changeName && fallback.length > 0) {
|
|
905
|
+
const { own, foreign } = splitOwnVsForeignDiffFiles(cwd, changeName, fallback)
|
|
906
|
+
if (foreign.length > 0) {
|
|
907
|
+
console.warn(`⚠️ verify 对账已排除 ${foreign.length} 个并行会话声明的文件(不参与本变更判定):${foreign.slice(0, 5).map(x => `${x.file}←${x.owners[0]}`).join(', ')}${foreign.length > 5 ? ' 等' : ''}`)
|
|
908
|
+
return own
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return fallback
|
|
682
912
|
}
|
|
683
913
|
|
|
684
914
|
/**
|
|
@@ -715,9 +945,24 @@ export function runVerifyTestCheck({ cwd, specBase, changeName = null, ctx = nul
|
|
|
715
945
|
const localYamlPath = join(specBase, 'local.yaml')
|
|
716
946
|
const yamlText = existsSync(localYamlPath) ? readFileSync(localYamlPath, 'utf8') : null
|
|
717
947
|
|
|
718
|
-
const
|
|
948
|
+
const rawStrategy = extractTestStrategy(yamlText)
|
|
719
949
|
const knownFailures = extractKnownFailures(yamlText)
|
|
720
950
|
|
|
951
|
+
// —— evidence-auto 生效策略解析(D-005@v2 / task-11)——
|
|
952
|
+
// 按变更目录 module-impact.md 影响面取生效策略(行为→module、纯文档/门禁→skip、
|
|
953
|
+
// 缺失/不可解析→降级 module 并注记)再进既有链路;full/module/skip/缺省四路径
|
|
954
|
+
// 不经此分支(消费语义逐字不变)。
|
|
955
|
+
let strategy = rawStrategy
|
|
956
|
+
let evidenceAuto = null
|
|
957
|
+
if (rawStrategy === 'evidence-auto') {
|
|
958
|
+
const changeDir = changeName ? join(specBase, 'changes', changeName) : null
|
|
959
|
+
const resolution = resolveTestStrategy({ yamlText, changeDir })
|
|
960
|
+
strategy = resolution.strategy
|
|
961
|
+
evidenceAuto = resolution.evidence_auto_recommendation
|
|
962
|
+
}
|
|
963
|
+
// evidence-auto 解析为 module 后的 hint/reason 追加注记(full/module 路径为空串,输出逐字不变)
|
|
964
|
+
const eaNote = rawStrategy === 'evidence-auto' ? '(生效策略来自 test_strategy: evidence-auto 推荐)' : ''
|
|
965
|
+
|
|
721
966
|
// —— 模块子集路径(test_strategy: module):算 modulesPresent / hitCount / hits ——
|
|
722
967
|
// resolveVerifyChangedFiles 返回 null 表示 git 不可用 → hitCount=-1(与 0 命中区分)。
|
|
723
968
|
// 注:module 子集策略只用主仓 diff(跨仓仓不参与 module 子集,design §6 + §5.4),
|
|
@@ -745,7 +990,44 @@ export function runVerifyTestCheck({ cwd, specBase, changeName = null, ctx = nul
|
|
|
745
990
|
|
|
746
991
|
const action = decideVerifyTestAction({ strategy, modulesPresent, hitCount })
|
|
747
992
|
let mainResult
|
|
748
|
-
if (action === '
|
|
993
|
+
if (action === 'skip') {
|
|
994
|
+
// —— skip 真跳过(D-005@v2 / R-07)——
|
|
995
|
+
// 显式 skip:兑现声明语义,不回退全量;输出显式标注留审计痕迹(R-07 行为变化提示:
|
|
996
|
+
// 此前配置 skip 实际仍跑全量,本版本起真跳过)。
|
|
997
|
+
// evidence-auto→skip:测试不在推荐组合内(module-impact.md 无行为类影响),
|
|
998
|
+
// reason 附推荐依据与否决路径。
|
|
999
|
+
const reason = rawStrategy === 'skip'
|
|
1000
|
+
? '测试已按 test_strategy=skip 配置跳过(D-005@v2 兑现声明语义:不回退全量 commands.test)。⚠️ 行为变化提示(R-07):此前配置 skip 实际仍跑全量,本版本起真跳过——本次 verify 结论不含测试客观核验;如需恢复实测,把 local.yaml 的 test_strategy 改回 full/module。'
|
|
1001
|
+
: `测试已按 test_strategy=evidence-auto 推荐跳过:${evidenceAuto && evidenceAuto.summary ? evidenceAuto.summary : 'module-impact.md 判定无行为类影响'}。本次 verify 结论不含测试客观核验。`
|
|
1002
|
+
mainResult = {
|
|
1003
|
+
status: 'skipped',
|
|
1004
|
+
command: null,
|
|
1005
|
+
exitCode: null,
|
|
1006
|
+
durationMs: null,
|
|
1007
|
+
outputTail: null,
|
|
1008
|
+
reason,
|
|
1009
|
+
resultPath: null,
|
|
1010
|
+
mode: 'strategy-skip',
|
|
1011
|
+
fallbackReason: null,
|
|
1012
|
+
}
|
|
1013
|
+
// 审计痕迹落盘(R-07):skip 决策与依据写入 verify-runs 时间线(test-result.json),供追溯
|
|
1014
|
+
writeRunResult({
|
|
1015
|
+
specBase,
|
|
1016
|
+
changeName,
|
|
1017
|
+
result: mainResult,
|
|
1018
|
+
extra: {
|
|
1019
|
+
strategy: rawStrategy,
|
|
1020
|
+
...(evidenceAuto ? {
|
|
1021
|
+
evidence_auto: {
|
|
1022
|
+
resolved_strategy: evidenceAuto.resolved_strategy,
|
|
1023
|
+
degraded: evidenceAuto.degraded,
|
|
1024
|
+
degraded_reason: evidenceAuto.degraded_reason,
|
|
1025
|
+
checks: evidenceAuto.checks,
|
|
1026
|
+
},
|
|
1027
|
+
} : {}),
|
|
1028
|
+
},
|
|
1029
|
+
})
|
|
1030
|
+
} else if (action === 'module-subset') {
|
|
749
1031
|
mainResult = runModuleSubset({ cwd, specBase, changeName, hits, knownFailures })
|
|
750
1032
|
} else if (action === 'module-zero-hit-skip') {
|
|
751
1033
|
// module 模式 0 命中:不静默回退注定超时/含预存失败的全量(坑 verify-worktree-... 修复方向 3)。
|
|
@@ -767,7 +1049,7 @@ export function runVerifyTestCheck({ cwd, specBase, changeName = null, ctx = nul
|
|
|
767
1049
|
durationMs: null,
|
|
768
1050
|
outputTail: null,
|
|
769
1051
|
reason: 'test_strategy: module 但本次变更未命中任何已配置 modules(0 命中)。为避免回退到注定超时/含预存失败的全量 commands.test,CLI 未自动跑全量——据 verify-result.md 自报告判定测试。若需全量覆盖,显式设 test_strategy: full。' +
|
|
770
|
-
` 诊断:${diagLines.join(';')}
|
|
1052
|
+
` 诊断:${diagLines.join(';')}` + eaNote,
|
|
771
1053
|
resultPath: null,
|
|
772
1054
|
mode: 'module-zero-hit',
|
|
773
1055
|
fallbackReason: null,
|
|
@@ -775,7 +1057,9 @@ export function runVerifyTestCheck({ cwd, specBase, changeName = null, ctx = nul
|
|
|
775
1057
|
} else {
|
|
776
1058
|
// —— 全量路径(full / module 无块 / module git 不可用)——
|
|
777
1059
|
// fallbackReason 非 null 表示本次全量是"非显式"的(缺省/配置不全/未命中),需明示。
|
|
778
|
-
|
|
1060
|
+
// evidence-auto 解析为 module 后落全量兜底时追加推荐来源注记(eaNote;其余路径空串零变化)。
|
|
1061
|
+
let fallbackReason = computeFullFallbackReason({ strategy, modulesPresent, hitCount })
|
|
1062
|
+
if (fallbackReason && eaNote) fallbackReason = fallbackReason + eaNote
|
|
779
1063
|
mainResult = runFullCommand({ yamlText, localYamlPath, cwd, specBase, changeName, fallbackReason, knownFailures })
|
|
780
1064
|
}
|
|
781
1065
|
|
|
@@ -1154,7 +1438,13 @@ function writeRunResult({ specBase, changeName, result, extra = {} }) {
|
|
|
1154
1438
|
export function printVerifyTestCheck(result) {
|
|
1155
1439
|
if (result.status === 'skipped') {
|
|
1156
1440
|
console.warn(`\n⚠️ Verify 实测跳过:${result.reason}`)
|
|
1157
|
-
|
|
1441
|
+
if (result.mode === 'strategy-skip') {
|
|
1442
|
+
// R-07:skip 由 test_strategy 显式生效(skip 配置 / evidence-auto 推荐),非「未配命令」——
|
|
1443
|
+
// 不给「去配 commands.test」的误导建议,改标审计口径
|
|
1444
|
+
console.warn(' (跳过由 test_strategy 配置生效,本次 verify 结论不含测试客观核验;跳过依据见上方 reason 并已落盘 test-result.json 留审计痕迹。)')
|
|
1445
|
+
} else {
|
|
1446
|
+
console.warn(' 建议在 local.yaml 的 commands.test 配置真实测试命令,让 CLI 可客观对账。')
|
|
1447
|
+
}
|
|
1158
1448
|
return
|
|
1159
1449
|
}
|
|
1160
1450
|
if (result.status === 'passed') {
|
|
@@ -1279,9 +1569,21 @@ export function runVerifyDeletionCheck({ cwd, specBase, changeName = null }) {
|
|
|
1279
1569
|
|
|
1280
1570
|
// 排除交付物外文件(.sillyspec/ 变更包/运行时/quicklog + meta.json),避免污染删除信号。
|
|
1281
1571
|
// 复用 worktree-apply.js 的 filterDeliverableFiles 去双写(坑3:保留 .sillyspec/docs/)。
|
|
1282
|
-
|
|
1572
|
+
let deliverable = filterDeliverableFiles(deletions.map(d => d.path))
|
|
1283
1573
|
.map(p => deletions.find(d => d.path === p))
|
|
1284
1574
|
|
|
1575
|
+
// 他者声明归属过滤(坑 verify-reconcile-foreign-wip):主仓 HEAD diff 撞并行会话在途
|
|
1576
|
+
// WIP 时,他者显式声明(quick --files / 他者 design 清单)的删除不参与本变更删除对账
|
|
1577
|
+
// (否则「他者删的文件 × 本变更 design 三态」产出未声明删除误报)。无主删除保留(fail-closed)。
|
|
1578
|
+
if (changeName && deliverable.length > 0) {
|
|
1579
|
+
const { own, foreign } = splitOwnVsForeignDiffFiles(cwd, changeName, deliverable.map(d => d.path))
|
|
1580
|
+
if (foreign.length > 0) {
|
|
1581
|
+
console.warn(`⚠️ 删除对账已排除 ${foreign.length} 个并行会话声明的删除(${foreign.slice(0, 5).map(x => `${x.file}←${x.owners[0]}`).join(', ')}${foreign.length > 5 ? ' 等' : ''})`)
|
|
1582
|
+
const keep = new Set(own)
|
|
1583
|
+
deliverable = deliverable.filter(d => keep.has(d.path))
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1285
1587
|
if (deliverable.length === 0) {
|
|
1286
1588
|
return { status: 'skipped', highRisk: [], mediumRisk: [], compliant: [],
|
|
1287
1589
|
summary: '', reason: '本次变更无文件删除(或改动已被 commit,主仓 HEAD 已推进,删除对账无锚点)' }
|
package/src/verify-probes.js
CHANGED
|
@@ -20,12 +20,39 @@ import { gitQuiet } from './git-helper.js'
|
|
|
20
20
|
import { parseFileChangeListDetailed } from './change-list.js'
|
|
21
21
|
import { parseAllowedPaths } from './stages/plan-postcheck.js'
|
|
22
22
|
import { verifyApiParity } from './contract-matrix.js'
|
|
23
|
-
import {
|
|
23
|
+
import { splitOwnVsForeignDiffFiles } from './foreign-declared.js'
|
|
24
|
+
import { resolveSpecDir, resolveRuntimeRoot, detectWorktreeSpecDrift } from './run/shared.js'
|
|
24
25
|
|
|
25
26
|
const TODO_MARKER_RE = /尚未实现|TODO|FIXME|HACK|XXX/
|
|
26
27
|
const TEST_FILE_RE = /test|spec/i
|
|
27
28
|
const PROBE1_MAX_MATCHES = 200
|
|
28
29
|
|
|
30
|
+
/**
|
|
31
|
+
* verify-probes 的 spec 根解析(含 worktree 副本漂移锚定)。
|
|
32
|
+
*
|
|
33
|
+
* 坑 worktree-spec-artifact-misplace(2026-08-24 用户实证):在 worktree 内跑
|
|
34
|
+
* `verify-probes --init` 时 spec 根随 cwd 落进 worktree 的 .sillyspec checkout 副本——骨架
|
|
35
|
+
* 写进副本、主仓 verify gate 读不到,副本随 worktree 清理蒸发。与 run/command.js 对
|
|
36
|
+
* plan/execute/verify/archive 的漂移守卫同口径:命中副本自动锚回主仓后继续(不 exit);
|
|
37
|
+
* 平台模式 / 显式 --spec-dir 已明确指定,不纠正。
|
|
38
|
+
* @param {string} cwd
|
|
39
|
+
* @param {string|null} [specDir] --spec-dir(显式指定则不锚定)
|
|
40
|
+
* @param {string|null} [platformBase] 平台 spec 根(仅 .sillyspec-platform.json pointer 存在时传入;
|
|
41
|
+
* resolvePlatformSpecDir 无 pointer 时回退本地解析会返回 worktree 副本根,不能当平台根传)
|
|
42
|
+
* @returns {string} spec 根绝对路径(漂移时为主仓)
|
|
43
|
+
*/
|
|
44
|
+
export function resolveVerifyProbesSpecBase(cwd, specDir = null, platformBase = null) {
|
|
45
|
+
if (platformBase) return platformBase
|
|
46
|
+
const base = resolveSpecDir(cwd, { specDir: specDir || undefined })
|
|
47
|
+
if (specDir) return base
|
|
48
|
+
const wt = detectWorktreeSpecDrift(base)
|
|
49
|
+
if (wt) {
|
|
50
|
+
console.warn(`⚠️ 已自动锚定主仓 spec:${wt.mainSpecBase}(原 cwd 命中 worktree 副本 ${wt.changeName},verify-probes 产物落主仓,已纠正,流程继续)`)
|
|
51
|
+
return wt.mainSpecBase
|
|
52
|
+
}
|
|
53
|
+
return base
|
|
54
|
+
}
|
|
55
|
+
|
|
29
56
|
function listTaskIds(tasksPath) {
|
|
30
57
|
if (!existsSync(tasksPath)) return []
|
|
31
58
|
const ids = []
|
|
@@ -67,7 +94,7 @@ function findTestFiles(rootDir, cwd, cap = 10) {
|
|
|
67
94
|
* @returns {{ probe1: object, probe3: object, probe5: object, probe6: object }}
|
|
68
95
|
*/
|
|
69
96
|
export function runVerifyProbes({ cwd, changeName, specDir = null }) {
|
|
70
|
-
const specBase =
|
|
97
|
+
const specBase = resolveVerifyProbesSpecBase(cwd, specDir)
|
|
71
98
|
const changeDir = join(specBase, 'changes', changeName)
|
|
72
99
|
const designPath = join(changeDir, 'design.md')
|
|
73
100
|
const detailed = existsSync(designPath) ? parseFileChangeListDetailed(designPath) : []
|
|
@@ -127,8 +154,20 @@ export function runVerifyProbes({ cwd, changeName, specDir = null }) {
|
|
|
127
154
|
|
|
128
155
|
// ── 探针 6:代码删除对账(git diff --name-status HEAD 的 D/R × design 声明三态)──
|
|
129
156
|
const probe6 = { deletions: [], note: '以 git 事实为准(真实 > 声明);是否 FAIL blocker 由 agent 诚实判定' }
|
|
157
|
+
// 他者声明归属过滤(坑 verify-reconcile-foreign-wip):并行会话在途删除/改动不进本变更对账
|
|
130
158
|
const nsOut = gitQuiet(cwd, ['diff', '--name-status', 'HEAD']) || ''
|
|
131
|
-
|
|
159
|
+
let nsRows = nsOut.split('\n').filter(Boolean)
|
|
160
|
+
{
|
|
161
|
+
const rawTargets = nsRows.map(row => ((row.split('\t')[1]) || ''))
|
|
162
|
+
.filter(Boolean).map(t => t.split('\\').join('/'))
|
|
163
|
+
const { foreign } = splitOwnVsForeignDiffFiles(cwd, changeName, rawTargets)
|
|
164
|
+
if (foreign.length > 0) {
|
|
165
|
+
const foreignSet = new Set(foreign.map(x => x.file))
|
|
166
|
+
nsRows = nsRows.filter(row => !foreignSet.has((row.split('\t')[1] || '').split('\\').join('/')))
|
|
167
|
+
console.warn(`⚠️ 探针6 已排除 ${foreign.length} 个并行会话声明的删除/改动(${foreign.slice(0, 5).map(x => x.file).join(', ')}${foreign.length > 5 ? ' 等' : ''})`)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const row of nsRows) {
|
|
132
171
|
const [status, ...paths] = row.split('\t')
|
|
133
172
|
const st = (status || '').trim().toUpperCase()
|
|
134
173
|
if (!/^[DRC]/.test(st)) continue
|
|
@@ -191,6 +230,9 @@ export function renderVerifyProbesReport(result) {
|
|
|
191
230
|
|
|
192
231
|
L.push('#### 探针 5:API Contract Parity')
|
|
193
232
|
L.push(`- ${probe5.summary || `backend ${probe5.backendCount ?? 0} 端点 / frontend ${probe5.frontendCount ?? 0} 调用`}`)
|
|
233
|
+
if ((probe5.scanRoots || []).length > 1) {
|
|
234
|
+
L.push(`- ℹ️ 后端端点比对集为多根并集(主仓既有 ∪ worktree 新增 ∪ 存量 artifact),共扫 ${probe5.scanRoots.length} 个根`)
|
|
235
|
+
}
|
|
194
236
|
if ((probe5.missingBackend || []).length > 0) {
|
|
195
237
|
L.push('')
|
|
196
238
|
L.push('| 状态 | 前端调用 | 后端端点 | 文件 |')
|
|
@@ -251,7 +293,7 @@ export function generateVerifyResultSkeleton(result) {
|
|
|
251
293
|
'<!--TODO: TODO/FIXME/HACK 统计(探针 1 的命中已预填在上方探针结果)-->',
|
|
252
294
|
'',
|
|
253
295
|
'## 变更风险等级',
|
|
254
|
-
'<!--TODO: doc-only / unit-sufficient / contract-required / integration-critical / deployment-critical;若 design.md frontmatter 有 risk_level 显式声明,写明「显式声明 = <等级>」+
|
|
296
|
+
'<!--TODO: doc-only / unit-sufficient / contract-required / integration-critical / deployment-critical;若 design.md frontmatter 有 risk_level 显式声明,写明「显式声明 = <等级>」+ 理由;若有命中被同句否定语境抑制(如「不新增 daemon 协议」),写明被抑制关键词与理由(抑制可审计,不许用来静默降级)-->',
|
|
255
297
|
'',
|
|
256
298
|
'## Runtime Evidence',
|
|
257
299
|
'<!--TODO: 关键命令输出/时间戳/commit hash 证据链;integration/deployment-critical 必填,按实际触碰的运行时组件写(启动命令/端点/请求响应/日志片段/生命周期终态断言/失败模式排除),未涉及的行写「不涉及」-->',
|