sillyspec 3.9.1 → 3.10.0

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 (64) hide show
  1. package/.claude/skills/sillyspec-commit/SKILL.md +1 -1
  2. package/.claude/skills/sillyspec-continue/SKILL.md +1 -1
  3. package/.claude/skills/sillyspec-explore/SKILL.md +9 -0
  4. package/.claude/skills/sillyspec-plan/SKILL.md +0 -35
  5. package/.claude/skills/sillyspec-workspace/SKILL.md +1 -1
  6. package/README.md +7 -6
  7. package/SKILL.md +15 -10
  8. package/package.json +1 -1
  9. package/packages/dashboard/dist/assets/index-BcM2J-hv.css +1 -0
  10. package/packages/dashboard/dist/assets/{index-RsLVPAy7.js → index-DpLHK4jv.js} +974 -974
  11. package/packages/dashboard/dist/index.html +16 -17
  12. package/packages/dashboard/dist/prototype-dashboard.html +836 -0
  13. package/packages/dashboard/dist/prototype-overview.html +256 -0
  14. package/packages/dashboard/public/prototype-dashboard.html +836 -0
  15. package/packages/dashboard/public/prototype-overview.html +256 -0
  16. package/packages/dashboard/server/index.js +18 -13
  17. package/packages/dashboard/server/parser.js +109 -1
  18. package/packages/dashboard/server/watcher.js +14 -6
  19. package/packages/dashboard/src/App.vue +414 -186
  20. package/packages/dashboard/src/components/ActionBar.vue +10 -1
  21. package/packages/dashboard/src/components/CommandPalette.vue +5 -1
  22. package/packages/dashboard/src/components/DocPreview.vue +105 -8
  23. package/packages/dashboard/src/components/DocTree.vue +75 -19
  24. package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
  25. package/packages/dashboard/src/components/PipelineView.vue +23 -4
  26. package/packages/dashboard/src/components/ProjectCard.vue +187 -0
  27. package/packages/dashboard/src/components/ProjectOverview.vue +113 -139
  28. package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
  29. package/packages/dashboard/src/composables/useDashboard.js +28 -0
  30. package/packages/dashboard/src/composables/useLayout.js +131 -0
  31. package/src/index.js +7 -0
  32. package/src/init.js +17 -10
  33. package/src/migrate.js +5 -5
  34. package/src/progress.js +2 -1
  35. package/src/run.js +72 -61
  36. package/src/stages/brainstorm.js +28 -3
  37. package/src/stages/execute.js +52 -27
  38. package/src/stages/explore.js +34 -0
  39. package/src/stages/index.js +3 -1
  40. package/src/stages/plan.js +86 -14
  41. package/src/stages/scan.js +11 -11
  42. package/src/stages/status.js +1 -1
  43. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
  44. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
  45. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
  46. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
  47. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
  48. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
  49. package/.sillyspec/changes/auto-mode/design.md +0 -50
  50. package/.sillyspec/changes/auto-mode/proposal.md +0 -19
  51. package/.sillyspec/changes/auto-mode/requirements.md +0 -21
  52. package/.sillyspec/changes/auto-mode/tasks.md +0 -7
  53. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +0 -206
  54. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
  55. package/.sillyspec/changes/dashboard/design.md +0 -219
  56. package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
  57. package/.sillyspec/changes/run-command-design/design.md +0 -1230
  58. package/.sillyspec/changes/unified-docs-design/design.md +0 -199
  59. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  60. package/.sillyspec/knowledge/INDEX.md +0 -8
  61. package/.sillyspec/knowledge/uncategorized.md +0 -3
  62. package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
  63. package/.sillyspec/projects/sillyspec.yaml +0 -3
  64. package/packages/dashboard/dist/assets/index-CntACGUN.css +0 -1
