sillyspec 3.9.0 → 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 (206) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +17 -0
  2. package/.claude/skills/sillyspec-auto/SKILL.md +78 -0
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +17 -0
  4. package/{templates/commit.md → .claude/skills/sillyspec-commit/SKILL.md} +32 -47
  5. package/.claude/skills/sillyspec-continue/SKILL.md +45 -0
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +27 -0
  7. package/.claude/skills/sillyspec-execute/SKILL.md +17 -0
  8. package/.claude/skills/sillyspec-explore/SKILL.md +105 -0
  9. package/.claude/skills/sillyspec-export/SKILL.md +53 -0
  10. package/.claude/skills/sillyspec-init/SKILL.md +170 -0
  11. package/.claude/skills/sillyspec-plan/SKILL.md +17 -0
  12. package/.claude/skills/sillyspec-propose/SKILL.md +17 -0
  13. package/.claude/skills/sillyspec-quick/SKILL.md +17 -0
  14. package/.claude/skills/sillyspec-resume/SKILL.md +111 -0
  15. package/.claude/skills/sillyspec-scan/SKILL.md +17 -0
  16. package/.claude/skills/sillyspec-state/SKILL.md +54 -0
  17. package/.claude/skills/sillyspec-status/SKILL.md +17 -0
  18. package/.claude/skills/sillyspec-verify/SKILL.md +17 -0
  19. package/.claude/skills/sillyspec-workspace/SKILL.md +149 -0
  20. package/README.md +19 -11
  21. package/SKILL.md +15 -10
  22. package/package.json +7 -9
  23. package/packages/dashboard/dist/assets/index-BcM2J-hv.css +1 -0
  24. package/packages/dashboard/dist/assets/index-DpLHK4jv.js +7446 -0
  25. package/packages/dashboard/dist/index.html +16 -16
  26. package/packages/dashboard/dist/prototype-dashboard.html +836 -0
  27. package/packages/dashboard/dist/prototype-overview.html +256 -0
  28. package/packages/dashboard/package-lock.json +226 -6
  29. package/packages/dashboard/package.json +8 -5
  30. package/packages/dashboard/public/logo.jpg +0 -0
  31. package/packages/dashboard/public/prototype-dashboard.html +836 -0
  32. package/packages/dashboard/public/prototype-overview.html +256 -0
  33. package/packages/dashboard/server/executor.js +1 -1
  34. package/packages/dashboard/server/index.js +341 -113
  35. package/packages/dashboard/server/parser.js +442 -30
  36. package/packages/dashboard/server/watcher.js +214 -134
  37. package/packages/dashboard/src/App.vue +475 -71
  38. package/packages/dashboard/src/components/ActionBar.vue +36 -43
  39. package/packages/dashboard/src/components/CommandPalette.vue +45 -66
  40. package/packages/dashboard/src/components/DetailPanel.vue +68 -53
  41. package/packages/dashboard/src/components/DocPreview.vue +257 -0
  42. package/packages/dashboard/src/components/DocTree.vue +114 -0
  43. package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
  44. package/packages/dashboard/src/components/LogStream.vue +13 -33
  45. package/packages/dashboard/src/components/PipelineStage.vue +8 -8
  46. package/packages/dashboard/src/components/PipelineView.vue +99 -45
  47. package/packages/dashboard/src/components/ProjectCard.vue +187 -0
  48. package/packages/dashboard/src/components/ProjectList.vue +103 -45
  49. package/packages/dashboard/src/components/ProjectOverview.vue +152 -0
  50. package/packages/dashboard/src/components/StageBadge.vue +13 -13
  51. package/packages/dashboard/src/components/StepCard.vue +15 -15
  52. package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
  53. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -0
  54. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -0
  55. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -0
  56. package/packages/dashboard/src/composables/useDashboard.js +48 -6
  57. package/packages/dashboard/src/composables/useKeyboard.js +6 -4
  58. package/packages/dashboard/src/composables/useLayout.js +131 -0
  59. package/packages/dashboard/src/main.js +4 -1
  60. package/packages/dashboard/src/style.css +17 -17
  61. package/src/index.js +141 -22
  62. package/src/init.js +93 -231
  63. package/src/migrate.js +117 -0
  64. package/src/progress.js +460 -0
  65. package/src/run.js +635 -0
  66. package/src/setup.js +2 -72
  67. package/src/stages/archive.js +54 -0
  68. package/src/stages/brainstorm.js +264 -0
  69. package/src/stages/doctor.js +303 -0
  70. package/src/stages/execute.js +287 -0
  71. package/src/stages/explore.js +34 -0
  72. package/src/stages/index.js +28 -0
  73. package/src/stages/plan.js +354 -0
  74. package/src/stages/propose.js +115 -0
  75. package/src/stages/quick.js +64 -0
  76. package/src/stages/scan.js +141 -0
  77. package/src/stages/status.js +65 -0
  78. package/src/stages/verify.js +135 -0
  79. package/.sillyspec/changes/dashboard/design.md +0 -219
  80. package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
  81. package/.sillyspec/specs/2026-04-05-dashboard-design.md +0 -206
  82. package/dist/steps/brainstorm/01-load-context.md +0 -30
  83. package/dist/steps/brainstorm/02-reuse-check.md +0 -6
  84. package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
  85. package/dist/steps/brainstorm/04-module-split.md +0 -23
  86. package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
  87. package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
  88. package/dist/steps/brainstorm/07-present-design.md +0 -3
  89. package/dist/steps/brainstorm/08-write-design.md +0 -21
  90. package/dist/steps/brainstorm/09-self-review.md +0 -15
  91. package/dist/steps/brainstorm/10-user-confirm.md +0 -3
  92. package/dist/steps/brainstorm/11-output-spec.md +0 -7
  93. package/dist/steps/brainstorm/manifest.yaml +0 -26
  94. package/dist/steps/execute/01-load-context.md +0 -41
  95. package/dist/steps/execute/02-scan-conventions.md +0 -47
  96. package/dist/steps/execute/03-skill-mcp.md +0 -19
  97. package/dist/steps/execute/04-assign-task.md +0 -22
  98. package/dist/steps/execute/04b-prompt-template.md +0 -54
  99. package/dist/steps/execute/05-write-test.md +0 -7
  100. package/dist/steps/execute/06-write-code.md +0 -8
  101. package/dist/steps/execute/07-run-test.md +0 -26
  102. package/dist/steps/execute/08-fix-issues.md +0 -28
  103. package/dist/steps/execute/09-next-task.md +0 -33
  104. package/dist/steps/execute/manifest.yaml +0 -28
  105. package/dist/steps/plan/01-load-context.md +0 -22
  106. package/dist/steps/plan/02-anchor-confirm.md +0 -1
  107. package/dist/steps/plan/03-expand-tasks.md +0 -33
  108. package/dist/steps/plan/04-mark-order.md +0 -15
  109. package/dist/steps/plan/05-e2e-planning.md +0 -17
  110. package/dist/steps/plan/06-self-check.md +0 -16
  111. package/dist/steps/plan/07-save.md +0 -1
  112. package/dist/steps/plan/manifest.yaml +0 -18
  113. package/dist/steps/scan/01-env-detect.md +0 -51
  114. package/dist/steps/scan/02-tech-stack.md +0 -16
  115. package/dist/steps/scan/03-conventions.md +0 -16
  116. package/dist/steps/scan/04-structure.md +0 -19
  117. package/dist/steps/scan/05-quality.md +0 -18
  118. package/dist/steps/scan/06-complete.md +0 -49
  119. package/dist/steps/scan/manifest.yaml +0 -16
  120. package/dist/steps/verify/01-load-specs.md +0 -28
  121. package/dist/steps/verify/02-check-tasks.md +0 -1
  122. package/dist/steps/verify/03-check-design.md +0 -6
  123. package/dist/steps/verify/04-run-tests.md +0 -7
  124. package/dist/steps/verify/05-e2e-tests.md +0 -27
  125. package/dist/steps/verify/05b-e2e-fix.md +0 -33
  126. package/dist/steps/verify/06-code-quality.md +0 -25
  127. package/dist/steps/verify/07-lint-check.md +0 -27
  128. package/dist/steps/verify/08-output-report.md +0 -14
  129. package/dist/steps/verify/manifest.yaml +0 -22
  130. package/docs/.vitepress/config.mts +0 -45
  131. package/docs/.vitepress/dist/404.html +0 -25
  132. package/docs/.vitepress/dist/assets/app.YytxICdd.js +0 -1
  133. package/docs/.vitepress/dist/assets/chunks/framework.Czhw_PXq.js +0 -19
  134. package/docs/.vitepress/dist/assets/chunks/theme.DusTRZQk.js +0 -1
  135. package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.js +0 -1
  136. package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.lean.js +0 -1
  137. package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  138. package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  139. package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  140. package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  141. package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  142. package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  143. package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  144. package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  145. package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  146. package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  147. package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  148. package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  149. package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  150. package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  151. package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.js +0 -15
  152. package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.lean.js +0 -1
  153. package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.js +0 -4
  154. package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.lean.js +0 -1
  155. package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.js +0 -1
  156. package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.lean.js +0 -1
  157. package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.js +0 -4
  158. package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.lean.js +0 -1
  159. package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.js +0 -5
  160. package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.lean.js +0 -1
  161. package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.js +0 -28
  162. package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.lean.js +0 -1
  163. package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.js +0 -30
  164. package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.lean.js +0 -1
  165. package/docs/.vitepress/dist/assets/style.DFTx90Kk.css +0 -1
  166. package/docs/.vitepress/dist/hashmap.json +0 -1
  167. package/docs/.vitepress/dist/index.html +0 -28
  168. package/docs/.vitepress/dist/sillyspec/commands.html +0 -42
  169. package/docs/.vitepress/dist/sillyspec/dashboard.html +0 -31
  170. package/docs/.vitepress/dist/sillyspec/file-io.html +0 -28
  171. package/docs/.vitepress/dist/sillyspec/getting-started.html +0 -31
  172. package/docs/.vitepress/dist/sillyspec/install.html +0 -32
  173. package/docs/.vitepress/dist/sillyspec/lifecycle.html +0 -55
  174. package/docs/.vitepress/dist/sillyspec/structure.html +0 -57
  175. package/docs/.vitepress/dist/vp-icons.css +0 -1
  176. package/docs/index.md +0 -34
  177. package/docs/sillyspec/commands.md +0 -218
  178. package/docs/sillyspec/dashboard.md +0 -51
  179. package/docs/sillyspec/file-io.md +0 -34
  180. package/docs/sillyspec/getting-started.md +0 -61
  181. package/docs/sillyspec/install.md +0 -51
  182. package/docs/sillyspec/lifecycle.md +0 -146
  183. package/docs/sillyspec/structure.md +0 -62
  184. package/packages/dashboard/dist/assets/index-Bh-GPjKY.css +0 -1
  185. package/packages/dashboard/dist/assets/index-CrCn5Gg6.js +0 -17
  186. package/src/step.js +0 -543
  187. package/templates/archive.md +0 -120
  188. package/templates/brainstorm.md +0 -170
  189. package/templates/continue.md +0 -32
  190. package/templates/execute.md +0 -304
  191. package/templates/explore.md +0 -59
  192. package/templates/export.md +0 -21
  193. package/templates/init.md +0 -61
  194. package/templates/plan.md +0 -146
  195. package/templates/quick.md +0 -135
  196. package/templates/scan-quick.md +0 -49
  197. package/templates/scan.md +0 -156
  198. package/templates/skills/playwright-e2e/SKILL.md +0 -340
  199. package/templates/status.md +0 -75
  200. package/templates/verify.md +0 -236
  201. package/templates/workspace-sync.md +0 -99
  202. package/templates/workspace.md +0 -70
  203. /package/{docs/.vitepress/dist/logo.jpg → logo.jpg} +0 -0
  204. /package/{docs/.vitepress → packages/dashboard}/dist/favicon.jpg +0 -0
  205. /package/{docs/public → packages/dashboard/dist}/logo.jpg +0 -0
  206. /package/{docs → packages/dashboard}/public/favicon.jpg +0 -0
