sillyspec 3.20.3 → 3.20.4

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 (224) 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 +125 -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 +431 -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 +625 -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 +511 -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 +330 -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/scan-docs-yaml-placeholders.test.mjs +84 -0
  139. package/test/scan-knowledge.test.mjs +175 -0
  140. package/test/scan-paths.test.mjs +68 -0
  141. package/test/scan-postcheck-project-priority.test.mjs +85 -0
  142. package/test/scan-postcheck.test.mjs +197 -0
  143. package/test/scan-workflow-anyfailed-block.test.mjs +52 -0
  144. package/test/spec-dir.test.mjs +206 -0
  145. package/test/stage-contract-failed-post-check.test.mjs +102 -0
  146. package/test/stage-contract.test.mjs +299 -0
  147. package/test/stage-definitions.test.mjs +39 -0
  148. package/test/wait-gates.test.mjs +501 -0
  149. package/test/workflow-spec-base.test.mjs +142 -0
  150. package/test/worktree-deps-provision.test.mjs +148 -0
  151. package/test/worktree-guard.test.mjs +136 -0
  152. package/test/worktree-native-overlay.test.mjs +188 -0
  153. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
  154. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
  155. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
  156. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
  157. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
  158. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
  159. package/.sillyspec/changes/auto-mode/design.md +0 -50
  160. package/.sillyspec/changes/auto-mode/proposal.md +0 -19
  161. package/.sillyspec/changes/auto-mode/requirements.md +0 -21
  162. package/.sillyspec/changes/auto-mode/tasks.md +0 -7
  163. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +0 -206
  164. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
  165. package/.sillyspec/changes/dashboard/design.md +0 -219
  166. package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
  167. package/.sillyspec/changes/run-command-design/design.md +0 -1230
  168. package/.sillyspec/changes/unified-docs-design/design.md +0 -199
  169. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  170. package/.sillyspec/knowledge/INDEX.md +0 -8
  171. package/.sillyspec/knowledge/uncategorized.md +0 -3
  172. package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
  173. package/.sillyspec/projects/sillyspec.yaml +0 -3
  174. package/dist/steps/brainstorm/01-load-context.md +0 -30
  175. package/dist/steps/brainstorm/02-reuse-check.md +0 -6
  176. package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
  177. package/dist/steps/brainstorm/04-module-split.md +0 -23
  178. package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
  179. package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
  180. package/dist/steps/brainstorm/07-present-design.md +0 -3
  181. package/dist/steps/brainstorm/08-write-design.md +0 -21
  182. package/dist/steps/brainstorm/09-self-review.md +0 -15
  183. package/dist/steps/brainstorm/10-user-confirm.md +0 -3
  184. package/dist/steps/brainstorm/11-output-spec.md +0 -7
  185. package/dist/steps/brainstorm/manifest.yaml +0 -26
  186. package/dist/steps/execute/01-load-context.md +0 -41
  187. package/dist/steps/execute/02-scan-conventions.md +0 -47
  188. package/dist/steps/execute/03-skill-mcp.md +0 -19
  189. package/dist/steps/execute/04-assign-task.md +0 -22
  190. package/dist/steps/execute/04b-prompt-template.md +0 -54
  191. package/dist/steps/execute/05-write-test.md +0 -7
  192. package/dist/steps/execute/06-write-code.md +0 -8
  193. package/dist/steps/execute/07-run-test.md +0 -26
  194. package/dist/steps/execute/08-fix-issues.md +0 -28
  195. package/dist/steps/execute/09-next-task.md +0 -33
  196. package/dist/steps/execute/manifest.yaml +0 -28
  197. package/dist/steps/plan/01-load-context.md +0 -22
  198. package/dist/steps/plan/02-anchor-confirm.md +0 -1
  199. package/dist/steps/plan/03-expand-tasks.md +0 -33
  200. package/dist/steps/plan/04-mark-order.md +0 -15
  201. package/dist/steps/plan/05-e2e-planning.md +0 -17
  202. package/dist/steps/plan/06-self-check.md +0 -16
  203. package/dist/steps/plan/07-save.md +0 -1
  204. package/dist/steps/plan/manifest.yaml +0 -18
  205. package/dist/steps/scan/01-env-detect.md +0 -51
  206. package/dist/steps/scan/02-tech-stack.md +0 -16
  207. package/dist/steps/scan/03-conventions.md +0 -16
  208. package/dist/steps/scan/04-structure.md +0 -19
  209. package/dist/steps/scan/05-quality.md +0 -18
  210. package/dist/steps/scan/06-complete.md +0 -49
  211. package/dist/steps/scan/manifest.yaml +0 -16
  212. package/dist/steps/verify/01-load-specs.md +0 -28
  213. package/dist/steps/verify/02-check-tasks.md +0 -1
  214. package/dist/steps/verify/03-check-design.md +0 -6
  215. package/dist/steps/verify/04-run-tests.md +0 -7
  216. package/dist/steps/verify/05-e2e-tests.md +0 -27
  217. package/dist/steps/verify/05b-e2e-fix.md +0 -33
  218. package/dist/steps/verify/06-code-quality.md +0 -25
  219. package/dist/steps/verify/07-lint-check.md +0 -27
  220. package/dist/steps/verify/08-output-report.md +0 -14
  221. package/dist/steps/verify/manifest.yaml +0 -22
  222. package/packages/dashboard/dist/assets/index-DGe8CqeP.css +0 -1
  223. package/src/derive.js +0 -147
  224. package/src/step.js +0 -543
