sillyspec 3.7.14 → 3.7.16

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 (85) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +77 -0
  2. package/.claude/skills/sillyspec-brainstorm/SKILL.md +591 -0
  3. package/.claude/skills/sillyspec-continue/SKILL.md +44 -0
  4. package/.claude/skills/sillyspec-execute/SKILL.md +233 -0
  5. package/.claude/skills/sillyspec-explore/SKILL.md +96 -0
  6. package/.claude/skills/sillyspec-export/SKILL.md +53 -0
  7. package/.claude/skills/sillyspec-init/SKILL.md +171 -0
  8. package/.claude/skills/sillyspec-plan/SKILL.md +263 -0
  9. package/.claude/skills/sillyspec-propose/SKILL.md +248 -0
  10. package/.claude/skills/sillyspec-quick/SKILL.md +102 -0
  11. package/.claude/skills/sillyspec-resume/SKILL.md +111 -0
  12. package/{templates/scan.md → .claude/skills/sillyspec-scan/SKILL.md} +22 -60
  13. package/.claude/skills/sillyspec-state/SKILL.md +54 -0
  14. package/.claude/skills/sillyspec-status/SKILL.md +131 -0
  15. package/.claude/skills/sillyspec-verify/SKILL.md +146 -0
  16. package/.claude/skills/sillyspec-workspace/SKILL.md +149 -0
  17. package/.sillyspec/changes/run-command-design/design.md +1230 -0
  18. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  19. package/.sillyspec/knowledge/INDEX.md +8 -0
  20. package/.sillyspec/knowledge/uncategorized.md +3 -0
  21. package/.sillyspec/projects/sillyspec.yaml +3 -0
  22. package/package.json +1 -1
  23. package/packages/dashboard/dist/assets/index-Bx0cgoK_.js +7446 -0
  24. package/packages/dashboard/dist/assets/index-DbkUSsNO.css +1 -0
  25. package/packages/dashboard/dist/index.html +2 -2
  26. package/packages/dashboard/package-lock.json +220 -0
  27. package/packages/dashboard/package.json +8 -5
  28. package/packages/dashboard/server/index.js +91 -3
  29. package/packages/dashboard/server/parser.js +252 -28
  30. package/packages/dashboard/src/App.vue +54 -8
  31. package/packages/dashboard/src/components/ActionBar.vue +23 -39
  32. package/packages/dashboard/src/components/CommandPalette.vue +40 -65
  33. package/packages/dashboard/src/components/DetailPanel.vue +68 -53
  34. package/packages/dashboard/src/components/DocPreview.vue +137 -20
  35. package/packages/dashboard/src/components/DocTree.vue +48 -26
  36. package/packages/dashboard/src/components/LogStream.vue +12 -32
  37. package/packages/dashboard/src/components/PipelineStage.vue +8 -8
  38. package/packages/dashboard/src/components/PipelineView.vue +35 -43
  39. package/packages/dashboard/src/components/ProjectList.vue +51 -77
  40. package/packages/dashboard/src/components/ProjectOverview.vue +178 -0
  41. package/packages/dashboard/src/components/StageBadge.vue +13 -13
  42. package/packages/dashboard/src/components/StepCard.vue +11 -11
  43. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -0
  44. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -0
  45. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -0
  46. package/packages/dashboard/src/main.js +4 -1
  47. package/packages/dashboard/src/style.css +14 -14
  48. package/src/index.js +55 -8
  49. package/src/init.js +69 -196
  50. package/src/migrate.js +1 -18
  51. package/src/progress.js +279 -281
  52. package/src/run.js +320 -0
  53. package/src/setup.js +1 -9
  54. package/src/stages/brainstorm.js +210 -0
  55. package/src/stages/execute.js +190 -0
  56. package/src/stages/index.js +22 -0
  57. package/src/stages/plan.js +118 -0
  58. package/src/stages/propose.js +115 -0
  59. package/src/stages/verify.js +98 -0
  60. package/packages/dashboard/dist/assets/index-hNnQCobe.css +0 -1
  61. package/packages/dashboard/dist/assets/index-qgPzQGjk.js +0 -17
  62. package/templates/archive.md +0 -121
  63. package/templates/brainstorm.md +0 -246
  64. package/templates/commit.md +0 -123
  65. package/templates/continue.md +0 -32
  66. package/templates/execute.md +0 -314
  67. package/templates/explore.md +0 -60
  68. package/templates/export.md +0 -21
  69. package/templates/init.md +0 -61
  70. package/templates/plan.md +0 -157
  71. package/templates/progress-format.md +0 -90
  72. package/templates/propose.md +0 -73
  73. package/templates/quick.md +0 -135
  74. package/templates/resume-dialog.md +0 -55
  75. package/templates/resume.md +0 -53
  76. package/templates/scan-quick.md +0 -49
  77. package/templates/skills/playwright-e2e/SKILL.md +0 -340
  78. package/templates/status.md +0 -72
  79. package/templates/verify.md +0 -253
  80. package/templates/workspace-sync.md +0 -89
  81. package/templates/workspace.md +0 -67
  82. /package/.sillyspec/{docs/sillyspec/brainstorm → changes/brainstorm-archive}/2026-04-05-dashboard-design.md +0 -0
  83. /package/.sillyspec/{docs/sillyspec/brainstorm → changes/brainstorm-archive}/2026-04-05-unified-docs-design.md +0 -0
  84. /package/.sillyspec/{specs/2026-04-05-dashboard-design.md → changes/dashboard/design.md.braindraft} +0 -0
  85. /package/.sillyspec/{specs/2026-04-05-unified-docs-design.md → changes/unified-docs-design/design.md} +0 -0
