claude-smart 0.2.23 → 0.2.25
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/.agents/plugins/marketplace.json +20 -0
- package/README.md +76 -28
- package/bin/claude-smart.js +355 -11
- package/package.json +11 -1
- package/plugin/.claude-plugin/plugin.json +17 -0
- package/plugin/.codex-plugin/plugin.json +35 -0
- package/plugin/LICENSE +202 -0
- package/plugin/README.md +37 -0
- package/plugin/bin/cs-cite +77 -0
- package/plugin/commands/clear-all.md +8 -0
- package/plugin/commands/dashboard.md +8 -0
- package/plugin/commands/learn.md +12 -0
- package/plugin/commands/restart.md +8 -0
- package/plugin/commands/show.md +8 -0
- package/plugin/dashboard/AGENTS.md +6 -0
- package/plugin/dashboard/app/api/claude-settings/route.ts +19 -0
- package/plugin/dashboard/app/api/config/route.ts +16 -0
- package/plugin/dashboard/app/api/health/route.ts +10 -0
- package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +63 -0
- package/plugin/dashboard/app/api/sessions/[id]/route.ts +28 -0
- package/plugin/dashboard/app/api/sessions/route.ts +14 -0
- package/plugin/dashboard/app/configure/env/page.tsx +318 -0
- package/plugin/dashboard/app/configure/layout.tsx +47 -0
- package/plugin/dashboard/app/configure/page.tsx +5 -0
- package/plugin/dashboard/app/configure/server/page.tsx +258 -0
- package/plugin/dashboard/app/dashboard/page.tsx +227 -0
- package/plugin/dashboard/app/globals.css +129 -0
- package/plugin/dashboard/app/icon.png +0 -0
- package/plugin/dashboard/app/layout.tsx +40 -0
- package/plugin/dashboard/app/page.tsx +5 -0
- package/plugin/dashboard/app/preferences/[id]/page.tsx +531 -0
- package/plugin/dashboard/app/preferences/page.tsx +126 -0
- package/plugin/dashboard/app/providers.tsx +12 -0
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +321 -0
- package/plugin/dashboard/app/sessions/page.tsx +186 -0
- package/plugin/dashboard/app/skills/page.tsx +362 -0
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +597 -0
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +830 -0
- package/plugin/dashboard/components/common/delete-all-button.tsx +45 -0
- package/plugin/dashboard/components/common/empty-state.tsx +34 -0
- package/plugin/dashboard/components/common/learnings-badge.tsx +34 -0
- package/plugin/dashboard/components/common/page-header.tsx +34 -0
- package/plugin/dashboard/components/common/page-tabs.tsx +115 -0
- package/plugin/dashboard/components/common/stat-card.tsx +38 -0
- package/plugin/dashboard/components/layout/nav-items.ts +22 -0
- package/plugin/dashboard/components/layout/sidebar.tsx +45 -0
- package/plugin/dashboard/components/layout/top-bar.tsx +64 -0
- package/plugin/dashboard/components/stall-banner.tsx +53 -0
- package/plugin/dashboard/components/ui/badge.tsx +52 -0
- package/plugin/dashboard/components/ui/button.tsx +60 -0
- package/plugin/dashboard/components/ui/collapsible.tsx +21 -0
- package/plugin/dashboard/components/ui/input.tsx +20 -0
- package/plugin/dashboard/components/ui/label.tsx +20 -0
- package/plugin/dashboard/components/ui/scroll-area.tsx +55 -0
- package/plugin/dashboard/components/ui/select.tsx +201 -0
- package/plugin/dashboard/components/ui/separator.tsx +25 -0
- package/plugin/dashboard/components/ui/sheet.tsx +135 -0
- package/plugin/dashboard/components/ui/switch.tsx +32 -0
- package/plugin/dashboard/components.json +25 -0
- package/plugin/dashboard/eslint.config.mjs +16 -0
- package/plugin/dashboard/hooks/use-settings.tsx +88 -0
- package/plugin/dashboard/hooks/use-stall-state.ts +59 -0
- package/plugin/dashboard/lib/claude-settings-file.ts +114 -0
- package/plugin/dashboard/lib/config-file.ts +131 -0
- package/plugin/dashboard/lib/format.ts +58 -0
- package/plugin/dashboard/lib/reflexio-client.ts +238 -0
- package/plugin/dashboard/lib/reflexio-url.ts +17 -0
- package/plugin/dashboard/lib/session-reader.ts +245 -0
- package/plugin/dashboard/lib/status.ts +24 -0
- package/plugin/dashboard/lib/types.ts +145 -0
- package/plugin/dashboard/lib/utils.ts +6 -0
- package/plugin/dashboard/next.config.ts +7 -0
- package/plugin/dashboard/package-lock.json +10275 -0
- package/plugin/dashboard/package.json +37 -0
- package/plugin/dashboard/postcss.config.mjs +7 -0
- package/plugin/dashboard/public/claude-smart-icon.png +0 -0
- package/plugin/dashboard/tsconfig.json +34 -0
- package/plugin/hooks/codex-hooks.json +67 -0
- package/plugin/hooks/hooks.json +111 -0
- package/plugin/pyproject.toml +49 -0
- package/plugin/scripts/_codex_env.sh +27 -0
- package/plugin/scripts/_lib.sh +325 -0
- package/plugin/scripts/backend-service.sh +208 -0
- package/plugin/scripts/cli.sh +40 -0
- package/plugin/scripts/dashboard-build.sh +139 -0
- package/plugin/scripts/dashboard-open.sh +107 -0
- package/plugin/scripts/dashboard-service.sh +195 -0
- package/plugin/scripts/ensure-plugin-root.sh +84 -0
- package/plugin/scripts/hook_entry.sh +70 -0
- package/plugin/scripts/smart-install.sh +411 -0
- package/plugin/src/claude_smart/__init__.py +3 -0
- package/plugin/src/claude_smart/cli.py +1342 -0
- package/plugin/src/claude_smart/context_format.py +277 -0
- package/plugin/src/claude_smart/context_inject.py +92 -0
- package/plugin/src/claude_smart/cs_cite.py +236 -0
- package/plugin/src/claude_smart/events/__init__.py +1 -0
- package/plugin/src/claude_smart/events/post_tool.py +148 -0
- package/plugin/src/claude_smart/events/pre_tool.py +52 -0
- package/plugin/src/claude_smart/events/session_end.py +20 -0
- package/plugin/src/claude_smart/events/session_start.py +119 -0
- package/plugin/src/claude_smart/events/stop.py +393 -0
- package/plugin/src/claude_smart/events/user_prompt.py +73 -0
- package/plugin/src/claude_smart/hook.py +114 -0
- package/plugin/src/claude_smart/ids.py +56 -0
- package/plugin/src/claude_smart/internal_call.py +89 -0
- package/plugin/src/claude_smart/optimizer_assistant.py +203 -0
- package/plugin/src/claude_smart/publish.py +71 -0
- package/plugin/src/claude_smart/query_compose.py +51 -0
- package/plugin/src/claude_smart/reflexio_adapter.py +403 -0
- package/plugin/src/claude_smart/runtime.py +52 -0
- package/plugin/src/claude_smart/stall_banner.py +61 -0
- package/plugin/src/claude_smart/state.py +276 -0
- package/plugin/uv.lock +3720 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { BookOpen, Layers3 } from "lucide-react";
|
|
6
|
+
import { PageHeader } from "@/components/common/page-header";
|
|
7
|
+
import { EmptyState } from "@/components/common/empty-state";
|
|
8
|
+
import { DeleteAllButton } from "@/components/common/delete-all-button";
|
|
9
|
+
import { PageTabs } from "@/components/common/page-tabs";
|
|
10
|
+
import { Badge } from "@/components/ui/badge";
|
|
11
|
+
import { Input } from "@/components/ui/input";
|
|
12
|
+
import {
|
|
13
|
+
Select,
|
|
14
|
+
SelectContent,
|
|
15
|
+
SelectItem,
|
|
16
|
+
SelectTrigger,
|
|
17
|
+
SelectValue,
|
|
18
|
+
} from "@/components/ui/select";
|
|
19
|
+
import { reflexio } from "@/lib/reflexio-client";
|
|
20
|
+
import { useSettings } from "@/hooks/use-settings";
|
|
21
|
+
import { formatRelative } from "@/lib/format";
|
|
22
|
+
import { cn } from "@/lib/utils";
|
|
23
|
+
import {
|
|
24
|
+
agentPlaybookStatusLabel,
|
|
25
|
+
statusLabel,
|
|
26
|
+
type AgentPlaybookStatusLabel,
|
|
27
|
+
type StatusLabel,
|
|
28
|
+
} from "@/lib/status";
|
|
29
|
+
import type { AgentPlaybook, UserPlaybook } from "@/lib/types";
|
|
30
|
+
|
|
31
|
+
type SkillKind = "project" | "shared";
|
|
32
|
+
type SkillStatus = StatusLabel | AgentPlaybookStatusLabel;
|
|
33
|
+
|
|
34
|
+
const SHARED_STATUS_META: Record<
|
|
35
|
+
AgentPlaybookStatusLabel,
|
|
36
|
+
{ label: string; description: string }
|
|
37
|
+
> = {
|
|
38
|
+
PENDING: {
|
|
39
|
+
label: "Auto generated",
|
|
40
|
+
description: "Auto-generated shared skill. It may be updated automatically.",
|
|
41
|
+
},
|
|
42
|
+
APPROVED: {
|
|
43
|
+
label: "Persisted",
|
|
44
|
+
description: "Persisted shared skill. It will not be auto updated.",
|
|
45
|
+
},
|
|
46
|
+
REJECTED: {
|
|
47
|
+
label: "Rejected",
|
|
48
|
+
description: "Rejected shared skill. It will not be used in claude-smart.",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
interface SkillCard {
|
|
53
|
+
kind: SkillKind;
|
|
54
|
+
id: number;
|
|
55
|
+
agentVersion: string;
|
|
56
|
+
createdAt: number;
|
|
57
|
+
content: string;
|
|
58
|
+
trigger: string | null;
|
|
59
|
+
rationale: string | null;
|
|
60
|
+
status: SkillStatus;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function projectSkill(p: UserPlaybook): SkillCard {
|
|
64
|
+
return {
|
|
65
|
+
kind: "project",
|
|
66
|
+
id: p.user_playbook_id,
|
|
67
|
+
agentVersion: p.agent_version || "default",
|
|
68
|
+
createdAt: p.created_at,
|
|
69
|
+
content: p.content,
|
|
70
|
+
trigger: p.trigger,
|
|
71
|
+
rationale: p.rationale,
|
|
72
|
+
status: statusLabel(p),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function sharedSkill(p: AgentPlaybook): SkillCard {
|
|
77
|
+
return {
|
|
78
|
+
kind: "shared",
|
|
79
|
+
id: p.agent_playbook_id,
|
|
80
|
+
agentVersion: p.agent_version || "default",
|
|
81
|
+
createdAt: p.created_at,
|
|
82
|
+
content: p.content,
|
|
83
|
+
trigger: p.trigger,
|
|
84
|
+
rationale: p.rationale,
|
|
85
|
+
status: agentPlaybookStatusLabel(p),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default function SkillsPage() {
|
|
90
|
+
const { reflexioUrl } = useSettings();
|
|
91
|
+
const [projectSkills, setProjectSkills] = useState<UserPlaybook[] | null>(null);
|
|
92
|
+
const [sharedSkills, setSharedSkills] = useState<AgentPlaybook[] | null>(null);
|
|
93
|
+
const [error, setError] = useState<string | null>(null);
|
|
94
|
+
const [activeKind, setActiveKind] = useState<SkillKind>("project");
|
|
95
|
+
const [agentVersion, setAgentVersion] = useState<string>("__all__");
|
|
96
|
+
const [statusFilter, setStatusFilter] = useState<string>("CURRENT");
|
|
97
|
+
const [search, setSearch] = useState("");
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
let cancelled = false;
|
|
101
|
+
async function load() {
|
|
102
|
+
try {
|
|
103
|
+
const [projectRes, sharedRes] = await Promise.all([
|
|
104
|
+
reflexio.getUserPlaybooks({ reflexioUrl, limit: 200 }),
|
|
105
|
+
reflexio.getAgentPlaybooks({ reflexioUrl, limit: 200 }),
|
|
106
|
+
]);
|
|
107
|
+
if (cancelled) return;
|
|
108
|
+
setProjectSkills(projectRes.user_playbooks ?? []);
|
|
109
|
+
setSharedSkills(sharedRes.agent_playbooks ?? []);
|
|
110
|
+
setError(null);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
if (!cancelled) setError(e instanceof Error ? e.message : String(e));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
load();
|
|
116
|
+
return () => {
|
|
117
|
+
cancelled = true;
|
|
118
|
+
};
|
|
119
|
+
}, [reflexioUrl]);
|
|
120
|
+
|
|
121
|
+
const activeSkills = useMemo(() => {
|
|
122
|
+
return activeKind === "project"
|
|
123
|
+
? (projectSkills ?? []).map(projectSkill)
|
|
124
|
+
: (sharedSkills ?? []).map(sharedSkill);
|
|
125
|
+
}, [activeKind, projectSkills, sharedSkills]);
|
|
126
|
+
|
|
127
|
+
const projects = useMemo(() => {
|
|
128
|
+
const set = new Set<string>();
|
|
129
|
+
for (const p of activeSkills) set.add(p.agentVersion);
|
|
130
|
+
return Array.from(set).sort();
|
|
131
|
+
}, [activeSkills]);
|
|
132
|
+
|
|
133
|
+
const filtered = useMemo(() => {
|
|
134
|
+
return activeSkills.filter((p) => {
|
|
135
|
+
if (agentVersion !== "__all__" && p.agentVersion !== agentVersion)
|
|
136
|
+
return false;
|
|
137
|
+
if (statusFilter !== "__all__" && p.status !== statusFilter) return false;
|
|
138
|
+
if (search) {
|
|
139
|
+
const s = search.toLowerCase();
|
|
140
|
+
const hay = `${p.content} ${p.trigger ?? ""} ${p.rationale ?? ""}`.toLowerCase();
|
|
141
|
+
if (!hay.includes(s)) return false;
|
|
142
|
+
}
|
|
143
|
+
return true;
|
|
144
|
+
});
|
|
145
|
+
}, [activeSkills, agentVersion, statusFilter, search]);
|
|
146
|
+
|
|
147
|
+
const projectCount = projectSkills?.length ?? 0;
|
|
148
|
+
const sharedCount = sharedSkills?.length ?? 0;
|
|
149
|
+
const activeCount = activeKind === "project" ? projectCount : sharedCount;
|
|
150
|
+
const loading = projectSkills === null || sharedSkills === null;
|
|
151
|
+
const hasNoSharedSkills = activeKind === "shared" && sharedCount === 0;
|
|
152
|
+
|
|
153
|
+
const switchKind = (kind: SkillKind) => {
|
|
154
|
+
setActiveKind(kind);
|
|
155
|
+
setAgentVersion("__all__");
|
|
156
|
+
setStatusFilter(kind === "project" ? "CURRENT" : "__all__");
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<div className="flex-1 overflow-auto">
|
|
161
|
+
<PageHeader
|
|
162
|
+
title="Skills"
|
|
163
|
+
description="Project-specific and shared skills learned from corrections."
|
|
164
|
+
actions={
|
|
165
|
+
<div className="flex max-w-full flex-wrap items-center justify-end gap-2">
|
|
166
|
+
<Select
|
|
167
|
+
value={agentVersion}
|
|
168
|
+
onValueChange={(v) => setAgentVersion(v ?? "__all__")}
|
|
169
|
+
>
|
|
170
|
+
<SelectTrigger size="sm" className="w-40 text-xs">
|
|
171
|
+
<SelectValue placeholder="Project" />
|
|
172
|
+
</SelectTrigger>
|
|
173
|
+
<SelectContent>
|
|
174
|
+
<SelectItem value="__all__">All projects</SelectItem>
|
|
175
|
+
{projects.map((p) => (
|
|
176
|
+
<SelectItem key={p} value={p}>
|
|
177
|
+
{p}
|
|
178
|
+
</SelectItem>
|
|
179
|
+
))}
|
|
180
|
+
</SelectContent>
|
|
181
|
+
</Select>
|
|
182
|
+
<Select
|
|
183
|
+
value={statusFilter}
|
|
184
|
+
onValueChange={(v) => setStatusFilter(v ?? "__all__")}
|
|
185
|
+
>
|
|
186
|
+
<SelectTrigger size="sm" className="w-36 text-xs">
|
|
187
|
+
<SelectValue placeholder="Status" />
|
|
188
|
+
</SelectTrigger>
|
|
189
|
+
<SelectContent>
|
|
190
|
+
<SelectItem value="__all__">All</SelectItem>
|
|
191
|
+
{activeKind === "project" ? (
|
|
192
|
+
<>
|
|
193
|
+
<SelectItem value="CURRENT">Current</SelectItem>
|
|
194
|
+
<SelectItem value="PENDING">Pending</SelectItem>
|
|
195
|
+
<SelectItem value="ARCHIVED">Archived</SelectItem>
|
|
196
|
+
</>
|
|
197
|
+
) : (
|
|
198
|
+
<>
|
|
199
|
+
<SelectItem
|
|
200
|
+
value="PENDING"
|
|
201
|
+
title={SHARED_STATUS_META.PENDING.description}
|
|
202
|
+
>
|
|
203
|
+
{SHARED_STATUS_META.PENDING.label}
|
|
204
|
+
</SelectItem>
|
|
205
|
+
<SelectItem
|
|
206
|
+
value="APPROVED"
|
|
207
|
+
title={SHARED_STATUS_META.APPROVED.description}
|
|
208
|
+
>
|
|
209
|
+
{SHARED_STATUS_META.APPROVED.label}
|
|
210
|
+
</SelectItem>
|
|
211
|
+
<SelectItem
|
|
212
|
+
value="REJECTED"
|
|
213
|
+
title={SHARED_STATUS_META.REJECTED.description}
|
|
214
|
+
>
|
|
215
|
+
{SHARED_STATUS_META.REJECTED.label}
|
|
216
|
+
</SelectItem>
|
|
217
|
+
</>
|
|
218
|
+
)}
|
|
219
|
+
</SelectContent>
|
|
220
|
+
</Select>
|
|
221
|
+
<Input
|
|
222
|
+
value={search}
|
|
223
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
224
|
+
placeholder="Search"
|
|
225
|
+
className="h-8 w-48 text-xs"
|
|
226
|
+
/>
|
|
227
|
+
<DeleteAllButton
|
|
228
|
+
label={`Delete ${activeKind === "project" ? "project" : "shared"}${activeCount > 0 ? ` (${activeCount})` : ""}`}
|
|
229
|
+
confirmMessage={`Delete ALL ${activeCount} ${activeKind === "project" ? "project-specific skills" : "shared skills"}? This cannot be undone.`}
|
|
230
|
+
disabled={activeCount === 0}
|
|
231
|
+
onConfirm={async () => {
|
|
232
|
+
if (activeKind === "project") {
|
|
233
|
+
await reflexio.deleteAllUserPlaybooks(reflexioUrl);
|
|
234
|
+
setProjectSkills([]);
|
|
235
|
+
} else {
|
|
236
|
+
await reflexio.deleteAllAgentPlaybooks(reflexioUrl);
|
|
237
|
+
setSharedSkills([]);
|
|
238
|
+
}
|
|
239
|
+
}}
|
|
240
|
+
/>
|
|
241
|
+
</div>
|
|
242
|
+
}
|
|
243
|
+
/>
|
|
244
|
+
|
|
245
|
+
<div className="p-6 space-y-4">
|
|
246
|
+
<PageTabs
|
|
247
|
+
activeId={activeKind}
|
|
248
|
+
onSelect={(id) => switchKind(id as SkillKind)}
|
|
249
|
+
items={[
|
|
250
|
+
{
|
|
251
|
+
id: "project",
|
|
252
|
+
label: "Project-specific skills",
|
|
253
|
+
description: "Repo-local rules learned from direct corrections",
|
|
254
|
+
count: projectCount,
|
|
255
|
+
icon: BookOpen,
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: "shared",
|
|
259
|
+
label: "Shared skills",
|
|
260
|
+
description: "Rollups available across projects",
|
|
261
|
+
count: sharedCount,
|
|
262
|
+
icon: Layers3,
|
|
263
|
+
},
|
|
264
|
+
]}
|
|
265
|
+
/>
|
|
266
|
+
|
|
267
|
+
{error && (
|
|
268
|
+
<div className="rounded-lg border border-destructive/30 bg-destructive/5 text-destructive px-4 py-3 text-sm">
|
|
269
|
+
{error}. Is reflexio running on the URL in the top bar?
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
|
|
273
|
+
{loading && !error ? (
|
|
274
|
+
<div className="text-sm text-muted-foreground">Loading...</div>
|
|
275
|
+
) : filtered.length === 0 ? (
|
|
276
|
+
<EmptyState
|
|
277
|
+
icon={activeKind === "project" ? BookOpen : Layers3}
|
|
278
|
+
title={
|
|
279
|
+
hasNoSharedSkills
|
|
280
|
+
? "No shared skills yet"
|
|
281
|
+
: `No ${activeKind === "project" ? "project-specific skills" : "shared skills"} match`
|
|
282
|
+
}
|
|
283
|
+
description={
|
|
284
|
+
hasNoSharedSkills
|
|
285
|
+
? "Shared skills become available after claude-smart has learnings from more than one repo. Keep using Claude with claude-smart enabled across projects, and shared patterns will appear here when they emerge."
|
|
286
|
+
: "Adjust the filters, or keep using Claude with claude-smart enabled. Skills are extracted automatically when patterns emerge."
|
|
287
|
+
}
|
|
288
|
+
/>
|
|
289
|
+
) : (
|
|
290
|
+
<div className="grid gap-3 lg:grid-cols-2">
|
|
291
|
+
{filtered.map((p) => (
|
|
292
|
+
<Link
|
|
293
|
+
key={`${p.kind}:${p.id}`}
|
|
294
|
+
href={`/skills/${p.kind}/${p.id}`}
|
|
295
|
+
className="block rounded-xl border border-border bg-card p-4 hover:bg-accent/40 transition-colors"
|
|
296
|
+
>
|
|
297
|
+
<header className="flex items-center justify-between gap-2 mb-2">
|
|
298
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
299
|
+
<Badge variant="outline" className="h-5 font-mono text-[10px]">
|
|
300
|
+
{p.agentVersion}
|
|
301
|
+
</Badge>
|
|
302
|
+
<StatusBadge kind={p.kind} status={p.status} />
|
|
303
|
+
<Badge variant="secondary" className="h-5 text-[10px]">
|
|
304
|
+
{p.kind === "project" ? "project-specific" : "shared"}
|
|
305
|
+
</Badge>
|
|
306
|
+
</div>
|
|
307
|
+
<span className="text-[11px] text-muted-foreground shrink-0">
|
|
308
|
+
{formatRelative(p.createdAt)}
|
|
309
|
+
</span>
|
|
310
|
+
</header>
|
|
311
|
+
<p
|
|
312
|
+
className={cn(
|
|
313
|
+
"text-sm leading-relaxed line-clamp-4",
|
|
314
|
+
!p.trigger && "text-muted-foreground italic",
|
|
315
|
+
)}
|
|
316
|
+
>
|
|
317
|
+
{p.trigger || "Always applies"}
|
|
318
|
+
</p>
|
|
319
|
+
<p className="text-xs text-muted-foreground mt-2 line-clamp-2">
|
|
320
|
+
<span className="font-medium">Rule:</span> {p.content}
|
|
321
|
+
</p>
|
|
322
|
+
{p.rationale && (
|
|
323
|
+
<p className="text-xs text-muted-foreground mt-1 line-clamp-2">
|
|
324
|
+
<span className="font-medium">Why:</span> {p.rationale}
|
|
325
|
+
</p>
|
|
326
|
+
)}
|
|
327
|
+
</Link>
|
|
328
|
+
))}
|
|
329
|
+
</div>
|
|
330
|
+
)}
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function StatusBadge({
|
|
337
|
+
kind,
|
|
338
|
+
status,
|
|
339
|
+
}: {
|
|
340
|
+
kind: SkillKind;
|
|
341
|
+
status: SkillStatus;
|
|
342
|
+
}) {
|
|
343
|
+
const sharedMeta =
|
|
344
|
+
kind === "shared"
|
|
345
|
+
? SHARED_STATUS_META[status as AgentPlaybookStatusLabel]
|
|
346
|
+
: null;
|
|
347
|
+
const variant =
|
|
348
|
+
status === "CURRENT" || status === "APPROVED"
|
|
349
|
+
? "secondary"
|
|
350
|
+
: status === "ARCHIVED" || status === "REJECTED"
|
|
351
|
+
? "outline"
|
|
352
|
+
: "default";
|
|
353
|
+
return (
|
|
354
|
+
<Badge
|
|
355
|
+
variant={variant}
|
|
356
|
+
className="h-5 text-[10px]"
|
|
357
|
+
title={sharedMeta?.description}
|
|
358
|
+
>
|
|
359
|
+
{sharedMeta?.label ?? status}
|
|
360
|
+
</Badge>
|
|
361
|
+
);
|
|
362
|
+
}
|