sillyspec 3.20.3 → 3.20.5

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 (225) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +21 -17
  2. package/.claude/skills/sillyspec-auto/SKILL.md +83 -77
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -17
  4. package/.claude/skills/sillyspec-commit/SKILL.md +106 -105
  5. package/.claude/skills/sillyspec-continue/SKILL.md +45 -44
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +31 -22
  7. package/.claude/skills/sillyspec-execute/SKILL.md +30 -17
  8. package/.claude/skills/sillyspec-explore/SKILL.md +109 -96
  9. package/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  10. package/.claude/skills/sillyspec-plan/SKILL.md +21 -52
  11. package/.claude/skills/sillyspec-propose/SKILL.md +21 -17
  12. package/.claude/skills/sillyspec-quick/SKILL.md +21 -17
  13. package/.claude/skills/sillyspec-resume/SKILL.md +68 -111
  14. package/.claude/skills/sillyspec-scan/SKILL.md +21 -17
  15. package/.claude/skills/sillyspec-state/SKILL.md +54 -54
  16. package/.claude/skills/sillyspec-status/SKILL.md +21 -17
  17. package/.claude/skills/sillyspec-verify/SKILL.md +21 -17
  18. package/.claude/skills/sillyspec-workspace/SKILL.md +157 -149
  19. package/.husky/pre-push +13 -0
  20. package/CLAUDE.md +18 -0
  21. package/README.md +198 -186
  22. package/SKILL.md +90 -87
  23. package/bin/sillyspec.js +2 -2
  24. package/docs/brainstorm-plan-contract.md +64 -0
  25. package/docs/plan-execute-contract.md +123 -0
  26. package/docs/platform-scan-protocol.md +298 -0
  27. package/docs/revision-mode.md +115 -0
  28. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -0
  29. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -0
  30. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -0
  31. package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -0
  32. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -0
  33. package/docs/sillyspec/file-lifecycle.md +131 -0
  34. package/docs/workflow-contract-regression.md +106 -0
  35. package/docs/worktree-isolation.md +252 -0
  36. package/package.json +40 -34
  37. package/packages/dashboard/dist/assets/{index-D1EVTLmc.js → index-Bq_Z2hne.js} +7446 -7446
  38. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  39. package/packages/dashboard/dist/index.html +16 -16
  40. package/packages/dashboard/dist/prototype-dashboard.html +836 -0
  41. package/packages/dashboard/dist/prototype-overview.html +256 -0
  42. package/packages/dashboard/index.html +15 -15
  43. package/packages/dashboard/package-lock.json +2384 -2384
  44. package/packages/dashboard/package.json +25 -25
  45. package/packages/dashboard/public/prototype-dashboard.html +836 -0
  46. package/packages/dashboard/public/prototype-overview.html +256 -0
  47. package/packages/dashboard/server/executor.js +86 -86
  48. package/packages/dashboard/server/index.js +588 -508
  49. package/packages/dashboard/server/parser.js +526 -458
  50. package/packages/dashboard/server/watcher.js +350 -342
  51. package/packages/dashboard/src/App.vue +558 -325
  52. package/packages/dashboard/src/components/ActionBar.vue +93 -84
  53. package/packages/dashboard/src/components/CommandPalette.vue +96 -92
  54. package/packages/dashboard/src/components/DetailPanel.vue +137 -137
  55. package/packages/dashboard/src/components/DocPreview.vue +105 -8
  56. package/packages/dashboard/src/components/DocTree.vue +75 -19
  57. package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
  58. package/packages/dashboard/src/components/LogStream.vue +65 -65
  59. package/packages/dashboard/src/components/PipelineStage.vue +95 -75
  60. package/packages/dashboard/src/components/PipelineView.vue +156 -129
  61. package/packages/dashboard/src/components/ProjectCard.vue +187 -0
  62. package/packages/dashboard/src/components/ProjectList.vue +210 -210
  63. package/packages/dashboard/src/components/ProjectOverview.vue +113 -139
  64. package/packages/dashboard/src/components/StageBadge.vue +67 -53
  65. package/packages/dashboard/src/components/StepCard.vue +94 -89
  66. package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
  67. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
  68. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
  69. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
  70. package/packages/dashboard/src/composables/useDashboard.js +192 -164
  71. package/packages/dashboard/src/composables/useKeyboard.js +119 -119
  72. package/packages/dashboard/src/composables/useLayout.js +131 -0
  73. package/packages/dashboard/src/composables/useWebSocket.js +129 -129
  74. package/packages/dashboard/src/main.js +8 -8
  75. package/packages/dashboard/src/style.css +132 -132
  76. package/packages/dashboard/vite.config.js +18 -18
  77. package/src/brainstorm-postcheck.js +158 -0
  78. package/src/change-list.js +52 -0
  79. package/src/change-risk-profile.js +352 -0
  80. package/src/classify-change.js +73 -0
  81. package/src/constants.js +70 -0
  82. package/src/contract-matrix.js +278 -0
  83. package/src/db.js +201 -0
  84. package/src/endpoint-extractor.js +315 -0
  85. package/src/hooks/claude-pre-tool-use.cjs +125 -0
  86. package/src/hooks/worktree-guard.js +761 -0
  87. package/src/index.js +922 -252
  88. package/src/init.js +470 -375
  89. package/src/knowledge-match.js +130 -0
  90. package/src/migrate.js +117 -117
  91. package/src/modules.js +482 -0
  92. package/src/progress.js +1734 -508
  93. package/src/run.js +3465 -652
  94. package/src/scan-postcheck.js +387 -0
  95. package/src/setup.js +398 -460
  96. package/src/stage-contract.js +700 -0
  97. package/src/stages/archive.js +160 -54
  98. package/src/stages/brainstorm-auto.js +229 -0
  99. package/src/stages/brainstorm.js +645 -239
  100. package/src/stages/doctor.js +365 -312
  101. package/src/stages/execute.js +634 -264
  102. package/src/stages/explore.js +34 -0
  103. package/src/stages/index.js +29 -35
  104. package/src/stages/knowledge.js +498 -0
  105. package/src/stages/plan-postcheck.js +518 -0
  106. package/src/stages/plan.js +582 -279
  107. package/src/stages/propose.js +174 -115
  108. package/src/stages/quick.js +102 -63
  109. package/src/stages/scan.js +558 -141
  110. package/src/stages/status.js +65 -65
  111. package/src/stages/verify.js +322 -135
  112. package/src/sync.js +497 -0
  113. package/src/task-review.js +346 -0
  114. package/src/workflow.js +785 -0
  115. package/src/worktree-apply.js +549 -0
  116. package/src/worktree-deps.js +185 -0
  117. package/src/worktree.js +982 -0
  118. package/templates/workflows/archive-impact.yaml +79 -0
  119. package/templates/workflows/scan-docs.yaml +132 -0
  120. package/test/brainstorm-plan-contract.test.mjs +273 -0
  121. package/test/check-syntax.mjs +26 -0
  122. package/test/cli-top-level-aliases.test.mjs +174 -0
  123. package/test/contract-artifacts.test.mjs +323 -0
  124. package/test/decision-supersede.test.mjs +277 -0
  125. package/test/knowledge-match.test.mjs +231 -0
  126. package/test/plan-execute-contract.test.mjs +357 -0
  127. package/test/plan-optimization.test.mjs +572 -0
  128. package/test/platform-artifacts.test.mjs +190 -0
  129. package/test/platform-failure-samples.test.mjs +199 -0
  130. package/test/platform-recovery-chain.test.mjs +179 -0
  131. package/test/platform-recovery.test.mjs +167 -0
  132. package/test/platform-scan-p0.test.mjs +175 -0
  133. package/test/revision-v1.test.mjs +1145 -0
  134. package/test/run-sanitize-project-name.test.mjs +51 -0
  135. package/test/run-scan-postcheck-fail.test.mjs +64 -0
  136. package/test/run-scan-project-parse.test.mjs +200 -0
  137. package/test/run-tests.mjs +48 -0
  138. package/test/runtime-cleanup-keeps-worktree.test.mjs +107 -0
  139. package/test/scan-docs-yaml-placeholders.test.mjs +84 -0
  140. package/test/scan-knowledge.test.mjs +175 -0
  141. package/test/scan-paths.test.mjs +68 -0
  142. package/test/scan-postcheck-project-priority.test.mjs +85 -0
  143. package/test/scan-postcheck.test.mjs +197 -0
  144. package/test/scan-workflow-anyfailed-block.test.mjs +52 -0
  145. package/test/spec-dir.test.mjs +206 -0
  146. package/test/stage-contract-failed-post-check.test.mjs +102 -0
  147. package/test/stage-contract.test.mjs +299 -0
  148. package/test/stage-definitions.test.mjs +39 -0
  149. package/test/wait-gates.test.mjs +501 -0
  150. package/test/workflow-spec-base.test.mjs +142 -0
  151. package/test/worktree-deps-provision.test.mjs +148 -0
  152. package/test/worktree-guard.test.mjs +136 -0
  153. package/test/worktree-native-overlay.test.mjs +188 -0
  154. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
  155. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
  156. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
  157. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
  158. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
  159. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
  160. package/.sillyspec/changes/auto-mode/design.md +0 -50
  161. package/.sillyspec/changes/auto-mode/proposal.md +0 -19
  162. package/.sillyspec/changes/auto-mode/requirements.md +0 -21
  163. package/.sillyspec/changes/auto-mode/tasks.md +0 -7
  164. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +0 -206
  165. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
  166. package/.sillyspec/changes/dashboard/design.md +0 -219
  167. package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
  168. package/.sillyspec/changes/run-command-design/design.md +0 -1230
  169. package/.sillyspec/changes/unified-docs-design/design.md +0 -199
  170. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  171. package/.sillyspec/knowledge/INDEX.md +0 -8
  172. package/.sillyspec/knowledge/uncategorized.md +0 -3
  173. package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
  174. package/.sillyspec/projects/sillyspec.yaml +0 -3
  175. package/dist/steps/brainstorm/01-load-context.md +0 -30
  176. package/dist/steps/brainstorm/02-reuse-check.md +0 -6
  177. package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
  178. package/dist/steps/brainstorm/04-module-split.md +0 -23
  179. package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
  180. package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
  181. package/dist/steps/brainstorm/07-present-design.md +0 -3
  182. package/dist/steps/brainstorm/08-write-design.md +0 -21
  183. package/dist/steps/brainstorm/09-self-review.md +0 -15
  184. package/dist/steps/brainstorm/10-user-confirm.md +0 -3
  185. package/dist/steps/brainstorm/11-output-spec.md +0 -7
  186. package/dist/steps/brainstorm/manifest.yaml +0 -26
  187. package/dist/steps/execute/01-load-context.md +0 -41
  188. package/dist/steps/execute/02-scan-conventions.md +0 -47
  189. package/dist/steps/execute/03-skill-mcp.md +0 -19
  190. package/dist/steps/execute/04-assign-task.md +0 -22
  191. package/dist/steps/execute/04b-prompt-template.md +0 -54
  192. package/dist/steps/execute/05-write-test.md +0 -7
  193. package/dist/steps/execute/06-write-code.md +0 -8
  194. package/dist/steps/execute/07-run-test.md +0 -26
  195. package/dist/steps/execute/08-fix-issues.md +0 -28
  196. package/dist/steps/execute/09-next-task.md +0 -33
  197. package/dist/steps/execute/manifest.yaml +0 -28
  198. package/dist/steps/plan/01-load-context.md +0 -22
  199. package/dist/steps/plan/02-anchor-confirm.md +0 -1
  200. package/dist/steps/plan/03-expand-tasks.md +0 -33
  201. package/dist/steps/plan/04-mark-order.md +0 -15
  202. package/dist/steps/plan/05-e2e-planning.md +0 -17
  203. package/dist/steps/plan/06-self-check.md +0 -16
  204. package/dist/steps/plan/07-save.md +0 -1
  205. package/dist/steps/plan/manifest.yaml +0 -18
  206. package/dist/steps/scan/01-env-detect.md +0 -51
  207. package/dist/steps/scan/02-tech-stack.md +0 -16
  208. package/dist/steps/scan/03-conventions.md +0 -16
  209. package/dist/steps/scan/04-structure.md +0 -19
  210. package/dist/steps/scan/05-quality.md +0 -18
  211. package/dist/steps/scan/06-complete.md +0 -49
  212. package/dist/steps/scan/manifest.yaml +0 -16
  213. package/dist/steps/verify/01-load-specs.md +0 -28
  214. package/dist/steps/verify/02-check-tasks.md +0 -1
  215. package/dist/steps/verify/03-check-design.md +0 -6
  216. package/dist/steps/verify/04-run-tests.md +0 -7
  217. package/dist/steps/verify/05-e2e-tests.md +0 -27
  218. package/dist/steps/verify/05b-e2e-fix.md +0 -33
  219. package/dist/steps/verify/06-code-quality.md +0 -25
  220. package/dist/steps/verify/07-lint-check.md +0 -27
  221. package/dist/steps/verify/08-output-report.md +0 -14
  222. package/dist/steps/verify/manifest.yaml +0 -22
  223. package/packages/dashboard/dist/assets/index-DGe8CqeP.css +0 -1
  224. package/src/derive.js +0 -147
  225. package/src/step.js +0 -543
