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.
- 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 -258
- 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,363 +1,59 @@
|
|
|
1
|
-
import { readFileSync, existsSync, readdirSync
|
|
2
|
-
import { execSync } from 'child_process'
|
|
1
|
+
import { readFileSync, existsSync, readdirSync } from 'fs'
|
|
3
2
|
import { join } from 'path'
|
|
4
3
|
import { fileURLToPath } from 'url'
|
|
5
4
|
import { dirname } from 'path'
|
|
6
5
|
|
|
7
6
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
8
7
|
|
|
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
|
-
|
|
326
8
|
/**
|
|
327
9
|
* Parse project state from .sillyspec directory
|
|
328
10
|
* @param {string} projectPath - Path to the project directory
|
|
329
11
|
* @returns {object} Project state with currentStage, nextStep, progress, stages, specs, lastActive
|
|
330
12
|
*/
|
|
331
|
-
export function parseProjectState(projectPath) {
|
|
13
|
+
export async function parseProjectState(projectPath) {
|
|
332
14
|
const sillyspecDir = join(projectPath, '.sillyspec')
|
|
333
15
|
|
|
334
16
|
if (!existsSync(sillyspecDir)) {
|
|
335
17
|
return null
|
|
336
18
|
}
|
|
337
19
|
|
|
338
|
-
let currentStage = ''
|
|
20
|
+
let currentStage = 'unknown'
|
|
339
21
|
let nextStep = null
|
|
340
22
|
let progress = { stages: {} }
|
|
341
23
|
let stages = []
|
|
342
24
|
let specs = []
|
|
343
25
|
let lastActive = null
|
|
344
26
|
|
|
345
|
-
// Read
|
|
27
|
+
// Read STATE.md for current stage and next step
|
|
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
|
|
346
43
|
const progressPath = join(sillyspecDir, '.runtime', 'progress.json')
|
|
347
44
|
if (existsSync(progressPath)) {
|
|
348
45
|
try {
|
|
349
|
-
const
|
|
350
|
-
progress =
|
|
351
|
-
|
|
352
|
-
stages = Object.keys(progressData.stages || {})
|
|
46
|
+
const progressContent = readFileSync(progressPath, 'utf-8')
|
|
47
|
+
progress = JSON.parse(progressContent)
|
|
48
|
+
stages = Object.keys(progress.stages || {})
|
|
353
49
|
|
|
354
|
-
// Find last active
|
|
355
|
-
if (
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
50
|
+
// Find last active stage
|
|
51
|
+
if (progress.stages) {
|
|
52
|
+
for (const [stageName, stageData] of Object.entries(progress.stages)) {
|
|
53
|
+
if (stageData.lastActive) {
|
|
54
|
+
if (!lastActive || new Date(stageData.lastActive) > new Date(lastActive)) {
|
|
55
|
+
lastActive = stageData.lastActive
|
|
56
|
+
}
|
|
361
57
|
}
|
|
362
58
|
}
|
|
363
59
|
}
|