sillyspec 3.12.5 → 3.12.6
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/package.json +1 -1
- package/src/hooks/worktree-guard.js +17 -6
- package/src/run.js +4 -0
- package/src/stages/quick.js +2 -41
- package/src/stages/scan.js +1 -1
- package/src/worktree.js +20 -13
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@ import path from 'path'
|
|
|
14
14
|
|
|
15
15
|
// ── 常量 ──
|
|
16
16
|
|
|
17
|
-
const
|
|
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 (!
|
|
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
|
-
//
|
|
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 (!
|
|
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
|
-
//
|
|
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 的记忆,是团队协作的基础,是后续维护的唯一依据。任何代码产出必须先有对应的设计/规范文档支撑。')
|
package/src/stages/quick.js
CHANGED
|
@@ -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\`
|
|
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 暂存并更新任务记录。
|
package/src/stages/scan.js
CHANGED
|
@@ -134,7 +134,7 @@ export const definition = {
|
|
|
134
134
|
|
|
135
135
|
### 每个子代理的共同要求
|
|
136
136
|
- **上下文注入**:主 agent 在启动子代理前,必须将以下信息拼入子代理 prompt:
|
|
137
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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. 删除分支(忽略分支不存在的错误)
|