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.
@@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
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-6 pb-4" style="border-bottom: 1px solid #1F1F22;">
5
- <h2 class="text-[11px] font-semibold uppercase tracking-[0.2em] font-[JetBrains_Mono,monospace]" style="color: #525252;">
6
- 项目流水线
7
- </h2>
8
- <p v-if="project" class="text-[12px] mt-1.5 font-[JetBrains_Mono,monospace]" style="color: #8B8B8E;">
9
- {{ project.name }} <span style="color: #2A2A2D;">/</span> <span style="color: #FBBF24;">{{ currentStage }}</span>
10
- </p>
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
- <!-- Stages -->
14
- <div class="flex-1 overflow-y-auto px-6 py-5">
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 h-full">
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
- <div v-else class="space-y-5">
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
- <!-- Activity Log -->
39
- <div v-if="project?.state?.progress" style="border-top: 1px solid #1F1F22; background: rgba(10,10,11,0.6);">
40
- <div class="px-6 py-2.5 flex items-center justify-between">
41
- <div class="text-[9px] font-semibold uppercase tracking-[0.25em] font-[JetBrains_Mono,monospace]" style="color: #525252;">活动日志</div>
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="px-6 pb-3 space-y-0.5 max-h-32 overflow-y-auto">
45
- <div v-for="(log, i) in activityLogs" :key="i" class="flex items-start gap-2.5 text-[11px] py-0.5">
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 dashboard 启动 Dashboard Web UI
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/codebase/CONVENTIONS.md\`(代码风格)和 \`.sillyspec/codebase/ARCHITECTURE.md\`(架构)
84
+ - 写代码前先读取 \`.sillyspec/docs/<project>/scan/CONVENTIONS.md\`(代码风格)和 \`.sillyspec/docs/<project>/scan/ARCHITECTURE.md\`(架构)
85
85
  - 调用已有方法前,用 grep 确认方法存在,不许编造
86
- - 遵循 \`.sillyspec/codebase/CONVENTIONS.md\` 中的代码风格
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/codebase/ → scan
207
- // .sillyspec/codebase/details/scan (deep)
208
- // .sillyspec/changes/ brainstorm/propose
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
- // (plan 内容已合并到 tasks.md)
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
+ }
@@ -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/changes/archive/YYYY-MM-DD-<change-name>/`
104
+ - 目标路径:`.sillyspec/docs/<project>/archive/YYYY-MM-DD-<change-name>/`
105
105
  - **检查目标路径是否已存在**,存在则中止并报错,防止覆盖
106
106
  - 移动变更目录到归档路径
107
107
 
@@ -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/codebase/{STRUCTURE,CONVENTIONS,ARCHITECTURE}.md 2>/dev/null
80
- ls .sillyspec/changes/ 2>/dev/null | grep -v archive
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/changes/<变更名>/design.md`:
156
+ 保存到 `.sillyspec/docs/<project>/brainstorm/<变更名>.md`:
151
157
 
152
158
  ```markdown
153
159
  # [Feature Name] 设计
@@ -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/codebase/{CONVENTIONS,ARCHITECTURE}.md 2>/dev/null
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/codebase/{CONVENTIONS,ARCHITECTURE}.md 2>/dev/null
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