@@ -1,77 +1,69 @@
1
1
  <template>
2
- <div class="flex flex-col h-full" style="background: #0E0E10;">
2
+ <div class="flex flex-col h-full" style="background: #F5F5F7;">
3
3
  <!-- Header with Tabs -->
4
- <div class="px-6 pt-4 pb-0" style="border-bottom: 1px solid #1F1F22;">
4
+ <div class="px-6 pt-4 pb-0" style="border-bottom: 1px solid #F0F0F3;">
5
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: #8B8FA3;">
6
+ <h2 class="text-[11px] font-semibold uppercase tracking-[0.2em] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">
7
7
  {{ project?.name || '项目' }}
8
8
  </h2>
9
- <div class="flex gap-1">
10
- <button
11
- @click="$emit('switch-tab', 'pipeline')"
12
- class="px-3 py-1.5 text-[11px] font-[JetBrains_Mono,monospace] transition-colors rounded"
13
- :style="{ color: activeTab === 'pipeline' ? '#FBBF24' : '#8B8FA3', background: activeTab === 'pipeline' ? 'rgba(251,191,36,0.08)' : 'transparent' }"
14
- >流水线</button>
15
- <button
16
- @click="$emit('switch-tab', 'docs')"
17
- class="px-3 py-1.5 text-[11px] font-[JetBrains_Mono,monospace] transition-colors rounded"
18
- :style="{ color: activeTab === 'docs' ? '#FBBF24' : '#8B8FA3', background: activeTab === 'docs' ? 'rgba(251,191,36,0.08)' : 'transparent' }"
19
- >文档</button>
20
- </div>
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>
21
13
  </div>
22
14
  </div>
23
15
 
24
16
  <!-- Pipeline Tab -->
25
17
  <div v-if="activeTab === 'pipeline'" class="flex flex-col flex-1 overflow-hidden">
26
- <div class="px-6 pt-4 pb-2">
27
- <p v-if="project" class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #A0A4B5;">
28
- <span style="color: #FBBF24;">{{ currentStage }}</span>
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>
29
21
  </p>
30
22
  </div>
31
23
 
32
24
  <!-- Empty state -->
