sillyspec 3.8.7 → 3.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) 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 +96 -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 +52 -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/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
  21. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
  22. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
  23. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
  24. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
  25. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
  26. package/.sillyspec/changes/auto-mode/design.md +50 -0
  27. package/.sillyspec/changes/auto-mode/proposal.md +19 -0
  28. package/.sillyspec/changes/auto-mode/requirements.md +21 -0
  29. package/.sillyspec/changes/auto-mode/tasks.md +7 -0
  30. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
  31. package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
  32. package/.sillyspec/changes/run-command-design/design.md +1230 -0
  33. package/.sillyspec/changes/unified-docs-design/design.md +199 -0
  34. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  35. package/.sillyspec/knowledge/INDEX.md +8 -0
  36. package/.sillyspec/knowledge/uncategorized.md +3 -0
  37. package/.sillyspec/projects/sillyspec.yaml +3 -0
  38. package/README.md +12 -5
  39. package/package.json +9 -11
  40. package/packages/dashboard/dist/assets/index-CntACGUN.css +1 -0
  41. package/packages/dashboard/dist/assets/index-RsLVPAy7.js +7446 -0
  42. package/packages/dashboard/dist/index.html +3 -2
  43. package/packages/dashboard/package-lock.json +226 -6
  44. package/packages/dashboard/package.json +8 -5
  45. package/packages/dashboard/public/logo.jpg +0 -0
  46. package/packages/dashboard/server/executor.js +1 -1
  47. package/packages/dashboard/server/index.js +336 -113
  48. package/packages/dashboard/server/parser.js +333 -29
  49. package/packages/dashboard/server/watcher.js +203 -131
  50. package/packages/dashboard/src/App.vue +187 -11
  51. package/packages/dashboard/src/components/ActionBar.vue +26 -42
  52. package/packages/dashboard/src/components/CommandPalette.vue +40 -65
  53. package/packages/dashboard/src/components/DetailPanel.vue +68 -53
  54. package/packages/dashboard/src/components/DocPreview.vue +160 -0
  55. package/packages/dashboard/src/components/DocTree.vue +58 -0
  56. package/packages/dashboard/src/components/LogStream.vue +13 -33
  57. package/packages/dashboard/src/components/PipelineStage.vue +8 -8
  58. package/packages/dashboard/src/components/PipelineView.vue +80 -45
  59. package/packages/dashboard/src/components/ProjectList.vue +103 -45
  60. package/packages/dashboard/src/components/ProjectOverview.vue +178 -0
  61. package/packages/dashboard/src/components/StageBadge.vue +13 -13
  62. package/packages/dashboard/src/components/StepCard.vue +15 -15
  63. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -0
  64. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -0
  65. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -0
  66. package/packages/dashboard/src/composables/useDashboard.js +20 -6
  67. package/packages/dashboard/src/composables/useKeyboard.js +6 -4
  68. package/packages/dashboard/src/main.js +4 -1
  69. package/packages/dashboard/src/style.css +17 -17
  70. package/src/index.js +136 -14
  71. package/src/init.js +83 -228
  72. package/src/migrate.js +117 -0
  73. package/src/progress.js +459 -0
  74. package/src/run.js +624 -0
  75. package/src/setup.js +2 -72
  76. package/src/stages/archive.js +54 -0
  77. package/src/stages/brainstorm.js +239 -0
  78. package/src/stages/doctor.js +303 -0
  79. package/src/stages/execute.js +262 -0
  80. package/src/stages/index.js +26 -0
  81. package/src/stages/plan.js +282 -0
  82. package/src/stages/propose.js +115 -0
  83. package/src/stages/quick.js +64 -0
  84. package/src/stages/scan.js +141 -0
  85. package/src/stages/status.js +65 -0
  86. package/src/stages/verify.js +135 -0
  87. package/docs/.vitepress/config.mts +0 -45
  88. package/docs/.vitepress/dist/404.html +0 -25
  89. package/docs/.vitepress/dist/assets/app.YytxICdd.js +0 -1
  90. package/docs/.vitepress/dist/assets/chunks/framework.Czhw_PXq.js +0 -19
  91. package/docs/.vitepress/dist/assets/chunks/theme.DusTRZQk.js +0 -1
  92. package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.js +0 -1
  93. package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.lean.js +0 -1
  94. package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  95. package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  96. package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  97. package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  98. package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  99. package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  100. package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  101. package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  102. package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  103. package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  104. package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  105. package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  106. package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  107. package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  108. package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.js +0 -15
  109. package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.lean.js +0 -1
  110. package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.js +0 -4
  111. package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.lean.js +0 -1
  112. package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.js +0 -1
  113. package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.lean.js +0 -1
  114. package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.js +0 -4
  115. package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.lean.js +0 -1
  116. package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.js +0 -5
  117. package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.lean.js +0 -1
  118. package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.js +0 -28
  119. package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.lean.js +0 -1
  120. package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.js +0 -30
  121. package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.lean.js +0 -1
  122. package/docs/.vitepress/dist/assets/style.DFTx90Kk.css +0 -1
  123. package/docs/.vitepress/dist/hashmap.json +0 -1
  124. package/docs/.vitepress/dist/index.html +0 -28
  125. package/docs/.vitepress/dist/sillyspec/commands.html +0 -42
  126. package/docs/.vitepress/dist/sillyspec/dashboard.html +0 -31
  127. package/docs/.vitepress/dist/sillyspec/file-io.html +0 -28
  128. package/docs/.vitepress/dist/sillyspec/getting-started.html +0 -31
  129. package/docs/.vitepress/dist/sillyspec/install.html +0 -32
  130. package/docs/.vitepress/dist/sillyspec/lifecycle.html +0 -55
  131. package/docs/.vitepress/dist/sillyspec/structure.html +0 -57
  132. package/docs/.vitepress/dist/vp-icons.css +0 -1
  133. package/docs/index.md +0 -34
  134. package/docs/sillyspec/commands.md +0 -218
  135. package/docs/sillyspec/dashboard.md +0 -51
  136. package/docs/sillyspec/file-io.md +0 -34
  137. package/docs/sillyspec/getting-started.md +0 -61
  138. package/docs/sillyspec/install.md +0 -51
  139. package/docs/sillyspec/lifecycle.md +0 -146
  140. package/docs/sillyspec/structure.md +0 -62
  141. package/packages/dashboard/dist/assets/index-Bh-GPjKY.css +0 -1
  142. package/packages/dashboard/dist/assets/index-CrCn5Gg6.js +0 -17
  143. package/templates/archive.md +0 -120
  144. package/templates/brainstorm.md +0 -170
  145. package/templates/continue.md +0 -32
  146. package/templates/execute.md +0 -304
  147. package/templates/explore.md +0 -59
  148. package/templates/export.md +0 -21
  149. package/templates/init.md +0 -61
  150. package/templates/plan.md +0 -146
  151. package/templates/quick.md +0 -135
  152. package/templates/scan-quick.md +0 -49
  153. package/templates/scan.md +0 -156
  154. package/templates/skills/playwright-e2e/SKILL.md +0 -340
  155. package/templates/status.md +0 -75
  156. package/templates/verify.md +0 -236
  157. package/templates/workspace-sync.md +0 -99
  158. package/templates/workspace.md +0 -70
  159. /package/.sillyspec/{specs → changes/brainstorm-archive}/2026-04-05-dashboard-design.md +0 -0
  160. /package/{docs/.vitepress/dist/logo.jpg → logo.jpg} +0 -0
  161. /package/{docs/.vitepress → packages/dashboard}/dist/favicon.jpg +0 -0
  162. /package/{docs/public → packages/dashboard/dist}/logo.jpg +0 -0
  163. /package/{docs → packages/dashboard}/public/favicon.jpg +0 -0
