sillyspec 3.12.5 → 3.12.7

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.
@@ -21,6 +21,7 @@ $ARGUMENTS
21
21
  1. 运行 `sillyspec run auto --input "<用户需求>"`
22
22
  2. 读取 CLI 输出的 step prompt(包含你的角色描述)
23
23
  3. 执行 prompt 中的操作
24
+ 4. **记录 CLI 输出中显示的 Change 名称**(如 `Change: 2026-06-02-xxx`)
24
25
 
25
26
  ### 步骤循环
26
27
  重复以下循环直到 CLI 输出"全部流程已完成":
@@ -31,6 +32,9 @@ $ARGUMENTS
31
32
  - 纯内部操作 → **直接执行**
32
33
  3. **执行 prompt 要求的操作**
33
34
  4. **完成后运行** `sillyspec run auto --done --output "<你的摘要>"`
35
+ - ⚠️ **必须携带 --change <变更名>**,变更名来自启动时 CLI 输出的 `Change:` 字段
36
+ - 示例:`sillyspec run auto --done --change 2026-06-02-spec-bootstrap-agent-stream-interaction --output "摘要"`
37
+ - **绝不使用 `--change default`**,除非 CLI 启动时明确显示的 Change 名称就是 `default`
34
38
  5. **读取 CLI 输出的下一步 prompt**,回到步骤 1
35
39
 
36
40
  ### 阶段审核门控
@@ -69,6 +73,7 @@ $ARGUMENTS
69
73
  - 不要使用 npx
70
74
  - 不要编造不存在的 CLI 子命令
71
75
  - 遇到命令报错 → 展示错误,暂停等用户介入
76
+ - **每次调用 `sillyspec run auto --done` 都必须携带 `--change <变更名>`**,变更名 = CLI 首次输出中显示的 Change 名称。如果 CLI 首次运行没有显示 Change 名称,从 progress 或用户输入中确认变更名后再调用
72
77
 
73
78
  ### 异常处理
74
79
  - 命令执行失败 → 展示错误信息,暂停等待用户指示
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.12.5",
3
+ "version": "3.12.7",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
@@ -14,7 +14,7 @@ import path from 'path'
14
14
 
15
15
  // ── 常量 ──
16
16
 
17
- const ALLOWED_STAGES = ['execute', 'quick']
17
+ const WORKTREE_STAGES = ['execute'] // 这些阶段必须在 worktree 里
18
18
 
19
19
  const WORKTREE_SEGMENT = '.sillyspec/.runtime/worktrees/'
20
20
 