33
- <div v-if="!project || !project.state" class="flex items-center justify-center flex-1">
34
- <div class="text-center">
35
- <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);">
36
- <svg class="w-5 h-5" style="color: #8B8FA3; transform: rotate(-45deg);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
37
- <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"/>
38
- </svg>
39
- </div>
40
- <p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #8B8FA3;">选择一个项目查看流水线</p>
41
- </div>
42
- </div>
25
+ <n-empty v-if="!project || !project.state" description="选择一个项目查看流水线" style="margin: auto;" />
43
26
 
44
27
  <!-- Stages -->
45
28
  <div v-else class="flex-1 overflow-y-auto px-6 pb-5 space-y-5">
46
29
  <PipelineStage name="brainstorm" title="头脑风暴" :steps="getStageSteps('brainstorm')" :status="getStageStatus('brainstorm')" :is-active="currentStage === 'brainstorm'" :active-step="activeStep" @select-step="handleSelectStep" />
47
- <div v-if="hasStage('plan')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #1F1F22;" /></div>
30
+ <div v-if="hasStage('plan')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
48
31
  <PipelineStage name="plan" title="规划" :steps="getStageSteps('plan')" :status="getStageStatus('plan')" :is-active="currentStage === 'plan'" :active-step="activeStep" @select-step="handleSelectStep" />
49
- <div v-if="hasStage('execute')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #1F1F22;" /></div>
32
+ <div v-if="hasStage('execute')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
50
33
  <PipelineStage name="execute" title="执行" :steps="getStageSteps('execute')" :status="getStageStatus('execute')" :is-active="currentStage === 'execute'" :active-step="activeStep" @select-step="handleSelectStep" />
51
- <div v-if="hasStage('verify')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #1F1F22;" /></div>
34
+ <div v-if="hasStage('verify')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
52
35
  <PipelineStage name="verify" title="验证" :steps="getStageSteps('verify')" :status="getStageStatus('verify')" :is-active="currentStage === 'verify'" :active-step="activeStep" @select-step="handleSelectStep" />
53
36
  </div>
54
37
 
55
38
  <!-- Activity Log -->
56
- <div v-if="project?.state?.progress" style="border-top: 1px solid #1F1F22; background: rgba(10,10,11,0.6);">
39
+ <div v-if="project?.state?.progress" style="border-top: 1px solid #F0F0F3; background: rgba(0,0,0,0.03);">
57
40
  <div class="px-6 py-2.5 flex items-center justify-between">
58
- <div class="text-[9px] font-semibold uppercase tracking-[0.25em] font-[JetBrains_Mono,monospace]" style="color: #8B8FA3;">活动日志</div>
59
- <div class="text-[10px] font-mono-log" style="color: #3A3A3D;">{{ activityLogs.length }}</div>
41
+ <div class="text-[9px] font-semibold uppercase tracking-[0.25em] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">活动日志</div>
42
+ <div class="text-[10px] font-mono-log" style="color: #D1D1D6;">{{ activityLogs.length }}</div>
60
43
  </div>
61
- <div class="px-6 pb-3 space-y-0.5 max-h-32 overflow-y-auto">
62
- <div v-for="(log, i) in activityLogs" :key="i" class="flex items-start gap-2.5 text-[11px] py-0.5">
63
- <span class="w-10 font-mono-log flex-shrink-0" style="color: #3A3A3D;">{{ log.time }}</span>
64
- <span :style="{ color: log.status === 'completed' ? '#34D399' : '#FBBF24' }">›</span>
65
- <span style="color: #A0A4B5;">{{ log.description }}</span>
66
- </div>
67
- <div v-if="activityLogs.length === 0" class="text-[10px] py-1" style="color: #3A3A3D;">暂无活动记录</div>
44
+ <div class="px-6 pb-3 max-h-32 overflow-y-auto">
45
+ <n-timeline size="small">
46
+ <n-timeline-item
47
+ v-for="(log, i) in activityLogs"
48
+ :key="i"
49
+ :type="log.status === 'completed' ? 'success' : 'warning'"
50
+ >
51
+ <template #default>
52
+ <span class="text-[11px]" style="color: #636366;">{{ log.description }}</span>
53
+ </template>
54
+ <template #time>
55
+ <span class="text-[10px] font-mono-log" style="color: #D1D1D6;">{{ log.time }}</span>
56
+ </template>
57
+ </n-timeline-item>
58
+ </n-timeline>
59
+ <div v-if="activityLogs.length === 0" class="text-[10px] py-1" style="color: #D1D1D6;">暂无活动记录</div>
68
60
  </div>
