selftune 0.2.21 → 0.2.23
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/README.md +15 -8
- package/apps/local-dashboard/dist/assets/index-CwOtTrUS.css +1 -0
- package/apps/local-dashboard/dist/assets/index-f1HQpbeH.js +59 -0
- package/apps/local-dashboard/dist/assets/vendor-ui-jVSaIZey.js +12 -0
- package/apps/local-dashboard/dist/index.html +3 -3
- package/cli/selftune/adapters/cline/hook.ts +167 -0
- package/cli/selftune/adapters/cline/install.ts +197 -0
- package/cli/selftune/adapters/codex/hook.ts +296 -0
- package/cli/selftune/adapters/codex/install.ts +289 -0
- package/cli/selftune/adapters/opencode/hook.ts +222 -0
- package/cli/selftune/adapters/opencode/install.ts +543 -0
- package/cli/selftune/adapters/pi/hook.ts +273 -0
- package/cli/selftune/adapters/pi/install.ts +207 -0
- package/cli/selftune/constants.ts +10 -1
- package/cli/selftune/dashboard-contract.ts +14 -0
- package/cli/selftune/evolution/engines/judge-engine.ts +96 -0
- package/cli/selftune/evolution/engines/replay-engine.ts +158 -0
- package/cli/selftune/evolution/evidence.ts +2 -6
- package/cli/selftune/evolution/evolve-body.ts +73 -20
- package/cli/selftune/evolution/validate-body.ts +78 -42
- package/cli/selftune/evolution/validate-routing.ts +45 -104
- package/cli/selftune/hooks/auto-activate.ts +43 -37
- package/cli/selftune/hooks/skill-eval.ts +2 -1
- package/cli/selftune/hooks-shared/git-metadata.ts +149 -0
- package/cli/selftune/hooks-shared/hook-output.ts +105 -0
- package/cli/selftune/hooks-shared/normalize.ts +196 -0
- package/cli/selftune/hooks-shared/session-state.ts +76 -0
- package/cli/selftune/hooks-shared/skill-paths.ts +50 -0
- package/cli/selftune/hooks-shared/stdin-dispatch.ts +59 -0
- package/cli/selftune/hooks-shared/types.ts +91 -0
- package/cli/selftune/index.ts +76 -6
- package/cli/selftune/ingestors/pi-ingest.ts +726 -0
- package/cli/selftune/init.ts +11 -1
- package/cli/selftune/localdb/direct-write.ts +85 -0
- package/cli/selftune/localdb/materialize.ts +6 -7
- package/cli/selftune/localdb/queries.ts +126 -0
- package/cli/selftune/localdb/schema.ts +38 -0
- package/cli/selftune/observability.ts +8 -1
- package/cli/selftune/orchestrate.ts +43 -0
- package/cli/selftune/registry/client.ts +74 -0
- package/cli/selftune/registry/history.ts +54 -0
- package/cli/selftune/registry/index.ts +90 -0
- package/cli/selftune/registry/install.ts +141 -0
- package/cli/selftune/registry/list.ts +44 -0
- package/cli/selftune/registry/push.ts +171 -0
- package/cli/selftune/registry/rollback.ts +49 -0
- package/cli/selftune/registry/status.ts +62 -0
- package/cli/selftune/registry/sync.ts +125 -0
- package/cli/selftune/repair/skill-usage.ts +4 -1
- package/cli/selftune/status.ts +31 -0
- package/cli/selftune/sync.ts +127 -23
- package/cli/selftune/types.ts +2 -1
- package/cli/selftune/utils/jsonl.ts +1 -30
- package/cli/selftune/utils/llm-call.ts +99 -34
- package/cli/selftune/utils/skill-discovery.ts +22 -0
- package/node_modules/@selftune/telemetry-contract/fixtures/evidence-only-push.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/package.json +1 -1
- package/node_modules/@selftune/telemetry-contract/src/index.ts +1 -0
- package/node_modules/@selftune/telemetry-contract/src/schemas.ts +22 -4
- package/node_modules/@selftune/telemetry-contract/src/types.ts +1 -12
- package/node_modules/@selftune/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/package.json +1 -1
- package/packages/telemetry-contract/fixtures/evidence-only-push.ts +1 -1
- package/packages/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
- package/packages/telemetry-contract/package.json +1 -1
- package/packages/telemetry-contract/src/index.ts +1 -0
- package/packages/telemetry-contract/src/schemas.ts +22 -4
- package/packages/telemetry-contract/src/types.ts +1 -12
- package/packages/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/packages/ui/AGENTS.md +16 -0
- package/packages/ui/README.md +1 -1
- package/packages/ui/package.json +1 -1
- package/packages/ui/src/components/ActivityTimeline.tsx +152 -168
- package/packages/ui/src/components/AnalyticsCharts.tsx +344 -0
- package/packages/ui/src/components/EvidenceViewer.tsx +153 -443
- package/packages/ui/src/components/EvolutionTimeline.tsx +34 -87
- package/packages/ui/src/components/InfoTip.tsx +1 -2
- package/packages/ui/src/components/InvocationsPanel.tsx +413 -0
- package/packages/ui/src/components/JobHistoryTimeline.tsx +156 -0
- package/packages/ui/src/components/OrchestrateRunsPanel.tsx +18 -36
- package/packages/ui/src/components/OverviewPanels.tsx +652 -0
- package/packages/ui/src/components/PipelineStatusBar.tsx +65 -0
- package/packages/ui/src/components/SkillReportGuide.tsx +215 -0
- package/packages/ui/src/components/SkillReportPanels.tsx +919 -0
- package/packages/ui/src/components/SkillsLibrary.tsx +437 -0
- package/packages/ui/src/components/index.ts +56 -1
- package/packages/ui/src/components/section-cards.tsx +18 -35
- package/packages/ui/src/components/skill-health-grid.tsx +47 -37
- package/packages/ui/src/lib/constants.tsx +0 -1
- package/packages/ui/src/primitives/card.tsx +1 -1
- package/packages/ui/src/primitives/checkbox.tsx +1 -1
- package/packages/ui/src/primitives/dropdown-menu.tsx +2 -2
- package/packages/ui/src/primitives/select.tsx +2 -2
- package/packages/ui/src/types.ts +172 -4
- package/skill/SKILL.md +26 -2
- package/skill/Workflows/Ingest.md +60 -2
- package/skill/Workflows/Initialize.md +54 -9
- package/skill/Workflows/PlatformHooks.md +109 -0
- package/skill/Workflows/Registry.md +99 -0
- package/skill/Workflows/Sync.md +3 -1
- package/apps/local-dashboard/dist/assets/index-D8O-RG1I.js +0 -60
- package/apps/local-dashboard/dist/assets/index-_EcLywDg.css +0 -1
- package/apps/local-dashboard/dist/assets/vendor-ui-CGEmUayx.js +0 -12
- package/cli/selftune/utils/html.ts +0 -27
- package/packages/ui/src/components/RecentActivityFeed.tsx +0 -117
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Zod validation schemas for all canonical telemetry record types
|
|
3
|
+
* and the PushPayloadV2 envelope.
|
|
4
|
+
*
|
|
5
|
+
* This is the single source of truth -- cloud consumers should import
|
|
6
|
+
* from @selftune/telemetry-contract/schemas instead of maintaining
|
|
7
|
+
* their own copies.
|
|
8
|
+
*/
|
|
2
9
|
|
|
10
|
+
import { z } from "zod";
|
|
3
11
|
import {
|
|
4
12
|
CANONICAL_CAPTURE_MODES,
|
|
5
13
|
CANONICAL_COMPLETION_STATUSES,
|
|
@@ -11,6 +19,8 @@ import {
|
|
|
11
19
|
CANONICAL_SOURCE_SESSION_KINDS,
|
|
12
20
|
} from "./types.js";
|
|
13
21
|
|
|
22
|
+
// ---------- Shared enum schemas ----------
|
|
23
|
+
|
|
14
24
|
export const canonicalPlatformSchema = z.enum(CANONICAL_PLATFORMS);
|
|
15
25
|
export const captureModeSchema = z.enum(CANONICAL_CAPTURE_MODES);
|
|
16
26
|
export const sourceSessionKindSchema = z.enum(CANONICAL_SOURCE_SESSION_KINDS);
|
|
@@ -19,6 +29,8 @@ export const invocationModeSchema = z.enum(CANONICAL_INVOCATION_MODES);
|
|
|
19
29
|
export const completionStatusSchema = z.enum(CANONICAL_COMPLETION_STATUSES);
|
|
20
30
|
export const recordKindSchema = z.enum(CANONICAL_RECORD_KINDS);
|
|
21
31
|
|
|
32
|
+
// ---------- Shared structural schemas ----------
|
|
33
|
+
|
|
22
34
|
export const rawSourceRefSchema = z.object({
|
|
23
35
|
path: z.string().optional(),
|
|
24
36
|
line: z.number().int().nonnegative().optional(),
|
|
@@ -42,6 +54,8 @@ export const canonicalSessionRecordBaseSchema = canonicalRecordBaseSchema.extend
|
|
|
42
54
|
session_id: z.string().min(1),
|
|
43
55
|
});
|
|
44
56
|
|
|
57
|
+
// ---------- Canonical record schemas ----------
|
|
58
|
+
|
|
45
59
|
export const CanonicalSessionRecordSchema = canonicalSessionRecordBaseSchema.extend({
|
|
46
60
|
record_kind: z.literal("session"),
|
|
47
61
|
external_session_id: z.string().optional(),
|
|
@@ -137,6 +151,8 @@ export const CanonicalEvolutionEvidenceRecordSchema = z.object({
|
|
|
137
151
|
raw_source_ref: rawSourceRefSchema.optional(),
|
|
138
152
|
});
|
|
139
153
|
|
|
154
|
+
// ---------- Orchestrate run schemas ----------
|
|
155
|
+
|
|
140
156
|
export const OrchestrateRunSkillActionSchema = z.object({
|
|
141
157
|
skill: z.string().min(1),
|
|
142
158
|
action: z.enum(["evolve", "watch", "skip"]),
|
|
@@ -163,12 +179,12 @@ export const PushOrchestrateRunRecordSchema = z.object({
|
|
|
163
179
|
skill_actions: z.array(OrchestrateRunSkillActionSchema),
|
|
164
180
|
});
|
|
165
181
|
|
|
182
|
+
// ---------- Push V2 envelope ----------
|
|
183
|
+
|
|
166
184
|
export const PushPayloadV2Schema = z.object({
|
|
167
185
|
schema_version: z.literal("2.0"),
|
|
168
186
|
client_version: z.string().min(1),
|
|
169
|
-
|
|
170
|
-
// requires a stable non-empty idempotency key.
|
|
171
|
-
push_id: z.string().min(1),
|
|
187
|
+
push_id: z.string().uuid(),
|
|
172
188
|
normalizer_version: z.string().min(1),
|
|
173
189
|
canonical: z.object({
|
|
174
190
|
sessions: z.array(CanonicalSessionRecordSchema).min(0),
|
|
@@ -181,6 +197,8 @@ export const PushPayloadV2Schema = z.object({
|
|
|
181
197
|
}),
|
|
182
198
|
});
|
|
183
199
|
|
|
200
|
+
// ---------- Inferred types from Zod schemas ----------
|
|
201
|
+
|
|
184
202
|
export type PushPayloadV2 = z.infer<typeof PushPayloadV2Schema>;
|
|
185
203
|
export type ZodCanonicalSessionRecord = z.infer<typeof CanonicalSessionRecordSchema>;
|
|
186
204
|
export type ZodCanonicalPromptRecord = z.infer<typeof CanonicalPromptRecordSchema>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const CANONICAL_SCHEMA_VERSION = "2.0" as const;
|
|
2
2
|
export type CanonicalSchemaVersion = typeof CANONICAL_SCHEMA_VERSION;
|
|
3
3
|
|
|
4
|
-
export const CANONICAL_PLATFORMS = ["claude_code", "codex", "opencode", "openclaw"] as const;
|
|
4
|
+
export const CANONICAL_PLATFORMS = ["claude_code", "codex", "opencode", "openclaw", "pi"] as const;
|
|
5
5
|
export type CanonicalPlatform = (typeof CANONICAL_PLATFORMS)[number];
|
|
6
6
|
|
|
7
7
|
export const CANONICAL_CAPTURE_MODES = [
|
|
@@ -143,18 +143,7 @@ export interface CanonicalExecutionFactRecord extends CanonicalSessionRecordBase
|
|
|
143
143
|
errors_encountered: number;
|
|
144
144
|
input_tokens?: number;
|
|
145
145
|
output_tokens?: number;
|
|
146
|
-
cached_input_tokens?: number;
|
|
147
|
-
reasoning_output_tokens?: number;
|
|
148
|
-
cost_usd?: number;
|
|
149
146
|
duration_ms?: number;
|
|
150
|
-
files_changed?: number;
|
|
151
|
-
lines_added?: number;
|
|
152
|
-
lines_removed?: number;
|
|
153
|
-
lines_modified?: number;
|
|
154
|
-
/** Count of output-producing tool calls (Write, Edit, WebFetch, WebSearch, Skill, Agent). */
|
|
155
|
-
artifact_count?: number;
|
|
156
|
-
/** Inferred session type based on tool distribution. */
|
|
157
|
-
session_type?: "dev" | "research" | "content" | "mixed";
|
|
158
147
|
completion_status?: CanonicalCompletionStatus;
|
|
159
148
|
end_reason?: string;
|
|
160
149
|
}
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@ import type { PushPayloadV2 } from "../src/schemas.js";
|
|
|
7
7
|
export const evidenceOnlyPush: PushPayloadV2 = {
|
|
8
8
|
schema_version: "2.0",
|
|
9
9
|
client_version: "0.9.0",
|
|
10
|
-
push_id: "d4e5f6a7-b8c9-
|
|
10
|
+
push_id: "d4e5f6a7-b8c9-8123-9efa-234567890123",
|
|
11
11
|
normalizer_version: "0.2.1",
|
|
12
12
|
canonical: {
|
|
13
13
|
sessions: [],
|
|
@@ -10,7 +10,7 @@ import type { PushPayloadV2 } from "../src/schemas.js";
|
|
|
10
10
|
export const partialPushUnresolvedParents: PushPayloadV2 = {
|
|
11
11
|
schema_version: "2.0",
|
|
12
12
|
client_version: "0.9.0",
|
|
13
|
-
push_id: "c3d4e5f6-a7b8-
|
|
13
|
+
push_id: "c3d4e5f6-a7b8-8012-8def-123456789012",
|
|
14
14
|
normalizer_version: "0.2.1",
|
|
15
15
|
canonical: {
|
|
16
16
|
sessions: [],
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"type": "module",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": "./index.ts",
|
|
16
|
-
"./schemas": "./src/schemas.ts",
|
|
17
16
|
"./types": "./src/types.ts",
|
|
18
17
|
"./validators": "./src/validators.ts",
|
|
18
|
+
"./schemas": "./src/schemas.ts",
|
|
19
19
|
"./fixtures": "./fixtures/index.ts"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Zod validation schemas for all canonical telemetry record types
|
|
3
|
+
* and the PushPayloadV2 envelope.
|
|
4
|
+
*
|
|
5
|
+
* This is the single source of truth -- cloud consumers should import
|
|
6
|
+
* from @selftune/telemetry-contract/schemas instead of maintaining
|
|
7
|
+
* their own copies.
|
|
8
|
+
*/
|
|
2
9
|
|
|
10
|
+
import { z } from "zod";
|
|
3
11
|
import {
|
|
4
12
|
CANONICAL_CAPTURE_MODES,
|
|
5
13
|
CANONICAL_COMPLETION_STATUSES,
|
|
@@ -11,6 +19,8 @@ import {
|
|
|
11
19
|
CANONICAL_SOURCE_SESSION_KINDS,
|
|
12
20
|
} from "./types.js";
|
|
13
21
|
|
|
22
|
+
// ---------- Shared enum schemas ----------
|
|
23
|
+
|
|
14
24
|
export const canonicalPlatformSchema = z.enum(CANONICAL_PLATFORMS);
|
|
15
25
|
export const captureModeSchema = z.enum(CANONICAL_CAPTURE_MODES);
|
|
16
26
|
export const sourceSessionKindSchema = z.enum(CANONICAL_SOURCE_SESSION_KINDS);
|
|
@@ -19,6 +29,8 @@ export const invocationModeSchema = z.enum(CANONICAL_INVOCATION_MODES);
|
|
|
19
29
|
export const completionStatusSchema = z.enum(CANONICAL_COMPLETION_STATUSES);
|
|
20
30
|
export const recordKindSchema = z.enum(CANONICAL_RECORD_KINDS);
|
|
21
31
|
|
|
32
|
+
// ---------- Shared structural schemas ----------
|
|
33
|
+
|
|
22
34
|
export const rawSourceRefSchema = z.object({
|
|
23
35
|
path: z.string().optional(),
|
|
24
36
|
line: z.number().int().nonnegative().optional(),
|
|
@@ -42,6 +54,8 @@ export const canonicalSessionRecordBaseSchema = canonicalRecordBaseSchema.extend
|
|
|
42
54
|
session_id: z.string().min(1),
|
|
43
55
|
});
|
|
44
56
|
|
|
57
|
+
// ---------- Canonical record schemas ----------
|
|
58
|
+
|
|
45
59
|
export const CanonicalSessionRecordSchema = canonicalSessionRecordBaseSchema.extend({
|
|
46
60
|
record_kind: z.literal("session"),
|
|
47
61
|
external_session_id: z.string().optional(),
|
|
@@ -137,6 +151,8 @@ export const CanonicalEvolutionEvidenceRecordSchema = z.object({
|
|
|
137
151
|
raw_source_ref: rawSourceRefSchema.optional(),
|
|
138
152
|
});
|
|
139
153
|
|
|
154
|
+
// ---------- Orchestrate run schemas ----------
|
|
155
|
+
|
|
140
156
|
export const OrchestrateRunSkillActionSchema = z.object({
|
|
141
157
|
skill: z.string().min(1),
|
|
142
158
|
action: z.enum(["evolve", "watch", "skip"]),
|
|
@@ -163,12 +179,12 @@ export const PushOrchestrateRunRecordSchema = z.object({
|
|
|
163
179
|
skill_actions: z.array(OrchestrateRunSkillActionSchema),
|
|
164
180
|
});
|
|
165
181
|
|
|
182
|
+
// ---------- Push V2 envelope ----------
|
|
183
|
+
|
|
166
184
|
export const PushPayloadV2Schema = z.object({
|
|
167
185
|
schema_version: z.literal("2.0"),
|
|
168
186
|
client_version: z.string().min(1),
|
|
169
|
-
|
|
170
|
-
// requires a stable non-empty idempotency key.
|
|
171
|
-
push_id: z.string().min(1),
|
|
187
|
+
push_id: z.string().uuid(),
|
|
172
188
|
normalizer_version: z.string().min(1),
|
|
173
189
|
canonical: z.object({
|
|
174
190
|
sessions: z.array(CanonicalSessionRecordSchema).min(0),
|
|
@@ -181,6 +197,8 @@ export const PushPayloadV2Schema = z.object({
|
|
|
181
197
|
}),
|
|
182
198
|
});
|
|
183
199
|
|
|
200
|
+
// ---------- Inferred types from Zod schemas ----------
|
|
201
|
+
|
|
184
202
|
export type PushPayloadV2 = z.infer<typeof PushPayloadV2Schema>;
|
|
185
203
|
export type ZodCanonicalSessionRecord = z.infer<typeof CanonicalSessionRecordSchema>;
|
|
186
204
|
export type ZodCanonicalPromptRecord = z.infer<typeof CanonicalPromptRecordSchema>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const CANONICAL_SCHEMA_VERSION = "2.0" as const;
|
|
2
2
|
export type CanonicalSchemaVersion = typeof CANONICAL_SCHEMA_VERSION;
|
|
3
3
|
|
|
4
|
-
export const CANONICAL_PLATFORMS = ["claude_code", "codex", "opencode", "openclaw"] as const;
|
|
4
|
+
export const CANONICAL_PLATFORMS = ["claude_code", "codex", "opencode", "openclaw", "pi"] as const;
|
|
5
5
|
export type CanonicalPlatform = (typeof CANONICAL_PLATFORMS)[number];
|
|
6
6
|
|
|
7
7
|
export const CANONICAL_CAPTURE_MODES = [
|
|
@@ -143,18 +143,7 @@ export interface CanonicalExecutionFactRecord extends CanonicalSessionRecordBase
|
|
|
143
143
|
errors_encountered: number;
|
|
144
144
|
input_tokens?: number;
|
|
145
145
|
output_tokens?: number;
|
|
146
|
-
cached_input_tokens?: number;
|
|
147
|
-
reasoning_output_tokens?: number;
|
|
148
|
-
cost_usd?: number;
|
|
149
146
|
duration_ms?: number;
|
|
150
|
-
files_changed?: number;
|
|
151
|
-
lines_added?: number;
|
|
152
|
-
lines_removed?: number;
|
|
153
|
-
lines_modified?: number;
|
|
154
|
-
/** Count of output-producing tool calls (Write, Edit, WebFetch, WebSearch, Skill, Agent). */
|
|
155
|
-
artifact_count?: number;
|
|
156
|
-
/** Inferred session type based on tool distribution. */
|
|
157
|
-
session_type?: "dev" | "research" | "content" | "mixed";
|
|
158
147
|
completion_status?: CanonicalCompletionStatus;
|
|
159
148
|
end_reason?: string;
|
|
160
149
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# @selftune/ui
|
|
2
|
+
|
|
3
|
+
Shared React component library used by both the cloud dashboard and the local OSS dashboard. Canonical copy lives here; synced to `oss/selftune/packages/ui` via `scripts/sync-embedded-shared.sh`.
|
|
4
|
+
|
|
5
|
+
| Directory | Contents |
|
|
6
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
7
|
+
| `src/primitives/` | Base UI: Badge, Button, Card, Checkbox, Collapsible, DropdownMenu, Label, Select, Table, Tabs, Tooltip |
|
|
8
|
+
| `src/components/` | Shared components: SkillHealthGrid, EvolutionTimeline, EvidenceViewer, ActivityTimeline, OrchestrateRunsPanel, SectionCards, InfoTip, SkillReportPanels, SkillReportGuide, InvocationsPanel, SkillsLibrary, AnalyticsCharts, OverviewPanels |
|
|
9
|
+
| `src/types.ts` | Shared types: SkillCard, SkillHealthStatus, EvalSnapshot, EvolutionEntry, TrustState, TrustFields, ExampleRow, AutonomyStatus, TrustWatchlistEntry, AttentionItem, AutonomousDecision |
|
|
10
|
+
| `src/lib/` | Utilities: format (formatRate, timeAgo), constants (STATUS_CONFIG), utils (deriveStatus, sortByPassRateAndChecks) |
|
|
11
|
+
|
|
12
|
+
**Exports:** `./primitives`, `./components`, `./types`, `./lib`
|
|
13
|
+
|
|
14
|
+
**Dependencies:** react, @base-ui/react, lucide-react, clsx, tailwind-merge
|
|
15
|
+
|
|
16
|
+
**Important:** Do NOT edit `oss/selftune/packages/ui/` directly. Edit here and run `scripts/sync-embedded-shared.sh`.
|
package/packages/ui/README.md
CHANGED
|
@@ -57,7 +57,7 @@ Presentational components for selftune dashboard views. No data fetching, no rou
|
|
|
57
57
|
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
58
58
|
| `SkillHealthGrid` | Sortable/filterable data table with drag-and-drop, pagination, and view tabs. Accepts `renderSkillName` prop for custom routing. |
|
|
59
59
|
| `EvolutionTimeline` | Proposal lifecycle timeline grouped by proposal ID, with pass rate deltas. |
|
|
60
|
-
| `ActivityPanel` | Tabbed activity feed (
|
|
60
|
+
| `ActivityPanel` | Tabbed activity feed (pending proposals, timeline events, unmatched queries). |
|
|
61
61
|
| `EvidenceViewer` | Full evidence trail for a proposal — side-by-side diffs, validation results, iteration rounds. |
|
|
62
62
|
| `SectionCards` | Dashboard metric stat cards (skills count, pass rate, unmatched, sessions, etc.). |
|
|
63
63
|
| `OrchestrateRunsPanel` | Collapsible orchestrate run reports with per-skill action details. |
|
package/packages/ui/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { ClockIcon, GitPullRequestArrowIcon, SearchXIcon, ActivityIcon } from "lucide-react";
|
|
2
|
-
|
|
3
|
-
import { timeAgo } from "../lib/format";
|
|
4
1
|
import { Badge } from "../primitives/badge";
|
|
5
2
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../primitives/card";
|
|
6
3
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../primitives/tabs";
|
|
7
4
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/tooltip";
|
|
8
5
|
import type { EvolutionEntry, PendingProposal, UnmatchedQuery } from "../types";
|
|
6
|
+
import { timeAgo } from "../lib/format";
|
|
7
|
+
import { ClockIcon, GitPullRequestArrowIcon, SearchXIcon, ActivityIcon } from "lucide-react";
|
|
9
8
|
|
|
10
9
|
const ACTION_VARIANT: Record<string, "default" | "secondary" | "destructive" | "outline"> = {
|
|
11
10
|
created: "outline",
|
|
@@ -21,179 +20,15 @@ export function ActivityPanel({
|
|
|
21
20
|
pendingProposals,
|
|
22
21
|
unmatchedQueries,
|
|
23
22
|
onSelectProposal,
|
|
24
|
-
embedded = false,
|
|
25
23
|
}: {
|
|
26
24
|
evolution: EvolutionEntry[];
|
|
27
25
|
pendingProposals: PendingProposal[];
|
|
28
26
|
unmatchedQueries: UnmatchedQuery[];
|
|
29
27
|
onSelectProposal?: (skillName: string, proposalId: string) => void;
|
|
30
|
-
embedded?: boolean;
|
|
31
28
|
}) {
|
|
32
29
|
const hasActivity =
|
|
33
30
|
evolution.length > 0 || pendingProposals.length > 0 || unmatchedQueries.length > 0;
|
|
34
31
|
|
|
35
|
-
const content = hasActivity ? (
|
|
36
|
-
<Tabs
|
|
37
|
-
defaultValue={
|
|
38
|
-
pendingProposals.length > 0 ? "pending" : evolution.length > 0 ? "timeline" : "unmatched"
|
|
39
|
-
}
|
|
40
|
-
>
|
|
41
|
-
<TooltipProvider>
|
|
42
|
-
<TabsList className="w-full">
|
|
43
|
-
{pendingProposals.length > 0 && (
|
|
44
|
-
<Tooltip>
|
|
45
|
-
<TooltipTrigger
|
|
46
|
-
render={
|
|
47
|
-
<TabsTrigger
|
|
48
|
-
value="pending"
|
|
49
|
-
className="flex-1 gap-1.5"
|
|
50
|
-
aria-label={`Pending proposals (${pendingProposals.length})`}
|
|
51
|
-
/>
|
|
52
|
-
}
|
|
53
|
-
>
|
|
54
|
-
<GitPullRequestArrowIcon className="size-3.5" />
|
|
55
|
-
<Badge variant="secondary" className="h-4 px-1 text-[10px]">
|
|
56
|
-
{pendingProposals.length}
|
|
57
|
-
</Badge>
|
|
58
|
-
</TooltipTrigger>
|
|
59
|
-
<TooltipContent>Undeployed proposals</TooltipContent>
|
|
60
|
-
</Tooltip>
|
|
61
|
-
)}
|
|
62
|
-
<Tooltip>
|
|
63
|
-
<TooltipTrigger
|
|
64
|
-
render={<TabsTrigger value="timeline" className="flex-1" aria-label="Timeline" />}
|
|
65
|
-
>
|
|
66
|
-
<ClockIcon className="size-3.5" />
|
|
67
|
-
</TooltipTrigger>
|
|
68
|
-
<TooltipContent>Timeline</TooltipContent>
|
|
69
|
-
</Tooltip>
|
|
70
|
-
{unmatchedQueries.length > 0 && (
|
|
71
|
-
<Tooltip>
|
|
72
|
-
<TooltipTrigger
|
|
73
|
-
render={
|
|
74
|
-
<TabsTrigger
|
|
75
|
-
value="unmatched"
|
|
76
|
-
className="flex-1 gap-1.5"
|
|
77
|
-
aria-label={`Unmatched queries (${unmatchedQueries.length})`}
|
|
78
|
-
/>
|
|
79
|
-
}
|
|
80
|
-
>
|
|
81
|
-
<SearchXIcon className="size-3.5" />
|
|
82
|
-
<Badge variant="destructive" className="h-4 px-1 text-[10px]">
|
|
83
|
-
{unmatchedQueries.length}
|
|
84
|
-
</Badge>
|
|
85
|
-
</TooltipTrigger>
|
|
86
|
-
<TooltipContent>Unmatched queries</TooltipContent>
|
|
87
|
-
</Tooltip>
|
|
88
|
-
)}
|
|
89
|
-
</TabsList>
|
|
90
|
-
</TooltipProvider>
|
|
91
|
-
|
|
92
|
-
{pendingProposals.length > 0 && (
|
|
93
|
-
<TabsContent value="pending" className="mt-4 space-y-3">
|
|
94
|
-
{pendingProposals.slice(0, 10).map((p) => (
|
|
95
|
-
<button
|
|
96
|
-
key={p.proposal_id}
|
|
97
|
-
type="button"
|
|
98
|
-
onClick={() => {
|
|
99
|
-
if (p.skill_name && onSelectProposal) onSelectProposal(p.skill_name, p.proposal_id);
|
|
100
|
-
}}
|
|
101
|
-
disabled={!p.skill_name || !onSelectProposal}
|
|
102
|
-
className="flex w-full gap-3 rounded-md p-1.5 text-left transition-colors enabled:hover:bg-accent/40 disabled:cursor-default"
|
|
103
|
-
>
|
|
104
|
-
<div className="mt-1 size-2 shrink-0 rounded-full bg-primary-accent" />
|
|
105
|
-
<div className="flex-1 min-w-0 space-y-1">
|
|
106
|
-
<div className="flex items-center gap-2">
|
|
107
|
-
<Badge variant={ACTION_VARIANT[p.action] ?? "secondary"} className="text-[10px]">
|
|
108
|
-
{p.action}
|
|
109
|
-
</Badge>
|
|
110
|
-
<span className="text-[10px] text-slate-500 font-mono">
|
|
111
|
-
{timeAgo(p.timestamp)}
|
|
112
|
-
</span>
|
|
113
|
-
</div>
|
|
114
|
-
<p className="text-xs text-muted-foreground line-clamp-2">{p.details}</p>
|
|
115
|
-
{p.skill_name && (
|
|
116
|
-
<span className="text-[10px] text-muted-foreground/60 font-mono">
|
|
117
|
-
{p.skill_name} · #{p.proposal_id.slice(0, 8)}
|
|
118
|
-
</span>
|
|
119
|
-
)}
|
|
120
|
-
</div>
|
|
121
|
-
</button>
|
|
122
|
-
))}
|
|
123
|
-
</TabsContent>
|
|
124
|
-
)}
|
|
125
|
-
|
|
126
|
-
<TabsContent value="timeline" className="mt-4 space-y-3">
|
|
127
|
-
{evolution.slice(0, 30).map((entry, i) => (
|
|
128
|
-
<button
|
|
129
|
-
key={`${entry.proposal_id}-${i}`}
|
|
130
|
-
type="button"
|
|
131
|
-
onClick={() => {
|
|
132
|
-
if (entry.skill_name && onSelectProposal)
|
|
133
|
-
onSelectProposal(entry.skill_name, entry.proposal_id);
|
|
134
|
-
}}
|
|
135
|
-
disabled={!entry.skill_name || !onSelectProposal}
|
|
136
|
-
className="flex w-full gap-3 rounded-md p-1.5 text-left transition-colors enabled:hover:bg-accent/40 disabled:cursor-default"
|
|
137
|
-
>
|
|
138
|
-
<div
|
|
139
|
-
className={`mt-1 size-2 shrink-0 rounded-full ${
|
|
140
|
-
entry.action === "deployed"
|
|
141
|
-
? "bg-primary"
|
|
142
|
-
: entry.action === "rejected" || entry.action === "rolled_back"
|
|
143
|
-
? "bg-destructive"
|
|
144
|
-
: entry.action === "validated"
|
|
145
|
-
? "bg-primary-accent"
|
|
146
|
-
: "bg-primary-accent"
|
|
147
|
-
}`}
|
|
148
|
-
/>
|
|
149
|
-
<div className="flex-1 min-w-0 space-y-1">
|
|
150
|
-
<div className="flex items-center gap-2">
|
|
151
|
-
<Badge
|
|
152
|
-
variant={ACTION_VARIANT[entry.action] ?? "secondary"}
|
|
153
|
-
className="text-[10px]"
|
|
154
|
-
>
|
|
155
|
-
{entry.action}
|
|
156
|
-
</Badge>
|
|
157
|
-
<span className="text-xs text-muted-foreground font-mono">
|
|
158
|
-
{timeAgo(entry.timestamp)}
|
|
159
|
-
</span>
|
|
160
|
-
</div>
|
|
161
|
-
<p className="text-xs text-muted-foreground line-clamp-2">{entry.details}</p>
|
|
162
|
-
<span className="text-[10px] text-muted-foreground/60 font-mono">
|
|
163
|
-
{entry.skill_name ? `${entry.skill_name} · ` : ""}#{entry.proposal_id.slice(0, 8)}
|
|
164
|
-
</span>
|
|
165
|
-
</div>
|
|
166
|
-
</button>
|
|
167
|
-
))}
|
|
168
|
-
{evolution.length === 0 && (
|
|
169
|
-
<p className="text-sm text-muted-foreground py-4 text-center">No timeline events</p>
|
|
170
|
-
)}
|
|
171
|
-
</TabsContent>
|
|
172
|
-
|
|
173
|
-
{unmatchedQueries.length > 0 && (
|
|
174
|
-
<TabsContent value="unmatched" className="mt-4 space-y-2">
|
|
175
|
-
{unmatchedQueries.slice(0, 15).map((q, i) => (
|
|
176
|
-
<div key={`${q.session_id}-${i}`} className="flex gap-3">
|
|
177
|
-
<div className="mt-1 size-2 shrink-0 rounded-full bg-muted-foreground/40" />
|
|
178
|
-
<div className="flex-1 min-w-0 space-y-0.5">
|
|
179
|
-
<span className="font-mono text-xs text-muted-foreground">
|
|
180
|
-
{timeAgo(q.timestamp)}
|
|
181
|
-
</span>
|
|
182
|
-
<p className="line-clamp-2 font-mono text-xs text-foreground/80">{q.query}</p>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
185
|
-
))}
|
|
186
|
-
</TabsContent>
|
|
187
|
-
)}
|
|
188
|
-
</Tabs>
|
|
189
|
-
) : (
|
|
190
|
-
<p className="py-6 text-center text-sm text-muted-foreground">No recent activity</p>
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
if (embedded) {
|
|
194
|
-
return <div>{content}</div>;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
32
|
if (!hasActivity) {
|
|
198
33
|
return (
|
|
199
34
|
<Card>
|
|
@@ -219,7 +54,156 @@ export function ActivityPanel({
|
|
|
219
54
|
</CardTitle>
|
|
220
55
|
<CardDescription>Recent evolution events and queries</CardDescription>
|
|
221
56
|
</CardHeader>
|
|
222
|
-
<CardContent>
|
|
57
|
+
<CardContent>
|
|
58
|
+
<Tabs
|
|
59
|
+
defaultValue={
|
|
60
|
+
pendingProposals.length > 0
|
|
61
|
+
? "pending"
|
|
62
|
+
: evolution.length > 0
|
|
63
|
+
? "timeline"
|
|
64
|
+
: "unmatched"
|
|
65
|
+
}
|
|
66
|
+
>
|
|
67
|
+
<TooltipProvider>
|
|
68
|
+
<TabsList className="w-full">
|
|
69
|
+
{pendingProposals.length > 0 && (
|
|
70
|
+
<Tooltip>
|
|
71
|
+
<TooltipTrigger
|
|
72
|
+
render={<TabsTrigger value="pending" className="flex-1 gap-1.5" />}
|
|
73
|
+
>
|
|
74
|
+
<GitPullRequestArrowIcon className="size-3.5" />
|
|
75
|
+
<Badge variant="secondary" className="h-4 px-1 text-[10px]">
|
|
76
|
+
{pendingProposals.length}
|
|
77
|
+
</Badge>
|
|
78
|
+
</TooltipTrigger>
|
|
79
|
+
<TooltipContent>Pending proposals</TooltipContent>
|
|
80
|
+
</Tooltip>
|
|
81
|
+
)}
|
|
82
|
+
<Tooltip>
|
|
83
|
+
<TooltipTrigger render={<TabsTrigger value="timeline" className="flex-1" />}>
|
|
84
|
+
<ClockIcon className="size-3.5" />
|
|
85
|
+
</TooltipTrigger>
|
|
86
|
+
<TooltipContent>Timeline</TooltipContent>
|
|
87
|
+
</Tooltip>
|
|
88
|
+
{unmatchedQueries.length > 0 && (
|
|
89
|
+
<Tooltip>
|
|
90
|
+
<TooltipTrigger
|
|
91
|
+
render={<TabsTrigger value="unmatched" className="flex-1 gap-1.5" />}
|
|
92
|
+
>
|
|
93
|
+
<SearchXIcon className="size-3.5" />
|
|
94
|
+
<Badge variant="destructive" className="h-4 px-1 text-[10px]">
|
|
95
|
+
{unmatchedQueries.length}
|
|
96
|
+
</Badge>
|
|
97
|
+
</TooltipTrigger>
|
|
98
|
+
<TooltipContent>Unmatched queries</TooltipContent>
|
|
99
|
+
</Tooltip>
|
|
100
|
+
)}
|
|
101
|
+
</TabsList>
|
|
102
|
+
</TooltipProvider>
|
|
103
|
+
|
|
104
|
+
{pendingProposals.length > 0 && (
|
|
105
|
+
<TabsContent value="pending" className="mt-4 space-y-3">
|
|
106
|
+
{pendingProposals.slice(0, 10).map((p) => (
|
|
107
|
+
<button
|
|
108
|
+
key={p.proposal_id}
|
|
109
|
+
type="button"
|
|
110
|
+
onClick={() => {
|
|
111
|
+
if (p.skill_name && onSelectProposal)
|
|
112
|
+
onSelectProposal(p.skill_name, p.proposal_id);
|
|
113
|
+
}}
|
|
114
|
+
disabled={!p.skill_name || !onSelectProposal}
|
|
115
|
+
className="flex w-full gap-3 rounded-md p-1.5 text-left transition-colors enabled:hover:bg-accent/40 disabled:cursor-default"
|
|
116
|
+
>
|
|
117
|
+
<div className="mt-1 size-2 shrink-0 rounded-full bg-amber-400" />
|
|
118
|
+
<div className="flex-1 min-w-0 space-y-1">
|
|
119
|
+
<div className="flex items-center gap-2">
|
|
120
|
+
<Badge
|
|
121
|
+
variant={ACTION_VARIANT[p.action] ?? "secondary"}
|
|
122
|
+
className="text-[10px]"
|
|
123
|
+
>
|
|
124
|
+
{p.action}
|
|
125
|
+
</Badge>
|
|
126
|
+
<span className="text-xs text-muted-foreground font-mono">
|
|
127
|
+
{timeAgo(p.timestamp)}
|
|
128
|
+
</span>
|
|
129
|
+
</div>
|
|
130
|
+
<p className="text-xs text-muted-foreground line-clamp-2">{p.details}</p>
|
|
131
|
+
{p.skill_name && (
|
|
132
|
+
<span className="text-[10px] text-muted-foreground/60 font-mono">
|
|
133
|
+
{p.skill_name} · #{p.proposal_id.slice(0, 8)}
|
|
134
|
+
</span>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
</button>
|
|
138
|
+
))}
|
|
139
|
+
</TabsContent>
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
<TabsContent value="timeline" className="mt-4 space-y-3">
|
|
143
|
+
{evolution.slice(0, 30).map((entry, i) => (
|
|
144
|
+
<button
|
|
145
|
+
key={`${entry.proposal_id}-${i}`}
|
|
146
|
+
type="button"
|
|
147
|
+
onClick={() => {
|
|
148
|
+
if (entry.skill_name && onSelectProposal)
|
|
149
|
+
onSelectProposal(entry.skill_name, entry.proposal_id);
|
|
150
|
+
}}
|
|
151
|
+
disabled={!entry.skill_name || !onSelectProposal}
|
|
152
|
+
className="flex w-full gap-3 rounded-md p-1.5 text-left transition-colors enabled:hover:bg-accent/40 disabled:cursor-default"
|
|
153
|
+
>
|
|
154
|
+
<div
|
|
155
|
+
className={`mt-1 size-2 shrink-0 rounded-full ${
|
|
156
|
+
entry.action === "deployed"
|
|
157
|
+
? "bg-emerald-500"
|
|
158
|
+
: entry.action === "rejected" || entry.action === "rolled_back"
|
|
159
|
+
? "bg-red-500"
|
|
160
|
+
: entry.action === "validated"
|
|
161
|
+
? "bg-amber-400"
|
|
162
|
+
: "bg-primary-accent"
|
|
163
|
+
}`}
|
|
164
|
+
/>
|
|
165
|
+
<div className="flex-1 min-w-0 space-y-1">
|
|
166
|
+
<div className="flex items-center gap-2">
|
|
167
|
+
<Badge
|
|
168
|
+
variant={ACTION_VARIANT[entry.action] ?? "secondary"}
|
|
169
|
+
className="text-[10px]"
|
|
170
|
+
>
|
|
171
|
+
{entry.action}
|
|
172
|
+
</Badge>
|
|
173
|
+
<span className="text-xs text-muted-foreground font-mono">
|
|
174
|
+
{timeAgo(entry.timestamp)}
|
|
175
|
+
</span>
|
|
176
|
+
</div>
|
|
177
|
+
<p className="text-xs text-muted-foreground line-clamp-2">{entry.details}</p>
|
|
178
|
+
<span className="text-[10px] text-muted-foreground/60 font-mono">
|
|
179
|
+
{entry.skill_name ? `${entry.skill_name} · ` : ""}#
|
|
180
|
+
{entry.proposal_id.slice(0, 8)}
|
|
181
|
+
</span>
|
|
182
|
+
</div>
|
|
183
|
+
</button>
|
|
184
|
+
))}
|
|
185
|
+
{evolution.length === 0 && (
|
|
186
|
+
<p className="text-sm text-muted-foreground text-center py-4">No timeline events</p>
|
|
187
|
+
)}
|
|
188
|
+
</TabsContent>
|
|
189
|
+
|
|
190
|
+
{unmatchedQueries.length > 0 && (
|
|
191
|
+
<TabsContent value="unmatched" className="mt-4 space-y-2">
|
|
192
|
+
{unmatchedQueries.slice(0, 15).map((q, i) => (
|
|
193
|
+
<div key={`${q.session_id}-${i}`} className="flex gap-3">
|
|
194
|
+
<div className="mt-1 size-2 shrink-0 rounded-full bg-muted-foreground/40" />
|
|
195
|
+
<div className="flex-1 min-w-0 space-y-0.5">
|
|
196
|
+
<span className="text-xs text-muted-foreground font-mono">
|
|
197
|
+
{timeAgo(q.timestamp)}
|
|
198
|
+
</span>
|
|
199
|
+
<p className="text-xs font-mono text-foreground/80 line-clamp-2">{q.query}</p>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
))}
|
|
203
|
+
</TabsContent>
|
|
204
|
+
)}
|
|
205
|
+
</Tabs>
|
|
206
|
+
</CardContent>
|
|
223
207
|
</Card>
|
|
224
208
|
);
|
|
225
209
|
}
|