sillyspec 3.26.6 → 3.26.8
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-doctor/SKILL.md +2 -0
- package/.claude/skills/sillyspec-execute/SKILL.md +1 -1
- package/.claude/skills/sillyspec-knowledge/SKILL.md +1 -1
- package/.claude/skills/sillyspec-plan/SKILL.md +1 -1
- package/.husky/pre-push +9 -2
- package/bin/sillyspec.js +1 -1
- package/package.json +3 -3
- package/packages/dashboard/server/index.js +8 -1
- package/src/config-schema.js +305 -287
- package/src/docs-check.js +333 -0
- package/src/docs-debt.js +226 -0
- package/src/docs-gate.js +106 -0
- package/src/doctor-diagnostics.js +23 -2
- package/src/index.js +1880 -1750
- package/src/init.js +81 -14
- package/src/modules.js +490 -486
- package/src/progress.js +37 -13
- package/src/quicklog.js +19 -4
- package/src/run/command.js +54 -24
- package/src/run/complete-handlers.js +1254 -1128
- package/src/run/complete.js +8 -34
- package/src/run/gates.js +32 -3
- package/src/run/prompt.js +713 -633
- package/src/run/quick-audit.js +137 -123
- package/src/run/shared.js +1109 -929
- package/src/run/stage.js +8 -3
- package/src/scan-staleness.js +90 -0
- package/src/setup.js +27 -9
- package/src/sillyhub-mcp/client.js +149 -14
- package/src/stage-contract-spec.js +15 -0
- package/src/stage-contract.js +8 -5
- package/src/stages/brainstorm-auto.js +0 -1
- package/src/stages/brainstorm.js +1 -1
- package/src/stages/execute.js +985 -967
- package/src/stages/plan-postcheck.js +93 -36
- package/src/stages/verify.js +1 -1
- package/src/sync.js +60 -32
- package/src/task-review.js +38 -6
- package/src/verify-postcheck.js +22 -5
- package/src/worktree-apply.js +6 -3
- package/src/worktree-deps.js +5 -3
- package/src/worktree.js +4 -2
package/src/run/quick-audit.js
CHANGED
|
@@ -1,123 +1,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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
+
}
|