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
@@ -1,53 +1,77 @@
1
1
  <template>
2
- <div class="flex flex-col h-full" style="background: #0E0E10;">
3
- <!-- Header -->
4
- <div class="px-6 pt-6 pb-4" style="border-bottom: 1px solid #1F1F22;">
5
- <h2 class="text-[11px] font-semibold uppercase tracking-[0.2em] font-[JetBrains_Mono,monospace]" style="color: #525252;">
6
- Pipeline
7
- </h2>
8
- <p v-if="project" class="text-[12px] mt-1.5 font-[JetBrains_Mono,monospace]" style="color: #8B8B8E;">
9
- {{ project.name }} <span style="color: #2A2A2D;">/</span> <span style="color: #FBBF24;">{{ currentStage }}</span>
10
- </p>
2
+ <div class="flex flex-col h-full" style="background: #F5F5F7;">
3
+ <!-- Header with Tabs -->
4
+ <div class="px-6 pt-4 pb-0" style="border-bottom: 1px solid #F0F0F3;">
5
+ <div class="flex items-center gap-6">
6
+ <h2 class="text-[11px] font-semibold uppercase tracking-[0.2em] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">
7
+ {{ project?.name || '项目' }}
8
+ </h2>
9
+ <n-tabs :value="activeTab" type="segment" size="small" @update:value="$emit('switch-tab', $event)" style="max-width: 200px;">
10
+ <n-tab name="pipeline">流水线</n-tab>
11
+ <n-tab name="docs">文档</n-tab>
12
+ </n-tabs>
13
+ </div>
11
14
  </div>
12
15
 
13
- <!-- Stages -->
14
- <div class="flex-1 overflow-y-auto px-6 py-5">
16
+ <!-- Pipeline Tab -->
17
+ <div v-if="activeTab === 'pipeline'" class="flex flex-col flex-1 overflow-hidden">
18
+ <div v-if="project && currentStage" class="px-6 pt-4 pb-2">
19
+ <p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #636366;">
20
+ <span style="color: #D97706;">{{ currentStage }}</span>
21
+ </p>
22
+ </div>
23
+
15
24
  <!-- Empty state -->
16
- <div v-if="!project || !project.state" class="flex items-center justify-center h-full">
17
- <div class="text-center">
18
- <div class="w-14 h-14 mx-auto mb-4 rounded-md flex items-center justify-center" style="border: 1px dashed #2A2A2D; transform: rotate(45deg);">
19
- <svg class="w-5 h-5" style="color: #525252; transform: rotate(-45deg);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
20
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
21
- </svg>
22
- </div>
23
- <p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #525252;">Select a project</p>
24
- </div>
25
+ <n-empty v-if="!project || !project.state" description="选择一个项目查看流水线" style="margin: auto;" />
26
+
27
+ <!-- Stages -->
28
+ <div v-else class="flex-1 overflow-y-auto px-6 pb-5 space-y-5">
29
+ <PipelineStage name="scan" title="代码扫描" :steps="getStageSteps('scan')" :status="getStageStatus('scan')" :is-active="currentStage === 'scan'" :active-step="activeStep" @select-step="handleSelectStep" />
30
+ <div v-if="hasStage('brainstorm')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
31
+ <PipelineStage name="brainstorm" title="头脑风暴" :steps="getStageSteps('brainstorm')" :status="getStageStatus('brainstorm')" :is-active="currentStage === 'brainstorm'" :active-step="activeStep" @select-step="handleSelectStep" />
32
+ <div v-if="hasStage('plan')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
33
+ <PipelineStage name="plan" title="规划" :steps="getStageSteps('plan')" :status="getStageStatus('plan')" :is-active="currentStage === 'plan'" :active-step="activeStep" @select-step="handleSelectStep" />
34
+ <div v-if="hasStage('execute')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
35
+ <PipelineStage name="execute" title="执行" :steps="getStageSteps('execute')" :status="getStageStatus('execute')" :is-active="currentStage === 'execute'" :active-step="activeStep" @select-step="handleSelectStep" />
36
+ <div v-if="hasStage('verify')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
37
+ <PipelineStage name="verify" title="验证" :steps="getStageSteps('verify')" :status="getStageStatus('verify')" :is-active="currentStage === 'verify'" :active-step="activeStep" @select-step="handleSelectStep" />
38
+ <div v-if="hasStage('archive')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
39
+ <PipelineStage name="archive" title="归档" :steps="getStageSteps('archive')" :status="getStageStatus('archive')" :is-active="currentStage === 'archive'" :active-step="activeStep" @select-step="handleSelectStep" />
25
40
  </div>
