selftune 0.2.18 → 0.2.20

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 (77) hide show
  1. package/README.md +9 -4
  2. package/apps/local-dashboard/dist/assets/index-D8O-RG1I.js +60 -0
  3. package/apps/local-dashboard/dist/assets/index-_EcLywDg.css +1 -0
  4. package/apps/local-dashboard/dist/assets/vendor-table-BIiI3YhS.js +1 -0
  5. package/apps/local-dashboard/dist/assets/vendor-ui-CGEmUayx.js +12 -0
  6. package/apps/local-dashboard/dist/index.html +5 -5
  7. package/cli/selftune/alpha-upload/stage-canonical.ts +7 -6
  8. package/cli/selftune/constants.ts +10 -0
  9. package/cli/selftune/contribute/contribute.ts +30 -2
  10. package/cli/selftune/contribution-config.ts +249 -0
  11. package/cli/selftune/contribution-relay.ts +177 -0
  12. package/cli/selftune/contribution-signals.ts +219 -0
  13. package/cli/selftune/contribution-staging.ts +147 -0
  14. package/cli/selftune/contributions.ts +532 -0
  15. package/cli/selftune/creator-contributions.ts +333 -0
  16. package/cli/selftune/dashboard-contract.ts +209 -1
  17. package/cli/selftune/dashboard-server.ts +45 -11
  18. package/cli/selftune/eval/family-overlap.ts +714 -0
  19. package/cli/selftune/eval/hooks-to-evals.ts +182 -28
  20. package/cli/selftune/eval/synthetic-evals.ts +298 -11
  21. package/cli/selftune/evolution/evidence.ts +5 -0
  22. package/cli/selftune/evolution/evolve-body.ts +62 -2
  23. package/cli/selftune/evolution/evolve.ts +58 -1
  24. package/cli/selftune/evolution/validate-body.ts +10 -0
  25. package/cli/selftune/evolution/validate-host-replay.ts +236 -0
  26. package/cli/selftune/evolution/validate-proposal.ts +10 -0
  27. package/cli/selftune/evolution/validate-routing.ts +112 -5
  28. package/cli/selftune/export.ts +2 -2
  29. package/cli/selftune/index.ts +41 -5
  30. package/cli/selftune/ingestors/codex-rollout.ts +31 -35
  31. package/cli/selftune/ingestors/codex-wrapper.ts +32 -24
  32. package/cli/selftune/localdb/db.ts +2 -2
  33. package/cli/selftune/localdb/direct-write.ts +8 -3
  34. package/cli/selftune/localdb/materialize.ts +7 -2
  35. package/cli/selftune/localdb/queries.ts +712 -31
  36. package/cli/selftune/localdb/schema.ts +30 -1
  37. package/cli/selftune/recover.ts +153 -0
  38. package/cli/selftune/repair/skill-usage.ts +363 -4
  39. package/cli/selftune/routes/actions.ts +35 -1
  40. package/cli/selftune/routes/analytics.ts +14 -0
  41. package/cli/selftune/routes/index.ts +1 -0
  42. package/cli/selftune/routes/overview.ts +112 -4
  43. package/cli/selftune/routes/skill-report.ts +575 -11
  44. package/cli/selftune/status.ts +81 -2
  45. package/cli/selftune/sync.ts +56 -2
  46. package/cli/selftune/trust-model.ts +66 -0
  47. package/cli/selftune/types.ts +103 -0
  48. package/cli/selftune/utils/skill-detection.ts +43 -0
  49. package/cli/selftune/utils/text-similarity.ts +73 -0
  50. package/cli/selftune/watchlist.ts +65 -0
  51. package/package.json +1 -1
  52. package/packages/ui/src/components/ActivityTimeline.tsx +165 -150
  53. package/packages/ui/src/components/EvidenceViewer.tsx +419 -145
  54. package/packages/ui/src/components/EvolutionTimeline.tsx +81 -29
  55. package/packages/ui/src/components/OrchestrateRunsPanel.tsx +33 -16
  56. package/packages/ui/src/components/RecentActivityFeed.tsx +72 -41
  57. package/packages/ui/src/components/section-cards.tsx +12 -9
  58. package/packages/ui/src/primitives/card.tsx +1 -1
  59. package/packages/ui/src/types.ts +4 -0
  60. package/skill/SKILL.md +11 -1
  61. package/skill/Workflows/AlphaUpload.md +4 -0
  62. package/skill/Workflows/Composability.md +78 -0
  63. package/skill/Workflows/Contribute.md +6 -3
  64. package/skill/Workflows/Contributions.md +97 -0
  65. package/skill/Workflows/CreatorContributions.md +74 -0
  66. package/skill/Workflows/Dashboard.md +31 -0
  67. package/skill/Workflows/Evals.md +57 -8
  68. package/skill/Workflows/Evolve.md +23 -0
  69. package/skill/Workflows/Ingest.md +7 -0
  70. package/skill/Workflows/Initialize.md +20 -1
  71. package/skill/Workflows/Recover.md +84 -0
  72. package/skill/Workflows/RepairSkillUsage.md +12 -4
  73. package/skill/Workflows/Sync.md +18 -12
  74. package/apps/local-dashboard/dist/assets/index-BMIS6uUh.css +0 -2
  75. package/apps/local-dashboard/dist/assets/index-DOu3iLD9.js +0 -16
  76. package/apps/local-dashboard/dist/assets/vendor-table-pHbDxq36.js +0 -8
  77. package/apps/local-dashboard/dist/assets/vendor-ui-DIwlrGlb.js +0 -12
