sillyspec 3.26.8 → 3.26.10

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.
Files changed (55) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +1 -2
  2. package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -2
  3. package/.claude/skills/sillyspec-execute/SKILL.md +11 -3
  4. package/.claude/skills/sillyspec-explore/SKILL.md +0 -1
  5. package/.claude/skills/sillyspec-plan/SKILL.md +1 -2
  6. package/.claude/skills/sillyspec-quick/SKILL.md +4 -5
  7. package/.claude/skills/sillyspec-scan/SKILL.md +2 -2
  8. package/.claude/skills/sillyspec-status/SKILL.md +0 -1
  9. package/.claude/skills/sillyspec-verify/SKILL.md +4 -3
  10. package/README.md +5 -9
  11. package/package.json +2 -2
  12. package/packages/dashboard/server/index.js +14 -8
  13. package/src/change-risk-profile.js +5 -207
  14. package/src/config-schema.js +6 -1
  15. package/src/constants.js +97 -106
  16. package/src/db-engine.js +1 -1
  17. package/src/docs-check.js +34 -5
  18. package/src/git-helper.js +2 -2
  19. package/src/hooks/worktree-guard.js +1 -1
  20. package/src/index.js +96 -13
  21. package/src/init.js +14 -3
  22. package/src/machine-interface.js +7 -4
  23. package/src/progress/stage-machine.js +3 -0
  24. package/src/quicklog.js +203 -4
  25. package/src/run/command.js +101 -3
  26. package/src/run/complete-handlers.js +1399 -1254
  27. package/src/run/complete.js +9 -1
  28. package/src/run/concurrent-detect.js +22 -0
  29. package/src/run/gates.js +67 -1
  30. package/src/run/prompt.js +761 -713
  31. package/src/run/quick-audit.js +146 -137
  32. package/src/run/shared.js +1215 -1109
  33. package/src/run/stage.js +17 -1
  34. package/src/scan-diff.js +388 -0
  35. package/src/scan-staleness.js +18 -13
  36. package/src/spec-sync.js +319 -0
  37. package/src/stage-contract-spec.js +1 -1
  38. package/src/stage-review.js +1 -1
  39. package/src/stages/archive.js +2 -1
  40. package/src/stages/brainstorm.js +2 -2
  41. package/src/stages/execute.js +1003 -985
  42. package/src/stages/plan-postcheck.js +22 -1
  43. package/src/stages/plan.js +74 -40
  44. package/src/stages/quick.js +13 -5
  45. package/src/stages/scan.js +4 -3
  46. package/src/stages/verify.js +14 -12
  47. package/src/sync.js +22 -0
  48. package/src/task-review.js +12 -4
  49. package/src/workflow.js +8 -185
  50. package/src/worktree-apply.js +122 -0
  51. package/src/worktree.js +50 -6
  52. package/templates/claude-instruction.md +1 -1
  53. package/templates/prompts/taskcard-rules.md +1 -0
  54. package/templates/prompts/verify-probes.md +2 -2
  55. package/templates/skills/sillyspec-onboard/SKILL.md +0 -92