26
41
 
27
- <div v-else class="space-y-5">
28
- <PipelineStage name="brainstorm" title="BRAINSTORM" :steps="getStageSteps('brainstorm')" :status="getStageStatus('brainstorm')" :is-active="currentStage === 'brainstorm'" :active-step="activeStep" @select-step="handleSelectStep" />
29
- <div v-if="hasStage('plan')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #1F1F22;" /></div>
30
- <PipelineStage name="plan" title="PLAN" :steps="getStageSteps('plan')" :status="getStageStatus('plan')" :is-active="currentStage === 'plan'" :active-step="activeStep" @select-step="handleSelectStep" />
31
- <div v-if="hasStage('execute')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #1F1F22;" /></div>
32
- <PipelineStage name="execute" title="EXECUTE" :steps="getStageSteps('execute')" :status="getStageStatus('execute')" :is-active="currentStage === 'execute'" :active-step="activeStep" @select-step="handleSelectStep" />
33
- <div v-if="hasStage('verify')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #1F1F22;" /></div>
34
- <PipelineStage name="verify" title="VERIFY" :steps="getStageSteps('verify')" :status="getStageStatus('verify')" :is-active="currentStage === 'verify'" :active-step="activeStep" @select-step="handleSelectStep" />
42
+ <!-- Activity Log -->
43
+ <div v-if="project?.state?.progress" style="border-top: 1px solid #F0F0F3; background: rgba(0,0,0,0.03);">
44
+ <div class="px-6 py-2.5 flex items-center justify-between">
45
+ <div class="text-[9px] font-semibold uppercase tracking-[0.25em] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">活动日志</div>
46
+ <div class="text-[10px] font-mono-log" style="color: #D1D1D6;">{{ activityLogs.length }}</div>
47
+ </div>
48
+ <div class="px-6 pb-3 max-h-32 overflow-y-auto">
49
+ <n-timeline size="small">
50
+ <n-timeline-item
51
+ v-for="(log, i) in activityLogs"
52
+ :key="i"
53
+ :type="log.status === 'completed' ? 'success' : 'warning'"
54
+ >
55
+ <template #default>
56
+ <span class="text-[11px]" style="color: #636366;">{{ log.description }}</span>
57
+ </template>
58
+ <template #time>
59
+ <span class="text-[10px] font-mono-log" style="color: #D1D1D6;">{{ log.time }}</span>
60
+ </template>
61
+ </n-timeline-item>
62
+ </n-timeline>
63
+ <div v-if="activityLogs.length === 0" class="text-[10px] py-1" style="color: #D1D1D6;">暂无活动记录</div>
64
+ </div>
35
65
  </div>
36
66
  </div>
37
67
 
38
- <!-- Activity Log -->
39
- <div v-if="project?.state?.progress" style="border-top: 1px solid #1F1F22; background: rgba(10,10,11,0.6);">
40
- <div class="px-6 py-2.5 flex items-center justify-between">
41
- <div class="text-[9px] font-semibold uppercase tracking-[0.25em] font-[JetBrains_Mono,monospace]" style="color: #525252;">Activity</div>
42
- <div class="text-[10px] font-mono-log" style="color: #3A3A3D;">{{ activityLogs.length }}</div>
68
+ <!-- Docs Tab -->
69
+ <div v-if="activeTab === 'docs'" class="docs-panel flex-1 flex overflow-hidden">
70
+ <div class="docs-tree-pane flex-shrink-0 overflow-hidden" style="border-right: 1px solid #F0F0F3;">
71
+ <DocTree :groups="docs.groups" :selected-file="selectedDocFile" @select-file="$emit('select-doc-file', $event)" />
43
72
  </div>
44
- <div class="px-6 pb-3 space-y-0.5 max-h-32 overflow-y-auto">
45
- <div v-for="(log, i) in activityLogs" :key="i" class="flex items-start gap-2.5 text-[11px] py-0.5">
46
- <span class="w-10 font-mono-log flex-shrink-0" style="color: #3A3A3D;">{{ log.time }}</span>
47
- <span :style="{ color: log.status === 'completed' ? '#34D399' : '#FBBF24' }">›</span>
48
- <span style="color: #8B8B8E;">{{ log.description }}</span>
49
- </div>
50
- <div v-if="activityLogs.length === 0" class="text-[10px] py-1" style="color: #3A3A3D;">No activity</div>
73
+ <div class="flex-1 overflow-hidden">
74
+ <DocPreview :content="docContent" :loading="docLoading" :selected-file="selectedDocFile" />
51
75
  </div>
