sillyspec 3.8.4 → 3.8.6

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 +121 -0
  83. package/templates/brainstorm.md +240 -0
  84. package/{.claude/skills/sillyspec-commit/SKILL.md → templates/commit.md} +47 -29
  85. package/templates/continue.md +32 -0
  86. package/templates/execute.md +314 -0
  87. package/templates/explore.md +60 -0
  88. package/templates/export.md +21 -0
  89. package/templates/init.md +61 -0
  90. package/templates/plan.md +157 -0
  91. package/templates/quick.md +135 -0
  92. package/templates/scan-quick.md +49 -0
  93. package/templates/scan.md +172 -0
  94. package/templates/skills/playwright-e2e/SKILL.md +340 -0
  95. package/templates/status.md +75 -0
  96. package/templates/verify.md +253 -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 -258
  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,202 +1,100 @@
1
1
  import chokidar from 'chokidar'
2
- import { join, basename, dirname, sep } from 'path'
2
+ import { join } from 'path'
3
3
  import { homedir } from 'os'
4
- import { existsSync, readdirSync, realpathSync } from 'fs'
4
+ import { existsSync } from 'fs'
5
5
  import { parseProjectState } from './parser.js'
6
6
 
7
7
  let watcher = null
8
8
  let updateCallback = null
9
9
  let projectStates = new Map()
10
- export const customScanPaths = new Set()
11
-
12
- // Directories to exclude (system junk, cache, etc.)
13
- const excludeDirs = new Set([
14
- '.Trash', '.cache', '.npm', '.local', '.vscode', 'Library',
15
- '.git', 'node_modules', '.Trash-*', '.DS_Store', '.config',
16
- '.cocoapods', '.gem', '.rvm', '.nvm', '.asdf', '.brew',
17
- 'AppData', 'Application Data', '.cargo', '.rustup',
18
- '.nuget', '.android', '.gradle', '.m2', '.vscode-server'
19
- ])
20
10
 
21
11
  /**
22
- * Check if directory should be excluded from scanning
23
- * @param {string} name - Directory name
24
- * @returns {boolean}
12
+ * Start watching all .sillyspec directories
13
+ * @param {function} callback - Callback function when projects are updated
14
+ * @returns {object} The watcher instance
25
15
  */
