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.
- package/.claude/skills/sillyspec-commit/SKILL.md +1 -1
- package/.claude/skills/sillyspec-continue/SKILL.md +1 -1
- package/.claude/skills/sillyspec-explore/SKILL.md +9 -0
- package/.claude/skills/sillyspec-plan/SKILL.md +0 -35
- package/.claude/skills/sillyspec-workspace/SKILL.md +1 -1
- package/README.md +7 -6
- package/SKILL.md +15 -10
- package/package.json +1 -1
- package/packages/dashboard/dist/assets/index-BcM2J-hv.css +1 -0
- package/packages/dashboard/dist/assets/{index-RsLVPAy7.js → index-DpLHK4jv.js} +974 -974
- package/packages/dashboard/dist/index.html +16 -17
- package/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/packages/dashboard/dist/prototype-overview.html +256 -0
- package/packages/dashboard/public/prototype-dashboard.html +836 -0
- package/packages/dashboard/public/prototype-overview.html +256 -0
- package/packages/dashboard/server/index.js +18 -13
- package/packages/dashboard/server/parser.js +109 -1
- package/packages/dashboard/server/watcher.js +14 -6
- package/packages/dashboard/src/App.vue +414 -186
- package/packages/dashboard/src/components/ActionBar.vue +10 -1
- package/packages/dashboard/src/components/CommandPalette.vue +5 -1
- package/packages/dashboard/src/components/DocPreview.vue +105 -8
- package/packages/dashboard/src/components/DocTree.vue +75 -19
- package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
- package/packages/dashboard/src/components/PipelineView.vue +23 -4
- package/packages/dashboard/src/components/ProjectCard.vue +187 -0
- package/packages/dashboard/src/components/ProjectOverview.vue +113 -139
- package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
- package/packages/dashboard/src/composables/useDashboard.js +28 -0
- package/packages/dashboard/src/composables/useLayout.js +131 -0
- package/src/index.js +7 -0
- package/src/init.js +17 -10
- package/src/migrate.js +5 -5
- package/src/progress.js +2 -1
- package/src/run.js +72 -61
- package/src/stages/brainstorm.js +28 -3
- package/src/stages/execute.js +52 -27
- package/src/stages/explore.js +34 -0
- package/src/stages/index.js +3 -1
- package/src/stages/plan.js +86 -14
- package/src/stages/scan.js +11 -11
- package/src/stages/status.js +1 -1
- 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-dashboard-design.md +0 -206
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
- package/.sillyspec/changes/dashboard/design.md +0 -219
- 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/plans/2026-04-05-dashboard.md +0 -737
- package/.sillyspec/projects/sillyspec.yaml +0 -3
- package/packages/dashboard/dist/assets/index-CntACGUN.css +0 -1
|
@@ -1,178 +1,152 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
<!--
|
|
4
|
-
<div class="
|
|
5
|
-
<
|
|
6
|
-
|
|
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
|
-
<!--
|
|
11
|
-
<div class="
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 {
|
|
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
|
-
|
|
34
|
+
projects: { type: Array, default: () => [] },
|
|
35
|
+
activeProject: { type: Object, default: null }
|
|
56
36
|
})
|
|
57
37
|
|
|
58
|
-
defineEmits(['
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
90
|
-
|
|
49
|
+
function resetLayout() {
|
|
50
|
+
if (confirm('确定要重置布局吗?')) {
|
|
51
|
+
useLayout().resetLayout()
|
|
52
|
+
}
|
|
91
53
|
}
|
|
92
54
|
</script>
|
|
93
55
|
|
|
94
56
|
<style scoped>
|
|
95
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
95
|
+
|
|
96
|
+
.btn {
|
|
97
|
+
padding: 6px 12px;
|
|
98
|
+
border-radius: 6px;
|
|
99
|
+
border: none;
|
|
114
100
|
cursor: pointer;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
transition: background 0.15s;
|
|
101
|
+
font-size: 13px;
|
|
102
|
+
font-weight: 500;
|
|
103
|
+
transition: all 0.2s;
|
|
119
104
|
}
|
|
120
|
-
|
|
121
|
-
|
|
105
|
+
|
|
106
|
+
.btn:hover {
|
|
107
|
+
opacity: 0.9;
|
|
122
108
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
color: #
|
|
109
|
+
|
|
110
|
+
.btn-secondary {
|
|
111
|
+
background: #E5E7EB;
|
|
112
|
+
color: #374151;
|
|
127
113
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
125
|
+
|
|
126
|
+
.btn-icon:hover {
|
|
127
|
+
background: #E5E7EB;
|
|
135
128
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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', '
|
|
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
|
|
189
|
-
const
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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', '
|
|
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
|
|