sillyspec 3.27.1 → 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/README.md +1 -1
- 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 +803 -0
- package/src/change-list.js +22 -2
- package/src/change-risk-profile.js +83 -14
- package/src/config-schema.js +18 -3
- package/src/contract-matrix.js +153 -8
- 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 +99 -10
- 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 +61 -7
- package/src/run/command.js +139 -20
- package/src/run/complete-handlers.js +48 -19
- package/src/run/complete.js +43 -3
- package/src/run/gates.js +87 -22
- package/src/run/prompt.js +84 -42
- package/src/run/quick-audit.js +28 -0
- package/src/run/shared.js +176 -11
- package/src/run/stage.js +23 -0
- 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 +33 -6
- package/src/stages/plan-postcheck.js +79 -18
- package/src/stages/plan.js +4 -0
- package/src/stages/quick.js +6 -1
- package/src/stages/verify.js +31 -14
- package/src/sync.js +94 -2
- 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 +69 -0
- package/src/worktree.js +201 -58
- package/templates/prompts/taskcard-rules.md +1 -0
package/src/change-list.js
CHANGED
|
@@ -18,6 +18,22 @@ export function normalizePath(raw) {
|
|
|
18
18
|
.trim()
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* 组合路径单元格拆分(坑 design-combined-cell-mismatch,2026-08-22 实证:"router.py + service.py"
|
|
23
|
+
* 一行两文件整体当单路径,字面匹配对账不过)。拆分符:+ 、 / | ; ;——每 token 独立校验
|
|
24
|
+
* (looksLikePath 不过的丢弃,防自由文本混入);无分隔符返回单元素数组(零变化)。
|
|
25
|
+
* 不含空白要求("a.py+b.py" 与 "a.py + b.py" 都拆)。
|
|
26
|
+
* @param {string} cell 路径单元格(normalizePath 后)
|
|
27
|
+
* @returns {string[]}
|
|
28
|
+
*/
|
|
29
|
+
function splitCombinedPaths(cell) {
|
|
30
|
+
const raw = String(cell || '').trim()
|
|
31
|
+
if (!raw) return []
|
|
32
|
+
const tokens = raw.split(/\s*[+|;;、]\s*|//).map(t => t.trim()).filter(Boolean)
|
|
33
|
+
const valid = tokens.filter(t => !isPlaceholder(t) && looksLikePath(t) && !t.startsWith('.sillyspec/'))
|
|
34
|
+
return valid.length > 0 ? valid : (looksLikePath(raw) ? [raw] : [])
|
|
35
|
+
}
|
|
36
|
+
|
|
21
37
|
/**
|
|
22
38
|
* glob → 正则:双星(globstar)跨零或多段目录(含零段,bash 语义)、
|
|
23
39
|
* 单星 → 单段([^/]*)、问号 → 单字符([^/]),其余转义。
|
|
@@ -226,8 +242,12 @@ function _parseFileListDetailed(designMdPath, { keepSillyspecDocs = false } = {}
|
|
|
226
242
|
// incidental:说明列(非路径列的所有 cell)+ 路径 cell 原始值(剥注释前的括号内容)
|
|
227
243
|
const incidental = cells.some((c, i) => i !== pathColIdx && INCIDENTAL_RE.test(c))
|
|
228
244
|
|| INCIDENTAL_RE.test(cells[pathColIdx] || '')
|
|
229
|
-
|
|
230
|
-
|
|
245
|
+
// 组合路径单元格拆分(坑 design-combined-cell-mismatch,2026-08-22 实证:单元格写
|
|
246
|
+
// "router.py + service.py" 一行两文件——整体当单路径字面匹配对账不过,逼文档写法服务
|
|
247
|
+
// 校验器)。拆分符:+ / / / | / 、 / ; / ;——每个 token 独立过校验入表;单路径零变化。
|
|
248
|
+
const pathTokens = splitCombinedPaths(filePath)
|
|
249
|
+
if (listMode === 'exclude') { for (const t of pathTokens) entries.delete(t); continue }
|
|
250
|
+
for (const t of pathTokens) entries.set(t, { path: t, operation, incidental })
|
|
231
251
|
continue
|
|
232
252
|
}
|
|
233
253
|
|
|
@@ -122,7 +122,8 @@ export const RISK_LEVEL_CAUSES = {
|
|
|
122
122
|
'design.md / plan.md 命中启动入口关键词(cli.ts / main.ts / server.(js|ts) / bootstrap / entrypoint)。' +
|
|
123
123
|
'注意:这是按 design/plan 里的措辞判定的,不一定代表你真改了启动入口;若属误判可在 design.md frontmatter 用 risk_level 显式声明真实等级(如 unit-sufficient)覆盖——不要靠删措辞绕门控,危险链路该有真实启动证据。',
|
|
124
124
|
'integration-critical':
|
|
125
|
-
'design.md / plan.md 命中跨进程/状态机关键词(daemon / backend / session / lease / lifecycle / heartbeat 等)。'
|
|
125
|
+
'design.md / plan.md 命中跨进程/状态机关键词(daemon / backend / session / lease / lifecycle / heartbeat 等)。' +
|
|
126
|
+
'关键词前方同句有否定提示(不/未/无/避免…)的命中已被抑制不参与判级——若本变更实际涉及跨进程/状态机改动,请在 design.md frontmatter 用 risk_level 声明真实等级。',
|
|
126
127
|
'contract-required': 'design.md / plan.md 命中 API contract 关键词(api / client / contract / dto)。',
|
|
127
128
|
'explicit': 'design.md frontmatter 的 risk_level 显式声明(覆盖关键词判级)。',
|
|
128
129
|
}
|
|
@@ -130,6 +131,69 @@ export const RISK_LEVEL_CAUSES = {
|
|
|
130
131
|
/** design.md frontmatter 可显式声明的合法 risk_level 值(与 detectChangeRisk 判级结果同集合) */
|
|
131
132
|
const RISK_LEVELS = ['doc-only', 'unit-sufficient', 'contract-required', 'integration-critical', 'deployment-critical']
|
|
132
133
|
|
|
134
|
+
// ============ 同句否定抑制(坑 risk-negation-blindness) ============
|
|
135
|
+
//
|
|
136
|
+
// detectChangeRisk 原为全文 blob 字面命中:design 写「本次不新增 daemon 协议」仍判
|
|
137
|
+
// integration-critical、强制全套集成证据,对无状态后端变更误伤(用户实证 2026-08-24)。
|
|
138
|
+
// 机械抑制规则(不做事语义理解,三段判定):
|
|
139
|
+
// ① 子句切分:按 句号/分号/逗号/换行 切子句,否定只在同子句内生效——「不改动 A。新增 B」
|
|
140
|
+
// 的 B 不继承否定;
|
|
141
|
+
// ② 直接否定:命中位置前方 NEGATION_WINDOW 字符内出现否定提示 → 抑制;
|
|
142
|
+
// ③ 枚举继承:「不改动 daemon / session / lifecycle」中后位关键词与前位被抑制关键词之间
|
|
143
|
+
// 只隔枚举连接符(空格 / 、 , 和 与 及 and or)→ 继承抑制——纯距离窗口盖不住长枚举,
|
|
144
|
+
// 而「不涉及 daemon 的情况下重构 session」的间隔是普通文字,不继承(session 正常命中)。
|
|
145
|
+
// 同一关键词的**全部**命中都被抑制才不参与判级(任一处未抑制命中仍算数)。降级不静默:
|
|
146
|
+
// design/verify 两道 gate 会把被抑制词打进 warning(可审计),design.md frontmatter
|
|
147
|
+
// risk_level 仍是双向权威覆盖通道(声明优先级最高,行为不变)。
|
|
148
|
+
//
|
|
149
|
+
// 否定提示词排除假阳:不同/不断/不久/不时/不仅(「不」的非否定合成词)、无状态/无法
|
|
150
|
+
// (「无状态后端」是常见肯定表述)、非常/非空/非法(「非空校验 session」是在改 session)。
|
|
151
|
+
const NEGATION_CUES = /不(?!同|断|久|时|仅|妨)|未|无(?!状态|法)|非(?!常|空|法)|避免|排除|无需|不再|no\s+new|not\b|don'?t|without|avoid/i
|
|
152
|
+
const NEGATION_WINDOW = 16
|
|
153
|
+
const CLAUSE_SPLIT_RE = /[。;;,,\n]/
|
|
154
|
+
const ENUM_GAP_RE = /^[\s/|、,,]*(?:(?:和|与|及|and|or)[\s/|、,,]*)*$/i
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 单行内统计各关键词命中(同子句否定窗口 + 枚举继承)。
|
|
158
|
+
* @returns {Map<string, {kept: number, suppressed: number}>} trigger 原文 → 命中统计
|
|
159
|
+
*/
|
|
160
|
+
function collectLineTriggerStats(line, patterns, stats = new Map()) {
|
|
161
|
+
for (const pattern of patterns) {
|
|
162
|
+
const re = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : pattern.flags + 'g')
|
|
163
|
+
let m
|
|
164
|
+
while ((m = re.exec(line)) !== null) {
|
|
165
|
+
if (m[0].length === 0) { re.lastIndex++; continue }
|
|
166
|
+
// 行内绝对偏移 → 子句内相对偏移(子句内可能有多命中,逐一判定)
|
|
167
|
+
const clauseStart = line.slice(0, m.index).split(CLAUSE_SPLIT_RE).pop().length
|
|
168
|
+
const clauseStartAbs = m.index - clauseStart
|
|
169
|
+
const clause = line.slice(clauseStartAbs).split(CLAUSE_SPLIT_RE)[0]
|
|
170
|
+
const relIndex = m.index - clauseStartAbs
|
|
171
|
+
const prefix = clause.slice(0, relIndex)
|
|
172
|
+
let negated = NEGATION_CUES.test(prefix.slice(-NEGATION_WINDOW))
|
|
173
|
+
if (!negated && prefix.length > 0) {
|
|
174
|
+
// 枚举继承:查同子句内更早的关键词命中,且间隔只有枚举连接符
|
|
175
|
+
for (const other of patterns) {
|
|
176
|
+
const ore = new RegExp(other.source, other.flags.includes('g') ? other.flags : other.flags + 'g')
|
|
177
|
+
let om
|
|
178
|
+
while ((om = ore.exec(clause)) !== null) {
|
|
179
|
+
if (om[0].length === 0) { ore.lastIndex++; continue }
|
|
180
|
+
if (om.index >= relIndex) break
|
|
181
|
+
const gap = clause.slice(om.index + om[0].length, relIndex)
|
|
182
|
+
const prevSuppressed = NEGATION_CUES.test(clause.slice(0, om.index).slice(-NEGATION_WINDOW))
|
|
183
|
+
if (prevSuppressed && ENUM_GAP_RE.test(gap)) { negated = true; break }
|
|
184
|
+
}
|
|
185
|
+
if (negated) break
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const stat = stats.get(m[0]) || { kept: 0, suppressed: 0 }
|
|
189
|
+
if (negated) stat.suppressed++
|
|
190
|
+
else stat.kept++
|
|
191
|
+
stats.set(m[0], stat)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return stats
|
|
195
|
+
}
|
|
196
|
+
|
|
133
197
|
/**
|
|
134
198
|
* 从 design.md 顶部 frontmatter 提取显式 risk_level 声明。
|
|
135
199
|
* 只认文档最开头 `---\n...\n---` 块内的 `risk_level: <level>` 单行(与 plan.md 的 plan_level 同款解析),
|
|
@@ -148,10 +212,11 @@ export function detectChangeRisk({ designContent = '', planContent = '', changed
|
|
|
148
212
|
const triggers = []
|
|
149
213
|
|
|
150
214
|
// ── 显式豁免优先:design.md frontmatter 声明 risk_level → 以声明为准,跳过关键词判级 ──
|
|
151
|
-
// 历史教训:detectChangeRisk
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
215
|
+
// 历史教训:detectChangeRisk 是机械字面匹配——design 写「本次不改动 daemon/session」曾命中关键词
|
|
216
|
+
// 被误判 integration/deployment-critical,强制要全套集成证据,对无状态后端变更误伤。现给两条通道:
|
|
217
|
+
// ① 同句否定抑制(上方 NEGATION_CUES,机械可审计、只认「关键词前方否定窗口」不做语义理解);
|
|
218
|
+
// ② frontmatter 显式声明(显式、诚实、可审计,落在 design frontmatter + verify-result)——本分支。
|
|
219
|
+
// 声明仍走门控(结论 FAIL 仍拦),但豁免级(unit-sufficient 等)不再强制集成证据。
|
|
155
220
|
const explicit = extractExplicitRiskLevel(designContent)
|
|
156
221
|
if (explicit) {
|
|
157
222
|
const requiredVerification =
|
|
@@ -160,17 +225,20 @@ export function detectChangeRisk({ designContent = '', planContent = '', changed
|
|
|
160
225
|
explicit === 'contract-required' ? ['unit_tests', 'contract_tests'] :
|
|
161
226
|
explicit === 'doc-only' ? ['static_check'] :
|
|
162
227
|
['unit_tests']
|
|
163
|
-
return { level: explicit, triggers: ['risk_level (explicit)'], requiredVerification, explicit: true }
|
|
228
|
+
return { level: explicit, triggers: ['risk_level (explicit)'], suppressedTriggers: [], requiredVerification, explicit: true }
|
|
164
229
|
}
|
|
165
230
|
|
|
166
231
|
const combined = [designContent, planContent].join('\n')
|
|
167
232
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
233
|
+
// 逐行收集命中:同句否定窗口内的命中进 suppressed(全部命中均被抑制才剔除该 trigger)
|
|
234
|
+
const stats = new Map()
|
|
235
|
+
for (const line of combined.split('\n')) {
|
|
236
|
+
collectLineTriggerStats(line, INTEGRATION_CRITICAL_PATTERNS, stats)
|
|
237
|
+
}
|
|
238
|
+
const suppressedTriggers = []
|
|
239
|
+
for (const [t, stat] of stats) {
|
|
240
|
+
if (stat.kept > 0) triggers.push(t)
|
|
241
|
+
else suppressedTriggers.push(t)
|
|
174
242
|
}
|
|
175
243
|
|
|
176
244
|
for (const file of changedFiles) {
|
|
@@ -184,7 +252,8 @@ export function detectChangeRisk({ designContent = '', planContent = '', changed
|
|
|
184
252
|
}
|
|
185
253
|
|
|
186
254
|
if (triggers.length === 0) {
|
|
187
|
-
|
|
255
|
+
// 关键词命中全被否定语境抑制(或本就无命中)→ doc-only;抑制词随 suppressedTriggers 上报可审计
|
|
256
|
+
return { level: 'doc-only', triggers: [], suppressedTriggers, requiredVerification: ['static_check'] }
|
|
188
257
|
}
|
|
189
258
|
|
|
190
259
|
const deploymentTrigger = triggers.some(t => /cli\.ts|main\.ts|server\.(js|ts)|bootstrap|entrypoint/i.test(t))
|
|
@@ -207,7 +276,7 @@ export function detectChangeRisk({ designContent = '', planContent = '', changed
|
|
|
207
276
|
level = 'unit-sufficient'
|
|
208
277
|
}
|
|
209
278
|
|
|
210
|
-
return { level, triggers, requiredVerification }
|
|
279
|
+
return { level, triggers, suppressedTriggers, requiredVerification }
|
|
211
280
|
}
|
|
212
281
|
|
|
213
282
|
/**
|
package/src/config-schema.js
CHANGED
|
@@ -115,9 +115,9 @@ export const LOCAL_YAML_SCHEMA = {
|
|
|
115
115
|
{
|
|
116
116
|
id: 'test_strategy',
|
|
117
117
|
title: '测试策略',
|
|
118
|
-
note: 'verify 阶段 CLI
|
|
118
|
+
note: 'verify 阶段 CLI 对账的收窄策略(D-005@v2:skip 接线兑现 + evidence-auto 新增,full/module 语义不变)。',
|
|
119
119
|
keys: [
|
|
120
|
-
{ path: 'test_strategy', type: 'enum', values: ['full', 'module'], optional: true, status: 'live', readers: ['extractTestStrategy (src/verify-postcheck.js)'], desc: 'full=全量 commands.test;module=按 git diff 命中 modules 子集收窄(需配 modules
|
|
120
|
+
{ path: 'test_strategy', type: 'enum', values: ['full', 'module', 'skip', 'evidence-auto'], optional: true, status: 'live', readers: ['extractTestStrategy (src/verify-postcheck.js)', 'resolveTestStrategy (src/verify-postcheck.js)'], desc: 'full=全量 commands.test;module=按 git diff 命中 modules 子集收窄(需配 modules);skip=真跳过测试(不回退全量,verify 输出显式标注留审计痕迹,R-07);evidence-auto=按变更目录 module-impact.md 影响类型推荐检查组合(行为→module 聚焦测试、文档/prompt→docs-check、门禁契约→gate;缺失/不可解析降级 module 并注记)。缺省=全量。', example: 'full' },
|
|
121
121
|
],
|
|
122
122
|
},
|
|
123
123
|
{
|
|
@@ -157,6 +157,14 @@ export const LOCAL_YAML_SCHEMA = {
|
|
|
157
157
|
{ path: 'docs-check.cross_repo_roots', type: 'object', optional: true, status: 'live', readers: ['readDocsCheckConfig (src/docs-check.js)'], desc: 'repo://<仓库名> 跨仓引用 → 本机仓库根目录映射。文档里 repo://name/src/x.js:12 形态的引用:未配映射默认跳过(不同设备仓库位置不同,防跨设备误报);配了映射走与本地引用相同的行号+关键词校验。local.yaml 是 gitignored,绝对路径不入库,每台设备各自配。', example: 'sillyspec: /c/Users/you/IdeaProjects/sillyspec' },
|
|
158
158
|
],
|
|
159
159
|
},
|
|
160
|
+
{
|
|
161
|
+
id: 'decisions',
|
|
162
|
+
title: '决策库复核阈值',
|
|
163
|
+
note: 'docs-check 决策规则(W1.1 behind 复核)的消费配置——决策锚定模块源码在「最近确认」后前进超阈值 → doctor 报「决策待复核」(advisory 不阻断)。读键按「存在则读、不存在用缺省」容错。',
|
|
164
|
+
keys: [
|
|
165
|
+
{ path: 'decisions.behind_threshold', type: 'integer', optional: true, status: 'live', readers: ['readDecisionRulesConfig (src/docs-check.js)'], desc: '决策 behind 复核阈值(锚定模块源码在「最近确认」commit 后的前进数),缺省 10;超阈报「决策待复核」提示。', example: '10' },
|
|
166
|
+
],
|
|
167
|
+
},
|
|
160
168
|
],
|
|
161
169
|
};
|
|
162
170
|
|
|
@@ -276,9 +284,16 @@ modules:
|
|
|
276
284
|
frontend: { path: "frontend/", test: "cd frontend && pnpm test" }
|
|
277
285
|
backend: { path: "backend/", test: "cd backend && npm test" }
|
|
278
286
|
|
|
279
|
-
# ──
|
|
287
|
+
# ── 测试策略(verify 实测收窄;full/module 语义不变,skip/evidence-auto 为 2026-08-23 新增)──
|
|
288
|
+
# full=全量 commands.test | module=按命中模块收窄(需配 modules)
|
|
289
|
+
# skip=真跳过测试(不回退全量,verify 输出显式标注留审计痕迹)
|
|
290
|
+
# evidence-auto=按变更 module-impact.md 影响面推荐检查组合(行为→module 聚焦测试、文档→docs-check、门禁→gate;缺失降级 module)
|
|
280
291
|
test_strategy: full
|
|
281
292
|
|
|
293
|
+
# ── 决策库 behind 复核阈值(docs-check 决策规则:源码在「最近确认」后前进超阈值 → 待复核提示;缺省 10)──
|
|
294
|
+
decisions:
|
|
295
|
+
behind_threshold: 10
|
|
296
|
+
|
|
282
297
|
# ── 预存失败豁免清单(变更前就失败的测试行;务必定期复核,防误豁免真实失败)──
|
|
283
298
|
known_failures:
|
|
284
299
|
- tests/some-old-stable-failure
|
package/src/contract-matrix.js
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs'
|
|
12
|
-
import { join, resolve, basename, relative } from 'path'
|
|
12
|
+
import { join, resolve, basename, dirname, relative } from 'path'
|
|
13
|
+
import { gitQuiet } from './git-helper.js'
|
|
14
|
+
import { splitOwnVsForeignDiffFiles } from './foreign-declared.js'
|
|
13
15
|
import {
|
|
14
16
|
scanBackendEndpoints,
|
|
15
17
|
scanFrontendApiCalls,
|
|
@@ -332,6 +334,74 @@ export function buildContractFieldInjection(changeDir, taskName) {
|
|
|
332
334
|
|
|
333
335
|
// ─── Verify 阶段:parity check ──────────────────────────────────────────
|
|
334
336
|
|
|
337
|
+
/**
|
|
338
|
+
* 读 worktree meta(parity 双根扫描 / diff 收窄共用)。
|
|
339
|
+
* 优先 specBase/.runtime/worktrees/<change>/meta.json(主仓 spec 根——cwd=worktree 时 specBase 已被
|
|
340
|
+
* command.js 漂移守卫锚定主仓;旧实现硬编码 scanRoot/.sillyspec/.runtime,worktree 内运行永远读不到
|
|
341
|
+
* meta);兼容回退 scanRoot/.sillyspec/.runtime(旧路径)。
|
|
342
|
+
* @returns {{ meta: object, gitDir: string }|null} gitDir = diff/扫描用 git 根(真实 worktree 或 scanRoot)
|
|
343
|
+
*/
|
|
344
|
+
function _readWorktreeMeta(specBase, scanRoot, changeName) {
|
|
345
|
+
if (!changeName) return null
|
|
346
|
+
const candidates = [
|
|
347
|
+
specBase ? join(specBase, '.runtime', 'worktrees', changeName, 'meta.json') : null,
|
|
348
|
+
join(scanRoot, '.sillyspec', '.runtime', 'worktrees', changeName, 'meta.json'),
|
|
349
|
+
].filter(Boolean)
|
|
350
|
+
for (const metaPath of candidates) {
|
|
351
|
+
try {
|
|
352
|
+
if (!existsSync(metaPath)) continue
|
|
353
|
+
const meta = JSON.parse(readFileSync(metaPath, 'utf8'))
|
|
354
|
+
const gitDir = (meta.worktreePath && meta.mode !== 'in-place-fallback' && existsSync(meta.worktreePath))
|
|
355
|
+
? meta.worktreePath : scanRoot
|
|
356
|
+
return { meta, gitDir }
|
|
357
|
+
} catch { /* 损坏 meta 试下一候选 */ }
|
|
358
|
+
}
|
|
359
|
+
return null
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* 取本变更 diff 文件集(parity 前端调用收窄用,坑 probe5-fullrepo-frontend-noise)。
|
|
364
|
+
* 优先 worktree meta 锚点 diff(与 verify 同源);无 meta 退主仓 git diff HEAD。
|
|
365
|
+
* @returns {string[]|null}
|
|
366
|
+
*/
|
|
367
|
+
function _resolveDiffFilesForParity(specBase, scanRoot, changeName) {
|
|
368
|
+
try {
|
|
369
|
+
const wt = _readWorktreeMeta(specBase, scanRoot, changeName)
|
|
370
|
+
if (wt) {
|
|
371
|
+
const diffBase = wt.meta.baselineCommit || wt.meta.actualBaseHash || wt.meta.baseHash
|
|
372
|
+
if (diffBase) {
|
|
373
|
+
const out = gitQuiet(wt.gitDir, ['diff', '--name-only', `${diffBase}..HEAD`], { timeout: 30000 })
|
|
374
|
+
if (out !== null) {
|
|
375
|
+
const files = out.split('\n').filter(Boolean)
|
|
376
|
+
// 并入 worktree 未提交(子代理默认不 commit 的形态,与 resolveVerifyChangedFiles 同口径)。
|
|
377
|
+
// -uall:porcelain 默认把整目录折叠成 `dir/` 一项,文件级匹配(前端调用过滤)会漏新目录
|
|
378
|
+
// 里的文件(坑 probe5-worktree-single-root-noise 实证:web/ 折叠 → feature.js 漏匹配)
|
|
379
|
+
try {
|
|
380
|
+
const wtStatus = gitQuiet(wt.gitDir, ['status', '--porcelain', '--untracked-files=all'], { timeout: 30000, trim: false })
|
|
381
|
+
for (const line of String(wtStatus || '').split('\n')) {
|
|
382
|
+
if (!line || line.length < 4) continue
|
|
383
|
+
const p = line.slice(3).trim().split(' -> ').pop() || ''
|
|
384
|
+
const clean = p.replace(/^"|"$/g, '')
|
|
385
|
+
if (clean) files.push(clean)
|
|
386
|
+
}
|
|
387
|
+
} catch {}
|
|
388
|
+
return [...new Set(files)]
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// 无 meta(brownfield/已 cleanup):主仓未提交 diff。他者声明归属过滤(坑
|
|
393
|
+
// verify-reconcile-foreign-wip):并行会话在途 WIP 的前端文件混入会产出「他者调用 ×
|
|
394
|
+
// 本变更端点」的 missingBackend 误报——剔除他者显式声明文件(fail-closed:无主保留)。
|
|
395
|
+
const out = gitQuiet(scanRoot, ['diff', '--name-only', 'HEAD'], { timeout: 30000 })
|
|
396
|
+
if (out === null) return null
|
|
397
|
+
const { own, foreign } = splitOwnVsForeignDiffFiles(scanRoot, changeName, out.split('\n').filter(Boolean))
|
|
398
|
+
if (foreign.length > 0) {
|
|
399
|
+
console.warn(`⚠️ parity 对账已排除 ${foreign.length} 个并行会话声明的文件(${foreign.slice(0, 5).map(x => x.file).join(', ')}${foreign.length > 5 ? ' 等' : ''})`)
|
|
400
|
+
}
|
|
401
|
+
return own
|
|
402
|
+
} catch { return null }
|
|
403
|
+
}
|
|
404
|
+
|
|
335
405
|
/**
|
|
336
406
|
* verify 阶段执行 API parity check
|
|
337
407
|
* @param {string} specBase - .sillyspec 目录
|
|
@@ -372,17 +442,92 @@ export function verifyApiParity(specBase, scanRoot, runtimeRoot, changeName = nu
|
|
|
372
442
|
return { ok: true, missingBackend: [], unusedBackend: [], summary: 'No scan root for parity check', backendCount: allProviderEndpoints.length, frontendCount: 0 }
|
|
373
443
|
}
|
|
374
444
|
|
|
375
|
-
|
|
376
|
-
|
|
445
|
+
// ── 双根并集现算(坑 probe5-worktree-single-root-noise,2026-08-24 用户实证:worktree 场景
|
|
446
|
+
// 只现算 scanRoot 单根,另一侧端点不在比对集——verify 从主仓跑(apply 前)漏 worktree 新增端点;
|
|
447
|
+
// verify 从 worktree 跑则既读不到 meta(旧路径硬编码 scanRoot/.sillyspec)也漏主仓既有 daemon
|
|
448
|
+
// 端点 → 前端调用全量误报 missing)。后端端点全集 = 主仓既有 ∪ worktree 新增 ∪ 存量 artifact:
|
|
449
|
+
// 现算根集 = [scanRoot, meta.worktreePath, 主仓根(cwd 自身在 linked worktree 内时经
|
|
450
|
+
// git-common-dir 解析)],逐根 scanBackendEndpoints 按 method+path 去重。
|
|
451
|
+
// 存量基线失配坑(probe5-endpoint-baseline-stale,2026-08-22)由本并集一并覆盖:现算当前
|
|
452
|
+
// 代码 ∪ 存量基线;unusedBackend 只对现算端点算(存量过期端点不再误报 unused)。
|
|
453
|
+
const wt = _readWorktreeMeta(specBase, scanRoot, changeName)
|
|
454
|
+
const liveRoots = []
|
|
455
|
+
const pushRoot = (root, label) => {
|
|
456
|
+
if (!root || !existsSync(root)) return
|
|
457
|
+
const abs = resolve(root)
|
|
458
|
+
if (liveRoots.some(r => resolve(r.root) === abs)) return
|
|
459
|
+
liveRoots.push({ root, label })
|
|
460
|
+
}
|
|
461
|
+
pushRoot(scanRoot, 'scan-root')
|
|
462
|
+
if (wt && wt.gitDir !== scanRoot) pushRoot(wt.gitDir, 'worktree')
|
|
463
|
+
// cwd 自身是 linked worktree 时补主仓根(主仓返回 .git → dirname=scanRoot 被 pushRoot 去重)
|
|
464
|
+
try {
|
|
465
|
+
const commonDir = gitQuiet(scanRoot, ['rev-parse', '--git-common-dir'])
|
|
466
|
+
if (commonDir) pushRoot(dirname(resolve(scanRoot, commonDir)), 'main')
|
|
467
|
+
} catch { /* 非 git 目录/解析失败:单根继续 */ }
|
|
468
|
+
|
|
469
|
+
const liveByRoot = []
|
|
470
|
+
for (const { root, label } of liveRoots) {
|
|
471
|
+
try { liveByRoot.push({ root, label, endpoints: scanBackendEndpoints(root) }) } catch { /* 单根失败不拖垮整体 */ }
|
|
472
|
+
}
|
|
473
|
+
const liveEndpoints = liveByRoot.flatMap(r => r.endpoints)
|
|
474
|
+
const liveKeys = new Set(liveEndpoints.map(e => `${e.method} ${e.path}`))
|
|
475
|
+
const mergedKeys = new Set(allProviderEndpoints.map(e => `${e.method} ${e.path}`))
|
|
476
|
+
const mergedProviderEndpoints = [...allProviderEndpoints]
|
|
477
|
+
for (const e of liveEndpoints) {
|
|
478
|
+
const key = `${e.method} ${e.path}`
|
|
479
|
+
if (!mergedKeys.has(key)) {
|
|
480
|
+
mergedKeys.add(key)
|
|
481
|
+
mergedProviderEndpoints.push(e)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ── 前端调用收窄到本变更 diff(坑 probe5-fullrepo-frontend-noise,2026-08-23 实证:
|
|
486
|
+
// 「全仓前端调用 × 本变更局部登记」口径下,未受本变更影响的存量调用对不上局部端点集
|
|
487
|
+
// → 143 个 missing 全是误报噪音)。比对口径 = 本变更 diff 文件内的调用 ∪ 全仓(仅当
|
|
488
|
+
// 端点登记源也是全仓时——无 changeName 的 CLI contractScan 场景)。diff 不可得时退回
|
|
489
|
+
// 全仓(不静默跳过对账)。
|
|
490
|
+
// 扫描根优先真实 worktree(坑 probe5-worktree-single-root-noise):apply 前新代码只在
|
|
491
|
+
// worktree,scanRoot(主仓 cwd)里是旧版本——读旧版会漏新调用/多已删调用。
|
|
492
|
+
let frontendCalls = []
|
|
493
|
+
let frontendScope = 'full-repo'
|
|
494
|
+
const frontendRoot = (wt && wt.gitDir !== scanRoot) ? wt.gitDir : scanRoot
|
|
495
|
+
const frontendRootLabel = frontendRoot === scanRoot ? 'scan-root' : 'worktree'
|
|
496
|
+
if (changeName) {
|
|
497
|
+
try {
|
|
498
|
+
const changed = _resolveDiffFilesForParity(specBase, scanRoot, changeName)
|
|
499
|
+
if (changed && changed.length > 0) {
|
|
500
|
+
const changedSet = new Set(changed.map(f => f.replace(/\\/g, '/')))
|
|
501
|
+
frontendCalls = scanFrontendApiCalls(frontendRoot).filter(c => {
|
|
502
|
+
const src = String(c.source || '').replace(/\\/g, '/')
|
|
503
|
+
return changedSet.has(src) || [...changedSet].some(cf => src.endsWith(cf) || cf.endsWith(src))
|
|
504
|
+
})
|
|
505
|
+
frontendScope = `change-diff (${changed.length} files @ ${frontendRootLabel})`
|
|
506
|
+
} else {
|
|
507
|
+
frontendCalls = scanFrontendApiCalls(frontendRoot)
|
|
508
|
+
}
|
|
509
|
+
} catch { frontendCalls = scanFrontendApiCalls(frontendRoot) }
|
|
510
|
+
} else {
|
|
511
|
+
frontendCalls = scanFrontendApiCalls(frontendRoot)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const { missingBackend, unusedBackend } = diffApiParity(frontendCalls, mergedProviderEndpoints)
|
|
515
|
+
// unusedBackend 收窄到现算端点(存量 artifact 的过期端点不参与——它不在当前代码里,
|
|
516
|
+
// 「前端未调用」可能是端点已被重构掉而非真泄漏)
|
|
517
|
+
const narrowedUnused = unusedBackend.filter(u => {
|
|
518
|
+
const key = `${u.method} ${u.path}`
|
|
519
|
+
return liveKeys.has(key) || liveEndpoints.length === 0
|
|
520
|
+
})
|
|
377
521
|
|
|
378
522
|
const ok = missingBackend.length === 0
|
|
523
|
+
const liveRootSummary = liveByRoot.map(r => `${r.label} ${r.endpoints.length}`).join(' + ')
|
|
379
524
|
let summary = ok
|
|
380
|
-
? `✅ API parity check passed: ${
|
|
381
|
-
: `❌ API parity check failed: ${missingBackend.length} frontend calls have no matching backend endpoint`
|
|
525
|
+
? `✅ API parity check passed: ${mergedProviderEndpoints.length} backend endpoints (live [${liveRootSummary}] + artifact ${allProviderEndpoints.length}), ${frontendCalls.length} frontend calls [scope: ${frontendScope}]`
|
|
526
|
+
: `❌ API parity check failed: ${missingBackend.length} frontend calls have no matching backend endpoint [scope: ${frontendScope}]`
|
|
382
527
|
|
|
383
|
-
if (
|
|
384
|
-
summary += ` | ${
|
|
528
|
+
if (narrowedUnused.length > 0) {
|
|
529
|
+
summary += ` | ${narrowedUnused.length} backend endpoints unused by frontend`
|
|
385
530
|
}
|
|
386
531
|
|
|
387
|
-
return { ok, missingBackend, unusedBackend, summary, backendCount:
|
|
532
|
+
return { ok, missingBackend, unusedBackend: narrowedUnused, summary, backendCount: mergedProviderEndpoints.length, frontendCount: frontendCalls.length, scanRoots: liveByRoot.map(r => r.root) }
|
|
388
533
|
}
|
package/src/datetime.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 人读墙钟时间工具(坑 taskcard-created-at-utc,2026-08-23 实证:taskcard 骨架 created_at 用
|
|
3
|
+
* toISOString() 落 UTC——本地 09:39 生成写 01:39,子代理两次手工改)。
|
|
4
|
+
*
|
|
5
|
+
* 项目内「人读的 markdown/YAML frontmatter 时间字段」统一走本函数:本地时区 +
|
|
6
|
+
* `YYYY-MM-DD HH:mm:ss` 形状(scan.js 文档示例形状),手工拼接零 locale/ICU 依赖(Node
|
|
7
|
+
* small-icu 构建下 toLocaleString('sv-SE') 不可靠)。机器可读处(JSON/DB 列/目录名)继续用
|
|
8
|
+
* toISOString()——那是 ISO 惯例不是坑。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 本地墙钟时间字符串(YYYY-MM-DD HH:mm:ss,各段补零)。
|
|
13
|
+
* @param {Date} [d] 可注入时钟(测试用),缺省当前时间
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
export function nowWallClock(d = new Date()) {
|
|
17
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
18
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} `
|
|
19
|
+
+ `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
20
|
+
}
|