sillyspec 3.8.5 → 3.8.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.
Files changed (163) hide show
  1. package/README.md +0 -6
  2. package/docs/.vitepress/config.mts +45 -0
  3. package/docs/.vitepress/dist/404.html +25 -0
  4. package/docs/.vitepress/dist/assets/app.YytxICdd.js +1 -0
  5. package/docs/.vitepress/dist/assets/chunks/framework.Czhw_PXq.js +19 -0
  6. package/docs/.vitepress/dist/assets/chunks/theme.DusTRZQk.js +1 -0
  7. package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.js +1 -0
  8. package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.lean.js +1 -0
  9. package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  10. package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  11. package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  12. package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  13. package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  14. package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  15. package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  16. package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  17. package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  18. package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  19. package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  20. package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  21. package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  22. package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  23. package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.js +15 -0
  24. package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.lean.js +1 -0
  25. package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.js +4 -0
  26. package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.lean.js +1 -0
  27. package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.js +1 -0
  28. package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.lean.js +1 -0
  29. package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.js +4 -0
  30. package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.lean.js +1 -0
  31. package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.js +5 -0
  32. package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.lean.js +1 -0
  33. package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.js +28 -0
  34. package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.lean.js +1 -0
  35. package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.js +30 -0
  36. package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.lean.js +1 -0
  37. package/docs/.vitepress/dist/assets/style.DFTx90Kk.css +1 -0
  38. package/docs/.vitepress/dist/hashmap.json +1 -0
  39. package/docs/.vitepress/dist/index.html +28 -0
  40. package/docs/.vitepress/dist/sillyspec/commands.html +42 -0
  41. package/docs/.vitepress/dist/sillyspec/dashboard.html +31 -0
  42. package/docs/.vitepress/dist/sillyspec/file-io.html +28 -0
  43. package/docs/.vitepress/dist/sillyspec/getting-started.html +31 -0
  44. package/docs/.vitepress/dist/sillyspec/install.html +32 -0
  45. package/docs/.vitepress/dist/sillyspec/lifecycle.html +55 -0
  46. package/docs/.vitepress/dist/sillyspec/structure.html +57 -0
  47. package/docs/.vitepress/dist/vp-icons.css +1 -0
  48. package/docs/index.md +34 -0
  49. package/docs/sillyspec/commands.md +218 -0
  50. package/docs/sillyspec/dashboard.md +51 -0
  51. package/docs/sillyspec/file-io.md +34 -0
  52. package/docs/sillyspec/getting-started.md +61 -0
  53. package/docs/sillyspec/install.md +51 -0
  54. package/docs/sillyspec/lifecycle.md +146 -0
  55. package/docs/sillyspec/structure.md +62 -0
  56. package/package.json +11 -9
  57. package/packages/dashboard/dist/assets/index-Bh-GPjKY.css +1 -0
  58. package/packages/dashboard/dist/assets/index-CrCn5Gg6.js +17 -0
  59. package/packages/dashboard/dist/index.html +2 -2
  60. package/packages/dashboard/package-lock.json +0 -220
  61. package/packages/dashboard/package.json +5 -8
  62. package/packages/dashboard/server/index.js +106 -255
  63. package/packages/dashboard/server/parser.js +29 -333
  64. package/packages/dashboard/server/watcher.js +131 -203
  65. package/packages/dashboard/src/App.vue +10 -181
  66. package/packages/dashboard/src/components/ActionBar.vue +42 -26
  67. package/packages/dashboard/src/components/CommandPalette.vue +65 -40
  68. package/packages/dashboard/src/components/DetailPanel.vue +53 -68
  69. package/packages/dashboard/src/components/LogStream.vue +33 -13
  70. package/packages/dashboard/src/components/PipelineStage.vue +8 -8
  71. package/packages/dashboard/src/components/PipelineView.vue +45 -80
  72. package/packages/dashboard/src/components/ProjectList.vue +45 -103
  73. package/packages/dashboard/src/components/StageBadge.vue +13 -13
  74. package/packages/dashboard/src/components/StepCard.vue +15 -15
  75. package/packages/dashboard/src/composables/useDashboard.js +6 -20
  76. package/packages/dashboard/src/composables/useKeyboard.js +4 -6
  77. package/packages/dashboard/src/main.js +1 -4
  78. package/packages/dashboard/src/style.css +17 -17
  79. package/src/index.js +12 -123
  80. package/src/init.js +227 -86
  81. package/src/setup.js +9 -1
  82. package/templates/archive.md +120 -0
  83. package/templates/brainstorm.md +170 -0
  84. package/{.claude/skills/sillyspec-commit/SKILL.md → templates/commit.md} +45 -29
  85. package/templates/continue.md +32 -0
  86. package/templates/execute.md +304 -0
  87. package/templates/explore.md +59 -0
  88. package/templates/export.md +21 -0
  89. package/templates/init.md +61 -0
  90. package/templates/plan.md +146 -0
  91. package/templates/quick.md +135 -0
  92. package/templates/scan-quick.md +49 -0
  93. package/templates/scan.md +156 -0
  94. package/templates/skills/playwright-e2e/SKILL.md +340 -0
  95. package/templates/status.md +75 -0
  96. package/templates/verify.md +236 -0
  97. package/templates/workspace-sync.md +99 -0
  98. package/templates/workspace.md +70 -0
  99. package/.claude/skills/sillyspec-archive/SKILL.md +0 -17
  100. package/.claude/skills/sillyspec-auto/SKILL.md +0 -77
  101. package/.claude/skills/sillyspec-brainstorm/SKILL.md +0 -17
  102. package/.claude/skills/sillyspec-continue/SKILL.md +0 -44
  103. package/.claude/skills/sillyspec-doctor/SKILL.md +0 -22
  104. package/.claude/skills/sillyspec-execute/SKILL.md +0 -17
  105. package/.claude/skills/sillyspec-explore/SKILL.md +0 -96
  106. package/.claude/skills/sillyspec-export/SKILL.md +0 -53
  107. package/.claude/skills/sillyspec-init/SKILL.md +0 -170
  108. package/.claude/skills/sillyspec-plan/SKILL.md +0 -52
  109. package/.claude/skills/sillyspec-propose/SKILL.md +0 -17
  110. package/.claude/skills/sillyspec-quick/SKILL.md +0 -17
  111. package/.claude/skills/sillyspec-resume/SKILL.md +0 -111
  112. package/.claude/skills/sillyspec-scan/SKILL.md +0 -17
  113. package/.claude/skills/sillyspec-state/SKILL.md +0 -54
  114. package/.claude/skills/sillyspec-status/SKILL.md +0 -17
  115. package/.claude/skills/sillyspec-verify/SKILL.md +0 -17
  116. package/.claude/skills/sillyspec-workspace/SKILL.md +0 -149
  117. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
  118. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
  119. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
  120. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
  121. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
  122. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
  123. package/.sillyspec/changes/auto-mode/design.md +0 -50
  124. package/.sillyspec/changes/auto-mode/proposal.md +0 -19
  125. package/.sillyspec/changes/auto-mode/requirements.md +0 -21
  126. package/.sillyspec/changes/auto-mode/tasks.md +0 -7
  127. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
  128. package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
  129. package/.sillyspec/changes/run-command-design/design.md +0 -1230
  130. package/.sillyspec/changes/unified-docs-design/design.md +0 -199
  131. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  132. package/.sillyspec/knowledge/INDEX.md +0 -8
  133. package/.sillyspec/knowledge/uncategorized.md +0 -3
  134. package/.sillyspec/projects/sillyspec.yaml +0 -3
  135. package/packages/dashboard/dist/assets/index-D1EVTLmc.js +0 -7446
  136. package/packages/dashboard/dist/assets/index-DGe8CqeP.css +0 -1
  137. package/packages/dashboard/public/logo.jpg +0 -0
  138. package/packages/dashboard/src/components/DocPreview.vue +0 -160
  139. package/packages/dashboard/src/components/DocTree.vue +0 -58
  140. package/packages/dashboard/src/components/ProjectOverview.vue +0 -178
  141. package/packages/dashboard/src/components/detail/DocsDetail.vue +0 -48
  142. package/packages/dashboard/src/components/detail/GitDetail.vue +0 -61
  143. package/packages/dashboard/src/components/detail/TechDetail.vue +0 -43
  144. package/src/derive.js +0 -147
  145. package/src/migrate.js +0 -117
  146. package/src/progress.js +0 -495
  147. package/src/run.js +0 -640
  148. package/src/stages/archive.js +0 -54
  149. package/src/stages/brainstorm.js +0 -239
  150. package/src/stages/doctor.js +0 -312
  151. package/src/stages/execute.js +0 -259
  152. package/src/stages/index.js +0 -35
  153. package/src/stages/plan.js +0 -259
  154. package/src/stages/propose.js +0 -115
  155. package/src/stages/quick.js +0 -64
  156. package/src/stages/scan.js +0 -141
  157. package/src/stages/status.js +0 -65
  158. package/src/stages/verify.js +0 -135
  159. /package/.sillyspec/{changes/brainstorm-archive → specs}/2026-04-05-dashboard-design.md +0 -0
  160. /package/{packages/dashboard → docs/.vitepress}/dist/favicon.jpg +0 -0
  161. /package/{logo.jpg → docs/.vitepress/dist/logo.jpg} +0 -0
  162. /package/{packages/dashboard → docs}/public/favicon.jpg +0 -0
  163. /package/{packages/dashboard/dist → docs/public}/logo.jpg +0 -0