@@ -1,137 +1,137 @@
1
- <template>
2
- <div
3
- :class="['flex flex-col transition-all duration-300', isOpen ? 'w-[340px]' : 'w-0 opacity-0 overflow-hidden']"
4
- style="background: #F5F5F7;"
5
- >
6
- <!-- Header -->
7
- <div class="px-4 py-3 flex items-center justify-between flex-shrink-0" style="border-bottom: 1px solid #F0F0F3;">
8
- <h2 class="text-[11px] font-semibold uppercase tracking-[0.2em] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">{{ detailType ? detailTitle : '详情' }}</h2>
9
- <n-button quaternary size="tiny" @click="handleClose">
10
- <template #icon>
11
- <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
13
- </svg>
14
- </template>
15
- </n-button>
16
- </div>
17
-
18
- <!-- Content -->
19
- <div class="flex-1 overflow-y-auto">
20
- <!-- Empty state -->
21
- <n-empty v-if="!activeStep && !detailType" description="选择一个步骤" style="margin: auto; padding: 48px 0;" />
22
-
23
- <!-- Detail views -->
24
- <template v-if="detailType === 'git'">
25
- <GitDetail :data="detailData" />
26
- </template>
27
- <template v-else-if="detailType === 'tech'">
28
- <TechDetail :data="detailData" />
29
- </template>
30
- <template v-else-if="detailType === 'docs'">
31
- <DocsDetail :data="detailData" @open-file="$emit('open-doc-file', $event)" />
32
- </template>
33
-
34
- <!-- Step detail (original) -->
35
- <div v-else-if="activeStep">
36
- <!-- Title -->
37
- <div class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
38
- <h3 class="text-[13px] font-semibold font-[JetBrains_Mono,monospace]" style="color: #D97706;">
39
- {{ activeStep.title || activeStep.name }}
40
- </h3>
41
- <div v-if="activeStep.status" class="mt-2">
42
- <StageBadge :status="activeStep.status" />
43
- </div>
44
- </div>
45
-
46
- <!-- Description -->
47
- <div v-if="activeStep.description || activeStep.summary" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
48
- <h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">描述</h4>
49
- <p class="text-[11px] leading-relaxed" style="color: #636366;">{{ activeStep.description || activeStep.summary }}</p>
50
- </div>
51
-
52
- <!-- Conclusion -->
53
- <div v-if="activeStep.conclusion" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
54
- <h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">结论</h4>
55
- <p class="text-[11px] leading-relaxed" style="color: #1C1C1E;">{{ activeStep.conclusion }}</p>
56
- </div>
57
-
58
- <!-- Decision -->
59
- <div v-if="activeStep.decision" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
60
- <h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">决策</h4>
61
- <p class="text-[11px] leading-relaxed" style="color: #1C1C1E;">{{ activeStep.decision }}</p>
62
- </div>
63
-
64
- <!-- User Query -->
65
- <div v-if="activeStep.userQuery" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
66
- <h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">用户提问</h4>
67
- <div class="px-3 py-2 rounded-md" style="background: #F5F5F7; border: 1px solid #F0F0F3;">
68
- <p class="text-[11px] italic" style="color: #636366;">"{{ activeStep.userQuery }}"</p>
69
- </div>
70
- </div>
71
-
72
- <!-- Metadata -->
73
- <div v-if="activeStep.duration || activeStep.timestamp" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
74
- <h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">元信息</h4>
75
- <div class="space-y-1 text-[11px]" style="color: #6B7280;">
76
- <div v-if="activeStep.duration"><span style="color: #636366;">耗时:</span> {{ activeStep.duration }}</div>
77
- <div v-if="activeStep.timestamp"><span style="color: #636366;">时间:</span> {{ formatTimestamp(activeStep.timestamp) }}</div>
78
- </div>
79
- </div>
80
-
81
- <!-- Output -->
82
- <div v-if="activeStep.output || activeStep.files" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
83
- <h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">输出</h4>
84
- <n-code v-if="activeStep.output" :code="activeStep.output" language="text" word-wrap style="max-height: 300px; overflow-y: auto; padding: 8px; border-radius: 4px; background: #F5F5F7;" />
85
- <div v-if="activeStep.files" class="mt-2 space-y-1">
86
- <div v-for="(file, i) in activeStep.files" :key="i" class="flex items-center gap-2 text-[10px]" style="color: #6B7280;">
87
- <svg class="w-3 h-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
88
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
89
- </svg>
90
- <span class="truncate">{{ file }}</span>
91
- </div>
92
- </div>
93
- </div>
94
- </div>
95
- </div>
96
-
97
- <!-- Log Stream -->
98
- <div class="flex-shrink-0" style="height: 200px; border-top: 1px solid #F0F0F3;">
99
- <LogStream :logs="logs" @clear="$emit('clear-logs')" />
100
- </div>
101
- </div>
102
- </template>
103
-
104
- <script setup>
105
- import { computed } from 'vue'
106
- import StageBadge from './StageBadge.vue'
107
- import LogStream from './LogStream.vue'
108
- import GitDetail from './detail/GitDetail.vue'
109
- import TechDetail from './detail/TechDetail.vue'
110
- import DocsDetail from './detail/DocsDetail.vue'
111
-
112
- const props = defineProps({
113
- isOpen: { type: Boolean, default: true },
114
- activeStep: { type: Object, default: null },
115
- logs: { type: Array, default: () => [] },
116
- detailType: { type: String, default: null },
117
- detailData: { type: [Object, Array], default: null }
118
- })
119
-
120
- const emit = defineEmits(['close', 'clear-logs', 'open-doc-file'])
121
-
122
- const detailTitle = computed(() => {
123
- if (props.detailType === 'git') return 'Git 详情'
124
- if (props.detailType === 'tech') return '技术栈详情'
125
- if (props.detailType === 'docs') return '文档列表'
126
- return '详情'
127
- })
128
-
129
- function handleClose() {
130
- emit('close')
131
- }
132
-
133
- function formatTimestamp(ts) {
134
- if (!ts) return ''
135
- return new Date(ts).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
136
- }
137
- </script>
1
+ <template>
2
+ <div
3
+ :class="['flex flex-col transition-all duration-300', isOpen ? 'w-[340px]' : 'w-0 opacity-0 overflow-hidden']"
4
+ style="background: #F5F5F7;"
5
+ >
6
+ <!-- Header -->
7
+ <div class="px-4 py-3 flex items-center justify-between flex-shrink-0" style="border-bottom: 1px solid #F0F0F3;">
8
+ <h2 class="text-[11px] font-semibold uppercase tracking-[0.2em] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">{{ detailType ? detailTitle : '详情' }}</h2>
9
+ <n-button quaternary size="tiny" @click="handleClose">
10
+ <template #icon>
11
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
13
+ </svg>
14
+ </template>
15
+ </n-button>
16
+ </div>
17
+
18
+ <!-- Content -->
19
+ <div class="flex-1 overflow-y-auto">
20
+ <!-- Empty state -->
21
+ <n-empty v-if="!activeStep && !detailType" description="选择一个步骤" style="margin: auto; padding: 48px 0;" />
22
+
23
+ <!-- Detail views -->
24
+ <template v-if="detailType === 'git'">
25
+ <GitDetail :data="detailData" />
26
+ </template>
27
+ <template v-else-if="detailType === 'tech'">
28
+ <TechDetail :data="detailData" />
29
+ </template>
30
+ <template v-else-if="detailType === 'docs'">
31
+ <DocsDetail :data="detailData" @open-file="$emit('open-doc-file', $event)" />
32
+ </template>
33
+
34
+ <!-- Step detail (original) -->
35
+ <div v-else-if="activeStep">
36
+ <!-- Title -->
37
+ <div class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
38
+ <h3 class="text-[13px] font-semibold font-[JetBrains_Mono,monospace]" style="color: #D97706;">
39
+ {{ activeStep.title || activeStep.name }}
40
+ </h3>
41
+ <div v-if="activeStep.status" class="mt-2">
42
+ <StageBadge :status="activeStep.status" />
43
+ </div>
44
+ </div>
45
+
46
+ <!-- Description -->
47
+ <div v-if="activeStep.description || activeStep.summary" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
48
+ <h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">描述</h4>
49
+ <p class="text-[11px] leading-relaxed" style="color: #636366;">{{ activeStep.description || activeStep.summary }}</p>
50
+ </div>
51
+
52
+ <!-- Conclusion -->
53
+ <div v-if="activeStep.conclusion" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
54
+ <h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">结论</h4>
55
+ <p class="text-[11px] leading-relaxed" style="color: #1C1C1E;">{{ activeStep.conclusion }}</p>
56
+ </div>
57
+
58
+ <!-- Decision -->
59
+ <div v-if="activeStep.decision" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
60
+ <h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">决策</h4>
61
+ <p class="text-[11px] leading-relaxed" style="color: #1C1C1E;">{{ activeStep.decision }}</p>
62
+ </div>
63
+
64
+ <!-- User Query -->
65
+ <div v-if="activeStep.userQuery" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
66
+ <h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">用户提问</h4>
67
+ <div class="px-3 py-2 rounded-md" style="background: #F5F5F7; border: 1px solid #F0F0F3;">
68
+ <p class="text-[11px] italic" style="color: #636366;">"{{ activeStep.userQuery }}"</p>
69
+ </div>
70
+ </div>
71
+
72
+ <!-- Metadata -->
73
+ <div v-if="activeStep.duration || activeStep.timestamp" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
74
+ <h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">元信息</h4>
75
+ <div class="space-y-1 text-[11px]" style="color: #6B7280;">
76
+ <div v-if="activeStep.duration"><span style="color: #636366;">耗时:</span> {{ activeStep.duration }}</div>
77
+ <div v-if="activeStep.timestamp"><span style="color: #636366;">时间:</span> {{ formatTimestamp(activeStep.timestamp) }}</div>
78
+ </div>
79
+ </div>
80
+
81
+ <!-- Output -->
82
+ <div v-if="activeStep.output || activeStep.files" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
83
+ <h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">输出</h4>
84
+ <n-code v-if="activeStep.output" :code="activeStep.output" language="text" word-wrap style="max-height: 300px; overflow-y: auto; padding: 8px; border-radius: 4px; background: #F5F5F7;" />
85
+ <div v-if="activeStep.files" class="mt-2 space-y-1">
86
+ <div v-for="(file, i) in activeStep.files" :key="i" class="flex items-center gap-2 text-[10px]" style="color: #6B7280;">
87
+ <svg class="w-3 h-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
88
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
89
+ </svg>
90
+ <span class="truncate">{{ file }}</span>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+
97
+ <!-- Log Stream -->
98
+ <div class="flex-shrink-0" style="height: 200px; border-top: 1px solid #F0F0F3;">
99
+ <LogStream :logs="logs" @clear="$emit('clear-logs')" />
100
+ </div>
101
+ </div>
102
+ </template>
103
+
104
+ <script setup>
105
+ import { computed } from 'vue'
106
+ import StageBadge from './StageBadge.vue'
107
+ import LogStream from './LogStream.vue'
108
+ import GitDetail from './detail/GitDetail.vue'
109
+ import TechDetail from './detail/TechDetail.vue'
110
+ import DocsDetail from './detail/DocsDetail.vue'
111
+
112
+ const props = defineProps({
113
+ isOpen: { type: Boolean, default: true },
114
+ activeStep: { type: Object, default: null },
115
+ logs: { type: Array, default: () => [] },
116
+ detailType: { type: String, default: null },
117
+ detailData: { type: [Object, Array], default: null }
118
+ })
119
+
120
+ const emit = defineEmits(['close', 'clear-logs', 'open-doc-file'])
121
+
122
+ const detailTitle = computed(() => {
123
+ if (props.detailType === 'git') return 'Git 详情'
124
+ if (props.detailType === 'tech') return '技术栈详情'
125
+ if (props.detailType === 'docs') return '文档列表'
126
+ return '详情'
127
+ })
128
+
129
+ function handleClose() {
130
+ emit('close')
131
+ }
132
+
133
+ function formatTimestamp(ts) {
134
+ if (!ts) return ''
135
+ return new Date(ts).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
136
+ }
137
+ </script>
@@ -1,29 +1,59 @@
1
1
  <template>