52
76
  </div>
53
77
  </div>
@@ -56,24 +80,43 @@
56
80
  <script setup>
57
81
  import { computed } from 'vue'
58
82
  import PipelineStage from './PipelineStage.vue'
83
+ import DocTree from './DocTree.vue'
84
+ import DocPreview from './DocPreview.vue'
59
85
 
60
86
  const props = defineProps({
61
87
  project: { type: Object, default: null },
62
- activeStep: { type: Object, default: null }
88
+ activeStep: { type: Object, default: null },
89
+ activeTab: { type: String, default: 'pipeline' },
90
+ docs: { type: Object, default: () => ({ groups: [] }) },
91
+ selectedDocFile: { type: Object, default: null },
92
+ docContent: { type: String, default: '' },
93
+ docLoading: { type: Boolean, default: false }
63
94
  })
64
95
 
65
- const emit = defineEmits(['select-step'])
96
+ const emit = defineEmits(['select-step', 'switch-tab', 'select-doc-file'])
66
97
 
67
- const currentStage = computed(() => props.project?.state?.currentStage || 'unknown')
98
+ const currentStage = computed(() => props.project?.state?.currentStage || '')
68
99
  const progress = computed(() => props.project?.state?.progress || {})
69
100
  const stages = computed(() => progress.value.stages || {})
70
101
 
102
+ const stageNameMap = {
103
+ scan: '代码扫描',
104
+ brainstorm: '头脑风暴',
105
+ plan: '规划',
106
+ execute: '执行',
107
+ verify: '验证',
108
+ archive: '归档',
109
+ quick: '快速任务',
110
+ explore: '自由探索'
111
+ }
112
+
71
113
  const activityLogs = computed(() => {
72
114
  if (!props.project?.state) return []
73
115
  const logs = []
74
116
  for (const [name, data] of Object.entries(progress.value.stages || {})) {
75
- if (data.status === 'completed') logs.push({ time: data.completedAt ? formatTime(data.completedAt) : '--:--', status: 'completed', description: `${name} completed` })
76
- else if (data.status === 'in-progress') logs.push({ time: '--:--', status: 'in-progress', description: `${name} running` })
117
+ const label = stageNameMap[name] || name
118
+ if (data.status === 'completed') logs.push({ time: data.completedAt ? formatTime(data.completedAt) : '--:--', status: 'completed', description: `${label} 已完成` })
119
+ else if (data.status === 'in-progress') logs.push({ time: '--:--', status: 'in-progress', description: `${label} 运行中` })
77
120
  }
78
121
  return logs.reverse()
79
122
  })
@@ -92,3 +135,14 @@ function getStageStatus(n) {
92
135
  }
93
136
  function handleSelectStep(step) { emit('select-step', step) }
94
137
  </script>