@@ -0,0 +1,256 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Dashboard 概览区域原型</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+ body {
14
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
15
+ background: #F5F5F7;
16
+ height: 100vh;
17
+ display: flex;
18
+ flex-direction: column;
19
+ }
20
+ /* 概览区域 - 40% */
21
+ .overview-section {
22
+ height: 40%;
23
+ background: #FFFFFF;
24
+ border-bottom: 2px solid #2A3040;
25
+ padding: 16px 24px;
26
+ display: flex;
27
+ flex-direction: column;
28
+ }
29
+ .section-header {
30
+ display: flex;
31
+ justify-content: space-between;
32
+ align-items: center;
33
+ margin-bottom: 16px;
34
+ }
35
+ .section-title {
36
+ font-size: 18px;
37
+ font-weight: 600;
38
+ color: #1A1A1A;
39
+ }
40
+ .section-actions {
41
+ display: flex;
42
+ gap: 8px;
43
+ }
44
+ .btn {
45
+ padding: 6px 12px;
46
+ border-radius: 6px;
47
+ border: none;
48
+ cursor: pointer;
49
+ font-size: 13px;
50
+ font-weight: 500;
51
+ }
52
+ .btn-primary {
53
+ background: #D97706;
54
+ color: white;
55
+ }
56
+ .btn-secondary {
57
+ background: #E5E7EB;
58
+ color: #374151;
59
+ }
60
+ /* 水平滚动卡片容器 */
61
+ .cards-container {
62
+ flex: 1;
63
+ display: flex;
64
+ gap: 16px;
65
+ overflow-x: auto;
66
+ overflow-y: hidden;
67
+ padding-bottom: 8px;
68
+ }
69
+ .cards-container::-webkit-scrollbar {
70
+ height: 8px;
71
+ }
72
+ .cards-container::-webkit-scrollbar-track {
73
+ background: #F3F4F6;
74
+ border-radius: 4px;
75
+ }
76
+ .cards-container::-webkit-scrollbar-thumb {
77
+ background: #D1D5DB;
78
+ border-radius: 4px;
79
+ }
80
+ /* 项目卡片 */
81
+ .project-card {
82
+ width: 280px;
83
+ height: 120px;
84
+ background: white;
85
+ border: 2px solid #E5E7EB;
86
+ border-radius: 12px;
87
+ padding: 16px;
88
+ display: flex;
89
+ flex-direction: column;
90
+ justify-content: space-between;
91
+ cursor: pointer;
92
+ transition: all 0.2s;
93
+ flex-shrink: 0;
94
+ }
95
+ .project-card:hover {
96
+ border-color: #D97706;
97
+ box-shadow: 0 4px 12px rgba(217, 119, 6, 0.15);
98
+ }
99
+ .project-card.selected {
100
+ border-color: #D97706;
101
+ box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.2);
102
+ }
103
+ .card-header {
104
+ display: flex;
105
+ justify-content: space-between;
106
+ align-items: flex-start;
107
+ }
108
+ .project-name {
109
+ font-size: 16px;
110
+ font-weight: 600;
111
+ color: #1A1A1A;
112
+ }
113
+ .last-active {
114
+ font-size: 11px;
115
+ color: #9CA3AF;
116
+ }
117
+ .stage-badge {
118
+ display: inline-block;
119
+ padding: 4px 10px;
120
+ border-radius: 12px;
121
+ font-size: 12px;
122
+ font-weight: 500;
123
+ }
124
+ .stage-badge.in-progress {
125
+ background: #DBEAFE;
126
+ color: #1D4ED8;
127
+ }
128
+ .stage-badge.completed {
129
+ background: #D1FAE5;
130
+ color: #047857;
131
+ }
132
+ .stage-badge.pending {
133
+ background: #F3F4F6;
134
+ color: #6B7280;
135
+ }
136
+ .progress-section {
137
+ display: flex;
138
+ align-items: center;
139
+ gap: 8px;
140
+ }
141
+ .progress-bar {
142
+ flex: 1;
143
+ height: 6px;
144
+ background: #E5E7EB;
145
+ border-radius: 3px;
146
+ overflow: hidden;
147
+ }
148
+ .progress-fill {
149
+ height: 100%;
150
+ border-radius: 3px;
151
+ transition: width 0.3s;
152
+ }
153
+ .progress-fill.in-progress { background: #3B82F6; }
154
+ .progress-fill.completed { background: #10B981; }
155
+ .progress-fill.pending { background: #9CA3AF; }
156
+ .progress-text {
157
+ font-size: 12px;
158
+ font-weight: 600;
159
+ color: #374151;
160
+ }
161
+ /* 详情区域 - 60% */
162
+ .detail-section {
163
+ flex: 1;
164
+ background: #FAFAFA;
165
+ padding: 24px;
166
+ display: flex;
167
+ align-items: center;
168
+ justify-content: center;
169
+ color: #9CA3AF;
170
+ font-size: 14px;
171
+ }
172
+ </style>
173
+ </head>
174
+ <body>
175
+ <!-- 概览区域 -->
176
+ <div class="overview-section">
177
+ <div class="section-header">
178
+ <h1 class="section-title">项目概览</h1>
179
+ <div class="section-actions">
180
+ <button class="btn btn-secondary">刷新</button>
181
+ <button class="btn btn-primary">添加项目</button>
182
+ </div>
183
+ </div>
184
+ <div class="cards-container">
185
+ <!-- 项目卡片 1 - 进行中 -->
186
+ <div class="project-card selected">
187
+ <div class="card-header">
188
+ <span class="project-name">sillyspec</span>
189
+ <span class="last-active">2分钟前</span>
190
+ </div>
191
+ <div>
192
+ <span class="stage-badge in-progress">需求探索</span>
193
+ </div>
194
+ <div class="progress-section">
195
+ <div class="progress-bar">
196
+ <div class="progress-fill in-progress" style="width: 40%"></div>
197
+ </div>
198
+ <span class="progress-text">40%</span>
199
+ </div>
200
+ </div>
201
+ <!-- 项目卡片 2 - 进行中 -->
202
+ <div class="project-card">
203
+ <div class="card-header">
204
+ <span class="project-name">my-blog</span>
205
+ <span class="last-active">15分钟前</span>
206
+ </div>
207
+ <div>
208
+ <span class="stage-badge in-progress">实现计划</span>
209
+ </div>
210
+ <div class="progress-section">
211
+ <div class="progress-bar">
212
+ <div class="progress-fill in-progress" style="width: 75%"></div>
213
+ </div>
214
+ <span class="progress-text">75%</span>
215
+ </div>
216
+ </div>
217
+ <!-- 项目卡片 3 - 已完成 -->
218
+ <div class="project-card">
219
+ <div class="card-header">
220
+ <span class="project-name">user-service</span>
221
+ <span class="last-active">1小时前</span>
222
+ </div>
223
+ <div>
224
+ <span class="stage-badge completed">验证确认</span>
225
+ </div>
226
+ <div class="progress-section">
227
+ <div class="progress-bar">
228
+ <div class="progress-fill completed" style="width: 100%"></div>
229
+ </div>
230
+ <span class="progress-text">100%</span>
231
+ </div>
232
+ </div>
233
+ <!-- 项目卡片 4 - 未开始 -->
234
+ <div class="project-card">
235
+ <div class="card-header">
236
+ <span class="project-name">payment-api</span>
237
+ <span class="last-active">昨天</span>
238
+ </div>
239
+ <div>
240
+ <span class="stage-badge pending">未开始</span>
241
+ </div>
242
+ <div class="progress-section">
243
+ <div class="progress-bar">
244
+ <div class="progress-fill pending" style="width: 0%"></div>
245
+ </div>
246
+ <span class="progress-text">0%</span>
247
+ </div>
248
+ </div>
249
+ </div>
250
+ </div>
251
+ <!-- 详情区域 -->
252
+ <div class="detail-section">
253
+ 点击上方卡片查看项目详情
254
+ </div>
255
+ </body>
256
+ </html>
@@ -5,7 +5,7 @@ import { join, dirname, basename, sep, resolve, relative } from 'path'
5
5
  import { fileURLToPath } from 'url'
6
6
  import { homedir } from 'os'
7
7
  import open from 'open'
8
- import { parseProjectState, parseDocsTree, parseProjectOverview, parseGitDetail, parseTechStackDetail, parseDocsList } from './parser.js'
8
+ import { parseProjectState, parseSillyspecDocsTree, parseProjectOverview, parseGitDetail, parseTechStackDetail } from './parser.js'
9
9
  import { startWatcher, stopWatcher, addCustomScanPath, removeCustomScanPath, getCustomScanPaths, customScanPaths } from './watcher.js'
10
10
  import { executeCommand } from './executor.js'
11
11
 
@@ -14,7 +14,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
14
14
  // WebSocket clients and active processes
15
15
  let wss = null
16
16
  const activeProcesses = new Map()
17
- const allowedStages = new Set(['brainstorm', 'plan', 'execute', 'verify', 'scan', 'quick', 'archive', 'status', 'doctor', 'auto'])
17
+ const allowedStages = new Set(['brainstorm', 'plan', 'execute', 'verify', 'scan', 'quick', 'explore', 'archive', 'status', 'doctor', 'auto'])
18
18
 
19
19
  function isAllowedCliCommand(command) {
20
20
  const args = String(command || '').trim().split(/\s+/).filter(Boolean)
@@ -97,12 +97,13 @@ function isInside(parent, child) {
97
97
  return rel === '' || (!!rel && !rel.startsWith('..') && !rel.includes(`..${sep}`))
98
98
  }
99
99
 
100
- function isSillyspecDocsPath(filePath) {
100
+ function isSillyspecPath(filePath) {
101
101
  const parts = resolve(filePath).split(/[\\/]+/)
102
- for (let i = 0; i < parts.length - 1; i++) {
103
- if (parts[i] === '.sillyspec' && parts[i + 1] === 'docs') return true
104
- }
105
- return false
102
+ return parts.includes('.sillyspec')
103
+ }
104
+
105
+ function isViewableDocPath(filePath) {
106
+ return /\.(md|markdown|mdx|html?|txt|log|json|ya?ml|toml|xml|csv)$/i.test(filePath)
106
107
  }
107
108
 
108
109
  function isLocalOrigin(origin) {
@@ -316,7 +317,7 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
316
317
  try {
317
318
  if (type === 'git') data = parseGitDetail(projectPath)
318
319
  else if (type === 'tech') data = parseTechStackDetail(projectPath)
319
- else if (type === 'docs') data = parseDocsList(projectPath)
320
+ else if (type === 'docs') data = parseSillyspecDocsTree(projectPath).groups
320
321
  else { res.writeHead(400); res.end(JSON.stringify({ error: 'Invalid type' })); return }
321
322
  res.setHeader('Content-Type', 'application/json')
322
323
  res.writeHead(200)
@@ -373,9 +374,9 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
373
374
  res.end(JSON.stringify({ error: 'Missing path parameter' }))
374
375
  return
375
376
  }
376
- // Security: only allow reading files under a .sillyspec/docs/ tree.
377
+ // Security: only allow reading viewable text documents under a .sillyspec tree.
377
378
  const normalizedPath = resolve(filePath)
378
- if (!isSillyspecDocsPath(normalizedPath)) {
379
+ if (!isSillyspecPath(normalizedPath) || !isViewableDocPath(normalizedPath)) {
379
380
  res.writeHead(403)
380
381
  res.end(JSON.stringify({ error: 'Access denied' }))
381
382
  return
@@ -405,7 +406,7 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
405
406
  res.end(JSON.stringify({ error: 'Missing project parameter' }))
406
407
  return
407
408
  }
408
- const docs = parseDocsTree(projectPath)
409
+ const docs = parseSillyspecDocsTree(projectPath)
409
410
  res.setHeader('Content-Type', 'application/json')
410
411
  res.writeHead(200)
411
412
  res.end(JSON.stringify(docs))
@@ -508,7 +509,11 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
508
509
  discoverProjects().then(projects => {
509
510
  broadcast({
510
511
  type: 'projects:updated',
511
- data: projects.map(p => ({ ...p, state: parseProjectState(p.path) }))
512
+ data: projects.map(p => ({
513
+ ...p,
514
+ state: parseProjectState(p.path),
515
+ overview: parseProjectOverview(p.path)
516
+ }))
512
517
  })
513
518
  })
514
519
  }
@@ -524,7 +529,7 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
524
529
  break
525
530
  case 'docs:get':
526
531
  if (data.data?.projectPath) {
527
- const docs = parseDocsTree(data.data.projectPath)
532
+ const docs = parseSillyspecDocsTree(data.data.projectPath)
528
533
  ws.send(JSON.stringify({ type: 'docs:tree', data: docs }))
529
534
  }
530
535
  break
@@ -1,6 +1,6 @@
1
1
  import { readFileSync, existsSync, readdirSync, statSync } from 'fs'
2
2
  import { execSync } from 'child_process'
3
- import { join } from 'path'
3
+ import { join, relative, sep } from 'path'
4
4
  import { fileURLToPath } from 'url'
5
5
  import { dirname } from 'path'
6
6
 
@@ -323,6 +323,114 @@ export function parseDocsTree(projectPath) {
323
323
  return { groups }
324
324
  }
325
325
 
326
+ const VIEWABLE_SILLYSPEC_DOC_EXTENSIONS = new Set([
327
+ '.md', '.markdown', '.mdx',
328
+ '.html', '.htm',
329
+ '.txt', '.log',
330
+ '.json', '.yaml', '.yml', '.toml',
331
+ '.xml', '.csv'
332
+ ])
333
+
334
+ const SILLYSPEC_DOC_GROUPS = [
335
+ { key: 'docs', label: '📚 docs', dir: 'docs' },
336
+ { key: 'changes', label: '⚙️ changes', dir: 'changes' },
337
+ { key: 'plans', label: '🧾 plans', dir: 'plans' },
338
+ { key: 'quicklog', label: '⚡ quicklog', dir: 'quicklog' },
339
+ { key: 'knowledge', label: '🧠 knowledge', dir: 'knowledge' },
340
+ { key: 'projects', label: '📁 projects', dir: 'projects' },
341
+ { key: 'workspace', label: '🗂️ workspace', dir: 'workspace' },
342
+ { key: 'shared', label: '🔗 shared', dir: 'shared' },
343
+ { key: 'runtime', label: '🧰 .runtime', dir: '.runtime' }
344
+ ]
345
+
346
+ function sillyspecDocExt(fileName) {
347
+ const index = fileName.lastIndexOf('.')
348
+ return index === -1 ? '' : fileName.slice(index).toLowerCase()
349
+ }
350
+
351
+ function isViewableSillyspecDoc(fileName) {
352
+ return VIEWABLE_SILLYSPEC_DOC_EXTENSIONS.has(sillyspecDocExt(fileName))
353
+ }
354
+
355
+ function titleFromSillyspecDoc(filePath, fileName) {
356
+ const ext = sillyspecDocExt(fileName)
357
+ if (ext === '.md' || ext === '.markdown' || ext === '.mdx') {
358
+ try {
359
+ const content = readFileSync(filePath, 'utf-8')
360
+ const titleMatch = content.match(/^#\s+(.+)$/m)
361
+ if (titleMatch) return titleMatch[1]
362
+ } catch {}
363
+ }
364
+ return fileName.replace(/\.(md|markdown|mdx|html?|txt|log|json|ya?ml|toml|xml|csv)$/i, '')
365
+ }
366
+
367
+ function listSillyspecDocsRecursive(dir, rootDir) {
368
+ const files = []
369
+ try {
370
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
371
+ const filePath = join(dir, entry.name)
372
+ if (entry.isDirectory()) {
373
+ files.push(...listSillyspecDocsRecursive(filePath, rootDir))
374
+ } else if (entry.isFile() && isViewableSillyspecDoc(entry.name)) {
375
+ const s = statSync(filePath)
376
+ files.push({
377
+ name: relative(rootDir, filePath).split(sep).join('/'),
378
+ path: filePath,
379
+ title: titleFromSillyspecDoc(filePath, entry.name),
380
+ extension: sillyspecDocExt(entry.name).slice(1),
381
+ size: s.size,
382
+ mtime: s.mtime.toISOString()
383
+ })
384
+ }
385
+ }
386
+ } catch {}
387
+ return files
388
+ }
389
+
390
+ export function parseSillyspecDocsTree(projectPath) {
391
+ const sillyspecDir = join(projectPath, '.sillyspec')
392
+ if (!existsSync(sillyspecDir)) return { groups: [] }
393
+
394
+ const groups = []
395
+ const seen = new Set()
396
+
397
+ for (const group of SILLYSPEC_DOC_GROUPS) {
398
+ const groupDir = join(sillyspecDir, group.dir)
399
+ if (!existsSync(groupDir)) continue
400
+
401
+ const files = listSillyspecDocsRecursive(groupDir, groupDir)
402
+ .sort((a, b) => a.name.localeCompare(b.name))
403
+
404
+ if (files.length === 0) continue
405
+ for (const file of files) seen.add(file.path)
406
+ groups.push({ key: group.key, label: group.label, project: '.sillyspec', files })
407
+ }
408
+
409
+ const rootFiles = []
410
+ try {
411
+ for (const entry of readdirSync(sillyspecDir, { withFileTypes: true })) {
412
+ if (!entry.isFile() || !isViewableSillyspecDoc(entry.name)) continue
413
+ const filePath = join(sillyspecDir, entry.name)
414
+ if (seen.has(filePath)) continue
415
+ const s = statSync(filePath)
416
+ rootFiles.push({
417
+ name: entry.name,
418
+ path: filePath,
419
+ title: titleFromSillyspecDoc(filePath, entry.name),
420
+ extension: sillyspecDocExt(entry.name).slice(1),
421
+ size: s.size,
422
+ mtime: s.mtime.toISOString()
423
+ })
424
+ }
425
+ } catch {}
426
+
427
+ if (rootFiles.length > 0) {
428
+ groups.unshift({ key: 'root', label: '📄 .sillyspec', project: '.sillyspec', files: rootFiles })
429
+ }
430
+
431
+ return { groups }
432
+ }
433
+
326
434
  /**
327
435
  * Parse project state from .sillyspec directory
328
436
  * @param {string} projectPath - Path to the project directory
@@ -2,7 +2,7 @@ import chokidar from 'chokidar'
2
2
  import { join, basename, dirname, sep } from 'path'
3
3
  import { homedir } from 'os'
4
4
  import { existsSync, readdirSync, realpathSync } from 'fs'
5
- import { parseProjectState } from './parser.js'
5
+ import { parseProjectState, parseProjectOverview } from './parser.js'
6
6
 
7
7
  let watcher = null
8
8
  let updateCallback = null
@@ -146,8 +146,12 @@ function scanSelf(seen) {
146
146
  const cwd = process.cwd()
147
147
  const projects = []
148
148
 
149
- if (!seen.has(cwd)) {
150
- seen.add(cwd)
149
+ let realPath
150
+ try { realPath = realpathSync(cwd) } catch { realPath = cwd }
151
+ const normalizedPath = realPath.toLowerCase()
152
+
153
+ if (!seen.has(normalizedPath)) {
154
+ seen.add(normalizedPath)
151
155
  const sillyspecPath = join(cwd, '.sillyspec')
152
156
  if (existsSync(sillyspecPath)) {
153
157
  projects.push({
@@ -200,8 +204,9 @@ export function startWatcher(callback) {
200
204
  // Parse initial states
201
205
  for (const project of projects) {
202
206
  const state = parseProjectState(project.path)
207
+ const overview = parseProjectOverview(project.path)
203
208
  if (state) {
204
- projectStates.set(project.name, { ...project, state })
209
+ projectStates.set(project.name, { ...project, state, overview })
205
210
  }
206
211
  }
207
212
 
@@ -261,8 +266,9 @@ async function handleFileChange(filePath) {
261
266
  const project = projectStates.get(projectName)
262
267
  if (project) {
263
268
  const newState = parseProjectState(project.path)
269
+ const overview = parseProjectOverview(project.path)
264
270
  if (newState) {
265
- projectStates.set(projectName, { ...project, state: newState })
271
+ projectStates.set(projectName, { ...project, state: newState, overview })
266
272
  }
267
273
  }
268
274
 
@@ -280,11 +286,13 @@ async function rescanProjects() {
280
286
  for (const project of projects) {
281
287
  if (!projectStates.has(project.name)) {
282
288
  const state = parseProjectState(project.path)
289
+ const overview = parseProjectOverview(project.path)
283
290
  if (state) {
284
291
  projectStates.set(project.name, {
285
292
  name: project.name,
286
293
  path: project.path,
287
- state
294
+ state,
295
+ overview
288
296
  })
289
297
  }
290
298
  }