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.
- package/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
- package/.npmrc-tmp +1 -0
- package/package.json +1 -1
- package/src/index.js +59 -0
- package/src/run.js +64 -22
- package/src/stage-contract.js +22 -10
- package/src/stages/execute.js +36 -26
- package/src/stages/knowledge.js +498 -0
- package/src/stages/plan-postcheck.js +513 -0
- package/src/stages/plan.js +152 -346
- package/src/workflow.js +116 -10
- package/src/worktree-apply.js +141 -0
- package/test/decision-supersede.test.mjs +277 -0
- package/test/plan-optimization.test.mjs +572 -0
- package/test/stage-contract.test.mjs +5 -4
|
@@ -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
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
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
const
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
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
|
}
|
package/src/stage-contract.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
//
|
|
147
|
-
|
|
148
|
-
//
|
|
149
|
-
|
|
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(
|
|
153
|
-
: join(
|
|
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(
|
|
177
|
-
: join(
|
|
188
|
+
? join(specBase, 'docs', projectName, 'modules')
|
|
189
|
+
: join(specBase, 'docs', 'modules')
|
|
178
190
|
if (!existsSync(modulesRoot)) {
|
|
179
191
|
warnings.push('modules 目录不存在')
|
|
180
192
|
} else {
|
package/src/stages/execute.js
CHANGED
|
@@ -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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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.
|
|
316
|
-
2.
|
|
317
|
-
3.
|
|
318
|
-
4.
|
|
319
|
-
5.
|
|
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
|
|
325
|
-
3. 输出 Worktree: none
|
|
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
|
-
|
|
346
|
+
Apply Decision + 下一步建议
|
|
336
347
|
|
|
337
348
|
### 注意
|
|
338
|
-
- 如果用户不想 apply → 运行 cleanup 丢弃
|
|
339
349
|
- 完成后运行 \`sillyspec run execute --done\` 即可自动推进阶段`,
|
|
340
350
|
outputHint: 'apply 结果',
|
|
341
351
|
optional: false
|