sillyspec 3.20.2 → 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.
- package/.claude/skills/sillyspec-archive/SKILL.md +21 -21
- package/.claude/skills/sillyspec-auto/SKILL.md +83 -83
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -44
- package/.claude/skills/sillyspec-commit/SKILL.md +106 -106
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
- package/.claude/skills/sillyspec-doctor/SKILL.md +31 -31
- package/.claude/skills/sillyspec-execute/SKILL.md +30 -30
- package/.claude/skills/sillyspec-explore/SKILL.md +109 -109
- package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
- package/.claude/skills/sillyspec-plan/SKILL.md +21 -21
- package/.claude/skills/sillyspec-propose/SKILL.md +21 -21
- package/.claude/skills/sillyspec-quick/SKILL.md +21 -21
- package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
- package/.claude/skills/sillyspec-scan/SKILL.md +21 -21
- package/.claude/skills/sillyspec-state/SKILL.md +54 -54
- package/.claude/skills/sillyspec-status/SKILL.md +21 -21
- package/.claude/skills/sillyspec-verify/SKILL.md +21 -21
- package/.claude/skills/sillyspec-workspace/SKILL.md +157 -157
- package/.husky/pre-push +13 -13
- package/CLAUDE.md +18 -18
- package/README.md +198 -188
- package/SKILL.md +90 -91
- package/bin/sillyspec.js +2 -2
- package/docs/brainstorm-plan-contract.md +64 -64
- package/docs/plan-execute-contract.md +123 -123
- package/docs/platform-scan-protocol.md +298 -298
- package/docs/revision-mode.md +115 -115
- package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -99
- package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -218
- package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -167
- package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -148
- package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -193
- package/docs/sillyspec/file-lifecycle.md +125 -125
- package/docs/workflow-contract-regression.md +106 -106
- package/docs/worktree-isolation.md +252 -252
- package/package.json +40 -40
- package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
- package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
- package/packages/dashboard/dist/index.html +16 -16
- package/packages/dashboard/index.html +15 -15
- package/packages/dashboard/package-lock.json +2384 -2384
- package/packages/dashboard/package.json +25 -25
- package/packages/dashboard/server/executor.js +86 -86
- package/packages/dashboard/server/index.js +588 -588
- package/packages/dashboard/server/parser.js +526 -526
- package/packages/dashboard/server/watcher.js +344 -344
- package/packages/dashboard/src/App.vue +558 -558
- package/packages/dashboard/src/components/ActionBar.vue +93 -93
- package/packages/dashboard/src/components/CommandPalette.vue +96 -96
- package/packages/dashboard/src/components/DetailPanel.vue +137 -137
- package/packages/dashboard/src/components/LogStream.vue +65 -65
- package/packages/dashboard/src/components/PipelineStage.vue +95 -95
- package/packages/dashboard/src/components/PipelineView.vue +156 -156
- package/packages/dashboard/src/components/ProjectList.vue +210 -210
- package/packages/dashboard/src/components/StageBadge.vue +67 -67
- package/packages/dashboard/src/components/StepCard.vue +94 -94
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
- package/packages/dashboard/src/composables/useDashboard.js +170 -170
- package/packages/dashboard/src/composables/useKeyboard.js +119 -119
- package/packages/dashboard/src/composables/useWebSocket.js +129 -129
- package/packages/dashboard/src/main.js +8 -8
- package/packages/dashboard/src/style.css +132 -132
- package/packages/dashboard/vite.config.js +18 -18
- package/src/brainstorm-postcheck.js +158 -158
- package/src/change-list.js +52 -52
- package/src/change-risk-profile.js +352 -352
- package/src/classify-change.js +73 -73
- package/src/constants.js +70 -70
- package/src/contract-matrix.js +278 -278
- package/src/db.js +201 -201
- package/src/endpoint-extractor.js +315 -315
- package/src/hooks/claude-pre-tool-use.cjs +125 -125
- package/src/hooks/worktree-guard.js +653 -653
- package/src/index.js +922 -900
- package/src/init.js +431 -431
- package/src/knowledge-match.js +130 -130
- package/src/migrate.js +117 -117
- package/src/modules.js +482 -482
- package/src/progress.js +1734 -1734
- package/src/run.js +3465 -3358
- package/src/scan-postcheck.js +387 -383
- package/src/setup.js +398 -398
- package/src/stage-contract.js +700 -700
- package/src/stages/archive.js +160 -160
- package/src/stages/brainstorm-auto.js +229 -229
- package/src/stages/brainstorm.js +645 -645
- package/src/stages/doctor.js +365 -365
- package/src/stages/execute.js +625 -625
- package/src/stages/explore.js +34 -34
- package/src/stages/index.js +29 -29
- package/src/stages/knowledge.js +498 -498
- package/src/stages/plan-postcheck.js +511 -513
- package/src/stages/plan.js +582 -582
- package/src/stages/propose.js +174 -174
- package/src/stages/quick.js +82 -82
- package/src/stages/scan.js +558 -558
- package/src/stages/status.js +65 -65
- package/src/stages/verify.js +322 -322
- package/src/sync.js +497 -497
- package/src/task-review.js +346 -346
- package/src/workflow.js +785 -785
- package/src/worktree-apply.js +549 -549
- package/src/worktree-deps.js +185 -0
- package/src/worktree.js +982 -932
- package/templates/workflows/archive-impact.yaml +79 -79
- package/templates/workflows/scan-docs.yaml +132 -132
- package/test/brainstorm-plan-contract.test.mjs +273 -273
- package/test/check-syntax.mjs +26 -26
- package/test/contract-artifacts.test.mjs +323 -323
- package/test/decision-supersede.test.mjs +277 -277
- package/test/knowledge-match.test.mjs +231 -231
- package/test/plan-execute-contract.test.mjs +330 -330
- package/test/plan-optimization.test.mjs +572 -572
- package/test/platform-artifacts.test.mjs +166 -166
- package/test/platform-failure-samples.test.mjs +199 -199
- package/test/platform-recovery-chain.test.mjs +167 -167
- package/test/platform-recovery.test.mjs +136 -136
- package/test/platform-scan-p0.test.mjs +168 -168
- package/test/revision-v1.test.mjs +1145 -1145
- package/test/run-scan-project-parse.test.mjs +200 -200
- package/test/run-tests.mjs +48 -48
- package/test/scan-knowledge.test.mjs +175 -175
- package/test/scan-paths.test.mjs +68 -68
- package/test/scan-postcheck.test.mjs +197 -197
- package/test/spec-dir.test.mjs +206 -206
- package/test/stage-contract.test.mjs +299 -299
- package/test/stage-definitions.test.mjs +39 -39
- package/test/wait-gates.test.mjs +496 -496
- package/test/worktree-deps-provision.test.mjs +148 -0
- package/test/worktree-guard.test.mjs +71 -71
- package/test/worktree-native-overlay.test.mjs +188 -188
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
:class="['group relative rounded-md cursor-pointer transition-all duration-200']"
|
|
4
|
-
:style="cardStyle"
|
|
5
|
-
@mouseenter="hovered = true"
|
|
6
|
-
@mouseleave="hovered = false"
|
|
7
|
-
@click="handleClick"
|
|
8
|
-
>
|
|
9
|
-
<!-- Left accent bar -->
|
|
10
|
-
<div class="absolute left-0 top-0 bottom-0 w-[2px]" :style="{ background: barColor }" />
|
|
11
|
-
|
|
12
|
-
<div class="pl-4 pr-4 py-3">
|
|
13
|
-
<div class="flex items-center gap-2">
|
|
14
|
-
<h3 class="text-[12px] font-medium transition-colors duration-150" :style="{ color: isActive ? '#D97706' : '#1C1C1E', fontFamily: '\'JetBrains Mono\', monospace' }">
|
|
15
|
-
{{ step.title || step.name }}
|
|
16
|
-
</h3>
|
|
17
|
-
<StageBadge v-if="step.status" :status="step.status" :label="statusLabel" />
|
|
18
|
-
</div>
|
|
19
|
-
|
|
20
|
-
<!-- Hover summary -->
|
|
21
|
-
<div
|
|
22
|
-
v-if="step.summary || step.description"
|
|
23
|
-
class="transition-all duration-200"
|
|
24
|
-
:style="{ maxHeight: hovered ? '80px' : '0', opacity: hovered ? 1 : 0, overflow: 'hidden' }"
|
|
25
|
-
>
|
|
26
|
-
<p class="mt-1.5 text-[11px] line-clamp-2" style="color: #636366;">
|
|
27
|
-
{{ step.summary || step.description }}
|
|
28
|
-
</p>
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
<!-- Active details -->
|
|
32
|
-
<div v-if="isActive" class="mt-2 space-y-1">
|
|
33
|
-
<p v-if="step.conclusion" class="text-[11px]" style="color: #1C1C1E;">
|
|
34
|
-
<span style="color: #D97706; font-weight: 600;">结论:</span> {{ step.conclusion }}
|
|
35
|
-
</p>
|
|
36
|
-
<p v-if="step.decision" class="text-[11px]" style="color: #1C1C1E;">
|
|
37
|
-
<span style="color: #D97706; font-weight: 600;">决策:</span> {{ step.decision }}
|
|
38
|
-
</p>
|
|
39
|
-
<p v-if="step.userQuery" class="text-[11px] italic" style="color: #636366;">
|
|
40
|
-
"{{ step.userQuery }}"
|
|
41
|
-
</p>
|
|
42
|
-
</div>
|
|
43
|
-
|
|
44
|
-
<div v-if="step.duration" class="mt-1 text-[10px] font-mono-log" style="color: #6B7280;">
|
|
45
|
-
{{ step.duration }}
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
</template>
|
|
50
|
-
|
|
51
|
-
<script setup>
|
|
52
|
-
import { ref, computed } from 'vue'
|
|
53
|
-
import StageBadge from './StageBadge.vue'
|
|
54
|
-
|
|
55
|
-
const props = defineProps({
|
|
56
|
-
step: { type: Object, required: true },
|
|
57
|
-
isActive: { type: Boolean, default: false },
|
|
58
|
-
isClickable: { type: Boolean, default: true }
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
const emit = defineEmits(['select'])
|
|
62
|
-
const hovered = ref(false)
|
|
63
|
-
|
|
64
|
-
const barColor = computed(() => {
|
|
65
|
-
const colors = {
|
|
66
|
-
'completed': '#16A34A', 'in-progress': '#D97706', 'blocked': '#EA580C', 'failed': '#DC2626', 'pending': '#E5E5EA',
|
|
67
|
-
'revising': '#7C3AED', 'stale': '#CA8A04', 'waiting': '#2563EB',
|
|
68
|
-
}
|
|
69
|
-
return colors[props.step.status] || colors.pending
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
const cardStyle = computed(() => ({
|
|
73
|
-
background: props.isActive ? 'rgba(217,119,6,0.08)' : (hovered.value ? 'rgba(0,0,0,0.02)' : '#FFFFFF'),
|
|
74
|
-
border: `1px solid ${props.isActive ? 'rgba(251,191,36,0.2)' : '#F0F0F3'}`,
|
|
75
|
-
}))
|
|
76
|
-
|
|
77
|
-
const statusLabel = computed(() => {
|
|
78
|
-
const labels = { 'completed': '完成', 'in-progress': '进行中', 'blocked': '阻塞', 'failed': '失败', 'pending': '待办',
|
|
79
|
-
'revising': '修订中', 'stale': '已失效', 'waiting': '等待中',
|
|
80
|
-
}
|
|
81
|
-
return labels[props.step.status] || '待办'
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
function handleClick() { if (props.isClickable) emit('select', props.step) }
|
|
85
|
-
</script>
|
|
86
|
-
|
|
87
|
-
<style scoped>
|
|
88
|
-
.line-clamp-2 {
|
|
89
|
-
display: -webkit-box;
|
|
90
|
-
-webkit-line-clamp: 2;
|
|
91
|
-
-webkit-box-orient: vertical;
|
|
92
|
-
overflow: hidden;
|
|
93
|
-
}
|
|
94
|
-
</style>
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="['group relative rounded-md cursor-pointer transition-all duration-200']"
|
|
4
|
+
:style="cardStyle"
|
|
5
|
+
@mouseenter="hovered = true"
|
|
6
|
+
@mouseleave="hovered = false"
|
|
7
|
+
@click="handleClick"
|
|
8
|
+
>
|
|
9
|
+
<!-- Left accent bar -->
|
|
10
|
+
<div class="absolute left-0 top-0 bottom-0 w-[2px]" :style="{ background: barColor }" />
|
|
11
|
+
|
|
12
|
+
<div class="pl-4 pr-4 py-3">
|
|
13
|
+
<div class="flex items-center gap-2">
|
|
14
|
+
<h3 class="text-[12px] font-medium transition-colors duration-150" :style="{ color: isActive ? '#D97706' : '#1C1C1E', fontFamily: '\'JetBrains Mono\', monospace' }">
|
|
15
|
+
{{ step.title || step.name }}
|
|
16
|
+
</h3>
|
|
17
|
+
<StageBadge v-if="step.status" :status="step.status" :label="statusLabel" />
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<!-- Hover summary -->
|
|
21
|
+
<div
|
|
22
|
+
v-if="step.summary || step.description"
|
|
23
|
+
class="transition-all duration-200"
|
|
24
|
+
:style="{ maxHeight: hovered ? '80px' : '0', opacity: hovered ? 1 : 0, overflow: 'hidden' }"
|
|
25
|
+
>
|
|
26
|
+
<p class="mt-1.5 text-[11px] line-clamp-2" style="color: #636366;">
|
|
27
|
+
{{ step.summary || step.description }}
|
|
28
|
+
</p>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<!-- Active details -->
|
|
32
|
+
<div v-if="isActive" class="mt-2 space-y-1">
|
|
33
|
+
<p v-if="step.conclusion" class="text-[11px]" style="color: #1C1C1E;">
|
|
34
|
+
<span style="color: #D97706; font-weight: 600;">结论:</span> {{ step.conclusion }}
|
|
35
|
+
</p>
|
|
36
|
+
<p v-if="step.decision" class="text-[11px]" style="color: #1C1C1E;">
|
|
37
|
+
<span style="color: #D97706; font-weight: 600;">决策:</span> {{ step.decision }}
|
|
38
|
+
</p>
|
|
39
|
+
<p v-if="step.userQuery" class="text-[11px] italic" style="color: #636366;">
|
|
40
|
+
"{{ step.userQuery }}"
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div v-if="step.duration" class="mt-1 text-[10px] font-mono-log" style="color: #6B7280;">
|
|
45
|
+
{{ step.duration }}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script setup>
|
|
52
|
+
import { ref, computed } from 'vue'
|
|
53
|
+
import StageBadge from './StageBadge.vue'
|
|
54
|
+
|
|
55
|
+
const props = defineProps({
|
|
56
|
+
step: { type: Object, required: true },
|
|
57
|
+
isActive: { type: Boolean, default: false },
|
|
58
|
+
isClickable: { type: Boolean, default: true }
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const emit = defineEmits(['select'])
|
|
62
|
+
const hovered = ref(false)
|
|
63
|
+
|
|
64
|
+
const barColor = computed(() => {
|
|
65
|
+
const colors = {
|
|
66
|
+
'completed': '#16A34A', 'in-progress': '#D97706', 'blocked': '#EA580C', 'failed': '#DC2626', 'pending': '#E5E5EA',
|
|
67
|
+
'revising': '#7C3AED', 'stale': '#CA8A04', 'waiting': '#2563EB',
|
|
68
|
+
}
|
|
69
|
+
return colors[props.step.status] || colors.pending
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const cardStyle = computed(() => ({
|
|
73
|
+
background: props.isActive ? 'rgba(217,119,6,0.08)' : (hovered.value ? 'rgba(0,0,0,0.02)' : '#FFFFFF'),
|
|
74
|
+
border: `1px solid ${props.isActive ? 'rgba(251,191,36,0.2)' : '#F0F0F3'}`,
|
|
75
|
+
}))
|
|
76
|
+
|
|
77
|
+
const statusLabel = computed(() => {
|
|
78
|
+
const labels = { 'completed': '完成', 'in-progress': '进行中', 'blocked': '阻塞', 'failed': '失败', 'pending': '待办',
|
|
79
|
+
'revising': '修订中', 'stale': '已失效', 'waiting': '等待中',
|
|
80
|
+
}
|
|
81
|
+
return labels[props.step.status] || '待办'
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
function handleClick() { if (props.isClickable) emit('select', props.step) }
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<style scoped>
|
|
88
|
+
.line-clamp-2 {
|
|
89
|
+
display: -webkit-box;
|
|
90
|
+
-webkit-line-clamp: 2;
|
|
91
|
+
-webkit-box-orient: vertical;
|
|
92
|
+
overflow: hidden;
|
|
93
|
+
}
|
|
94
|
+
</style>
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div v-if="data?.length" class="px-4 py-3">
|
|
3
|
-
<div v-for="group in data" :key="group.key" class="mb-4">
|
|
4
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">
|
|
5
|
-
{{ group.icon }} {{ group.label }} ({{ group.files.length }})
|
|
6
|
-
</h4>
|
|
7
|
-
<div class="space-y-1">
|
|
8
|
-
<div
|
|
9
|
-
v-for="f in group.files"
|
|
10
|
-
:key="f.path"
|
|
11
|
-
class="flex items-center gap-2 px-2 py-1.5 rounded text-[11px] cursor-pointer hover:bg-[#FEF3C7] transition-colors"
|
|
12
|
-
@click="$emit('open-file', f)"
|
|
13
|
-
>
|
|
14
|
-
<svg class="w-3 h-3 flex-shrink-0" fill="none" stroke="#9CA3AF" viewBox="0 0 24 24">
|
|
15
|
-
<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"/>
|
|
16
|
-
</svg>
|
|
17
|
-
<span class="truncate" style="color: #1C1C1E;">{{ f.name }}</span>
|
|
18
|
-
<span class="ml-auto flex-shrink-0 font-mono text-[10px]" style="color: #9CA3AF;">{{ formatSize(f.size) }}</span>
|
|
19
|
-
<span class="flex-shrink-0 text-[10px]" style="color: #9CA3AF;">{{ relativeTime(f.mtime) }}</span>
|
|
20
|
-
</div>
|
|
21
|
-
</div>
|
|
22
|
-
</div>
|
|
23
|
-
</div>
|
|
24
|
-
<n-empty v-else description="无文档" style="padding: 48px 0;" />
|
|
25
|
-
</template>
|
|
26
|
-
|
|
27
|
-
<script setup>
|
|
28
|
-
defineProps({ data: { type: Array, default: () => [] } })
|
|
29
|
-
defineEmits(['open-file'])
|
|
30
|
-
|
|
31
|
-
function formatSize(bytes) {
|
|
32
|
-
if (!bytes) return '—'
|
|
33
|
-
if (bytes < 1024) return bytes + ' B'
|
|
34
|
-
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'
|
|
35
|
-
return (bytes / 1048576).toFixed(1) + ' MB'
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function relativeTime(iso) {
|
|
39
|
-
if (!iso) return ''
|
|
40
|
-
const d = new Date(iso)
|
|
41
|
-
const diff = Date.now() - d.getTime()
|
|
42
|
-
if (diff < 60000) return '刚刚'
|
|
43
|
-
if (diff < 3600000) return `${Math.floor(diff / 60000)} 分钟前`
|
|
44
|
-
if (diff < 86400000) return `${Math.floor(diff / 3600000)} 小时前`
|
|
45
|
-
if (diff < 604800000) return `${Math.floor(diff / 86400000)} 天前`
|
|
46
|
-
return `${d.getMonth() + 1}/${d.getDate()}`
|
|
47
|
-
}
|
|
48
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="data?.length" class="px-4 py-3">
|
|
3
|
+
<div v-for="group in data" :key="group.key" class="mb-4">
|
|
4
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">
|
|
5
|
+
{{ group.icon }} {{ group.label }} ({{ group.files.length }})
|
|
6
|
+
</h4>
|
|
7
|
+
<div class="space-y-1">
|
|
8
|
+
<div
|
|
9
|
+
v-for="f in group.files"
|
|
10
|
+
:key="f.path"
|
|
11
|
+
class="flex items-center gap-2 px-2 py-1.5 rounded text-[11px] cursor-pointer hover:bg-[#FEF3C7] transition-colors"
|
|
12
|
+
@click="$emit('open-file', f)"
|
|
13
|
+
>
|
|
14
|
+
<svg class="w-3 h-3 flex-shrink-0" fill="none" stroke="#9CA3AF" viewBox="0 0 24 24">
|
|
15
|
+
<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"/>
|
|
16
|
+
</svg>
|
|
17
|
+
<span class="truncate" style="color: #1C1C1E;">{{ f.name }}</span>
|
|
18
|
+
<span class="ml-auto flex-shrink-0 font-mono text-[10px]" style="color: #9CA3AF;">{{ formatSize(f.size) }}</span>
|
|
19
|
+
<span class="flex-shrink-0 text-[10px]" style="color: #9CA3AF;">{{ relativeTime(f.mtime) }}</span>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<n-empty v-else description="无文档" style="padding: 48px 0;" />
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup>
|
|
28
|
+
defineProps({ data: { type: Array, default: () => [] } })
|
|
29
|
+
defineEmits(['open-file'])
|
|
30
|
+
|
|
31
|
+
function formatSize(bytes) {
|
|
32
|
+
if (!bytes) return '—'
|
|
33
|
+
if (bytes < 1024) return bytes + ' B'
|
|
34
|
+
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'
|
|
35
|
+
return (bytes / 1048576).toFixed(1) + ' MB'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function relativeTime(iso) {
|
|
39
|
+
if (!iso) return ''
|
|
40
|
+
const d = new Date(iso)
|
|
41
|
+
const diff = Date.now() - d.getTime()
|
|
42
|
+
if (diff < 60000) return '刚刚'
|
|
43
|
+
if (diff < 3600000) return `${Math.floor(diff / 60000)} 分钟前`
|
|
44
|
+
if (diff < 86400000) return `${Math.floor(diff / 3600000)} 小时前`
|
|
45
|
+
if (diff < 604800000) return `${Math.floor(diff / 86400000)} 天前`
|
|
46
|
+
return `${d.getMonth() + 1}/${d.getDate()}`
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div v-if="data" class="px-4 py-3">
|
|
3
|
-
<!-- Branch -->
|
|
4
|
-
<div class="mb-4">
|
|
5
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">当前分支</h4>
|
|
6
|
-
<div class="text-[18px] font-bold font-[JetBrains_Mono,monospace]" style="color: #D97706;">{{ data.branch || '—' }}</div>
|
|
7
|
-
</div>
|
|
8
|
-
|
|
9
|
-
<!-- Commits -->
|
|
10
|
-
<div v-if="data.commits?.length" class="mb-4">
|
|
11
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">最近提交</h4>
|
|
12
|
-
<div class="space-y-2">
|
|
13
|
-
<div v-for="c in data.commits" :key="c.hash" class="px-3 py-2 rounded-md" style="background: #F5F5F7;">
|
|
14
|
-
<div class="flex items-center gap-2">
|
|
15
|
-
<span class="text-[11px] font-mono font-semibold" style="color: #D97706;">{{ c.hash }}</span>
|
|
16
|
-
<span class="text-[11px] flex-1 truncate" style="color: #1C1C1E;">{{ c.message }}</span>
|
|
17
|
-
</div>
|
|
18
|
-
<div class="flex items-center gap-2 mt-1 text-[10px]" style="color: #6B7280;">
|
|
19
|
-
<span>{{ c.author }}</span>
|
|
20
|
-
<span>·</span>
|
|
21
|
-
<span>{{ relativeTime(c.date) }}</span>
|
|
22
|
-
</div>
|
|
23
|
-
</div>
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
<!-- Untracked -->
|
|
28
|
-
<div v-if="data.untracked?.length">
|
|
29
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">未提交文件</h4>
|
|
30
|
-
<div class="space-y-1">
|
|
31
|
-
<div v-for="f in data.untracked" :key="f.file" class="flex items-center gap-2 px-2 py-1 rounded text-[11px] font-[JetBrains_Mono,monospace]">
|
|
32
|
-
<span class="font-semibold w-5 text-center" :style="{ color: statusColor(f.status) }">{{ f.status }}</span>
|
|
33
|
-
<span class="truncate" style="color: #1C1C1E;">{{ f.file }}</span>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
<n-empty v-else description="无 Git 信息" style="padding: 48px 0;" />
|
|
39
|
-
</template>
|
|
40
|
-
|
|
41
|
-
<script setup>
|
|
42
|
-
defineProps({ data: { type: Object, default: null } })
|
|
43
|
-
|
|
44
|
-
function statusColor(s) {
|
|
45
|
-
if (s.includes('M')) return '#F59E0B'
|
|
46
|
-
if (s.includes('A') || s === '?') return '#10B981'
|
|
47
|
-
if (s.includes('D')) return '#EF4444'
|
|
48
|
-
return '#9CA3AF'
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function relativeTime(iso) {
|
|
52
|
-
if (!iso) return ''
|
|
53
|
-
const d = new Date(iso)
|
|
54
|
-
const diff = Date.now() - d.getTime()
|
|
55
|
-
if (diff < 60000) return '刚刚'
|
|
56
|
-
if (diff < 3600000) return `${Math.floor(diff / 60000)} 分钟前`
|
|
57
|
-
if (diff < 86400000) return `${Math.floor(diff / 3600000)} 小时前`
|
|
58
|
-
if (diff < 604800000) return `${Math.floor(diff / 86400000)} 天前`
|
|
59
|
-
return `${d.getMonth() + 1}/${d.getDate()}`
|
|
60
|
-
}
|
|
61
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="data" class="px-4 py-3">
|
|
3
|
+
<!-- Branch -->
|
|
4
|
+
<div class="mb-4">
|
|
5
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">当前分支</h4>
|
|
6
|
+
<div class="text-[18px] font-bold font-[JetBrains_Mono,monospace]" style="color: #D97706;">{{ data.branch || '—' }}</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<!-- Commits -->
|
|
10
|
+
<div v-if="data.commits?.length" class="mb-4">
|
|
11
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">最近提交</h4>
|
|
12
|
+
<div class="space-y-2">
|
|
13
|
+
<div v-for="c in data.commits" :key="c.hash" class="px-3 py-2 rounded-md" style="background: #F5F5F7;">
|
|
14
|
+
<div class="flex items-center gap-2">
|
|
15
|
+
<span class="text-[11px] font-mono font-semibold" style="color: #D97706;">{{ c.hash }}</span>
|
|
16
|
+
<span class="text-[11px] flex-1 truncate" style="color: #1C1C1E;">{{ c.message }}</span>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="flex items-center gap-2 mt-1 text-[10px]" style="color: #6B7280;">
|
|
19
|
+
<span>{{ c.author }}</span>
|
|
20
|
+
<span>·</span>
|
|
21
|
+
<span>{{ relativeTime(c.date) }}</span>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- Untracked -->
|
|
28
|
+
<div v-if="data.untracked?.length">
|
|
29
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">未提交文件</h4>
|
|
30
|
+
<div class="space-y-1">
|
|
31
|
+
<div v-for="f in data.untracked" :key="f.file" class="flex items-center gap-2 px-2 py-1 rounded text-[11px] font-[JetBrains_Mono,monospace]">
|
|
32
|
+
<span class="font-semibold w-5 text-center" :style="{ color: statusColor(f.status) }">{{ f.status }}</span>
|
|
33
|
+
<span class="truncate" style="color: #1C1C1E;">{{ f.file }}</span>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<n-empty v-else description="无 Git 信息" style="padding: 48px 0;" />
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script setup>
|
|
42
|
+
defineProps({ data: { type: Object, default: null } })
|
|
43
|
+
|
|
44
|
+
function statusColor(s) {
|
|
45
|
+
if (s.includes('M')) return '#F59E0B'
|
|
46
|
+
if (s.includes('A') || s === '?') return '#10B981'
|
|
47
|
+
if (s.includes('D')) return '#EF4444'
|
|
48
|
+
return '#9CA3AF'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function relativeTime(iso) {
|
|
52
|
+
if (!iso) return ''
|
|
53
|
+
const d = new Date(iso)
|
|
54
|
+
const diff = Date.now() - d.getTime()
|
|
55
|
+
if (diff < 60000) return '刚刚'
|
|
56
|
+
if (diff < 3600000) return `${Math.floor(diff / 60000)} 分钟前`
|
|
57
|
+
if (diff < 86400000) return `${Math.floor(diff / 3600000)} 小时前`
|
|
58
|
+
if (diff < 604800000) return `${Math.floor(diff / 86400000)} 天前`
|
|
59
|
+
return `${d.getMonth() + 1}/${d.getDate()}`
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div v-if="data" class="px-4 py-3">
|
|
3
|
-
<!-- Frameworks -->
|
|
4
|
-
<div v-if="data.frameworks?.length" class="mb-4">
|
|
5
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">识别框架</h4>
|
|
6
|
-
<div class="flex flex-wrap gap-2">
|
|
7
|
-
<n-tag v-for="f in data.frameworks" :key="f" type="warning" size="small" round>{{ f }}</n-tag>
|
|
8
|
-
</div>
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
<!-- Dependencies -->
|
|
12
|
-
<div v-if="depEntries.length" class="mb-4">
|
|
13
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">dependencies ({{ depEntries.length }})</h4>
|
|
14
|
-
<div class="space-y-1">
|
|
15
|
-
<div v-for="[name, ver] in depEntries" :key="name" class="flex items-center gap-2 px-2 py-1 rounded text-[11px]" style="background: #F5F5F7;">
|
|
16
|
-
<span class="font-mono font-medium truncate" style="color: #1C1C1E;">{{ name }}</span>
|
|
17
|
-
<span class="font-mono ml-auto flex-shrink-0" style="color: #9CA3AF;">{{ ver }}</span>
|
|
18
|
-
</div>
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
|
21
|
-
|
|
22
|
-
<!-- Dev Dependencies -->
|
|
23
|
-
<div v-if="devDepEntries.length">
|
|
24
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">devDependencies ({{ devDepEntries.length }})</h4>
|
|
25
|
-
<div class="space-y-1">
|
|
26
|
-
<div v-for="[name, ver] in devDepEntries" :key="name" class="flex items-center gap-2 px-2 py-1 rounded text-[11px]" style="background: #F5F5F7;">
|
|
27
|
-
<span class="font-mono font-medium truncate" style="color: #1C1C1E;">{{ name }}</span>
|
|
28
|
-
<span class="font-mono ml-auto flex-shrink-0" style="color: #9CA3AF;">{{ ver }}</span>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
<n-empty v-else description="无依赖信息" style="padding: 48px 0;" />
|
|
34
|
-
</template>
|
|
35
|
-
|
|
36
|
-
<script setup>
|
|
37
|
-
import { computed } from 'vue'
|
|
38
|
-
|
|
39
|
-
const props = defineProps({ data: { type: Object, default: null } })
|
|
40
|
-
|
|
41
|
-
const depEntries = computed(() => props.data ? Object.entries(props.data.dependencies || {}) : [])
|
|
42
|
-
const devDepEntries = computed(() => props.data ? Object.entries(props.data.devDependencies || {}) : [])
|
|
43
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="data" class="px-4 py-3">
|
|
3
|
+
<!-- Frameworks -->
|
|
4
|
+
<div v-if="data.frameworks?.length" class="mb-4">
|
|
5
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">识别框架</h4>
|
|
6
|
+
<div class="flex flex-wrap gap-2">
|
|
7
|
+
<n-tag v-for="f in data.frameworks" :key="f" type="warning" size="small" round>{{ f }}</n-tag>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<!-- Dependencies -->
|
|
12
|
+
<div v-if="depEntries.length" class="mb-4">
|
|
13
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">dependencies ({{ depEntries.length }})</h4>
|
|
14
|
+
<div class="space-y-1">
|
|
15
|
+
<div v-for="[name, ver] in depEntries" :key="name" class="flex items-center gap-2 px-2 py-1 rounded text-[11px]" style="background: #F5F5F7;">
|
|
16
|
+
<span class="font-mono font-medium truncate" style="color: #1C1C1E;">{{ name }}</span>
|
|
17
|
+
<span class="font-mono ml-auto flex-shrink-0" style="color: #9CA3AF;">{{ ver }}</span>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<!-- Dev Dependencies -->
|
|
23
|
+
<div v-if="devDepEntries.length">
|
|
24
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">devDependencies ({{ devDepEntries.length }})</h4>
|
|
25
|
+
<div class="space-y-1">
|
|
26
|
+
<div v-for="[name, ver] in devDepEntries" :key="name" class="flex items-center gap-2 px-2 py-1 rounded text-[11px]" style="background: #F5F5F7;">
|
|
27
|
+
<span class="font-mono font-medium truncate" style="color: #1C1C1E;">{{ name }}</span>
|
|
28
|
+
<span class="font-mono ml-auto flex-shrink-0" style="color: #9CA3AF;">{{ ver }}</span>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<n-empty v-else description="无依赖信息" style="padding: 48px 0;" />
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script setup>
|
|
37
|
+
import { computed } from 'vue'
|
|
38
|
+
|
|
39
|
+
const props = defineProps({ data: { type: Object, default: null } })
|
|
40
|
+
|
|
41
|
+
const depEntries = computed(() => props.data ? Object.entries(props.data.dependencies || {}) : [])
|
|
42
|
+
const devDepEntries = computed(() => props.data ? Object.entries(props.data.devDependencies || {}) : [])
|
|
43
|
+
</script>
|