sillyspec 3.9.1 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.claude/skills/sillyspec-commit/SKILL.md +1 -1
  2. package/.claude/skills/sillyspec-continue/SKILL.md +1 -1
  3. package/.claude/skills/sillyspec-explore/SKILL.md +9 -0
  4. package/.claude/skills/sillyspec-plan/SKILL.md +0 -35
  5. package/.claude/skills/sillyspec-workspace/SKILL.md +1 -1
  6. package/README.md +7 -6
  7. package/SKILL.md +15 -10
  8. package/package.json +1 -1
  9. package/packages/dashboard/dist/assets/index-BcM2J-hv.css +1 -0
  10. package/packages/dashboard/dist/assets/{index-RsLVPAy7.js → index-DpLHK4jv.js} +974 -974
  11. package/packages/dashboard/dist/index.html +16 -17
  12. package/packages/dashboard/dist/prototype-dashboard.html +836 -0
  13. package/packages/dashboard/dist/prototype-overview.html +256 -0
  14. package/packages/dashboard/public/prototype-dashboard.html +836 -0
  15. package/packages/dashboard/public/prototype-overview.html +256 -0
  16. package/packages/dashboard/server/index.js +18 -13
  17. package/packages/dashboard/server/parser.js +109 -1
  18. package/packages/dashboard/server/watcher.js +14 -6
  19. package/packages/dashboard/src/App.vue +414 -186
  20. package/packages/dashboard/src/components/ActionBar.vue +10 -1
  21. package/packages/dashboard/src/components/CommandPalette.vue +5 -1
  22. package/packages/dashboard/src/components/DocPreview.vue +105 -8
  23. package/packages/dashboard/src/components/DocTree.vue +75 -19
  24. package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
  25. package/packages/dashboard/src/components/PipelineView.vue +23 -4
  26. package/packages/dashboard/src/components/ProjectCard.vue +187 -0
  27. package/packages/dashboard/src/components/ProjectOverview.vue +113 -139
  28. package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
  29. package/packages/dashboard/src/composables/useDashboard.js +28 -0
  30. package/packages/dashboard/src/composables/useLayout.js +131 -0
  31. package/src/index.js +7 -0
  32. package/src/init.js +17 -10
  33. package/src/migrate.js +5 -5
  34. package/src/progress.js +2 -1
  35. package/src/run.js +72 -61
  36. package/src/stages/brainstorm.js +28 -3
  37. package/src/stages/execute.js +52 -27
  38. package/src/stages/explore.js +34 -0
  39. package/src/stages/index.js +3 -1
  40. package/src/stages/plan.js +86 -14
  41. package/src/stages/scan.js +11 -11
  42. package/src/stages/status.js +1 -1
  43. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
  44. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
  45. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
  46. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
  47. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
  48. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
  49. package/.sillyspec/changes/auto-mode/design.md +0 -50
  50. package/.sillyspec/changes/auto-mode/proposal.md +0 -19
  51. package/.sillyspec/changes/auto-mode/requirements.md +0 -21
  52. package/.sillyspec/changes/auto-mode/tasks.md +0 -7
  53. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +0 -206
  54. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
  55. package/.sillyspec/changes/dashboard/design.md +0 -219
  56. package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
  57. package/.sillyspec/changes/run-command-design/design.md +0 -1230
  58. package/.sillyspec/changes/unified-docs-design/design.md +0 -199
  59. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  60. package/.sillyspec/knowledge/INDEX.md +0 -8
  61. package/.sillyspec/knowledge/uncategorized.md +0 -3
  62. package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
  63. package/.sillyspec/projects/sillyspec.yaml +0 -3
  64. package/packages/dashboard/dist/assets/index-CntACGUN.css +0 -1
@@ -1,178 +1,152 @@
1
1
  <template>
