sillyspec 3.19.1 → 3.20.0

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.
@@ -0,0 +1,270 @@
1
+ ---
2
+ name: sillyspec:knowledge
3
+ description: Agent-safe knowledge base commands for SillySpec repositories. Search, inspect, validate, refresh, and propose knowledge entries.
4
+ argument-hint: "search --query \"<text>\" --limit N | inspect --id \"<id>\" | validate | refresh | propose --title \"<title>\" --category <name>"
5
+ version: "3.19.2"
6
+ ---
7
+
8
+ ## 交互规范
9
+ 当需要用户从多个选项中做出选择时,必须使用 Claude Code 内置的 AskUserQuestion 工具,将选项以参数传入。
10
+
11
+ ## 何时使用此技能
12
+ Use this skill when working on a SillySpec repository and the task may depend on prior project knowledge, known issues, implementation patterns, scan-derived learnings, execute-stage knowledge hits, or `.sillyspec/knowledge`.
13
+
14
+ Before implementing changes, **first search the knowledge base** to avoid repeating known work or running into known issues.
15
+
16
+ ## 命令列表
17
+
18
+ ### 1. knowledge search — 搜索知识库
19
+ 在执行任务前搜索相关知识。
20
+
21
+ ```bash
22
+ sillyspec knowledge search --query "<task summary>" --limit 5
23
+ ```
24
+
25
+ **示例:**
26
+ ```bash
27
+ sillyspec knowledge search --query "worktree baseline apply conflict" --limit 5
28
+ sillyspec knowledge search --query "postcheck validation error"
29
+ sillyspec knowledge search --query "GLM usage metadata"
30
+ ```
31
+
32
+ **输出(JSON):**
33
+ ```json
34
+ {
35
+ "ok": true,
36
+ "query": "worktree baseline",
37
+ "matches": [
38
+ {
39
+ "id": "patterns",
40
+ "path": "knowledge/patterns.md",
41
+ "title": "Patterns",
42
+ "summary": "每个阶段对应 `src/stages/` 下的独立模块...",
43
+ "score": 3,
44
+ "tags": ["worktree", "git-worktree", "WorktreeManager"],
45
+ "category": "Patterns"
46
+ }
47
+ ]
48
+ }
49
+ ```
50
+
51
+ **使用场景:**
52
+ - 实现新功能前,检查是否有现有模式可复用
53
+ - 遇到报错前,搜索是否是已知问题
54
+ - 修改特定模块前,了解约定和坑点
55
+
56
+ ---
57
+
58
+ ### 2. knowledge inspect — 读取知识条目详情
59
+ 搜索命中后,读取完整条目内容。
60
+
61
+ ```bash
62
+ sillyspec knowledge inspect --id "<knowledge-id>"
63
+ ```
64
+
65
+ **示例:**
66
+ ```bash
67
+ sillyspec knowledge inspect --id "patterns"
68
+ sillyspec knowledge inspect --id "known-issues/sqljs-wasm-only"
69
+ sillyspec knowledge inspect --id "generated/worktree-baseline-conflict"
70
+ ```
71
+
72
+ **输出(JSON):**
73
+ ```json
74
+ {
75
+ "ok": true,
76
+ "entry": {
77
+ "id": "patterns",
78
+ "title": "Patterns",
79
+ "summary": "每个阶段对应 `src/stages/` 下的独立模块...",
80
+ "zone": "manual",
81
+ "path": "knowledge/patterns.md",
82
+ "meta": {
83
+ "author": "qinyi",
84
+ "created_at": "2026-06-19T12:40:00+08:00",
85
+ "updated_at": "2026-06-24T10:00:00+08:00"
86
+ },
87
+ "body": "完整 markdown 内容..."
88
+ }
89
+ }
90
+ ```
91
+
92
+ **zone 说明:**
93
+ - `manual` — 人工维护的知识(可直接信任)
94
+ - `generated` — 自动提取的知识(scan 生成,需人工审核)
95
+ - `proposed` — 提议待审核的知识(未合并)
96
+
97
+ ---
98
+
99
+ ### 3. knowledge validate — 校验知识库健康度
100
+ 在 commit/archive 前检查知识库是否可用。
101
+
102
+ ```bash
103
+ sillyspec knowledge validate
104
+ ```
105
+
106
+ **输出(JSON):**
107
+ ```json
108
+ {
109
+ "ok": false,
110
+ "errors": [
111
+ {
112
+ "code": "broken_reference",
113
+ "path": "knowledge/generated/foo.md",
114
+ "referenced_in": "INDEX.md",
115
+ "display": "Foo Pattern"
116
+ },
117
+ {
118
+ "code": "empty_file",
119
+ "path": "knowledge/bar.md"
120
+ }
121
+ ],
122
+ "warnings": [
123
+ {
124
+ "code": "too_many_uncategorized",
125
+ "count": 12,
126
+ "path": "knowledge/uncategorized.md"
127
+ },
128
+ {
129
+ "code": "unregistered_file",
130
+ "path": "knowledge/new-file.md"
131
+ }
132
+ ]
133
+ }
134
+ ```
135
+
136
+ **错误码说明:**
137
+ | code | 说明 |
138
+ |---|---|
139
+ | `knowledge_dir_missing` | `.sillyspec/knowledge/` 目录不存在 |
140
+ | `missing_index` | `INDEX.md` 不存在 |
141
+ | `broken_reference` | INDEX.md 引用的文件不存在 |
142
+ | `empty_file` | 知识文件为空 |
143
+ | `too_many_uncategorized` | uncategorized.md 条目 ≥10,需清理 |
144
+ | `unregistered_file` | 手动知识文件未在 INDEX.md 注册 |
145
+
146
+ ---
147
+
148
+ ### 4. knowledge refresh — 从 scan 文档刷新自动知识
149
+ **仅写 generated/ 区,不覆盖 manual/。**
150
+
151
+ ```bash
152
+ sillyspec knowledge refresh
153
+ ```
154
+
155
+ **使用场景:**
156
+ - scan 阶段完成后,自动提取可复用知识
157
+ - archive 阶段完成后,沉淀长期有效的模式
158
+
159
+ **行为:**
160
+ 1. 扫描 `.sillyspec/docs/<project>/scan/*.md`
161
+ 2. 提取每个 `## 章节`(至少 3 行)作为知识条目
162
+ 3. 写入 `.sillyspec/knowledge/generated/<slug>.md`
163
+ 4. 生成 `generated/INDEX.md`
164
+
165
+ **输出(JSON):**
166
+ ```json
167
+ {
168
+ "ok": true,
169
+ "generated_count": 8,
170
+ "new_count": 5,
171
+ "overwritten_count": 3,
172
+ "files": [
173
+ { "file": "generated/worktree-isolation.md", "title": "...", "new": true },
174
+ { "file": "generated/progress-management.md", "title": "...", "new": false }
175
+ ]
176
+ }
177
+ ```
178
+
179
+ **注意事项:**
180
+ - generated/ 区知识是自动生成,不保证准确,需人工审核后合并到 manual/
181
+ - 已有文件会被覆盖( overwrite_count)
182
+
183
+ ---
184
+
185
+ ### 5. knowledge propose — 提议新知识
186
+ 不直接编辑 manual/,而是写入 proposed/ 供审核。
187
+
188
+ ```bash
189
+ sillyspec knowledge propose --title "<title>" --category <category> --body "<content>" --from "<source>"
190
+ ```
191
+
192
+ **参数说明:**
193
+ | 参数 | 必填 | 说明 |
194
+ |---|---|---|
195
+ | `--title` | ✅ | 知识标题 |
196
+ | `--category` | ❌ | 分类(默认 uncategorized),如 known-issues, patterns, conventions |
197
+ | `--body` | ❌ | 知识正文 |
198
+ | `--from` | ❌ | 来源标注(如 execute/quick/scan) |
199
+
200
+ **示例:**
201
+ ```bash
202
+ sillyspec knowledge propose --title "GLM API 超时处理" --category known-issues --body "GLM 模型默认超时 30 秒..." --from "execute"
203
+ ```
204
+
205
+ **输出(JSON):**
206
+ ```json
207
+ {
208
+ "ok": true,
209
+ "id": "proposed/glm-api-timeout",
210
+ "path": "knowledge/proposed/glm-api-timeout.md",
211
+ "title": "GLM API 超时处理",
212
+ "category": "known-issues",
213
+ "new": true,
214
+ "action": "created"
215
+ }
216
+ ```
217
+
218
+ **后续流程:**
219
+ - archive/commit 阶段审核 proposed/ 条目
220
+ - 合格的合并到 manual/,不合格的删除
221
+
222
+ ---
223
+
224
+ ## Agent 使用指南
225
+
226
+ ### 任务前必查
227
+ 在开始实现新功能、修改现有代码、或遇到报错前:
228
+ 1. `knowledge search --query "<任务关键词>"`
229
+ 2. 命中则 `knowledge inspect --id "<id>"`
230
+ 3. 遵循知识中的约定和避坑建议
231
+
232
+ ### 发现新知识
233
+ 执行任务中发现可复用模式或新坑:
234
+ 1. `knowledge propose --title "<标题>" --category <分类> --body "<内容>"`
235
+ 2. 不要直接编辑 `.sillyspec/knowledge/manual/*.md`
236
+
237
+ ### 提交前校验
238
+ commit/archive 前检查知识库状态:
239
+ 1. `knowledge validate`
240
+ 2. 如有 errors,先修复再提交
241
+
242
+ ### 扫描后刷新
243
+ scan 阶段完成后:
244
+ 1. `knowledge refresh`
245
+ 2. 审核生成的 `generated/*.md`,有用的合并到 manual/
246
+
247
+ ---
248
+
249
+ ## 禁止事项
250
+
251
+ ❌ **不要直接编辑** `manual/` 或 `generated/` 中的知识文件
252
+ - 使用 `knowledge propose` 提议新知识
253
+ - 使用 `knowledge refresh` 刷新自动知识
254
+
255
+ ❌ **不要使用 `grep` 或 `cat` 直接读取知识文件**
256
+ - 使用 `knowledge search` 和 `knowledge inspect`
257
+ - 它们使用与 execute 阶段相同的匹配引擎
258
+
259
+ ❌ **不要让 agent 去审核 proposed/ 条目**
260
+ - 提议供人工审核,不要自动合并
261
+ - 只有用户明确要求才合并
262
+
263
+ ---
264
+
265
+ ## 设计原则
266
+
267
+ - **Agent-safe:** 所有输出为 JSON,失败有明确错误码
268
+ - **写保护:** refresh 仅写 generated/,propose 仅写 proposed/,不碰 manual/
269
+ - **匹配引擎:** 使用 `knowledge-match.js`(与 execute 阶段一致)
270
+ - **审计友好:** 每个条目记录来源、创建/更新时间
package/.npmrc-tmp ADDED
@@ -0,0 +1 @@
1
+ //registry.npmjs.org/:_authToken=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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.19.1",
3
+ "version": "3.20.0",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
package/src/index.js CHANGED
@@ -63,6 +63,13 @@ SillySpec CLI — 规范驱动开发工具包
63
63
 
