sillyspec 3.9.0 → 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.
- package/.claude/skills/sillyspec-archive/SKILL.md +17 -0
- package/.claude/skills/sillyspec-auto/SKILL.md +78 -0
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +17 -0
- package/{templates/commit.md → .claude/skills/sillyspec-commit/SKILL.md} +32 -47
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -0
- package/.claude/skills/sillyspec-doctor/SKILL.md +27 -0
- package/.claude/skills/sillyspec-execute/SKILL.md +17 -0
- package/.claude/skills/sillyspec-explore/SKILL.md +105 -0
- package/.claude/skills/sillyspec-export/SKILL.md +53 -0
- package/.claude/skills/sillyspec-init/SKILL.md +170 -0
- package/.claude/skills/sillyspec-plan/SKILL.md +17 -0
- package/.claude/skills/sillyspec-propose/SKILL.md +17 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +17 -0
- package/.claude/skills/sillyspec-resume/SKILL.md +111 -0
- package/.claude/skills/sillyspec-scan/SKILL.md +17 -0
- package/.claude/skills/sillyspec-state/SKILL.md +54 -0
- package/.claude/skills/sillyspec-status/SKILL.md +17 -0
- package/.claude/skills/sillyspec-verify/SKILL.md +17 -0
- package/.claude/skills/sillyspec-workspace/SKILL.md +149 -0
- package/README.md +19 -11
- package/SKILL.md +15 -10
- package/package.json +7 -9
- package/packages/dashboard/dist/assets/index-BcM2J-hv.css +1 -0
- package/packages/dashboard/dist/assets/index-DpLHK4jv.js +7446 -0
- package/packages/dashboard/dist/index.html +16 -16
- package/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/packages/dashboard/dist/prototype-overview.html +256 -0
- package/packages/dashboard/package-lock.json +226 -6
- package/packages/dashboard/package.json +8 -5
- package/packages/dashboard/public/logo.jpg +0 -0
- package/packages/dashboard/public/prototype-dashboard.html +836 -0
- package/packages/dashboard/public/prototype-overview.html +256 -0
- package/packages/dashboard/server/executor.js +1 -1
- package/packages/dashboard/server/index.js +341 -113
- package/packages/dashboard/server/parser.js +442 -30
- package/packages/dashboard/server/watcher.js +214 -134
- package/packages/dashboard/src/App.vue +475 -71
- package/packages/dashboard/src/components/ActionBar.vue +36 -43
- package/packages/dashboard/src/components/CommandPalette.vue +45 -66
- package/packages/dashboard/src/components/DetailPanel.vue +68 -53
- package/packages/dashboard/src/components/DocPreview.vue +257 -0
- package/packages/dashboard/src/components/DocTree.vue +114 -0
- package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
- package/packages/dashboard/src/components/LogStream.vue +13 -33
- package/packages/dashboard/src/components/PipelineStage.vue +8 -8
- package/packages/dashboard/src/components/PipelineView.vue +99 -45
- package/packages/dashboard/src/components/ProjectCard.vue +187 -0
- package/packages/dashboard/src/components/ProjectList.vue +103 -45
- package/packages/dashboard/src/components/ProjectOverview.vue +152 -0
- package/packages/dashboard/src/components/StageBadge.vue +13 -13
- package/packages/dashboard/src/components/StepCard.vue +15 -15
- package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -0
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -0
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -0
- package/packages/dashboard/src/composables/useDashboard.js +48 -6
- package/packages/dashboard/src/composables/useKeyboard.js +6 -4
- package/packages/dashboard/src/composables/useLayout.js +131 -0
- package/packages/dashboard/src/main.js +4 -1
- package/packages/dashboard/src/style.css +17 -17
- package/src/index.js +141 -22
- package/src/init.js +93 -231
- package/src/migrate.js +117 -0
- package/src/progress.js +460 -0
- package/src/run.js +635 -0
- package/src/setup.js +2 -72
- package/src/stages/archive.js +54 -0
- package/src/stages/brainstorm.js +264 -0
- package/src/stages/doctor.js +303 -0
- package/src/stages/execute.js +287 -0
- package/src/stages/explore.js +34 -0
- package/src/stages/index.js +28 -0
- package/src/stages/plan.js +354 -0
- package/src/stages/propose.js +115 -0
- package/src/stages/quick.js +64 -0
- package/src/stages/scan.js +141 -0
- package/src/stages/status.js +65 -0
- package/src/stages/verify.js +135 -0
- package/.sillyspec/changes/dashboard/design.md +0 -219
- package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
- package/.sillyspec/specs/2026-04-05-dashboard-design.md +0 -206
- package/dist/steps/brainstorm/01-load-context.md +0 -30
- package/dist/steps/brainstorm/02-reuse-check.md +0 -6
- package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
- package/dist/steps/brainstorm/04-module-split.md +0 -23
- package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
- package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
- package/dist/steps/brainstorm/07-present-design.md +0 -3
- package/dist/steps/brainstorm/08-write-design.md +0 -21
- package/dist/steps/brainstorm/09-self-review.md +0 -15
- package/dist/steps/brainstorm/10-user-confirm.md +0 -3
- package/dist/steps/brainstorm/11-output-spec.md +0 -7
- package/dist/steps/brainstorm/manifest.yaml +0 -26
- package/dist/steps/execute/01-load-context.md +0 -41
- package/dist/steps/execute/02-scan-conventions.md +0 -47
- package/dist/steps/execute/03-skill-mcp.md +0 -19
- package/dist/steps/execute/04-assign-task.md +0 -22
- package/dist/steps/execute/04b-prompt-template.md +0 -54
- package/dist/steps/execute/05-write-test.md +0 -7
- package/dist/steps/execute/06-write-code.md +0 -8
- package/dist/steps/execute/07-run-test.md +0 -26
- package/dist/steps/execute/08-fix-issues.md +0 -28
- package/dist/steps/execute/09-next-task.md +0 -33
- package/dist/steps/execute/manifest.yaml +0 -28
- package/dist/steps/plan/01-load-context.md +0 -22
- package/dist/steps/plan/02-anchor-confirm.md +0 -1
- package/dist/steps/plan/03-expand-tasks.md +0 -33
- package/dist/steps/plan/04-mark-order.md +0 -15
- package/dist/steps/plan/05-e2e-planning.md +0 -17
- package/dist/steps/plan/06-self-check.md +0 -16
- package/dist/steps/plan/07-save.md +0 -1
- package/dist/steps/plan/manifest.yaml +0 -18
- package/dist/steps/scan/01-env-detect.md +0 -51
- package/dist/steps/scan/02-tech-stack.md +0 -16
- package/dist/steps/scan/03-conventions.md +0 -16
- package/dist/steps/scan/04-structure.md +0 -19
- package/dist/steps/scan/05-quality.md +0 -18
- package/dist/steps/scan/06-complete.md +0 -49
- package/dist/steps/scan/manifest.yaml +0 -16
- package/dist/steps/verify/01-load-specs.md +0 -28
- package/dist/steps/verify/02-check-tasks.md +0 -1
- package/dist/steps/verify/03-check-design.md +0 -6
- package/dist/steps/verify/04-run-tests.md +0 -7
- package/dist/steps/verify/05-e2e-tests.md +0 -27
- package/dist/steps/verify/05b-e2e-fix.md +0 -33
- package/dist/steps/verify/06-code-quality.md +0 -25
- package/dist/steps/verify/07-lint-check.md +0 -27
- package/dist/steps/verify/08-output-report.md +0 -14
- package/dist/steps/verify/manifest.yaml +0 -22
- package/docs/.vitepress/config.mts +0 -45
- package/docs/.vitepress/dist/404.html +0 -25
- package/docs/.vitepress/dist/assets/app.YytxICdd.js +0 -1
- package/docs/.vitepress/dist/assets/chunks/framework.Czhw_PXq.js +0 -19
- package/docs/.vitepress/dist/assets/chunks/theme.DusTRZQk.js +0 -1
- package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.js +0 -1
- package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.lean.js +0 -1
- 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 +0 -15
- package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.js +0 -4
- package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.js +0 -4
- package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.js +0 -5
- package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.js +0 -28
- package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.js +0 -30
- package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.lean.js +0 -1
- package/docs/.vitepress/dist/assets/style.DFTx90Kk.css +0 -1
- package/docs/.vitepress/dist/hashmap.json +0 -1
- package/docs/.vitepress/dist/index.html +0 -28
- package/docs/.vitepress/dist/sillyspec/commands.html +0 -42
- package/docs/.vitepress/dist/sillyspec/dashboard.html +0 -31
- package/docs/.vitepress/dist/sillyspec/file-io.html +0 -28
- package/docs/.vitepress/dist/sillyspec/getting-started.html +0 -31
- package/docs/.vitepress/dist/sillyspec/install.html +0 -32
- package/docs/.vitepress/dist/sillyspec/lifecycle.html +0 -55
- package/docs/.vitepress/dist/sillyspec/structure.html +0 -57
- package/docs/.vitepress/dist/vp-icons.css +0 -1
- package/docs/index.md +0 -34
- package/docs/sillyspec/commands.md +0 -218
- package/docs/sillyspec/dashboard.md +0 -51
- package/docs/sillyspec/file-io.md +0 -34
- package/docs/sillyspec/getting-started.md +0 -61
- package/docs/sillyspec/install.md +0 -51
- package/docs/sillyspec/lifecycle.md +0 -146
- package/docs/sillyspec/structure.md +0 -62
- package/packages/dashboard/dist/assets/index-Bh-GPjKY.css +0 -1
- package/packages/dashboard/dist/assets/index-CrCn5Gg6.js +0 -17
- package/src/step.js +0 -543
- package/templates/archive.md +0 -120
- package/templates/brainstorm.md +0 -170
- package/templates/continue.md +0 -32
- package/templates/execute.md +0 -304
- package/templates/explore.md +0 -59
- package/templates/export.md +0 -21
- package/templates/init.md +0 -61
- package/templates/plan.md +0 -146
- package/templates/quick.md +0 -135
- package/templates/scan-quick.md +0 -49
- package/templates/scan.md +0 -156
- package/templates/skills/playwright-e2e/SKILL.md +0 -340
- package/templates/status.md +0 -75
- package/templates/verify.md +0 -236
- package/templates/workspace-sync.md +0 -99
- package/templates/workspace.md +0 -70
- /package/{docs/.vitepress/dist/logo.jpg → logo.jpg} +0 -0
- /package/{docs/.vitepress → packages/dashboard}/dist/favicon.jpg +0 -0
- /package/{docs/public → packages/dashboard/dist}/logo.jpg +0 -0
- /package/{docs → packages/dashboard}/public/favicon.jpg +0 -0
|
@@ -1,105 +1,212 @@
|
|
|
1
1
|
import chokidar from 'chokidar'
|
|
2
|
-
import { join } from 'path'
|
|
2
|
+
import { join, basename, dirname, sep } from 'path'
|
|
3
3
|
import { homedir } from 'os'
|
|
4
|
-
import { existsSync } from 'fs'
|
|
5
|
-
import { parseProjectState } from './parser.js'
|
|
4
|
+
import { existsSync, readdirSync, realpathSync } from 'fs'
|
|
5
|
+
import { parseProjectState, parseProjectOverview } 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
|
+
])
|
|
10
20
|
|
|
11
21
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @param {
|
|
14
|
-
* @returns {
|
|
22
|
+
* Check if directory should be excluded from scanning
|
|
23
|
+
* @param {string} name - Directory name
|
|
24
|
+
* @returns {boolean}
|
|
15
25
|
*/
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
|
|
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
|
+
}
|
|
19
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
|
|
39
|
+
}
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
20
42
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Build list of directories to scan
|
|
45
|
+
* @returns {string[]}
|
|
46
|
+
*/
|
|
47
|
+
function buildScanDirs() {
|
|
24
48
|
const home = homedir()
|
|
25
49
|
const cwd = process.cwd()
|
|
26
50
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
}
|
|
51
|
+
const scanDirs = new Set()
|
|
52
|
+
|
|
53
|
+
// Always scan cwd and its parent
|
|
54
|
+
scanDirs.add(cwd)
|
|
55
|
+
scanDirs.add(dirname(cwd))
|
|
51
56
|
|
|
52
|
-
//
|
|
53
|
-
const
|
|
54
|
-
|
|
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)
|
|
63
|
+
|
|
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
|
+
]
|
|
55
74
|
|
|
56
75
|
for (const extra of extraDirs) {
|
|
57
76
|
const extraPath = join(home, extra)
|
|
58
77
|
if (existsSync(extraPath)) {
|
|
59
|
-
scanDirs.
|
|
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)
|
|
60
86
|
}
|
|
61
87
|
}
|
|
62
88
|
|
|
63
|
-
|
|
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()
|
|
64
102
|
const projects = []
|
|
65
|
-
const seen = new Set() // Dedupe by path
|
|
66
103
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const { readdirSync } = require('fs')
|
|
70
|
-
const entries = readdirSync(baseDir, { withFileTypes: true })
|
|
104
|
+
try {
|
|
105
|
+
const entries = readdirSync(baseDir, { withFileTypes: true })
|
|
71
106
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
if (!entry.isDirectory()) continue
|
|
109
|
+
if (shouldExclude(entry.name, cwd)) continue
|
|
75
110
|
|
|
76
|
-
|
|
111
|
+
const dirPath = join(baseDir, entry.name)
|
|
77
112
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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)
|
|
81
118
|
|
|
82
|
-
|
|
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
|
+
}
|
|
83
127
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
name: entry.name,
|
|
88
|
-
path: dirPath
|
|
89
|
-
})
|
|
90
|
-
}
|
|
128
|
+
// Recurse into subdirectories if not at max depth
|
|
129
|
+
if (currentDepth < maxDepth) {
|
|
130
|
+
projects.push(...scanDirectory(dirPath, seen, maxDepth, currentDepth + 1))
|
|
91
131
|
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
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
|
+
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)
|
|
155
|
+
const sillyspecPath = join(cwd, '.sillyspec')
|
|
156
|
+
if (existsSync(sillyspecPath)) {
|
|
157
|
+
projects.push({
|
|
158
|
+
name: basename(cwd),
|
|
159
|
+
path: cwd
|
|
160
|
+
})
|
|
95
161
|
}
|
|
96
162
|
}
|
|
97
163
|
|
|
164
|
+
return projects
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Discover all .sillyspec projects
|
|
169
|
+
* @returns {{ projects: Array, watchPaths: string[] }}
|
|
170
|
+
*/
|
|
171
|
+
function discoverAll() {
|
|
172
|
+
const scanDirs = buildScanDirs()
|
|
173
|
+
const seen = new Set()
|
|
174
|
+
const allProjects = []
|
|
175
|
+
|
|
176
|
+
// Check cwd itself first
|
|
177
|
+
allProjects.push(...scanSelf(seen))
|
|
178
|
+
|
|
179
|
+
// Scan each base directory
|
|
180
|
+
for (const baseDir of scanDirs) {
|
|
181
|
+
allProjects.push(...scanDirectory(baseDir, seen, 2, 0))
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Build watch paths
|
|
185
|
+
const watchPaths = allProjects.map(p => join(p.path, '.sillyspec'))
|
|
186
|
+
|
|
187
|
+
return { projects: allProjects, watchPaths }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Start watching all .sillyspec directories
|
|
192
|
+
* @param {function} callback - Callback function when projects are updated
|
|
193
|
+
* @returns {object} The watcher instance
|
|
194
|
+
*/
|
|
195
|
+
export function startWatcher(callback) {
|
|
196
|
+
if (watcher) {
|
|
197
|
+
stopWatcher()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
updateCallback = callback
|
|
201
|
+
|
|
202
|
+
const { projects, watchPaths } = discoverAll()
|
|
203
|
+
|
|
98
204
|
// Parse initial states
|
|
99
205
|
for (const project of projects) {
|
|
100
206
|
const state = parseProjectState(project.path)
|
|
207
|
+
const overview = parseProjectOverview(project.path)
|
|
101
208
|
if (state) {
|
|
102
|
-
projectStates.set(project.name, { ...project, state })
|
|
209
|
+
projectStates.set(project.name, { ...project, state, overview })
|
|
103
210
|
}
|
|
104
211
|
}
|
|
105
212
|
|
|
@@ -144,27 +251,27 @@ export function startWatcher(callback) {
|
|
|
144
251
|
* @param {string} filePath - Path to the changed file
|
|
145
252
|
*/
|
|
146
253
|
async function handleFileChange(filePath) {
|
|
147
|
-
//
|
|
254
|
+
// Normalize path for comparison
|
|
255
|
+
const normalizedPath = filePath.replace(/\\/g, '/')
|
|
256
|
+
|
|
148
257
|
const projectName = Array.from(projectStates.values()).find(p =>
|
|
149
|
-
|
|
258
|
+
normalizedPath.startsWith(p.path.replace(/\\/g, '/'))
|
|
150
259
|
)?.name
|
|
151
260
|
|
|
152
261
|
if (!projectName) {
|
|
153
|
-
// Re-scan for new projects
|
|
154
262
|
await rescanProjects()
|
|
155
263
|
return
|
|
156
264
|
}
|
|
157
265
|
|
|
158
|
-
// Re-parse the project state
|
|
159
266
|
const project = projectStates.get(projectName)
|
|
160
267
|
if (project) {
|
|
161
268
|
const newState = parseProjectState(project.path)
|
|
269
|
+
const overview = parseProjectOverview(project.path)
|
|
162
270
|
if (newState) {
|
|
163
|
-
projectStates.set(projectName, { ...project, state: newState })
|
|
271
|
+
projectStates.set(projectName, { ...project, state: newState, overview })
|
|
164
272
|
}
|
|
165
273
|
}
|
|
166
274
|
|
|
167
|
-
// Emit updated state
|
|
168
275
|
if (updateCallback) {
|
|
169
276
|
updateCallback(Array.from(projectStates.values()))
|
|
170
277
|
}
|
|
@@ -174,77 +281,51 @@ async function handleFileChange(filePath) {
|
|
|
174
281
|
* Re-scan for projects (e.g., new .sillyspec directories)
|
|
175
282
|
*/
|
|
176
283
|
async function rescanProjects() {
|
|
177
|
-
const
|
|
178
|
-
const cwd = process.cwd()
|
|
284
|
+
const { projects } = discoverAll()
|
|
179
285
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$')
|
|
192
|
-
if (regex.test(name)) return true
|
|
286
|
+
for (const project of projects) {
|
|
287
|
+
if (!projectStates.has(project.name)) {
|
|
288
|
+
const state = parseProjectState(project.path)
|
|
289
|
+
const overview = parseProjectOverview(project.path)
|
|
290
|
+
if (state) {
|
|
291
|
+
projectStates.set(project.name, {
|
|
292
|
+
name: project.name,
|
|
293
|
+
path: project.path,
|
|
294
|
+
state,
|
|
295
|
+
overview
|
|
296
|
+
})
|
|
193
297
|
}
|
|
194
298
|
}
|
|
195
|
-
if (name.startsWith('.') && name !== cwd.split('/').pop()) {
|
|
196
|
-
return true
|
|
197
|
-
}
|
|
198
|
-
return false
|
|
199
299
|
}
|
|
200
300
|
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
}
|
|
301
|
+
if (updateCallback) {
|
|
302
|
+
updateCallback(Array.from(projectStates.values()))
|
|
210
303
|
}
|
|
304
|
+
}
|
|
211
305
|
|
|
212
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Add a custom scan path and rescan
|
|
308
|
+
* @param {string} path - Path to add
|
|
309
|
+
*/
|
|
310
|
+
export function addCustomScanPath(path) {
|
|
311
|
+
customScanPaths.add(path)
|
|
312
|
+
rescanProjects()
|
|
313
|
+
}
|
|
213
314
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
}
|
|
315
|
+
/**
|
|
316
|
+
* Remove a custom scan path
|
|
317
|
+
* @param {string} path - Path to remove
|
|
318
|
+
*/
|
|
319
|
+
export function removeCustomScanPath(path) {
|
|
320
|
+
customScanPaths.delete(path)
|
|
321
|
+
}
|
|
244
322
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
323
|
+
/**
|
|
324
|
+
* Get list of custom scan paths
|
|
325
|
+
* @returns {string[]}
|
|
326
|
+
*/
|
|
327
|
+
export function getCustomScanPaths() {
|
|
328
|
+
return Array.from(customScanPaths)
|
|
248
329
|
}
|
|
249
330
|
|
|
250
331
|
/**
|
|
@@ -274,4 +355,3 @@ export function getProjectStates() {
|
|
|
274
355
|
export function getProjectState(projectName) {
|
|
275
356
|
return projectStates.get(projectName) || null
|
|
276
357
|
}
|
|
277
|
-
|