2
- <div v-if="project" class="project-overview">
3
- <!-- Name + Path -->
4
- <div class="ov-section">
5
- <span class="ov-name">{{ project.name }}</span>
6
- <span class="ov-path">{{ shortPath }}</span>
2
+ <div class="overview-section">
3
+ <!-- 头部 -->
4
+ <div class="section-header">
5
+ <h1 class="section-title">
6
+ 项目概览
7
+ <span class="badge">{{ projects.length }} 个项目</span>
8
+ </h1>
9
+ <div class="section-actions">
10
+ <button class="btn btn-icon" title="刷新" @click="refresh">↻</button>
11
+ <button class="btn btn-secondary" @click="resetLayout">重置布局</button>
12
+ </div>
7
13
  </div>
8
- <div class="ov-divider" />
9
14
 
10
- <!-- Tech Stack -->
11
- <div class="ov-section ov-clickable" @click="$emit('show-detail', 'tech')">
12
- <span class="ov-label">技术栈</span>
13
- <span v-for="t in overview.techStack" :key="t" class="ov-tag">{{ t }}</span>
14
- </div>
15
- <div class="ov-divider" />
16
-
17
- <!-- Stage -->
18
- <div v-if="project.state?.currentStage" class="ov-section">
19
- <span class="ov-label">阶段</span>
20
- <span class="ov-value">{{ project.state.currentStage }}</span>
21
- </div>
22
- <div v-else-if="project.state" class="ov-section">
23
- <span class="ov-label">阶段</span>
24
- <span class="ov-value" style="color:#9CA3AF;">未执行</span>
25
- </div>
26
- <div v-if="project.state?.currentStage" class="ov-divider" />
27
-
28
- <!-- Last Active -->
29
- <div v-if="overview.lastActive" class="ov-section">
30
- <span class="ov-label">最近活跃</span>
31
- <span class="ov-value">{{ formatTime(overview.lastActive) }}</span>
32
- </div>
33
- <div v-if="overview.lastActive" class="ov-divider" />
34
-
35
- <!-- Doc Stats -->
36
- <div v-if="overview.docStats.total > 0" class="ov-section ov-clickable" @click="$emit('show-detail', 'docs')">
37
- <span class="ov-label">文档</span>
38
- <span class="ov-value">{{ docSummary }}</span>
39
- </div>
40
- <div v-if="overview.docStats.total > 0" class="ov-divider" />
41
-
42
- <!-- Git -->
43
- <div class="ov-section ov-git ov-clickable" @click="$emit('show-detail', 'git')">
44
- <span v-if="overview.git.branch" class="ov-branch">{{ overview.git.branch }}</span>
45
- <span v-if="overview.git.lastCommit" class="ov-commit" :title="overview.git.lastCommit">{{ truncate(overview.git.lastCommit, 40) }}</span>
46
- <span v-if="overview.git.dirtyCount > 0" class="ov-dirty">{{ overview.git.dirtyCount }} 未提交</span>
15
+ <!-- 卡片容器 -->
16
+ <div class="cards-container">
17
+ <ProjectCard
18
+ v-for="project in projects"
19
+ :key="project.name"
20
+ :project="project"
21
+ :is-selected="activeProject?.name === project.name"
22
+ @select="handleSelect"
23
+ />
47
24
  </div>
48
25
  </div>
49
26
  </template>
50
27
 
51
28
  <script setup>
52
- import { computed } from 'vue'
29
+ import { defineProps, defineEmits } from 'vue'
30
+ import ProjectCard from './ProjectCard.vue'
31
+ import { useLayout } from '../composables/useLayout.js'
53
32
 
54
33
  const props = defineProps({
55
- project: { type: Object, default: null }
34
+ projects: { type: Array, default: () => [] },
35
+ activeProject: { type: Object, default: null }
56
36
  })
57
37
 