2
- <div class="h-full overflow-y-auto px-6 py-4">
3
- <div v-if="!content && !loading" class="flex items-center justify-center h-full">
4
- <p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #636366;">选择一个文档查看内容</p>
2
+ <div class="doc-preview-shell">
3
+ <div v-if="!content && !loading" class="doc-empty">
4
+ <p>选择一个文档查看内容</p>
5
5
  </div>
6
- <div v-else-if="loading" class="flex items-center justify-center h-full">
7
- <p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #636366;">加载中...</p>
6
+ <div v-else-if="loading" class="doc-empty">
7
+ <p>加载中...</p>
8
8
  </div>
9
- <div v-else class="doc-preview" v-html="renderedContent"></div>
9
+ <template v-else>
10
+ <div class="doc-toolbar">
11
+ <div class="doc-title" :title="fileTitle">{{ fileTitle }}</div>
12
+ <n-button size="tiny" type="primary" @click="isModalOpen = true">弹窗阅读</n-button>
13
+ </div>
14
+ <div class="doc-preview-scroll">
15
+ <div class="doc-preview" v-html="renderedContent"></div>
16
+ </div>
17
+ </template>
18
+
19
+ <n-modal
20
+ v-model:show="isModalOpen"
21
+ preset="card"
22
+ :title="fileTitle"
23
+ style="width: min(920px, calc(100vw - 48px));"
24
+ >
25
+ <div class="doc-modal-body">
26
+ <div class="doc-preview doc-preview-large" v-html="renderedContent"></div>
27
+ </div>
28
+ </n-modal>
10
29
  </div>