@@ -1,137 +1,146 @@
1
- /**
2
- * run/quick-audit.js(W6 Step2 从 run.js 抽出)。
3
- *
4
- * quick 审计结论打印 + quick 多变更关联选择(自洽,不依赖 run.js 闭包)。
5
- * 安全锚:run.js 始终 barrel,本模块函数 run.js import 回来;无 test 直接 import,无需 re-export。
6
- *
7
- * 路径修正(相对 src/run/):
8
- * - resolveQuickLinkedChanges 的 quick-recommend.js 动态 import './quick-recommend.js' → '../quick-recommend.js'
9
- * - 脏文件信号改用 safeGit(带 safe.directory,Q3);不再 import execSync
10
- * - parsePorcelainPath 从 ./shared.js 重 import;checkbox 顶部 import
11
- */
12
- import { checkbox } from '@inquirer/prompts'
13
- import { parsePorcelainPath, safeGit } from './shared.js'
14
-
15
- /**
16
- * 打印 quick 完成审计结论(按 review.status 输出 SAFE/WARNING/BLOCKED)。
17
- */
18
- export function printQuickAuditReview(review) {
19
- if (review.status === 'blocked') {
20
- console.error(`\n🚫 quick 变更边界审计 — BLOCKED:`)
21
- for (const r of review.reasons) {
22
- console.error(` - ${r}`)
23
- }
24
- console.error(`\n quick 已停止:请恢复/拆分这些变更,或重新运行 quick 并显式声明范围。`)
25
- // 解锁咒语须在 BLOCKED 分支也给出 —— 原只在 confirm/review 分支打印,
26
- // 普通 --done 走这里只看到「恢复/拆分」反方向指引,正确解法(带 flag 重跑)根本不出现。
27
- // 删除:默认 fail-closed(高危操作),但 --allow-delete 显式 opt-in 可解锁(对称 --allow-new)。
28
- if (review.deletedFiles && review.deletedFiles.length > 0) {
29
- console.error(`\n ⛔ 本次含删除文件 ${review.deletedFiles.length} 个:${review.deletedFiles.join(', ')}`)
30
- console.error(` 删除是破坏性操作默认 fail-closed,确认删除这些文件请带 --allow-delete 显式解锁:`)
31
- console.error(` sillyspec run quick --done --allow-delete --change <id> --output "..."`)
32
- console.error(` (--force-baseline / --allow-new 不能解锁删除;不确认请 git restore 撤回删除后再 --done,或走完整流程 execute 由 review 把关)`)
33
- } else {
34
- console.error(` 如确认接受这些变更,重新运行 --done 时带上对应 flag 即可解锁:`)
35
- console.error(` sillyspec run quick --done --force-baseline --allow-new --change <id> --output "..."`)
36
- console.error(` (--force-baseline 覆盖受保护/危险文件如 src/run.js;--allow-new 允许新增文件;--allow-delete 允许删除文件)`)
37
- }
38
- } else if (review.status === 'warning') {
39
- console.warn(`\n⚠️ quick 变更边界审计 — WARNING:`)
40
- for (const r of review.reasons) {
41
- console.warn(` - ${r}`)
42
- }
43
- } else {
44
- // 区分「本轮新增」(changedFiles,已扣前序 baseline)vs「累计暂存」(stagedTotal,全部未提交)。
45
- // 仅当存在前序 baseline 残留(累计 > 本轮新增)时才追加括注,避免普通场景冗余。
46
- const stagedTotal = typeof review.stagedTotal === 'number' ? review.stagedTotal : review.changedFiles.length
47
- if (stagedTotal > review.changedFiles.length) {
48
- console.log(`\n✅ quick 变更边界审计 — SAFE (本轮新增变更 ${review.changedFiles.length} 个文件;累计暂存 ${stagedTotal} 个,含前序 baseline ${stagedTotal - review.changedFiles.length} 个未计入本轮)`)
49
- } else {
50
- console.log(`\n✅ quick 变更边界审计 — SAFE (本轮新增变更 ${review.changedFiles.length} 个文件)`)
51
- }
52
- }
53
- // D-8 文档欠账显性化(advisory,独立于 status 三态都打):改了源码没动文档 → 一行欠账标记。
54
- // 不阻断、不解锁、纯显性化——累积欠账可事后审计 QUICKLOG reasons 追溯。
55
- if (review.docSyncHint && review.docSyncHint.touchedSource > 0 && review.docSyncHint.docFiles.length === 0) {
56
- console.warn(`\n📝 文档欠账标记(D-8):本次 ${review.docSyncHint.touchedSource} 个源码文件改动未同步任何模块文档。`)
57
- // O-1(docs-signals-o12):模块归属——从"改了 N 文件"到"欠在哪"(matchFilesToModules 纯函数,map 缺失时空数组降级)
58
- if (Array.isArray(review.docSyncHint.modules) && review.docSyncHint.modules.length > 0) {
59
- console.warn(` 涉及模块:${review.docSyncHint.modules.map(m => m.id).join(' · ')}(模块卡待同步,execute 场景详见 [docs-debt] 块)`)
60
- }
61
- console.warn(` quick 不强制文档同步,但欠账已记录(QUICKLOG reasons)。若改动触及接口/契约,建议顺手同步模块文档。`)
62
- }
63
- if (review.docsCheckHint && review.docsCheckHint.invalid > 0) {
64
- console.warn(`\n📎 文档引用失效(docs check):本次改动的文档含 ${review.docsCheckHint.invalid}/${review.docsCheckHint.total} 处失效 file:line 引用。`)
65
- console.warn(` 行号漂移 → 更新到当前源码;文件删改名 → 更新引用路径。跑 sillyspec docs check 可看完整清单。`)
66
- }
67
- }
68
-
69
- /**
70
- * quick 阶段多变更交互式选择关联变更。
71
- * - 0 活跃变更 → [](仅记 QUICKLOG,不关联)
72
- * - 1 活跃变更 → 默认关联它(保持现状友好,不弹交互)
73
- * - ≥2 活跃变更 + 交互 → checkbox 多选(推荐项默认勾,空选 = 不关联)
74
- * - ≥2 活跃变更 + 非交互 → [](不关联)+ 提示用 --change a,b
75
- */
76
- export async function resolveQuickLinkedChanges({ pm, cwd, specDir, quickFiles, taskDescription, nonInteractive }) {
77
- let activeChanges = []
78
- try {
79
- activeChanges = pm.listChanges(cwd)
80
- } catch {
81
- activeChanges = []
82
- }
83
- if (activeChanges.length === 0) return []
84
- if (activeChanges.length === 1) return [activeChanges[0]]
85
-
86
- if (nonInteractive || !process.stdin.isTTY) {
87
- console.log('💡 非交互环境,已默认不关联变更;如需关联请用 --change a,b')
88
- return []
89
- }
90
-
91
- // 脏文件(推荐信号之一)。safeGit 带 safe.directory,避免 linked worktree/容器/挂载点下裸
92
- // `git status` 抛错致推荐信号静默丢失(multi-agent-review Q3 同类)。推荐非关键路径,失败回退 []。
93
- // trim:false:porcelain 首行前导空格是状态码,trim 会削掉致 parsePorcelainPath 丢首字符。
94
- let baselineFiles = []
95
- const statusResult = safeGit(cwd, ['status', '--porcelain'], { trim: false })
96
- if (!statusResult.error) {
97
- baselineFiles = (statusResult.value || '').split('\n').filter(Boolean)
98
- .map(l => parsePorcelainPath(l))
99
- .filter(Boolean)
100
- .filter(f => !f.startsWith('.sillyspec/'))
101
- }
102
-
103
- // 推荐打分(脏文件 + 任务描述双信号)
104
- let recommendations = []
105
- try {
106
- // quick-recommend.js 在 src/,本模块在 src/run/ → 退一层(真环依赖,保留动态 import)
107
- const { recommendChanges } = await import('../quick-recommend.js')
108
- recommendations = recommendChanges({ activeChanges, specDir, baselineFiles, quickFiles, taskDescription })
109
- } catch {
110
- recommendations = activeChanges.map(name => ({ name, score: 0, reasons: [] }))
111
- }
112
- const scoreMap = new Map(recommendations.map(r => [r.name, r.score]))
113
- const reasonMap = new Map(recommendations.map(r => [r.name, r.reasons]))
114
- const recommendedSet = new Set(recommendations.filter(r => r.score > 0).map(r => r.name))
115
-
116
- // 按推荐分降序展示
117
- const ordered = [...activeChanges].sort((a, b) =>
118
- (scoreMap.get(b) || 0) - (scoreMap.get(a) || 0) || a.localeCompare(b))
119
-
120
- const choices = ordered.map(name => {
121
- const reasons = reasonMap.get(name) || []
122
- const isRec = recommendedSet.has(name)
123
- return {
124
- name: `${isRec ? '⭐ ' : ' '}${name}`,
125
- value: name,
126
- description: reasons.length > 0 ? reasons.join(';') : '无推荐信号',
127
- checked: isRec,
128
- }
129
- })
130
-
131
- console.log('🔗 检测到多个活跃变更,选择本次 quick 关联哪些(可多选;不勾选任何项 = 仅记 QUICKLOG,不关联变更)')
132
- if (recommendedSet.size > 0) {
133
- console.log(' ⭐ = 基于脏文件/任务描述推荐,已默认勾选')
134
- }
135
- const selected = await checkbox({ message: '关联变更(空格切换,回车确认)', choices })
136
- return selected
137
- }
1
+ /**
2
+ * run/quick-audit.js(W6 Step2 从 run.js 抽出)。
3
+ *
4
+ * quick 审计结论打印 + quick 多变更关联选择(自洽,不依赖 run.js 闭包)。
5
+ * 安全锚:run.js 始终 barrel,本模块函数 run.js import 回来;无 test 直接 import,无需 re-export。
6
+ *
7
+ * 路径修正(相对 src/run/):
8
+ * - resolveQuickLinkedChanges 的 quick-recommend.js 动态 import './quick-recommend.js' → '../quick-recommend.js'
9
+ * - 脏文件信号改用 safeGit(带 safe.directory,Q3);不再 import execSync
10
+ * - parsePorcelainPath 从 ./shared.js 重 import;checkbox 顶部 import
11
+ */
12
+ import { checkbox } from '@inquirer/prompts'
13
+ import { parsePorcelainPath, safeGit } from './shared.js'
14
+
15
+ /**
16
+ * 打印 quick 完成审计结论(按 review.status 输出 SAFE/WARNING/BLOCKED)。
17
+ */
18
+ export function printQuickAuditReview(review) {
19
+ if (review.status === 'blocked') {
20
+ console.error(`\n🚫 quick 变更边界审计 — BLOCKED:`)
21
+ for (const r of review.reasons) {
22
+ console.error(` - ${r}`)
23
+ }
24
+ console.error(`\n quick 已停止:请恢复/拆分这些变更,或重新运行 quick 并显式声明范围。`)
25
+ // 解锁咒语须在 BLOCKED 分支也给出 —— 原只在 confirm/review 分支打印,
26
+ // 普通 --done 走这里只看到「恢复/拆分」反方向指引,正确解法(带 flag 重跑)根本不出现。
27
+ // 删除:默认 fail-closed(高危操作),但 --allow-delete 显式 opt-in 可解锁(对称 --allow-new)。
28
+ if (review.deletedFiles && review.deletedFiles.length > 0) {
29
+ console.error(`\n ⛔ 本次含删除文件 ${review.deletedFiles.length} 个:${review.deletedFiles.join(', ')}`)
30
+ console.error(` 删除是破坏性操作默认 fail-closed,确认删除这些文件请带 --allow-delete 显式解锁:`)
31
+ console.error(` sillyspec run quick --done --allow-delete --change <id> --output "..."`)
32
+ console.error(` (--force-baseline / --allow-new 不能解锁删除;不确认请 git restore 撤回删除后再 --done,或走完整流程 execute 由 review 把关)`)
33
+ } else {
34
+ console.error(` 如确认接受这些变更,重新运行 --done 时带上对应 flag 即可解锁:`)
35
+ console.error(` sillyspec run quick --done --force-baseline --allow-new --change <id> --output "..."`)
36
+ console.error(` (--force-baseline 覆盖受保护/危险文件如 src/run.js;--allow-new 允许新增文件;--allow-delete 允许删除文件)`)
37
+ }
38
+ } else if (review.status === 'warning') {
39
+ console.warn(`\n⚠️ quick 变更边界审计 — WARNING:`)
40
+ for (const r of review.reasons) {
41
+ console.warn(` - ${r}`)
42
+ }
43
+ } else {
44
+ // 区分「本轮新增」(changedFiles,已扣前序 baseline)vs「累计暂存」(stagedTotal,全部未提交)。
45
+ // 仅当存在前序 baseline 残留(累计 > 本轮新增)时才追加括注,避免普通场景冗余。
46
+ const stagedTotal = typeof review.stagedTotal === 'number' ? review.stagedTotal : review.changedFiles.length
47
+ if (stagedTotal > review.changedFiles.length) {
48
+ console.log(`\n✅ quick 变更边界审计 — SAFE (本轮新增变更 ${review.changedFiles.length} 个文件;累计暂存 ${stagedTotal} 个,含前序 baseline ${stagedTotal - review.changedFiles.length} 个未计入本轮)`)
49
+ } else {
50
+ console.log(`\n✅ quick 变更边界审计 — SAFE (本轮新增变更 ${review.changedFiles.length} 个文件)`)
51
+ }
52
+ }
53
+ // D-8 文档欠账显性化(advisory,独立于 status 三态都打):改了源码没动文档 → 一行欠账标记。
54
+ // 不阻断、不解锁、纯显性化——累积欠账可事后审计 QUICKLOG reasons 追溯。
55
+ if (review.docSyncHint && review.docSyncHint.touchedSource > 0 && review.docSyncHint.docFiles.length === 0) {
56
+ console.warn(`\n📝 文档欠账标记(D-8):本次 ${review.docSyncHint.touchedSource} 个源码文件改动未同步任何模块文档。`)
57
+ // O-1(docs-signals-o12):模块归属——从"改了 N 文件"到"欠在哪"(matchFilesToModules 纯函数,map 缺失时空数组降级)
58
+ if (Array.isArray(review.docSyncHint.modules) && review.docSyncHint.modules.length > 0) {
59
+ console.warn(` 涉及模块:${review.docSyncHint.modules.map(m => m.id).join(' · ')}(模块卡待同步,execute 场景详见 [docs-debt] 块)`)
60
+ }
61
+ console.warn(` quick 不强制文档同步,欠账已随本条 QUICKLOG「审计:」行落盘(可事后追溯)。若改动触及接口/契约,建议顺手同步模块文档。`)
62
+ }
63
+ if (review.docsCheckHint && review.docsCheckHint.invalid > 0) {
64
+ console.warn(`\n📎 文档引用失效(docs check):本次改动的文档含 ${review.docsCheckHint.invalid}/${review.docsCheckHint.total} 处失效 file:line 引用。`)
65
+ console.warn(` 行号漂移 → 更新到当前源码;文件删改名 → 更新引用路径。跑 sillyspec docs check 可看完整清单。`)
66
+ console.warn(` 引用格式:\`src/foo.js:42\`(或 42-48)+ 同行反引号代码符号(如 \`runDocsCheck\`)——符号可让 --suggest 给出候选行号。`)
67
+ }
68
+ // task-03: 活文档引用漂移(advisory 不阻断,不改 status 三态判定)——方向与上方互补:
69
+ // 上方查「本次改的文档」,这里查「本次改的 src 被活文档(platform-interface-map 等)引用」。
70
+ if (review.docsCheckHint && review.docsCheckHint.livingDocDrift) {
71
+ const drift = review.docsCheckHint.livingDocDrift
72
+ const total = typeof drift.total === 'number' ? drift.total : drift.files.length
73
+ console.warn(`\n📎 活文档引用漂移:改动 ${drift.files.length}/${total} 被 ${drift.docs.join('、')} 引用——建议顺手跑 docs check 修引用行号。`)
74
+ console.warn(` 文件:${drift.files.join(', ')}`)
75
+ }
76
+ }
77
+
78
+ /**
79
+ * quick 阶段多变更交互式选择关联变更。
80
+ * - 0 活跃变更 → [](仅记 QUICKLOG,不关联)
81
+ * - 1 活跃变更 → 默认关联它(保持现状友好,不弹交互)
82
+ * - ≥2 活跃变更 + 交互 → checkbox 多选(推荐项默认勾,空选 = 不关联)
83
+ * - ≥2 活跃变更 + 非交互 → [](不关联)+ 提示用 --change a,b
84
+ */
85
+ export async function resolveQuickLinkedChanges({ pm, cwd, specDir, quickFiles, taskDescription, nonInteractive }) {
86
+ let activeChanges = []
87
+ try {
88
+ activeChanges = pm.listChanges(cwd)
89
+ } catch {
90
+ activeChanges = []
91
+ }
92
+ if (activeChanges.length === 0) return []
93
+ if (activeChanges.length === 1) return [activeChanges[0]]
94
+
95
+ if (nonInteractive || !process.stdin.isTTY) {
96
+ console.log('💡 非交互环境,已默认不关联变更;如需关联请用 --change a,b')
97
+ return []
98
+ }
99
+
100
+ // 脏文件(推荐信号之一)。safeGit 带 safe.directory,避免 linked worktree/容器/挂载点下裸
101
+ // `git status` 抛错致推荐信号静默丢失(multi-agent-review Q3 同类)。推荐非关键路径,失败回退 []。
102
+ // trim:false:porcelain 首行前导空格是状态码,trim 会削掉致 parsePorcelainPath 丢首字符。
103
+ let baselineFiles = []
104
+ const statusResult = safeGit(cwd, ['status', '--porcelain'], { trim: false })
105
+ if (!statusResult.error) {
106
+ baselineFiles = (statusResult.value || '').split('\n').filter(Boolean)
107
+ .map(l => parsePorcelainPath(l))
108
+ .filter(Boolean)
109
+ .filter(f => !f.startsWith('.sillyspec/'))
110
+ }
111
+
112
+ // 推荐打分(脏文件 + 任务描述双信号)
113
+ let recommendations = []
114
+ try {
115
+ // quick-recommend.js 在 src/,本模块在 src/run/ → 退一层(真环依赖,保留动态 import)
116
+ const { recommendChanges } = await import('../quick-recommend.js')
117
+ recommendations = recommendChanges({ activeChanges, specDir, baselineFiles, quickFiles, taskDescription })
118
+ } catch {
119
+ recommendations = activeChanges.map(name => ({ name, score: 0, reasons: [] }))
120
+ }
121
+ const scoreMap = new Map(recommendations.map(r => [r.name, r.score]))
122
+ const reasonMap = new Map(recommendations.map(r => [r.name, r.reasons]))
123
+ const recommendedSet = new Set(recommendations.filter(r => r.score > 0).map(r => r.name))
124
+
125
+ // 按推荐分降序展示
126
+ const ordered = [...activeChanges].sort((a, b) =>
127
+ (scoreMap.get(b) || 0) - (scoreMap.get(a) || 0) || a.localeCompare(b))
128
+
129
+ const choices = ordered.map(name => {
130
+ const reasons = reasonMap.get(name) || []
131
+ const isRec = recommendedSet.has(name)
132
+ return {
133
+ name: `${isRec ? '⭐ ' : ' '}${name}`,
134
+ value: name,
135
+ description: reasons.length > 0 ? reasons.join(';') : '无推荐信号',
136
+ checked: isRec,
137
+ }
138
+ })
139
+
140
+ console.log('🔗 检测到多个活跃变更,选择本次 quick 关联哪些(可多选;不勾选任何项 = 仅记 QUICKLOG,不关联变更)')
141
+ if (recommendedSet.size > 0) {
142
+ console.log(' ⭐ = 基于脏文件/任务描述推荐,已默认勾选')
143
+ }
144
+ const selected = await checkbox({ message: '关联变更(空格切换,回车确认)', choices })
145
+ return selected
146
+ }