64
64
  sillyspec docs migrate 迁移旧文档到统一结构
65
65
 
66
+ sillyspec knowledge <cmd> 知识库管理(agent-safe,输出 JSON)
67
+ search --query "<text>" --limit N 搜索知识库
68
+ inspect --id "<id>" 读取知识条目详情
69
+ validate 校验知识库健康度
70
+ refresh 从 scan 文档刷新自动知识(仅写 generated/)
71
+ propose --title "<title>" --category <name> 提议新知识(写入 proposed/)
72
+
66
73
  sillyspec platform <cmd> SillyHub 平台同步
67
74
  connect <url> [--token <t>] 连接平台
68
75
  disconnect 断开连接
@@ -304,6 +311,11 @@ async function main() {
304
311
  await runCommand(stageArgs, resolveEffectiveDir(dir), specDir)
305
312
  break
306
313
  }
314
+ case 'knowledge': {
315
+ const { cmdKnowledge } = await import('./stages/knowledge.js')
316
+ await cmdKnowledge(filteredArgs.slice(1), resolveEffectiveDir(dir), { specDir })
317
+ break
318
+ }
307
319
  case 'dashboard': {
308
320
  // Parse dashboard options
309
321
  let port = 3456;
@@ -359,6 +371,7 @@ SillySpec worktree — git worktree 隔离管理
359
371
  用法:
360
372
  sillyspec worktree create <change-name> [--base <branch>] 创建隔离 worktree
361
373
  sillyspec worktree apply <change-name> [--check-only] 校验并应用变更到主工作区
374
+ sillyspec worktree assess <change-name> 风险审计 + 自动 apply
362
375
  sillyspec worktree list 列出所有活跃 worktree
363
376
  sillyspec worktree cleanup <change-name> [--force] 强制清理 worktree
364
377
  sillyspec worktree doctor [--fix] [--stale-hours N] 健康检查 + 修复
@@ -434,6 +447,52 @@ SillySpec worktree — git worktree 隔离管理
434
447
  }
