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.
- package/README.md +24 -19
- package/bin/run-hook.cjs +36 -0
- package/cli/selftune/alpha-upload/build-payloads.ts +14 -1
- package/cli/selftune/alpha-upload/client.ts +51 -1
- package/cli/selftune/alpha-upload/flush.ts +46 -5
- package/cli/selftune/alpha-upload/stage-canonical.ts +25 -4
- package/cli/selftune/alpha-upload-contract.ts +9 -0
- package/cli/selftune/constants.ts +82 -5
- package/cli/selftune/contribute/sanitize.ts +52 -5
- package/cli/selftune/dashboard-contract.ts +100 -0
- package/cli/selftune/dashboard-server.ts +2 -2
- package/cli/selftune/evolution/description-quality.ts +12 -11
- package/cli/selftune/evolution/evolve.ts +238 -53
- package/cli/selftune/evolution/unblock-suggestions.ts +159 -0
- package/cli/selftune/evolution/validate-proposal.ts +9 -6
- package/cli/selftune/grading/grade-session.ts +20 -0
- package/cli/selftune/hooks/commit-track.ts +188 -0
- package/cli/selftune/hooks/prompt-log.ts +10 -1
- package/cli/selftune/hooks/session-stop.ts +2 -2
- package/cli/selftune/hooks/skill-eval.ts +15 -1
- package/cli/selftune/hooks/stdin-preview.ts +32 -0
- package/cli/selftune/init.ts +198 -27
- package/cli/selftune/localdb/direct-write.ts +69 -6
- package/cli/selftune/localdb/queries.ts +552 -7
- package/cli/selftune/localdb/schema.ts +46 -0
- package/cli/selftune/orchestrate.ts +32 -4
- package/cli/selftune/routes/overview.ts +41 -3
- package/cli/selftune/routes/skill-report.ts +88 -17
- package/cli/selftune/types.ts +32 -0
- package/cli/selftune/utils/hooks.ts +12 -2
- package/cli/selftune/utils/transcript.ts +210 -1
- package/node_modules/@selftune/telemetry-contract/src/types.ts +11 -0
- package/package.json +1 -1
- package/packages/telemetry-contract/src/types.ts +11 -0
- package/skill/SKILL.md +29 -1
- package/skill/Workflows/AutoActivation.md +1 -1
- package/skill/Workflows/Evolve.md +31 -13
- package/skill/Workflows/ExportCanonical.md +121 -0
- package/skill/Workflows/Hook.md +131 -0
- package/skill/Workflows/Initialize.md +9 -8
- package/skill/Workflows/Orchestrate.md +27 -5
- package/skill/Workflows/Quickstart.md +94 -0
- package/skill/Workflows/RepairSkillUsage.md +87 -0
- package/skill/Workflows/Uninstall.md +82 -0
- 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
|
|
160
|
-
const hasAction = ACTION_PATTERNS.some((p) => p.test(
|
|
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) =>
|
|
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
|
|