@@ -0,0 +1,152 @@
1
+ <template>
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>
13
+ </div>
14
+
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
+ />
24
+ </div>
25
+ </div>
26
+ </template>
27
+
28
+ <script setup>
29
+ import { defineProps, defineEmits } from 'vue'
30
+ import ProjectCard from './ProjectCard.vue'
31
+ import { useLayout } from '../composables/useLayout.js'
32
+
33
+ const props = defineProps({
34
+ projects: { type: Array, default: () => [] },
35
+ activeProject: { type: Object, default: null }
36
+ })
37
+
38
+ const emit = defineEmits(['select'])
39
+
40
+ function handleSelect(project) {
41
+ emit('select', project)
42
+ }
43
+
44
+ function refresh() {
45
+ // TODO: 实现刷新逻辑
46
+ window.location.reload()
47
+ }
48
+
49
+ function resetLayout() {
50
+ if (confirm('确定要重置布局吗?')) {
51
+ useLayout().resetLayout()
52
+ }
53
+ }
54
+ </script>
55
+
56
+ <style scoped>
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;
76
+ display: flex;
77
+ align-items: center;
78
+ gap: 8px;
79
+ }
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 {
91
+ display: flex;
92
+ gap: 8px;
93
+ align-items: center;
94
+ }
95
+
96
+ .btn {
97
+ padding: 6px 12px;
98
+ border-radius: 6px;
99
+ border: none;
100
+ cursor: pointer;
101
+ font-size: 13px;
102
+ font-weight: 500;
103
+ transition: all 0.2s;
104
+ }
105
+
106
+ .btn:hover {
107
+ opacity: 0.9;
108
+ }
109
+
110
+ .btn-secondary {
111
+ background: #E5E7EB;
112
+ color: #374151;
113
+ }
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;
124
+ }
125
+
126
+ .btn-icon:hover {
127
+ background: #E5E7EB;
128
+ }
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;
137
+ }
138
+
139
+ .cards-container::-webkit-scrollbar {
140
+ height: 8px;
141
+ }
142
+
143
+ .cards-container::-webkit-scrollbar-track {
144
+ background: #F3F4F6;
145
+ border-radius: 4px;
146
+ }
147
+
148
+ .cards-container::-webkit-scrollbar-thumb {
149
+ background: #D1D5DB;
150
+ border-radius: 4px;
151
+ }
152
+ </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', 'pending': 'idle' }
23
- return labels[props.status] || 'idle'
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(52,211,153,0.1)', color: '#34D399' },
31
- 'in-progress': { background: 'rgba(251,191,36,0.1)', color: '#FBBF24' },
32
- 'blocked': { background: 'rgba(251,146,60,0.1)', color: '#FB923C' },
33
- 'failed': { background: 'rgba(239,68,68,0.1)', color: '#EF4444' },
34
- 'pending': { background: 'rgba(82,82,82,0.15)', color: '#525252' }
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': '#34D399',
46
- 'in-progress': '#FBBF24',
47
- 'blocked': '#FB923C',
48
- 'failed': '#EF4444',
49
- 'pending': '#525252'
45
+ 'completed': '#16A34A',
46
+ 'in-progress': '#D97706',
47
+ 'blocked': '#EA580C',
48
+ 'failed': '#DC2626',
49
+ 'pending': '#6B7280'
50
50
  }
