sillyspec 3.8.5 → 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.
- package/README.md +0 -6
- package/docs/.vitepress/config.mts +45 -0
- package/docs/.vitepress/dist/404.html +25 -0
- package/docs/.vitepress/dist/assets/app.YytxICdd.js +1 -0
- package/docs/.vitepress/dist/assets/chunks/framework.Czhw_PXq.js +19 -0
- package/docs/.vitepress/dist/assets/chunks/theme.DusTRZQk.js +1 -0
- package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.js +1 -0
- package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.lean.js +1 -0
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.js +15 -0
- package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.lean.js +1 -0
- package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.js +4 -0
- package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.lean.js +1 -0
- package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.js +1 -0
- package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.lean.js +1 -0
- package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.js +4 -0
- package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.lean.js +1 -0
- package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.js +5 -0
- package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.lean.js +1 -0
- package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.js +28 -0
- package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.lean.js +1 -0
- package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.js +30 -0
- package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.lean.js +1 -0
- package/docs/.vitepress/dist/assets/style.DFTx90Kk.css +1 -0
- package/docs/.vitepress/dist/hashmap.json +1 -0
- package/docs/.vitepress/dist/index.html +28 -0
- package/docs/.vitepress/dist/sillyspec/commands.html +42 -0
- package/docs/.vitepress/dist/sillyspec/dashboard.html +31 -0
- package/docs/.vitepress/dist/sillyspec/file-io.html +28 -0
- package/docs/.vitepress/dist/sillyspec/getting-started.html +31 -0
- package/docs/.vitepress/dist/sillyspec/install.html +32 -0
- package/docs/.vitepress/dist/sillyspec/lifecycle.html +55 -0
- package/docs/.vitepress/dist/sillyspec/structure.html +57 -0
- package/docs/.vitepress/dist/vp-icons.css +1 -0
- package/docs/index.md +34 -0
- package/docs/sillyspec/commands.md +218 -0
- package/docs/sillyspec/dashboard.md +51 -0
- package/docs/sillyspec/file-io.md +34 -0
- package/docs/sillyspec/getting-started.md +61 -0
- package/docs/sillyspec/install.md +51 -0
- package/docs/sillyspec/lifecycle.md +146 -0
- package/docs/sillyspec/structure.md +62 -0
- package/package.json +11 -9
- package/packages/dashboard/dist/assets/index-Bh-GPjKY.css +1 -0
- package/packages/dashboard/dist/assets/index-CrCn5Gg6.js +17 -0
- package/packages/dashboard/dist/index.html +2 -2
- package/packages/dashboard/package-lock.json +0 -220
- package/packages/dashboard/package.json +5 -8
- package/packages/dashboard/server/index.js +106 -255
- package/packages/dashboard/server/parser.js +29 -333
- package/packages/dashboard/server/watcher.js +131 -203
- package/packages/dashboard/src/App.vue +10 -181
- package/packages/dashboard/src/components/ActionBar.vue +42 -26
- package/packages/dashboard/src/components/CommandPalette.vue +65 -40
- package/packages/dashboard/src/components/DetailPanel.vue +53 -68
- package/packages/dashboard/src/components/LogStream.vue +33 -13
- package/packages/dashboard/src/components/PipelineStage.vue +8 -8
- package/packages/dashboard/src/components/PipelineView.vue +45 -80
- package/packages/dashboard/src/components/ProjectList.vue +45 -103
- package/packages/dashboard/src/components/StageBadge.vue +13 -13
- package/packages/dashboard/src/components/StepCard.vue +15 -15
- package/packages/dashboard/src/composables/useDashboard.js +6 -20
- package/packages/dashboard/src/composables/useKeyboard.js +4 -6
- package/packages/dashboard/src/main.js +1 -4
- package/packages/dashboard/src/style.css +17 -17
- package/src/index.js +12 -123
- package/src/init.js +227 -86
- package/src/setup.js +9 -1
- package/templates/archive.md +121 -0
- package/templates/brainstorm.md +240 -0
- package/{.claude/skills/sillyspec-commit/SKILL.md → templates/commit.md} +47 -29
- package/templates/continue.md +32 -0
- package/templates/execute.md +314 -0
- package/templates/explore.md +60 -0
- package/templates/export.md +21 -0
- package/templates/init.md +61 -0
- package/templates/plan.md +157 -0
- package/templates/quick.md +135 -0
- package/templates/scan-quick.md +49 -0
- package/templates/scan.md +172 -0
- package/templates/skills/playwright-e2e/SKILL.md +340 -0
- package/templates/status.md +75 -0
- package/templates/verify.md +253 -0
- package/templates/workspace-sync.md +99 -0
- package/templates/workspace.md +70 -0
- package/.claude/skills/sillyspec-archive/SKILL.md +0 -17
- package/.claude/skills/sillyspec-auto/SKILL.md +0 -77
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +0 -17
- package/.claude/skills/sillyspec-continue/SKILL.md +0 -44
- package/.claude/skills/sillyspec-doctor/SKILL.md +0 -22
- package/.claude/skills/sillyspec-execute/SKILL.md +0 -17
- package/.claude/skills/sillyspec-explore/SKILL.md +0 -96
- package/.claude/skills/sillyspec-export/SKILL.md +0 -53
- package/.claude/skills/sillyspec-init/SKILL.md +0 -170
- package/.claude/skills/sillyspec-plan/SKILL.md +0 -52
- package/.claude/skills/sillyspec-propose/SKILL.md +0 -17
- package/.claude/skills/sillyspec-quick/SKILL.md +0 -17
- package/.claude/skills/sillyspec-resume/SKILL.md +0 -111
- package/.claude/skills/sillyspec-scan/SKILL.md +0 -17
- package/.claude/skills/sillyspec-state/SKILL.md +0 -54
- package/.claude/skills/sillyspec-status/SKILL.md +0 -17
- package/.claude/skills/sillyspec-verify/SKILL.md +0 -17
- package/.claude/skills/sillyspec-workspace/SKILL.md +0 -149
- package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
- package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
- package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
- package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
- package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
- package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
- package/.sillyspec/changes/auto-mode/design.md +0 -50
- package/.sillyspec/changes/auto-mode/proposal.md +0 -19
- package/.sillyspec/changes/auto-mode/requirements.md +0 -21
- package/.sillyspec/changes/auto-mode/tasks.md +0 -7
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
- package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
- package/.sillyspec/changes/run-command-design/design.md +0 -1230
- package/.sillyspec/changes/unified-docs-design/design.md +0 -199
- package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
- package/.sillyspec/knowledge/INDEX.md +0 -8
- package/.sillyspec/knowledge/uncategorized.md +0 -3
- package/.sillyspec/projects/sillyspec.yaml +0 -3
- package/packages/dashboard/dist/assets/index-D1EVTLmc.js +0 -7446
- package/packages/dashboard/dist/assets/index-DGe8CqeP.css +0 -1
- package/packages/dashboard/public/logo.jpg +0 -0
- package/packages/dashboard/src/components/DocPreview.vue +0 -160
- package/packages/dashboard/src/components/DocTree.vue +0 -58
- package/packages/dashboard/src/components/ProjectOverview.vue +0 -178
- package/packages/dashboard/src/components/detail/DocsDetail.vue +0 -48
- package/packages/dashboard/src/components/detail/GitDetail.vue +0 -61
- package/packages/dashboard/src/components/detail/TechDetail.vue +0 -43
- package/src/derive.js +0 -147
- package/src/migrate.js +0 -117
- package/src/progress.js +0 -495
- package/src/run.js +0 -640
- package/src/stages/archive.js +0 -54
- package/src/stages/brainstorm.js +0 -239
- package/src/stages/doctor.js +0 -312
- package/src/stages/execute.js +0 -259
- package/src/stages/index.js +0 -35
- package/src/stages/plan.js +0 -259
- package/src/stages/propose.js +0 -115
- package/src/stages/quick.js +0 -64
- package/src/stages/scan.js +0 -141
- package/src/stages/status.js +0 -65
- package/src/stages/verify.js +0 -135
- /package/.sillyspec/{changes/brainstorm-archive → specs}/2026-04-05-dashboard-design.md +0 -0
- /package/{packages/dashboard → docs/.vitepress}/dist/favicon.jpg +0 -0
- /package/{logo.jpg → docs/.vitepress/dist/logo.jpg} +0 -0
- /package/{packages/dashboard → docs}/public/favicon.jpg +0 -0
- /package/{packages/dashboard/dist → docs/public}/logo.jpg +0 -0
|
@@ -1,202 +1,100 @@
|
|
|
1
1
|
import chokidar from 'chokidar'
|
|
2
|
-
import { join
|
|
2
|
+
import { join } from 'path'
|
|
3
3
|
import { homedir } from 'os'
|
|
4
|
-
import { existsSync
|
|
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
|
-
*
|
|
23
|
-
* @param {
|
|
24
|
-
* @returns {
|
|
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
|
|
27
|
-
if (
|
|
28
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
//
|
|
65
|
-
const
|
|
66
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
67
|
+
for (const baseDir of scanDirs) {
|
|
68
|
+
try {
|
|
69
|
+
const { readdirSync } = require('fs')
|
|
70
|
+
const entries = readdirSync(baseDir, { withFileTypes: true })
|
|
106
71
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (!entry.isDirectory()) continue
|
|
74
|
+
if (shouldExclude(entry.name)) continue
|
|
110
75
|
|
|
111
|
-
|
|
76
|
+
const dirPath = join(baseDir, entry.name)
|
|
112
77
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
84
|
+
if (existsSync(sillyspecPath)) {
|
|
85
|
+
watchPaths.push(sillyspecPath)
|
|
86
|
+
projects.push({
|
|
87
|
+
name: entry.name,
|
|
88
|
+
path: dirPath
|
|
89
|
+
})
|
|
90
|
+
}
|
|
131
91
|
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
177
|
+
const home = homedir()
|
|
178
|
+
const cwd = process.cwd()
|
|
279
179
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
294
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
317
|
-
|
|
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
|
-
<
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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>
|