69
61
  </div>
70
62
  </div>
71
63
 
72
64
  <!-- Docs Tab -->
73
65
  <div v-if="activeTab === 'docs'" class="flex-1 flex overflow-hidden">
74
- <div class="w-[200px] flex-shrink-0 overflow-hidden" style="border-right: 1px solid #1F1F22;">
66
+ <div class="w-[200px] flex-shrink-0 overflow-hidden" style="border-right: 1px solid #F0F0F3;">
75
67
  <DocTree :groups="docs.groups" :selected-file="selectedDocFile" @select-file="$emit('select-doc-file', $event)" />
76
68
  </div>
77
69
  <div class="flex-1 overflow-hidden">
@@ -99,7 +91,7 @@ const props = defineProps({
99
91
 
100
92
  const emit = defineEmits(['select-step', 'switch-tab', 'select-doc-file'])
101
93
 
102
- const currentStage = computed(() => props.project?.state?.currentStage || 'unknown')
94
+ const currentStage = computed(() => props.project?.state?.currentStage || '')
103
95
  const progress = computed(() => props.project?.state?.progress || {})
104
96
  const stages = computed(() => progress.value.stages || {})
105
97
 
@@ -3,139 +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
9
  <img src="/logo.jpg" style="width:28px;height:28px;border-radius:6px;margin-right:8px;">
10
10
  <div class="flex-1">
11
- <h1 class="text-[13px] font-semibold tracking-tight font-[JetBrains_Mono,monospace]" style="color: #E4E4E7;">
11
+ <h1 class="text-[13px] font-semibold tracking-tight font-[JetBrains_Mono,monospace]" style="color: #1C1C1E;">
12
12
  SillySpec
13
13
  </h1>
14
- <p class="text-[10px] tracking-widest uppercase" style="color: #8B8FA3;">控制台</p>
14
+ <p class="text-[10px] tracking-widest uppercase" style="color: #6B7280;">控制台</p>
15
15
  </div>
16
16
  <!-- Scan paths gear button -->
17
- <button
18
- @click="showScanPanel = !showScanPanel"
19
- class="p-1.5 rounded-sm transition-colors duration-100"
20
- :style="{ color: showScanPanel ? '#FBBF24' : '#8B8FA3', background: showScanPanel ? 'rgba(251,191,36,0.08)' : 'transparent' }"
21
- title="扫描路径设置"
22
- >
23
- <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
24
- <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"/>
25
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
26
- </svg>
27
- </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>
28
25
  </div>
29
26
 
30
27
  <!-- Scan paths panel (inline) -->
31
28
  <Transition name="slide">
32
- <div v-if="showScanPanel" class="mt-3 rounded-md p-3" style="background: #141416; border: 1px solid #1F1F22;">
33
- <div class="text-[10px] font-semibold uppercase tracking-[0.15em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #8B8FA3;">扫描路径</div>
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>
34
31
 
35
- <div v-if="scanPaths.length === 0" class="text-[10px] py-1" style="color: #3A3A3D;">暂无自定义路径</div>
32
+ <div v-if="scanPaths.length === 0" class="text-[10px] py-1" style="color: #D1D1D6;">暂无自定义路径</div>
36
33
  <div v-else class="space-y-1 mb-2">
37
34
  <div v-for="(p, i) in scanPaths" :key="i" class="flex items-center gap-2 text-[10px] group">
38
- <span class="flex-1 truncate font-mono-log" style="color: #A0A4B5;">{{ p }}</span>
39
- <button
40
- @click="removePath(p)"
41
- class="opacity-0 group-hover:opacity-100 transition-opacity text-[9px] px-1 rounded-sm"
42
- style="color: #EF4444; background: rgba(239,68,68,0.08);"
43
- >✕</button>
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>
44
37
  </div>
45
38
  </div>
46
39
 
47
40
  <!-- Add path -->
48
41
  <div v-if="showAddInput" class="flex items-center gap-2">
49
- <input
50
- ref="pathInput"
51
- v-model="newPath"
52
- type="text"
53
- placeholder="输入目录路径..."
54
- class="flex-1 px-2 py-1 rounded-sm text-[10px] font-mono-log outline-none"
55
- style="background: #0A0A0B; border: 1px solid #2A2A2D; color: #E4E4E7;"
56
- @keydown.enter="addPath"
57
- @keydown.escape="showAddInput = false"
58
- />
59
- <button @click="addPath" class="px-2 py-1 text-[10px] rounded-sm" style="background: rgba(251,191,36,0.1); color: #FBBF24; border: 1px solid rgba(251,191,36,0.2);">添加</button>
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>
60
44
  </div>
61
- <button
62
- v-else
63
- @click="showAddInput = true"
64
- class="text-[10px] px-2 py-1 rounded-sm transition-colors duration-100"
65
- style="color: #8B8FA3; border: 1px dashed #2A2A2D;"
66
- >
67
- + 添加目录
68
- </button>
45
+ <n-button v-else size="tiny" dashed @click="showAddInput = true">+ 添加目录</n-button>
69
46
  </div>
70
47
  </Transition>
71
48
  </div>
72
49
 
73
50
  <!-- Divider -->
74
- <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>
75
52
 
76
53
  <!-- Projects List -->
77
54
  <div class="flex-1 overflow-y-auto py-3 relative z-10">
78
55
  <!-- Loading skeleton -->
79
56
  <div v-if="isLoading" class="px-4 space-y-2">
80
- <div v-for="i in 4" :key="i" class="rounded-lg p-3" style="background: #141416;">
81
- <div class="h-3 rounded w-20 skeleton-shimmer mb-2"></div>
82
- <div class="h-2 rounded w-32 skeleton-shimmer"></div>
83
- </div>
84
- <p class="text-center text-[10px] mt-4 font-[JetBrains_Mono,monospace]" style="color: #8B8FA3;">
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;">
85
62
  正在扫描项目...
86
63
  </p>
87
64
  </div>
88
65
 
89
66
  <!-- Empty state -->
90
- <div v-else-if="projects.length === 0" class="px-4 py-12 text-center">
91
- <div class="w-10 h-10 mx-auto mb-3 rounded-full flex items-center justify-center" style="border: 1px dashed #2A2A2D;">
92
- <svg class="w-4 h-4" style="color: #8B8FA3;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
93
- <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"/>
94
- </svg>
95
- </div>
96
- <p class="text-[11px]" style="color: #A0A4B5;">未发现 SillySpec 项目</p>
97
- <p class="text-[10px] mt-1" style="color: #8B8FA3;">正在扫描项目目录...</p>
98
- </div>
67
+ <n-empty v-else-if="projects.length === 0" description="未发现 SillySpec 项目" style="padding: 48px 0;" />
99
68
 
100
69
  <!-- Projects -->
101
70
  <div v-else class="px-3 space-y-0.5">
102
71
  <div
103
72
  v-for="project in projects"
104
73
  :key="project.path"
105
- :class="[
106
- 'relative rounded-md cursor-pointer transition-all duration-150 overflow-hidden group',
107
- ]"
74
+ :class="['relative rounded-md cursor-pointer transition-all duration-150 overflow-hidden group']"
108
75
  :style="{
109
- background: isActive(project) ? 'rgba(251,191,36,0.06)' : 'transparent',
110
- 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',
111
78
  }"