138
+
139
+ <style scoped>
140
+ .docs-panel {
141
+ min-height: 0;
142
+ }
143
+
144
+ .docs-tree-pane {
145
+ width: clamp(220px, 42%, 320px);
146
+ min-width: 180px;
147
+ }
148
+ </style>
@@ -0,0 +1,187 @@
1
+ <template>
2
+ <div
3
+ class="project-card"
4
+ :class="{ selected: isSelected }"
5
+ @click="$emit('select', project)"
6
+ >
7
+ <!-- 卡片头部 -->
8
+ <div class="card-header">
9
+ <span class="project-name">{{ project.name }}</span>
10
+ <span class="last-active">{{ formatTime(project.lastActive) }}</span>
11
+ </div>
12
+
13
+ <!-- 阶段标签 -->
14
+ <div>
15
+ <span class="stage-badge" :class="stageClass">{{ stageLabel }}</span>
16
+ </div>
17
+
18
+ <!-- 进度条 -->
19
+ <div class="progress-section">
20
+ <div class="progress-bar">
21
+ <div class="progress-fill" :class="stageClass" :style="{ width: progressPercent + '%' }"></div>
22
+ </div>
23
+ <span class="progress-text">{{ progressPercent }}%</span>
24
+ </div>
25
+ </div>
26
+ </template>
27
+
28
+ <script setup>
29
+ import { computed } from 'vue'
30
+
31
+ const props = defineProps({
32
+ project: { type: Object, required: true },
33
+ isSelected: { type: Boolean, default: false }
34
+ })
35
+
36
+ defineEmits(['select'])
37
+
38
+ const stageLabel = computed(() => {
39
+ const stage = props.project?.state?.currentStage
40
+ if (!stage) return '未开始'
41
+ const stageNames = {
42
+ brainstorm: '需求探索',
43
+ plan: '实现计划',
44
+ execute: '波次执行',
45
+ verify: '验证确认'
46
+ }
47
+ return stageNames[stage] || stage
48
+ })
49
+
50
+ const stageClass = computed(() => {
51
+ const stage = props.project?.state?.currentStage
52
+ if (!stage) return 'pending'
53
+ const progress = props.project?.state?.progress?.stages?.[stage]
54
+ if (progress?.status === 'completed') return 'completed'
55
+ if (progress?.status === 'in-progress') return 'in-progress'
56
+ return 'pending'
57
+ })
58
+
59
+ const progressPercent = computed(() => {
60
+ const stage = props.project?.state?.currentStage
61
+ if (!stage) return 0
62
+ const progress = props.project?.state?.progress?.stages?.[stage]
63
+ if (!progress) return 0
64
+ if (progress.completedSteps !== undefined && progress.steps !== undefined) {
65
+ return Math.round((progress.completedSteps / progress.steps) * 100)
66
+ }
67
+ if (progress.status === 'completed') return 100
68
+ if (progress.status === 'in-progress') return 50
69
+ return 0
70
+ })
71
+
72
+ function formatTime(iso) {
73
+ if (!iso) return '昨天'
74
+ const d = new Date(iso)
75
+ const now = new Date()
76
+ const diffMs = now - d
77
+ if (diffMs < 60000) return '刚刚'
78
+ if (diffMs < 3600000) return `${Math.floor(diffMs / 60000)}分钟前`
79
+ if (diffMs < 86400000) return `${Math.floor(diffMs / 3600000)}小时前`
80
+ return '昨天'
81
+ }
82
+ </script>
83
+
84
+ <style scoped>
85
+ .project-card {
86
+ width: 280px;
87
+ height: 120px;
88
+ background: white;
89
+ border: 2px solid #E5E7EB;
90
+ border-radius: 12px;
91
+ padding: 16px;
92
+ display: flex;
93
+ flex-direction: column;
94
+ justify-content: space-between;
95
+ cursor: pointer;
96
+ transition: all 0.2s;
97
+ flex-shrink: 0;
98
+ }
99
+
100
+ .project-card:hover {
101
+ border-color: #D97706;
102
+ box-shadow: 0 4px 12px rgba(217, 119, 6, 0.15);
103
+ }
104
+
105
+ .project-card.selected {
106
+ border-color: #D97706;
107
+ box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.2);
108
+ }
109
+
110
+ .card-header {
111
+ display: flex;
112
+ justify-content: space-between;
113
+ align-items: flex-start;
114
+ }
115
+
116
+ .project-name {
117
+ font-size: 16px;
118
+ font-weight: 600;
119
+ color: #1A1A1A;
120
+ }
121
+
122
+ .last-active {
123
+ font-size: 11px;
124
+ color: #9CA3AF;
125
+ }
126
+
127
+ .stage-badge {
128
+ display: inline-block;
129
+ padding: 4px 10px;
130
+ border-radius: 12px;
131
+ font-size: 12px;
132
+ font-weight: 500;
133
+ }
134
+
135
+ .stage-badge.in-progress {
136
+ background: #DBEAFE;
137
+ color: #1D4ED8;
138
+ }
139
+
140
+ .stage-badge.completed {
141
+ background: #D1FAE5;
142
+ color: #047857;
143
+ }
144
+
145
+ .stage-badge.pending {
146
+ background: #F3F4F6;
147
+ color: #6B7280;
148
+ }
149
+
150
+ .progress-section {
151
+ display: flex;
152
+ align-items: center;
153
+ gap: 8px;
154
+ }
155
+
156
+ .progress-bar {
157
+ flex: 1;
158
+ height: 6px;
159
+ background: #E5E7EB;
160
+ border-radius: 3px;
161
+ overflow: hidden;
162
+ }
163
+
164
+ .progress-fill {
165
+ height: 100%;
166
+ border-radius: 3px;
167
+ transition: width 0.3s;
168
+ }
169
+
170
+ .progress-fill.in-progress {
171
+ background: #3B82F6;
172
+ }
173
+
174
+ .progress-fill.completed {
175
+ background: #10B981;
176
+ }
177
+
178
+ .progress-fill.pending {
179
+ background: #9CA3AF;
180
+ }
181
+
182
+ .progress-text {
183
+ font-size: 12px;
184
+ font-weight: 600;
185
+ color: #374151;
186
+ }
187
+ </style>
@@ -3,84 +3,109 @@
3
3
  <!-- Header -->
