sillyspec 3.7.11 → 3.7.13
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/.sillyspec/docs/sillyspec/brainstorm/2026-04-05-dashboard-design.md +206 -0
- package/.sillyspec/docs/sillyspec/brainstorm/2026-04-05-unified-docs-design.md +199 -0
- package/.sillyspec/specs/2026-04-05-unified-docs-design.md +199 -0
- package/package.json +1 -1
- package/packages/dashboard/dist/assets/index-5Rrvs0Rl.css +1 -0
- package/packages/dashboard/dist/assets/index-ZNToqi9V.js +17 -0
- package/packages/dashboard/dist/index.html +2 -2
- package/packages/dashboard/server/index.js +55 -1
- package/packages/dashboard/server/parser.js +83 -3
- package/packages/dashboard/src/App.vue +31 -1
- package/packages/dashboard/src/components/DocPreview.vue +43 -0
- package/packages/dashboard/src/components/DocTree.vue +36 -0
- package/packages/dashboard/src/components/PipelineView.vue +61 -26
- package/packages/dashboard/src/composables/useDashboard.js +14 -2
- package/src/index.js +12 -1
- package/src/init.js +27 -11
- package/src/migrate.js +134 -0
- package/templates/archive.md +5 -5
- package/templates/brainstorm.md +15 -9
- package/templates/execute.md +6 -6
- package/templates/plan.md +3 -3
- package/templates/propose.md +3 -3
- package/templates/quick.md +8 -8
- package/templates/scan.md +14 -8
- package/packages/dashboard/dist/assets/index-DHbVrCnq.css +0 -1
- package/packages/dashboard/dist/assets/index-qx7yiwun.js +0 -17
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="h-full overflow-y-auto px-6 py-4">
|
|
3
|
+
<div v-if="!content && !loading" class="flex items-center justify-center h-full">
|
|
4
|
+
<p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #525252;">选择一个文档查看内容</p>
|
|
5
|
+
</div>
|
|
6
|
+
<div v-else-if="loading" class="flex items-center justify-center h-full">
|
|
7
|
+
<p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #525252;">加载中...</p>
|
|
8
|
+
</div>
|
|
9
|
+
<div v-else class="doc-preview" v-html="renderedContent"></div>
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup>
|
|
14
|
+
import { computed } from 'vue'
|
|
15
|
+
|
|
16
|
+
const props = defineProps({
|
|
17
|
+
content: { type: String, default: '' },
|
|
18
|
+
loading: { type: Boolean, default: false }
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const renderedContent = computed(() => {
|
|
22
|
+
if (!props.content) return ''
|
|
23
|
+
return simpleMarkdown(props.content)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
function simpleMarkdown(md) {
|
|
27
|
+
let html = md
|
|
28
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
29
|
+
.replace(/^### (.+)$/gm, '<h3 style="color:#FBBF24;font-size:14px;font-weight:600;margin:16px 0 8px;">$1</h3>')
|
|
30
|
+
.replace(/^## (.+)$/gm, '<h2 style="color:#E5E5E5;font-size:15px;font-weight:600;margin:20px 0 10px;">$1</h2>')
|
|
31
|
+
.replace(/^# (.+)$/gm, '<h1 style="color:#FBBF24;font-size:18px;font-weight:700;margin:0 0 16px;">$1</h1>')
|
|
32
|
+
.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre style="background:#0A0A0B;border:1px solid #1F1F22;border-radius:6px;padding:12px;overflow-x:auto;font-size:12px;margin:8px 0;"><code>$2</code></pre>')
|
|
33
|
+
.replace(/`([^`]+)`/g, '<code style="background:#0A0A0B;padding:1px 4px;border-radius:3px;font-size:11px;">$1</code>')
|
|
34
|
+
.replace(/\*\*([^*]+)\*\*/g, '<strong style="color:#E5E5E5;">$1</strong>')
|
|
35
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" style="color:#FBBF24;">$1</a>')
|
|
36
|
+
.replace(/^- \[x\] (.+)$/gm, '<div style="color:#34D399;">☑ $1</div>')
|
|
37
|
+
.replace(/^- \[ \] (.+)$/gm, '<div style="color:#8B8B8E;">☐ $1</div>')
|
|
38
|
+
.replace(/^- (.+)$/gm, '<div style="padding-left:12px;color:#8B8B8E;">• $1</div>')
|
|
39
|
+
.replace(/\n\n/g, '<br/><br/>')
|
|
40
|
+
.replace(/\n/g, '<br/>')
|
|
41
|
+
return html
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col h-full">
|
|
3
|
+
<div v-if="groups.length === 0" class="flex items-center justify-center h-full">
|
|
4
|
+
<p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #525252;">暂无文档</p>
|
|
5
|
+
</div>
|
|
6
|
+
<div v-else class="overflow-y-auto px-4 py-3 space-y-4">
|
|
7
|
+
<div v-for="group in groups" :key="group.key">
|
|
8
|
+
<div class="text-[10px] font-semibold uppercase tracking-[0.15em] font-[JetBrains_Mono,monospace] mb-2" style="color: #525252;">
|
|
9
|
+
{{ group.label }}
|
|
10
|
+
</div>
|
|
11
|
+
<div class="space-y-0.5">
|
|
12
|
+
<button
|
|
13
|
+
v-for="file in group.files"
|
|
14
|
+
:key="file.path"
|
|
15
|
+
@click="$emit('select-file', file)"
|
|
16
|
+
class="w-full text-left px-2.5 py-1.5 rounded text-[12px] font-[JetBrains_Mono,monospace] transition-colors"
|
|
17
|
+
:style="{
|
|
18
|
+
color: selectedFile?.path === file.path ? '#FBBF24' : '#8B8B8E',
|
|
19
|
+
background: selectedFile?.path === file.path ? 'rgba(251,191,36,0.08)' : 'transparent'
|
|
20
|
+
}"
|
|
21
|
+
>
|
|
22
|
+
📄 {{ file.title }}
|
|
23
|
+
</button>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup>
|
|
31
|
+
defineProps({
|
|
32
|
+
groups: { type: Array, default: () => [] },
|
|
33
|
+
selectedFile: { type: Object, default: null }
|
|
34
|
+
})
|
|
35
|
+
defineEmits(['select-file'])
|
|
36
|
+
</script>
|
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="flex flex-col h-full" style="background: #0E0E10;">
|
|
3
|
-
<!-- Header -->
|
|
4
|
-
<div class="px-6 pt-
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
<!-- Header with Tabs -->
|
|
4
|
+
<div class="px-6 pt-4 pb-0" style="border-bottom: 1px solid #1F1F22;">
|
|
5
|
+
<div class="flex items-center gap-6">
|
|
6
|
+
<h2 class="text-[11px] font-semibold uppercase tracking-[0.2em] font-[JetBrains_Mono,monospace]" style="color: #525252;">
|
|
7
|
+
{{ project?.name || '项目' }}
|
|
8
|
+
</h2>
|
|
9
|
+
<div class="flex gap-1">
|
|
10
|
+
<button
|
|
11
|
+
@click="$emit('switch-tab', 'pipeline')"
|
|
12
|
+
class="px-3 py-1.5 text-[11px] font-[JetBrains_Mono,monospace] transition-colors rounded"
|
|
13
|
+
:style="{ color: activeTab === 'pipeline' ? '#FBBF24' : '#525252', background: activeTab === 'pipeline' ? 'rgba(251,191,36,0.08)' : 'transparent' }"
|
|
14
|
+
>流水线</button>
|
|
15
|
+
<button
|
|
16
|
+
@click="$emit('switch-tab', 'docs')"
|
|
17
|
+
class="px-3 py-1.5 text-[11px] font-[JetBrains_Mono,monospace] transition-colors rounded"
|
|
18
|
+
:style="{ color: activeTab === 'docs' ? '#FBBF24' : '#525252', background: activeTab === 'docs' ? 'rgba(251,191,36,0.08)' : 'transparent' }"
|
|
19
|
+
>文档</button>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
11
22
|
</div>
|
|
12
23
|
|
|
13
|
-
<!--
|
|
14
|
-
<div class="flex
|
|
24
|
+
<!-- Pipeline Tab -->
|
|
25
|
+
<div v-if="activeTab === 'pipeline'" class="flex flex-col flex-1 overflow-hidden">
|
|
26
|
+
<div class="px-6 pt-4 pb-2">
|
|
27
|
+
<p v-if="project" class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #8B8B8E;">
|
|
28
|
+
<span style="color: #FBBF24;">{{ currentStage }}</span>
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
15
32
|
<!-- Empty state -->
|
|
16
|
-
<div v-if="!project || !project.state" class="flex items-center justify-center
|
|
33
|
+
<div v-if="!project || !project.state" class="flex items-center justify-center flex-1">
|
|
17
34
|
<div class="text-center">
|
|
18
35
|
<div class="w-14 h-14 mx-auto mb-4 rounded-md flex items-center justify-center" style="border: 1px dashed #2A2A2D; transform: rotate(45deg);">
|
|
19
36
|
<svg class="w-5 h-5" style="color: #525252; transform: rotate(-45deg);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -24,7 +41,8 @@
|
|
|
24
41
|
</div>
|
|
25
42
|
</div>
|
|
26
43
|
|
|
27
|
-
|
|
44
|
+
<!-- Stages -->
|
|
45
|
+
<div v-else class="flex-1 overflow-y-auto px-6 pb-5 space-y-5">
|
|
28
46
|
<PipelineStage name="brainstorm" title="头脑风暴" :steps="getStageSteps('brainstorm')" :status="getStageStatus('brainstorm')" :is-active="currentStage === 'brainstorm'" :active-step="activeStep" @select-step="handleSelectStep" />
|
|
29
47
|
<div v-if="hasStage('plan')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #1F1F22;" /></div>
|
|
30
48
|
<PipelineStage name="plan" title="规划" :steps="getStageSteps('plan')" :status="getStageStatus('plan')" :is-active="currentStage === 'plan'" :active-step="activeStep" @select-step="handleSelectStep" />
|
|
@@ -33,21 +51,31 @@
|
|
|
33
51
|
<div v-if="hasStage('verify')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #1F1F22;" /></div>
|
|
34
52
|
<PipelineStage name="verify" title="验证" :steps="getStageSteps('verify')" :status="getStageStatus('verify')" :is-active="currentStage === 'verify'" :active-step="activeStep" @select-step="handleSelectStep" />
|
|
35
53
|
</div>
|
|
54
|
+
|
|
55
|
+
<!-- Activity Log -->
|
|
56
|
+
<div v-if="project?.state?.progress" style="border-top: 1px solid #1F1F22; background: rgba(10,10,11,0.6);">
|
|
57
|
+
<div class="px-6 py-2.5 flex items-center justify-between">
|
|
58
|
+
<div class="text-[9px] font-semibold uppercase tracking-[0.25em] font-[JetBrains_Mono,monospace]" style="color: #525252;">活动日志</div>
|
|
59
|
+
<div class="text-[10px] font-mono-log" style="color: #3A3A3D;">{{ activityLogs.length }}</div>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="px-6 pb-3 space-y-0.5 max-h-32 overflow-y-auto">
|
|
62
|
+
<div v-for="(log, i) in activityLogs" :key="i" class="flex items-start gap-2.5 text-[11px] py-0.5">
|
|
63
|
+
<span class="w-10 font-mono-log flex-shrink-0" style="color: #3A3A3D;">{{ log.time }}</span>
|
|
64
|
+
<span :style="{ color: log.status === 'completed' ? '#34D399' : '#FBBF24' }">›</span>
|
|
65
|
+
<span style="color: #8B8B8E;">{{ log.description }}</span>
|
|
66
|
+
</div>
|
|
67
|
+
<div v-if="activityLogs.length === 0" class="text-[10px] py-1" style="color: #3A3A3D;">暂无活动记录</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
36
70
|
</div>
|
|
37
71
|
|
|
38
|
-
<!--
|
|
39
|
-
<div v-if="
|
|
40
|
-
<div class="
|
|
41
|
-
<
|
|
42
|
-
<div class="text-[10px] font-mono-log" style="color: #3A3A3D;">{{ activityLogs.length }}</div>
|
|
72
|
+
<!-- Docs Tab -->
|
|
73
|
+
<div v-if="activeTab === 'docs'" class="flex-1 flex overflow-hidden">
|
|
74
|
+
<div class="w-[200px] flex-shrink-0 overflow-hidden" style="border-right: 1px solid #1F1F22;">
|
|
75
|
+
<DocTree :groups="docs.groups" :selected-file="selectedDocFile" @select-file="$emit('select-doc-file', $event)" />
|
|
43
76
|
</div>
|
|
44
|
-
<div class="
|
|
45
|
-
<
|
|
46
|
-
<span class="w-10 font-mono-log flex-shrink-0" style="color: #3A3A3D;">{{ log.time }}</span>
|
|
47
|
-
<span :style="{ color: log.status === 'completed' ? '#34D399' : '#FBBF24' }">›</span>
|
|
48
|
-
<span style="color: #8B8B8E;">{{ log.description }}</span>
|
|
49
|
-
</div>
|
|
50
|
-
<div v-if="activityLogs.length === 0" class="text-[10px] py-1" style="color: #3A3A3D;">暂无活动记录</div>
|
|
77
|
+
<div class="flex-1 overflow-hidden">
|
|
78
|
+
<DocPreview :content="docContent" :loading="docLoading" />
|
|
51
79
|
</div>
|
|
52
80
|
</div>
|
|
53
81
|
</div>
|
|
@@ -56,13 +84,20 @@
|
|
|
56
84
|
<script setup>
|
|
57
85
|
import { computed } from 'vue'
|
|
58
86
|
import PipelineStage from './PipelineStage.vue'
|
|
87
|
+
import DocTree from './DocTree.vue'
|
|
88
|
+
import DocPreview from './DocPreview.vue'
|
|
59
89
|
|
|
60
90
|
const props = defineProps({
|
|
61
91
|
project: { type: Object, default: null },
|
|
62
|
-
activeStep: { type: Object, default: null }
|
|
92
|
+
activeStep: { type: Object, default: null },
|
|
93
|
+
activeTab: { type: String, default: 'pipeline' },
|
|
94
|
+
docs: { type: Object, default: () => ({ groups: [] }) },
|
|
95
|
+
selectedDocFile: { type: Object, default: null },
|
|
96
|
+
docContent: { type: String, default: '' },
|
|
97
|
+
docLoading: { type: Boolean, default: false }
|
|
63
98
|
})
|
|
64
99
|
|
|
65
|
-
const emit = defineEmits(['select-step'])
|
|
100
|
+
const emit = defineEmits(['select-step', 'switch-tab', 'select-doc-file'])
|
|
66
101
|
|
|
67
102
|
const currentStage = computed(() => props.project?.state?.currentStage || 'unknown')
|
|
68
103
|
const progress = computed(() => props.project?.state?.progress || {})
|
|
@@ -13,7 +13,12 @@ export function useDashboard() {
|
|
|
13
13
|
logs: [],
|
|
14
14
|
isPanelOpen: true,
|
|
15
15
|
executingProject: null,
|
|
16
|
-
isLoading: true
|
|
16
|
+
isLoading: true,
|
|
17
|
+
activeTab: 'pipeline',
|
|
18
|
+
docs: { groups: [] },
|
|
19
|
+
selectedDocFile: null,
|
|
20
|
+
docContent: '',
|
|
21
|
+
docLoading: false
|
|
17
22
|
})
|
|
18
23
|
|
|
19
24
|
/**
|
|
@@ -38,6 +43,8 @@ export function useDashboard() {
|
|
|
38
43
|
state.activeProject = proj
|
|
39
44
|
state.activeStep = null
|
|
40
45
|
state.logs = []
|
|
46
|
+
state.selectedDocFile = null
|
|
47
|
+
state.docContent = ''
|
|
41
48
|
|
|
42
49
|
// Load initial logs from project state if available
|
|
43
50
|
if (proj.state?.progress?.currentLogs) {
|
|
@@ -168,6 +175,11 @@ export function useDashboard() {
|
|
|
168
175
|
activeProjectPath,
|
|
169
176
|
activeProjectStage,
|
|
170
177
|
hasProjects,
|
|
171
|
-
activeProjectSteps
|
|
178
|
+
activeProjectSteps,
|
|
179
|
+
setActiveTab(tab) { state.activeTab = tab },
|
|
180
|
+
updateDocs(docs) { state.docs = docs },
|
|
181
|
+
selectDocFile(file) { state.selectedDocFile = file },
|
|
182
|
+
setDocContent(content) { state.docContent = content },
|
|
183
|
+
setDocLoading(loading) { state.docLoading = loading }
|
|
172
184
|
}
|
|
173
185
|
}
|
package/src/index.js
CHANGED
|
@@ -31,7 +31,8 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
31
31
|
validate 校验并修复进度文件
|
|
32
32
|
reset [--stage X] 重置进度(全部或指定阶段)
|
|
33
33
|
complete --stage X 归档已完成阶段
|
|
34
|
-
sillyspec
|
|
34
|
+
sillyspec docs migrate 迁移旧文档到统一结构
|
|
35
|
+
sillyspec dashboard 启动 Dashboard Web UI
|
|
35
36
|
[--port <number>] 指定端口(默认 3456)
|
|
36
37
|
[--no-open] 不自动打开浏览器
|
|
37
38
|
|
|
@@ -134,6 +135,16 @@ async function main() {
|
|
|
134
135
|
}
|
|
135
136
|
break;
|
|
136
137
|
}
|
|
138
|
+
case 'docs': {
|
|
139
|
+
const docsSubCmd = filteredArgs[1];
|
|
140
|
+
if (docsSubCmd === 'migrate') {
|
|
141
|
+
const { migrateDocs } = await import('./migrate.js');
|
|
142
|
+
migrateDocs(dir);
|
|
143
|
+
} else {
|
|
144
|
+
console.log('用法: sillyspec docs migrate');
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
137
148
|
case 'dashboard': {
|
|
138
149
|
// Parse dashboard options
|
|
139
150
|
let port = 3456;
|
package/src/init.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, copyFileSync } from 'fs';
|
|
2
|
-
import { join, resolve, dirname } from 'path';
|
|
2
|
+
import { join, resolve, dirname, basename } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
import { checkbox, select, confirm, input } from '@inquirer/prompts';
|
|
@@ -81,13 +81,13 @@ const INJECTION_CONTENT = `## SillySpec — 规范驱动开发
|
|
|
81
81
|
在执行开发任务时,遵循以下规范:
|
|
82
82
|
|
|
83
83
|
### 代码规范
|
|
84
|
-
- 写代码前先读取 \`.sillyspec/
|
|
84
|
+
- 写代码前先读取 \`.sillyspec/docs/<project>/scan/CONVENTIONS.md\`(代码风格)和 \`.sillyspec/docs/<project>/scan/ARCHITECTURE.md\`(架构)
|
|
85
85
|
- 调用已有方法前,用 grep 确认方法存在,不许编造
|
|
86
|
-
- 遵循 \`.sillyspec/
|
|
86
|
+
- 遵循 \`.sillyspec/docs/<project>/scan/CONVENTIONS.md\` 中的代码风格
|
|
87
87
|
|
|
88
88
|
### 工作流程
|
|
89
89
|
- 读取 \`.sillyspec/STATE.md\` 确认当前阶段
|
|
90
|
-
- 各阶段产出文件位于 \`.sillyspec/changes/<变更名>/\` 下
|
|
90
|
+
- 各阶段产出文件位于 \`.sillyspec/docs/<project>/changes/<变更名>/\` 下
|
|
91
91
|
- 详细流程参考模板文件:\`.sillyspec/.templates/\`(brainstorm.md, plan.md, execute.md 等)
|
|
92
92
|
`;
|
|
93
93
|
|
|
@@ -203,14 +203,30 @@ function isTTY() {
|
|
|
203
203
|
|
|
204
204
|
async function doInstall(projectDir, tools, isWorkspace, subprojects = []) {
|
|
205
205
|
// 创建基础目录
|
|
206
|
-
// .sillyspec/
|
|
207
|
-
// .sillyspec/
|
|
208
|
-
// .sillyspec/
|
|
209
|
-
// .sillyspec/changes/archive/ → archive
|
|
210
|
-
// .sillyspec/quicklog/ → quick
|
|
211
|
-
// .sillyspec/knowledge/ → archive (spec 沉淀)
|
|
206
|
+
// .sillyspec/projects/ → 项目注册表
|
|
207
|
+
// .sillyspec/docs/<name>/ → 统一文档中心
|
|
208
|
+
// .sillyspec/knowledge/ → 跨项目共享知识库
|
|
212
209
|
// .sillyspec/.runtime/ → progress (gitignored)
|
|
213
|
-
|
|
210
|
+
|
|
211
|
+
// 注册当前项目到 projects/
|
|
212
|
+
const projectName = basename(projectDir) || 'project';
|
|
213
|
+
const projectsDir = join(projectDir, '.sillyspec', 'projects');
|
|
214
|
+
mkdirSync(projectsDir, { recursive: true });
|
|
215
|
+
const projectYamlPath = join(projectsDir, `${projectName}.yaml`);
|
|
216
|
+
if (!existsSync(projectYamlPath)) {
|
|
217
|
+
writeFileSync(projectYamlPath, `name: ${projectName}\npath: .\nstatus: active\n`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 创建 docs/<projectName>/ 子目录结构
|
|
221
|
+
const docsBase = join(projectDir, '.sillyspec', 'docs', projectName);
|
|
222
|
+
for (const sub of ['scan', 'brainstorm', 'plan', 'changes', 'archive', 'quicklog']) {
|
|
223
|
+
const subDir = join(docsBase, sub);
|
|
224
|
+
mkdirSync(subDir, { recursive: true });
|
|
225
|
+
const gitkeepPath = join(subDir, '.gitkeep');
|
|
226
|
+
if (!existsSync(gitkeepPath)) writeFileSync(gitkeepPath, '');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 兼容:保留旧的 codebase/ changes/ quicklog/ 目录(如果已存在不删除)
|
|
214
230
|
if (isWorkspace) {
|
|
215
231
|
mkdirSync(join(projectDir, '.sillyspec', 'shared'), { recursive: true });
|
|
216
232
|
mkdirSync(join(projectDir, '.sillyspec', 'workspace'), { recursive: true });
|
package/src/migrate.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, renameSync, copyFileSync } from 'fs';
|
|
2
|
+
import { join, resolve } from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Migrate old .sillyspec/ structure to unified docs/<project>/ structure
|
|
7
|
+
* @param {string} projectDir - Path to the project directory
|
|
8
|
+
*/
|
|
9
|
+
export function migrateDocs(projectDir) {
|
|
10
|
+
const sillyspecDir = join(projectDir, '.sillyspec');
|
|
11
|
+
if (!existsSync(sillyspecDir)) {
|
|
12
|
+
console.error('❌ .sillyspec/ 目录不存在');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Determine project name from projects/*.yaml or directory name
|
|
17
|
+
let projectName = projectDir.split('/').pop();
|
|
18
|
+
const projectsDir = join(sillyspecDir, 'projects');
|
|
19
|
+
if (existsSync(projectsDir)) {
|
|
20
|
+
const yamlFiles = readdirSync(projectsDir).filter(f => f.endsWith('.yaml'));
|
|
21
|
+
if (yamlFiles.length > 0) {
|
|
22
|
+
projectName = yamlFiles[0].replace('.yaml', '');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(chalk.cyan(`📦 迁移项目: ${projectName}`));
|
|
27
|
+
console.log('');
|
|
28
|
+
|
|
29
|
+
const docsBase = join(sillyspecDir, 'docs', projectName);
|
|
30
|
+
let migrated = 0;
|
|
31
|
+
|
|
32
|
+
// 1. codebase/ → docs/<project>/scan/
|
|
33
|
+
const codebaseDir = join(sillyspecDir, 'codebase');
|
|
34
|
+
if (existsSync(codebaseDir)) {
|
|
35
|
+
const targetDir = join(docsBase, 'scan');
|
|
36
|
+
mkdirSync(targetDir, { recursive: true });
|
|
37
|
+
const files = readdirSync(codebaseDir).filter(f => f.endsWith('.md'));
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
const src = join(codebaseDir, file);
|
|
40
|
+
const dest = join(targetDir, file);
|
|
41
|
+
if (!existsSync(dest)) {
|
|
42
|
+
copyFileSync(src, dest);
|
|
43
|
+
console.log(chalk.green(' ✅') + ` scan/${file}`);
|
|
44
|
+
migrated++;
|
|
45
|
+
} else {
|
|
46
|
+
console.log(chalk.yellow(' ⏭️') + ` scan/${file} (已存在)`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 2. specs/ → docs/<project>/brainstorm/
|
|
52
|
+
const specsDir = join(sillyspecDir, 'specs');
|
|
53
|
+
if (existsSync(specsDir)) {
|
|
54
|
+
const targetDir = join(docsBase, 'brainstorm');
|
|
55
|
+
mkdirSync(targetDir, { recursive: true });
|
|
56
|
+
const files = readdirSync(specsDir).filter(f => f.endsWith('.md'));
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
const src = join(specsDir, file);
|
|
59
|
+
const dest = join(targetDir, file);
|
|
60
|
+
if (!existsSync(dest)) {
|
|
61
|
+
copyFileSync(src, dest);
|
|
62
|
+
console.log(chalk.green(' ✅') + ` brainstorm/${file}`);
|
|
63
|
+
migrated++;
|
|
64
|
+
} else {
|
|
65
|
+
console.log(chalk.yellow(' ⏭️') + ` brainstorm/${file} (已存在)`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 3. changes/archive/ → docs/<project>/archive/
|
|
71
|
+
const archiveDir = join(sillyspecDir, 'changes', 'archive');
|
|
72
|
+
if (existsSync(archiveDir)) {
|
|
73
|
+
const targetDir = join(docsBase, 'archive');
|
|
74
|
+
mkdirSync(targetDir, { recursive: true });
|
|
75
|
+
const entries = readdirSync(archiveDir);
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
const src = join(archiveDir, entry);
|
|
78
|
+
const dest = join(targetDir, entry);
|
|
79
|
+
if (!existsSync(dest)) {
|
|
80
|
+
copyFileSync(src, dest);
|
|
81
|
+
console.log(chalk.green(' ✅') + ` archive/${entry}`);
|
|
82
|
+
migrated++;
|
|
83
|
+
} else {
|
|
84
|
+
console.log(chalk.yellow(' ⏭️') + ` archive/${entry} (已存在)`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 4. knowledge/ → docs/<project>/archive/ (append knowledge files)
|
|
90
|
+
const knowledgeDir = join(sillyspecDir, 'knowledge');
|
|
91
|
+
if (existsSync(knowledgeDir)) {
|
|
92
|
+
const targetDir = join(docsBase, 'archive');
|
|
93
|
+
mkdirSync(targetDir, { recursive: true });
|
|
94
|
+
const files = readdirSync(knowledgeDir).filter(f => f.endsWith('.md'));
|
|
95
|
+
for (const file of files) {
|
|
96
|
+
const src = join(knowledgeDir, file);
|
|
97
|
+
const dest = join(targetDir, `knowledge-${file}`);
|
|
98
|
+
if (!existsSync(dest)) {
|
|
99
|
+
copyFileSync(src, dest);
|
|
100
|
+
console.log(chalk.green(' ✅') + ` archive/knowledge-${file}`);
|
|
101
|
+
migrated++;
|
|
102
|
+
} else {
|
|
103
|
+
console.log(chalk.yellow(' ⏭️') + ` archive/knowledge-${file} (已存在)`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 5. quicklog/ → docs/<project>/quicklog/
|
|
109
|
+
const quicklogDir = join(sillyspecDir, 'quicklog');
|
|
110
|
+
if (existsSync(quicklogDir)) {
|
|
111
|
+
const targetDir = join(docsBase, 'quicklog');
|
|
112
|
+
mkdirSync(targetDir, { recursive: true });
|
|
113
|
+
const files = readdirSync(quicklogDir).filter(f => f.endsWith('.md'));
|
|
114
|
+
for (const file of files) {
|
|
115
|
+
const src = join(quicklogDir, file);
|
|
116
|
+
const dest = join(targetDir, file);
|
|
117
|
+
if (!existsSync(dest)) {
|
|
118
|
+
copyFileSync(src, dest);
|
|
119
|
+
console.log(chalk.green(' ✅') + ` quicklog/${file}`);
|
|
120
|
+
migrated++;
|
|
121
|
+
} else {
|
|
122
|
+
console.log(chalk.yellow(' ⏭️') + ` quicklog/${file} (已存在)`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log('');
|
|
128
|
+
if (migrated > 0) {
|
|
129
|
+
console.log(chalk.green(` ✅ 迁移完成,共迁移 ${migrated} 个文件`));
|
|
130
|
+
console.log(chalk.dim(' 旧文件保留在原位,确认无误后可手动删除'));
|
|
131
|
+
} else {
|
|
132
|
+
console.log(chalk.yellow(' 没有需要迁移的文件'));
|
|
133
|
+
}
|
|
134
|
+
}
|
package/templates/archive.md
CHANGED
|
@@ -16,7 +16,7 @@ $ARGUMENTS
|
|
|
16
16
|
|
|
17
17
|
### 1. 前置检查(门禁)
|
|
18
18
|
|
|
19
|
-
读取 `.sillyspec/changes/<change-name>/` 下所有必要文件,逐项检查:
|
|
19
|
+
读取 `.sillyspec/docs/<project>/changes/<change-name>/` 下所有必要文件,逐项检查:
|
|
20
20
|
|
|
21
21
|
- [ ] **文件完整性:** 检查 `design.md` 是否存在,缺失则警告
|
|
22
22
|
- [ ] **任务完成度:** 读取 `tasks.md`,统计已完成/未完成任务数。**有未完成的 → 用 AskUserQuestion 询问:**
|
|
@@ -32,11 +32,11 @@ $ARGUMENTS
|
|
|
32
32
|
- 包含的文件列表
|
|
33
33
|
- 任务完成统计(✅ 已完成 / ⬜ 未完成)
|
|
34
34
|
- 一句话总结本次变更
|
|
35
|
-
- quicklog 修改记录(如有 `.sillyspec/changes/<change-name>/quicklog/` 目录)
|
|
35
|
+
- quicklog 修改记录(如有 `.sillyspec/docs/<project>/changes/<change-name>/quicklog/` 目录)
|
|
36
36
|
|
|
37
37
|
### 3. Spec 沉淀
|
|
38
38
|
|
|
39
|
-
将 `.sillyspec/changes/<change-name>/` 下的设计文档**复制到 `.sillyspec/knowledge/` 主目录**,确保已完成的设计规范可被后续变更参考。如目标已存在同名文件则跳过并提示。
|
|
39
|
+
将 `.sillyspec/docs/<project>/changes/<change-name>/` 下的设计文档**复制到 `.sillyspec/knowledge/` 主目录**,确保已完成的设计规范可被后续变更参考。如目标已存在同名文件则跳过并提示。
|
|
40
40
|
|
|
41
41
|
### 4. 用户确认
|
|
42
42
|
|
|
@@ -74,7 +74,7 @@ $ARGUMENTS
|
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
76
|
# 收集该变更相关的 git commit(按变更名过滤或按时间范围)
|
|
77
|
-
git log --oneline --no-merges -- .sillyspec/changes/<change-name>/ 2>/dev/null
|
|
77
|
+
git log --oneline --no-merges -- .sillyspec/docs/<project>/changes/<change-name>/ 2>/dev/null
|
|
78
78
|
# 以及变更目录创建后的所有 commit
|
|
79
79
|
git log --oneline --no-merges --since="<创建时间>" -- "*.ts" "*.js" "*.vue" "*.java" 2>/dev/null
|
|
80
80
|
```
|
|
@@ -101,7 +101,7 @@ git log --oneline --no-merges --since="<创建时间>" -- "*.ts" "*.js" "*.vue"
|
|
|
101
101
|
|
|
102
102
|
### 5. 执行归档
|
|
103
103
|
|
|
104
|
-
- 目标路径:`.sillyspec/
|
|
104
|
+
- 目标路径:`.sillyspec/docs/<project>/archive/YYYY-MM-DD-<change-name>/`
|
|
105
105
|
- **检查目标路径是否已存在**,存在则中止并报错,防止覆盖
|
|
106
106
|
- 移动变更目录到归档路径
|
|
107
107
|
|
package/templates/brainstorm.md
CHANGED
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
cat .sillyspec/STATE.md 2>/dev/null
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
**确定当前项目名:**
|
|
22
|
+
```bash
|
|
23
|
+
ls .sillyspec/projects/*.yaml 2>/dev/null
|
|
24
|
+
```
|
|
25
|
+
从 projects/ 目录确定当前项目名。后续设计文档输出到 `.sillyspec/docs/<project>/brainstorm/`。
|
|
26
|
+
|
|
21
27
|
- phase 为 `brainstorm` 或无 STATE.md → ✅ 继续
|
|
22
28
|
- 其他 phase → 提示用户当前阶段,建议先完成
|
|
23
29
|
|
|
@@ -71,19 +77,19 @@ $ARGUMENTS
|
|
|
71
77
|
cat .sillyspec/config.yaml 2>/dev/null
|
|
72
78
|
```
|
|
73
79
|
|
|
74
|
-
**工作区模式:** AskUserQuestion 选子项目,**cd 到子项目目录执行**,加载子项目上下文 + 共享规范 + 工作区概览,设计文档保存到子项目 `.sillyspec/changes/`。修改在子项目目录中暂存。
|
|
80
|
+
**工作区模式:** AskUserQuestion 选子项目,**cd 到子项目目录执行**,加载子项目上下文 + 共享规范 + 工作区概览,设计文档保存到子项目 `.sillyspec/docs/<project>/changes/`。修改在子项目目录中暂存。
|
|
75
81
|
|
|
76
|
-
|
|
82
|
+
**加载项目上下文:**
|
|
77
83
|
```bash
|
|
78
84
|
cat .sillyspec/{PROJECT,REQUIREMENTS,ROADMAP}.md 2>/dev/null
|
|
79
|
-
cat .sillyspec/
|
|
80
|
-
ls .sillyspec/changes/ 2>/dev/null
|
|
85
|
+
cat .sillyspec/docs/<project>/scan/{STRUCTURE,CONVENTIONS,ARCHITECTURE}.md 2>/dev/null
|
|
86
|
+
ls .sillyspec/docs/<project>/changes/ 2>/dev/null
|
|
81
87
|
ls .sillyspec/knowledge/ 2>/dev/null
|
|
82
88
|
```
|
|
83
89
|
|
|
84
90
|
### Step 2: 协作与复用检查
|
|
85
91
|
|
|
86
|
-
- **同名变更:** `ls .sillyspec/changes/ | grep -v archive` — 有相关变更则提示避免冲突
|
|
92
|
+
- **同名变更:** `ls .sillyspec/docs/<project>/changes/ | grep -v archive` — 有相关变更则提示避免冲突
|
|
87
93
|
- **全局模板:** `ls ~/.sillyspec/templates/ 2>/dev/null` — 有匹配模板则建议复用
|
|
88
94
|
|
|
89
95
|
无匹配则跳过,不输出。
|
|
@@ -111,20 +117,20 @@ ls .sillyspec/knowledge/ 2>/dev/null
|
|
|
111
117
|
确认拆分后生成 MASTER.md:
|
|
112
118
|
|
|
113
119
|
```bash
|
|
114
|
-
mkdir -p .sillyspec/changes/<变更名>/stages
|
|
120
|
+
mkdir -p .sillyspec/docs/<project>/changes/<变更名>/stages
|
|
115
121
|
```
|
|
116
122
|
|
|
117
123
|
`MASTER.md` 内容:概述、拆分计划表(阶段/范围/状态)、整体技术方向、阶段间依赖、原型分析摘要、经验记录。
|
|
118
124
|
|
|
119
125
|
```bash
|
|
120
|
-
git add .sillyspec/changes/<变更名>/MASTER.md
|
|
126
|
+
git add .sillyspec/docs/<project>/changes/<变更名>/MASTER.md
|
|
121
127
|
```
|
|
122
128
|
|
|
123
129
|
💡 大模块计划已暂存。准备好后用 `/sillyspec:commit` 提交。
|
|
124
130
|
|
|
125
131
|
提示用户:`/sillyspec:brainstorm <变更名>/stage-1`
|
|
126
132
|
|
|
127
|
-
**子阶段 brainstorm:** 读取 MASTER.md + 前序阶段经验 + 对应原型,设计文档保存到 `.sillyspec/changes/<变更名>/stages/<stage-N>/`。
|
|
133
|
+
**子阶段 brainstorm:** 读取 MASTER.md + 前序阶段经验 + 对应原型,设计文档保存到 `.sillyspec/docs/<project>/changes/<变更名>/stages/<stage-N>/`。
|
|
128
134
|
|
|
129
135
|
### Step 5: 对话式探索
|
|
130
136
|
|
|
@@ -147,7 +153,7 @@ git add .sillyspec/changes/<变更名>/MASTER.md
|
|
|
147
153
|
|
|
148
154
|
### Step 8: 写设计文档
|
|
149
155
|
|
|
150
|
-
保存到 `.sillyspec/
|
|
156
|
+
保存到 `.sillyspec/docs/<project>/brainstorm/<变更名>.md`:
|
|
151
157
|
|
|
152
158
|
```markdown
|
|
153
159
|
# [Feature Name] 设计
|
package/templates/execute.md
CHANGED
|
@@ -18,7 +18,7 @@ cat .sillyspec/STATE.md 2>/dev/null
|
|
|
18
18
|
有 STATE.md 且 phase 为 execute → 继续。无 STATE.md 或 phase 不对 → 检查是否有未完成的 tasks.md:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
ls .sillyspec/changes/*/tasks.md 2>/dev/null | xargs grep -l '\- \[ \]' 2>/dev/null
|
|
21
|
+
ls .sillyspec/docs/<project>/changes/*/tasks.md 2>/dev/null | xargs grep -l '\- \[ \]' 2>/dev/null
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
有未完成的 tasks.md → 继续。没有 → 提示 `/sillyspec:continue`。
|
|
@@ -38,10 +38,10 @@ cat .sillyspec/config.yaml 2>/dev/null
|
|
|
38
38
|
|
|
39
39
|
**加载以下文件(主代理读取,后续注入子代理):**
|
|
40
40
|
```bash
|
|
41
|
-
PLAN=$(ls -t .sillyspec/changes/*/tasks.md 2>/dev/null | head -1); cat "$PLAN"
|
|
42
|
-
LATEST=$(ls -d .sillyspec/changes/*/ | grep -v archive | tail -1)
|
|
41
|
+
PLAN=$(ls -t .sillyspec/docs/<project>/changes/*/tasks.md 2>/dev/null | head -1); cat "$PLAN"
|
|
42
|
+
LATEST=$(ls -d .sillyspec/docs/<project>/changes/*/ | grep -v archive | tail -1)
|
|
43
43
|
cat "$LATEST"/{tasks,design}.md 2>/dev/null
|
|
44
|
-
cat .sillyspec/
|
|
44
|
+
cat .sillyspec/docs/<project>/scan/{CONVENTIONS,ARCHITECTURE}.md 2>/dev/null
|
|
45
45
|
cat .sillyspec/local.yaml 2>/dev/null
|
|
46
46
|
```
|
|
47
47
|
|
|
@@ -242,7 +242,7 @@ done
|
|
|
242
242
|
- 先 cat 相关功能代码和页面组件,理解交互逻辑
|
|
243
243
|
- 参考 prompt 中「测试模式参考」段的已有测试风格
|
|
244
244
|
- **查阅 Playwright 用法:** 优先使用已安装的 playwright skill(SKILL.md),不要凭记忆写 API。未安装则通过 Context7 MCP 或 web search 查最新文档
|
|
245
|
-
- 有测试框架则编写测试文件,无框架则编写 `.sillyspec/changes/<变更名>/e2e-steps.md` 结构化测试步骤
|
|
245
|
+
- 有测试框架则编写测试文件,无框架则编写 `.sillyspec/docs/<project>/changes/<变更名>/e2e-steps.md` 结构化测试步骤
|
|
246
246
|
- **写完必须立即跑一遍确认通过**,失败则修复后重跑,不要"写了就算完成"
|
|
247
247
|
8. **Lint 校验:** 完成后对修改的文件运行 lint 工具(与 quick 相同规则),自动修复可修复的问题,不可修复的标注在报告中
|
|
248
248
|
9. **暂存:** lint 通过后执行 git add -A(不要 commit,由用户通过 /sillyspec:commit 统一提交)
|
|
@@ -286,7 +286,7 @@ done
|
|
|
286
286
|
所有任务完成后,主代理必须执行以下检查:
|
|
287
287
|
|
|
288
288
|
```bash
|
|
289
|
-
cat .sillyspec/changes/*/tasks.md 2>/dev/null
|
|
289
|
+
cat .sillyspec/docs/<project>/changes/*/tasks.md 2>/dev/null
|
|
290
290
|
```
|
|
291
291
|
|
|
292
292
|
逐条验证:
|
package/templates/plan.md
CHANGED
|
@@ -29,9 +29,9 @@ cat .sillyspec/config.yaml 2>/dev/null
|
|
|
29
29
|
|
|
30
30
|
**单项目模式:**
|
|
31
31
|
```bash
|
|
32
|
-
LATEST=$(ls -d .sillyspec/changes/*/ | grep -v archive | tail -1)
|
|
32
|
+
LATEST=$(ls -d .sillyspec/docs/<project>/changes/*/ | grep -v archive | tail -1)
|
|
33
33
|
cat "$LATEST"/{design,tasks}.md 2>/dev/null
|
|
34
|
-
cat .sillyspec/
|
|
34
|
+
cat .sillyspec/docs/<project>/scan/{CONVENTIONS,ARCHITECTURE}.md 2>/dev/null
|
|
35
35
|
cat .sillyspec/REQUIREMENTS.md 2>/dev/null
|
|
36
36
|
```
|
|
37
37
|
|
|
@@ -96,7 +96,7 @@ cat .sillyspec/REQUIREMENTS.md 2>/dev/null
|
|
|
96
96
|
|
|
97
97
|
### 4. 保存
|
|
98
98
|
|
|
99
|
-
**直接覆盖** `.sillyspec/changes/<变更名>/tasks.md`。不再生成单独的 plan.md 文件。
|
|
99
|
+
**直接覆盖** `.sillyspec/docs/<project>/changes/<变更名>/tasks.md`。不再生成单独的 plan.md 文件。
|
|
100
100
|
|
|
101
101
|
### 5. E2E 测试规划
|
|
102
102
|
|