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
@@ -1,363 +1,59 @@
1
- import { readFileSync, existsSync, readdirSync, statSync } from 'fs'
2
- import { execSync } from 'child_process'
1
+ import { readFileSync, existsSync, readdirSync } from 'fs'
3
2
  import { join } from 'path'
4
3
  import { fileURLToPath } from 'url'
5
4
  import { dirname } from 'path'
6
5
 
7
6
  const __dirname = dirname(fileURLToPath(import.meta.url))
8
7
 
9
- /**
10
- * Parse docs tree for a project
11
- * @param {string} projectPath - Path to the project directory
12
- * @returns {object} Docs tree grouped by type
13
- */
14
- // Known framework detection keywords in package.json dependencies
15
- const FRAMEWORK_PATTERNS = [
16
- { keys: ['react', 'react-dom'], name: 'React' },
17
- { keys: ['vue'], name: 'Vue' },
18
- { keys: ['next'], name: 'Next.js' },
19
- { keys: ['nuxt'], name: 'Nuxt' },
20
- { keys: ['express'], name: 'Express' },
21
- { keys: ['koa'], name: 'Koa' },
22
- { keys: ['fastify'], name: 'Fastify' },
23
- { keys: ['nestjs', '@nestjs/core'], name: 'NestJS' },
24
- { keys: ['svelte'], name: 'Svelte' },
25
- { keys: ['astro'], name: 'Astro' },
26
- { keys: ['vite'], name: 'Vite' },
27
- { keys: ['webpack'], name: 'Webpack' },
28
- { keys: ['typescript'], name: 'TypeScript' },
29
- { keys: ['tailwindcss'], name: 'Tailwind' },
30
- { keys: ['prisma'], name: 'Prisma' },
31
- { keys: ['drizzle-orm'], name: 'Drizzle' },
32
- ]
33
-
34
- /**
35
- * Parse project overview info
36
- * @param {string} projectPath
37
- * @returns {object} Overview data
38
- */
39
- export function parseProjectOverview(projectPath) {
40
- const result = {
41
- techStack: [],
42
- lastActive: null,
43
- docStats: { design: 0, plan: 0, archive: 0, changes: 0, scan: 0, quicklog: 0, total: 0 },
44
- git: { branch: '', lastCommit: '', dirtyCount: 0 }
45
- }
46
-
47
- // --- Tech stack ---
48
- const pkgPath = join(projectPath, 'package.json')
49
- if (existsSync(pkgPath)) {
50
- try {
51
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
52
- const deps = Object.keys({ ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) })
53
- for (const pattern of FRAMEWORK_PATTERNS) {
54
- if (pattern.keys.some(k => deps.includes(k))) {
55
- result.techStack.push(pattern.name)
56
- }
57
- }
58
- } catch {}
59
- }
60
- if (existsSync(join(projectPath, 'pom.xml'))) {
61
- result.techStack.push('Java')
62
- try {
63
- const content = readFileSync(join(projectPath, 'pom.xml'), 'utf-8')
64
- if (content.includes('spring-boot')) result.techStack.push('Spring Boot')
65
- } catch {}
66
- }
67
- if (existsSync(join(projectPath, 'build.gradle')) || existsSync(join(projectPath, 'build.gradle.kts'))) {
68
- if (!result.techStack.includes('Java')) result.techStack.push('Gradle')
69
- }
70
- if (existsSync(join(projectPath, 'requirements.txt')) || existsSync(join(projectPath, 'pyproject.toml'))) {
71
- result.techStack.push('Python')
72
- }
73
- if (existsSync(join(projectPath, 'go.mod'))) {
74
- result.techStack.push('Go')
75
- }
76
- if (result.techStack.length === 0) result.techStack = []
77
-
78
- // --- Last active ---
79
- const sillyspecDir = join(projectPath, '.sillyspec')
80
- const progressPath = join(sillyspecDir, '.runtime', 'progress.json')
81
- if (existsSync(progressPath)) {
82
- try {
83
- const progress = JSON.parse(readFileSync(progressPath, 'utf-8'))
84
- if (progress.stages) {
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
92
- } catch {}
93
- }
94
- if (!result.lastActive) {
95
- // Fallback: most recently modified file in .sillyspec
96
- try {
97
- const findRecent = (dir) => {
98
- let latest = null
99
- if (!existsSync(dir)) return latest
100
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
101
- const p = join(dir, entry.name)
102
- try {
103
- const s = statSync(p)
104
- if (entry.isDirectory()) {
105
- const sub = findRecent(p)
106
- if (sub && (!latest || sub > latest)) latest = sub
107
- } else if (!latest || s.mtimeMs > latest) {
108
- latest = s.mtimeMs
109
- }
110
- } catch {}
111
- }
112
- return latest
113
- }
114
- const ts = findRecent(sillyspecDir)
115
- if (ts) result.lastActive = new Date(ts).toISOString()
116
- } catch {}
117
- }
118
-
119
- // --- Doc stats ---
120
- const docsDir = join(sillyspecDir, 'docs')
121
- if (existsSync(docsDir)) {
122
- const typeMap = { brainstorm: 'design', plan: 'plan', archive: 'archive', changes: 'changes', scan: 'scan', quicklog: 'quicklog' }
123
- try {
124
- for (const projDir of readdirSync(docsDir, { withFileTypes: true }).filter(d => d.isDirectory())) {
125
- for (const typeDir of readdirSync(join(docsDir, projDir.name), { withFileTypes: true }).filter(d => d.isDirectory())) {
126
- const key = typeMap[typeDir.name]
127
- if (!key) continue
128
- const count = countMdFiles(join(docsDir, projDir.name, typeDir.name))
129
- result.docStats[key] += count
130
- result.docStats.total += count
131
- }
132
- }
133
- } catch {}
134
- }
135
-
136
- // --- Git info ---
137
- try {
138
- result.git.lastCommit = execSync('git log -1 --format=%s', { cwd: projectPath, encoding: 'utf-8' }).trim()
139
- } catch {}
140
- try {
141
- result.git.branch = execSync('git branch --show-current', { cwd: projectPath, encoding: 'utf-8' }).trim()
142
- } catch {}
143
- try {
144
- result.git.dirtyCount = parseInt(execSync('git status --porcelain', { cwd: projectPath, encoding: 'utf-8' }).trim().split('\n').filter(Boolean).length, 10) || 0
145
- } catch {}
146
-
147
- return result
148
- }
149
-
150
- export function parseGitDetail(projectPath) {
151
- const result = { branch: '', commits: [], untracked: [] }
152
- try {
153
- result.branch = execSync('git branch --show-current', { cwd: projectPath, encoding: 'utf-8' }).trim()
154
- } catch {}
155
- try {
156
- const log = execSync('git log -5 --format=%h|%s|%an|%aI', { cwd: projectPath, encoding: 'utf-8' }).trim()
157
- result.commits = log.split('\n').filter(Boolean).map(line => {
158
- const [hash, message, author, date] = line.split('|')
159
- return { hash, message, author, date }
160
- })
161
- } catch {}
162
- try {
163
- const status = execSync('git status --porcelain', { cwd: projectPath, encoding: 'utf-8' }).trim()
164
- result.untracked = status.split('\n').filter(Boolean).map(line => ({
165
- status: line.slice(0, 2).trim(),
166
- file: line.slice(3)
167
- }))
168
- } catch {}
169
- return result
170
- }
171
-
172
- export function parseTechStackDetail(projectPath) {
173
- const result = { frameworks: [], dependencies: {}, devDependencies: {} }
174
- const pkgPath = join(projectPath, 'package.json')
175
- if (existsSync(pkgPath)) {
176
- try {
177
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
178
- result.dependencies = pkg.dependencies || {}
179
- result.devDependencies = pkg.devDependencies || {}
180
- const allDeps = Object.keys({ ...result.dependencies, ...result.devDependencies })
181
- for (const pattern of FRAMEWORK_PATTERNS) {
182
- if (pattern.keys.some(k => allDeps.includes(k))) {
183
- result.frameworks.push(pattern.name)
184
- }
185
- }
186
- } catch {}
187
- }
188
- return result
189
- }
190
-
191
- export function parseDocsList(projectPath) {
192
- const sillyspecDir = join(projectPath, '.sillyspec')
193
- const docsDir = join(sillyspecDir, 'docs')
194
- const groups = []
195
- if (!existsSync(docsDir)) return groups
196
- const typeMap = {
197
- brainstorm: { label: '设计文档', icon: '📋' },
198
- plan: { label: '实现计划', icon: '📐' },
199
- archive: { label: '已归档', icon: '📦' },
200
- changes: { label: '当前变更', icon: '⚙️' },
201
- scan: { label: '架构文档', icon: '🔍' },
202
- quicklog: { label: '快速修复', icon: '⚡' }
203
- }
204
- try {
205
- for (const projDir of readdirSync(docsDir, { withFileTypes: true }).filter(d => d.isDirectory())) {
206
- for (const typeDir of readdirSync(join(docsDir, projDir.name), { withFileTypes: true }).filter(d => d.isDirectory())) {
207
- const cfg = typeMap[typeDir.name]
208
- if (!cfg) continue
209
- const files = listFilesRecursive(join(docsDir, projDir.name, typeDir.name))
210
- if (files.length) {
211
- groups.push({ key: typeDir.name, label: cfg.label, icon: cfg.icon, files })
212
- }
213
- }
214
- }
215
- } catch {}
216
- return groups
217
- }
218
-
219
- function listFilesRecursive(dir) {
220
- const files = []
221
- try {
222
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
223
- const p = join(dir, entry.name)
224
- if (entry.isDirectory()) {
225
- const sub = listFilesRecursive(p)
226
- for (const f of sub) f.name = entry.name + '/' + f.name
227
- files.push(...sub)
228
- } else if (entry.name.endsWith('.md')) {
229
- const s = statSync(p)
230
- files.push({ name: entry.name, path: p, size: s.size, mtime: s.mtime.toISOString() })
231
- }
232
- }
233
- } catch {}
234
- return files
235
- }
236
-
237
- function countMdFiles(dir) {
238
- let count = 0
239
- try {
240
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
241
- if (entry.isDirectory()) {
242
- count += countMdFiles(join(dir, entry.name))
243
- } else if (entry.name.endsWith('.md')) {
244
- count++
245
- }
246
- }
247
- } catch {}
248
- return count
249
- }
250
-
251
- export function parseDocsTree(projectPath) {
252
- const sillyspecDir = join(projectPath, '.sillyspec')
253
- const docsDir = join(sillyspecDir, 'docs')
254
-
255
- if (!existsSync(docsDir)) {
256
- return { groups: [] }
257
- }
258
-
259
- const groupConfig = [
260
- { key: 'brainstorm', label: '📋 设计文档', icon: '📋', dir: 'brainstorm' },
261
- { key: 'plan', label: '📐 实现计划', icon: '📐', dir: 'plan' },
262
- { key: 'changes', label: '⚙️ 当前变更', icon: '⚙️', dir: 'changes' },
263
- { key: 'archive', label: '📦 已归档', icon: '📦', dir: 'archive' },
264
- { key: 'scan', label: '🔍 架构文档', icon: '🔍', dir: 'scan' },
265
- { key: 'quicklog', label: '⚡ 快速修复', icon: '⚡', dir: 'quicklog' },
266
- ]
267
-
268
- const groups = []
269
-
270
- // Find project dirs under docs/
271
- const projectDirs = existsSync(docsDir) ? readdirSync(docsDir, { withFileTypes: true })
272
- .filter(d => d.isDirectory()).map(d => d.name) : []
273
-
274
- for (const projName of projectDirs) {
275
- const projDocsDir = join(docsDir, projName)
276
-
277
- for (const group of groupConfig) {
278
- const groupDir = join(projDocsDir, group.dir)
279
- if (!existsSync(groupDir)) continue
280
-
281
- const files = []
282
- try {
283
- const entries = readdirSync(groupDir, { withFileTypes: true })
284
- for (const entry of entries) {
285
- if (entry.isDirectory()) {
286
- // For changes/ and archive/ subdirs
287
- const subDir = join(groupDir, entry.name)
288
- try {
289
- const subFiles = readdirSync(subDir).filter(f => f.endsWith('.md'))
290
- for (const sf of subFiles) {
291
- const filePath = join(subDir, sf)
292
- files.push({
293
- name: `${entry.name}/${sf}`,
294
- path: filePath,
295
- title: sf.replace('.md', '')
296
- })
297
- }
298
- } catch {}
299
- } else if (entry.name.endsWith('.md')) {
300
- const filePath = join(groupDir, entry.name)
301
- let title = entry.name.replace('.md', '')
302
- try {
303
- const content = readFileSync(filePath, 'utf-8')
304
- const titleMatch = content.match(/^#\s+(.+)$/m)
305
- if (titleMatch) title = titleMatch[1]
306
- } catch {}
307
- files.push({ name: entry.name, path: filePath, title })
308
- }
309
- }
310
- } catch {}
311
-
312
- if (files.length > 0) {
313
- groups.push({
314
- key: `${projName}::${group.key}`,
315
- label: group.label,
316
- project: projName,
317
- files
318
- })
319
- }
320
- }
321
- }
322
-
323
- return { groups }
324
- }
325
-
326
8
  /**
327
9
  * Parse project state from .sillyspec directory
328
10
  * @param {string} projectPath - Path to the project directory
329
11
  * @returns {object} Project state with currentStage, nextStep, progress, stages, specs, lastActive
330
12
  */
331
- export function parseProjectState(projectPath) {
13
+ export async function parseProjectState(projectPath) {
332
14
  const sillyspecDir = join(projectPath, '.sillyspec')
333
15
 
334
16
  if (!existsSync(sillyspecDir)) {
335
17
  return null
336
18
  }
337
19
 
338
- let currentStage = ''
20
+ let currentStage = 'unknown'
339
21
  let nextStep = null
340
22
  let progress = { stages: {} }
341
23
  let stages = []
342
24
  let specs = []
343
25
  let lastActive = null
344
26
 
345
- // Read progress.json for current stage
27
+ // Read STATE.md for current stage and next step
28
+ const statePath = join(sillyspecDir, 'STATE.md')
29
+ if (existsSync(statePath)) {
30
+ try {
31
+ const stateContent = readFileSync(statePath, 'utf-8')
32
+ const stageMatch = stateContent.match(/current_stage:\s*(\w+)/i)
33
+ const stepMatch = stateContent.match(/next_step:\s*(.+)/i)
34
+
35
+ if (stageMatch) currentStage = stageMatch[1]
36
+ if (stepMatch) nextStep = stepMatch[1].trim()
37
+ } catch (err) {
38
+ // State file exists but couldn't be read
39
+ }
40
+ }
41
+
42
+ // Read progress.json from .runtime directory
346
43
  const progressPath = join(sillyspecDir, '.runtime', 'progress.json')
347
44
  if (existsSync(progressPath)) {
348
45
  try {
349
- const progressData = JSON.parse(readFileSync(progressPath, 'utf-8'))
350
- progress = progressData
351
- currentStage = progressData.currentStage || ''
352
- stages = Object.keys(progressData.stages || {})
46
+ const progressContent = readFileSync(progressPath, 'utf-8')
47
+ progress = JSON.parse(progressContent)
48
+ stages = Object.keys(progress.stages || {})
353
49
 
354
- // Find last active
355
- if (progressData.lastActive) lastActive = progressData.lastActive
356
- if (progressData.stages) {
357
- for (const [stageName, stageData] of Object.entries(progressData.stages)) {
358
- if (stageData.lastActive || stageData.startedAt) {
359
- const t = stageData.lastActive || stageData.startedAt
360
- if (!lastActive || new Date(t) > new Date(lastActive)) lastActive = t
50
+ // Find last active stage
51
+ if (progress.stages) {
52
+ for (const [stageName, stageData] of Object.entries(progress.stages)) {
53
+ if (stageData.lastActive) {
54
+ if (!lastActive || new Date(stageData.lastActive) > new Date(lastActive)) {
55
+ lastActive = stageData.lastActive
56
+ }
361
57
  }
362
58
  }
363
59
  }