4
4
  <div class="relative z-10 px-5 pt-5 pb-4">
5
5
  <div class="flex items-center gap-3">
6
- <div class="w-8 h-8 rounded-md flex items-center justify-center" style="background: linear-gradient(135deg, #FBBF24 0%, #F59E0B 100%); clip-path: polygon(0 0, 100% 0, 85% 100%, 15% 100%);">
6
+ <div class="w-8 h-8 rounded-md flex items-center justify-center" style="background: linear-gradient(135deg, #D97706 0%, #F59E0B 100%); clip-path: polygon(0 0, 100% 0, 85% 100%, 15% 100%);">
7
7
  <span class="text-[10px] font-bold text-black font-[JetBrains_Mono,monospace]">S</span>
8
8
  </div>
9
- <div>
10
- <h1 class="text-[13px] font-semibold tracking-tight font-[JetBrains_Mono,monospace]" style="color: #E4E4E7;">
9
+ <img src="/logo.jpg" style="width:28px;height:28px;border-radius:6px;margin-right:8px;">
10
+ <div class="flex-1">
11
+ <h1 class="text-[13px] font-semibold tracking-tight font-[JetBrains_Mono,monospace]" style="color: #1C1C1E;">
11
12
  SillySpec
12
13
  </h1>
13
- <p class="text-[10px] tracking-widest uppercase" style="color: #525252;">Dashboard</p>
14
+ <p class="text-[10px] tracking-widest uppercase" style="color: #6B7280;">控制台</p>
14
15
  </div>
16
+ <!-- Scan paths gear button -->
17
+ <n-button quaternary size="tiny" @click="showScanPanel = !showScanPanel" :type="showScanPanel ? 'primary' : 'default'">
18
+ <template #icon>
19
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
20
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
21
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
22
+ </svg>
23
+ </template>
24
+ </n-button>
15
25
  </div>
26
+
27
+ <!-- Scan paths panel (inline) -->
28
+ <Transition name="slide">
29
+ <div v-if="showScanPanel" class="mt-3 rounded-md p-3" style="background: #FFFFFF; border: 1px solid #F0F0F3;">
30
+ <div class="text-[10px] font-semibold uppercase tracking-[0.15em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">扫描路径</div>
31
+
32
+ <div v-if="scanPaths.length === 0" class="text-[10px] py-1" style="color: #D1D1D6;">暂无自定义路径</div>
33
+ <div v-else class="space-y-1 mb-2">
34
+ <div v-for="(p, i) in scanPaths" :key="i" class="flex items-center gap-2 text-[10px] group">
35
+ <span class="flex-1 truncate font-mono-log" style="color: #636366;">{{ p }}</span>
36
+ <n-button quaternary size="tiny" type="error" @click="removePath(p)">✕</n-button>
37
+ </div>
38
+ </div>
39
+
40
+ <!-- Add path -->
41
+ <div v-if="showAddInput" class="flex items-center gap-2">
42
+ <n-input v-model:value="newPath" size="tiny" placeholder="输入目录路径..." @keydown.enter="addPath" @keydown.escape="showAddInput = false" ref="pathInput" />
43
+ <n-button size="tiny" type="primary" @click="addPath">添加</n-button>
44
+ </div>
45
+ <n-button v-else size="tiny" dashed @click="showAddInput = true">+ 添加目录</n-button>
46
+ </div>
47
+ </Transition>
16
48
  </div>
17
49
 
18
50
  <!-- Divider -->
19
- <div class="mx-4 h-px" style="background: linear-gradient(90deg, transparent, #2A2A2D, transparent);"></div>
51
+ <div class="mx-4 h-px" style="background: linear-gradient(90deg, transparent, #E5E5EA, transparent);"></div>
20
52
 
21
53
  <!-- Projects List -->
