sillyspec 3.18.4 → 3.18.5
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-auto/SKILL.md +1 -1
- package/.claude/skills/sillyspec-doctor/SKILL.md +1 -1
- package/.claude/skills/sillyspec-execute/SKILL.md +2 -0
- package/.claude/skills/sillyspec-resume/SKILL.md +5 -5
- package/.claude/skills/sillyspec-state/SKILL.md +8 -8
- package/docs/sillyspec/file-lifecycle/storage-and-state.md +1 -1
- package/package.json +1 -1
- package/packages/dashboard/server/index.js +4 -3
- package/packages/dashboard/server/parser.js +30 -70
- package/src/index.js +21 -5
- package/src/stages/execute.js +1 -0
- package/src/worktree-apply.js +5 -4
|
@@ -23,6 +23,8 @@ description: 用于按 plan 执行代码实现。适合用户说"开始写代码
|
|
|
23
23
|
- Worktree 路径在 Step 3(确认 worktree 路径)中输出,后续子代理的 cwd 必须设为该路径
|
|
24
24
|
- **禁止跳过 worktree 或在主仓库直接写代码**
|
|
25
25
|
- 如果 worktree 创建失败,CLI 会报错并退出,需要排查后再重试
|
|
26
|
+
- **未提交的文件、dirty 状态等不影响 worktree 创建和进入,直接按 CLI 输出的 worktree 路径操作即可**
|
|
27
|
+
- 不要自行检查 git 状态来判断是否可以进入 worktree,CLI 会自动处理
|
|
26
28
|
|
|
27
29
|
## 用户指令
|
|
28
30
|
$ARGUMENTS
|
|
@@ -22,15 +22,15 @@ description: 恢复工作 — 从中断处继续
|
|
|
22
22
|
sillyspec progress show
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
### 2.
|
|
25
|
+
### 2. 如果有活跃变更
|
|
26
26
|
|
|
27
|
-
从
|
|
27
|
+
从 `sillyspec progress show` 输出中提取并展示当前状态。
|
|
28
28
|
|
|
29
29
|
然后问用户:
|
|
30
30
|
1. 直接继续执行下一步
|
|
31
31
|
2. 查看更多细节
|
|
32
32
|
|
|
33
|
-
### 3.
|
|
33
|
+
### 3. 如果没有活跃变更
|
|
34
34
|
|
|
35
35
|
自动探测项目状态:
|
|
36
36
|
|
|
@@ -64,5 +64,5 @@ cat .sillyspec/ROADMAP.md 2>/dev/null
|
|
|
64
64
|
|
|
65
65
|
### 4. 关键原则
|
|
66
66
|
|
|
67
|
-
-
|
|
68
|
-
-
|
|
67
|
+
- 进度数据存储在 SQLite 数据库中(`.sillyspec/.runtime/sillyspec.db`),通过 `sillyspec progress show` 命令查看
|
|
68
|
+
- 进度随 `sillyspec run <stage> --done` 自动更新,不需要手动保存
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sillyspec:state
|
|
3
|
-
description: 查看当前工作状态 — 显示
|
|
3
|
+
description: 查看当前工作状态 — 显示 SillySpec 进度
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
你现在是 SillySpec 的状态查看器。
|
|
7
7
|
|
|
8
8
|
## 流程
|
|
9
9
|
|
|
10
|
-
### 1.
|
|
10
|
+
### 1. 读取进度
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
sillyspec
|
|
13
|
+
sillyspec progress show
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
### 2.
|
|
16
|
+
### 2. 如果有活跃变更
|
|
17
17
|
|
|
18
18
|
格式化展示当前状态:
|
|
19
19
|
|
|
@@ -33,9 +33,9 @@ sillyspec run state --status 2>/dev/null
|
|
|
33
33
|
> **阻塞项**:
|
|
34
34
|
> - xxx(如无则省略)
|
|
35
35
|
|
|
36
|
-
### 3.
|
|
36
|
+
### 3. 如果没有活跃变更
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
提示用户项目还没有开始:
|
|
39
39
|
|
|
40
40
|
> 📊 还没有工作记录。
|
|
41
41
|
>
|
|
@@ -44,11 +44,11 @@ sillyspec run state --status 2>/dev/null
|
|
|
44
44
|
> - 已有项目:`/sillyspec:scan`
|
|
45
45
|
> - 恢复中断的工作:`/sillyspec:resume`
|
|
46
46
|
>
|
|
47
|
-
>
|
|
47
|
+
> 进度数据会在 `sillyspec init` 时自动创建到 SQLite 数据库中。
|
|
48
48
|
|
|
49
49
|
### 注意
|
|
50
50
|
|
|
51
51
|
- 这是只读命令,**不修改任何文件**
|
|
52
52
|
- `/sillyspec:status` 查看项目整体进度(change 文件级别)
|
|
53
|
-
- `/sillyspec:state`
|
|
53
|
+
- `/sillyspec:state` 查看当前工作状态(阶段/步骤级别)
|
|
54
54
|
- 两者互补:status 看"有什么",state 看"在做什么"
|
|
@@ -43,7 +43,7 @@ created_at: 2026-06-04 16:25:42
|
|
|
43
43
|
| `batch_progress` | 批量任务统计 |
|
|
44
44
|
| `approvals` | 平台审批状态 |
|
|
45
45
|
|
|
46
|
-
`progress.js` 通过 SQL 读写这些表,并组装成兼容旧 progress
|
|
46
|
+
`progress.js` 通过 SQL 读写这些表,并组装成兼容旧 progress 格式的 JS 对象。进度数据仅存储在 SQLite 数据库中,不再使用 progress.json 文件。
|
|
47
47
|
|
|
48
48
|
注意:`db.js` 的 `project.schema_version` DDL 默认值是 `4`,但 `progress.js` 的 `CURRENT_VERSION` 是 `3`,并在初始化/写入时使用 `3`。文档不要把这里写成稳定的 v4 schema 事实。
|
|
49
49
|
|
package/package.json
CHANGED
|
@@ -48,12 +48,13 @@ function startProgressWatch(projectPath) {
|
|
|
48
48
|
progressWatchers.get(projectPath).refCount++
|
|
49
49
|
return
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
// Watch the SQLite database file for changes (replaces old progress.json watch)
|
|
52
|
+
const dbFile = join(projectPath, '.sillyspec', '.runtime', 'sillyspec.db')
|
|
53
|
+
if (!existsSync(dbFile)) return
|
|
53
54
|
|
|
54
55
|
let timer = null
|
|
55
56
|
try {
|
|
56
|
-
const watcher = watch(
|
|
57
|
+
const watcher = watch(dbFile, (eventType) => {
|
|
57
58
|
if (timer) clearTimeout(timer)
|
|
58
59
|
timer = setTimeout(() => {
|
|
59
60
|
timer = null
|
|
@@ -77,18 +77,13 @@ export function parseProjectOverview(projectPath) {
|
|
|
77
77
|
|
|
78
78
|
// --- Last active ---
|
|
79
79
|
const sillyspecDir = join(projectPath, '.sillyspec')
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
// Progress is stored in SQLite (.sillyspec/.runtime/sillyspec.db), not progress.json
|
|
81
|
+
// Use mtime of the DB file as a fallback for lastActive
|
|
82
|
+
const dbPath = join(sillyspecDir, '.runtime', 'sillyspec.db')
|
|
83
|
+
if (existsSync(dbPath)) {
|
|
82
84
|
try {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
for (const stageData of Object.values(progress.stages)) {
|
|
86
|
-
if (stageData.lastActive && (!result.lastActive || new Date(stageData.lastActive) > new Date(result.lastActive))) {
|
|
87
|
-
result.lastActive = stageData.lastActive
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (progress.lastActive) result.lastActive = progress.lastActive
|
|
85
|
+
const s = statSync(dbPath)
|
|
86
|
+
result.lastActive = s.mtime.toISOString()
|
|
92
87
|
} catch {}
|
|
93
88
|
}
|
|
94
89
|
if (!result.lastActive) {
|
|
@@ -432,81 +427,46 @@ export function parseSillyspecDocsTree(projectPath) {
|
|
|
432
427
|
}
|
|
433
428
|
|
|
434
429
|
/**
|
|
435
|
-
* Parse project state from .sillyspec
|
|
430
|
+
* Parse project state from .sillyspec SQLite database via CLI.
|
|
431
|
+
* Progress data is stored in SQLite (.sillyspec/.runtime/sillyspec.db),
|
|
432
|
+
* accessed through `sillyspec progress show`.
|
|
436
433
|
* @param {string} projectPath - Path to the project directory
|
|
437
|
-
* @returns {object} Project state with currentStage,
|
|
434
|
+
* @returns {object|null} Project state with currentStage, stages, lastActive
|
|
438
435
|
*/
|
|
439
436
|
export function parseProjectState(projectPath) {
|
|
440
437
|
const sillyspecDir = join(projectPath, '.sillyspec')
|
|
438
|
+
const dbPath = join(sillyspecDir, '.runtime', 'sillyspec.db')
|
|
441
439
|
|
|
442
|
-
if (!existsSync(sillyspecDir)) {
|
|
440
|
+
if (!existsSync(sillyspecDir) || !existsSync(dbPath)) {
|
|
443
441
|
return null
|
|
444
442
|
}
|
|
445
443
|
|
|
446
444
|
let currentStage = ''
|
|
447
|
-
let nextStep = null
|
|
448
|
-
let progress = { stages: {} }
|
|
449
|
-
let stages = []
|
|
450
|
-
let specs = []
|
|
451
445
|
let lastActive = null
|
|
452
446
|
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
progress = progressData
|
|
459
|
-
currentStage = progressData.currentStage || ''
|
|
460
|
-
stages = Object.keys(progressData.stages || {})
|
|
461
|
-
|
|
462
|
-
// Find last active
|
|
463
|
-
if (progressData.lastActive) lastActive = progressData.lastActive
|
|
464
|
-
if (progressData.stages) {
|
|
465
|
-
for (const [stageName, stageData] of Object.entries(progressData.stages)) {
|
|
466
|
-
if (stageData.lastActive || stageData.startedAt) {
|
|
467
|
-
const t = stageData.lastActive || stageData.startedAt
|
|
468
|
-
if (!lastActive || new Date(t) > new Date(lastActive)) lastActive = t
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
} catch (err) {
|
|
473
|
-
// Progress file exists but couldn't be parsed
|
|
474
|
-
}
|
|
475
|
-
}
|
|
447
|
+
// Use DB file mtime as lastActive indicator
|
|
448
|
+
try {
|
|
449
|
+
const s = statSync(dbPath)
|
|
450
|
+
lastActive = s.mtime.toISOString()
|
|
451
|
+
} catch {}
|
|
476
452
|
|
|
477
|
-
//
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const specPath = join(specsDir, f)
|
|
487
|
-
try {
|
|
488
|
-
const content = readFileSync(specPath, 'utf-8')
|
|
489
|
-
const titleMatch = content.match(/^#\s+(.+)$/m)
|
|
490
|
-
return {
|
|
491
|
-
name: f,
|
|
492
|
-
title: titleMatch ? titleMatch[1] : f,
|
|
493
|
-
path: specPath
|
|
494
|
-
}
|
|
495
|
-
} catch {
|
|
496
|
-
return { name: f, title: f, path: specPath }
|
|
497
|
-
}
|
|
498
|
-
})
|
|
499
|
-
} catch (err) {
|
|
500
|
-
// Specs directory couldn't be read
|
|
501
|
-
}
|
|
453
|
+
// Use CLI to read current stage from SQLite
|
|
454
|
+
try {
|
|
455
|
+
const output = execSync('sillyspec progress show 2>/dev/null', {
|
|
456
|
+
cwd: projectPath, encoding: 'utf-8', timeout: 5000
|
|
457
|
+
})
|
|
458
|
+
const stageMatch = output.match(/当前阶段:\s*(\S+)/)
|
|
459
|
+
if (stageMatch) currentStage = stageMatch[1]
|
|
460
|
+
} catch {
|
|
461
|
+
// CLI unavailable or no active change
|
|
502
462
|
}
|
|
503
463
|
|
|
504
464
|
return {
|
|
505
465
|
currentStage,
|
|
506
|
-
nextStep,
|
|
507
|
-
progress,
|
|
508
|
-
stages,
|
|
509
|
-
specs,
|
|
466
|
+
nextStep: null,
|
|
467
|
+
progress: { stages: {} },
|
|
468
|
+
stages: [],
|
|
469
|
+
specs: [],
|
|
510
470
|
lastActive
|
|
511
471
|
}
|
|
512
472
|
}
|
package/src/index.js
CHANGED
|
@@ -145,6 +145,21 @@ async function main() {
|
|
|
145
145
|
targetDir = resolve(filteredArgs[1]);
|
|
146
146
|
filteredArgs.splice(1, 1);
|
|
147
147
|
}
|
|
148
|
+
// ── 自动纠正 cwd ──
|
|
149
|
+
// 当 agent 在 worktree 内跑 pnpm 等工具后 shell cwd 可能被改变,
|
|
150
|
+
// 导致 sillyspec 命令找不到 .sillyspec。此函数尝试从 git root 解析。
|
|
151
|
+
function resolveEffectiveDir(baseDir) {
|
|
152
|
+
if (existsSync(join(baseDir, '.sillyspec'))) return baseDir
|
|
153
|
+
try {
|
|
154
|
+
const { execSync } = require('child_process')
|
|
155
|
+
const gitRoot = execSync('git rev-parse --show-toplevel', {
|
|
156
|
+
cwd: baseDir, encoding: 'utf8', timeout: 5000
|
|
157
|
+
}).trim()
|
|
158
|
+
if (gitRoot && existsSync(join(gitRoot, '.sillyspec'))) return gitRoot
|
|
159
|
+
} catch {}
|
|
160
|
+
return baseDir
|
|
161
|
+
}
|
|
162
|
+
|
|
148
163
|
const dir = targetDir;
|
|
149
164
|
|
|
150
165
|
if (command === 'init' && !existsSync(dir)) {
|
|
@@ -167,6 +182,7 @@ async function main() {
|
|
|
167
182
|
break;
|
|
168
183
|
case 'progress': {
|
|
169
184
|
const pm = new ProgressManager();
|
|
185
|
+
const progDir = resolveEffectiveDir(dir);
|
|
170
186
|
const subCommand = filteredArgs[1];
|
|
171
187
|
const stageIdx = filteredArgs.indexOf('--stage');
|
|
172
188
|
const stage = stageIdx >= 0 && filteredArgs[stageIdx + 1] ? filteredArgs[stageIdx + 1] : null;
|
|
@@ -176,18 +192,18 @@ async function main() {
|
|
|
176
192
|
|
|
177
193
|
switch (subCommand) {
|
|
178
194
|
case 'init':
|
|
179
|
-
pm.init(
|
|
195
|
+
pm.init(progDir);
|
|
180
196
|
break;
|
|
181
197
|
case 'status':
|
|
182
198
|
case 'show':
|
|
183
|
-
pm.show(
|
|
199
|
+
pm.show(progDir, progChangeName);
|
|
184
200
|
break;
|
|
185
201
|
case 'check':
|
|
186
|
-
await pm.checkConsistency(
|
|
202
|
+
await pm.checkConsistency(progDir, progChangeName);
|
|
187
203
|
break;
|
|
188
204
|
case 'repair': {
|
|
189
205
|
const repairApply = filteredArgs.includes('--apply');
|
|
190
|
-
await pm.repairConsistency(
|
|
206
|
+
await pm.repairConsistency(progDir, { apply: repairApply, changeName: progChangeName });
|
|
191
207
|
break;
|
|
192
208
|
}
|
|
193
209
|
case 'validate':
|
|
@@ -270,7 +286,7 @@ async function main() {
|
|
|
270
286
|
}
|
|
271
287
|
case 'run': {
|
|
272
288
|
const { runCommand } = await import('./run.js')
|
|
273
|
-
await runCommand(filteredArgs.slice(1), dir, specDir)
|
|
289
|
+
await runCommand(filteredArgs.slice(1), resolveEffectiveDir(dir), specDir)
|
|
274
290
|
break
|
|
275
291
|
}
|
|
276
292
|
case 'dashboard': {
|
package/src/stages/execute.js
CHANGED
|
@@ -165,6 +165,7 @@ const fixedPrefix = [
|
|
|
165
165
|
- **worktree 已由 CLI 在 execute 阶段启动时自动创建,不要自行创建或跳过**
|
|
166
166
|
- **后续所有子代理的 cwd 必须设为该 worktree 路径**
|
|
167
167
|
- 如果 meta.json 不存在(说明创建失败),停止并报错
|
|
168
|
+
- **不要自行检查 git dirty/uncommitted 状态来判断是否可以进入 worktree,CLI 已自动处理**
|
|
168
169
|
|
|
169
170
|
### 输出
|
|
170
171
|
worktree 路径 + 分支名 + 模式
|
package/src/worktree-apply.js
CHANGED
|
@@ -214,7 +214,7 @@ export function applyWorktree(changeName, { cwd, checkOnly = false } = {}) {
|
|
|
214
214
|
const patchFiles = hasAllowList
|
|
215
215
|
? [...allowSet].filter(f => changedFiles.includes(f))
|
|
216
216
|
: changedFiles;
|
|
217
|
-
const fileArgs = patchFiles.
|
|
217
|
+
const fileArgs = patchFiles.length > 0 ? `-- ${patchFiles.join(' ')}` : '';
|
|
218
218
|
|
|
219
219
|
// 创建临时文件
|
|
220
220
|
const tmpDir = mkdtempSync(join(tmpdir(), 'sillyspec-patch-'));
|
|
@@ -236,7 +236,7 @@ export function applyWorktree(changeName, { cwd, checkOnly = false } = {}) {
|
|
|
236
236
|
|
|
237
237
|
// tracked 文件:git diff baseHash
|
|
238
238
|
if (trackedFiles.length > 0) {
|
|
239
|
-
const trackedArgs = trackedFiles.
|
|
239
|
+
const trackedArgs = trackedFiles.length > 0 ? `-- ${trackedFiles.join(' ')}` : '';
|
|
240
240
|
patchContent += execSync(
|
|
241
241
|
`git diff --binary ${diffBase} ${trackedArgs}`,
|
|
242
242
|
{ cwd: worktreePath, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
@@ -245,11 +245,12 @@ export function applyWorktree(changeName, { cwd, checkOnly = false } = {}) {
|
|
|
245
245
|
|
|
246
246
|
// untracked 新文件:git add 到 index,git diff --cached,然后 reset
|
|
247
247
|
if (untrackedPatchFiles.length > 0) {
|
|
248
|
-
const addArgs = untrackedPatchFiles.
|
|
248
|
+
const addArgs = untrackedPatchFiles.length > 0 ? `-- ${untrackedPatchFiles.join(' ')}` : '';
|
|
249
249
|
git(worktreePath, `add ${addArgs}`);
|
|
250
250
|
try {
|
|
251
|
+
const diffCachedArgs = untrackedPatchFiles.length > 0 ? `-- ${untrackedPatchFiles.join(' ')}` : '';
|
|
251
252
|
patchContent += execSync(
|
|
252
|
-
`git diff --binary --cached ${
|
|
253
|
+
`git diff --binary --cached ${diffCachedArgs}`,
|
|
253
254
|
{ cwd: worktreePath, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
254
255
|
);
|
|
255
256
|
} finally {
|