sillyspec 3.8.7 → 3.9.1
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-archive/SKILL.md +17 -0
- package/.claude/skills/sillyspec-auto/SKILL.md +78 -0
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +17 -0
- package/{templates/commit.md → .claude/skills/sillyspec-commit/SKILL.md} +32 -47
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -0
- package/.claude/skills/sillyspec-doctor/SKILL.md +27 -0
- package/.claude/skills/sillyspec-execute/SKILL.md +17 -0
- package/.claude/skills/sillyspec-explore/SKILL.md +96 -0
- package/.claude/skills/sillyspec-export/SKILL.md +53 -0
- package/.claude/skills/sillyspec-init/SKILL.md +170 -0
- package/.claude/skills/sillyspec-plan/SKILL.md +52 -0
- package/.claude/skills/sillyspec-propose/SKILL.md +17 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +17 -0
- package/.claude/skills/sillyspec-resume/SKILL.md +111 -0
- package/.claude/skills/sillyspec-scan/SKILL.md +17 -0
- package/.claude/skills/sillyspec-state/SKILL.md +54 -0
- package/.claude/skills/sillyspec-status/SKILL.md +17 -0
- package/.claude/skills/sillyspec-verify/SKILL.md +17 -0
- package/.claude/skills/sillyspec-workspace/SKILL.md +149 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
- package/.sillyspec/changes/auto-mode/design.md +50 -0
- package/.sillyspec/changes/auto-mode/proposal.md +19 -0
- package/.sillyspec/changes/auto-mode/requirements.md +21 -0
- package/.sillyspec/changes/auto-mode/tasks.md +7 -0
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
- package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
- package/.sillyspec/changes/run-command-design/design.md +1230 -0
- package/.sillyspec/changes/unified-docs-design/design.md +199 -0
- package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
- package/.sillyspec/knowledge/INDEX.md +8 -0
- package/.sillyspec/knowledge/uncategorized.md +3 -0
- package/.sillyspec/projects/sillyspec.yaml +3 -0
- package/README.md +12 -5
- package/package.json +9 -11
- package/packages/dashboard/dist/assets/index-CntACGUN.css +1 -0
- package/packages/dashboard/dist/assets/index-RsLVPAy7.js +7446 -0
- package/packages/dashboard/dist/index.html +3 -2
- package/packages/dashboard/package-lock.json +226 -6
- package/packages/dashboard/package.json +8 -5
- package/packages/dashboard/public/logo.jpg +0 -0
- package/packages/dashboard/server/executor.js +1 -1
- package/packages/dashboard/server/index.js +336 -113
- package/packages/dashboard/server/parser.js +333 -29
- package/packages/dashboard/server/watcher.js +203 -131
- package/packages/dashboard/src/App.vue +187 -11
- package/packages/dashboard/src/components/ActionBar.vue +26 -42
- package/packages/dashboard/src/components/CommandPalette.vue +40 -65
- package/packages/dashboard/src/components/DetailPanel.vue +68 -53
- package/packages/dashboard/src/components/DocPreview.vue +160 -0
- package/packages/dashboard/src/components/DocTree.vue +58 -0
- package/packages/dashboard/src/components/LogStream.vue +13 -33
- package/packages/dashboard/src/components/PipelineStage.vue +8 -8
- package/packages/dashboard/src/components/PipelineView.vue +80 -45
- package/packages/dashboard/src/components/ProjectList.vue +103 -45
- package/packages/dashboard/src/components/ProjectOverview.vue +178 -0
- package/packages/dashboard/src/components/StageBadge.vue +13 -13
- package/packages/dashboard/src/components/StepCard.vue +15 -15
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -0
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -0
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -0
- package/packages/dashboard/src/composables/useDashboard.js +20 -6
- package/packages/dashboard/src/composables/useKeyboard.js +6 -4
- package/packages/dashboard/src/main.js +4 -1
- package/packages/dashboard/src/style.css +17 -17
- package/src/index.js +136 -14
- package/src/init.js +83 -228
- package/src/migrate.js +117 -0
- package/src/progress.js +459 -0
- package/src/run.js +624 -0
- package/src/setup.js +2 -72
- package/src/stages/archive.js +54 -0
- package/src/stages/brainstorm.js +239 -0
- package/src/stages/doctor.js +303 -0
- package/src/stages/execute.js +262 -0
- package/src/stages/index.js +26 -0
- package/src/stages/plan.js +282 -0
- package/src/stages/propose.js +115 -0
- package/src/stages/quick.js +64 -0
- package/src/stages/scan.js +141 -0
- package/src/stages/status.js +65 -0
- package/src/stages/verify.js +135 -0
- package/docs/.vitepress/config.mts +0 -45
- package/docs/.vitepress/dist/404.html +0 -25
- package/docs/.vitepress/dist/assets/app.YytxICdd.js +0 -1
- package/docs/.vitepress/dist/assets/chunks/framework.Czhw_PXq.js +0 -19
- package/docs/.vitepress/dist/assets/chunks/theme.DusTRZQk.js +0 -1
- package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.js +0 -1
- package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.lean.js +0 -1
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.js +0 -15
- package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.js +0 -4
- package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.js +0 -4
- package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.js +0 -5
- package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.js +0 -28
- package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.js +0 -30
- package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.lean.js +0 -1
- package/docs/.vitepress/dist/assets/style.DFTx90Kk.css +0 -1
- package/docs/.vitepress/dist/hashmap.json +0 -1
- package/docs/.vitepress/dist/index.html +0 -28
- package/docs/.vitepress/dist/sillyspec/commands.html +0 -42
- package/docs/.vitepress/dist/sillyspec/dashboard.html +0 -31
- package/docs/.vitepress/dist/sillyspec/file-io.html +0 -28
- package/docs/.vitepress/dist/sillyspec/getting-started.html +0 -31
- package/docs/.vitepress/dist/sillyspec/install.html +0 -32
- package/docs/.vitepress/dist/sillyspec/lifecycle.html +0 -55
- package/docs/.vitepress/dist/sillyspec/structure.html +0 -57
- package/docs/.vitepress/dist/vp-icons.css +0 -1
- package/docs/index.md +0 -34
- package/docs/sillyspec/commands.md +0 -218
- package/docs/sillyspec/dashboard.md +0 -51
- package/docs/sillyspec/file-io.md +0 -34
- package/docs/sillyspec/getting-started.md +0 -61
- package/docs/sillyspec/install.md +0 -51
- package/docs/sillyspec/lifecycle.md +0 -146
- package/docs/sillyspec/structure.md +0 -62
- package/packages/dashboard/dist/assets/index-Bh-GPjKY.css +0 -1
- package/packages/dashboard/dist/assets/index-CrCn5Gg6.js +0 -17
- package/templates/archive.md +0 -120
- package/templates/brainstorm.md +0 -170
- package/templates/continue.md +0 -32
- package/templates/execute.md +0 -304
- package/templates/explore.md +0 -59
- package/templates/export.md +0 -21
- package/templates/init.md +0 -61
- package/templates/plan.md +0 -146
- package/templates/quick.md +0 -135
- package/templates/scan-quick.md +0 -49
- package/templates/scan.md +0 -156
- package/templates/skills/playwright-e2e/SKILL.md +0 -340
- package/templates/status.md +0 -75
- package/templates/verify.md +0 -236
- package/templates/workspace-sync.md +0 -99
- package/templates/workspace.md +0 -70
- /package/.sillyspec/{specs → changes/brainstorm-archive}/2026-04-05-dashboard-design.md +0 -0
- /package/{docs/.vitepress/dist/logo.jpg → logo.jpg} +0 -0
- /package/{docs/.vitepress → packages/dashboard}/dist/favicon.jpg +0 -0
- /package/{docs/public → packages/dashboard/dist}/logo.jpg +0 -0
- /package/{docs → packages/dashboard}/public/favicon.jpg +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
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>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="ov-divider" />
|
|
9
|
+
|
|
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>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script setup>
|
|
52
|
+
import { computed } from 'vue'
|
|
53
|
+
|
|
54
|
+
const props = defineProps({
|
|
55
|
+
project: { type: Object, default: null }
|
|
56
|
+
})
|
|
57
|
+
|
|
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
|
+
})
|
|
66
|
+
|
|
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
|
+
})
|
|
76
|
+
|
|
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()}`
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function truncate(s, n) {
|
|
90
|
+
return s.length > n ? s.slice(0, n) + '…' : s
|
|
91
|
+
}
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<style scoped>
|
|
95
|
+
.project-overview {
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
background: #FFFFFF;
|
|
99
|
+
border-bottom: 1px solid #E5E5EA;
|
|
100
|
+
padding: 8px 16px;
|
|
101
|
+
min-height: 48px;
|
|
102
|
+
gap: 8px;
|
|
103
|
+
flex-shrink: 0;
|
|
104
|
+
overflow-x: auto;
|
|
105
|
+
}
|
|
106
|
+
.ov-section {
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
gap: 6px;
|
|
110
|
+
white-space: nowrap;
|
|
111
|
+
flex-shrink: 0;
|
|
112
|
+
}
|
|
113
|
+
.ov-clickable {
|
|
114
|
+
cursor: pointer;
|
|
115
|
+
border-radius: 4px;
|
|
116
|
+
padding: 2px 6px;
|
|
117
|
+
margin: -2px -6px;
|
|
118
|
+
transition: background 0.15s;
|
|
119
|
+
}
|
|
120
|
+
.ov-clickable:hover {
|
|
121
|
+
background: rgba(217,119,6,0.06);
|
|
122
|
+
}
|
|
123
|
+
.ov-name {
|
|
124
|
+
font-weight: 600;
|
|
125
|
+
font-size: 13px;
|
|
126
|
+
color: #1C1C1E;
|
|
127
|
+
}
|
|
128
|
+
.ov-path {
|
|
129
|
+
font-size: 11px;
|
|
130
|
+
color: #636366;
|
|
131
|
+
}
|
|
132
|
+
.ov-label {
|
|
133
|
+
font-size: 11px;
|
|
134
|
+
color: #636366;
|
|
135
|
+
}
|
|
136
|
+
.ov-value {
|
|
137
|
+
font-size: 12px;
|
|
138
|
+
color: #1C1C1E;
|
|
139
|
+
font-weight: 500;
|
|
140
|
+
}
|
|
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;
|
|
148
|
+
}
|
|
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;
|
|
164
|
+
border-radius: 4px;
|
|
165
|
+
}
|
|
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;
|
|
177
|
+
}
|
|
178
|
+
</style>
|
|
@@ -19,19 +19,19 @@ const props = defineProps({
|
|
|
19
19
|
|
|
20
20
|
const displayLabel = computed(() => {
|
|
21
21
|
if (props.label) return props.label
|
|
22
|
-
const labels = { 'completed': 'done', 'in-progress': 'running', 'blocked': 'blocked', 'failed': 'error'
|
|
23
|
-
return labels[props.status] || '
|
|
22
|
+
const labels = { 'completed': 'done', 'in-progress': 'running', 'blocked': 'blocked', 'failed': 'error' }
|
|
23
|
+
return labels[props.status] || ''
|
|
24
24
|
})
|
|
25
25
|
|
|
26
26
|
const sizeClass = computed(() => props.size === 'sm' ? 'px-1.5 py-0.5' : 'px-2 py-1')
|
|
27
27
|
|
|
28
28
|
const badgeStyle = computed(() => {
|
|
29
29
|
const styles = {
|
|
30
|
-
'completed': { background: 'rgba(
|
|
31
|
-
'in-progress': { background: 'rgba(251,191,36,0.1)', color: '#
|
|
32
|
-
'blocked': { background: 'rgba(251,146,60,0.1)', color: '#
|
|
33
|
-
'failed': { background: 'rgba(239,68,68,0.1)', color: '#
|
|
34
|
-
'pending': { background: 'rgba(82,82,82,0.15)', color: '#
|
|
30
|
+
'completed': { background: 'rgba(22,163,74,0.1)', color: '#16A34A' },
|
|
31
|
+
'in-progress': { background: 'rgba(251,191,36,0.1)', color: '#D97706' },
|
|
32
|
+
'blocked': { background: 'rgba(251,146,60,0.1)', color: '#EA580C' },
|
|
33
|
+
'failed': { background: 'rgba(239,68,68,0.1)', color: '#DC2626' },
|
|
34
|
+
'pending': { background: 'rgba(82,82,82,0.15)', color: '#6B7280' }
|
|
35
35
|
}
|
|
36
36
|
return styles[props.status] || styles.pending
|
|
37
37
|
})
|
|
@@ -42,12 +42,12 @@ const dotClass = computed(() => {
|
|
|
42
42
|
|
|
43
43
|
const dotStyle = computed(() => {
|
|
44
44
|
const colors = {
|
|
45
|
-
'completed': '#
|
|
46
|
-
'in-progress': '#
|
|
47
|
-
'blocked': '#
|
|
48
|
-
'failed': '#
|
|
49
|
-
'pending': '#
|
|
45
|
+
'completed': '#16A34A',
|
|
46
|
+
'in-progress': '#D97706',
|
|
47
|
+
'blocked': '#EA580C',
|
|
48
|
+
'failed': '#DC2626',
|
|
49
|
+
'pending': '#6B7280'
|
|
50
50
|
}
|
|
51
|
-
return { background: colors[props.status] || '#
|
|
51
|
+
return { background: colors[props.status] || '#6B7280' }
|
|
52
52
|
})
|
|
53
53
|
</script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
:class="['group relative rounded-md cursor-pointer
|
|
3
|
+
:class="['group relative rounded-md cursor-pointer transition-all duration-200']"
|
|
4
4
|
:style="cardStyle"
|
|
5
5
|
@mouseenter="hovered = true"
|
|
6
6
|
@mouseleave="hovered = false"
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
<!-- Left accent bar -->
|
|
10
10
|
<div class="absolute left-0 top-0 bottom-0 w-[2px]" :style="{ background: barColor }" />
|
|
11
11
|
|
|
12
|
-
<div class="pl-
|
|
12
|
+
<div class="pl-4 pr-4 py-3">
|
|
13
13
|
<div class="flex items-center gap-2">
|
|
14
|
-
<h3 class="text-[12px] font-medium transition-colors duration-150" :style="{ color: isActive ? '#
|
|
14
|
+
<h3 class="text-[12px] font-medium transition-colors duration-150" :style="{ color: isActive ? '#D97706' : '#1C1C1E', fontFamily: '\'JetBrains Mono\', monospace' }">
|
|
15
15
|
{{ step.title || step.name }}
|
|
16
16
|
</h3>
|
|
17
17
|
<StageBadge v-if="step.status" :status="step.status" :label="statusLabel" />
|
|
@@ -20,28 +20,28 @@
|
|
|
20
20
|
<!-- Hover summary -->
|
|
21
21
|
<div
|
|
22
22
|
v-if="step.summary || step.description"
|
|
23
|
-
class="
|
|
24
|
-
:style="{ maxHeight: hovered ? '
|
|
23
|
+
class="transition-all duration-200"
|
|
24
|
+
:style="{ maxHeight: hovered ? '80px' : '0', opacity: hovered ? 1 : 0, overflow: 'hidden' }"
|
|
25
25
|
>
|
|
26
|
-
<p class="mt-1.5 text-[11px] line-clamp-2" style="color: #
|
|
26
|
+
<p class="mt-1.5 text-[11px] line-clamp-2" style="color: #636366;">
|
|
27
27
|
{{ step.summary || step.description }}
|
|
28
28
|
</p>
|
|
29
29
|
</div>
|
|
30
30
|
|
|
31
31
|
<!-- Active details -->
|
|
32
32
|
<div v-if="isActive" class="mt-2 space-y-1">
|
|
33
|
-
<p v-if="step.conclusion" class="text-[11px]" style="color: #
|
|
34
|
-
<span style="color: #
|
|
33
|
+
<p v-if="step.conclusion" class="text-[11px]" style="color: #1C1C1E;">
|
|
34
|
+
<span style="color: #D97706; font-weight: 600;">结论:</span> {{ step.conclusion }}
|
|
35
35
|
</p>
|
|
36
|
-
<p v-if="step.decision" class="text-[11px]" style="color: #
|
|
37
|
-
<span style="color: #
|
|
36
|
+
<p v-if="step.decision" class="text-[11px]" style="color: #1C1C1E;">
|
|
37
|
+
<span style="color: #D97706; font-weight: 600;">决策:</span> {{ step.decision }}
|
|
38
38
|
</p>
|
|
39
|
-
<p v-if="step.userQuery" class="text-[11px] italic" style="color: #
|
|
39
|
+
<p v-if="step.userQuery" class="text-[11px] italic" style="color: #636366;">
|
|
40
40
|
"{{ step.userQuery }}"
|
|
41
41
|
</p>
|
|
42
42
|
</div>
|
|
43
43
|
|
|
44
|
-
<div v-if="step.duration" class="mt-1 text-[10px] font-mono-log" style="color: #
|
|
44
|
+
<div v-if="step.duration" class="mt-1 text-[10px] font-mono-log" style="color: #6B7280;">
|
|
45
45
|
{{ step.duration }}
|
|
46
46
|
</div>
|
|
47
47
|
</div>
|
|
@@ -62,13 +62,13 @@ const emit = defineEmits(['select'])
|
|
|
62
62
|
const hovered = ref(false)
|
|
63
63
|
|
|
64
64
|
const barColor = computed(() => {
|
|
65
|
-
const colors = { 'completed': '#
|
|
65
|
+
const colors = { 'completed': '#16A34A', 'in-progress': '#D97706', 'blocked': '#EA580C', 'failed': '#DC2626', 'pending': '#E5E5EA' }
|
|
66
66
|
return colors[props.step.status] || colors.pending
|
|
67
67
|
})
|
|
68
68
|
|
|
69
69
|
const cardStyle = computed(() => ({
|
|
70
|
-
background: props.isActive ? 'rgba(
|
|
71
|
-
border: `1px solid ${props.isActive ? 'rgba(251,191,36,0.2)' : '#
|
|
70
|
+
background: props.isActive ? 'rgba(217,119,6,0.08)' : (hovered.value ? 'rgba(0,0,0,0.02)' : '#FFFFFF'),
|
|
71
|
+
border: `1px solid ${props.isActive ? 'rgba(251,191,36,0.2)' : '#F0F0F3'}`,
|
|
72
72
|
}))
|
|
73
73
|
|
|
74
74
|
const statusLabel = computed(() => {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="data?.length" class="px-4 py-3">
|
|
3
|
+
<div v-for="group in data" :key="group.key" class="mb-4">
|
|
4
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">
|
|
5
|
+
{{ group.icon }} {{ group.label }} ({{ group.files.length }})
|
|
6
|
+
</h4>
|
|
7
|
+
<div class="space-y-1">
|
|
8
|
+
<div
|
|
9
|
+
v-for="f in group.files"
|
|
10
|
+
:key="f.path"
|
|
11
|
+
class="flex items-center gap-2 px-2 py-1.5 rounded text-[11px] cursor-pointer hover:bg-[#FEF3C7] transition-colors"
|
|
12
|
+
@click="$emit('open-file', f)"
|
|
13
|
+
>
|
|
14
|
+
<svg class="w-3 h-3 flex-shrink-0" fill="none" stroke="#9CA3AF" viewBox="0 0 24 24">
|
|
15
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
16
|
+
</svg>
|
|
17
|
+
<span class="truncate" style="color: #1C1C1E;">{{ f.name }}</span>
|
|
18
|
+
<span class="ml-auto flex-shrink-0 font-mono text-[10px]" style="color: #9CA3AF;">{{ formatSize(f.size) }}</span>
|
|
19
|
+
<span class="flex-shrink-0 text-[10px]" style="color: #9CA3AF;">{{ relativeTime(f.mtime) }}</span>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<n-empty v-else description="无文档" style="padding: 48px 0;" />
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup>
|
|
28
|
+
defineProps({ data: { type: Array, default: () => [] } })
|
|
29
|
+
defineEmits(['open-file'])
|
|
30
|
+
|
|
31
|
+
function formatSize(bytes) {
|
|
32
|
+
if (!bytes) return '—'
|
|
33
|
+
if (bytes < 1024) return bytes + ' B'
|
|
34
|
+
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'
|
|
35
|
+
return (bytes / 1048576).toFixed(1) + ' MB'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function relativeTime(iso) {
|
|
39
|
+
if (!iso) return ''
|
|
40
|
+
const d = new Date(iso)
|
|
41
|
+
const diff = Date.now() - d.getTime()
|
|
42
|
+
if (diff < 60000) return '刚刚'
|
|
43
|
+
if (diff < 3600000) return `${Math.floor(diff / 60000)} 分钟前`
|
|
44
|
+
if (diff < 86400000) return `${Math.floor(diff / 3600000)} 小时前`
|
|
45
|
+
if (diff < 604800000) return `${Math.floor(diff / 86400000)} 天前`
|
|
46
|
+
return `${d.getMonth() + 1}/${d.getDate()}`
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="data" class="px-4 py-3">
|
|
3
|
+
<!-- Branch -->
|
|
4
|
+
<div class="mb-4">
|
|
5
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">当前分支</h4>
|
|
6
|
+
<div class="text-[18px] font-bold font-[JetBrains_Mono,monospace]" style="color: #D97706;">{{ data.branch || '—' }}</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<!-- Commits -->
|
|
10
|
+
<div v-if="data.commits?.length" class="mb-4">
|
|
11
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">最近提交</h4>
|
|
12
|
+
<div class="space-y-2">
|
|
13
|
+
<div v-for="c in data.commits" :key="c.hash" class="px-3 py-2 rounded-md" style="background: #F5F5F7;">
|
|
14
|
+
<div class="flex items-center gap-2">
|
|
15
|
+
<span class="text-[11px] font-mono font-semibold" style="color: #D97706;">{{ c.hash }}</span>
|
|
16
|
+
<span class="text-[11px] flex-1 truncate" style="color: #1C1C1E;">{{ c.message }}</span>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="flex items-center gap-2 mt-1 text-[10px]" style="color: #6B7280;">
|
|
19
|
+
<span>{{ c.author }}</span>
|
|
20
|
+
<span>·</span>
|
|
21
|
+
<span>{{ relativeTime(c.date) }}</span>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- Untracked -->
|
|
28
|
+
<div v-if="data.untracked?.length">
|
|
29
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">未提交文件</h4>
|
|
30
|
+
<div class="space-y-1">
|
|
31
|
+
<div v-for="f in data.untracked" :key="f.file" class="flex items-center gap-2 px-2 py-1 rounded text-[11px] font-[JetBrains_Mono,monospace]">
|
|
32
|
+
<span class="font-semibold w-5 text-center" :style="{ color: statusColor(f.status) }">{{ f.status }}</span>
|
|
33
|
+
<span class="truncate" style="color: #1C1C1E;">{{ f.file }}</span>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<n-empty v-else description="无 Git 信息" style="padding: 48px 0;" />
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script setup>
|
|
42
|
+
defineProps({ data: { type: Object, default: null } })
|
|
43
|
+
|
|
44
|
+
function statusColor(s) {
|
|
45
|
+
if (s.includes('M')) return '#F59E0B'
|
|
46
|
+
if (s.includes('A') || s === '?') return '#10B981'
|
|
47
|
+
if (s.includes('D')) return '#EF4444'
|
|
48
|
+
return '#9CA3AF'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function relativeTime(iso) {
|
|
52
|
+
if (!iso) return ''
|
|
53
|
+
const d = new Date(iso)
|
|
54
|
+
const diff = Date.now() - d.getTime()
|
|
55
|
+
if (diff < 60000) return '刚刚'
|
|
56
|
+
if (diff < 3600000) return `${Math.floor(diff / 60000)} 分钟前`
|
|
57
|
+
if (diff < 86400000) return `${Math.floor(diff / 3600000)} 小时前`
|
|
58
|
+
if (diff < 604800000) return `${Math.floor(diff / 86400000)} 天前`
|
|
59
|
+
return `${d.getMonth() + 1}/${d.getDate()}`
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="data" class="px-4 py-3">
|
|
3
|
+
<!-- Frameworks -->
|
|
4
|
+
<div v-if="data.frameworks?.length" class="mb-4">
|
|
5
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">识别框架</h4>
|
|
6
|
+
<div class="flex flex-wrap gap-2">
|
|
7
|
+
<n-tag v-for="f in data.frameworks" :key="f" type="warning" size="small" round>{{ f }}</n-tag>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<!-- Dependencies -->
|
|
12
|
+
<div v-if="depEntries.length" class="mb-4">
|
|
13
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">dependencies ({{ depEntries.length }})</h4>
|
|
14
|
+
<div class="space-y-1">
|
|
15
|
+
<div v-for="[name, ver] in depEntries" :key="name" class="flex items-center gap-2 px-2 py-1 rounded text-[11px]" style="background: #F5F5F7;">
|
|
16
|
+
<span class="font-mono font-medium truncate" style="color: #1C1C1E;">{{ name }}</span>
|
|
17
|
+
<span class="font-mono ml-auto flex-shrink-0" style="color: #9CA3AF;">{{ ver }}</span>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<!-- Dev Dependencies -->
|
|
23
|
+
<div v-if="devDepEntries.length">
|
|
24
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">devDependencies ({{ devDepEntries.length }})</h4>
|
|
25
|
+
<div class="space-y-1">
|
|
26
|
+
<div v-for="[name, ver] in devDepEntries" :key="name" class="flex items-center gap-2 px-2 py-1 rounded text-[11px]" style="background: #F5F5F7;">
|
|
27
|
+
<span class="font-mono font-medium truncate" style="color: #1C1C1E;">{{ name }}</span>
|
|
28
|
+
<span class="font-mono ml-auto flex-shrink-0" style="color: #9CA3AF;">{{ ver }}</span>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<n-empty v-else description="无依赖信息" style="padding: 48px 0;" />
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script setup>
|
|
37
|
+
import { computed } from 'vue'
|
|
38
|
+
|
|
39
|
+
const props = defineProps({ data: { type: Object, default: null } })
|
|
40
|
+
|
|
41
|
+
const depEntries = computed(() => props.data ? Object.entries(props.data.dependencies || {}) : [])
|
|
42
|
+
const devDepEntries = computed(() => props.data ? Object.entries(props.data.devDependencies || {}) : [])
|
|
43
|
+
</script>
|
|
@@ -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
|
/**
|
|
@@ -21,13 +26,13 @@ export function useDashboard() {
|
|
|
21
26
|
* @param {string} name - Project name
|
|
22
27
|
* @returns {object|null} Project or null
|
|
23
28
|
*/
|
|
24
|
-
function getProject(
|
|
25
|
-
return state.projects.find(p => p.
|
|
29
|
+
function getProject(path) {
|
|
30
|
+
return state.projects.find(p => p.path === path) || null
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
/**
|
|
29
34
|
* Select a project as active
|
|
30
|
-
* @param {object|string} project - Project object or
|
|
35
|
+
* @param {object|string} project - Project object or path
|
|
31
36
|
*/
|
|
32
37
|
function selectProject(project) {
|
|
33
38
|
const proj = typeof project === 'string'
|
|
@@ -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) {
|
|
@@ -115,7 +122,7 @@ export function useDashboard() {
|
|
|
115
122
|
|
|
116
123
|
// Restore active project if it still exists
|
|
117
124
|
if (state.activeProject) {
|
|
118
|
-
const updated = getProject(state.activeProject.
|
|
125
|
+
const updated = getProject(state.activeProject.path)
|
|
119
126
|
if (updated) {
|
|
120
127
|
state.activeProject = updated
|
|
121
128
|
}
|
|
@@ -141,6 +148,7 @@ export function useDashboard() {
|
|
|
141
148
|
|
|
142
149
|
// Computed properties
|
|
143
150
|
const activeProjectName = computed(() => state.activeProject?.name || null)
|
|
151
|
+
const activeProjectPath = computed(() => state.activeProject?.path || null)
|
|
144
152
|
const activeProjectStage = computed(() => state.activeProject?.state?.currentStage || null)
|
|
145
153
|
const hasProjects = computed(() => state.projects.length > 0)
|
|
146
154
|
const activeProjectSteps = computed(() => {
|
|
@@ -164,8 +172,14 @@ export function useDashboard() {
|
|
|
164
172
|
setExecuting,
|
|
165
173
|
isExecuting,
|
|
166
174
|
activeProjectName,
|
|
175
|
+
activeProjectPath,
|
|
167
176
|
activeProjectStage,
|
|
168
177
|
hasProjects,
|
|
169
|
-
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 }
|
|
170
184
|
}
|
|
171
185
|
}
|
|
@@ -14,15 +14,17 @@ export function useKeyboard(options = {}) {
|
|
|
14
14
|
onEnter = null,
|
|
15
15
|
onArrowUp = null,
|
|
16
16
|
onArrowDown = null,
|
|
17
|
-
disabled = false
|
|
17
|
+
disabled: initialDisabled = false
|
|
18
18
|
} = options
|
|
19
19
|
|
|
20
|
+
let isDisabled = initialDisabled
|
|
21
|
+
|
|
20
22
|
/**
|
|
21
23
|
* Handle keyboard events
|
|
22
24
|
* @param {KeyboardEvent} event
|
|
23
25
|
*/
|
|
24
26
|
function handleKeyDown(event) {
|
|
25
|
-
if (
|
|
27
|
+
if (isDisabled) return
|
|
26
28
|
|
|
27
29
|
// Ignore if in input field
|
|
28
30
|
const target = event.target
|
|
@@ -87,8 +89,8 @@ export function useKeyboard(options = {}) {
|
|
|
87
89
|
})
|
|
88
90
|
|
|
89
91
|
return {
|
|
90
|
-
disable: () => {
|
|
91
|
-
enable: () => {
|
|
92
|
+
disable: () => { isDisabled = true },
|
|
93
|
+
enable: () => { isDisabled = false }
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
|
|
3
3
|
@theme {
|
|
4
|
-
--color-bg: #
|
|
5
|
-
--color-primary: #
|
|
6
|
-
--color-primary-dim: #
|
|
7
|
-
--color-warning: #
|
|
8
|
-
--color-danger: #
|
|
9
|
-
--color-muted: #
|
|
10
|
-
--color-surface: #
|
|
11
|
-
--color-surface-2: #
|
|
12
|
-
--color-border: #
|
|
13
|
-
--color-border-light: #
|
|
14
|
-
--color-text: #
|
|
15
|
-
--color-text-secondary: #
|
|
16
|
-
--color-info: #
|
|
17
|
-
--color-success: #
|
|
4
|
+
--color-bg: #F5F5F7;
|
|
5
|
+
--color-primary: #D97706;
|
|
6
|
+
--color-primary-dim: #92400E;
|
|
7
|
+
--color-warning: #EA580C;
|
|
8
|
+
--color-danger: #DC2626;
|
|
9
|
+
--color-muted: #6B7280;
|
|
10
|
+
--color-surface: #FFFFFF;
|
|
11
|
+
--color-surface-2: #F0F0F3;
|
|
12
|
+
--color-border: #E5E5EA;
|
|
13
|
+
--color-border-light: #D1D1D6;
|
|
14
|
+
--color-text: #1C1C1E;
|
|
15
|
+
--color-text-secondary: #636366;
|
|
16
|
+
--color-info: #2563EB;
|
|
17
|
+
--color-success: #16A34A;
|
|
18
18
|
|
|
19
19
|
--font-display: 'JetBrains Mono', monospace;
|
|
20
20
|
--font-body: 'DM Sans', sans-serif;
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
|
|
97
97
|
/* Skeleton shimmer */
|
|
98
98
|
.skeleton-shimmer {
|
|
99
|
-
background: linear-gradient(90deg, #
|
|
99
|
+
background: linear-gradient(90deg, #1A1E28 25%, #2A3040 50%, #1A1E28 75%);
|
|
100
100
|
background-size: 200% 100%;
|
|
101
101
|
animation: shimmer 1.5s ease-in-out infinite;
|
|
102
102
|
}
|
|
@@ -112,12 +112,12 @@
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
::-webkit-scrollbar-thumb {
|
|
115
|
-
background: #
|
|
115
|
+
background: #2A3040;
|
|
116
116
|
border-radius: 10px;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
::-webkit-scrollbar-thumb:hover {
|
|
120
|
-
background: #
|
|
120
|
+
background: #3A4555;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
* {
|