22
54
  <div class="flex-1 overflow-y-auto py-3 relative z-10">
23
55
  <!-- Loading skeleton -->
24
56
  <div v-if="isLoading" class="px-4 space-y-2">
25
- <div v-for="i in 4" :key="i" class="rounded-lg p-3" style="background: #141416;">
26
- <div class="h-3 rounded w-20 skeleton-shimmer mb-2"></div>
27
- <div class="h-2 rounded w-32 skeleton-shimmer"></div>
28
- </div>
29
- <p class="text-center text-[10px] mt-4 font-[JetBrains_Mono,monospace]" style="color: #525252;">
30
- scanning projects...
57
+ <n-card v-for="i in 4" :key="i" size="small" :bordered="false">
58
+ <n-skeleton text :width="80" size="small" />
59
+ <n-skeleton text :width="140" size="small" style="margin-top: 6px;" />
60
+ </n-card>
61
+ <p class="text-center text-[10px] mt-4 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">
62
+ 正在扫描项目...
31
63
  </p>
32
64
  </div>
33
65
 
34
66
  <!-- Empty state -->
35
- <div v-else-if="projects.length === 0" class="px-4 py-12 text-center">
36
- <div class="w-10 h-10 mx-auto mb-3 rounded-full flex items-center justify-center" style="border: 1px dashed #2A2A2D;">
37
- <svg class="w-4 h-4" style="color: #525252;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
38
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
39
- </svg>
40
- </div>
41
- <p class="text-[11px]" style="color: #8B8B8E;">No projects found</p>
42
- <p class="text-[10px] mt-1" style="color: #525252;">Looking for .sillyspec dirs</p>
43
- </div>
67
+ <n-empty v-else-if="projects.length === 0" description="未发现 SillySpec 项目" style="padding: 48px 0;" />
44
68
 
45
69
  <!-- Projects -->
46
70
  <div v-else class="px-3 space-y-0.5">
47
71
  <div
48
72
  v-for="project in projects"
49
- :key="project.name"
50
- :class="[
51
- 'relative rounded-md cursor-pointer transition-all duration-150 overflow-hidden group',
52
- ]"
73
+ :key="project.path"
74
+ :class="['relative rounded-md cursor-pointer transition-all duration-150 overflow-hidden group']"
53
75
  :style="{
54
- background: isActive(project) ? 'rgba(251,191,36,0.06)' : 'transparent',
55
- borderLeft: isActive(project) ? '2px solid #FBBF24' : '2px solid transparent',
76
+ background: isActive(project) ? 'rgba(217,119,6,0.06)' : 'transparent',
77
+ borderLeft: isActive(project) ? '2px solid #D97706' : '2px solid transparent',
56
78
  }"
57
- @mouseenter="$event.currentTarget.style.background = isActive(project) ? 'rgba(251,191,36,0.08)' : 'rgba(255,255,255,0.02)'"
58
- @mouseleave="$event.currentTarget.style.background = isActive(project) ? 'rgba(251,191,36,0.06)' : 'transparent'"
79
+ @mouseenter="$event.currentTarget.style.background = isActive(project) ? 'rgba(217,119,6,0.08)' : 'rgba(255,255,255,0.02)'"
80
+ @mouseleave="$event.currentTarget.style.background = isActive(project) ? 'rgba(217,119,6,0.06)' : 'transparent'"
59
81
  @click="$emit('select', project)"
60
82
  >
61
83
  <div class="px-3 py-2.5">
62
84
  <div class="flex items-center justify-between gap-2">
63
85
  <div class="flex-1 min-w-0">
64
86
  <h3
65
- :class="['text-[12px] font-medium truncate transition-colors duration-150 font-[JetBrains_Mono,monospace]']"
66
- :style="{ color: isActive(project) ? '#FBBF24' : '#E4E4E7' }"
87
+ class="text-[12px] font-medium truncate transition-colors duration-150 font-[JetBrains_Mono,monospace]"
88
+ :style="{ color: isActive(project) ? '#D97706' : '#1C1C1E' }"
67
89
  >
68
90
  {{ project.name }}
69
91
  </h3>
70
- <p class="text-[10px] mt-0.5 truncate font-mono-log" style="color: #525252;">
92
+ <p class="text-[10px] mt-0.5 truncate font-mono-log" style="color: #6B7280;">
71
93
  {{ project.path }}
72
94
  </p>
73
95
  </div>