112
- @mouseenter="$event.currentTarget.style.background = isActive(project) ? 'rgba(251,191,36,0.08)' : 'rgba(255,255,255,0.02)'"
113
- @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'"
114
81
  @click="$emit('select', project)"
115
82
  >
116
83
  <div class="px-3 py-2.5">
117
84
  <div class="flex items-center justify-between gap-2">
118
85
  <div class="flex-1 min-w-0">
119
86
  <h3
120
- :class="['text-[12px] font-medium truncate transition-colors duration-150 font-[JetBrains_Mono,monospace]']"
121
- :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' }"
122
89
  >
123
90
  {{ project.name }}
124
91
  </h3>
125
- <p class="text-[10px] mt-0.5 truncate font-mono-log" style="color: #8B8FA3;">
92
+ <p class="text-[10px] mt-0.5 truncate font-mono-log" style="color: #6B7280;">
126
93
  {{ project.path }}
127
94
  </p>
128
95
  </div>
129
- <StageBadge
96
+ <n-tag
130
97
  v-if="project.state?.currentStage"
131
- :status="getProjectStatus(project)"
132
- :label="stageLabel(project)"
133
- size="sm"
134
- />
98
+ :type="statusTagType(getProjectStatus(project))"
99
+ size="small"
100
+ :bordered="false"
101
+ round
102
+ >
103
+ {{ stageLabel(project) }}
104
+ </n-tag>
135
105
  </div>