@@ -1,51 +1,31 @@
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 -->
4
- <div class="px-3 py-2 flex items-center gap-2" style="border-bottom: 1px solid #1F1F22;">
5
- <input
6
- v-model="searchQuery"
7
- type="text"
8
- placeholder="filter logs..."
9
- class="flex-1 px-2 py-1 rounded-sm text-[10px] font-mono-log outline-none transition-colors duration-100"
10
- style="background: #141416; border: 1px solid #1F1F22; color: #8B8B8E;"
11
- />
12
- <button
13
- @click="clearLogs"
14
- class="px-2 py-1 text-[10px] rounded-sm transition-colors duration-100"
15
- style="color: #525252; border: 1px solid #1F1F22;"
16
- >
17
- clear
18
- </button>
19
- <button
20
- @click="toggleAutoScroll"
21
- class="px-2 py-1 text-[10px] rounded-sm font-mono-log transition-colors duration-100"
22
- :style="{
23
- color: autoScroll ? '#FBBF24' : '#525252',
24
- background: autoScroll ? 'rgba(251,191,36,0.08)' : 'transparent',
25
- border: autoScroll ? '1px solid rgba(251,191,36,0.2)' : '1px solid #1F1F22'
26
- }"
27
- >
28
- {{ autoScroll ? 'auto' : 'pause' }}
29
- </button>
4
+ <div class="px-3 py-2 flex items-center gap-2" style="border-bottom: 1px solid #F0F0F3;">
5
+ <n-input v-model:value="searchQuery" size="tiny" placeholder="过滤日志..." clearable style="flex: 1;" />
6
+ <n-button size="tiny" @click="clearLogs">清空</n-button>
7
+ <n-button size="tiny" :type="autoScroll ? 'primary' : 'default'" @click="toggleAutoScroll">
8
+ {{ autoScroll ? '自动' : '暂停' }}
9
+ </n-button>
30
10
  </div>
