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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* classify-change.js — 变更规模分类器
|
|
3
|
+
*
|
|
4
|
+
* 将用户需求描述分类为 quick / auto / full,
|
|
5
|
+
* 供 auto 模式决定内部流程深度。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync } from 'fs'
|
|
9
|
+
import { join } from 'path'
|
|
10
|
+
import jsYaml from 'js-yaml'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 用户显式指定的关键词 → 强制模式
|
|
14
|
+
*/
|
|
15
|
+
const FORCE_FULL_PATTERNS = [
|
|
16
|
+
/数据库|database|schema/i,
|
|
17
|
+
/迁移|migration|migrate/i,
|
|
18
|
+
/鉴权|权限|auth|permission|rbac/i,
|
|
19
|
+
/支付|payment|billing/i,
|
|
20
|
+
/重构|refactor.*architectur/i,
|
|
21
|
+
/微服务|microserv/i,
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
const FORCE_QUICK_PATTERNS = [
|
|
25
|
+
/fix typo/i,
|
|
26
|
+
/更新文案|改文案|文案修改/i,
|
|
27
|
+
/样式调整|style.*tweak/i,
|
|
28
|
+
/修复.*\s*\bbug\b.*\bfix\b/i,
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 读 local.yaml 的 auto_mode 段(best-effort,绝不抛)。
|
|
33
|
+
* 供 runCommand 接线:传给 classifyChange 的 localConfig,让 force_*_patterns 真生效。
|
|
34
|
+
* @param {string} cwd - 主仓库根(local.yaml 在 <cwd>/.sillyspec/local.yaml)
|
|
35
|
+
* @returns {{ force_full_patterns?: string[], force_quick_patterns?: string[] } | null}
|
|
36
|
+
* auto_mode 段含至少一个 string pattern → 归一化数组;文件缺/解析失败/无段/空 → null(降级默认关键词)
|
|
37
|
+
*/
|
|
38
|
+
export function readAutoModeFromLocalYaml(cwd) {
|
|
39
|
+
try {
|
|
40
|
+
const p = join(cwd, '.sillyspec', 'local.yaml')
|
|
41
|
+
if (!existsSync(p)) return null
|
|
42
|
+
const doc = jsYaml.load(readFileSync(p, 'utf8'))
|
|
43
|
+
if (!doc || typeof doc !== 'object') return null
|
|
44
|
+
const am = doc.auto_mode
|
|
45
|
+
if (!am || typeof am !== 'object') return null
|
|
46
|
+
const ffp = Array.isArray(am.force_full_patterns) ? am.force_full_patterns.filter(s => typeof s === 'string') : []
|
|
47
|
+
const fqp = Array.isArray(am.force_quick_patterns) ? am.force_quick_patterns.filter(s => typeof s === 'string') : []
|
|
48
|
+
if (ffp.length === 0 && fqp.length === 0) return null
|
|
49
|
+
return { force_full_patterns: ffp, force_quick_patterns: fqp }
|
|
50
|
+
} catch {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 分类变更规模
|
|
57
|
+
* @param {object} opts
|
|
58
|
+
* @param {string} opts.description - 用户需求描述
|
|
59
|
+
* @param {string} [opts.explicitMode] - 用户显式指定的模式(auto/quick/full)
|
|
60
|
+
* @param {object} [opts.localConfig] - local.yaml auto_mode 段(由 readAutoModeFromLocalYaml 读出)
|
|
61
|
+
* @returns {{ mode: 'quick'|'auto'|'full', reason: string }}
|
|
62
|
+
*/
|
|
63
|
+
export function classifyChange({ description = '', explicitMode, localConfig } = {}) {
|
|
64
|
+
// 1. 用户显式指定优先级最高
|
|
65
|
+
if (explicitMode && ['quick', 'auto', 'full'].includes(explicitMode)) {
|
|
66
|
+
return { mode: explicitMode, reason: '用户显式指定' }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 2. local.yaml force patterns
|
|
70
|
+
if (localConfig) {
|
|
71
|
+
const forceFullPatterns = localConfig.force_full_patterns || []
|
|
72
|
+
const forceQuickPatterns = localConfig.force_quick_patterns || []
|
|
73
|
+
|
|
74
|
+
for (const pattern of forceFullPatterns) {
|
|
75
|
+
try {
|
|
76
|
+
if (new RegExp(pattern, 'i').test(description)) {
|
|
77
|
+
return { mode: 'full', reason: `local.yaml force_full_pattern 匹配: ${pattern}` }
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// 非法正则跳过,不崩 auto 分类(review-2026-08-09 #30)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const pattern of forceQuickPatterns) {
|
|
84
|
+
try {
|
|
85
|
+
if (new RegExp(pattern, 'i').test(description)) {
|
|
86
|
+
return { mode: 'quick', reason: `local.yaml force_quick_pattern 匹配: ${pattern}` }
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
// 非法正则跳过
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 3. 默认关键词匹配
|
|
95
|
+
for (const pattern of FORCE_QUICK_PATTERNS) {
|
|
96
|
+
if (pattern.test(description)) {
|
|
97
|
+
return { mode: 'quick', reason: `命中 quick 关键词: ${pattern.source}` }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (const pattern of FORCE_FULL_PATTERNS) {
|
|
102
|
+
if (pattern.test(description)) {
|
|
103
|
+
return { mode: 'full', reason: `命中 full 关键词: ${pattern.source}` }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 4. 默认 auto
|
|
108
|
+
return { mode: 'auto', reason: '默认模式' }
|
|
109
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commit-suggest.js — `sillyspec commit` 的语义收集 + commit message 建议生成
|
|
3
|
+
* (2026-08-21 agent-手工产出审计第二批 G1-G3)。
|
|
4
|
+
*
|
|
5
|
+
* commit skill 此前让 agent 手工收集语义:取 LAST_COMMIT_TIME、cat 多处 QUICKLOG、
|
|
6
|
+
* 扫变更目录、按时间戳过滤已勾 task、按路径模式识别来源阶段——进度库/quicklog/
|
|
7
|
+
* git 全在 CLI 手里,这是「CLI 有数据却让 agent 重新收集」的最典型案例。
|
|
8
|
+
*
|
|
9
|
+
* 本模块一条命令产出全部上下文 + conventional commits 建议 message + 可照抄的
|
|
10
|
+
* git commit 命令。**只建议不执行**(对齐 skill 绝对规则「不要自动提交」——确认权在人)。
|
|
11
|
+
*
|
|
12
|
+
* 语义来源(与 skill 手工流程同源):
|
|
13
|
+
* A. QUICKLOG:specBase/quicklog/QUICKLOG-<user>.md + changes/<change>/quicklog/ 下的归属条目,
|
|
14
|
+
* 条目时间 > 上次 commit 时间者(首次提交全收)
|
|
15
|
+
* B. execute 已勾 task:活跃变更 tasks.md 的 [x] 行(tasks.md mtime > 上次 commit 才计入,
|
|
16
|
+
* 防旧变更全勾 task 永久污染建议)
|
|
17
|
+
* C. 阶段产出:changed paths 按路径模式归 scan/brainstorm/plan/execute/archive/knowledge
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'fs'
|
|
20
|
+
import { join } from 'path'
|
|
21
|
+
import { gitQuiet } from './git-helper.js'
|
|
22
|
+
import { resolveSpecDir } from './run/shared.js'
|
|
23
|
+
|
|
24
|
+
function listActiveChanges(specBase) {
|
|
25
|
+
try {
|
|
26
|
+
return readdirSync(join(specBase, 'changes'), { withFileTypes: true })
|
|
27
|
+
.filter(e => e.isDirectory() && e.name !== 'archive')
|
|
28
|
+
.map(e => e.name)
|
|
29
|
+
.sort()
|
|
30
|
+
} catch {
|
|
31
|
+
return []
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 解析 QUICKLOG 文件里的条目(## ql-xxx | time | title + 状态:行)
|
|
37
|
+
*/
|
|
38
|
+
function parseQuicklogEntries(file) {
|
|
39
|
+
let content
|
|
40
|
+
try {
|
|
41
|
+
content = readFileSync(file, 'utf8')
|
|
42
|
+
} catch {
|
|
43
|
+
return []
|
|
44
|
+
}
|
|
45
|
+
const entries = []
|
|
46
|
+
const lines = content.split('\n')
|
|
47
|
+
let cur = null
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
const h = line.match(/^## (ql-\S+) \| ([0-9: -]+?) \| (.*)$/)
|
|
50
|
+
if (h) {
|
|
51
|
+
if (cur) entries.push(cur)
|
|
52
|
+
cur = { qlId: h[1], time: h[2].trim(), title: h[3].trim(), status: '未知' }
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
if (cur) {
|
|
56
|
+
const s = line.match(/^状态[::]\s*(\S+)/)
|
|
57
|
+
if (s) cur.status = s[1]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (cur) entries.push(cur)
|
|
61
|
+
return entries
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function classifyStagePath(p) {
|
|
65
|
+
const posix = p.replace(/\\/g, '/')
|
|
66
|
+
if (/^\.sillyspec\/docs\/[^/]+\/scan\//.test(posix)) return 'scan'
|
|
67
|
+
if (/^\.sillyspec\/changes\/archive\//.test(posix)) return 'archive'
|
|
68
|
+
if (/^\.sillyspec\/changes\/[^/]+\/(proposal|design)\.md/.test(posix)) return 'brainstorm'
|
|
69
|
+
if (/^\.sillyspec\/changes\/[^/]+\/(tasks|plan)\.md/.test(posix)) return 'plan'
|
|
70
|
+
if (/^\.sillyspec\/knowledge\//.test(posix)) return 'knowledge'
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 收集提交上下文并生成建议 message。
|
|
76
|
+
* @param {{ cwd: string, specDir?: string|null }} opts
|
|
77
|
+
* @returns {{
|
|
78
|
+
* lastCommitTime: string|null, hasChanges: boolean, changedCount: number, stat: string,
|
|
79
|
+
* quickEntries: Array<{qlId,time,title,status,file}>, checkedTasks: Array<{change,item}>,
|
|
80
|
+
* stageArtifacts: string[], suggestion: {type, subject, body}|null
|
|
81
|
+
* }}
|
|
82
|
+
*/
|
|
83
|
+
export function collectCommitContext({ cwd, specDir = null }) {
|
|
84
|
+
const specBase = resolveSpecDir(cwd, { specDir })
|
|
85
|
+
const lastCommitTime = gitQuiet(cwd, ['log', '-1', '--format=%ci'])
|
|
86
|
+
const lastCommitKey = lastCommitTime ? lastCommitTime.slice(0, 19) : null
|
|
87
|
+
|
|
88
|
+
// -uall:未跟踪目录不折叠(.sillyspec/ 整体未跟踪时默认 porcelain 只给一行 `?? .sillyspec/`,
|
|
89
|
+
// 路径模式分类会瞎;-uall 逐文件列出,scan/brainstorm/plan 分类才有输入)
|
|
90
|
+
const porcelain = gitQuiet(cwd, ['status', '--porcelain', '-uall']) || ''
|
|
91
|
+
const changedLines = porcelain.split('\n').filter(Boolean)
|
|
92
|
+
const changedPaths = changedLines.map(l => l.slice(3).trim()).filter(Boolean)
|
|
93
|
+
const untrackedCount = changedLines.filter(l => l.startsWith('??')).length
|
|
94
|
+
const stat = gitQuiet(cwd, ['diff', 'HEAD', '--stat']) || (changedPaths.length > 0 ? `(${changedPaths.length} 个文件待首提交)` : '')
|
|
95
|
+
|
|
96
|
+
// ── 来源 A:QUICKLOG(主 quicklog + 各活跃变更归属 quicklog)──
|
|
97
|
+
const quickFiles = []
|
|
98
|
+
try {
|
|
99
|
+
for (const f of readdirSync(join(specBase, 'quicklog'))) {
|
|
100
|
+
if (/^QUICKLOG.*\.md$/.test(f)) quickFiles.push(join(specBase, 'quicklog', f))
|
|
101
|
+
}
|
|
102
|
+
} catch {}
|
|
103
|
+
for (const c of listActiveChanges(specBase)) {
|
|
104
|
+
try {
|
|
105
|
+
for (const f of readdirSync(join(specBase, 'changes', c, 'quicklog'))) {
|
|
106
|
+
if (f.endsWith('.md')) quickFiles.push(join(specBase, 'changes', c, 'quicklog', f))
|
|
107
|
+
}
|
|
108
|
+
} catch {}
|
|
109
|
+
}
|
|
110
|
+
const quickEntries = quickFiles
|
|
111
|
+
.flatMap(file => parseQuicklogEntries(file).map(e => ({ ...e, file })))
|
|
112
|
+
.filter(e => !lastCommitKey || e.time > lastCommitKey)
|
|
113
|
+
.sort((a, b) => (a.time < b.time ? -1 : 1))
|
|
114
|
+
const doneQuick = quickEntries.filter(e => e.status === '已完成')
|
|
115
|
+
|
|
116
|
+
// ── 来源 B:活跃变更已勾 task(tasks.md mtime 晚于上次 commit 才计入)──
|
|
117
|
+
const lastCommitMs = lastCommitTime ? Date.parse(lastCommitTime) : null
|
|
118
|
+
const checkedTasks = []
|
|
119
|
+
for (const c of listActiveChanges(specBase)) {
|
|
120
|
+
const tasksPath = join(specBase, 'changes', c, 'tasks.md')
|
|
121
|
+
if (!existsSync(tasksPath)) continue
|
|
122
|
+
if (lastCommitMs !== null && !Number.isNaN(lastCommitMs)) {
|
|
123
|
+
try {
|
|
124
|
+
// epoch 对比(非字符串):git %ci 带本地时区偏移,Date.parse 归一;字符串对比会因
|
|
125
|
+
// UTC/本地时差把刚改过的 tasks.md 误判为早于上次 commit 而漏收
|
|
126
|
+
if (statSync(tasksPath).mtime.getTime() <= lastCommitMs) continue
|
|
127
|
+
} catch { continue }
|
|
128
|
+
}
|
|
129
|
+
for (const line of readFileSync(tasksPath, 'utf8').split('\n')) {
|
|
130
|
+
const m = line.match(/^[-*]\s*\[[xX]\]\s*(task-\d+)\s*[::]?\s*(.*)/)
|
|
131
|
+
if (m) checkedTasks.push({ change: c, item: `${m[1]}: ${m[2].trim()}` })
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── 来源 C:阶段产出(changed paths 按模式归类去重)──
|
|
136
|
+
const stageSet = new Set()
|
|
137
|
+
for (const p of changedPaths) {
|
|
138
|
+
const stage = classifyStagePath(p)
|
|
139
|
+
if (stage) stageSet.add(stage)
|
|
140
|
+
}
|
|
141
|
+
const stageArtifacts = [...stageSet]
|
|
142
|
+
|
|
143
|
+
// ── 建议 message(对齐 commit skill 的 type 表)──
|
|
144
|
+
let suggestion = null
|
|
145
|
+
const bodyLines = []
|
|
146
|
+
const hasQuick = doneQuick.length > 0
|
|
147
|
+
const hasTasks = checkedTasks.length > 0
|
|
148
|
+
const hasStages = stageArtifacts.length > 0
|
|
149
|
+
|
|
150
|
+
if (hasQuick || hasTasks || hasStages) {
|
|
151
|
+
let type
|
|
152
|
+
let subject
|
|
153
|
+
if (hasTasks && !hasQuick) {
|
|
154
|
+
type = 'feat'
|
|
155
|
+
const byChange = new Map()
|
|
156
|
+
for (const t of checkedTasks) {
|
|
157
|
+
if (!byChange.has(t.change)) byChange.set(t.change, [])
|
|
158
|
+
byChange.get(t.change).push(t.item)
|
|
159
|
+
}
|
|
160
|
+
const [firstChange, items] = [...byChange][0]
|
|
161
|
+
subject = `feat: ${firstChange} 完成 task ×${items.length}`
|
|
162
|
+
} else if (hasQuick && !hasTasks) {
|
|
163
|
+
type = 'fix'
|
|
164
|
+
if (doneQuick.length === 1) {
|
|
165
|
+
subject = `fix: ${doneQuick[0].title}`
|
|
166
|
+
} else if (doneQuick.length === 2) {
|
|
167
|
+
subject = `fix: ${doneQuick[0].title};${doneQuick[1].title}`
|
|
168
|
+
} else {
|
|
169
|
+
subject = `fix: quick 修复 ${doneQuick.length} 项`
|
|
170
|
+
}
|
|
171
|
+
} else if (hasTasks) {
|
|
172
|
+
type = 'feat'
|
|
173
|
+
subject = `feat: 变更交付 + quick 修复 ${doneQuick.length} 项`
|
|
174
|
+
} else {
|
|
175
|
+
type = 'fix'
|
|
176
|
+
subject = `fix: quick 修复 + 阶段产出`
|
|
177
|
+
}
|
|
178
|
+
if (doneQuick.length >= 2 || hasTasks || hasStages) {
|
|
179
|
+
for (const q of doneQuick) bodyLines.push(`- quick ${q.qlId}: ${q.title}`)
|
|
180
|
+
}
|
|
181
|
+
for (const t of checkedTasks) bodyLines.push(`- ${t.change} ${t.item}`)
|
|
182
|
+
for (const s of stageArtifacts) bodyLines.push(`- 阶段产出: ${s}`)
|
|
183
|
+
if (!hasStages && !hasTasks && doneQuick.length === 1) bodyLines.length = 0
|
|
184
|
+
suggestion = {
|
|
185
|
+
type,
|
|
186
|
+
subject: subject.length > 100 ? subject.slice(0, 100) + '…' : subject,
|
|
187
|
+
body: bodyLines.length > 0 ? bodyLines.join('\n') : '',
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
lastCommitTime,
|
|
193
|
+
hasChanges: changedPaths.length > 0,
|
|
194
|
+
changedCount: changedPaths.length,
|
|
195
|
+
untrackedCount,
|
|
196
|
+
stat,
|
|
197
|
+
quickEntries,
|
|
198
|
+
checkedTasks,
|
|
199
|
+
stageArtifacts,
|
|
200
|
+
suggestion,
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config-cat.js — local.yaml 实际值读取的权威路径解析器(`sillyspec config cat` 数据源)。
|
|
3
|
+
*
|
|
4
|
+
* 背景(2026-08-21 root-local-yaml 治理):`config schema` 只列键不含值,agent 拿实际配置
|
|
5
|
+
* 只能翻文件——worktree 内 `.sillyspec/` 是 checkout 副本,gitignored 的 local.yaml 不随
|
|
6
|
+
* checkout 出现,agent 找不到就漂去项目根乱找甚至乱建。cat 按「文件存在」解析真实路径,
|
|
7
|
+
* agent 不需要知道文件在哪。
|
|
8
|
+
*
|
|
9
|
+
* 候选链(首个「文件存在」者胜):
|
|
10
|
+
* 1. specBase(--spec-dir / 平台 pointer / resolveSpecDir 祖先链首个 .sillyspec 目录)——
|
|
11
|
+
* resolveSpecDir 按「目录存在」命中,worktree 内会停在副本目录,文件并不在那里;
|
|
12
|
+
* 2. cwd 祖先链逐级 <dir>/.sillyspec/local.yaml——worktree 副本无此文件会继续向上命中主仓
|
|
13
|
+
* (sillyspec worktree 恒挂在 <主仓>/.sillyspec/.runtime/worktrees/ 下,祖先链必达);
|
|
14
|
+
* 3. git common-dir 反推主仓根——用户手动 `git worktree add` 到任意位置时的兜底。
|
|
15
|
+
*
|
|
16
|
+
* 候选链不含项目根 <dir>/local.yaml——那里从来没有这个文件(hook 同步拦写)。
|
|
17
|
+
* 纯只读;平台 pointer 异常由调用方吞掉走祖先链(真实 reader 各自 fail-closed,cat 是
|
|
18
|
+
* 只读便利口,不必加重)。
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync } from 'fs'
|
|
21
|
+
import { join, resolve, dirname } from 'path'
|
|
22
|
+
import { gitQuiet } from './git-helper.js'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 解析真实 local.yaml 路径。
|
|
26
|
+
* @param {string} dir 起点目录(CLI --dir / cwd)
|
|
27
|
+
* @param {{ specBase?: string|null }} [opts] specBase = resolvePlatformSpecDir 预解析结果(可 null)
|
|
28
|
+
* @returns {{ exists: boolean, path: string|null, source: string|null, searched: string[] }}
|
|
29
|
+
* searched 为候选链前几个路径(未命中时供人看「找过哪里」,最多 6 条)
|
|
30
|
+
*/
|
|
31
|
+
export function resolveLocalYaml(dir, opts = {}) {
|
|
32
|
+
const start = resolve(dir || process.cwd())
|
|
33
|
+
const seen = new Set()
|
|
34
|
+
const candidates = []
|
|
35
|
+
const push = (p, source) => {
|
|
36
|
+
const key = resolve(p)
|
|
37
|
+
if (seen.has(key)) return
|
|
38
|
+
seen.add(key)
|
|
39
|
+
candidates.push({ path: key, source })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (opts.specBase) push(join(opts.specBase, 'local.yaml'), 'spec 根(--spec-dir/平台 pointer/最近 .sillyspec)')
|
|
43
|
+
for (let d = start; ; d = dirname(d)) {
|
|
44
|
+
push(join(d, '.sillyspec', 'local.yaml'), 'cwd 祖先链(worktree 内向上命中主仓)')
|
|
45
|
+
const parent = dirname(d)
|
|
46
|
+
if (parent === d) break
|
|
47
|
+
}
|
|
48
|
+
// git 兜底:linked worktree 内 --git-common-dir 指向主仓共享 .git,dirname 即主仓根
|
|
49
|
+
const commonDir = gitQuiet(start, ['rev-parse', '--git-common-dir'])
|
|
50
|
+
if (commonDir) {
|
|
51
|
+
const mainRoot = dirname(resolve(start, String(commonDir).trim()))
|
|
52
|
+
push(join(mainRoot, '.sillyspec', 'local.yaml'), 'git common-dir 反推主仓根')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const hit = candidates.find(c => existsSync(c.path))
|
|
56
|
+
return {
|
|
57
|
+
exists: !!hit,
|
|
58
|
+
path: hit ? hit.path : null,
|
|
59
|
+
source: hit ? hit.source : null,
|
|
60
|
+
searched: candidates.slice(0, 6).map(c => c.path),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 写侧目标解析(`sillyspec local register-repo` 用):已存在 local.yaml → 就地更新该文件
|
|
66
|
+
* (含 worktree → 主仓解析);全不存在 → 取 specBase(或 cwd 链)候选,但候选落在 worktree
|
|
67
|
+
* 副本下(<主仓>/.sillyspec/.runtime/worktrees/ 内)时重定向 git common-dir 主仓根——
|
|
68
|
+
* local.yaml 是 gitignored 不随 worktree checkout,写进副本目录是死配置。
|
|
69
|
+
*
|
|
70
|
+
* @param {string} dir 起点目录
|
|
71
|
+
* @param {{ specBase?: string|null }} [opts]
|
|
72
|
+
* @returns {{ path: string, existed: boolean }}
|
|
73
|
+
*/
|
|
74
|
+
export function resolveLocalYamlWriteTarget(dir, opts = {}) {
|
|
75
|
+
const r = resolveLocalYaml(dir, opts)
|
|
76
|
+
if (r.exists) return { path: r.path, existed: true }
|
|
77
|
+
const start = resolve(dir || process.cwd())
|
|
78
|
+
const preferred = opts.specBase ? join(opts.specBase, 'local.yaml') : join(start, '.sillyspec', 'local.yaml')
|
|
79
|
+
const posix = resolve(preferred).replace(/\\/g, '/')
|
|
80
|
+
if (posix.includes('/.sillyspec/.runtime/worktrees/')) {
|
|
81
|
+
const commonDir = gitQuiet(start, ['rev-parse', '--git-common-dir'])
|
|
82
|
+
if (commonDir) {
|
|
83
|
+
const mainRoot = dirname(resolve(start, String(commonDir).trim()))
|
|
84
|
+
return { path: join(mainRoot, '.sillyspec', 'local.yaml'), existed: false }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { path: resolve(preferred), existed: false }
|
|
88
|
+
}
|