selftune 0.2.15 → 0.2.18

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 (45) hide show
  1. package/README.md +24 -19
  2. package/bin/run-hook.cjs +36 -0
  3. package/cli/selftune/alpha-upload/build-payloads.ts +14 -1
  4. package/cli/selftune/alpha-upload/client.ts +51 -1
  5. package/cli/selftune/alpha-upload/flush.ts +46 -5
  6. package/cli/selftune/alpha-upload/stage-canonical.ts +25 -4
  7. package/cli/selftune/alpha-upload-contract.ts +9 -0
  8. package/cli/selftune/constants.ts +82 -5
  9. package/cli/selftune/contribute/sanitize.ts +52 -5
  10. package/cli/selftune/dashboard-contract.ts +100 -0
  11. package/cli/selftune/dashboard-server.ts +2 -2
  12. package/cli/selftune/evolution/description-quality.ts +12 -11
  13. package/cli/selftune/evolution/evolve.ts +238 -53
  14. package/cli/selftune/evolution/unblock-suggestions.ts +159 -0
  15. package/cli/selftune/evolution/validate-proposal.ts +9 -6
  16. package/cli/selftune/grading/grade-session.ts +20 -0
  17. package/cli/selftune/hooks/commit-track.ts +188 -0
  18. package/cli/selftune/hooks/prompt-log.ts +10 -1
  19. package/cli/selftune/hooks/session-stop.ts +2 -2
  20. package/cli/selftune/hooks/skill-eval.ts +15 -1
  21. package/cli/selftune/hooks/stdin-preview.ts +32 -0
  22. package/cli/selftune/init.ts +198 -27
  23. package/cli/selftune/localdb/direct-write.ts +69 -6
  24. package/cli/selftune/localdb/queries.ts +552 -7
  25. package/cli/selftune/localdb/schema.ts +46 -0
  26. package/cli/selftune/orchestrate.ts +32 -4
  27. package/cli/selftune/routes/overview.ts +41 -3
  28. package/cli/selftune/routes/skill-report.ts +88 -17
  29. package/cli/selftune/types.ts +32 -0
  30. package/cli/selftune/utils/hooks.ts +12 -2
  31. package/cli/selftune/utils/transcript.ts +210 -1
  32. package/node_modules/@selftune/telemetry-contract/src/types.ts +11 -0
  33. package/package.json +1 -1
  34. package/packages/telemetry-contract/src/types.ts +11 -0
  35. package/skill/SKILL.md +29 -1
  36. package/skill/Workflows/AutoActivation.md +1 -1
  37. package/skill/Workflows/Evolve.md +31 -13
  38. package/skill/Workflows/ExportCanonical.md +121 -0
  39. package/skill/Workflows/Hook.md +131 -0
  40. package/skill/Workflows/Initialize.md +9 -8
  41. package/skill/Workflows/Orchestrate.md +27 -5
  42. package/skill/Workflows/Quickstart.md +94 -0
  43. package/skill/Workflows/RepairSkillUsage.md +87 -0
  44. package/skill/Workflows/Uninstall.md +82 -0
  45. package/skill/settings_snippet.json +19 -8