51
- return { background: colors[props.status] || '#525252' }
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 overflow-hidden transition-all duration-200']"
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-3.5 pr-3 py-2.5">
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 ? '#FBBF24' : '#E4E4E7', fontFamily: '\'JetBrains Mono\', monospace' }">
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="overflow-hidden transition-all duration-200"
24
- :style="{ maxHeight: hovered ? '60px' : '0', opacity: hovered ? 1 : 0 }"
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: #8B8B8E;">
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: #E4E4E7;">
34
- <span style="color: #FBBF24; font-weight: 600;">结论:</span> {{ step.conclusion }}
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: #E4E4E7;">
37
- <span style="color: #FBBF24; font-weight: 600;">决策:</span> {{ step.decision }}
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: #8B8B8E;">
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: #525252;">
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': '#34D399', 'in-progress': '#FBBF24', 'blocked': '#FB923C', 'failed': '#EF4444', 'pending': '#2A2A2D' }
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(251,191,36,0.04)' : (hovered.value ? 'rgba(255,255,255,0.015)' : '#141416'),
71
- border: `1px solid ${props.isActive ? 'rgba(251,191,36,0.2)' : '#1F1F22'}`,
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,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>
@@ -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(name) {
25
- return state.projects.find(p => p.name === name) || null
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 name
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,11 +122,38 @@ 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.name)
125
+ const updated = getProject(state.activeProject.path)
119
126
  if (updated) {
120
127
  state.activeProject = updated
128
+ return
121
129
  }