@@ -1,11 +1,13 @@
1
1
  /**
2
- * Route handler: POST /api/actions/{watch,evolve,rollback}
2
+ * Route handler: POST /api/actions/{watch,evolve,rollback,watchlist}
3
3
  *
4
4
  * Triggers selftune CLI commands as child processes and returns the result.
5
5
  */
6
6
 
7
7
  import { join } from "node:path";
8
8
 
9
+ import { saveWatchedSkills } from "../watchlist.js";
10
+
9
11
  export type ActionRunner = (
10
12
  command: string,
11
13
  args: string[],
@@ -41,6 +43,38 @@ export async function handleAction(
41
43
  body: Record<string, unknown>,
42
44
  executeAction: ActionRunner = runAction,
43
45
  ): Promise<Response> {
46
+ if (action === "watchlist") {
47
+ const skills = body.skills;
48
+ if (skills === undefined || skills === null) {
49
+ return Response.json(
50
+ { success: false, error: "Missing required field: skills[]" },
51
+ { status: 400 },
52
+ );
53
+ }
54
+ if (!Array.isArray(skills) || !skills.every((skill) => typeof skill === "string")) {
55
+ return Response.json(
56
+ {
57
+ success: false,
58
+ error: "Invalid type for skills: expected array of strings",
59
+ },
60
+ { status: 400 },
61
+ );
62
+ }
63
+ try {
64
+ const saved = saveWatchedSkills(skills);
65
+ return Response.json({ success: true, watched_skills: saved, error: null });
66
+ } catch (error: unknown) {
67
+ const message = error instanceof Error ? error.message : String(error);
68
+ return Response.json(
69
+ {
70
+ success: false,
71
+ error: `Failed to save watched skills. Check your selftune config directory and try again. ${message}`,
72
+ },
73
+ { status: 500 },
74
+ );
75
+ }
76
+ }
77
+
44
78
  if (action === "watch" || action === "evolve") {
45
79
  const skill = body.skill as string | undefined;
46
80
  const skillPath = body.skillPath as string | undefined;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Route handler: GET /api/v2/analytics
3
+ *
4
+ * Returns performance analytics payload from SQLite.
5
+ */
6
+
7
+ import type { Database } from "bun:sqlite";
8
+
9
+ import { getAnalyticsPayload } from "../localdb/queries.js";
10
+
11
+ export function handleAnalytics(db: Database): Response {
12
+ const analytics = getAnalyticsPayload(db);
13
+ return Response.json(analytics);
14
+ }
@@ -6,6 +6,7 @@
6
6
 
7
7
  export type { ActionRunner } from "./actions.js";
8
8
  export { handleAction, runAction } from "./actions.js";
9
+ export { handleAnalytics } from "./analytics.js";
9
10
  export { handleBadge } from "./badge.js";
10
11
  export { handleDoctor } from "./doctor.js";
11
12
  export { handleOrchestrateRuns } from "./orchestrate-runs.js";
@@ -8,12 +8,24 @@
8
8
 
9
9
  import type { Database } from "bun:sqlite";
10
10
 
11
+ import type {
12
+ AttentionItem,
13
+ AutonomousDecision,
14
+ AutonomyStatus,
15
+ AutonomyStatusLevel,
16
+ OverviewResponse,
17
+ } from "../dashboard-contract.js";
11
18
  import { parseCursorParam, parseIntParam } from "../dashboard-contract.js";
12
19
  import {
20
+ getAttentionQueue,
13
21
  getOverviewPayload,
14
22
  getOverviewPayloadPaginated,
23
+ getRecentDecisions,
24
+ getSkillTrustSummaries,
15
25
  getSkillsList,
16
26
  } from "../localdb/queries.js";
27
+ import { buildTrustWatchlist } from "../trust-model.js";
28
+ import { loadWatchedSkills } from "../watchlist.js";
17
29
 
18
30
  export function handleOverview(
19
31
  db: Database,
@@ -22,18 +34,43 @@ export function handleOverview(
22
34
  ): Response {
23
35
  const skills = getSkillsList(db);
24
36
 
25
- // Check if any pagination params are provided
37
+ // -- Autonomy-first enrichment fields ----------------------------------------
38
+ const attentionQueue = getAttentionQueue(db);
39
+ const recentDecisions = getRecentDecisions(db);
40
+ const trustSummaries = getSkillTrustSummaries(db);
41
+ const pendingReviews = attentionQueue.filter((a) => a.category === "needs_review").length;
42
+
43
+ const trustWatchlist = buildTrustWatchlist(trustSummaries);
44
+ const autonomyStatus = buildAutonomyStatus(
45
+ db,
46
+ attentionQueue,
47
+ recentDecisions,
48
+ skills.length,
49
+ pendingReviews,
50
+ );
51
+
52
+ const enrichment = {
53
+ watched_skills: loadWatchedSkills(),
54
+ autonomy_status: autonomyStatus,
55
+ attention_queue: attentionQueue,
56
+ trust_watchlist: trustWatchlist,
57
+ recent_decisions: recentDecisions,
58
+ };
59
+
60
+ // -- Standard overview payload -----------------------------------------------
26
61
  const hasPaginationParams =
27
62
  searchParams &&
28
63
  (searchParams.has("telemetry_cursor") ||
29
64
  searchParams.has("telemetry_limit") ||
30
65
  searchParams.has("skills_cursor") ||
31
66
  searchParams.has("skills_limit"));
67
+ const hasSkillsPagination =
68
+ searchParams && (searchParams.has("skills_cursor") || searchParams.has("skills_limit"));
32
69
 
33
70
  if (!hasPaginationParams) {
34
- // Backward-compatible: return the unpaginated overview
35
71
  const overview = getOverviewPayload(db);
36
- return Response.json({ overview, skills, version });
72
+ const response: OverviewResponse = { overview, skills, version, ...enrichment };
73
+ return Response.json(response);
37
74
  }
38
75
 
39
76
  // Parse pagination params
@@ -49,5 +86,76 @@ export function handleOverview(
49
86
  skills_limit: skillsLimit,
50
87
  });
51
88
 
52
- return Response.json({ overview, skills, version });
89
+ const paginatedSkillNames = new Set(overview.skills_page.items.map((row) => row.skill_name));
90
+ const paginatedSkills = hasSkillsPagination
91
+ ? skills.filter((skill) => paginatedSkillNames.has(skill.skill_name))
92
+ : skills;
93
+
94
+ return Response.json({ overview, skills: paginatedSkills, version, ...enrichment });
95
+ }
96
+
97
+ // -- Internal helpers ----------------------------------------------------------
98
+
99
+ function buildAutonomyStatus(
100
+ db: Database,
101
+ attentionQueue: AttentionItem[],
102
+ recentDecisions: AutonomousDecision[],
103
+ skillsObserved: number,
104
+ pendingReviews: number,
105
+ ): AutonomyStatus {
106
+ let lastRun: string | null = null;
107
+ try {
108
+ const row = db
109
+ .query(`SELECT timestamp FROM orchestrate_runs ORDER BY timestamp DESC LIMIT 1`)
110
+ .get() as { timestamp: string } | null;
111
+ lastRun = row?.timestamp ?? null;
112
+ } catch {
113
+ // Table may not exist
114
+ }
115
+
116
+ const hasCritical = attentionQueue.some((a) => a.severity === "critical");
117
+
118
+ // "watching" means recent autonomous activity — last run within 24 hours
119
+ // or recent decisions within the 7-day freshness window
120
+ const hasRecentActivity =
121
+ (lastRun != null && Date.now() - new Date(lastRun).getTime() < 24 * 60 * 60 * 1000) ||
122
+ recentDecisions.length > 0;
123
+
124
+ let level: AutonomyStatusLevel;
125
+ if (hasCritical) {
126
+ level = "blocked";
127
+ } else if (pendingReviews > 0) {
128
+ level = "needs_review";
129
+ } else if (hasRecentActivity) {
130
+ level = "watching";
131
+ } else {
132
+ level = "healthy";
133
+ }
134
+
135
+ let summary: string;
136
+ switch (level) {
137
+ case "healthy":
138
+ summary = "No action needed. System is healthy.";
139
+ break;
140
+ case "blocked": {
141
+ const critCount = attentionQueue.filter((a) => a.severity === "critical").length;
142
+ summary = `${critCount} skill${critCount !== 1 ? "s" : ""} need${critCount === 1 ? "s" : ""} urgent attention after rollback.`;
143
+ break;
144
+ }
145
+ case "needs_review":
146
+ summary = `selftune is watching ${skillsObserved} skill${skillsObserved !== 1 ? "s" : ""} and needs review on ${pendingReviews} proposal${pendingReviews !== 1 ? "s" : ""}.`;
147
+ break;
148
+ case "watching":
149
+ summary = `selftune is actively watching ${skillsObserved} skill${skillsObserved !== 1 ? "s" : ""}. No action needed.`;
150
+ break;
151
+ }
152
+
153
+ return {
154
+ level,
155
+ summary,
156
+ last_run: lastRun,
157
+ skills_observed: skillsObserved,
158
+ pending_reviews: pendingReviews,
159
+ attention_required: attentionQueue.length,
160
+ };
53
161
  }