@@ -1,3 +1,71 @@
1
+ // -- Cursor-based pagination types -------------------------------------------
2
+
3
+ export interface PaginationCursor {
4
+ timestamp: string;
5
+ id: number | string;
6
+ }
7
+
8
+ export interface PaginatedResult<T> {
9
+ items: T[];
10
+ next_cursor: PaginationCursor | null;
11
+ has_more: boolean;
12
+ }
13
+
14
+ /** Parse a JSON cursor param from a URL search string. Returns null on invalid input. */
15
+ export function parseCursorParam(value: string | null | undefined): PaginationCursor | null {
16
+ if (!value) return null;
17
+ try {
18
+ const parsed: unknown = JSON.parse(value);
19
+ if (parsed && typeof parsed === "object" && "timestamp" in parsed && "id" in parsed) {
20
+ const { timestamp, id } = parsed as { timestamp: unknown; id: unknown };
21
+ if (
22
+ typeof timestamp === "string" &&
23
+ (typeof id === "string" || (typeof id === "number" && Number.isFinite(id)))
24
+ ) {
25
+ return { timestamp, id };
26
+ }
27
+ }
28
+ } catch {
29
+ // Invalid cursor JSON — treat as no cursor
30
+ }
31
+ return null;
32
+ }
33
+
34
+ /** Parse an integer query param with bounds clamping. */
35
+ export function parseIntParam(value: string | null | undefined, defaultValue: number): number {
36
+ if (value == null) return defaultValue;
37
+ const n = Number.parseInt(value, 10);
38
+ return Number.isNaN(n) ? defaultValue : Math.max(1, Math.min(n, 10000));
39
+ }
40
+
41
+ // -- Paginated overview payload (returned when cursor params are provided) ----
42
+
43
+ export interface OverviewPaginatedPayload {
44
+ telemetry_page: PaginatedResult<TelemetryRecord>;
45
+ skills_page: PaginatedResult<SkillUsageRecord>;
46
+ evolution: EvolutionEntry[];
47
+ counts: OverviewPayload["counts"];
48
+ unmatched_queries: UnmatchedQuery[];
49
+ pending_proposals: PendingProposal[];
50
+ active_sessions: number;
51
+ recent_activity: RecentActivityItem[];
52
+ }
53
+
54
+ export interface SkillReportPaginatedPayload extends Omit<
55
+ SkillReportPayload,
56
+ "recent_invocations"
57
+ > {
58
+ invocations_page: PaginatedResult<{
59
+ timestamp: string;
60
+ session_id: string;
61
+ query: string;
62
+ triggered: boolean;
63
+ source: string | null;
64
+ }>;
65
+ }
66
+
67
+ // -- Core record types -------------------------------------------------------
68
+
1
69
  export interface TelemetryRecord {
2
70
  timestamp: string;
3
71
  session_id: string;
@@ -220,6 +288,36 @@ export interface HealthResponse {
220
288
  // -- Doctor / health check types ----------------------------------------------
221
289
  export type { DoctorResult, HealthCheck, HealthStatus } from "./types.js";
222
290
 
291
+ // -- Execution metrics (aggregated from execution_facts enrichment columns) ---
292
+
293
+ export interface ExecutionMetrics {
294
+ avg_files_changed: number;
295
+ total_lines_added: number;
296
+ total_lines_removed: number;
297
+ total_cost_usd: number;
298
+ avg_cost_usd: number;
299
+ cached_input_tokens_total: number;
300
+ reasoning_output_tokens_total: number;
301
+ artifact_count: number;
302
+ session_type_distribution: Record<string, number>;
303
+ }
304
+
305
+ // -- Commit summary (aggregated from commit_tracking table) -------------------
306
+
307
+ export interface CommitRecord {
308
+ commit_sha: string;
309
+ commit_title: string | null;
310
+ branch: string | null;
311
+ repo_remote: string | null;
312
+ timestamp: string;
313
+ }
314
+
315
+ export interface CommitSummary {
316
+ total_commits: number;
317
+ unique_branches: number;
318
+ recent_commits: Array<{ sha: string; title: string; branch: string; timestamp: string }>;
319
+ }
320
+
223
321
  export interface SkillReportResponse extends SkillReportPayload {
224
322
  evolution: EvolutionEntry[];
225
323
  pending_proposals: PendingProposal[];
@@ -242,6 +340,8 @@ export interface SkillReportResponse extends SkillReportPayload {
242
340
  };
243
341
  prompt_samples: PromptSample[];
244
342
  session_metadata: SessionMeta[];
343
+ execution_metrics?: ExecutionMetrics | null;
344
+ commit_summary?: CommitSummary | null;
245
345
  description_quality?: {
246
346
  composite: number;
247
347
  criteria: {
@@ -448,7 +448,7 @@ export async function startDashboardServer(
448
448
  );
449
449
  }
450
450
  refreshV2Data();
451
- return withCors(handleOverview(db, selftuneVersion));
451
+ return withCors(handleOverview(db, selftuneVersion, url.searchParams));
452
452
  }
453
453
 
454
454
  // ---- GET /api/v2/orchestrate-runs ----
@@ -495,7 +495,7 @@ export async function startDashboardServer(
495
495
  );
496
496
  }
497
497
  refreshV2Data();
498
- return withCors(handleSkillReport(db, skillName));
498
+ return withCors(handleSkillReport(db, skillName, url.searchParams));
499
499
  }
500
500
 
501
501
  // ---- SPA fallback ----
@@ -139,27 +139,27 @@ export function scoreLengthCriterion(description: string): number {
139
139
  }
140
140
 
141
141
  /** Score presence of trigger context words (when/if/before/after etc). */
142
- export function scoreTriggerContextCriterion(description: string): number {
143
- const matches = countWordMatches(description.toLowerCase(), TRIGGER_PATTERNS);
142
+ export function scoreTriggerContextCriterion(description: string, lower?: string): number {
143
+ const matches = countWordMatches(lower ?? description.toLowerCase(), TRIGGER_PATTERNS);
144
144
  if (matches === 0) return 0.0;
145
145
  if (matches === 1) return 0.7;
146
146
  return Math.min(1.0, 0.7 + 0.15 * (matches - 1));
147
147
  }
148
148
 
149
149
  /** Score absence of vague words (lower is worse). */
150
- export function scoreVaguenessCriterion(description: string): number {
151
- const matches = countWordMatches(description.toLowerCase(), VAGUE_PATTERNS);
150
+ export function scoreVaguenessCriterion(description: string, lower?: string): number {
151
+ const matches = countWordMatches(lower ?? description.toLowerCase(), VAGUE_PATTERNS);
152
152
  if (matches === 0) return 1.0;
153
153
  if (matches === 1) return 0.6;
154
154
  return Math.max(0.1, 0.6 - 0.15 * (matches - 1));
155
155
  }
156
156
 
157
157
  /** Score whether description specifies at least one concrete action or domain. */
158
- export function scoreSpecificityCriterion(description: string): number {
159
- const lower = description.toLowerCase();
160
- const hasAction = ACTION_PATTERNS.some((p) => p.test(lower));
158
+ export function scoreSpecificityCriterion(description: string, lower?: string): number {
159
+ const l = lower ?? description.toLowerCase();
160
+ const hasAction = ACTION_PATTERNS.some((p) => p.test(l));
161
161
 
162
- const fillerCount = FILLER_PHRASES.filter((f) => lower.includes(f)).length;
162
+ const fillerCount = FILLER_PHRASES.filter((f) => l.includes(f)).length;
163
163
  const words = description.split(/\s+/).length;
164
164
  const fillerRatio = fillerCount > 0 ? fillerCount / Math.max(1, words / 10) : 0;
165
165
 
@@ -204,11 +204,12 @@ const WEIGHTS = {
204
204
  * Pure function — no I/O, no LLM calls.
205
205
  */
206
206
  export function scoreDescription(description: string, skillName?: string): DescriptionQualityScore {
207
+ const lower = description.toLowerCase();
207
208
  const criteria = {
208
209
  length: scoreLengthCriterion(description),
209
- trigger_context: scoreTriggerContextCriterion(description),
210
- vagueness: scoreVaguenessCriterion(description),
211
- specificity: scoreSpecificityCriterion(description),
210
+ trigger_context: scoreTriggerContextCriterion(description, lower),
211
+ vagueness: scoreVaguenessCriterion(description, lower),
212
+ specificity: scoreSpecificityCriterion(description, lower),
212
213
  not_just_name: scoreNotJustNameCriterion(description, skillName),
213
214
  };
214
215