sillyspec 3.9.0 → 3.9.1

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 (212) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +17 -0
  2. package/.claude/skills/sillyspec-auto/SKILL.md +78 -0
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +17 -0
  4. package/{templates/commit.md → .claude/skills/sillyspec-commit/SKILL.md} +32 -47
  5. package/.claude/skills/sillyspec-continue/SKILL.md +45 -0
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +27 -0
  7. package/.claude/skills/sillyspec-execute/SKILL.md +17 -0
  8. package/.claude/skills/sillyspec-explore/SKILL.md +96 -0
  9. package/.claude/skills/sillyspec-export/SKILL.md +53 -0
  10. package/.claude/skills/sillyspec-init/SKILL.md +170 -0
  11. package/.claude/skills/sillyspec-plan/SKILL.md +52 -0
  12. package/.claude/skills/sillyspec-propose/SKILL.md +17 -0
  13. package/.claude/skills/sillyspec-quick/SKILL.md +17 -0
  14. package/.claude/skills/sillyspec-resume/SKILL.md +111 -0
  15. package/.claude/skills/sillyspec-scan/SKILL.md +17 -0
  16. package/.claude/skills/sillyspec-state/SKILL.md +54 -0
  17. package/.claude/skills/sillyspec-status/SKILL.md +17 -0
  18. package/.claude/skills/sillyspec-verify/SKILL.md +17 -0
  19. package/.claude/skills/sillyspec-workspace/SKILL.md +149 -0
  20. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
  21. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
  22. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
  23. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
  24. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
  25. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
  26. package/.sillyspec/changes/auto-mode/design.md +50 -0
  27. package/.sillyspec/changes/auto-mode/proposal.md +19 -0
  28. package/.sillyspec/changes/auto-mode/requirements.md +21 -0
  29. package/.sillyspec/changes/auto-mode/tasks.md +7 -0
  30. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
  31. package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
  32. package/.sillyspec/changes/run-command-design/design.md +1230 -0
  33. package/.sillyspec/changes/unified-docs-design/design.md +199 -0
  34. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  35. package/.sillyspec/knowledge/INDEX.md +8 -0
  36. package/.sillyspec/knowledge/uncategorized.md +3 -0
  37. package/.sillyspec/projects/sillyspec.yaml +3 -0
  38. package/README.md +12 -5
  39. package/package.json +7 -9
  40. package/packages/dashboard/dist/assets/index-CntACGUN.css +1 -0
  41. package/packages/dashboard/dist/assets/index-RsLVPAy7.js +7446 -0
  42. package/packages/dashboard/dist/index.html +3 -2
  43. package/packages/dashboard/package-lock.json +226 -6
  44. package/packages/dashboard/package.json +8 -5
  45. package/packages/dashboard/public/logo.jpg +0 -0
  46. package/packages/dashboard/server/executor.js +1 -1
  47. package/packages/dashboard/server/index.js +336 -113
  48. package/packages/dashboard/server/parser.js +333 -29
  49. package/packages/dashboard/server/watcher.js +203 -131
  50. package/packages/dashboard/src/App.vue +187 -11
  51. package/packages/dashboard/src/components/ActionBar.vue +26 -42
  52. package/packages/dashboard/src/components/CommandPalette.vue +40 -65
  53. package/packages/dashboard/src/components/DetailPanel.vue +68 -53
  54. package/packages/dashboard/src/components/DocPreview.vue +160 -0
  55. package/packages/dashboard/src/components/DocTree.vue +58 -0
  56. package/packages/dashboard/src/components/LogStream.vue +13 -33
  57. package/packages/dashboard/src/components/PipelineStage.vue +8 -8
  58. package/packages/dashboard/src/components/PipelineView.vue +80 -45
  59. package/packages/dashboard/src/components/ProjectList.vue +103 -45
  60. package/packages/dashboard/src/components/ProjectOverview.vue +178 -0
  61. package/packages/dashboard/src/components/StageBadge.vue +13 -13
  62. package/packages/dashboard/src/components/StepCard.vue +15 -15
  63. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -0
  64. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -0
  65. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -0
  66. package/packages/dashboard/src/composables/useDashboard.js +20 -6
  67. package/packages/dashboard/src/composables/useKeyboard.js +6 -4
  68. package/packages/dashboard/src/main.js +4 -1
  69. package/packages/dashboard/src/style.css +17 -17
  70. package/src/index.js +134 -22
  71. package/src/init.js +83 -228
  72. package/src/migrate.js +117 -0
  73. package/src/progress.js +459 -0
  74. package/src/run.js +624 -0
  75. package/src/setup.js +2 -72
  76. package/src/stages/archive.js +54 -0
  77. package/src/stages/brainstorm.js +239 -0
  78. package/src/stages/doctor.js +303 -0
  79. package/src/stages/execute.js +262 -0
  80. package/src/stages/index.js +26 -0
  81. package/src/stages/plan.js +282 -0
  82. package/src/stages/propose.js +115 -0
  83. package/src/stages/quick.js +64 -0
  84. package/src/stages/scan.js +141 -0
  85. package/src/stages/status.js +65 -0
  86. package/src/stages/verify.js +135 -0
  87. package/dist/steps/brainstorm/01-load-context.md +0 -30
  88. package/dist/steps/brainstorm/02-reuse-check.md +0 -6
  89. package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
  90. package/dist/steps/brainstorm/04-module-split.md +0 -23
  91. package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
  92. package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
  93. package/dist/steps/brainstorm/07-present-design.md +0 -3
  94. package/dist/steps/brainstorm/08-write-design.md +0 -21
  95. package/dist/steps/brainstorm/09-self-review.md +0 -15
  96. package/dist/steps/brainstorm/10-user-confirm.md +0 -3
  97. package/dist/steps/brainstorm/11-output-spec.md +0 -7
  98. package/dist/steps/brainstorm/manifest.yaml +0 -26
  99. package/dist/steps/execute/01-load-context.md +0 -41
  100. package/dist/steps/execute/02-scan-conventions.md +0 -47
  101. package/dist/steps/execute/03-skill-mcp.md +0 -19
  102. package/dist/steps/execute/04-assign-task.md +0 -22
  103. package/dist/steps/execute/04b-prompt-template.md +0 -54
  104. package/dist/steps/execute/05-write-test.md +0 -7
  105. package/dist/steps/execute/06-write-code.md +0 -8
  106. package/dist/steps/execute/07-run-test.md +0 -26
  107. package/dist/steps/execute/08-fix-issues.md +0 -28
  108. package/dist/steps/execute/09-next-task.md +0 -33
  109. package/dist/steps/execute/manifest.yaml +0 -28
  110. package/dist/steps/plan/01-load-context.md +0 -22
  111. package/dist/steps/plan/02-anchor-confirm.md +0 -1
  112. package/dist/steps/plan/03-expand-tasks.md +0 -33
  113. package/dist/steps/plan/04-mark-order.md +0 -15
  114. package/dist/steps/plan/05-e2e-planning.md +0 -17
  115. package/dist/steps/plan/06-self-check.md +0 -16
  116. package/dist/steps/plan/07-save.md +0 -1
  117. package/dist/steps/plan/manifest.yaml +0 -18
  118. package/dist/steps/scan/01-env-detect.md +0 -51
  119. package/dist/steps/scan/02-tech-stack.md +0 -16
  120. package/dist/steps/scan/03-conventions.md +0 -16
  121. package/dist/steps/scan/04-structure.md +0 -19
  122. package/dist/steps/scan/05-quality.md +0 -18
  123. package/dist/steps/scan/06-complete.md +0 -49
  124. package/dist/steps/scan/manifest.yaml +0 -16
  125. package/dist/steps/verify/01-load-specs.md +0 -28
  126. package/dist/steps/verify/02-check-tasks.md +0 -1
  127. package/dist/steps/verify/03-check-design.md +0 -6
  128. package/dist/steps/verify/04-run-tests.md +0 -7
  129. package/dist/steps/verify/05-e2e-tests.md +0 -27
  130. package/dist/steps/verify/05b-e2e-fix.md +0 -33
  131. package/dist/steps/verify/06-code-quality.md +0 -25
  132. package/dist/steps/verify/07-lint-check.md +0 -27
  133. package/dist/steps/verify/08-output-report.md +0 -14
  134. package/dist/steps/verify/manifest.yaml +0 -22
  135. package/docs/.vitepress/config.mts +0 -45
  136. package/docs/.vitepress/dist/404.html +0 -25
  137. package/docs/.vitepress/dist/assets/app.YytxICdd.js +0 -1
  138. package/docs/.vitepress/dist/assets/chunks/framework.Czhw_PXq.js +0 -19
  139. package/docs/.vitepress/dist/assets/chunks/theme.DusTRZQk.js +0 -1
  140. package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.js +0 -1
  141. package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.lean.js +0 -1
  142. package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  143. package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  144. package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  145. package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  146. package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  147. package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  148. package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  149. package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  150. package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  151. package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  152. package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  153. package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  154. package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  155. package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  156. package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.js +0 -15
  157. package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.lean.js +0 -1
  158. package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.js +0 -4
  159. package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.lean.js +0 -1
  160. package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.js +0 -1
  161. package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.lean.js +0 -1
  162. package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.js +0 -4
  163. package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.lean.js +0 -1
  164. package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.js +0 -5
  165. package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.lean.js +0 -1
  166. package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.js +0 -28
  167. package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.lean.js +0 -1
  168. package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.js +0 -30
  169. package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.lean.js +0 -1
  170. package/docs/.vitepress/dist/assets/style.DFTx90Kk.css +0 -1
  171. package/docs/.vitepress/dist/hashmap.json +0 -1
  172. package/docs/.vitepress/dist/index.html +0 -28
  173. package/docs/.vitepress/dist/sillyspec/commands.html +0 -42
  174. package/docs/.vitepress/dist/sillyspec/dashboard.html +0 -31
  175. package/docs/.vitepress/dist/sillyspec/file-io.html +0 -28
  176. package/docs/.vitepress/dist/sillyspec/getting-started.html +0 -31
  177. package/docs/.vitepress/dist/sillyspec/install.html +0 -32
  178. package/docs/.vitepress/dist/sillyspec/lifecycle.html +0 -55
  179. package/docs/.vitepress/dist/sillyspec/structure.html +0 -57
  180. package/docs/.vitepress/dist/vp-icons.css +0 -1
  181. package/docs/index.md +0 -34
  182. package/docs/sillyspec/commands.md +0 -218
  183. package/docs/sillyspec/dashboard.md +0 -51
  184. package/docs/sillyspec/file-io.md +0 -34
  185. package/docs/sillyspec/getting-started.md +0 -61
  186. package/docs/sillyspec/install.md +0 -51
  187. package/docs/sillyspec/lifecycle.md +0 -146
  188. package/docs/sillyspec/structure.md +0 -62
  189. package/packages/dashboard/dist/assets/index-Bh-GPjKY.css +0 -1
  190. package/packages/dashboard/dist/assets/index-CrCn5Gg6.js +0 -17
  191. package/src/step.js +0 -543
  192. package/templates/archive.md +0 -120
  193. package/templates/brainstorm.md +0 -170
  194. package/templates/continue.md +0 -32
  195. package/templates/execute.md +0 -304
  196. package/templates/explore.md +0 -59
  197. package/templates/export.md +0 -21
  198. package/templates/init.md +0 -61
  199. package/templates/plan.md +0 -146
  200. package/templates/quick.md +0 -135
  201. package/templates/scan-quick.md +0 -49
  202. package/templates/scan.md +0 -156
  203. package/templates/skills/playwright-e2e/SKILL.md +0 -340
  204. package/templates/status.md +0 -75
  205. package/templates/verify.md +0 -236
  206. package/templates/workspace-sync.md +0 -99
  207. package/templates/workspace.md +0 -70
  208. /package/.sillyspec/{specs → changes/brainstorm-archive}/2026-04-05-dashboard-design.md +0 -0
  209. /package/{docs/.vitepress/dist/logo.jpg → logo.jpg} +0 -0
  210. /package/{docs/.vitepress → packages/dashboard}/dist/favicon.jpg +0 -0
  211. /package/{docs/public → packages/dashboard/dist}/logo.jpg +0 -0
  212. /package/{docs → packages/dashboard}/public/favicon.jpg +0 -0
