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
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* module-impact.js — `sillyspec module-impact` 的归类骨架生成
|
|
3
|
+
* (2026-08-21 agent-手工产出审计第三批 D3)。
|
|
4
|
+
*
|
|
5
|
+
* archive 阶段的 module-impact.md 要求「模块影响矩阵」+「未匹配文件」两章节、≥20 行,
|
|
6
|
+
* agent 此前从零手写整份矩阵。文件 × 模块的归属是纯机械分类(module-map paths 前缀匹配,
|
|
7
|
+
* 与 scan diff 归属同源),影响类型判定(逻辑/数据结构/接口/调用关系/配置/新增)是语义——
|
|
8
|
+
* 骨架预填「模块|文件|状态」三列,影响类型列留 <!--TODO--> 由 agent 逐行填。
|
|
9
|
+
*
|
|
10
|
+
* diff 源:resolveVerifyChangedFiles(worktree-aware,与 task-review 同源);
|
|
11
|
+
* module-map:.sillyspec/docs/<p>/modules/_module-map.yaml 的 modules.<id>.paths
|
|
12
|
+
* (目录条目以 / 结尾按前缀匹配,与模块上下文索引读侧口径一致)。
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, readFileSync, readdirSync } from 'fs'
|
|
15
|
+
import { join } from 'path'
|
|
16
|
+
import { resolveVerifyChangedFiles } from './verify-postcheck.js'
|
|
17
|
+
import { resolveSpecDir } from './run/shared.js'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 轻量解析 _module-map.yaml:modules.<id>.paths 列表(手写解析同 parseRepoRegistry 风格,
|
|
21
|
+
* 只取 paths 平面信息,status/doc 等忽略)。
|
|
22
|
+
* @returns {Map<string, string[]>} moduleId → paths(posix)
|
|
23
|
+
*/
|
|
24
|
+
export function parseModuleMapPaths(yamlText) {
|
|
25
|
+
const map = new Map()
|
|
26
|
+
if (!yamlText) return map
|
|
27
|
+
let currentModule = null
|
|
28
|
+
let inPaths = false
|
|
29
|
+
for (const line of yamlText.split('\n')) {
|
|
30
|
+
if (!line.trim() || line.trim().startsWith('#')) continue
|
|
31
|
+
if (!/^\s/.test(line)) {
|
|
32
|
+
inPaths = line.startsWith('modules:')
|
|
33
|
+
currentModule = null
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
const modMatch = line.match(/^ ([A-Za-z0-9_.\-]+):\s*$/)
|
|
37
|
+
if (modMatch) {
|
|
38
|
+
currentModule = modMatch[1]
|
|
39
|
+
inPaths = false
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
if (currentModule && /^ paths:\s*$/.test(line)) {
|
|
43
|
+
inPaths = true
|
|
44
|
+
if (!map.has(currentModule)) map.set(currentModule, [])
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
if (inPaths && currentModule) {
|
|
48
|
+
const item = line.match(/^ - (.+)$/)
|
|
49
|
+
if (item) map.get(currentModule).push(item[1].trim().replace(/\\/g, '/'))
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return map
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function classifyFile(posixPath, modulePaths) {
|
|
56
|
+
for (const [moduleId, paths] of modulePaths) {
|
|
57
|
+
for (const p of paths) {
|
|
58
|
+
// 目录条目以 / 结尾 → 前缀匹配;文件条目 → 精确或子路径(与模块上下文索引读侧口径一致)
|
|
59
|
+
if (p.endsWith('/')) {
|
|
60
|
+
if (posixPath.startsWith(p)) return moduleId
|
|
61
|
+
} else if (posixPath === p || posixPath.startsWith(p + '/')) {
|
|
62
|
+
return moduleId
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 生成 module-impact.md 骨架。
|
|
71
|
+
* @param {{ cwd: string, changeName: string, specDir?: string|null }} opts
|
|
72
|
+
* @returns {{ markdown: string, matchedCount: number, unmatchedCount: number }|null}
|
|
73
|
+
* 无 module-map / 无 diff → null(无可归类输入,agent 全手写)
|
|
74
|
+
*/
|
|
75
|
+
export function generateModuleImpactSkeleton({ cwd, changeName, specDir = null }) {
|
|
76
|
+
const specBase = resolveSpecDir(cwd, { specDir })
|
|
77
|
+
|
|
78
|
+
// 找 _module-map.yaml(docs/<p>/modules/;多项目取首个含 modules 索引的)
|
|
79
|
+
let moduleMapPath = null
|
|
80
|
+
try {
|
|
81
|
+
for (const p of readdirSync(join(specBase, 'docs'), { withFileTypes: true })) {
|
|
82
|
+
if (!p.isDirectory()) continue
|
|
83
|
+
const candidate = join(specBase, 'docs', p.name, 'modules', '_module-map.yaml')
|
|
84
|
+
if (existsSync(candidate)) {
|
|
85
|
+
moduleMapPath = candidate
|
|
86
|
+
break
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch { /* 无 docs/ */ }
|
|
90
|
+
if (!moduleMapPath) return null
|
|
91
|
+
|
|
92
|
+
const modulePaths = parseModuleMapPaths(readFileSync(moduleMapPath, 'utf8'))
|
|
93
|
+
if (modulePaths.size === 0) return null
|
|
94
|
+
|
|
95
|
+
// includeWorkingTree 同 verify module 子集(坑 module-subset-zero-hit-uncommitted):
|
|
96
|
+
// worktree 未提交改动也计入模块影响面(module-impact 按实际 diff 归属模块)
|
|
97
|
+
const diffFiles = resolveVerifyChangedFiles(cwd, changeName, null, { includeWorkingTree: true }) || []
|
|
98
|
+
const sourceFiles = diffFiles.map(f => f.split('\\').join('/')).filter(f => !f.startsWith('.sillyspec/'))
|
|
99
|
+
if (sourceFiles.length === 0) return null
|
|
100
|
+
|
|
101
|
+
const byModule = new Map()
|
|
102
|
+
const unmatched = []
|
|
103
|
+
for (const f of sourceFiles) {
|
|
104
|
+
const mod = classifyFile(f, modulePaths)
|
|
105
|
+
if (mod) {
|
|
106
|
+
if (!byModule.has(mod)) byModule.set(mod, [])
|
|
107
|
+
byModule.get(mod).push(f)
|
|
108
|
+
} else {
|
|
109
|
+
unmatched.push(f)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const L = [
|
|
114
|
+
'# 模块影响分析(骨架由 `sillyspec module-impact --change <变更名>` 生成)',
|
|
115
|
+
'',
|
|
116
|
+
'> 文件×模块归属由 CLI 按 _module-map.yaml paths 前缀匹配预填;',
|
|
117
|
+
'> **影响类型**(逻辑变更/数据结构变更/接口变更/调用关系变更/配置变更/新增)与 review 标记是语义判断,',
|
|
118
|
+
'> 逐行把 <!--TODO--> 替换为真实结论——以 git diff 为准(真实 > 声明)。',
|
|
119
|
+
'',
|
|
120
|
+
'## 模块影响矩阵',
|
|
121
|
+
'',
|
|
122
|
+
'| 模块 | 变更文件 | 影响类型 | 需 review |',
|
|
123
|
+
'|---|---|---|---|',
|
|
124
|
+
]
|
|
125
|
+
const rows = [...byModule.entries()].sort((a, b) => a[0].localeCompare(b[0]))
|
|
126
|
+
for (const [moduleId, files] of rows) {
|
|
127
|
+
for (const f of files) {
|
|
128
|
+
L.push(`| ${moduleId} | \`${f}\` | <!--TODO--> | <!--TODO--> |`)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
L.push('')
|
|
132
|
+
L.push('## 未匹配文件')
|
|
133
|
+
L.push('')
|
|
134
|
+
if (unmatched.length === 0) {
|
|
135
|
+
L.push('无(全部变更文件已归属到模块)。')
|
|
136
|
+
} else {
|
|
137
|
+
L.push('以下变更文件未命中 _module-map.yaml 任何模块 paths——确认是模块索引过期(该跑 `sillyspec modules rebuild`)还是真的游离文件:')
|
|
138
|
+
L.push('')
|
|
139
|
+
for (const f of unmatched) L.push(`- \`${f}\` <!--TODO: 归属判定-->`)
|
|
140
|
+
}
|
|
141
|
+
L.push('')
|
|
142
|
+
L.push('## 影响类型说明')
|
|
143
|
+
L.push('')
|
|
144
|
+
L.push('逻辑变更 / 数据结构变更 / 接口变更 / 调用关系变更 / 配置变更 / 新增;不确定的影响标 needs review。')
|
|
145
|
+
L.push('')
|
|
146
|
+
// 「更新结果」表骨架(2026-08-21 审查 CLI-3):verify --done 死信门(extractPendingDocSyncRows)
|
|
147
|
+
// 与 archive 移动前校验同一口径硬校验本表无 pending 行,但骨架此前不产出该表——agent 只能
|
|
148
|
+
// 从 gate 报错反推格式(plan.js prompt 自认的坑)。此处按矩阵命中模块机械生成行,agent 只回填状态。
|
|
149
|
+
L.push('## 更新结果')
|
|
150
|
+
L.push('')
|
|
151
|
+
L.push('| 目标 | 操作 | 状态 |')
|
|
152
|
+
L.push('|------|------|------|')
|
|
153
|
+
for (const [moduleId] of rows) {
|
|
154
|
+
L.push(`| \`modules/${moduleId}.md\` | 更新${moduleId}模块卡(本次变更涉及) | pending |`)
|
|
155
|
+
}
|
|
156
|
+
if (unmatched.length > 0) {
|
|
157
|
+
L.push('| `_module-map.yaml` | <!--TODO: 有未匹配文件,判定模块索引是否需增改(modules rebuild)--> | pending |')
|
|
158
|
+
} else {
|
|
159
|
+
L.push('| `_module-map.yaml` | 无变化(未增删模块) | skipped |')
|
|
160
|
+
}
|
|
161
|
+
L.push('')
|
|
162
|
+
L.push('规则:execute/verify 完成文档同步后把对应行回填 done;确定不同步的行改 skipped 并在操作列写明原因。')
|
|
163
|
+
L.push('')
|
|
164
|
+
|
|
165
|
+
return { markdown: L.join('\n'), matchedCount: sourceFiles.length - unmatched.length, unmatchedCount: unmatched.length }
|
|
166
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* module-resolve.js — 模块卡两级级联解析(token 成本优化 P0a,2026-08-22-token-cost-optimization)
|
|
3
|
+
*
|
|
4
|
+
* 背景(multi-agent-platform 2026-08-22 实测):monorepo 根层 _module-map.yaml 把整个子项目
|
|
5
|
+
* (backend/**)映射到单张大卡(backend.md 58KB),而子项目细粒度卡(docs/backend/modules/
|
|
6
|
+
* agent.md,~6KB)由 scan 生成却无人消费——execute 每个 task 子代理都被指引去读整张大卡,
|
|
7
|
+
* 单卡一项全流程烧 20 万+ tokens 输入,且大卡「变更索引」段每变更追加、单调膨胀。
|
|
8
|
+
*
|
|
9
|
+
* 本模块 = 纯机械分类(与 module-impact.js 同性质,CLI 算事实):
|
|
10
|
+
* - collectModuleMaps:收齐 <spec>/docs/<project>/modules/_module-map.yaml 全部层级;
|
|
11
|
+
* project 名恰为仓库顶层目录(monorepo 子项目,如 backend/)→ 其 paths 视为相对该
|
|
12
|
+
* 子目录(effective = `<project>/` + path);否则视为仓库根相对(根层粗粒度 map)
|
|
13
|
+
* - matchModuleCard:跨全部 map 最长有效前缀匹配(细卡 agent/** 胜过粗卡 backend/**)
|
|
14
|
+
* - resolveChangeModuleCards:tasks.md 注册表 × tasks/task-NN.md allowed_paths → per-task
|
|
15
|
+
* 最优卡表;卡的体量(字节数)一并给出,读取建议按体量分级(>12KB 按节读)
|
|
16
|
+
*
|
|
17
|
+
* 铁律(对齐仓内先例):
|
|
18
|
+
* - 纯读:不写任何文件,不跑 git
|
|
19
|
+
* - 无 map / 无 tasks.md / 无 task 卡 → 空结果(调用方注入占位说明,不抛)
|
|
20
|
+
* - 判定语义(哪个模块才是「对的」)仍归 agent——本表只是机械最优匹配
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs'
|
|
23
|
+
|
|
24
|
+
/** 模块卡字数预算(字节):超限在 resolve 表标 ⚠️ 并提示精简——文档是 agent 的读取税
|
|
25
|
+
* (对标 deepseek-harness verify-doc-budgets 理念:预算只降不升;2026-08-24 起 runtime.md
|
|
26
|
+
* 28KB/worktree.md 24KB 已在付税)。advisory 不阻断,16 为 dogfood 校准前缺省。 */
|
|
27
|
+
const MODULE_CARD_BUDGET_BYTES = 16 * 1024
|
|
28
|
+
import { join, relative } from 'path'
|
|
29
|
+
import { parseModuleMapSimple } from './modules.js'
|
|
30
|
+
import { parseTaskRegistry } from './stages/execute.js'
|
|
31
|
+
import { parseAllowedPaths } from './stages/plan-postcheck.js'
|
|
32
|
+
|
|
33
|
+
/** 模块卡「整卡可读」软上限(字节)。超过则建议按节读(契约摘要/注意事项/定位),跳过历史累积段。 */
|
|
34
|
+
export const MODULE_CARD_SOFT_LIMIT_BYTES = 12 * 1024
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 收齐 spec 下全部模块映射层。
|
|
38
|
+
* @param {{ cwd: string, specBase: string }} opts
|
|
39
|
+
* @returns {Array<{ project: string, mapPath: string, modulesDir: string, prefix: string,
|
|
40
|
+
* entries: Map<string, { paths: string[], doc: string|null }> }>}
|
|
41
|
+
* prefix:project 名为仓库顶层目录时 `<project>/`(子项目细 map),否则 ''(根层粗 map)
|
|
42
|
+
*/
|
|
43
|
+
export function collectModuleMaps({ cwd, specBase }) {
|
|
44
|
+
const maps = []
|
|
45
|
+
const docsDir = join(specBase, 'docs')
|
|
46
|
+
if (!existsSync(docsDir)) return maps
|
|
47
|
+
let projects = []
|
|
48
|
+
try {
|
|
49
|
+
projects = readdirSync(docsDir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name)
|
|
50
|
+
} catch { return maps }
|
|
51
|
+
for (const project of projects) {
|
|
52
|
+
const modulesDir = join(docsDir, project, 'modules')
|
|
53
|
+
const mapPath = join(modulesDir, '_module-map.yaml')
|
|
54
|
+
if (!existsSync(mapPath)) continue
|
|
55
|
+
let parsed = {}
|
|
56
|
+
try {
|
|
57
|
+
parsed = parseModuleMapSimple(readFileSync(mapPath, 'utf8'))
|
|
58
|
+
} catch { continue }
|
|
59
|
+
const entries = new Map()
|
|
60
|
+
for (const [id, data] of Object.entries(parsed)) {
|
|
61
|
+
const paths = Array.isArray(data?.paths) ? data.paths : []
|
|
62
|
+
if (paths.length === 0) continue
|
|
63
|
+
entries.set(id, { paths, doc: typeof data?.doc === 'string' ? data.doc : null })
|
|
64
|
+
}
|
|
65
|
+
if (entries.size === 0) continue
|
|
66
|
+
// 子项目判定:project 名恰为仓库顶层目录 → map 的 paths 相对该子目录(monorepo 细 map)。
|
|
67
|
+
// 根层 map(project = 仓名)paths 本就仓库根相对,prefix=''。
|
|
68
|
+
let prefix = ''
|
|
69
|
+
try {
|
|
70
|
+
const topDir = join(cwd, project)
|
|
71
|
+
if (existsSync(topDir) && statSync(topDir).isDirectory()) prefix = `${project}/`
|
|
72
|
+
} catch { /* stat 失败按根层处理 */ }
|
|
73
|
+
maps.push({ project, mapPath, modulesDir, prefix, entries })
|
|
74
|
+
}
|
|
75
|
+
return maps
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 路径归一:反斜杠→正斜杠,剥尾部 glob(/** 与 /*)与目录斜杠。
|
|
80
|
+
* `backend/**` / `backend/` / `backend` → `backend`。
|
|
81
|
+
*/
|
|
82
|
+
function normalizeMapPath(raw) {
|
|
83
|
+
return String(raw).replace(/\\/g, '/').replace(/\/\*\*$/, '').replace(/\/\*$/, '').replace(/\/$/, '')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 跨全部 map 最长有效前缀匹配一个仓库根相对路径。
|
|
88
|
+
* @param {string} posixPath 仓库根相对 posix 路径
|
|
89
|
+
* @param {Array} maps collectModuleMaps 产物
|
|
90
|
+
* @returns {{ moduleId: string, map: object, effectivePrefix: string }|null}
|
|
91
|
+
*/
|
|
92
|
+
export function matchModuleCard(posixPath, maps) {
|
|
93
|
+
const p = String(posixPath).replace(/\\/g, '/')
|
|
94
|
+
let best = null
|
|
95
|
+
for (const map of maps) {
|
|
96
|
+
for (const [moduleId, entry] of map.entries) {
|
|
97
|
+
for (const raw of entry.paths) {
|
|
98
|
+
const effective = (map.prefix + normalizeMapPath(raw)).replace(/\/+$/, '')
|
|
99
|
+
if (!effective) continue
|
|
100
|
+
if (p === effective || p.startsWith(effective + '/')) {
|
|
101
|
+
if (!best || effective.length > best.effectivePrefix.length) {
|
|
102
|
+
best = { moduleId, map, effectivePrefix: effective }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return best
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 解析变更全部 task 的最优模块卡。
|
|
113
|
+
* @param {{ cwd: string, specBase: string, changeName: string }} opts
|
|
114
|
+
* @returns {{ hasMaps: boolean, rows: Array<{ taskId: string, taskName: string,
|
|
115
|
+
* moduleId: string|null, granularity: 'fine'|'coarse'|null,
|
|
116
|
+
* cardPath: string|null, cardBytes: number, advice: string,
|
|
117
|
+
* matchedCount: number, pathCount: number }> }}
|
|
118
|
+
* granularity:按胜者有效前缀段数判——1 段(整子项目)=coarse 大卡,≥2 段=fine 细卡
|
|
119
|
+
*/
|
|
120
|
+
export function resolveChangeModuleCards({ cwd, specBase, changeName }) {
|
|
121
|
+
const maps = collectModuleMaps({ cwd, specBase })
|
|
122
|
+
const changeDir = join(specBase, 'changes', changeName || '')
|
|
123
|
+
let registry = []
|
|
124
|
+
const tasksMd = join(changeDir, 'tasks.md')
|
|
125
|
+
if (existsSync(tasksMd)) {
|
|
126
|
+
registry = parseTaskRegistry(readFileSync(tasksMd, 'utf8'))
|
|
127
|
+
}
|
|
128
|
+
const rows = []
|
|
129
|
+
for (const task of registry) {
|
|
130
|
+
// 任务路径源:task 卡 allowed_paths(主)> 注册表行内 (文件路径) 尾注(兜底)
|
|
131
|
+
let paths = []
|
|
132
|
+
const cardFile = join(changeDir, 'tasks', `${task.id}.md`)
|
|
133
|
+
if (existsSync(cardFile)) {
|
|
134
|
+
paths = parseAllowedPaths(readFileSync(cardFile, 'utf8'))
|
|
135
|
+
}
|
|
136
|
+
if (paths.length === 0 && task.file) paths = [task.file]
|
|
137
|
+
paths = paths.map(p => p.replace(/\\/g, '/')).filter(Boolean)
|
|
138
|
+
|
|
139
|
+
// 全路径逐个匹配,取「最长有效前缀」的模块为该 task 的最优卡。粒度按有效前缀段数判:
|
|
140
|
+
// 1 段(backend/frontend 整子项目)=粗层大卡;≥2 段=细层卡(段数与卡体量正相关,
|
|
141
|
+
// SillyHub 这类根相对细 map 同样正确——不能按 map 的 prefix 有无判,会误标)
|
|
142
|
+
let winner = null
|
|
143
|
+
let matchedCount = 0
|
|
144
|
+
for (const p of paths) {
|
|
145
|
+
const m = matchModuleCard(p, maps)
|
|
146
|
+
if (!m) continue
|
|
147
|
+
matchedCount++
|
|
148
|
+
if (!winner || m.effectivePrefix.length > winner.effectivePrefix.length) winner = m
|
|
149
|
+
}
|
|
150
|
+
let granularity = null
|
|
151
|
+
if (winner) {
|
|
152
|
+
const segs = winner.effectivePrefix.split('/').filter(Boolean).length
|
|
153
|
+
granularity = segs >= 2 ? 'fine' : 'coarse'
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let cardPath = null
|
|
157
|
+
let cardBytes = 0
|
|
158
|
+
let advice = '—'
|
|
159
|
+
if (winner) {
|
|
160
|
+
const docRel = winner.map.entries.get(winner.moduleId)?.doc || `modules/${winner.moduleId}.md`
|
|
161
|
+
const abs = join(winner.map.modulesDir, '..', docRel)
|
|
162
|
+
if (existsSync(abs)) {
|
|
163
|
+
const rel = relative(cwd, abs).replace(/\\/g, '/')
|
|
164
|
+
cardPath = rel.startsWith('..') ? abs.replace(/\\/g, '/') : rel
|
|
165
|
+
try { cardBytes = statSync(abs).size } catch { cardBytes = 0 }
|
|
166
|
+
advice = cardBytes > MODULE_CARD_SOFT_LIMIT_BYTES
|
|
167
|
+
? `按节读(契约摘要/注意事项/定位;跳过变更索引/人工备注)`
|
|
168
|
+
: '整卡可读'
|
|
169
|
+
} else {
|
|
170
|
+
cardPath = relative(cwd, abs).replace(/\\/g, '/')
|
|
171
|
+
advice = '卡文件缺失(modules rebuild?)'
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
rows.push({
|
|
175
|
+
taskId: task.id,
|
|
176
|
+
taskName: task.name,
|
|
177
|
+
moduleId: winner ? winner.moduleId : null,
|
|
178
|
+
granularity,
|
|
179
|
+
cardPath,
|
|
180
|
+
cardBytes,
|
|
181
|
+
advice,
|
|
182
|
+
matchedCount,
|
|
183
|
+
pathCount: paths.length,
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
return { hasMaps: maps.length > 0, rows }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 渲染 per-task 模块卡表(注入 execute「加载上下文」步 prompt 用)。
|
|
191
|
+
* 无 map → 提示跳过;有 map 无命中行 → 单行说明。恒返回非空字符串(占位符必被替换)。
|
|
192
|
+
*/
|
|
193
|
+
export function renderModuleResolveTable({ cwd, specBase, changeName }) {
|
|
194
|
+
const { hasMaps, rows } = resolveChangeModuleCards({ cwd, specBase, changeName })
|
|
195
|
+
if (!hasMaps) return '(未找到 _module-map.yaml——本项目无模块卡,跳过模块文档加载)'
|
|
196
|
+
const withMatch = rows.filter(r => r.moduleId)
|
|
197
|
+
if (withMatch.length === 0) {
|
|
198
|
+
return '(已找到 _module-map.yaml,但当前变更任务未命中任何模块 paths——跳过模块文档加载;游离文件可在 Wave 收尾后跑 `sillyspec modules rebuild`)'
|
|
199
|
+
}
|
|
200
|
+
const kb = (n) => n > 0 ? `${(n / 1024).toFixed(1)}KB` : '?'
|
|
201
|
+
const L = [
|
|
202
|
+
'| task | 模块 | 粒度 | 模块卡(只读命中的卡,勿按目录整读) | 体量 | 读取建议 |',
|
|
203
|
+
'|---|---|---|---|---|---|',
|
|
204
|
+
]
|
|
205
|
+
const budgetBreached = new Set()
|
|
206
|
+
for (const r of rows) {
|
|
207
|
+
if (r.moduleId) {
|
|
208
|
+
const over = r.cardBytes > MODULE_CARD_BUDGET_BYTES
|
|
209
|
+
if (over) budgetBreached.add(r.moduleId)
|
|
210
|
+
L.push(`| ${r.taskId} | ${r.moduleId} | ${r.granularity === 'fine' ? '细' : '粗'} | \`${r.cardPath}\` | ${kb(r.cardBytes)}${over ? ' ⚠️超预算' : ''} | ${r.advice} |`)
|
|
211
|
+
} else {
|
|
212
|
+
L.push(`| ${r.taskId} | — | — | (无命中,跳过模块文档) | — | — |`)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
L.push('')
|
|
216
|
+
L.push('> 匹配规则:全部 _module-map.yaml 跨层最长前缀(子项目细卡优先于根层大卡),源=tasks 卡 allowed_paths。')
|
|
217
|
+
L.push('> 细卡整卡可读;仅粗卡命中且体量大时**按节读**——只读「契约摘要/注意事项/定位」,跳过「变更索引/人工备注」历史累积段。')
|
|
218
|
+
if (budgetBreached.size > 0) {
|
|
219
|
+
L.push(`> ⚠️ 模块卡超预算(>${Math.round(MODULE_CARD_BUDGET_BYTES / 1024)}KB):${[...budgetBreached].join('、')}——文档是 agent 的读取税,本次变更触及该模块,收尾时顺手 \`sillyspec modules split-changelog\` 迁出历史段或精简正文(对标 deepseek-harness verify-doc-budgets:预算只降不升)。`)
|
|
220
|
+
}
|
|
221
|
+
return L.join('\n')
|
|
222
|
+
}
|