435
448
  break;
436
449
  }
450
+ case 'assess': {
451
+ if (!wtName) {
452
+ console.error('❌ 用法: sillyspec worktree assess <change-name>');
453
+ process.exit(1);
454
+ }
455
+ const { assessApplyRisk } = await import('./worktree-apply.js');
456
+ const assessment = assessApplyRisk(wtName, { cwd: dir });
457
+
458
+ const SEPARATOR = '─'.repeat(32);
459
+ console.log('Worktree Apply Decision');
460
+ console.log(SEPARATOR);
461
+ const decisionIcon = assessment.decision === 'SAFE' ? '✅' : assessment.decision === 'WARNING' ? '⚠️ ' : '🚫';
462
+ console.log(`Decision: ${decisionIcon} ${assessment.decision}`);
463
+ console.log(`Changed files: ${assessment.changedFiles.length}`);
464
+ if (assessment.stats.additions > 0 || assessment.stats.deletions > 0) {
465
+ console.log(`Additions: +${assessment.stats.additions} Deletions: -${assessment.stats.deletions}`);
466
+ }
467
+
468
+ if (assessment.reasons.length > 0) {
469
+ console.log('');
470
+ console.log('Blocked reasons:');
471
+ for (const r of assessment.reasons) r.split('\n').forEach(l => console.log(` ${l}`));
472
+ }
473
+ if (assessment.warnings.length > 0) {
474
+ console.log('');
475
+ console.log('Warnings:');
476
+ for (const w of assessment.warnings) console.log(` ⚠️ ${w}`);
477
+ }
478
+
479
+ console.log('');
480
+ if (assessment.decision === 'SAFE' || assessment.decision === 'WARNING') {
481
+ console.log('Action: auto-applying...');
482
+ const { applyWorktree } = await import('./worktree-apply.js');
483
+ const applyResult = applyWorktree(wtName, { cwd: dir });
484
+ if (applyResult.errors.length > 0) {
485
+ console.error('❌ apply 失败:', applyResult.errors.join('; '));
486
+ } else {
487
+ console.log(`✅ 已自动应用 ${applyResult.changedFiles.length} 个文件变更`);
488
+ }
489
+ } else {
490
+ console.log('Action: blocked');
491
+ console.log(' → 检查变更: sillyspec worktree diff ' + wtName);
492
+ console.log(' → 丢弃变更: sillyspec worktree cleanup ' + wtName);
493
+ }
494
+ break;
495
+ }
437
496
  case 'list': {
438
497
  const items = wm.list();
439
498
  if (items.length === 0) {
package/src/run.js CHANGED
@@ -1067,6 +1067,19 @@ async function executeScanPostcheck(cwd, platformOpts, scanProfile) {
1067
1067
  }
1068
1068
  }
1069
1069
 
1070
+ /**
1071
+ * Plan postcheck 的执行代理:委托给 plan-postcheck.js 模块
1072
+ */
1073
+ async function executePlanPostcheck(cwd, platformOpts) {
1074
+ const { executePlanPostcheck: runPostcheck } = await import('./stages/plan-postcheck.js')
1075
+ const { resolveChangeDir } = await import('./modules.js')
1076
+ await runPostcheck({
1077
+ cwd,
1078
+ specRoot: platformOpts?.specRoot,
1079
+ resolveChangeDir
1080
+ })
1081
+ }
1082
+
1070
1083
  /**
1071
1084
  * sillyspec run <stage> 主命令
1072
1085
  */
@@ -1087,6 +1100,19 @@ export async function runCommand(args, cwd, specDir = null) {
1087
1100
  process.exit(1)
1088
1101
  }
1089
1102
 
1103
+ // ── cwd 纠正:向上查找真实项目根 ──
1104
+ // 防止多 project 工作区中 cwd 停在子目录(如 backend/)时
1105
+ // 状态写入子目录下误建的 .sillyspec,导致状态分裂
1106
+ if (!specDir) {
1107
+ const resolvedRoot = resolveSpecDir(cwd)
1108
+ if (resolvedRoot && resolvedRoot !== join(cwd, '.sillyspec')) {
1109
+ const realRoot = dirname(resolvedRoot)
1110
+ if (realRoot !== cwd) {
1111
+ cwd = realRoot
1112
+ }
1113
+ }
1114
+ }
1115
+
1090
1116
  // 平台模式参数(供 SillyHub 等平台调用)
1091
1117
  // --spec-dir 是统一参数名,--spec-root 保留为向后兼容别名
1092
1118
  const getFlagValue = (name) => {
@@ -1686,6 +1712,8 @@ async function runStage(pm, progress, stageName, cwd, changeName, skipApproval =
1686
1712
  await executeScanPreflight(cwd, platformOpts, scanProfile)
1687
1713
  } else if (cliAction === 'scanPostcheck') {
1688
1714
  await executeScanPostcheck(cwd, platformOpts, scanProfile)
1715
+ } else if (cliAction === 'planPostcheck') {
1716
+ await executePlanPostcheck(cwd, platformOpts)
1689
1717
  }
1690
1718
  stageData.steps[currentIdx].status = 'completed'
1691
1719
  stageData.steps[currentIdx].completedAt = new Date().toLocaleString('zh-CN', { hour12: false })
@@ -2171,29 +2199,43 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
2171
2199
  }
2172
2200
  }