@@ -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>
@@ -1,210 +1,210 @@
1
- <template>
2
- <div class="h-full flex flex-col noise-bg">
3
- <!-- Header -->
4
- <div class="relative z-10 px-5 pt-5 pb-4">
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, #D97706 0%, #F59E0B 100%); clip-path: polygon(0 0, 100% 0, 85% 100%, 15% 100%);">
7
- <span class="text-[10px] font-bold text-black font-[JetBrains_Mono,monospace]">S</span>
8
- </div>
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;">
12
- SillySpec
13
- </h1>
14
- <p class="text-[10px] tracking-widest uppercase" style="color: #6B7280;">控制台</p>
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>
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>
48
- </div>
49
-
50
- <!-- Divider -->
51
- <div class="mx-4 h-px" style="background: linear-gradient(90deg, transparent, #E5E5EA, transparent);"></div>
52
-
53
- <!-- Projects List -->
54
- <div class="flex-1 overflow-y-auto py-3 relative z-10">
55
- <!-- Loading skeleton -->
56
- <div v-if="isLoading" class="px-4 space-y-2">
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
- 正在扫描项目...
63
- </p>
64
- </div>
65
-
66
- <!-- Empty state -->
67
- <n-empty v-else-if="projects.length === 0" description="未发现 SillySpec 项目" style="padding: 48px 0;" />
68
-
69
- <!-- Projects -->
70
- <div v-else class="px-3 space-y-0.5">
71
- <div
72
- v-for="project in projects"
73
- :key="project.path"
74
- :class="['relative rounded-md cursor-pointer transition-all duration-150 overflow-hidden group']"
75
- :style="{
76
- background: isActive(project) ? 'rgba(217,119,6,0.06)' : 'transparent',
77
- borderLeft: isActive(project) ? '2px solid #D97706' : '2px solid transparent',
78
- }"
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'"
81
- @click="$emit('select', project)"
82
- >
83
- <div class="px-3 py-2.5">
84
- <div class="flex items-center justify-between gap-2">
85
- <div class="flex-1 min-w-0">
86
- <h3
87
- class="text-[12px] font-medium truncate transition-colors duration-150 font-[JetBrains_Mono,monospace]"
88
- :style="{ color: isActive(project) ? '#D97706' : '#1C1C1E' }"
89
- >
90
- {{ project.name }}
91
- </h3>
92
- <p class="text-[10px] mt-0.5 truncate font-mono-log" style="color: #6B7280;">
93
- {{ project.path }}
94
- </p>
95
- </div>
96
- <n-tag
97
- v-if="project.state?.currentStage"
98
- :type="statusTagType(getProjectStatus(project))"
99
- size="small"
100
- :bordered="false"
101
- round
102
- >
103
- {{ stageLabel(project) }}
104
- </n-tag>
105
- </div>
106
-
107
- <!-- Progress -->
108
- <div v-if="project.state?.progress" class="mt-2 h-[2px] rounded-full overflow-hidden" style="background: #FFFFFF;">
109
- <div
110
- class="h-full rounded-full transition-all duration-500 progress-gradient"
111
- :style="{ width: getProjectProgress(project) + '%' }"
112
- />
113
- </div>
114
- </div>
115
- </div>
116
- </div>
117
- </div>
118
-
119
- <!-- Footer -->
120
- <div class="relative z-10 px-4 py-2.5" style="border-top: 1px solid #F0F0F3;">
121
- <div class="flex items-center justify-between">
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>
124
- </div>
125
- </div>
126
- </div>
127
- </template>
128
-
129
- <script setup>
130
- import { ref, nextTick, watch } from 'vue'
131
-
132
- const props = defineProps({
133
- projects: { type: Array, default: () => [] },
134
- activeProject: { type: Object, default: null },
135
- isLoading: { type: Boolean, default: false },
136
- scanPaths: { type: Array, default: () => [] }
137
- })
138
-
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
- })
149
-
150
- function isActive(project) {
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'
157
- }
158
-
159
- function getProjectStatus(project) {
160
- const stage = project.state?.currentStage
161
- if (!stage) return 'pending'
162
- const stageData = project.state?.progress?.stages?.[stage]
163
- if (!stageData) return 'pending'
164
- const steps = stageData.steps || []
165
- if (steps.length === 0) return 'pending'
166
- if (steps.some(s => s.status === 'failed')) return 'failed'
167
- if (steps.some(s => s.status === 'blocked')) return 'blocked'
168
- if (steps.some(s => s.status === 'in-progress')) return 'in-progress'
169
- if (steps.every(s => s.status === 'completed')) return 'completed'
170
- return 'pending'
171
- }
172
-
173
- function stageLabel(project) {
174
- const stage = project.state?.currentStage
175
- const labels = { 'brainstorm': '头脑风暴', 'plan': '规划', 'execute': '执行', 'verify': '验证' }
176
- return labels[stage] || stage || '未知'
177
- }
178
-
179
- function getProjectProgress(project) {
180
- const progress = project.state?.progress
181
- if (!progress) return 0
182
- const stages = progress.stages || {}
183
- const stageNames = ['brainstorm', 'plan', 'execute', 'verify']
184
- let total = 0, done = 0
185
- for (const s of stageNames) {
186
- const st = stages[s]
187
- if (st?.steps) { total += st.steps.length; done += st.steps.filter(x => x.status === 'completed').length }
188
- }
189
- return total === 0 ? 0 : Math.round((done / total) * 100)
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
- }
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>
1
+ <template>
2
+ <div class="h-full flex flex-col noise-bg">
3
+ <!-- Header -->
4
+ <div class="relative z-10 px-5 pt-5 pb-4">
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, #D97706 0%, #F59E0B 100%); clip-path: polygon(0 0, 100% 0, 85% 100%, 15% 100%);">
7
+ <span class="text-[10px] font-bold text-black font-[JetBrains_Mono,monospace]">S</span>
8
+ </div>
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;">
12
+ SillySpec
13
+ </h1>
14
+ <p class="text-[10px] tracking-widest uppercase" style="color: #6B7280;">控制台</p>
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>
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>
48
+ </div>
49
+
50
+ <!-- Divider -->
51
+ <div class="mx-4 h-px" style="background: linear-gradient(90deg, transparent, #E5E5EA, transparent);"></div>
52
+
53
+ <!-- Projects List -->
54
+ <div class="flex-1 overflow-y-auto py-3 relative z-10">
55
+ <!-- Loading skeleton -->
56
+ <div v-if="isLoading" class="px-4 space-y-2">
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
+ 正在扫描项目...
63
+ </p>
64
+ </div>
65
+
66
+ <!-- Empty state -->
67
+ <n-empty v-else-if="projects.length === 0" description="未发现 SillySpec 项目" style="padding: 48px 0;" />
68
+
69
+ <!-- Projects -->
70
+ <div v-else class="px-3 space-y-0.5">
71
+ <div
72
+ v-for="project in projects"
73
+ :key="project.path"
74
+ :class="['relative rounded-md cursor-pointer transition-all duration-150 overflow-hidden group']"
75
+ :style="{
76
+ background: isActive(project) ? 'rgba(217,119,6,0.06)' : 'transparent',
77
+ borderLeft: isActive(project) ? '2px solid #D97706' : '2px solid transparent',
78
+ }"
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'"
81
+ @click="$emit('select', project)"
82
+ >
83
+ <div class="px-3 py-2.5">
84
+ <div class="flex items-center justify-between gap-2">
85
+ <div class="flex-1 min-w-0">
86
+ <h3
87
+ class="text-[12px] font-medium truncate transition-colors duration-150 font-[JetBrains_Mono,monospace]"
88
+ :style="{ color: isActive(project) ? '#D97706' : '#1C1C1E' }"
89
+ >
90
+ {{ project.name }}
91
+ </h3>
92
+ <p class="text-[10px] mt-0.5 truncate font-mono-log" style="color: #6B7280;">
93
+ {{ project.path }}
94
+ </p>
95
+ </div>
96
+ <n-tag
97
+ v-if="project.state?.currentStage"
98
+ :type="statusTagType(getProjectStatus(project))"
99
+ size="small"
100
+ :bordered="false"
101
+ round
102
+ >
103
+ {{ stageLabel(project) }}
104
+ </n-tag>
105
+ </div>
106
+
107
+ <!-- Progress -->
108
+ <div v-if="project.state?.progress" class="mt-2 h-[2px] rounded-full overflow-hidden" style="background: #FFFFFF;">
109
+ <div
110
+ class="h-full rounded-full transition-all duration-500 progress-gradient"
111
+ :style="{ width: getProjectProgress(project) + '%' }"
112
+ />
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ </div>
118
+
119
+ <!-- Footer -->
120
+ <div class="relative z-10 px-4 py-2.5" style="border-top: 1px solid #F0F0F3;">
121
+ <div class="flex items-center justify-between">
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>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </template>
128
+
129
+ <script setup>
130
+ import { ref, nextTick, watch } from 'vue'
131
+
132
+ const props = defineProps({
133
+ projects: { type: Array, default: () => [] },
134
+ activeProject: { type: Object, default: null },
135
+ isLoading: { type: Boolean, default: false },
136
+ scanPaths: { type: Array, default: () => [] }
137
+ })
138
+
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
+ })
149
+
150
+ function isActive(project) {
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'
157
+ }
158
+
159
+ function getProjectStatus(project) {
160
+ const stage = project.state?.currentStage
161
+ if (!stage) return 'pending'
162
+ const stageData = project.state?.progress?.stages?.[stage]
163
+ if (!stageData) return 'pending'
164
+ const steps = stageData.steps || []
165
+ if (steps.length === 0) return 'pending'
166
+ if (steps.some(s => s.status === 'failed')) return 'failed'
167
+ if (steps.some(s => s.status === 'blocked')) return 'blocked'
168
+ if (steps.some(s => s.status === 'in-progress')) return 'in-progress'
169
+ if (steps.every(s => s.status === 'completed')) return 'completed'
170
+ return 'pending'
171
+ }
172
+
173
+ function stageLabel(project) {
174
+ const stage = project.state?.currentStage
175
+ const labels = { 'brainstorm': '头脑风暴', 'plan': '规划', 'execute': '执行', 'verify': '验证' }
176
+ return labels[stage] || stage || '未知'
177
+ }
178
+
179
+ function getProjectProgress(project) {
180
+ const progress = project.state?.progress
181
+ if (!progress) return 0
182
+ const stages = progress.stages || {}
183
+ const stageNames = ['brainstorm', 'plan', 'execute', 'verify']
184
+ let total = 0, done = 0
185
+ for (const s of stageNames) {
186
+ const st = stages[s]
187
+ if (st?.steps) { total += st.steps.length; done += st.steps.filter(x => x.status === 'completed').length }
188
+ }
189
+ return total === 0 ? 0 : Math.round((done / total) * 100)
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
+ }
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>