sillyspec 3.8.7 → 3.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +96 -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 +52 -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/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
- package/.sillyspec/changes/auto-mode/design.md +50 -0
- package/.sillyspec/changes/auto-mode/proposal.md +19 -0
- package/.sillyspec/changes/auto-mode/requirements.md +21 -0
- package/.sillyspec/changes/auto-mode/tasks.md +7 -0
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
- package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
- package/.sillyspec/changes/run-command-design/design.md +1230 -0
- package/.sillyspec/changes/unified-docs-design/design.md +199 -0
- package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
- package/.sillyspec/knowledge/INDEX.md +8 -0
- package/.sillyspec/knowledge/uncategorized.md +3 -0
- package/.sillyspec/projects/sillyspec.yaml +3 -0
- package/README.md +12 -5
- package/package.json +9 -11
- package/packages/dashboard/dist/assets/index-CntACGUN.css +1 -0
- package/packages/dashboard/dist/assets/index-RsLVPAy7.js +7446 -0
- package/packages/dashboard/dist/index.html +3 -2
- 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/server/executor.js +1 -1
- package/packages/dashboard/server/index.js +336 -113
- package/packages/dashboard/server/parser.js +333 -29
- package/packages/dashboard/server/watcher.js +203 -131
- package/packages/dashboard/src/App.vue +187 -11
- package/packages/dashboard/src/components/ActionBar.vue +26 -42
- package/packages/dashboard/src/components/CommandPalette.vue +40 -65
- package/packages/dashboard/src/components/DetailPanel.vue +68 -53
- package/packages/dashboard/src/components/DocPreview.vue +160 -0
- package/packages/dashboard/src/components/DocTree.vue +58 -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 +80 -45
- package/packages/dashboard/src/components/ProjectList.vue +103 -45
- package/packages/dashboard/src/components/ProjectOverview.vue +178 -0
- package/packages/dashboard/src/components/StageBadge.vue +13 -13
- package/packages/dashboard/src/components/StepCard.vue +15 -15
- 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 +20 -6
- package/packages/dashboard/src/composables/useKeyboard.js +6 -4
- package/packages/dashboard/src/main.js +4 -1
- package/packages/dashboard/src/style.css +17 -17
- package/src/index.js +136 -14
- package/src/init.js +83 -228
- package/src/migrate.js +117 -0
- package/src/progress.js +459 -0
- package/src/run.js +624 -0
- package/src/setup.js +2 -72
- package/src/stages/archive.js +54 -0
- package/src/stages/brainstorm.js +239 -0
- package/src/stages/doctor.js +303 -0
- package/src/stages/execute.js +262 -0
- package/src/stages/index.js +26 -0
- package/src/stages/plan.js +282 -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/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/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/.sillyspec/{specs → changes/brainstorm-archive}/2026-04-05-dashboard-design.md +0 -0
- /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,59 +1,363 @@
|
|
|
1
|
-
import { readFileSync, existsSync, readdirSync } from 'fs'
|
|
1
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'fs'
|
|
2
|
+
import { execSync } from 'child_process'
|
|
2
3
|
import { join } from 'path'
|
|
3
4
|
import { fileURLToPath } from 'url'
|
|
4
5
|
import { dirname } from 'path'
|
|
5
6
|
|
|
6
7
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Parse docs tree for a project
|
|
11
|
+
* @param {string} projectPath - Path to the project directory
|
|
12
|
+
* @returns {object} Docs tree grouped by type
|
|
13
|
+
*/
|
|
14
|
+
// Known framework detection keywords in package.json dependencies
|
|
15
|
+
const FRAMEWORK_PATTERNS = [
|
|
16
|
+
{ keys: ['react', 'react-dom'], name: 'React' },
|
|
17
|
+
{ keys: ['vue'], name: 'Vue' },
|
|
18
|
+
{ keys: ['next'], name: 'Next.js' },
|
|
19
|
+
{ keys: ['nuxt'], name: 'Nuxt' },
|
|
20
|
+
{ keys: ['express'], name: 'Express' },
|
|
21
|
+
{ keys: ['koa'], name: 'Koa' },
|
|
22
|
+
{ keys: ['fastify'], name: 'Fastify' },
|
|
23
|
+
{ keys: ['nestjs', '@nestjs/core'], name: 'NestJS' },
|
|
24
|
+
{ keys: ['svelte'], name: 'Svelte' },
|
|
25
|
+
{ keys: ['astro'], name: 'Astro' },
|
|
26
|
+
{ keys: ['vite'], name: 'Vite' },
|
|
27
|
+
{ keys: ['webpack'], name: 'Webpack' },
|
|
28
|
+
{ keys: ['typescript'], name: 'TypeScript' },
|
|
29
|
+
{ keys: ['tailwindcss'], name: 'Tailwind' },
|
|
30
|
+
{ keys: ['prisma'], name: 'Prisma' },
|
|
31
|
+
{ keys: ['drizzle-orm'], name: 'Drizzle' },
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parse project overview info
|
|
36
|
+
* @param {string} projectPath
|
|
37
|
+
* @returns {object} Overview data
|
|
38
|
+
*/
|
|
39
|
+
export function parseProjectOverview(projectPath) {
|
|
40
|
+
const result = {
|
|
41
|
+
techStack: [],
|
|
42
|
+
lastActive: null,
|
|
43
|
+
docStats: { design: 0, plan: 0, archive: 0, changes: 0, scan: 0, quicklog: 0, total: 0 },
|
|
44
|
+
git: { branch: '', lastCommit: '', dirtyCount: 0 }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- Tech stack ---
|
|
48
|
+
const pkgPath = join(projectPath, 'package.json')
|
|
49
|
+
if (existsSync(pkgPath)) {
|
|
50
|
+
try {
|
|
51
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
|
52
|
+
const deps = Object.keys({ ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) })
|
|
53
|
+
for (const pattern of FRAMEWORK_PATTERNS) {
|
|
54
|
+
if (pattern.keys.some(k => deps.includes(k))) {
|
|
55
|
+
result.techStack.push(pattern.name)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch {}
|
|
59
|
+
}
|
|
60
|
+
if (existsSync(join(projectPath, 'pom.xml'))) {
|
|
61
|
+
result.techStack.push('Java')
|
|
62
|
+
try {
|
|
63
|
+
const content = readFileSync(join(projectPath, 'pom.xml'), 'utf-8')
|
|
64
|
+
if (content.includes('spring-boot')) result.techStack.push('Spring Boot')
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
67
|
+
if (existsSync(join(projectPath, 'build.gradle')) || existsSync(join(projectPath, 'build.gradle.kts'))) {
|
|
68
|
+
if (!result.techStack.includes('Java')) result.techStack.push('Gradle')
|
|
69
|
+
}
|
|
70
|
+
if (existsSync(join(projectPath, 'requirements.txt')) || existsSync(join(projectPath, 'pyproject.toml'))) {
|
|
71
|
+
result.techStack.push('Python')
|
|
72
|
+
}
|
|
73
|
+
if (existsSync(join(projectPath, 'go.mod'))) {
|
|
74
|
+
result.techStack.push('Go')
|
|
75
|
+
}
|
|
76
|
+
if (result.techStack.length === 0) result.techStack = []
|
|
77
|
+
|
|
78
|
+
// --- Last active ---
|
|
79
|
+
const sillyspecDir = join(projectPath, '.sillyspec')
|
|
80
|
+
const progressPath = join(sillyspecDir, '.runtime', 'progress.json')
|
|
81
|
+
if (existsSync(progressPath)) {
|
|
82
|
+
try {
|
|
83
|
+
const progress = JSON.parse(readFileSync(progressPath, 'utf-8'))
|
|
84
|
+
if (progress.stages) {
|
|
85
|
+
for (const stageData of Object.values(progress.stages)) {
|
|
86
|
+
if (stageData.lastActive && (!result.lastActive || new Date(stageData.lastActive) > new Date(result.lastActive))) {
|
|
87
|
+
result.lastActive = stageData.lastActive
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (progress.lastActive) result.lastActive = progress.lastActive
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
94
|
+
if (!result.lastActive) {
|
|
95
|
+
// Fallback: most recently modified file in .sillyspec
|
|
96
|
+
try {
|
|
97
|
+
const findRecent = (dir) => {
|
|
98
|
+
let latest = null
|
|
99
|
+
if (!existsSync(dir)) return latest
|
|
100
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
101
|
+
const p = join(dir, entry.name)
|
|
102
|
+
try {
|
|
103
|
+
const s = statSync(p)
|
|
104
|
+
if (entry.isDirectory()) {
|
|
105
|
+
const sub = findRecent(p)
|
|
106
|
+
if (sub && (!latest || sub > latest)) latest = sub
|
|
107
|
+
} else if (!latest || s.mtimeMs > latest) {
|
|
108
|
+
latest = s.mtimeMs
|
|
109
|
+
}
|
|
110
|
+
} catch {}
|
|
111
|
+
}
|
|
112
|
+
return latest
|
|
113
|
+
}
|
|
114
|
+
const ts = findRecent(sillyspecDir)
|
|
115
|
+
if (ts) result.lastActive = new Date(ts).toISOString()
|
|
116
|
+
} catch {}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// --- Doc stats ---
|
|
120
|
+
const docsDir = join(sillyspecDir, 'docs')
|
|
121
|
+
if (existsSync(docsDir)) {
|
|
122
|
+
const typeMap = { brainstorm: 'design', plan: 'plan', archive: 'archive', changes: 'changes', scan: 'scan', quicklog: 'quicklog' }
|
|
123
|
+
try {
|
|
124
|
+
for (const projDir of readdirSync(docsDir, { withFileTypes: true }).filter(d => d.isDirectory())) {
|
|
125
|
+
for (const typeDir of readdirSync(join(docsDir, projDir.name), { withFileTypes: true }).filter(d => d.isDirectory())) {
|
|
126
|
+
const key = typeMap[typeDir.name]
|
|
127
|
+
if (!key) continue
|
|
128
|
+
const count = countMdFiles(join(docsDir, projDir.name, typeDir.name))
|
|
129
|
+
result.docStats[key] += count
|
|
130
|
+
result.docStats.total += count
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch {}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// --- Git info ---
|
|
137
|
+
try {
|
|
138
|
+
result.git.lastCommit = execSync('git log -1 --format=%s', { cwd: projectPath, encoding: 'utf-8' }).trim()
|
|
139
|
+
} catch {}
|
|
140
|
+
try {
|
|
141
|
+
result.git.branch = execSync('git branch --show-current', { cwd: projectPath, encoding: 'utf-8' }).trim()
|
|
142
|
+
} catch {}
|
|
143
|
+
try {
|
|
144
|
+
result.git.dirtyCount = parseInt(execSync('git status --porcelain', { cwd: projectPath, encoding: 'utf-8' }).trim().split('\n').filter(Boolean).length, 10) || 0
|
|
145
|
+
} catch {}
|
|
146
|
+
|
|
147
|
+
return result
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function parseGitDetail(projectPath) {
|
|
151
|
+
const result = { branch: '', commits: [], untracked: [] }
|
|
152
|
+
try {
|
|
153
|
+
result.branch = execSync('git branch --show-current', { cwd: projectPath, encoding: 'utf-8' }).trim()
|
|
154
|
+
} catch {}
|
|
155
|
+
try {
|
|
156
|
+
const log = execSync('git log -5 --format=%h|%s|%an|%aI', { cwd: projectPath, encoding: 'utf-8' }).trim()
|
|
157
|
+
result.commits = log.split('\n').filter(Boolean).map(line => {
|
|
158
|
+
const [hash, message, author, date] = line.split('|')
|
|
159
|
+
return { hash, message, author, date }
|
|
160
|
+
})
|
|
161
|
+
} catch {}
|
|
162
|
+
try {
|
|
163
|
+
const status = execSync('git status --porcelain', { cwd: projectPath, encoding: 'utf-8' }).trim()
|
|
164
|
+
result.untracked = status.split('\n').filter(Boolean).map(line => ({
|
|
165
|
+
status: line.slice(0, 2).trim(),
|
|
166
|
+
file: line.slice(3)
|
|
167
|
+
}))
|
|
168
|
+
} catch {}
|
|
169
|
+
return result
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function parseTechStackDetail(projectPath) {
|
|
173
|
+
const result = { frameworks: [], dependencies: {}, devDependencies: {} }
|
|
174
|
+
const pkgPath = join(projectPath, 'package.json')
|
|
175
|
+
if (existsSync(pkgPath)) {
|
|
176
|
+
try {
|
|
177
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
|
178
|
+
result.dependencies = pkg.dependencies || {}
|
|
179
|
+
result.devDependencies = pkg.devDependencies || {}
|
|
180
|
+
const allDeps = Object.keys({ ...result.dependencies, ...result.devDependencies })
|
|
181
|
+
for (const pattern of FRAMEWORK_PATTERNS) {
|
|
182
|
+
if (pattern.keys.some(k => allDeps.includes(k))) {
|
|
183
|
+
result.frameworks.push(pattern.name)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} catch {}
|
|
187
|
+
}
|
|
188
|
+
return result
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function parseDocsList(projectPath) {
|
|
192
|
+
const sillyspecDir = join(projectPath, '.sillyspec')
|
|
193
|
+
const docsDir = join(sillyspecDir, 'docs')
|
|
194
|
+
const groups = []
|
|
195
|
+
if (!existsSync(docsDir)) return groups
|
|
196
|
+
const typeMap = {
|
|
197
|
+
brainstorm: { label: '设计文档', icon: '📋' },
|
|
198
|
+
plan: { label: '实现计划', icon: '📐' },
|
|
199
|
+
archive: { label: '已归档', icon: '📦' },
|
|
200
|
+
changes: { label: '当前变更', icon: '⚙️' },
|
|
201
|
+
scan: { label: '架构文档', icon: '🔍' },
|
|
202
|
+
quicklog: { label: '快速修复', icon: '⚡' }
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
for (const projDir of readdirSync(docsDir, { withFileTypes: true }).filter(d => d.isDirectory())) {
|
|
206
|
+
for (const typeDir of readdirSync(join(docsDir, projDir.name), { withFileTypes: true }).filter(d => d.isDirectory())) {
|
|
207
|
+
const cfg = typeMap[typeDir.name]
|
|
208
|
+
if (!cfg) continue
|
|
209
|
+
const files = listFilesRecursive(join(docsDir, projDir.name, typeDir.name))
|
|
210
|
+
if (files.length) {
|
|
211
|
+
groups.push({ key: typeDir.name, label: cfg.label, icon: cfg.icon, files })
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} catch {}
|
|
216
|
+
return groups
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function listFilesRecursive(dir) {
|
|
220
|
+
const files = []
|
|
221
|
+
try {
|
|
222
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
223
|
+
const p = join(dir, entry.name)
|
|
224
|
+
if (entry.isDirectory()) {
|
|
225
|
+
const sub = listFilesRecursive(p)
|
|
226
|
+
for (const f of sub) f.name = entry.name + '/' + f.name
|
|
227
|
+
files.push(...sub)
|
|
228
|
+
} else if (entry.name.endsWith('.md')) {
|
|
229
|
+
const s = statSync(p)
|
|
230
|
+
files.push({ name: entry.name, path: p, size: s.size, mtime: s.mtime.toISOString() })
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
} catch {}
|
|
234
|
+
return files
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function countMdFiles(dir) {
|
|
238
|
+
let count = 0
|
|
239
|
+
try {
|
|
240
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
241
|
+
if (entry.isDirectory()) {
|
|
242
|
+
count += countMdFiles(join(dir, entry.name))
|
|
243
|
+
} else if (entry.name.endsWith('.md')) {
|
|
244
|
+
count++
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} catch {}
|
|
248
|
+
return count
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function parseDocsTree(projectPath) {
|
|
252
|
+
const sillyspecDir = join(projectPath, '.sillyspec')
|
|
253
|
+
const docsDir = join(sillyspecDir, 'docs')
|
|
254
|
+
|
|
255
|
+
if (!existsSync(docsDir)) {
|
|
256
|
+
return { groups: [] }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const groupConfig = [
|
|
260
|
+
{ key: 'brainstorm', label: '📋 设计文档', icon: '📋', dir: 'brainstorm' },
|
|
261
|
+
{ key: 'plan', label: '📐 实现计划', icon: '📐', dir: 'plan' },
|
|
262
|
+
{ key: 'changes', label: '⚙️ 当前变更', icon: '⚙️', dir: 'changes' },
|
|
263
|
+
{ key: 'archive', label: '📦 已归档', icon: '📦', dir: 'archive' },
|
|
264
|
+
{ key: 'scan', label: '🔍 架构文档', icon: '🔍', dir: 'scan' },
|
|
265
|
+
{ key: 'quicklog', label: '⚡ 快速修复', icon: '⚡', dir: 'quicklog' },
|
|
266
|
+
]
|
|
267
|
+
|
|
268
|
+
const groups = []
|
|
269
|
+
|
|
270
|
+
// Find project dirs under docs/
|
|
271
|
+
const projectDirs = existsSync(docsDir) ? readdirSync(docsDir, { withFileTypes: true })
|
|
272
|
+
.filter(d => d.isDirectory()).map(d => d.name) : []
|
|
273
|
+
|
|
274
|
+
for (const projName of projectDirs) {
|
|
275
|
+
const projDocsDir = join(docsDir, projName)
|
|
276
|
+
|
|
277
|
+
for (const group of groupConfig) {
|
|
278
|
+
const groupDir = join(projDocsDir, group.dir)
|
|
279
|
+
if (!existsSync(groupDir)) continue
|
|
280
|
+
|
|
281
|
+
const files = []
|
|
282
|
+
try {
|
|
283
|
+
const entries = readdirSync(groupDir, { withFileTypes: true })
|
|
284
|
+
for (const entry of entries) {
|
|
285
|
+
if (entry.isDirectory()) {
|
|
286
|
+
// For changes/ and archive/ subdirs
|
|
287
|
+
const subDir = join(groupDir, entry.name)
|
|
288
|
+
try {
|
|
289
|
+
const subFiles = readdirSync(subDir).filter(f => f.endsWith('.md'))
|
|
290
|
+
for (const sf of subFiles) {
|
|
291
|
+
const filePath = join(subDir, sf)
|
|
292
|
+
files.push({
|
|
293
|
+
name: `${entry.name}/${sf}`,
|
|
294
|
+
path: filePath,
|
|
295
|
+
title: sf.replace('.md', '')
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
} catch {}
|
|
299
|
+
} else if (entry.name.endsWith('.md')) {
|
|
300
|
+
const filePath = join(groupDir, entry.name)
|
|
301
|
+
let title = entry.name.replace('.md', '')
|
|
302
|
+
try {
|
|
303
|
+
const content = readFileSync(filePath, 'utf-8')
|
|
304
|
+
const titleMatch = content.match(/^#\s+(.+)$/m)
|
|
305
|
+
if (titleMatch) title = titleMatch[1]
|
|
306
|
+
} catch {}
|
|
307
|
+
files.push({ name: entry.name, path: filePath, title })
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
} catch {}
|
|
311
|
+
|
|
312
|
+
if (files.length > 0) {
|
|
313
|
+
groups.push({
|
|
314
|
+
key: `${projName}::${group.key}`,
|
|
315
|
+
label: group.label,
|
|
316
|
+
project: projName,
|
|
317
|
+
files
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return { groups }
|
|
324
|
+
}
|
|
325
|
+
|
|
8
326
|
/**
|
|
9
327
|
* Parse project state from .sillyspec directory
|
|
10
328
|
* @param {string} projectPath - Path to the project directory
|
|
11
329
|
* @returns {object} Project state with currentStage, nextStep, progress, stages, specs, lastActive
|
|
12
330
|
*/
|
|
13
|
-
export
|
|
331
|
+
export function parseProjectState(projectPath) {
|
|
14
332
|
const sillyspecDir = join(projectPath, '.sillyspec')
|
|
15
333
|
|
|
16
334
|
if (!existsSync(sillyspecDir)) {
|
|
17
335
|
return null
|
|
18
336
|
}
|
|
19
337
|
|
|
20
|
-
let currentStage = '
|
|
338
|
+
let currentStage = ''
|
|
21
339
|
let nextStep = null
|
|
22
340
|
let progress = { stages: {} }
|
|
23
341
|
let stages = []
|
|
24
342
|
let specs = []
|
|
25
343
|
let lastActive = null
|
|
26
344
|
|
|
27
|
-
// Read
|
|
28
|
-
const statePath = join(sillyspecDir, 'STATE.md')
|
|
29
|
-
if (existsSync(statePath)) {
|
|
30
|
-
try {
|
|
31
|
-
const stateContent = readFileSync(statePath, 'utf-8')
|
|
32
|
-
const stageMatch = stateContent.match(/current_stage:\s*(\w+)/i)
|
|
33
|
-
const stepMatch = stateContent.match(/next_step:\s*(.+)/i)
|
|
34
|
-
|
|
35
|
-
if (stageMatch) currentStage = stageMatch[1]
|
|
36
|
-
if (stepMatch) nextStep = stepMatch[1].trim()
|
|
37
|
-
} catch (err) {
|
|
38
|
-
// State file exists but couldn't be read
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Read progress.json from .runtime directory
|
|
345
|
+
// Read progress.json for current stage
|
|
43
346
|
const progressPath = join(sillyspecDir, '.runtime', 'progress.json')
|
|
44
347
|
if (existsSync(progressPath)) {
|
|
45
348
|
try {
|
|
46
|
-
const
|
|
47
|
-
progress =
|
|
48
|
-
|
|
349
|
+
const progressData = JSON.parse(readFileSync(progressPath, 'utf-8'))
|
|
350
|
+
progress = progressData
|
|
351
|
+
currentStage = progressData.currentStage || ''
|
|
352
|
+
stages = Object.keys(progressData.stages || {})
|
|
49
353
|
|
|
50
|
-
// Find last active
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
354
|
+
// Find last active
|
|
355
|
+
if (progressData.lastActive) lastActive = progressData.lastActive
|
|
356
|
+
if (progressData.stages) {
|
|
357
|
+
for (const [stageName, stageData] of Object.entries(progressData.stages)) {
|
|
358
|
+
if (stageData.lastActive || stageData.startedAt) {
|
|
359
|
+
const t = stageData.lastActive || stageData.startedAt
|
|
360
|
+
if (!lastActive || new Date(t) > new Date(lastActive)) lastActive = t
|
|
57
361
|
}
|
|
58
362
|
}
|
|
59
363
|
}
|