122
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
+ }
123
157
  }
124
158
 
125
159
  /**
@@ -141,6 +175,7 @@ export function useDashboard() {
141
175
 
142
176
  // Computed properties
143
177
  const activeProjectName = computed(() => state.activeProject?.name || null)
178
+ const activeProjectPath = computed(() => state.activeProject?.path || null)
144
179
  const activeProjectStage = computed(() => state.activeProject?.state?.currentStage || null)
145
180
  const hasProjects = computed(() => state.projects.length > 0)
146
181
  const activeProjectSteps = computed(() => {
@@ -161,11 +196,18 @@ export function useDashboard() {
161
196
  openPanel,
162
197
  closePanel,
163
198
  updateProjects,
199
+ updateProject,
164
200
  setExecuting,
165
201
  isExecuting,
166
202
  activeProjectName,
203
+ activeProjectPath,
167
204
  activeProjectStage,
168
205
  hasProjects,
169
- activeProjectSteps
206
+ activeProjectSteps,
207
+ setActiveTab(tab) { state.activeTab = tab },
208
+ updateDocs(docs) { state.docs = docs },
209
+ selectDocFile(file) { state.selectedDocFile = file },
210
+ setDocContent(content) { state.docContent = content },
211
+ setDocLoading(loading) { state.docLoading = loading }
170
212
  }
171
213
  }
@@ -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 (disabled) return
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: () => { disabled = true },
91
- enable: () => { disabled = false }
92
+ disable: () => { isDisabled = true },
93
+ enable: () => { isDisabled = false }
92
94
  }
93
95
  }
94
96