11
30
  </template>
12
31
 
13
32
  <script setup>
14
- import { computed } from 'vue'
33
+ import { computed, ref, watch } from 'vue'
15
34
  import { marked } from 'marked'
16
35
 
17
36
  const props = defineProps({
18
37
  content: { type: String, default: '' },
19
- loading: { type: Boolean, default: false }
38
+ loading: { type: Boolean, default: false },
39
+ selectedFile: { type: Object, default: null }
20
40
  })
21
41
 
42
+ const isModalOpen = ref(false)
43
+
22
44
  marked.setOptions({
23
45
  breaks: true,
24
46
  gfm: true
25
47
  })
26
48
 
49
+ watch(() => props.selectedFile?.path, () => {
50
+ isModalOpen.value = false
51
+ })
52
+
53
+ const fileTitle = computed(() => {
54
+ return props.selectedFile?.title || props.selectedFile?.name || '文档'
55
+ })
56
+
27
57
  const renderedContent = computed(() => {
28
58
  if (!props.content) return ''
29
59
  return marked.parse(props.content)
@@ -31,10 +61,69 @@ const renderedContent = computed(() => {
31
61
  </script>
32
62
 
33
63
  <style scoped>
64
+ .doc-preview-shell {
65
+ height: 100%;
66
+ min-height: 0;
67
+ display: flex;
68
+ flex-direction: column;
69
+ background: #FFFFFF;
70
+ }
71
+
72
+ .doc-empty {
73
+ height: 100%;
74
+ display: flex;
75
+ align-items: center;
76
+ justify-content: center;
77
+ padding: 24px;
78
+ text-align: center;
79
+ font-size: 12px;
80
+ color: #636366;
81
+ font-family: 'JetBrains Mono', monospace;
82
+ }
83
+
84
+ .doc-toolbar {
85
+ flex-shrink: 0;
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: space-between;
89
+ gap: 12px;
90
+ padding: 10px 14px;
91
+ border-bottom: 1px solid #F0F0F3;
92
+ }
93
+
94
+ .doc-title {
95
+ min-width: 0;
96
+ overflow: hidden;
97
+ text-overflow: ellipsis;
98
+ white-space: nowrap;
99
+ font-size: 12px;
100
+ font-weight: 600;
101
+ color: #1C1C1E;
102
+ }
103
+
104
+ .doc-preview-scroll {
105
+ flex: 1;
106
+ min-height: 0;
107
+ overflow: auto;
108
+ padding: 14px 16px 22px;
109
+ }
110
+
111
+ .doc-modal-body {
112
+ max-height: min(72vh, 760px);
113
+ overflow: auto;
114
+ padding-right: 4px;
115
+ }
116
+
34
117
  .doc-preview {
35
118
  font-size: 13px;
36
119
  line-height: 1.7;
37
120
  color: #374151;
121
+ overflow-wrap: anywhere;
122
+ }
123
+
124
+ .doc-preview-large {
125
+ font-size: 14px;
126
+ line-height: 1.75;
38
127
  }
39
128
 
40
129
  .doc-preview :deep(h1) {
@@ -46,6 +135,10 @@ const renderedContent = computed(() => {
46
135
  padding-bottom: 8px;
47
136
  }
48
137
 
138
+ .doc-preview-large :deep(h1) {
139
+ font-size: 22px;
140
+ }
141
+
49
142
  .doc-preview :deep(h2) {
50
143
  color: #1C1C1E;
51
144
  font-size: 15px;
@@ -53,6 +146,10 @@ const renderedContent = computed(() => {
53
146
  margin: 20px 0 10px;
54
147
  }
55
148
 
149
+ .doc-preview-large :deep(h2) {
150
+ font-size: 17px;
151
+ }
152
+
56
153
  .doc-preview :deep(h3) {
57
154
  color: #D97706;
58
155
  font-size: 14px;
@@ -1,22 +1,31 @@
1
1
  <template>
2
- <div class="flex flex-col h-full">
2
+ <div class="doc-tree">
3
+ <div class="doc-tree-search">
4
+ <n-input
5
+ v-model:value="query"
6
+ size="small"
7
+ clearable
8
+ placeholder="搜索文档"
9
+ />
10
+ </div>
11
+
3
12
  <n-empty v-if="groups.length === 0" description="暂无文档" style="margin: auto;" />
13
+ <n-empty v-else-if="treeData.length === 0" description="无匹配文档" style="margin: auto;" />
4
14
  <n-tree
5
15
  v-else
16
+ class="doc-tree-list"
6
17
  :data="treeData"
7
18
  :selected-keys="selectedKeys"
8
19
  :default-expand-all="true"
9
20
  selectable
10
21
  block-line
11
22
  @update:selected-keys="handleSelect"
12
- style="padding: 8px 12px;"
13
23
  />
14
24
  </div>
15
25
  </template>
16
26
 
17
27
  <script setup>
18
- import { computed, h } from 'vue'
19
- import { NIcon } from 'naive-ui'
28
+ import { computed, ref } from 'vue'
20
29
 
21
30
  const props = defineProps({
22
31
  groups: { type: Array, default: () => [] },
@@ -24,30 +33,48 @@ const props = defineProps({
24
33
  })
25
34
 
26
35
  const emit = defineEmits(['select-file'])
36
+ const query = ref('')
27
37
 
28
38
  const selectedKeys = computed(() => {
29
39
  return props.selectedFile?.path ? [props.selectedFile.path] : []
30
40
  })
31
41
 
32
42
  const groupIcons = {
33
- '设计文档': '📋',
34
- '实现计划': '📐',
35
- '归档': '📦',
36
- ' proposals': '📝'
43
+ design: '📋',
44
+ plan: '🧾',
45
+ archive: '📦',
46
+ changes: '⚙️',
47
+ scan: '🔍',
48
+ quicklog: '⚡'
37
49
  }
38
50
 
39
51
  const treeData = computed(() => {
40
- return props.groups.map(group => ({
41
- key: `group-${group.key}`,
42
- label: group.project ? `${group.label} (${group.project})` : group.label,
43
- prefix: () => groupIcons[group.label] || '📄',
44
- children: group.files.map(file => ({
45
- key: file.path,
46
- label: file.title,
47
- prefix: () => '📄',
48
- data: file
49
- }))
50
- }))
52
+ const keyword = query.value.trim().toLowerCase()
53
+
54
+ return props.groups
55
+ .map(group => {
56
+ const groupLabel = group.project ? `${group.label} (${group.project})` : group.label
57
+ const groupMatched = !keyword || groupLabel.toLowerCase().includes(keyword)
58
+ const files = group.files.filter(file => {
59
+ if (groupMatched) return true
60
+ return `${file.title || ''} ${file.name || ''}`.toLowerCase().includes(keyword)
61
+ })
62
+
63
+ if (files.length === 0) return null
64
+
65
+ return {
66
+ key: `group-${group.key}`,
67
+ label: groupLabel,
68
+ prefix: () => groupIcons[group.key] || '📄',
69
+ children: files.map(file => ({
70
+ key: file.path,
71
+ label: file.title || file.name,
72
+ prefix: () => '📄',
73
+ data: file
74
+ }))
75
+ }
76
+ })
77
+ .filter(Boolean)
51
78
  })
52
79
 
53
80
  function handleSelect(keys, option) {
@@ -56,3 +83,32 @@ function handleSelect(keys, option) {
56
83
  }
57
84
  }
58
85
  </script>
86
+
87
+ <style scoped>
88
+ .doc-tree {
89
+ height: 100%;
90
+ min-height: 0;
91
+ display: flex;
92
+ flex-direction: column;
93
+ background: #FFFFFF;
94
+ }
95
+
96
+ .doc-tree-search {
97
+ flex-shrink: 0;
98
+ padding: 10px 12px 8px;
99
+ border-bottom: 1px solid #F0F0F3;
100
+ }
101
+
102
+ .doc-tree-list {
103
+ flex: 1;
104
+ min-height: 0;
105
+ overflow: auto;
106
+ padding: 8px 12px 14px;
107
+ }
108
+
109
+ .doc-tree-list :deep(.n-tree-node-content__text) {
110
+ white-space: normal;
111
+ line-height: 1.35;
112
+ overflow-wrap: anywhere;
113
+ }
114
+ </style>
@@ -0,0 +1,48 @@
1
+ <template>
2
+ <div
3
+ class="h-resize-handle"
4
+ :class="{ 'dragging': isDragging }"
5
+ @mousedown="handleMouseDown"
6
+ ></div>
7
+ </template>
8
+
9
+ <script setup>
10
+ const props = defineProps({
11
+ isDragging: { type: Boolean, default: false },
12
+ colIndex: { type: Number, required: true }
13
+ })
14
+
15
+ const emit = defineEmits(['drag-start', 'drag-end', 'resize'])
16
+
17
+ function handleMouseDown(e) {
18
+ e.preventDefault()
19
+ emit('drag-start', { type: 'horizontal', colIndex: props.colIndex, startX: e.clientX })
20
+
21
+ const onMove = (ev) => {
22
+ emit('resize', { deltaX: ev.clientX - e.clientX })
23
+ }
24
+
25
+ const onUp = () => {
26
+ emit('drag-end')
27
+ window.removeEventListener('mousemove', onMove)
28
+ window.removeEventListener('mouseup', onUp)
29
+ }
30
+
31
+ window.addEventListener('mousemove', onMove)
32
+ window.addEventListener('mouseup', onUp)
33
+ }
34
+ </script>
35
+
36
+ <style scoped>
37
+ .h-resize-handle {
38
+ width: 4px;
39
+ background: #E5E7EB;
40
+ cursor: col-resize;
41
+ transition: background 0.2s;
42
+ }
43
+
44
+ .h-resize-handle:hover,
45
+ .h-resize-handle.dragging {
46
+ background: #D97706;
47
+ }
48
+ </style>