31
11
 
32
12
  <!-- Log output -->
33
- <div ref="logContainer" class="flex-1 overflow-y-auto px-2 py-1.5 font-mono-log text-[10px]" style="background: #0A0A0B;" @scroll="handleScroll">
13
+ <div ref="logContainer" class="flex-1 overflow-y-auto px-2 py-1.5 font-mono-log text-[10px]" style="background: #F0F0F3;" @scroll="handleScroll">
34
14
  <div v-if="filteredLogs.length === 0" class="flex items-center justify-center h-full">
35
- <span class="font-mono-log" style="color: #2A2A2D;">{{ logs.length === 0 ? 'no logs' : 'no match' }}</span>
15
+ <span class="font-mono-log" style="color: #E5E5EA;">{{ logs.length === 0 ? '暂无日志' : '无匹配' }}</span>
36
16
  </div>
37
17
  <div v-else class="space-y-px">
38
18
  <div v-for="log in filteredLogs" :key="log.id" class="px-1.5 py-px rounded-sm" :style="{ background: logBg(log.type) }">
39
- <span style="color: #3A3A3D;" class="select-none">[{{ formatTime(log.timestamp) }}]</span>
19
+ <span style="color: #D1D1D6;" class="select-none">[{{ formatTime(log.timestamp) }}]</span>
40
20
  <span :style="{ color: logColor(log.type) }">{{ escapeHtml(log.content) }}</span>
41
21
  </div>
42
22
  </div>
43
23
  </div>
44
24
 
45
25
  <!-- Footer -->
46
- <div class="px-3 py-1 flex items-center justify-between text-[9px] font-mono-log" style="border-top: 1px solid #1F1F22; background: #0E0E10; color: #3A3A3D;">
26
+ <div class="px-3 py-1 flex items-center justify-between text-[9px] font-mono-log" style="border-top: 1px solid #F0F0F3; background: #F5F5F7; color: #D1D1D6;">
47
27
  <span>{{ filteredLogs.length }}/{{ logs.length }}</span>