58
- defineEmits(['show-detail'])
59
-
60
- const overview = computed(() => props.project?.overview || { techStack: [], lastActive: null, docStats: { design: 0, plan: 0, archive: 0, changes: 0, scan: 0, quicklog: 0, total: 0 }, git: { branch: '', lastCommit: '', dirtyCount: 0 } })
61
-
62
- const shortPath = computed(() => {
63
- const p = props.project?.path || ''
64
- return p.replace(/^\/Users\/[^/]+/, '~')
65
- })
38
+ const emit = defineEmits(['select'])
66
39
 
67
- const docSummary = computed(() => {
68
- const s = overview.value.docStats
69
- const parts = []
70
- if (s.design) parts.push(`设计 ${s.design}`)
71
- if (s.plan) parts.push(`计划 ${s.plan}`)
72
- if (s.archive) parts.push(`归档 ${s.archive}`)
73
- if (s.changes) parts.push(`变更 ${s.changes}`)
74
- return parts.join(' / ') || `${s.total} 篇`
75
- })
40
+ function handleSelect(project) {
41
+ emit('select', project)
42
+ }
76
43
 
77
- function formatTime(iso) {
78
- if (!iso) return ''
79
- const d = new Date(iso)
80
- const now = new Date()
81
- const diffMs = now - d
82
- if (diffMs < 60000) return '刚刚'
83
- if (diffMs < 3600000) return `${Math.floor(diffMs / 60000)} 分钟前`
84
- if (diffMs < 86400000) return `${Math.floor(diffMs / 3600000)} 小时前`
85
- if (diffMs < 604800000) return `${Math.floor(diffMs / 86400000)} 天前`
86
- return `${d.getMonth() + 1}/${d.getDate()}`
44
+ function refresh() {
45
+ // TODO: 实现刷新逻辑
46
+ window.location.reload()
87
47
  }
88
48
 
89
- function truncate(s, n) {
90
- return s.length > n ? s.slice(0, n) + '' : s
49
+ function resetLayout() {
50
+ if (confirm('确定要重置布局吗?')) {
51
+ useLayout().resetLayout()
52
+ }
91
53
  }
92
54
  </script>
93
55
 
94
56
  <style scoped>
95
- .project-overview {
57
+ .overview-section {
58
+ height: 100%;
59
+ background: #FFFFFF;
60
+ padding: 16px 24px;
61
+ display: flex;
62
+ flex-direction: column;
63
+ }
64
+
65
+ .section-header {
66
+ display: flex;
67
+ justify-content: space-between;
68
+ align-items: center;
69
+ margin-bottom: 16px;
70
+ }
71
+
72
+ .section-title {
73
+ font-size: 18px;
74
+ font-weight: 600;
75
+ color: #1A1A1A;
96
76
  display: flex;
97
77
  align-items: center;
98
- background: #FFFFFF;
99
- border-bottom: 1px solid #E5E5EA;
100
- padding: 8px 16px;
101
- min-height: 48px;
102
78
  gap: 8px;
103
- flex-shrink: 0;
104
- overflow-x: auto;
105
79
  }
106
- .ov-section {
80
+
81
+ .badge {
82
+ font-size: 12px;
83
+ padding: 2px 8px;
84
+ background: #E5E7EB;
85
+ border-radius: 10px;
86
+ color: #6B7280;
87
+ font-weight: 500;
88
+ }
89
+
90
+ .section-actions {
107
91
  display: flex;
92
+ gap: 8px;
108
93
  align-items: center;
109
- gap: 6px;
110
- white-space: nowrap;
111
- flex-shrink: 0;
112
94
  }
113
- .ov-clickable {
95
+
96
+ .btn {
97
+ padding: 6px 12px;
98
+ border-radius: 6px;
99
+ border: none;
114
100
  cursor: pointer;
115
- border-radius: 4px;
116
- padding: 2px 6px;
117
- margin: -2px -6px;
118
- transition: background 0.15s;
101
+ font-size: 13px;
102
+ font-weight: 500;
103
+ transition: all 0.2s;
119
104
  }
120
- .ov-clickable:hover {
121
- background: rgba(217,119,6,0.06);
105
+
106
+ .btn:hover {
107
+ opacity: 0.9;
122
108
  }
123
- .ov-name {
124
- font-weight: 600;
125
- font-size: 13px;
126
- color: #1C1C1E;
109
+
110
+ .btn-secondary {
111
+ background: #E5E7EB;
112
+ color: #374151;
127
113
  }
128
- .ov-path {
129
- font-size: 11px;
130
- color: #636366;
114
+
115
+ .btn-icon {
116
+ padding: 6px;
117
+ width: 32px;
118
+ height: 32px;
119
+ display: flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ background: #F3F4F6;
123
+ border-radius: 6px;
131
124
  }
132
- .ov-label {
133
- font-size: 11px;
134
- color: #636366;
125
+
126
+ .btn-icon:hover {
127
+ background: #E5E7EB;
135
128
  }
136
- .ov-value {
137
- font-size: 12px;
138
- color: #1C1C1E;
139
- font-weight: 500;
129
+
130
+ .cards-container {
131
+ flex: 1;
132
+ display: flex;
133
+ gap: 16px;
134
+ overflow-x: auto;
135
+ overflow-y: hidden;
136
+ padding-bottom: 8px;
140
137
  }
141
- .ov-tag {
142
- font-size: 10px;
143
- padding: 1px 8px;
144
- border-radius: 10px;
145
- background: rgba(217,119,6,0.08);
146
- color: #D97706;
147
- font-weight: 500;
138
+
139
+ .cards-container::-webkit-scrollbar {
140
+ height: 8px;
148
141
  }
149
- .ov-divider {
150
- width: 1px;
151
- height: 20px;
152
- background: #E5E5EA;
153
- flex-shrink: 0;
154
- }
155
- .ov-git {
156
- margin-left: auto;
157
- gap: 10px;
158
- }
159
- .ov-branch {
160
- font-size: 11px;
161
- color: #636366;
162
- background: #F5F5F7;
163
- padding: 1px 8px;
142
+
143
+ .cards-container::-webkit-scrollbar-track {
144
+ background: #F3F4F6;
164
145
  border-radius: 4px;
165
146
  }
166
- .ov-commit {
167
- font-size: 11px;
168
- color: #636366;
169
- max-width: 200px;
170
- overflow: hidden;
171
- text-overflow: ellipsis;
172
- }
173
- .ov-dirty {
174
- font-size: 11px;
175
- color: #D97706;
176
- font-weight: 500;
147
+
148
+ .cards-container::-webkit-scrollbar-thumb {
149
+ background: #D1D5DB;
150
+ border-radius: 4px;
177
151
  }
178
152
  </style>
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <div
3
+ class="v-resize-handle"
4
+ :class="{ 'dragging': isDragging }"
5
+ @mousedown="handleMouseDown"
6
+ >
7
+ <div class="drag-indicator"></div>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup>
12
+ const props = defineProps({
13
+ isDragging: { type: Boolean, default: false }
14
+ })
15
+
16
+ const emit = defineEmits(['drag-start', 'drag-end', 'resize'])
17
+
18
+ function handleMouseDown(e) {
19
+ emit('drag-start', { type: 'vertical', startY: e.clientY })
20
+
21
+ const onMove = (ev) => {
22
+ emit('resize', { deltaY: ev.clientY - e.clientY })
23
+ }
24
+
25
+ const onUp = () => {
26
+ emit('drag-end')
27
+ window.removeEventListener('mousemove', onMove)
28
+ window.removeEventListener('mouseup', onUp)
29
+ }
30
+
31
+ window.addEventListener('mousemove', onMove)
32
+ window.addEventListener('mouseup', onUp)
33
+ }
34
+ </script>
35
+
36
+ <style scoped>
37
+ .v-resize-handle {
38
+ height: 6px;
39
+ background: #2A3040;
40
+ cursor: row-resize;
41
+ position: relative;
42
+ transition: background 0.2s;
43
+ }
44
+
45
+ .v-resize-handle:hover,
46
+ .v-resize-handle.dragging {
47
+ background: #D97706;
48
+ }
49
+
50
+ .drag-indicator {
51
+ position: absolute;
52
+ top: 50%;
53
+ left: 50%;
54
+ transform: translate(-50%, -50%);
55
+ width: 40px;
56
+ height: 4px;
57
+ background: rgba(255, 255, 255, 0.3);
58
+ border-radius: 2px;
59
+ pointer-events: none;
60
+ }
61
+ </style>
@@ -125,8 +125,35 @@ export function useDashboard() {
125
125
  const updated = getProject(state.activeProject.path)
126
126
  if (updated) {
127
127
  state.activeProject = updated
128
+ return
128
129
  }
129
130
  }
131
+
132
+ state.activeProject = state.projects[0] || null
133
+ }
134
+
135
+ /**
136
+ * Merge a single project update into the current project list.
137
+ * @param {object} project - Partial project payload containing at least path
138
+ */
139
+ function updateProject(project) {
140
+ if (!project?.path) return
141
+
142
+ const index = state.projects.findIndex(p => p.path === project.path)
143
+ if (index === -1) return
144
+
145
+ const updated = {
146
+ ...state.projects[index],
147
+ ...project,
148
+ state: project.state ?? state.projects[index].state,
149
+ overview: project.overview ?? state.projects[index].overview
150
+ }
151
+
152
+ state.projects.splice(index, 1, updated)
153
+
154
+ if (state.activeProject?.path === project.path) {
155
+ state.activeProject = updated
156
+ }
130
157
  }
131
158
 
132
159
  /**
@@ -169,6 +196,7 @@ export function useDashboard() {
169
196
  openPanel,
170
197
  closePanel,
171
198
  updateProjects,
199
+ updateProject,
172
200
  setExecuting,
173
201
  isExecuting,
174
202
  activeProjectName,
@@ -0,0 +1,131 @@
1
+ /**
2
+ * 布局管理 Composable
3
+ * 管理双层布局的概览/详情比例和三栏宽度
4
+ */
5
+ import { reactive, ref, onMounted } from 'vue'
6
+
7
+ const STORAGE_KEY = 'dashboard-layout-v2'
8
+
9
+ // 默认值
10
+ const DEFAULT_OVERVIEW_HEIGHT = 30 // 概览区域百分比
11
+ const DEFAULT_COLUMN_WIDTHS = [33.33, 33.33, 33.33] // 三栏宽度百分比
12
+
13
+ // 最小值限制
14
+ const MIN_OVERVIEW_HEIGHT = 15
15
+ const MAX_OVERVIEW_HEIGHT = 75
16
+ const MIN_COLUMN_WIDTH = 10
17
+
18
+ /**
19
+ * 布局管理 Hook
20
+ */
21
+ export function useLayout() {
22
+ // 布局状态
23
+ const layout = reactive({
24
+ overviewHeight: DEFAULT_OVERVIEW_HEIGHT,
25
+ columnWidths: [...DEFAULT_COLUMN_WIDTHS]
26
+ })
27
+
28
+ // 拖动状态
29
+ const isDragging = ref(false)
30
+ const dragType = ref(null) // 'vertical' | 'horizontal'
31
+
32
+ /**
33
+ * 加载保存的布局
34
+ */
35
+ function loadLayout() {
36
+ try {
37
+ const saved = localStorage.getItem(STORAGE_KEY)
38
+ if (saved) {
39
+ const parsed = JSON.parse(saved)
40
+ if (parsed.overviewHeight !== undefined) {
41
+ layout.overviewHeight = Math.max(MIN_OVERVIEW_HEIGHT, Math.min(MAX_OVERVIEW_HEIGHT, parsed.overviewHeight))
42
+ }
43
+ if (Array.isArray(parsed.columnWidths) && parsed.columnWidths.length === 3) {
44
+ layout.columnWidths = parsed.columnWidths
45
+ }
46
+ }
47
+ } catch (e) {
48
+ console.error('Failed to load layout:', e)
49
+ // 使用默认值
50
+ resetLayout()
51
+ }
52
+ }
53
+
54
+ /**
55
+ * 保存布局到 localStorage
56
+ */
57
+ function saveLayout() {
58
+ try {
59
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({
60
+ overviewHeight: layout.overviewHeight,
61
+ columnWidths: layout.columnWidths
62
+ }))
63
+ } catch (e) {
64
+ console.error('Failed to save layout:', e)
65
+ }
66
+ }
67
+
68
+ /**
69
+ * 重置布局为默认值
70
+ */
71
+ function resetLayout() {
72
+ layout.overviewHeight = DEFAULT_OVERVIEW_HEIGHT
73
+ layout.columnWidths = [...DEFAULT_COLUMN_WIDTHS]
74
+ saveLayout()
75
+ }
76
+
77
+ /**
78
+ * 设置概览区域高度(百分比)
79
+ */
80
+ function setOverviewHeight(percent) {
81
+ layout.overviewHeight = Math.max(MIN_OVERVIEW_HEIGHT, Math.min(MAX_OVERVIEW_HEIGHT, percent))
82
+ saveLayout()
83
+ }
84
+
85
+ /**
86
+ * 设置列宽度(索引,百分比)
87
+ */
88
+ function setColumnWidth(index, percent) {
89
+ if (index >= 0 && index < 3) {
90
+ layout.columnWidths[index] = Math.max(MIN_COLUMN_WIDTH, percent)
91
+ saveLayout()
92
+ }
93
+ }
94
+
95
+ /**
96
+ * 开始拖动
97
+ */
98
+ function startDrag(type) {
99
+ isDragging.value = true
100
+ dragType.value = type
101
+ document.body.classList.add('resizing')
102
+ }
103
+
104
+ /**
105
+ * 结束拖动
106
+ */
107
+ function endDrag() {
108
+ isDragging.value = false
109
+ dragType.value = null
110
+ document.body.classList.remove('resizing')
111
+ saveLayout()
112
+ }
113
+
114
+ // 组件挂载时加载布局
115
+ onMounted(() => {
116
+ loadLayout()
117
+ })
118
+
119
+ return {
120
+ layout,
121
+ isDragging,
122
+ dragType,
123
+ loadLayout,
124
+ saveLayout,
125
+ resetLayout,
126
+ setOverviewHeight,
127
+ setColumnWidth,
128
+ startDrag,
129
+ endDrag
130
+ }
131
+ }
package/src/index.js CHANGED
@@ -34,6 +34,10 @@ SillySpec CLI — 规范驱动开发工具包
34
34
  --change <name> 设置当前变更名
35
35
  auto 连续推进 brainstorm→plan→execute→verify
36
36
 
37
+ 可选阶段:
38
+ scan, brainstorm, plan, execute, verify, archive
39
+ quick, explore, status, doctor
40
+
37
41
  sillyspec progress <cmd> 进度记录(轻量,不强制顺序)
38
42
  init 初始化 progress.json
39
43
  show 查看当前进度
@@ -56,7 +60,10 @@ SillySpec CLI — 规范驱动开发工具包
56
60
 
57
61
  示例:
58
62
  sillyspec init
63
+ sillyspec run scan
59
64
  sillyspec run brainstorm
65
+ sillyspec run quick
66
+ sillyspec run explore
60
67
  sillyspec run brainstorm --done --output "需求已澄清"
61
68
  sillyspec setup --list
62
69
  sillyspec dashboard --port 8080 --no-open
package/src/init.js CHANGED
@@ -27,11 +27,10 @@ function copyDirSync(src, dst) {
27
27
 
28
28
 
29
29
 
30
- const VALID_TOOLS = ['claude', 'claude_skills', 'cursor', 'openclaw', 'codex', 'gemini', 'opencode'];
30
+ const VALID_TOOLS = ['claude', 'cursor', 'openclaw', 'codex', 'gemini', 'opencode'];
31
31
 
32
32
  const TOOL_LABELS = {
33
33
  claude: 'Claude Code',
34
- claude_skills: 'Claude Skills',
35
34
  cursor: 'Cursor',
36
35
  openclaw: 'OpenClaw',
37
36
  codex: 'OpenAI Codex (通过 AGENTS.md)',
@@ -87,7 +86,6 @@ function injectInstructions(tool, projectDir) {
87
86
  function detectTools(projectDir) {
88
87
  const found = [];
89
88
  if (existsSync(join(projectDir, '.claude'))) found.push('claude');
90
- if (existsSync(join(projectDir, '.claude', 'skills'))) found.push('claude_skills');
91
89
  if (existsSync(join(projectDir, '.cursor'))) found.push('cursor');
92
90
  if (existsSync(join(projectDir, '.openclaw'))) found.push('openclaw');
93
91
  if (existsSync(join(projectDir, 'AGENTS.md'))) found.push('codex');
@@ -121,7 +119,7 @@ async function doInstall(projectDir, tools, subprojects = []) {
121
119
  writeFileSync(projectYamlPath, `name: ${projectName}\npath: .\nstatus: active\n`);
122
120
  }
123
121
 
124
- // 创建 docs/<projectName>/scan/ 子目录(代码扫描结果)
122
+ // 创建 .sillyspec/docs/<projectName>/scan/ 子目录(代码扫描结果)
125
123
  const scanDir = join(projectDir, '.sillyspec', 'docs', projectName, 'scan');
126
124
  mkdirSync(scanDir, { recursive: true });
127
125
  const gitkeepPath = join(scanDir, '.gitkeep');
@@ -185,17 +183,26 @@ async function doInstall(projectDir, tools, subprojects = []) {
185
183
  }
186
184
  }
187
185
 
188
- // 复制 skills 到 .claude/skills/(给 Claude Code 使用)
189
- const claudeSkillsDir = join(projectDir, '.claude', 'skills');
186
+ // 复制 skills 到各工具目录
187
+ const skillToolDirs = {
188
+ claude: '.claude/skills',
189
+ codex: '.codex/skills',
190
+ openclaw: '.openclaw/skills',
191
+ opencode: '.opencode/skills',
192
+ }
190
193
  const skillsSource = join(__dirname, '..', '.claude', 'skills');
191
194
  if (existsSync(skillsSource)) {
192
195
  const sillyspecSkills = readdirSync(skillsSource).filter(f => f.startsWith('sillyspec-') && statSync(join(skillsSource, f)).isDirectory());
193
196
  if (sillyspecSkills.length > 0) {
194
- mkdirSync(claudeSkillsDir, { recursive: true });
195
- for (const skill of sillyspecSkills) {
196
- copyDirSync(join(skillsSource, skill), join(claudeSkillsDir, skill));
197
+ for (const [tool, dir] of Object.entries(skillToolDirs)) {
198
+ if (!tools.includes(tool)) continue
199
+ const targetDir = join(projectDir, dir)
200
+ mkdirSync(targetDir, { recursive: true })
201
+ for (const skill of sillyspecSkills) {
202
+ copyDirSync(join(skillsSource, skill), join(targetDir, skill))
203
+ }
204
+ console.log(chalk.green(` ✓ ${TOOL_LABELS[tool]} skills 已同步 (${sillyspecSkills.length} 个)`))
197
205
  }
198
- console.log(chalk.green(' ✓ Claude Code skills 已同步 (' + sillyspecSkills.length + ' 个)'));
199
206
  }
200
207
  } else {
201
208
  console.log(chalk.yellow(' ⚠ 未找到 skills 目录(npm 包内无 .claude/skills/),跳过同步'));
package/src/migrate.js CHANGED
@@ -3,7 +3,7 @@ import { basename, join, resolve } from 'path';
3
3
  import chalk from 'chalk';
4
4
 
5
5
  /**
6
- * Migrate old .sillyspec/ structure to unified docs/<project>/ structure
6
+ * Migrate old .sillyspec/ structure to unified .sillyspec/docs/<project>/ structure
7
7
  * @param {string} projectDir - Path to the project directory
8
8
  */
9
9
  export function migrateDocs(projectDir) {
@@ -29,7 +29,7 @@ export function migrateDocs(projectDir) {
29
29
  const docsBase = join(sillyspecDir, 'docs', projectName);
30
30
  let migrated = 0;
31
31
 
32
- // 1. codebase/ → docs/<project>/scan/
32
+ // 1. codebase/ → .sillyspec/docs/<project>/scan/
33
33
  const codebaseDir = join(sillyspecDir, 'codebase');
34
34
  if (existsSync(codebaseDir)) {
35
35
  const targetDir = join(docsBase, 'scan');
@@ -50,7 +50,7 @@ export function migrateDocs(projectDir) {
50
50
 
51
51
  // 2. specs/ is deprecated — designs live in changes/<变更名>/design.md
52
52
 
53
- // 3. changes/archive/ → docs/<project>/archive/
53
+ // 3. changes/archive/ → .sillyspec/docs/<project>/archive/
54
54
  const archiveDir = join(sillyspecDir, 'changes', 'archive');
55
55
  if (existsSync(archiveDir)) {
56
56
  const targetDir = join(docsBase, 'archive');
@@ -69,7 +69,7 @@ export function migrateDocs(projectDir) {
69
69
  }
70
70
  }
71
71
 
72
- // 4. knowledge/ → docs/<project>/archive/ (append knowledge files)
72
+ // 4. knowledge/ → .sillyspec/docs/<project>/archive/ (append knowledge files)
73
73
  const knowledgeDir = join(sillyspecDir, 'knowledge');
74
74
  if (existsSync(knowledgeDir)) {
75
75
  const targetDir = join(docsBase, 'archive');
@@ -88,7 +88,7 @@ export function migrateDocs(projectDir) {
88
88
  }
89
89
  }
90
90
 
91
- // 5. quicklog/ → docs/<project>/quicklog/
91
+ // 5. quicklog/ → .sillyspec/docs/<project>/quicklog/
92
92
  const quicklogDir = join(sillyspecDir, 'quicklog');
93
93
  if (existsSync(quicklogDir)) {
94
94
  const targetDir = join(docsBase, 'quicklog');
package/src/progress.js CHANGED
@@ -14,7 +14,7 @@ const PROGRESS_FILE = 'progress.json';
14
14
  const BACKUP_FILE = 'progress.json.bak';
15
15
 
16
16
  const CURRENT_VERSION = 2;
17
- const VALID_STAGES = ['brainstorm', 'plan', 'execute', 'verify', 'scan', 'quick', 'archive'];
17
+ const VALID_STAGES = ['scan', 'brainstorm', 'plan', 'execute', 'verify', 'archive', 'quick', 'explore'];
18
18
  const VALID_STATUSES = ['pending', 'in-progress', 'completed', 'failed', 'blocked'];
19
19
 
20
20
  const STAGE_LABELS = {
@@ -24,6 +24,7 @@ const STAGE_LABELS = {
24
24
  verify: '🔍 验证确认',
25
25
  scan: '🔍 代码扫描',
26
26
  quick: '⚡ 快速任务',
27
+ explore: '🧭 自由探索',
27
28
  archive: '📦 归档变更',
28
29
  };
29
30