package/src/progress.js DELETED
@@ -1,495 +0,0 @@
1
- /**
2
- * SillySpec ProgressManager — 进度恢复管理
3
- *
4
- * 纯 Node.js,无外部依赖。管理 .sillyspec/.runtime/progress.json。
5
- *
6
- * Schema v2: { project, currentStage, stages: { [name]: { status, steps, startedAt, completedAt } }, lastActive }
7
- */
8
-
9
- import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, readdirSync, unlinkSync } from 'fs';
10
- import { join, resolve, dirname, basename } from 'path';
11
- import { fileURLToPath } from 'url';
12
-
13
- const __filename = fileURLToPath(import.meta.url);
14
- const __dirname = dirname(__filename);
15
- const RUNTIME_DIR = '.sillyspec/.runtime';
16
- const PROGRESS_FILE = 'progress.json';
17
- const BACKUP_FILE = 'progress.json.bak';
18
-
19
- const CURRENT_VERSION = 2;
20
- const VALID_STAGES = ['brainstorm', 'plan', 'execute', 'verify', 'scan', 'quick', 'archive', 'status', 'doctor'];
21
- const VALID_STATUSES = ['pending', 'in-progress', 'completed', 'failed', 'blocked'];
22
-
23
- const STAGE_LABELS = {
24
- brainstorm: '🧠 需求探索',
25
- plan: '📐 实现计划',
26
- execute: '⚡ 波次执行',
27
- verify: '🔍 验证确认',
28
- scan: '🔍 代码扫描',
29
- quick: '⚡ 快速任务',
30
- archive: '📦 归档变更',
31
- status: '📊 状态查看',
32
- doctor: '🩺 项目自检',
33
- };
34
-
35
- function emptyStage() {
36
- return { status: 'pending', steps: [], startedAt: null, completedAt: null };
37
- }
38
-
39
- function makeInitialProgress(project) {
40
- const stages = {};
41
- for (const s of VALID_STAGES) stages[s] = emptyStage();
42
- return { _version: CURRENT_VERSION, project: project || '', currentStage: '', currentChange: null, stages, lastActive: null };
43
- }
44
-
45
- // ── ProgressManager ──
46
-
47
- export class ProgressManager {
48
- // ── 核心读写 ──
49
-
50
- _path(cwd, ...parts) {
51
- return join(cwd, RUNTIME_DIR, ...parts);
52
- }
53
-
54
- read(cwd) {
55
- const progressPath = this._path(cwd, PROGRESS_FILE);
56
- const backupPath = this._path(cwd, BACKUP_FILE);
57
-
58
- for (const p of [progressPath, backupPath]) {
59
- if (!existsSync(p)) continue;
60
- const parsed = this._parseWithRecovery(readFileSync(p, 'utf8'));
61
- if (parsed) {
62
- if (p === backupPath) {
63
- console.log('⚠️ progress.json 损坏,已从备份恢复');
64
- writeFileSync(progressPath, JSON.stringify(parsed, null, 2) + '\n');
65
- }
66
- return parsed;
67
- }
68
- }
69
- return null;
70
- }
71
-
72
- _write(cwd, data) {
73
- const progressPath = this._path(cwd, PROGRESS_FILE);
74
- const tmpPath = progressPath + '.tmp';
75
- this._ensureDir(cwd);
76
- writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\n');
77
- renameSync(tmpPath, progressPath);
78
- }
79
-
80
- _ensureDir(cwd) {
81
- const runtimeDir = this._path(cwd);
82
- if (!existsSync(runtimeDir)) {
83
- mkdirSync(runtimeDir, { recursive: true });
84
- for (const d of ['artifacts', 'history', 'logs', 'templates']) {
85
- mkdirSync(join(runtimeDir, d), { recursive: true });
86
- }
87
- }
88
- }
89
-
90
- _backup(cwd) {
91
- const p = this._path(cwd, PROGRESS_FILE);
92
- if (existsSync(p)) renameSync(p, this._path(cwd, BACKUP_FILE));
93
- }
94
-
95
- // ── CLI 命令 ──
96
-
97
- init(cwd) {
98
- this._ensureDir(cwd);
99
- const progressPath = this._path(cwd, PROGRESS_FILE);
100
-
101
- if (existsSync(progressPath)) {
102
- console.log(`ℹ️ progress.json 已存在,跳过`);
103
- return this.read(cwd);
104
- }
105
-
106
- const project = basename(cwd);
107
- const data = makeInitialProgress(project);
108
- this._write(cwd, data);
109
- console.log(`✅ 已创建 ${join(RUNTIME_DIR, PROGRESS_FILE)}`);
110
-
111
- // 创建 user-inputs.md
112
- const inputsPath = this._path(cwd, 'user-inputs.md');
113
- if (!existsSync(inputsPath)) {
114
- writeFileSync(inputsPath, '# 用户输入记录\n\n> 每步完成时由 AI 自动追加,记录用户所有原话。\n\n');
115
- }
116
-
117
- this._ensureGitignore(cwd);
118
- return data;
119
- }
120
-
121
- setStage(cwd, stage) {
122
- if (!VALID_STAGES.includes(stage)) {
123
- console.log(`❌ 未知阶段: ${stage},可选: ${VALID_STAGES.join(', ')}`);
124
- return;
125
- }
126
-
127
- const data = this._readOrInit(cwd);
128
- if (!data) return;
129
-
130
- if (!data.stages[stage]) data.stages[stage] = emptyStage();
131
- const stageData = data.stages[stage];
132
-
133
- data.currentStage = stage;
134
- if (stageData.status === 'pending') {
135
- stageData.status = 'in-progress';
136
- stageData.startedAt = new Date().toLocaleString('zh-CN',{hour12:false});
137
- }
138
- data.lastActive = new Date().toLocaleString('zh-CN',{hour12:false});
139
-
140
- this._backup(cwd);
141
- this._write(cwd, data);
142
- console.log(`✅ 当前阶段已设为: ${STAGE_LABELS[stage] || stage} (${stageData.status})`);
143
- }
144
-
145
- addStep(cwd, stage, stepName) {
146
- if (!stepName) { console.log('❌ 请指定步骤名称'); return; }
147
- const data = this._requireStage(cwd, stage);
148
- if (!data) return;
149
-
150
- const stageData = data.stages[stage];
151
- if (stageData.steps.some(s => s.name === stepName)) {
152
- console.log(`ℹ️ 步骤 "${stepName}" 已存在于 ${stage}`);
153
- return;
154
- }
155
-
156
- stageData.steps.push({ name: stepName, status: 'pending' });
157
- data.lastActive = new Date().toLocaleString('zh-CN',{hour12:false});
158
-
159
- this._backup(cwd);
160
- this._write(cwd, data);
161
- console.log(`✅ 已添加步骤: ${stage}/${stepName}`);
162
- }
163
-
164
- updateStep(cwd, stage, stepName, options = {}) {
165
- const { status, output } = options;
166
- if (!stepName) { console.log('❌ 请指定步骤名称'); return; }
167
- const data = this._requireStage(cwd, stage);
168
- if (!data) return;
169
-
170
- const stageData = data.stages[stage];
171
- const step = stageData.steps.find(s => s.name === stepName);
172
- if (!step) { console.log(`❌ 步骤不存在: ${stage}/${stepName}`); return; }
173
-
174
- if (status) {
175
- if (!VALID_STATUSES.includes(status)) {
176
- console.log(`❌ 无效状态: ${status},可选: ${VALID_STATUSES.join(', ')}`);
177
- return;
178
- }
179
- step.status = status;
180
- }
181
- if (output !== undefined) step.output = output;
182
-
183
- // 检查是否所有步骤都 completed
184
- if (stageData.steps.length > 0 && stageData.steps.every(s => s.status === 'completed')) {
185
- stageData.status = 'completed';
186
- stageData.completedAt = new Date().toLocaleString('zh-CN',{hour12:false});
187
- console.log(`✅ 阶段 ${stage} 所有步骤已完成,阶段已标记为 completed`);
188
- }
189
-
190
- data.lastActive = new Date().toLocaleString('zh-CN',{hour12:false});
191
- this._backup(cwd);
192
- this._write(cwd, data);
193
- console.log(`✅ 步骤已更新: ${stage}/${stepName} → ${status || step.status}`);
194
- }
195
-
196
- completeStage(cwd, stage) {
197
- if (!VALID_STAGES.includes(stage)) {
198
- console.log(`❌ 未知阶段: ${stage}`);
199
- return;
200
- }
201
-
202
- const data = this._readOrInit(cwd);
203
- if (!data) return;
204
-
205
- if (!data.stages[stage]) data.stages[stage] = emptyStage();
206
- const stageData = data.stages[stage];
207
- stageData.status = 'completed';
208
- stageData.completedAt = new Date().toLocaleString('zh-CN',{hour12:false});
209
-
210
- // 标记所有未完成步骤为 completed
211
- for (const step of stageData.steps) {
212
- if (step.status === 'pending') step.status = 'completed';
213
- }
214
-
215
- // 推进到下一个未完成阶段
216
- const idx = VALID_STAGES.indexOf(stage);
217
- let nextStage = null;
218
- for (let i = idx + 1; i < VALID_STAGES.length; i++) {
219
- const s = data.stages[VALID_STAGES[i]];
220
- if (!s || s.status !== 'completed') {
221
- nextStage = VALID_STAGES[i];
222
- break;
223
- }
224
- }
225
-
226
- if (nextStage) {
227
- data.currentStage = nextStage;
228
- if (!data.stages[nextStage]) data.stages[nextStage] = emptyStage();
229
- if (data.stages[nextStage].status === 'pending') {
230
- data.stages[nextStage].status = 'in-progress';
231
- data.stages[nextStage].startedAt = new Date().toLocaleString('zh-CN',{hour12:false});
232
- }
233
- console.log(`✅ 阶段 ${stage} 已完成,推进到: ${STAGE_LABELS[nextStage] || nextStage}`);
234
- } else {
235
- data.currentStage = stage;
236
- console.log(`✅ 阶段 ${stage} 已完成(已是最后阶段)`);
237
- }
238
-
239
- data.lastActive = new Date().toLocaleString('zh-CN',{hour12:false});
240
-
241
- // 归档到 history/
242
- const historyDir = this._path(cwd, 'history');
243
- mkdirSync(historyDir, { recursive: true });
244
- const ts = new Date().toLocaleString('zh-CN',{hour12:false}).replace(/[:.]/g, '-');
245
- writeFileSync(join(historyDir, `${stage}-${ts}.json`), JSON.stringify({ stage, data: stageData, completedAt: stageData.completedAt }, null, 2) + '\n');
246
-
247
- this._backup(cwd);
248
- this._write(cwd, data);
249
- }
250
-
251
- show(cwd) {
252
- const data = this.read(cwd);
253
- if (!data) {
254
- console.log('❌ 未找到 progress.json,请先运行 sillyspec progress init');
255
- return;
256
- }
257
-
258
- console.log('');
259
- console.log(' ═══════════════════════════════════════');
260
- console.log(` 项目: ${data.project || '(未命名)'}`);
261
- console.log(` 当前阶段: ${STAGE_LABELS[data.currentStage] || data.currentStage || '(无)'}`);
262
- console.log(` 最近活跃: ${data.lastActive ? this._timeAgo(data.lastActive) : '未知'}`);
263
- console.log(' ═══════════════════════════════════════');
264
- console.log('');
265
-
266
- const statusIcons = { pending: '⬜', 'in-progress': '🔵', completed: '✅', failed: '❌', blocked: '🚫' };
267
-
268
- for (const stage of VALID_STAGES) {
269
- const stageData = data.stages[stage] || emptyStage();
270
- const label = STAGE_LABELS[stage] || stage;
271
- const icon = statusIcons[stageData.status] || '⬜';
272
- const isCurrent = data.currentStage === stage ? ' ◀' : '';
273
-
274
- console.log(` ${icon} ${label}${isCurrent}`);
275
-
276
- if (stageData.steps && stageData.steps.length > 0) {
277
- for (const step of stageData.steps) {
278
- const si = statusIcons[step.status] || '○';
279
- const out = step.output ? ` — ${step.output.slice(0, 60)}` : '';
280
- console.log(` ${si} ${step.name}${out}`);
281
- }
282
- }
283
-
284
- if (stageData.startedAt) {
285
- console.log(` 开始: ${new Date(stageData.startedAt).toLocaleString('zh-CN')}`);
286
- }
287
- if (stageData.completedAt) {
288
- console.log(` 完成: ${new Date(stageData.completedAt).toLocaleString('zh-CN')}`);
289
- }
290
- }
291
-
292
- // 批量进度
293
- if (data.batchProgress) {
294
- const batchLine = this._renderBatchProgress(data.batchProgress);
295
- if (batchLine) {
296
- console.log('');
297
- console.log(` ${batchLine}`);
298
- }
299
- }
300
-
301
- console.log('');
302
- }
303
-
304
- status(cwd) {
305
- this.show(cwd);
306
- }
307
-
308
- async validate(cwd, deep = false) {
309
- const data = this.read(cwd);
310
- if (!data) { console.log('❌ 无法读取 progress.json'); return false; }
311
-
312
- const errors = [];
313
- if (!data._version || !Number.isInteger(data._version) || data._version < 1) {
314
- errors.push(`_version 缺失或无效(期望正整数,实际为 ${JSON.stringify(data._version)})`);
315
- }
316
- if (!data.stages || typeof data.stages !== 'object') errors.push('缺少 stages');
317
- if (!VALID_STAGES.every(s => data.stages[s])) errors.push('缺少阶段定义');
318
-
319
- if (errors.length === 0) { console.log('✅ progress.json 格式正确'); return true; }
320
-
321
- console.log(`⚠️ 发现问题,尝试修复...`);
322
- let fixed = { ...data, stages: { ...data.stages } };
323
- let changed = false;
324
- if (!fixed.project) {
325
- fixed.project = basename(cwd);
326
- changed = true;
327
- }
328
- if (!fixed._version || !Number.isInteger(fixed._version) || fixed._version < 1) {
329
- fixed._version = CURRENT_VERSION;
330
- changed = true;
331
- }
332
- for (const s of VALID_STAGES) {
333
- if (!fixed.stages[s]) { fixed.stages[s] = emptyStage(); changed = true; }
334
- }
335
- if (changed) {
336
- this._backup(cwd);
337
- this._write(cwd, fixed);
338
- console.log('✅ 已修复并备份');
339
- }
340
-
341
- if (deep) {
342
- try {
343
- const { deriveState } = await import('./derive.js');
344
- const result = deriveState(cwd, { mode: 'full', fix: true, pm: this, progress: this.read(cwd) });
345
- if (result.issues.length > 0) {
346
- console.log(`\n📋 deriveState 深度校验(${result.issues.length} 项):`);
347
- for (const issue of result.issues) {
348
- const icon = issue.severity === 'issue' ? '🔴' : issue.severity === 'warning' ? '🟡' : '⚪';
349
- console.log(` ${icon} ${issue.stage} step ${issue.step}: ${issue.message}`);
350
- }
351
- if (result.fixed > 0) console.log(` 🔧 已自动修复 ${result.fixed} 项`);
352
- } else {
353
- console.log('✅ deriveState 深度校验通过,无不一致');
354
- }
355
- } catch (e) {
356
- console.log(`⚠️ deriveState 校验失败: ${e.message}`);
357
- }
358
- }
359
-
360
- return true;
361
- }
362
-
363
- reset(cwd, stage) {
364
- this._ensureDir(cwd);
365
- this._backup(cwd);
366
-
367
- if (stage) {
368
- const data = this.read(cwd);
369
- if (!data) { console.log('❌ 无法读取 progress.json'); return; }
370
- if (!data.stages[stage]) { console.log(`❌ 未知阶段: ${stage}`); return; }
371
- data.stages[stage] = emptyStage();
372
- data.lastActive = new Date().toLocaleString('zh-CN',{hour12:false});
373
- this._write(cwd, data);
374
- console.log(`✅ 已重置阶段: ${stage}`);
375
- } else {
376
- const p = this._path(cwd, PROGRESS_FILE);
377
- if (existsSync(p)) { unlinkSync(p); console.log('✅ 已重置所有进度(备份已保留)'); }
378
- else console.log('ℹ️ 无进度文件可重置');
379
- }
380
- }
381
-
382
- complete(cwd, stage) {
383
- this.completeStage(cwd, stage);
384
- }
385
-
386
- // ── 内部辅助 ──
387
-
388
- _readOrInit(cwd) {
389
- let data = this.read(cwd);
390
- if (!data) {
391
- this._ensureDir(cwd);
392
- const progressPath = this._path(cwd, PROGRESS_FILE);
393
- if (!existsSync(progressPath)) {
394
- data = makeInitialProgress(basename(cwd));
395
- this._write(cwd, data);
396
- } else {
397
- console.log('❌ progress.json 损坏,请运行 sillyspec progress validate');
398
- return null;
399
- }
400
- }
401
- return data;
402
- }
403
-
404
- _requireStage(cwd, stage) {
405
- if (!VALID_STAGES.includes(stage)) {
406
- console.log(`❌ 未知阶段: ${stage},可选: ${VALID_STAGES.join(', ')}`);
407
- return null;
408
- }
409
- const data = this._readOrInit(cwd);
410
- if (!data) return null;
411
- if (!data.stages[stage]) data.stages[stage] = emptyStage();
412
- return data;
413
- }
414
-
415
- _parseWithRecovery(jsonString) {
416
- try { return JSON.parse(jsonString); } catch {}
417
-
418
- let fixed = jsonString.replace(/,\s*([}\]])/g, '$1');
419
- fixed = fixed.replace(/([{,]\s*)'([^']+)'(\s*:)/g, '$1"$2"$3');
420
- fixed = fixed.replace(/:\s*'([^']*)'([,}\]])/g, ':"$1"$2');
421
- try { return JSON.parse(fixed); } catch {}
422
-
423
- const lastBrace = fixed.lastIndexOf('}');
424
- if (lastBrace > 0) {
425
- let open = 0;
426
- for (const ch of fixed.substring(0, lastBrace + 1)) {
427
- if (ch === '{') open++;
428
- if (ch === '}') open--;
429
- }
430
- try { return JSON.parse(fixed.substring(0, lastBrace + 1) + '}'.repeat(Math.max(0, open))); } catch {}
431
- }
432
- return null;
433
- }
434
-
435
- _timeAgo(dateStr) {
436
- if (!dateStr) return '未知';
437
- const diff = Date.now() - new Date(dateStr).getTime();
438
- const m = Math.floor(diff / 60000);
439
- if (m < 1) return '刚刚';
440
- if (m < 60) return `${m} 分钟前`;
441
- const h = Math.floor(m / 60);
442
- if (h < 24) return `${h} 小时前`;
443
- return `${Math.floor(h / 24)} 天前`;
444
- }
445
-
446
- // ── 批量进度 ──
447
-
448
- updateBatchProgress(cwd, batchData) {
449
- const data = this._readOrInit(cwd);
450
- if (!data) return;
451
-
452
- if (!data.batchProgress) {
453
- data.batchProgress = { total: 0, completed: 0, failed: 0, skipped: 0 };
454
- }
455
- if (batchData.total !== undefined) data.batchProgress.total = batchData.total;
456
- if (batchData.completed !== undefined) data.batchProgress.completed = batchData.completed;
457
- if (batchData.failed !== undefined) data.batchProgress.failed = batchData.failed;
458
- if (batchData.skipped !== undefined) data.batchProgress.skipped = batchData.skipped;
459
-
460
- data.lastActive = new Date().toLocaleString('zh-CN', { hour12: false });
461
- this._backup(cwd);
462
- this._write(cwd, data);
463
- }
464
-
465
- readBatchProgress(cwd) {
466
- const data = this.read(cwd);
467
- return data?.batchProgress || null;
468
- }
469
-
470
- _renderBatchProgress(batchProgress) {
471
- if (!batchProgress || !batchProgress.total) return null;
472
- const { total, completed = 0, failed = 0, skipped = 0 } = batchProgress;
473
- const done = Math.min(completed + failed + skipped, total);
474
- const barLen = 20;
475
- const filled = Math.round((completed / total) * barLen);
476
- const bar = '█'.repeat(filled) + '░'.repeat(barLen - filled);
477
- const parts = [];
478
- if (failed > 0) parts.push(`${failed} 失败`);
479
- if (skipped > 0) parts.push(`${skipped} 跳过`);
480
- const suffix = parts.length ? ` (${parts.join(', ')})` : '';
481
- return `📊 批量进度: ${bar} ${completed}/${total}${suffix}`;
482
- }
483
-
484
- _ensureGitignore(cwd) {
485
- const gitignorePath = join(cwd, '.gitignore');
486
- const rule = '.sillyspec/.runtime/';
487
- if (existsSync(gitignorePath)) {
488
- const content = readFileSync(gitignorePath, 'utf8');
489
- if (content.includes(rule)) return;
490
- writeFileSync(gitignorePath, content.trimEnd() + '\n' + rule + '\n');
491
- } else {
492
- writeFileSync(gitignorePath, rule + '\n');
493
- }
494
- }
495
- }