48
- <span v-if="!autoScroll" style="color: #FB923C;">paused</span>
28
+ <span v-if="!autoScroll" style="color: #EA580C;">已暂停</span>
49
29
  </div>
50
30
  </div>
51
31
  </template>
@@ -69,7 +49,7 @@ const filteredLogs = computed(() => {
69
49
  function formatTime(ts) { if (!ts) return ''; const d = new Date(ts); return d.toLocaleTimeString('zh-CN', { hour12: false }) }
70
50
  function escapeHtml(t) { if (!t) return ''; return t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') }
71
51
  function logBg(t) { return t === 'error' ? 'rgba(239,68,68,0.05)' : t === 'warn' ? 'rgba(251,146,60,0.05)' : 'transparent' }
72
- function logColor(t) { return t === 'error' ? '#EF4444' : t === 'warn' ? '#FB923C' : t === 'debug' ? '#525252' : '#8B8B8E' }
52
+ function logColor(t) { return t === 'error' ? '#DC2626' : t === 'warn' ? '#EA580C' : t === 'debug' ? '#6B7280' : '#636366' }
73
53
  function clearLogs() { emit('clear') }
74
54
  function toggleAutoScroll() { autoScroll.value = !autoScroll.value; if (autoScroll.value) scrollToBottom() }
75
55
  function handleScroll() {
@@ -16,7 +16,7 @@
16
16
  <div class="flex items-center gap-2.5 mb-3">
17
17
  <span
18
18
  class="text-[12px] font-semibold font-[JetBrains_Mono,monospace] tracking-tight"
19
- :style="{ color: isActive ? '#FBBF24' : '#E4E4E7' }"
19
+ :style="{ color: isActive ? '#D97706' : '#1C1C1E' }"
20
20
  >
21
21
  {{ title }}
22
22
  </span>
@@ -25,7 +25,7 @@
25
25
 
26
26
  <!-- Steps -->
27
27
  <div class="space-y-1">
28
- <div v-if="steps.length === 0" class="text-[11px] italic py-1" style="color: #525252;">
28
+ <div v-if="steps.length === 0" class="text-[11px] italic py-1" style="color: #6B7280;">
29
29
  No steps yet
30
30
  </div>
31
31
  <StepCard
@@ -58,13 +58,13 @@ const props = defineProps({
58
58
  const emit = defineEmits(['select-step'])
59
59
 
60
60
  const nodeStyle = computed(() => {
61
- if (props.isActive) return { background: '#FBBF24', boxShadow: '0 0 8px rgba(251,191,36,0.4)' }
61
+ if (props.isActive) return { background: '#D97706', boxShadow: '0 0 8px rgba(251,191,36,0.4)' }
62
62
  const colors = {
63
- 'completed': '#34D399',
64
- 'in-progress': '#FBBF24',
65
- 'blocked': '#FB923C',
66
- 'failed': '#EF4444',
67
- 'pending': '#2A2A2D'
63
+ 'completed': '#16A34A',
64
+ 'in-progress': '#D97706',
65
+ 'blocked': '#EA580C',
66
+ 'failed': '#DC2626',
67
+ 'pending': '#E5E5EA'
68
68
  }
69
69
  return { background: colors[props.status] || colors.pending }
70
70
  })
@@ -1,53 +1,73 @@
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="brainstorm" title="头脑风暴" :steps="getStageSteps('brainstorm')" :status="getStageStatus('brainstorm')" :is-active="currentStage === 'brainstorm'" :active-step="activeStep" @select-step="handleSelectStep" />
30
+ <div v-if="hasStage('plan')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
31
+ <PipelineStage name="plan" title="规划" :steps="getStageSteps('plan')" :status="getStageStatus('plan')" :is-active="currentStage === 'plan'" :active-step="activeStep" @select-step="handleSelectStep" />
32
+ <div v-if="hasStage('execute')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
33
+ <PipelineStage name="execute" title="执行" :steps="getStageSteps('execute')" :status="getStageStatus('execute')" :is-active="currentStage === 'execute'" :active-step="activeStep" @select-step="handleSelectStep" />
34
+ <div v-if="hasStage('verify')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
35
+ <PipelineStage name="verify" title="验证" :steps="getStageSteps('verify')" :status="getStageStatus('verify')" :is-active="currentStage === 'verify'" :active-step="activeStep" @select-step="handleSelectStep" />
25
36
  </div>
26
37
 
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" />
38
+ <!-- Activity Log -->
39
+ <div v-if="project?.state?.progress" style="border-top: 1px solid #F0F0F3; background: rgba(0,0,0,0.03);">
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: #6B7280;">活动日志</div>
42
+ <div class="text-[10px] font-mono-log" style="color: #D1D1D6;">{{ activityLogs.length }}</div>
43
+ </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>
60
+ </div>
35
61
  </div>
36
62
  </div>
37
63
 
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>
64
+ <!-- Docs Tab -->
65
+ <div v-if="activeTab === 'docs'" class="flex-1 flex overflow-hidden">
66
+ <div class="w-[200px] flex-shrink-0 overflow-hidden" style="border-right: 1px solid #F0F0F3;">
67
+ <DocTree :groups="docs.groups" :selected-file="selectedDocFile" @select-file="$emit('select-doc-file', $event)" />
43
68
  </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>
69
+ <div class="flex-1 overflow-hidden">
70
+ <DocPreview :content="docContent" :loading="docLoading" />
51
71
  </div>
52
72
  </div>
53
73
  </div>
@@ -56,24 +76,39 @@
56
76
  <script setup>
57
77
  import { computed } from 'vue'
58
78
  import PipelineStage from './PipelineStage.vue'
79
+ import DocTree from './DocTree.vue'
80
+ import DocPreview from './DocPreview.vue'
59
81
 
60
82
  const props = defineProps({
61
83
  project: { type: Object, default: null },
62
- activeStep: { type: Object, default: null }
84
+ activeStep: { type: Object, default: null },
85
+ activeTab: { type: String, default: 'pipeline' },
86
+ docs: { type: Object, default: () => ({ groups: [] }) },
87
+ selectedDocFile: { type: Object, default: null },
88
+ docContent: { type: String, default: '' },
89
+ docLoading: { type: Boolean, default: false }
63
90
  })
64
91
 
65
- const emit = defineEmits(['select-step'])
92
+ const emit = defineEmits(['select-step', 'switch-tab', 'select-doc-file'])
66
93
 
67
- const currentStage = computed(() => props.project?.state?.currentStage || 'unknown')
94
+ const currentStage = computed(() => props.project?.state?.currentStage || '')
68
95
  const progress = computed(() => props.project?.state?.progress || {})
69
96
  const stages = computed(() => progress.value.stages || {})
70
97
 
98
+ const stageNameMap = {
99
+ brainstorm: '头脑风暴',
100
+ plan: '规划',
101
+ execute: '执行',
102
+ verify: '验证'
103
+ }
104
+
71
105
  const activityLogs = computed(() => {
72
106
  if (!props.project?.state) return []
73
107
  const logs = []
74
108
  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` })
109
+ const label = stageNameMap[name] || name
110
+ if (data.status === 'completed') logs.push({ time: data.completedAt ? formatTime(data.completedAt) : '--:--', status: 'completed', description: `${label} 已完成` })
111
+ else if (data.status === 'in-progress') logs.push({ time: '--:--', status: 'in-progress', description: `${label} 运行中` })
77
112
  }
78
113
  return logs.reverse()
79
114
  })
@@ -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>