@@ -1,59 +1,363 @@
1
- import { readFileSync, existsSync, readdirSync } from 'fs'
1
+ import { readFileSync, existsSync, readdirSync, statSync } from 'fs'
2
+ import { execSync } from 'child_process'
2
3
  import { join } from 'path'
3
4
  import { fileURLToPath } from 'url'
4
5
  import { dirname } from 'path'
5
6
 
6
7
  const __dirname = dirname(fileURLToPath(import.meta.url))
7
8
 
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
+
8
326
  /**
9
327
  * Parse project state from .sillyspec directory
10
328
  * @param {string} projectPath - Path to the project directory
11
329
  * @returns {object} Project state with currentStage, nextStep, progress, stages, specs, lastActive
12
330
  */
13
- export async function parseProjectState(projectPath) {
331
+ export function parseProjectState(projectPath) {
14
332
  const sillyspecDir = join(projectPath, '.sillyspec')
15
333
 
16
334
  if (!existsSync(sillyspecDir)) {
17
335
  return null
18
336
  }
19
337
 
20
- let currentStage = 'unknown'
338
+ let currentStage = ''
21
339
  let nextStep = null
22
340
  let progress = { stages: {} }
23
341
  let stages = []
24
342
  let specs = []
25
343
  let lastActive = null
26
344
 
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
345
+ // Read progress.json for current stage
43
346
  const progressPath = join(sillyspecDir, '.runtime', 'progress.json')
44
347
  if (existsSync(progressPath)) {
45
348
  try {
46
- const progressContent = readFileSync(progressPath, 'utf-8')
47
- progress = JSON.parse(progressContent)
48
- stages = Object.keys(progress.stages || {})
349
+ const progressData = JSON.parse(readFileSync(progressPath, 'utf-8'))
350
+ progress = progressData
351
+ currentStage = progressData.currentStage || ''
352
+ stages = Object.keys(progressData.stages || {})
49
353
 
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
- }
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
57
361
  }
58
362
  }
59
363
  }