74
- <StageBadge
96
+ <n-tag
75
97
  v-if="project.state?.currentStage"
76
- :status="getProjectStatus(project)"
77
- :label="stageLabel(project)"
78
- size="sm"
79
- />
98
+ :type="statusTagType(getProjectStatus(project))"
99
+ size="small"
100
+ :bordered="false"
101
+ round
102
+ >
103
+ {{ stageLabel(project) }}
104
+ </n-tag>
80
105
  </div>
81
106
 
82
107
  <!-- Progress -->
83
- <div v-if="project.state?.progress" class="mt-2 h-[2px] rounded-full overflow-hidden" style="background: #1C1C1F;">
108
+ <div v-if="project.state?.progress" class="mt-2 h-[2px] rounded-full overflow-hidden" style="background: #FFFFFF;">
84
109
  <div
85
110
  class="h-full rounded-full transition-all duration-500 progress-gradient"
86
111
  :style="{ width: getProjectProgress(project) + '%' }"
@@ -92,29 +117,43 @@
92
117
  </div>
93
118
 
94
119
  <!-- Footer -->
95
- <div class="relative z-10 px-4 py-2.5" style="border-top: 1px solid #1F1F22;">
120
+ <div class="relative z-10 px-4 py-2.5" style="border-top: 1px solid #F0F0F3;">
96
121
  <div class="flex items-center justify-between">
97
- <span class="text-[10px] font-[JetBrains_Mono,monospace]" style="color: #525252;">{{ projects.length }} proj</span>
98
- <kbd class="text-[9px] px-1.5 py-0.5 rounded font-mono-log" style="color: #525252; background: #141416; border: 1px solid #2A2A2D;">⌘K</kbd>
122
+ <span class="text-[10px] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">{{ projects.length }} 个项目</span>
123
+ <kbd class="text-[9px] px-1.5 py-0.5 rounded font-mono-log" style="color: #6B7280; background: #FFFFFF; border: 1px solid #E5E5EA;">⌘K</kbd>
99
124
  </div>
100
125
  </div>
101
126
  </div>
102
127
  </template>
103
128
 
104
129
  <script setup>
105
- import { computed } from 'vue'
106
- import StageBadge from './StageBadge.vue'
130
+ import { ref, nextTick, watch } from 'vue'
107
131
 
108
132
  const props = defineProps({
109
133
  projects: { type: Array, default: () => [] },
110
134
  activeProject: { type: Object, default: null },
111
- isLoading: { type: Boolean, default: false }
135
+ isLoading: { type: Boolean, default: false },
136
+ scanPaths: { type: Array, default: () => [] }
112
137
  })
113
138
 
114
- const emit = defineEmits(['select'])
139
+ const emit = defineEmits(['select', 'scan:add-path', 'scan:remove-path'])
140
+
141
+ const showScanPanel = ref(false)
142
+ const showAddInput = ref(false)
143
+ const newPath = ref('')
144
+ const pathInput = ref(null)
145
+
146
+ watch(showAddInput, (v) => {
147
+ if (v) nextTick(() => { pathInput.value?.focus() })
148
+ })
115
149
 
116
150
  function isActive(project) {
117
- return props.activeProject?.name === project.name
151
+ return props.activeProject?.path === project.path
152
+ }
153
+
154
+ function statusTagType(status) {
155
+ const map = { 'in-progress': 'warning', 'completed': 'success', 'failed': 'error', 'blocked': 'warning' }
156
+ return map[status] || 'default'
118
157
  }
119
158
 
120
159
  function getProjectStatus(project) {
@@ -149,4 +188,23 @@ function getProjectProgress(project) {
149
188
  }
150
189
  return total === 0 ? 0 : Math.round((done / total) * 100)
151
190
  }
191
+
192
+ function addPath() {
193
+ const p = newPath.value.trim()
194
+ if (p) {
195
+ emit('scan:add-path', p)
196
+ newPath.value = ''
197
+ showAddInput.value = false
198
+ }
199
+ }
200
+
201
+ function removePath(p) {
202
+ emit('scan:remove-path', p)
203
+ }
152
204
  </script>
205
+
206
+ <style scoped>
207
+ .slide-enter-active, .slide-leave-active { transition: all 200ms ease; }
208
+ .slide-enter-from, .slide-leave-to { opacity: 0; max-height: 0; overflow: hidden; }
209
+ .slide-enter-to, .slide-leave-from { max-height: 300px; }
210
+ </style>