136
106
 
137
107
  <!-- Progress -->
138
- <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;">
139
109
  <div
140
110
  class="h-full rounded-full transition-all duration-500 progress-gradient"
141
111
  :style="{ width: getProjectProgress(project) + '%' }"
@@ -147,10 +117,10 @@
147
117
  </div>
148
118
 
149
119
  <!-- Footer -->
150
- <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;">
151
121
  <div class="flex items-center justify-between">
152
- <span class="text-[10px] font-[JetBrains_Mono,monospace]" style="color: #8B8FA3;">{{ projects.length }} 个项目</span>
153
- <kbd class="text-[9px] px-1.5 py-0.5 rounded font-mono-log" style="color: #8B8FA3; 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>
154
124
  </div>
155
125
  </div>
156
126
  </div>
@@ -158,7 +128,6 @@
158
128
 
159
129
  <script setup>
160
130
  import { ref, nextTick, watch } from 'vue'
161
- import StageBadge from './StageBadge.vue'
162
131
 
163
132
  const props = defineProps({
164
133
  projects: { type: Array, default: () => [] },
@@ -182,6 +151,11 @@ function isActive(project) {
182
151
  return props.activeProject?.path === project.path
183
152
  }
184
153
 
154
+ function statusTagType(status) {
155
+ const map = { 'in-progress': 'warning', 'completed': 'success', 'failed': 'error', 'blocked': 'warning' }
156
+ return map[status] || 'default'
157
+ }
158
+
185
159
  function getProjectStatus(project) {
186
160
  const stage = project.state?.currentStage
187
161
  if (!stage) return 'pending'
@@ -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', '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: '#8B8FA3' }
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': '#8B8FA3'
45
+ 'completed': '#16A34A',
46
+ 'in-progress': '#D97706',
47
+ 'blocked': '#EA580C',
48
+ 'failed': '#DC2626',
49
+ 'pending': '#6B7280'
50
50
  }
51
- return { background: colors[props.status] || '#8B8FA3' }
51
+ return { background: colors[props.status] || '#6B7280' }
52
52
  })
53
53
  </script>
@@ -11,7 +11,7 @@
11
11
 
12
12
  <div class="pl-3.5 pr-3 py-2.5">
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" />
@@ -23,25 +23,25 @@
23
23
  class="overflow-hidden transition-all duration-200"
24
24
  :style="{ maxHeight: hovered ? '60px' : '0', opacity: hovered ? 1 : 0 }"
25
25
  >
26
- <p class="mt-1.5 text-[11px] line-clamp-2" style="color: #A0A4B5;">
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: #A0A4B5;">
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: #8B8FA3;">
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(() => {