26
- function shouldExclude(name, cwd) {
27
- if (excludeDirs.has(name)) return true
28
- // Check wildcard patterns (like .Trash-*)
29
- for (const pattern of excludeDirs) {
30
- if (pattern.includes('*')) {
31
- const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$')
32
- if (regex.test(name)) return true
33
- }
34
- }
35
- // Exclude hidden directories unless it's the cwd basename
36
- const cwdName = cwd.split(sep).pop() || cwd.split('/').pop() || ''
37
- if (name.startsWith('.') && name !== cwdName) {
38
- return true
16
+ export function startWatcher(callback) {
17
+ if (watcher) {
18
+ stopWatcher()
39
19
  }
40
- return false
41
- }
42
20
 
43
- /**
44
- * Build list of directories to scan
45
- * @returns {string[]}
46
- */
47
- function buildScanDirs() {
21
+ updateCallback = callback
22
+
23
+ // Discover all .sillyspec directories
48
24
  const home = homedir()
49
25
  const cwd = process.cwd()
50
26
 
51
- const scanDirs = new Set()
52
-
53
- // Always scan cwd and its parent
54
- scanDirs.add(cwd)
55
- scanDirs.add(dirname(cwd))
56
-
57
- // Scan parent of parent (2 levels up from cwd) to discover sibling projects
58
- const parentParent = dirname(dirname(cwd))
59
- scanDirs.add(parentParent)
60
-
61
- // Home directory
62
- scanDirs.add(home)
27
+ // Directories to exclude (system junk, cache, etc.)
28
+ const excludeDirs = new Set([
29
+ '.Trash', '.cache', '.npm', '.local', '.vscode', 'Library',
30
+ '.git', 'node_modules', '.Trash-*', '.DS_Store', '.config',
31
+ '.cocoapods', '.gem', '.rvm', '.nvm', '.asdf', '.brew'
32
+ ])
33
+
34
+ // Helper to check if directory should be excluded
35
+ const shouldExclude = (name) => {
36
+ // Check exact matches
37
+ if (excludeDirs.has(name)) return true
38
+ // Check wildcard patterns (like .Trash-*)
39
+ for (const pattern of excludeDirs) {
40
+ if (pattern.includes('*')) {
41
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$')
42
+ if (regex.test(name)) return true
43
+ }
44
+ }
45
+ // Exclude hidden directories (starting with .) unless it's the cwd basename
46
+ if (name.startsWith('.') && name !== cwd.split('/').pop()) {
47
+ return true
48
+ }
49
+ return false
50
+ }
63
51
 
64
- // Common project directories - check both English and Chinese names
65
- const extraDirs = [
66
- 'Desktop', '桌面',
67
- 'Documents', '文档',
68
- 'Downloads', '下载',
69
- 'Projects', '项目',
70
- 'Work', '工作',
71
- 'Repos', 'Code', 'src', 'dev',
72
- 'workspace', '工作区'
73
- ]
52
+ // Build scan directories: cwd + home subdirs + common project locations
53
+ const scanDirs = [cwd, home]
54
+ const extraDirs = ['Desktop', 'Documents', 'Projects', 'Work', 'Repos', 'Code', 'src', 'dev']
74
55
 
75
56
  for (const extra of extraDirs) {
76
57
  const extraPath = join(home, extra)
77
58
  if (existsSync(extraPath)) {
78
- scanDirs.add(extraPath)
79
- }
80
- }
81
-
82
- // Add custom scan paths
83
- for (const customPath of customScanPaths) {
84
- if (existsSync(customPath)) {
85
- scanDirs.add(customPath)
59
+ scanDirs.push(extraPath)
86
60
  }
87
61
  }
88
62
 
89
- return Array.from(scanDirs)
90
- }
91
-
92
- /**
93
- * Recursively scan a directory for .sillyspec projects
94
- * @param {string} baseDir - Directory to scan
95
- * @param {Set} seen - Already seen paths
96
- * @param {number} maxDepth - Maximum recursion depth
97
- * @param {number} currentDepth - Current depth
98
- * @returns {Array} Found projects
99
- */
100
- function scanDirectory(baseDir, seen, maxDepth = 2, currentDepth = 0) {
101
- const cwd = process.cwd()
63
+ const watchPaths = []
102
64
  const projects = []
65
+ const seen = new Set() // Dedupe by path
103
66
 
104
- try {
105
- const entries = readdirSync(baseDir, { withFileTypes: true })
67
+ for (const baseDir of scanDirs) {
68
+ try {
69
+ const { readdirSync } = require('fs')
70
+ const entries = readdirSync(baseDir, { withFileTypes: true })
106
71
 
107
- for (const entry of entries) {
108
- if (!entry.isDirectory()) continue
109
- if (shouldExclude(entry.name, cwd)) continue
72
+ for (const entry of entries) {
73
+ if (!entry.isDirectory()) continue
74
+ if (shouldExclude(entry.name)) continue
110
75
 
111
- const dirPath = join(baseDir, entry.name)
76
+ const dirPath = join(baseDir, entry.name)
112
77
 
113
- let realPath
114
- try { realPath = realpathSync(dirPath) } catch { realPath = dirPath }
115
- const normalizedPath = realPath.toLowerCase()
116
- if (seen.has(normalizedPath)) continue
117
- seen.add(normalizedPath)
78
+ // Skip if we've already seen this path
79
+ if (seen.has(dirPath)) continue
80
+ seen.add(dirPath)
118
81
 
119
- // Check if this dir has .sillyspec
120
- const sillyspecPath = join(dirPath, '.sillyspec')
121
- if (existsSync(sillyspecPath)) {
122
- projects.push({
123
- name: entry.name,
124
- path: dirPath
125
- })
126
- }
82
+ const sillyspecPath = join(dirPath, '.sillyspec')
127
83
 
128
- // Recurse into subdirectories if not at max depth
129
- if (currentDepth < maxDepth) {
130
- projects.push(...scanDirectory(dirPath, seen, maxDepth, currentDepth + 1))
84
+ if (existsSync(sillyspecPath)) {
85
+ watchPaths.push(sillyspecPath)
86
+ projects.push({
87
+ name: entry.name,
88
+ path: dirPath
89
+ })
90
+ }
131
91
  }
132
- }
133
- } catch (err) {
134
- // Skip directories we can't read
135
- }
136
-
137
- return projects
138
- }
139
-
140
- /**
141
- * Scan cwd itself (it might be a project root)
142
- * @param {Set} seen - Already seen paths
143
- * @returns {Array} Found projects
144
- */
145
- function scanSelf(seen) {
146
- const cwd = process.cwd()
147
- const projects = []
148
-
149
- if (!seen.has(cwd)) {
150
- seen.add(cwd)
151
- const sillyspecPath = join(cwd, '.sillyspec')
152
- if (existsSync(sillyspecPath)) {
153
- projects.push({
154
- name: basename(cwd),
155
- path: cwd
156
- })
92
+ } catch (err) {
93
+ // Skip directories we can't read
94
+ continue
157
95
  }
158
96
  }
159
97
 
160
- return projects
161
- }
162
-
163
- /**
164
- * Discover all .sillyspec projects
165
- * @returns {{ projects: Array, watchPaths: string[] }}
166
- */
167
- function discoverAll() {
168
- const scanDirs = buildScanDirs()
169
- const seen = new Set()
170
- const allProjects = []
171
-
172
- // Check cwd itself first
173
- allProjects.push(...scanSelf(seen))
174
-
175
- // Scan each base directory
176
- for (const baseDir of scanDirs) {
177
- allProjects.push(...scanDirectory(baseDir, seen, 2, 0))
178
- }
179
-
180
- // Build watch paths
181
- const watchPaths = allProjects.map(p => join(p.path, '.sillyspec'))
182
-
183
- return { projects: allProjects, watchPaths }
184
- }
185
-
186
- /**
187
- * Start watching all .sillyspec directories
188
- * @param {function} callback - Callback function when projects are updated
189
- * @returns {object} The watcher instance
190
- */
191
- export function startWatcher(callback) {
192
- if (watcher) {
193
- stopWatcher()
194
- }
195
-
196
- updateCallback = callback
197
-
198
- const { projects, watchPaths } = discoverAll()
199
-
200
98
  // Parse initial states
201
99
  for (const project of projects) {
202
100
  const state = parseProjectState(project.path)
@@ -246,18 +144,18 @@ export function startWatcher(callback) {
246
144
  * @param {string} filePath - Path to the changed file
247
145
  */
248
146
  async function handleFileChange(filePath) {
249
- // Normalize path for comparison
250
- const normalizedPath = filePath.replace(/\\/g, '/')
251
-
147
+ // Find which project this file belongs to
252
148
  const projectName = Array.from(projectStates.values()).find(p =>
253
- normalizedPath.startsWith(p.path.replace(/\\/g, '/'))
149
+ filePath.startsWith(p.path)
254
150
  )?.name
255
151
 
256
152
  if (!projectName) {
153
+ // Re-scan for new projects
257
154
  await rescanProjects()
258
155
  return
259
156
  }
260
157
 
158
+ // Re-parse the project state
261
159
  const project = projectStates.get(projectName)
262
160
  if (project) {
263
161
  const newState = parseProjectState(project.path)
@@ -266,6 +164,7 @@ async function handleFileChange(filePath) {
266
164
  }
267
165
  }
268
166
 
167
+ // Emit updated state
269
168
  if (updateCallback) {
270
169
  updateCallback(Array.from(projectStates.values()))
271
170
  }
@@ -275,49 +174,77 @@ async function handleFileChange(filePath) {
275
174
  * Re-scan for projects (e.g., new .sillyspec directories)
276
175
  */
277
176
  async function rescanProjects() {
278
- const { projects } = discoverAll()
177
+ const home = homedir()
178
+ const cwd = process.cwd()
279
179
 
280
- for (const project of projects) {
281
- if (!projectStates.has(project.name)) {
282
- const state = parseProjectState(project.path)
283
- if (state) {
284
- projectStates.set(project.name, {
285
- name: project.name,
286
- path: project.path,
287
- state
288
- })
180
+ // Directories to exclude (system junk, cache, etc.)
181
+ const excludeDirs = new Set([
182
+ '.Trash', '.cache', '.npm', '.local', '.vscode', 'Library',
183
+ '.git', 'node_modules', '.Trash-*', '.DS_Store', '.config',
184
+ '.cocoapods', '.gem', '.rvm', '.nvm', '.asdf', '.brew'
185
+ ])
186
+
187
+ const shouldExclude = (name) => {
188
+ if (excludeDirs.has(name)) return true
189
+ for (const pattern of excludeDirs) {
190
+ if (pattern.includes('*')) {
191
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$')
192
+ if (regex.test(name)) return true
289
193
  }
290
194
  }
195
+ if (name.startsWith('.') && name !== cwd.split('/').pop()) {
196
+ return true
197
+ }
198
+ return false
291
199
  }
292
200
 
293
- if (updateCallback) {
294
- updateCallback(Array.from(projectStates.values()))
201
+ // Build scan directories
202
+ const scanDirs = [cwd, home]
203
+ const extraDirs = ['Desktop', 'Documents', 'Projects', 'Work', 'Repos', 'Code', 'src', 'dev']
204
+
205
+ for (const extra of extraDirs) {
206
+ const extraPath = join(home, extra)
207
+ if (existsSync(extraPath)) {
208
+ scanDirs.push(extraPath)
209
+ }
295
210
  }
296
- }
297
211
 
298
- /**
299
- * Add a custom scan path and rescan
300
- * @param {string} path - Path to add
301
- */
302
- export function addCustomScanPath(path) {
303
- customScanPaths.add(path)
304
- rescanProjects()
305
- }
212
+ const seen = new Set()
306
213
 
307
- /**
308
- * Remove a custom scan path
309
- * @param {string} path - Path to remove
310
- */
311
- export function removeCustomScanPath(path) {
312
- customScanPaths.delete(path)
313
- }
214
+ for (const baseDir of scanDirs) {
215
+ try {
216
+ const { readdirSync } = require('fs')
217
+ const entries = readdirSync(baseDir, { withFileTypes: true })
218
+
219
+ for (const entry of entries) {
220
+ if (!entry.isDirectory()) continue
221
+ if (shouldExclude(entry.name)) continue
222
+
223
+ const dirPath = join(baseDir, entry.name)
224
+ if (seen.has(dirPath)) continue
225
+ seen.add(dirPath)
226
+
227
+ const sillyspecPath = join(dirPath, '.sillyspec')
228
+
229
+ if (existsSync(sillyspecPath) && !projectStates.has(entry.name)) {
230
+ const state = parseProjectState(dirPath)
231
+ if (state) {
232
+ projectStates.set(entry.name, {
233
+ name: entry.name,
234
+ path: dirPath,
235
+ state
236
+ })
237
+ }
238
+ }
239
+ }
240
+ } catch (err) {
241
+ continue
242
+ }
243
+ }
314
244
 
315
- /**
316
- * Get list of custom scan paths
317
- * @returns {string[]}
318
- */
319
- export function getCustomScanPaths() {
320
- return Array.from(customScanPaths)
245
+ if (updateCallback) {
246
+ updateCallback(Array.from(projectStates.values()))
247
+ }
321
248
  }
322
249
 
323
250
  /**
@@ -347,3 +274,4 @@ export function getProjectStates() {
347
274
  export function getProjectState(projectName) {
348
275
  return projectStates.get(projectName) || null
349
276
  }
277
+
@@ -1,79 +1,43 @@
1
1
  <template>
2
- <n-config-provider :theme-overrides="themeOverrides">
3
- <div class="h-screen w-screen flex flex-col overflow-hidden font-[DM_Sans,sans-serif] relative" style="background-color: #F5F5F7;">
2
+ <div class="h-screen w-screen flex flex-col overflow-hidden font-[DM_Sans,sans-serif] relative" style="background-color: #0A0A0B;">
4
3
  <!-- Ambient background -->
5
4
  <div class="absolute inset-0 pointer-events-none" style="background: radial-gradient(ellipse 60% 40% at 10% 20%, rgba(251,191,36,0.04) 0%, transparent 70%), radial-gradient(ellipse 50% 50% at 90% 80%, rgba(251,191,36,0.02) 0%, transparent 70%);" />
6
5
 
7
6
  <!-- Main Content -->
8
- <div
9
- class="flex-1 flex overflow-hidden relative z-10"
10
- :style="isDragging ? 'user-select: none' : ''"
11
- >
7
+ <div class="flex-1 flex overflow-hidden relative z-10">
12
8
  <!-- Left: Project List -->
13
- <aside
14
- class="flex-shrink-0 relative overflow-hidden"
15
- :style="`width: ${leftWidth}px; background: #FFFFFF; border-right: none;`"
16
- >
9
+ <aside class="w-[240px] flex-shrink-0 relative" style="background: #111113; border-right: 1px solid #1F1F22;">
17
10
  <ProjectList
18
11
  :projects="dashboard.state.projects"
19
12
  :active-project="dashboard.state.activeProject"
20
13
  :is-loading="dashboard.state.isLoading"
21
- :scan-paths="scanPaths"
22
14
  @select="handleSelectProject"
23
- @scan:add-path="handleAddScanPath"
24
- @scan:remove-path="handleRemoveScanPath"
25
15
  />
26
16
  </aside>
27
17
 
28
- <!-- Left ↔ Center resize handle -->
29
- <div
30
- class="w-[2px] flex-shrink-0 cursor-col-resize hover:bg-[#D97706] active:bg-[#D97706] relative z-20"
31
- style="background: #2A3040;"
32
- @mousedown="startDragLeft"
33
- />
34
-
35
18
  <!-- Center: Pipeline View -->
36
- <main class="flex-1 overflow-hidden accent-stripe flex flex-col" style="min-width: 300px;">
37
- <ProjectOverview :project="dashboard.state.activeProject" @show-detail="handleShowDetail" />
19
+ <main class="flex-1 overflow-hidden accent-stripe">
38
20
  <PipelineView
39
21
  :project="dashboard.state.activeProject"
40
22
  :active-step="dashboard.state.activeStep"
41
- :active-tab="dashboard.state.activeTab"
42
- :docs="dashboard.state.docs"
43
- :selected-doc-file="dashboard.state.selectedDocFile"
44
- :doc-content="dashboard.state.docContent"
45
- :doc-loading="dashboard.state.docLoading"
46
23
  @select-step="handleSelectStep"
47
- @switch-tab="handleSwitchTab"
48
- @select-doc-file="handleSelectDocFile"
49
24
  />
50
25
  </main>
51
26
 
52
- <!-- Center ↔ Right resize handle -->
53
- <div
54
- v-if="dashboard.state.isPanelOpen"
55
- class="w-[2px] flex-shrink-0 cursor-col-resize hover:bg-[#D97706] active:bg-[#D97706] relative z-20"
56
- style="background: #2A3040;"
57
- @mousedown="startDragRight"
58
- />
59
-
60
27
  <!-- Right: Detail Panel -->
61
28
  <aside
62
29
  :class="[
63
- 'flex-shrink-0 relative overflow-hidden',
64
- dashboard.state.isPanelOpen ? '' : 'w-0'
30
+ 'flex-shrink-0 transition-all duration-300 relative overflow-hidden',
31
+ dashboard.state.isPanelOpen ? 'w-[340px]' : 'w-0'
65
32
  ]"
66
- :style="dashboard.state.isPanelOpen ? `width: ${rightWidth}px; background: #FFFFFF; transition: none;` : 'background: #FFFFFF;'"
33
+ style="background: #111113; border-left: 1px solid #1F1F22;"
67
34
  >
68
35
  <DetailPanel
69
36
  :is-open="dashboard.state.isPanelOpen"
70
37
  :active-step="dashboard.state.activeStep"
71
38
  :logs="dashboard.state.logs"
72
- :detail-type="detailType"
73
- :detail-data="detailData"
74
- @close="handleDetailClose"
39
+ @close="dashboard.closePanel"
75
40
  @clear-logs="dashboard.clearLogs"
76
- @open-doc-file="handleOpenDocFromDetail"
77
41
  />
78
42
  </aside>
79
43
  </div>
@@ -99,11 +63,10 @@
99
63
  @select-stage="handleSelectStage"
100
64
  />
101
65
  </div>
102
- </n-config-provider>
103
66
  </template>
104
67
 
105
68
  <script setup>
106
- import { ref, onMounted, readonly } from 'vue'
69
+ import { ref, onMounted } from 'vue'
107
70
  import { useWebSocket } from './composables/useWebSocket.js'
108
71
  import { useDashboard } from './composables/useDashboard.js'
109
72
  import { useDashboardKeyboard } from './composables/useKeyboard.js'
@@ -111,7 +74,6 @@ import ProjectList from './components/ProjectList.vue'
111
74
  import PipelineView from './components/PipelineView.vue'
112
75
  import DetailPanel from './components/DetailPanel.vue'
113
76
  import ActionBar from './components/ActionBar.vue'
114
- import ProjectOverview from './components/ProjectOverview.vue'
115
77
  import CommandPalette from './components/CommandPalette.vue'
116
78
 
117
79
  // Composables
@@ -119,70 +81,6 @@ const ws = useWebSocket()
119
81
  const dashboard = useDashboard()
120
82
  const isCommandPaletteOpen = ref(false)
121
83
  const executionResult = ref(null)
122
- const scanPaths = ref([])
123
- const detailType = ref(null)
124
- const detailData = ref(null)
125
-
126
- // Panel resize state
127
- const STORAGE_KEY = 'dashboard-panel-widths'
128
- const MIN_LEFT = 180
129
- const MIN_CENTER = 300
130
- const MIN_RIGHT = 260
131
- const DEFAULT_LEFT = 240
132
- const DEFAULT_RIGHT = 340
133
-
134
- const leftWidth = ref(DEFAULT_LEFT)
135
- const rightWidth = ref(DEFAULT_RIGHT)
136
- const isDragging = ref(false)
137
-
138
- // Load persisted widths
139
- try {
140
- const saved = JSON.parse(localStorage.getItem(STORAGE_KEY))
141
- if (saved?.left >= MIN_LEFT) leftWidth.value = saved.left
142
- if (saved?.right >= MIN_RIGHT) rightWidth.value = saved.right
143
- } catch {}
144
-
145
- function persistWidths() {
146
- localStorage.setItem(STORAGE_KEY, JSON.stringify({ left: leftWidth.value, right: rightWidth.value }))
147
- }
148
-
149
- function startDragLeft(e) {
150
- e.preventDefault()
151
- isDragging.value = true
152
- const startX = e.clientX
153
- const startW = leftWidth.value
154
- const onMove = (ev) => {
155
- const delta = ev.clientX - startX
156
- leftWidth.value = Math.max(MIN_LEFT, startW + delta)
157
- }
158
- const onUp = () => {
159
- window.removeEventListener('mousemove', onMove)
160
- window.removeEventListener('mouseup', onUp)
161
- isDragging.value = false
162
- persistWidths()
163
- }
164
- window.addEventListener('mousemove', onMove)
165
- window.addEventListener('mouseup', onUp)
166
- }
167
-
168
- function startDragRight(e) {
169
- e.preventDefault()
170
- isDragging.value = true
171
- const startX = e.clientX
172
- const startW = rightWidth.value
173
- const onMove = (ev) => {
174
- const delta = startX - ev.clientX
175
- rightWidth.value = Math.max(MIN_RIGHT, startW + delta)
176
- }
177
- const onUp = () => {
178
- window.removeEventListener('mousemove', onMove)
179
- window.removeEventListener('mouseup', onUp)
180
- isDragging.value = false
181
- persistWidths()
182
- }
183
- window.addEventListener('mousemove', onMove)
184
- window.addEventListener('mouseup', onUp)
185
- }
186
84
 
187
85
  // Keyboard shortcuts
188
86
  useDashboardKeyboard({
@@ -225,37 +123,11 @@ onMounted(() => {
225
123
  executionResult.value = { exitCode: -1, signal: 'SIGTERM' }
226
124
  }
227
125
  })
228
- ws.on('scan:paths', (paths) => { scanPaths.value = paths })
229
- ws.on('docs:tree', (docs) => { dashboard.updateDocs(docs) })
230
126
  })
231
127
 
232
- function handleSelectProject(project) {
233
- dashboard.selectProject(project)
234
- dashboard.selectDocFile(null)
235
- dashboard.setDocContent('')
236
- // Request docs for this project
237
- if (project?.path) {
238
- ws.send({ type: 'docs:get', data: { projectPath: project.path } })
239
- }
240
- }
128
+ function handleSelectProject(project) { dashboard.selectProject(project) }
241
129
  function handleSelectStage({ project, stage }) { dashboard.selectProject(project) }
242
130
  function handleSelectStep(step) { dashboard.selectStep(step) }
243
- function handleSwitchTab(tab) { dashboard.setActiveTab(tab) }
244
- function handleSelectDocFile(file) {
245
- dashboard.selectDocFile(file)
246
- dashboard.setDocLoading(true)
247
- // Fetch doc content via REST API
248
- fetch(`/api/docs/content?path=${encodeURIComponent(file.path)}`)
249
- .then(r => r.ok ? r.text() : '')
250
- .then(content => {
251
- dashboard.setDocContent(content)
252
- dashboard.setDocLoading(false)
253
- })
254
- .catch(() => {
255
- dashboard.setDocContent('')
256
- dashboard.setDocLoading(false)
257
- })
258
- }
259
131
  function handleExecute() {
260
132
  const projectName = dashboard.activeProjectName.value
261
133
  if (!projectName) return
@@ -267,49 +139,6 @@ function handleKill() {
267
139
  if (!projectName) return
268
140
  ws.send({ type: 'cli:kill', data: { projectName } })
269
141
  }
270
- function handleAddScanPath(path) {
271
- ws.send({ type: 'scan:add-path', data: { path } })
272
- }
273
- function handleRemoveScanPath(path) {
274
- ws.send({ type: 'scan:remove-path', data: { path } })
275
- }
276
-
277
- async function handleShowDetail(type) {
278
- const project = dashboard.state.activeProject
279
- if (!project?.path) return
280
- detailType.value = type
281
- detailData.value = null
282
- dashboard.openPanel()
283
- try {
284
- const url = `/api/projects/${encodeURIComponent(project.path)}/detail?type=${type}`
285
- const res = await fetch(url)
286
- if (res.ok) detailData.value = await res.json()
287
- } catch {}
288
- }
289
-
290
- function handleDetailClose() {
291
- detailType.value = null
292
- detailData.value = null
293
- dashboard.closePanel()
294
- }
295
-
296
- function handleOpenDocFromDetail(file) {
297
- detailType.value = null
298
- detailData.value = null
299
- dashboard.setActiveTab('docs')
300
- handleSelectDocFile(file)
301
- }
302
-
303
- const themeOverrides = {
304
- common: {
305
- primaryColor: '#D97706',
306
- primaryColorHover: '#F59E0B',
307
- primaryColorPressed: '#B45309',
308
- borderRadius: '6px',
309
- fontFamily: 'DM Sans, sans-serif',
310
- fontFamilyMono: 'JetBrains Mono, monospace'
311
- }
312
- }
313
142
  </script>
314
143
 
315
144
  <style>