2173
2201
 
2174
- // plan 阶段 "展开任务" 完成后,动态插入任务蓝图协调器步骤
2175
- if (stageName === 'plan' && steps[currentIdx]?.name === '展开任务并分组') {
2176
- const changeDir = resolveChangeDir(cwd, progress)
2177
- if (changeDir) {
2178
- const planFile = join(changeDir, 'plan.md')
2179
- if (existsSync(planFile)) {
2180
- const planContent = readFileSync(planFile, 'utf8')
2181
- const { buildPlanSteps, fixedPrefix, fixedSuffix } = await import('./stages/plan.js')
2182
- const fullSteps = buildPlanSteps(changeDir, planContent)
2183
- const prefixLen = fixedPrefix.length
2184
- const suffixLen = fixedSuffix.length
2185
- const coordinatorSteps = fullSteps.slice(prefixLen, suffixLen > 0 ? -suffixLen : undefined)
2186
- if (coordinatorSteps.length > 0) {
2187
- for (let i = 0; i < coordinatorSteps.length; i++) {
2188
- steps.splice(currentIdx + 1 + i, 0, {
2189
- name: coordinatorSteps[i].name,
2190
- status: 'pending',
2191
- prompt: coordinatorSteps[i].prompt,
2192
- outputHint: coordinatorSteps[i].outputHint,
2193
- optional: coordinatorSteps[i].optional
2194
- })
2202
+ // plan 阶段 "generate_plan" 完成后,动态插入任务蓝图 + postcheck 步骤
2203
+ // 使用稳定 id 匹配,不依赖中文标题
2204
+ if (stageName === 'plan') {
2205
+ const currentStepDef = defSteps?.[currentIdx]
2206
+ const currentStepEntry = steps[currentIdx]
2207
+ const stepId = currentStepDef?.id || currentStepEntry?.id || currentStepEntry?._stepId
2208
+ if (stepId === 'generate_plan') {
2209
+ const changeDir = resolveChangeDir(cwd, progress)
2210
+ if (changeDir) {
2211
+ const planFile = join(changeDir, 'plan.md')
2212
+ if (existsSync(planFile)) {
2213
+ const planContent = readFileSync(planFile, 'utf8')
2214
+ const { buildPlanSteps, fixedPrefix, fixedSuffix } = await import('./stages/plan.js')
2215
+ const fullSteps = buildPlanSteps(changeDir, planContent)
2216
+ const prefixLen = fixedPrefix.length
2217
+ const suffixLen = fixedSuffix.length
2218
+ // 新结构:[...fixedPrefix, coordinatorStep?, postcheckStep?]
2219
+ // fixedSuffix 为空,所以 coordinator + postcheck 都在 prefix 之后
2220
+ const coordinatorSteps = fullSteps.slice(prefixLen, suffixLen > 0 ? -suffixLen : undefined)
2221
+ if (coordinatorSteps.length > 0) {
2222
+ for (let i = 0; i < coordinatorSteps.length; i++) {
2223
+ const stepDef = coordinatorSteps[i]
2224
+ const stepEntry = {
2225
+ id: stepDef.id,
2226
+ name: stepDef.name,
2227
+ status: 'pending',
2228
+ prompt: stepDef.prompt || '',
2229
+ outputHint: stepDef.outputHint,
2230
+ optional: stepDef.optional
2231
+ }
2232
+ // 传递 noAI / _cliAction 属性
2233
+ if (stepDef.noAI) stepEntry.noAI = true
2234
+ if (stepDef._cliAction) stepEntry._cliAction = stepDef._cliAction
2235
+ steps.splice(currentIdx + 1 + i, 0, stepEntry)
2236
+ }
2237
+ console.log(` 📝 已动态插入 ${coordinatorSteps.length} 个步骤(${coordinatorSteps.map(s => s.name).join(', ')})`)
2195
2238
  }
2196
- console.log(` 📝 已动态插入 ${coordinatorSteps.length} 个任务蓝图步骤(${coordinatorSteps.map(s => s.name).join(', ')})`)
2197
2239
  }
2198
2240
  }
2199
2241
  }
@@ -67,7 +67,12 @@ function buildDecisionRecord(id, body) {
67
67
  const priorityMissing = priorityValue.length === 0
68
68
  const fallbackPriority = (['unresolved', 'blocking'].includes(status) || blocker) ? 'P1' : 'P2'
69
69
  const priority = (priorityValue.match(/P[0-2]/i)?.[0] || fallbackPriority).toUpperCase()
70
- return { id: id.toUpperCase(), body, status, priority, blocker, priorityMissing }
70
+ // 解析 supersedes 字段:记录本条决策取代了哪个旧版本
71
+ const supersedesRaw = readDecisionField(body, 'supersedes', '')
72
+ const supersedes = supersedesRaw
73
+ ? supersedesRaw.split(',').map(s => s.trim().toUpperCase().replace(/['"]/g, '')).filter(Boolean)
74
+ : []
75
+ return { id: id.toUpperCase(), body, status, priority, blocker, priorityMissing, supersedes }
71
76
  }
72
77
 
73
78
  function findNextDecisionBoundary(content, startIndex) {
@@ -114,8 +119,16 @@ function parseDecisionRecords(content) {
114
119
  function extractCurrentDecisionIds(content) {
115
120
  const records = parseDecisionRecords(content)
116
121
  if (records.length === 0) return extractIds(content, 'D')
122
+ // 收集所有被 supersedes 声明取代的旧版本 ID
123
+ const supersededIds = new Set()
124
+ for (const r of records) {
125
+ for (const oldId of r.supersedes) {
126
+ supersededIds.add(oldId)
127
+ }
128
+ }
117
129
  return records
118
130
  .filter(r => !['superseded', 'rejected'].includes(r.status))
131
+ .filter(r => !supersededIds.has(r.id)) // 被新版本显式取代的旧版本不再校验
119
132
  .map(r => r.id)
120
133
  .sort()
121
134
  }
@@ -143,14 +156,13 @@ function warnMissingIds(warnings, ids, targetContent, targetName, sourceName) {
143
156
  */
144
157
  function validateScanOutputs(cwd, changeName, context = {}) {
145
158
  const { projectName, specRoot } = context
146
- // 平台模式使用 specRoot,本地模式使用 cwd
147
- const base = specRoot || cwd
148
- // 如果 base 已经是 specDir(有 docs/ 子目录),直接用 base/docs/
149
- // 否则按传统模式拼接 .sillyspec/docs/
150
- const isSpecDir = existsSync(join(base, 'docs'))
159
+ // 平台模式:specRoot 直接是规范目录(含 docs/)
160
+ // 本地模式:规范目录是 cwd/.sillyspec
161
+ // 不用 isSpecDir 启发式猜测——很多项目根目录有自己的 docs/,会误判
162
+ const specBase = specRoot || join(cwd, '.sillyspec')
151
163
  const docsRoot = projectName
152
- ? join(base, isSpecDir ? 'docs' : '.sillyspec/docs', projectName, 'scan')
153
- : join(base, isSpecDir ? 'docs' : '.sillyspec/docs', 'scan')
164
+ ? join(specBase, 'docs', projectName, 'scan')
165
+ : join(specBase, 'docs', 'scan')
154
166
 
155
167
  const requiredDocs = [
156
168
  'ARCHITECTURE.md',
@@ -173,8 +185,8 @@ function validateScanOutputs(cwd, changeName, context = {}) {
173
185
 
174
186
  // 检查 modules 目录
175
187
  const modulesRoot = projectName
176
- ? join(base, isSpecDir ? 'docs' : '.sillyspec/docs', projectName, 'modules')
177
- : join(base, isSpecDir ? 'docs' : '.sillyspec/docs', 'modules')
188
+ ? join(specBase, 'docs', projectName, 'modules')
189
+ : join(specBase, 'docs', 'modules')
178
190
  if (!existsSync(modulesRoot)) {
179
191
  warnings.push('modules 目录不存在')
180
192
  } else {
@@ -297,45 +297,55 @@ const fixedSuffix = [
297
297
  先检查当前 worktree 的隔离模式:
298
298
  \`\`\`bash
299
299
  node -e "import('./src/worktree.js').then(w => { const wm = new w.WorktreeManager(); const m = wm.getMeta('<change-name>'); console.log(m ? JSON.stringify({mode: m.mode, path: m.worktreePath}) : 'no meta'); })"
300
- # 或从 DB 读取:
301
- sqlite3 -json .sillyspec/.runtime/sillyspec.db "SELECT isolation_status, isolation_mode, isolation_reason FROM changes WHERE name='<change-name>'" 2>/dev/null
302
300
  \`\`\`
303
301
 
304
302
  ### 操作(mode = worktree,SillySpec 创建的隔离 worktree)
305
- 1. 运行 \`sillyspec worktree apply --check-only <change-name>\`
306
- 2. 展示 diff 摘要(文件列表 + 变更统计)
307
- 3. 检查结果说明(是否通过文件清单校验)
308
- 4. 用户确认后运行 \`sillyspec worktree apply <change-name>\`
309
- 5. apply 成功 → 运行 \`sillyspec worktree cleanup <change-name>\` → 输出 Worktree: cleaned
310
- 6. apply 失败 → 展示错误详情,用户选择重试或手动处理
311
- 7. 如果用户不想 apply → 运行 \`sillyspec worktree cleanup <change-name>\` 丢弃
312
- 8. 建议下一步:\`sillyspec run verify\`
303
+
304
+ **自动审计流程(不需要用户确认代码):**
305
+
306
+ 1. 运行 \`sillyspec worktree assess <change-name>\` 自动风险审计
307
+ 2. 系统自动检查:
308
+ - patch --check 是否通过
309
+ - 变更是否在 allowed_paths
310
+ - 主工作区 baseline 是否变化
311
+ - 是否有高风险文件(lockfile/migration/配置/入口)
312
+ - diff 规模是否异常
313
+ 3. 输出 Apply Decision:
314
+
315
+ \`\`\`
316
+ Worktree Apply Decision
317
+ ────────────────────────
318
+ Decision: SAFE | WARNING | BLOCKED
319
+ Changed files: N
320
+ Additions: +N Deletions: -N
321
+ Risky files: none | <list>
322
+ Action: auto-applied | blocked
323
+ \`\`\`
324
+
325
+ 4. **SAFE** → 自动 \`sillyspec worktree apply <change-name>\` + cleanup
326
+ 5. **WARNING** → 自动 apply(有警告但不阻断)+ cleanup
327
+ 6. **BLOCKED** → 不 apply,输出原因,提示用户检查:
328
+ - \`sillyspec worktree diff <change-name>\` 查看具体变更
329
+ - \`sillyspec worktree cleanup <change-name>\` 丢弃
330
+ 7. 建议下一步:\`sillyspec run verify\`
313
331
 
314
332
  ### 操作(mode = native-worktree,用户已有的 linked worktree)
315
- 1. 运行 \`sillyspec worktree apply --check-only <change-name>\`
316
- 2. 展示 diff 摘要
317
- 3. 用户确认后运行 \`sillyspec worktree apply <change-name>\`
318
- 4. **不要运行 cleanup** — 这是用户自己的 worktree,SillySpec 不能删除
319
- 5. 输出 Worktree: kept(SillySpec 未创建此 worktree,保留不动)
320
- 6. 建议下一步:\`sillyspec run verify\`
333
+ 1. 同上自动审计流程
334
+ 2. SAFE/WARNING \`sillyspec worktree apply <change-name>\`
335
+ 3. **不要运行 cleanup**
336
+ 4. 输出 Worktree: kept
337
+ 5. 建议下一步:\`sillyspec run verify\`
321
338
 
322
339
  ### 操作(mode = in-place-fallback,降级模式无隔离目录)
323
340
  1. 展示本次执行摘要(\`git diff\` 查看变更)
324
- 2. 跳过 apply 和 cleanup(没有隔离 worktree)
325
- 3. 输出 Worktree: none(降级为 in-place,无隔离目录需要清理)
326
- 4. 建议下一步:\`sillyspec run verify\`
327
-
328
- ### 操作(无 worktree / --no-worktree 模式)
329
- 1. 展示本次执行摘要
330
- 2. 输出 Worktree: none
331
- 3. 提示用户直接使用 \`git diff\` 查看变更
341
+ 2. 跳过 apply 和 cleanup
342
+ 3. 输出 Worktree: none
332
343
  4. 建议下一步:\`sillyspec run verify\`
333
344
 
334
345
  ### 输出
335
- apply 结果 + 下一步建议(或执行摘要)
346
+ Apply Decision + 下一步建议
336
347
 
337
348
  ### 注意
338
- - 如果用户不想 apply → 运行 cleanup 丢弃
339
349
  - 完成后运行 \`sillyspec run execute --done\` 即可自动推进阶段`,
340
350
  outputHint: 'apply 结果',
341
351
  optional: false