@@ -410,14 +410,17 @@ export function shouldBlockWrite(filePath, cwd) {
410
410
  const effectiveCwd = cwd || process.cwd()
411
411
  const stage = readCurrentStage(effectiveCwd) || '(none)'
412
412
 
413
- if (!ALLOWED_STAGES.includes(stage)) {
413
+ if (!['execute', 'quick'].includes(stage)) {
414
414
  return {
415
415
  blocked: true,
416
416
  reason: buildStageHint(stage)
417
417
  }
418
418
  }
419
419
 
420
- // 3. 位置门禁
420
+ // quick 阶段直接放行(不要求 worktree)
421
+ if (stage === 'quick') return { blocked: false }
422
+
423
+ // execute 阶段:位置门禁
421
424
  if (isInsideWorktree(absPath)) return { blocked: false }
422
425
 
423
426
  // noWorktree 模式:无隔离环境,禁止源码写入(降级到更严格)
@@ -464,9 +467,8 @@ export function shouldBlockBash(command, cwd) {
464
467
 
465
468
  // 阶段门禁(使用 fallback 读取)
466
469
  const stage = readCurrentStage(effectiveCwd) || '(none)'
467
- const stageOk = ALLOWED_STAGES.includes(stage)
468
470
 
469
- if (!stageOk) {
471
+ if (!['execute', 'quick'].includes(stage)) {
470
472
  // 非 execute/quick 阶段,只允许只读白名单
471
473
  const localConfig = loadLocalConfig(effectiveCwd)
472
474
  const extraReadonly = localConfig.worktreeHook?.readonlyCommands || localConfig['worktree-hook']?.readonlyCommands || []
@@ -477,7 +479,16 @@ export function shouldBlockBash(command, cwd) {
477
479
  }
478
480
  }
479
481
 
480
- // execute/quick 阶段 + 主工作区
482
+ // quick 阶段直接放行(不要求 worktree)
483
+ if (stage === 'quick') {
484
+ // 危险黑名单仍然拦截
485
+ if (matchDangerBlacklist(command)) {
486
+ return { blocked: true, reason: `dangerous command blocked: ${command.trim()}` }
487
+ }
488
+ return { blocked: false }
489
+ }
490
+
491
+ // execute 阶段 + 主工作区
481
492
  const localConfig = loadLocalConfig(effectiveCwd)
482
493
  const extraReadonly = localConfig.worktreeHook?.readonlyCommands || localConfig['worktree-hook']?.readonlyCommands || []
483
494
 
package/src/run.js CHANGED
@@ -213,6 +213,10 @@ async function outputStep(stageName, stepIndex, steps, cwd, changeName, dbProjec
213
213
  promptText = promptText.replace(/<now-datetime>/g, nowDatetime)
214
214
  promptText = promptText.replace(/<now-timestamp>/g, nowTimestamp)
215
215
  promptText = promptText.replace(/<now-date>/g, nowDate)
216
+ // 替换 <change-name> 占位符
217
+ if (changeName && promptText.includes('<change-name>')) {
218
+ promptText = promptText.replace(/<change-name>/g, changeName)
219
+ }
216
220
  console.log(promptText)
217
221
  console.log(`\n### ⚠️ 铁律`)
218
222
  console.log('- **文档是核心资产,代码是文档的产物。** 没有文档就没有代码——文档是 AI 的记忆,是团队协作的基础,是后续维护的唯一依据。任何代码产出必须先有对应的设计/规范文档支撑。')
@@ -275,6 +275,14 @@ design.md 文件路径 + 自审结果
275
275
  - **tasks.md**:任务列表(只列名称和对应文件路径,细节在 plan 阶段展开)
276
276
  - \`git add .sillyspec/\` — 暂存规范文件(不要 commit)
277
277
 
278
+ 所有规范文件头部必须包含 YAML frontmatter:
279
+ \`\`\`\`yaml
280
+ ---
281
+ author: <git-user>
282
+ created_at: <now-datetime>
283
+ ---
284
+ \`\`\`\`
285
+
278
286
  ### proposal.md 格式要求
279
287
  \`\`\`markdown
280
288
  # Proposal
@@ -332,6 +340,7 @@ Then 期望结果
332
340
  - \`requirements.md\`:从 MASTER.md 中提取该包的需求范围(标记为「待完善」)
333
341
  - \`tasks.md\`:创建空任务列表,标记为「待 plan 阶段展开」
334
342
  4. \`git add .sillyspec/\` — 暂存所有新增文件(不要 commit)
343
+ 5. 后续变更包的骨架文件同样必须包含 \`author: <git-user>\` 和 \`created_at: <now-datetime>\`
335
344
 
336
345
  ### 输出
337
346
  所有规范文件路径(含后续变更包目录列表)
@@ -338,7 +338,7 @@ ${taskSummary}
338
338
  ${taskList}
339
339
 
340
340
  ### 调度要求
341
- 1. 同一 Wave 内任务可并行启动子代理
341
+ 1. **同一 Wave 内的任务必须并行启动子代理,禁止串行等待。** Wave 的定义就是"无依赖、可并行",不要自行分析依赖关系。如果有依赖应该在 plan.md 的不同 Wave 中。
342
342
  2. **Reverse Sync**:子代理报告实现与 design.md 不一致时,先检查是代码错了还是文档有遗漏
343
343
  3. **不要频繁编译!** 编译很慢,只在以下情况运行:
344
344
  - 写了大量代码后需要验证语法正确性
@@ -165,6 +165,32 @@ plan.md 总览内容`,
165
165
 
166
166
  // 固定后缀步骤
167
167
  export const fixedSuffix = [
168
+ {
169
+ name: '重排 Wave(基于 depends_on)',
170
+ prompt: `根据蓝图的 depends_on 字段重排 Wave 分组,更新 plan.md。
171
+
172
+ ### 操作
173
+ 1. 读取所有 tasks/task-NN.md 的 frontmatter,提取每个任务的 depends_on 列表
174
+ 2. 拓扑排序:无依赖的任务 → Wave 1,依赖 Wave 1 的 → Wave 2,依此类推
175
+ 3. 检查是否存在循环依赖,如有则报错暂停
176
+ 4. 用重排结果更新 plan.md:
177
+ - Wave 分组(含 checkbox 列表)
178
+ - 任务总表的 Wave 列
179
+ - 依赖关系图(Mermaid)
180
+ - 关键路径
181
+ 5. 如果 Wave 分组与原始 plan.md 一致,只需确认一致即可,不需要重写
182
+
183
+ ### 规则
184
+ - **Wave 是执行单元,同 Wave 内任务必须无依赖(可并行)**
185
+ - 有 depends_on 关系的任务必须在不同的 Wave
186
+ - depends_on 为空的任务放 Wave 1
187
+ - 取决于拓扑排序的最大深度决定 Wave 编号
188
+
189
+ ### 输出
190
+ 重排后的 Wave 分组摘要(如果与原 plan.md 一致则说明一致)`,
191
+ outputHint: 'Wave 重排结果',
192
+ optional: false
193
+ },
168
194
  {
169
195
  name: '审查一致性',
170
196
  prompt: `审查所有 task-N.md 的一致性。
@@ -213,6 +239,9 @@ function buildTaskPrompt(taskNum, taskName, changeDir) {
213
239
  const num = String(taskNum).padStart(2, '0')
214
240
  return `编写任务蓝图 tasks/task-${num}.md
215
241
 
242
+ 当前时间:<now-datetime>(frontmatter 的 created_at 使用此值)
243
+ 当前用户:<git-user>(frontmatter 的 author 使用此值)
244
+
216
245
  ### 任务
217
246
  ${taskName}
218
247
 
@@ -313,6 +342,8 @@ export function buildCoordinatorStep(changeDir, taskNames) {
313
342
  任务编号:task-${num}
314
343
  任务名称:${name}
315
344
  文件路径:${changeDir}/tasks/task-${num}.md
345
+ 当前时间:<now-datetime>(frontmatter 的 created_at 使用此值)
346
+ 当前用户:<git-user>(frontmatter 的 author 使用此值)
316
347
 
317
348
  操作:
318
349
  1. 读取 ${changeDir}/design.md 和 ${changeDir}/plan.md 了解上下文
@@ -383,6 +414,10 @@ allowed_paths:
383
414
  ## 任务清单
384
415
  ${taskList}
385
416
 
417
+ ## 时间和用户
418
+ 当前时间:<now-datetime>
419
+ 当前用户:<git-user>
420
+
386
421
  ## 执行方式(必须严格遵守)
387
422
 
388
423
  **你必须使用 Agent tool 启动子代理来写每个蓝图,不要自己写。**
@@ -40,33 +40,12 @@ export const definition = {
40
40
  outputHint: '任务理解',
41
41
  optional: false
42
42
  },
43
- {
44
- name: '创建 worktree',
45
- prompt: `为本次 quick 任务创建隔离的 git worktree。
46
-
47
- ### 操作
48
- 1. 确定变更名(change name):
49
- - 如携带 \`--change <变更名>\`,使用该变更名
50
- - 否则,生成临时变更名:\`quick-<now-timestamp>\`
51
- 2. 运行 \`sillyspec worktree create <变更名>\`
52
- 3. 记录输出的 worktree 路径(后续步骤需要使用)
53
- 4. 如果创建失败 → 报错并停止(不要在无隔离状态下继续)
54
-
55
- ### 输出
56
- worktree 路径 + 变更名 + 分支名`,
57
- outputHint: 'worktree 路径',
58
- optional: false
59
- },
60
43
  {
61
44
  name: '实现并验证',
62
- prompt: `实现任务。
63
-
64
- ### 工作目录
65
- 你必须在上一步记录的 worktree 路径中工作。
66
- 不要在主工作区修改源码文件。所有代码变更只在 worktree 中进行。
45
+ prompt: `直接在主工作区实现任务。
67
46
 
68
47
  ### 操作
69
- 1. 先读后写:调用已有方法前 \`cat\` 源文件确认签名,\`grep\` 确认方法存在(在 worktree 中读取)
48
+ 1. 先读后写:调用已有方法前 \`cat\` 源文件确认签名,\`grep\` 确认方法存在
70
49
  2. 写代码完成任务
71
50
  3. 如涉及逻辑变更,建议写单元测试验证(不强制,纯配置/文档/小改动可跳过)
72
51
  4. **不要编译!** 除非用户明确要求或改动量很大
@@ -81,24 +60,6 @@ worktree 路径 + 变更名 + 分支名`,
81
60
  outputHint: '实现摘要',
82
61
  optional: false
83
62
  },
84
- {
85
- name: 'apply 并 cleanup',
86
- prompt: `将 worktree 中的变更应用到主工作区并清理。
87
-
88
- ### 操作
89
- 1. 运行 \`sillyspec worktree apply --check-only <变更名>\`
90
- 2. 展示 diff 摘要(文件列表 + 变更统计)
91
- 3. 展示检查结果(是否通过文件清单校验)
92
- 4. 用户确认后运行 \`sillyspec worktree apply <变更名>\`
93
- 5. apply 成功 → 自动 cleanup,进入下一步
94
- 6. apply 失败 → 展示错误详情,用户选择重试或手动处理
95
- 7. 如果用户不想 apply → 运行 \`sillyspec worktree cleanup <变更名>\` 丢弃变更
96
-
97
- ### 输出
98
- apply 结果 + 下一步建议`,
99
- outputHint: 'apply 结果',
100
- optional: false
101
- },
102
63
  {
103
64
  name: '暂存和更新记录',
104
65
  prompt: `Git 暂存并更新任务记录。
@@ -134,7 +134,7 @@ export const definition = {
134
134
 
135
135
  ### 每个子代理的共同要求
136
136
  - **上下文注入**:主 agent 在启动子代理前,必须将以下信息拼入子代理 prompt:
137
- - 项目名(<project>)
137
+ - 项目名(直接用本次 prompt 中的实际项目名)
138
138
  - 断点续扫步骤列出的缺失文档列表(哪些要生成、哪些跳过)
139
139
  - 环境探测结果摘要(构建工具、语言框架、关键依赖)
140
140
  - _env-detect.md 内容(如存在,直接贴入)
package/src/worktree.js CHANGED
@@ -106,7 +106,13 @@ export class WorktreeManager {
106
106
 
107
107
  // 1. 检查 worktree 是否已存在
108
108
  if (existsSync(worktreePath)) {
109
- throw new Error(`worktree already exists: ${name}. Run cleanup first.`);
109
+ // 目录在但 meta.json 不存在(幽灵状态),自动清理
110
+ if (!this.getMeta(name)) {
111
+ console.log(`⚠️ 检测到幽灵 worktree 目录(无 meta.json),自动清理...`);
112
+ try { rmSync(worktreePath, { recursive: true, force: true }); } catch {}
113
+ } else {
114
+ throw new Error(`worktree already exists: ${name}. Run cleanup first.`);
115
+ }
110
116
  }
111
117
 
112
118
  // 2. 检查分支是否已存在
@@ -223,26 +229,27 @@ export class WorktreeManager {
223
229
  cleanup(changeName) {
224
230
  const name = validateChangeName(changeName);
225
231
  const meta = this.getMeta(name);
232
+ const worktreePath = this.getWorktreePath(name);
226
233
 
227
- if (!meta) {
228
- throw new Error(`worktree not found: ${name}。meta.json 不存在,可能已被清理或从未创建。`);
234
+ if (!meta && !existsSync(worktreePath)) {
235
+ throw new Error(`worktree not found: ${name}。meta.json 不存在,目录也不存在,可能已被清理或从未创建。`);
229
236
  }
230
237
 
231
- const worktreePath = meta.worktreePath || this.getWorktreePath(name);
232
- const branch = meta.branch || BRANCH_PREFIX + name;
233
-
234
- // 2. 移除 git worktree
238
+ // 1. 尝试 git worktree remove
235
239
  try {
236
240
  git(this.cwd, `worktree remove ${worktreePath} --force`);
237
241
  } catch {
238
242
  // git worktree remove 失败,尝试直接删除目录
239
- try {
240
- if (existsSync(worktreePath)) {
241
- rmSync(worktreePath, { recursive: true, force: true });
242
- }
243
- } catch (e) {
244
- throw new Error(`清理 worktree 目录失败: ${e.message}`);
243
+ }
244
+ const branch = (meta && meta.branch) || BRANCH_PREFIX + name;
245
+
246
+ // 2. 确保目录已删除
247
+ try {
248
+ if (existsSync(worktreePath)) {
249
+ rmSync(worktreePath, { recursive: true, force: true });
245
250
  }
251
+ } catch (e) {
252
+ throw new Error(`清理 worktree 目录失败: ${e.message}`);
246
253
  }
247
254
 
248
255
  // 3. 删除分支(忽略分支不存在的错误)