selftune 0.2.16 → 0.2.19

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 (91) hide show
  1. package/README.md +32 -22
  2. package/apps/local-dashboard/dist/assets/index-DnhnXQm6.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/build-payloads.ts +14 -1
  8. package/cli/selftune/alpha-upload/client.ts +51 -1
  9. package/cli/selftune/alpha-upload/flush.ts +46 -5
  10. package/cli/selftune/alpha-upload/stage-canonical.ts +32 -10
  11. package/cli/selftune/alpha-upload-contract.ts +9 -0
  12. package/cli/selftune/constants.ts +92 -5
  13. package/cli/selftune/contribute/contribute.ts +30 -2
  14. package/cli/selftune/contribute/sanitize.ts +52 -5
  15. package/cli/selftune/contribution-config.ts +249 -0
  16. package/cli/selftune/contribution-relay.ts +177 -0
  17. package/cli/selftune/contribution-signals.ts +219 -0
  18. package/cli/selftune/contribution-staging.ts +147 -0
  19. package/cli/selftune/contributions.ts +532 -0
  20. package/cli/selftune/creator-contributions.ts +333 -0
  21. package/cli/selftune/dashboard-contract.ts +305 -1
  22. package/cli/selftune/dashboard-server.ts +47 -13
  23. package/cli/selftune/eval/family-overlap.ts +395 -0
  24. package/cli/selftune/eval/hooks-to-evals.ts +182 -28
  25. package/cli/selftune/eval/synthetic-evals.ts +298 -11
  26. package/cli/selftune/evolution/description-quality.ts +12 -11
  27. package/cli/selftune/evolution/evolve.ts +214 -51
  28. package/cli/selftune/evolution/validate-proposal.ts +9 -6
  29. package/cli/selftune/export.ts +2 -2
  30. package/cli/selftune/grading/grade-session.ts +20 -0
  31. package/cli/selftune/hooks/commit-track.ts +188 -0
  32. package/cli/selftune/hooks/prompt-log.ts +10 -1
  33. package/cli/selftune/hooks/session-stop.ts +2 -2
  34. package/cli/selftune/hooks/skill-eval.ts +15 -1
  35. package/cli/selftune/hooks/stdin-preview.ts +32 -0
  36. package/cli/selftune/index.ts +41 -5
  37. package/cli/selftune/ingestors/codex-rollout.ts +31 -35
  38. package/cli/selftune/ingestors/codex-wrapper.ts +32 -24
  39. package/cli/selftune/localdb/db.ts +2 -2
  40. package/cli/selftune/localdb/direct-write.ts +69 -6
  41. package/cli/selftune/localdb/queries.ts +1253 -37
  42. package/cli/selftune/localdb/schema.ts +66 -0
  43. package/cli/selftune/orchestrate.ts +32 -4
  44. package/cli/selftune/recover.ts +153 -0
  45. package/cli/selftune/repair/skill-usage.ts +363 -4
  46. package/cli/selftune/routes/actions.ts +35 -1
  47. package/cli/selftune/routes/analytics.ts +14 -0
  48. package/cli/selftune/routes/index.ts +1 -0
  49. package/cli/selftune/routes/overview.ts +150 -4
  50. package/cli/selftune/routes/skill-report.ts +648 -18
  51. package/cli/selftune/status.ts +81 -2
  52. package/cli/selftune/sync.ts +56 -2
  53. package/cli/selftune/trust-model.ts +66 -0
  54. package/cli/selftune/types.ts +80 -0
  55. package/cli/selftune/utils/skill-detection.ts +43 -0
  56. package/cli/selftune/utils/transcript.ts +210 -1
  57. package/cli/selftune/watchlist.ts +65 -0
  58. package/node_modules/@selftune/telemetry-contract/src/types.ts +11 -0
  59. package/package.json +1 -1
  60. package/packages/telemetry-contract/src/types.ts +11 -0
  61. package/packages/ui/src/components/ActivityTimeline.tsx +165 -150
  62. package/packages/ui/src/components/EvidenceViewer.tsx +335 -144
  63. package/packages/ui/src/components/EvolutionTimeline.tsx +58 -28
  64. package/packages/ui/src/components/OrchestrateRunsPanel.tsx +33 -16
  65. package/packages/ui/src/components/RecentActivityFeed.tsx +72 -41
  66. package/packages/ui/src/components/section-cards.tsx +12 -9
  67. package/packages/ui/src/primitives/card.tsx +1 -1
  68. package/skill/SKILL.md +40 -2
  69. package/skill/Workflows/AlphaUpload.md +4 -0
  70. package/skill/Workflows/Composability.md +64 -0
  71. package/skill/Workflows/Contribute.md +6 -3
  72. package/skill/Workflows/Contributions.md +97 -0
  73. package/skill/Workflows/CreatorContributions.md +74 -0
  74. package/skill/Workflows/Dashboard.md +31 -0
  75. package/skill/Workflows/Evals.md +57 -8
  76. package/skill/Workflows/Evolve.md +31 -13
  77. package/skill/Workflows/ExportCanonical.md +121 -0
  78. package/skill/Workflows/Hook.md +131 -0
  79. package/skill/Workflows/Ingest.md +7 -0
  80. package/skill/Workflows/Initialize.md +29 -9
  81. package/skill/Workflows/Orchestrate.md +27 -5
  82. package/skill/Workflows/Quickstart.md +94 -0
  83. package/skill/Workflows/Recover.md +84 -0
  84. package/skill/Workflows/RepairSkillUsage.md +95 -0
  85. package/skill/Workflows/Sync.md +18 -12
  86. package/skill/Workflows/Uninstall.md +82 -0
  87. package/skill/settings_snippet.json +11 -0
  88. package/apps/local-dashboard/dist/assets/index-BMIS6uUh.css +0 -2
  89. package/apps/local-dashboard/dist/assets/index-DOu3iLD9.js +0 -16
  90. package/apps/local-dashboard/dist/assets/vendor-table-pHbDxq36.js +0 -8
  91. package/apps/local-dashboard/dist/assets/vendor-ui-DIwlrGlb.js +0 -12
@@ -20,13 +20,16 @@ import { getDb } from "./localdb/db.js";
20
20
  import {
21
21
  getLastUploadError,
22
22
  getLastUploadSuccess,
23
+ getSkillTrustSummaries,
23
24
  queryEvolutionAudit,
24
25
  queryQueryLog,
25
26
  querySessionTelemetry,
26
27
  querySkillUsageRecords,
28
+ type SkillTrustSummary,
27
29
  } from "./localdb/queries.js";
28
30
  import { computeMonitoringSnapshot, MIN_MONITORING_SKILL_CHECKS } from "./monitoring/watch.js";
29
31
  import { doctor } from "./observability.js";
32
+ import { deriveTrustBucket, deriveTrustBucketReason } from "./trust-model.js";
30
33
  import type {
31
34
  AgentCommandGuidance,
32
35
  AlphaLinkState,
@@ -273,7 +276,44 @@ const TREND_SYMBOLS: Record<string, string> = {
273
276
  unknown: "?",
274
277
  };
275
278
 
276
- export function formatStatus(result: StatusResult): string {
279
+ function formatTrustHighlights(trustSummaries: SkillTrustSummary[] | undefined): string[] {
280
+ if (!trustSummaries || trustSummaries.length === 0) return [];
281
+
282
+ const recentSort = (a: SkillTrustSummary, b: SkillTrustSummary) =>
283
+ (b.last_seen ?? "").localeCompare(a.last_seen ?? "");
284
+ const attention = [...trustSummaries]
285
+ .filter((summary) => deriveTrustBucket(summary) === "at_risk")
286
+ .sort(recentSort)
287
+ .slice(0, 3);
288
+ const improving = [...trustSummaries]
289
+ .filter((summary) => deriveTrustBucket(summary) === "improving")
290
+ .sort(recentSort)
291
+ .slice(0, 3);
292
+
293
+ if (attention.length === 0 && improving.length === 0) return [];
294
+
295
+ const lines = ["Highlights"];
296
+ if (attention.length > 0) {
297
+ lines.push(
298
+ ` Attention: ${attention
299
+ .map((summary) => `${summary.skill_name} (${deriveTrustBucketReason("at_risk", summary)})`)
300
+ .join("; ")}`,
301
+ );
302
+ }
303
+ if (improving.length > 0) {
304
+ lines.push(
305
+ ` Improving: ${improving
306
+ .map(
307
+ (summary) => `${summary.skill_name} (${deriveTrustBucketReason("improving", summary)})`,
308
+ )
309
+ .join("; ")}`,
310
+ );
311
+ }
312
+
313
+ return lines;
314
+ }
315
+
316
+ export function formatStatus(result: StatusResult, trustSummaries?: SkillTrustSummary[]): string {
277
317
  const noColor = !!process.env.NO_COLOR;
278
318
 
279
319
  const green = noColor ? (s: string) => s : (s: string) => colorize(s, "#788c5d");
@@ -284,6 +324,14 @@ export function formatStatus(result: StatusResult): string {
284
324
  lines.push("selftune status");
285
325
  lines.push("\u2550".repeat(15));
286
326
  lines.push("");
327
+ lines.push(formatStatusSummary(result, trustSummaries));
328
+ lines.push("");
329
+
330
+ const highlightLines = formatTrustHighlights(trustSummaries);
331
+ if (highlightLines.length > 0) {
332
+ lines.push(...highlightLines);
333
+ lines.push("");
334
+ }
287
335
 
288
336
  // Skills table
289
337
  const skillCount = result.skills.length;
@@ -351,6 +399,36 @@ export function formatStatus(result: StatusResult): string {
351
399
  return lines.join("\n");
352
400
  }
353
401
 
402
+ export function formatStatusSummary(
403
+ result: StatusResult,
404
+ trustSummaries?: SkillTrustSummary[],
405
+ ): string {
406
+ const watched = trustSummaries?.length ?? result.skills.length;
407
+ const improving =
408
+ trustSummaries?.filter((summary) => deriveTrustBucket(summary) === "improving").length ??
409
+ result.skills.filter((skill) => skill.trend === "up").length;
410
+ const needsAttention =
411
+ trustSummaries?.filter((summary) => deriveTrustBucket(summary) === "at_risk").length ??
412
+ result.skills.filter((skill) => skill.status === "WARNING" || skill.status === "CRITICAL")
413
+ .length;
414
+
415
+ const watchedText = `${watched} ${watched === 1 ? "skill" : "skills"} watched`;
416
+ const improvingText =
417
+ improving > 0
418
+ ? `${improving} improving`
419
+ : result.lastSession
420
+ ? "no recent lift"
421
+ : "no recent data";
422
+ const attentionText =
423
+ needsAttention > 0
424
+ ? `${needsAttention} needing attention`
425
+ : watched > 0
426
+ ? "nothing urgent"
427
+ : "nothing tracked yet";
428
+
429
+ return `${watchedText} | ${improvingText} | ${attentionText}`;
430
+ }
431
+
354
432
  // ---------------------------------------------------------------------------
355
433
  // Terminal color helper using ANSI escapes
356
434
  // ---------------------------------------------------------------------------
@@ -506,7 +584,8 @@ export async function cliMain(): Promise<void> {
506
584
  const doctorResult = await doctor();
507
585
 
508
586
  const result = computeStatus(telemetry, skillRecords, queryRecords, auditEntries, doctorResult);
509
- const output = formatStatus(result);
587
+ const trustSummaries = getSkillTrustSummaries(db);
588
+ const output = formatStatus(result, trustSummaries);
510
589
  console.log(output);
511
590
 
512
591
  // Alpha upload status section
@@ -31,6 +31,7 @@ import {
31
31
  SKILL_LOG,
32
32
  TELEMETRY_LOG,
33
33
  } from "./constants.js";
34
+ import { stageCreatorContributionSignals } from "./contribution-staging.js";
34
35
  import {
35
36
  findTranscriptFiles,
36
37
  parseSession,
@@ -58,6 +59,7 @@ import {
58
59
  import { getDb } from "./localdb/db.js";
59
60
  import { querySkillUsageRecords } from "./localdb/queries.js";
60
61
  import {
62
+ persistRepairedSkillUsageToDb,
61
63
  rebuildSkillUsageFromCodexRollouts,
62
64
  rebuildSkillUsageFromTranscripts,
63
65
  } from "./repair/skill-usage.js";
@@ -96,6 +98,12 @@ export interface SyncResult {
96
98
  repaired_records: number;
97
99
  codex_repaired_records: number;
98
100
  };
101
+ creator_contributions: {
102
+ ran: boolean;
103
+ eligible_skills: number;
104
+ built_signals: number;
105
+ staged_signals: number;
106
+ };
99
107
  timings: SyncPhaseTiming[];
100
108
  total_elapsed_ms: number;
101
109
  }
@@ -130,6 +138,14 @@ export interface SyncDeps {
130
138
  repairedRecords: number;
131
139
  codexRepairedRecords: number;
132
140
  };
141
+ stageCreatorContributions?: (
142
+ db: ReturnType<typeof getDb>,
143
+ options: { dryRun: boolean },
144
+ ) => {
145
+ eligible_skills: number;
146
+ built_signals: number;
147
+ staged_signals: number;
148
+ };
133
149
  }
134
150
 
135
151
  export function createDefaultSyncOptions(overrides: Partial<SyncOptions> = {}): SyncOptions {
@@ -344,6 +360,7 @@ function rebuildSkillUsageOverlay(
344
360
  options: SyncOptions,
345
361
  onProgress?: SyncProgressCallback,
346
362
  cache?: FileListCache,
363
+ db: ReturnType<typeof getDb> = getDb(),
347
364
  ): {
348
365
  repairedSessions: number;
349
366
  repairedRecords: number;
@@ -363,7 +380,6 @@ function rebuildSkillUsageOverlay(
363
380
  let rawSkillRecords: SkillUsageRecord[];
364
381
  if (options.skillLogPath === SKILL_LOG) {
365
382
  try {
366
- const db = getDb();
367
383
  rawSkillRecords = querySkillUsageRecords(db) as SkillUsageRecord[];
368
384
  } catch {
369
385
  rawSkillRecords = readJsonl<SkillUsageRecord>(options.skillLogPath);
@@ -389,6 +405,7 @@ function rebuildSkillUsageOverlay(
389
405
  repairedRecords.push(...codexRecords);
390
406
 
391
407
  if (!options.dryRun) {
408
+ persistRepairedSkillUsageToDb(db, repairedRecords);
392
409
  writeRepairedSkillUsageRecords(
393
410
  repairedRecords,
394
411
  repairedSessionIds,
@@ -429,6 +446,8 @@ export function syncSources(
429
446
  const runOpenCode = deps.syncOpenCode;
430
447
  const runOpenClaw = deps.syncOpenClaw;
431
448
  const runRepair = deps.rebuildSkillUsage;
449
+ const runCreatorContributions = deps.stageCreatorContributions;
450
+ const db = getDb();
432
451
 
433
452
  const disabledStep: SyncStepResult = { available: false, scanned: 0, synced: 0, skipped: 0 };
434
453
 
@@ -470,11 +489,27 @@ export function syncSources(
470
489
  ? timePhase(
471
490
  "repair",
472
491
  () =>
473
- runRepair ? runRepair(options) : rebuildSkillUsageOverlay(options, onProgress, cache),
492
+ runRepair ? runRepair(options) : rebuildSkillUsageOverlay(options, onProgress, cache, db),
474
493
  timings,
475
494
  )
476
495
  : { repairedSessions: 0, repairedRecords: 0, codexRepairedRecords: 0 };
477
496
 
497
+ const creatorContributions = timePhase(
498
+ "creator_contributions",
499
+ () => {
500
+ const staged = runCreatorContributions
501
+ ? runCreatorContributions(db, { dryRun: options.dryRun })
502
+ : stageCreatorContributionSignals(db, { dryRun: options.dryRun });
503
+ return {
504
+ ran: true,
505
+ eligible_skills: staged.eligible_skills,
506
+ built_signals: staged.built_signals,
507
+ staged_signals: staged.staged_signals,
508
+ };
509
+ },
510
+ timings,
511
+ );
512
+
478
513
  const totalElapsed = Math.round(performance.now() - totalStart);
479
514
 
480
515
  return {
@@ -487,6 +522,7 @@ export function syncSources(
487
522
  repaired_records: repair.repairedRecords,
488
523
  codex_repaired_records: repair.codexRepairedRecords,
489
524
  },
525
+ creator_contributions: creatorContributions,
490
526
  timings,
491
527
  total_elapsed_ms: totalElapsed,
492
528
  };
@@ -636,6 +672,24 @@ Options:
636
672
  );
637
673
  }
638
674
 
675
+ if (
676
+ result.creator_contributions.eligible_skills > 0 ||
677
+ result.creator_contributions.built_signals > 0
678
+ ) {
679
+ const contributionTiming = timingMap.get("creator_contributions");
680
+ const contributionTime = contributionTiming
681
+ ? ` (${formatMs(contributionTiming.elapsed_ms)})`
682
+ : "";
683
+ process.stderr.write(
684
+ `Creator contributions: ${result.creator_contributions.built_signals} signals from ` +
685
+ `${result.creator_contributions.eligible_skills} skills` +
686
+ (result.dry_run
687
+ ? " ready to stage"
688
+ : ` staged=${result.creator_contributions.staged_signals}`) +
689
+ `${contributionTime}\n`,
690
+ );
691
+ }
692
+
639
693
  process.stderr.write(`\nDone in ${formatMs(result.total_elapsed_ms)}\n`);
640
694
  }
641
695
 
@@ -0,0 +1,66 @@
1
+ import type { TrustBucket, TrustState, TrustWatchlistEntry } from "./dashboard-contract.js";
2
+ import type { SkillTrustSummary } from "./localdb/queries.js";
3
+
4
+ const AT_RISK_MISS_RATE_THRESHOLD = 0.15;
5
+ const UNCERTAIN_MIN_CHECKS = 10;
6
+
7
+ function formatPercent(value: number): string {
8
+ return `${(value * 100).toFixed(1).replace(/\.0$/, "")}%`;
9
+ }
10
+
11
+ export function deriveTrustState(summary: SkillTrustSummary): TrustState {
12
+ if (summary.latest_action === "rolled_back") return "rolled_back";
13
+ if (summary.latest_action === "deployed") return "deployed";
14
+ if (summary.latest_action === "validated") return "validated";
15
+ if (summary.latest_action === "watch") return "watch";
16
+ if (summary.total_checks < 5) return "low_sample";
17
+ return "observed";
18
+ }
19
+
20
+ export function deriveTrustBucket(summary: SkillTrustSummary): TrustBucket {
21
+ if (summary.latest_action === "rolled_back" || summary.miss_rate > AT_RISK_MISS_RATE_THRESHOLD) {
22
+ return "at_risk";
23
+ }
24
+ if (
25
+ summary.latest_action === "validated" ||
26
+ summary.latest_action === "created" ||
27
+ summary.latest_action === "proposed"
28
+ ) {
29
+ return "improving";
30
+ }
31
+ if (summary.total_checks < UNCERTAIN_MIN_CHECKS || summary.latest_action === "watch") {
32
+ return "uncertain";
33
+ }
34
+ return "stable";
35
+ }
36
+
37
+ export function deriveTrustBucketReason(bucket: TrustBucket, summary: SkillTrustSummary): string {
38
+ switch (bucket) {
39
+ case "at_risk":
40
+ if (summary.latest_action === "rolled_back") return "Recently rolled back";
41
+ return `High miss rate (${formatPercent(summary.miss_rate)})`;
42
+ case "improving":
43
+ if (summary.latest_action === "validated") return "Proposal validated, pending deploy";
44
+ return "Has pending evolution proposal";
45
+ case "uncertain":
46
+ if (summary.total_checks < 10) return `Low sample size (${summary.total_checks} checks)`;
47
+ return "Under active observation";
48
+ case "stable":
49
+ return "Routing healthy, no issues detected";
50
+ }
51
+ }
52
+
53
+ export function buildTrustWatchlist(summaries: SkillTrustSummary[]): TrustWatchlistEntry[] {
54
+ return summaries.map((summary) => {
55
+ const bucket = deriveTrustBucket(summary);
56
+ return {
57
+ skill_name: summary.skill_name,
58
+ bucket,
59
+ trust_state: deriveTrustState(summary),
60
+ reason: deriveTrustBucketReason(bucket, summary),
61
+ pass_rate: summary.pass_rate,
62
+ checks: summary.total_checks,
63
+ last_seen: summary.last_seen,
64
+ };
65
+ });
66
+ }
@@ -93,6 +93,17 @@ export interface SessionTelemetryRecord {
93
93
  source?: string;
94
94
  input_tokens?: number;
95
95
  output_tokens?: number;
96
+ cached_input_tokens?: number;
97
+ reasoning_output_tokens?: number;
98
+ cost_usd?: number;
99
+ files_changed?: number;
100
+ lines_added?: number;
101
+ lines_removed?: number;
102
+ lines_modified?: number;
103
+ /** Count of output-producing tool calls (Write, Edit, WebFetch, WebSearch, Skill, Agent). */
104
+ artifact_count?: number;
105
+ /** Inferred session type based on tool distribution. */
106
+ session_type?: SessionType;
96
107
  agent_summary?: string;
97
108
  rollout_path?: string;
98
109
  }
@@ -140,6 +151,13 @@ export {
140
151
  CANONICAL_SOURCE_SESSION_KINDS,
141
152
  } from "@selftune/telemetry-contract/types";
142
153
 
154
+ // ---------------------------------------------------------------------------
155
+ // Session classification
156
+ // ---------------------------------------------------------------------------
157
+
158
+ /** Inferred session type based on tool distribution. */
159
+ export type SessionType = "dev" | "research" | "content" | "mixed";
160
+
143
161
  // ---------------------------------------------------------------------------
144
162
  // Transcript parsing
145
163
  // ---------------------------------------------------------------------------
@@ -156,6 +174,17 @@ export interface TranscriptMetrics {
156
174
  last_user_query: string;
157
175
  input_tokens?: number;
158
176
  output_tokens?: number;
177
+ cached_input_tokens?: number;
178
+ reasoning_output_tokens?: number;
179
+ cost_usd?: number;
180
+ files_changed?: number;
181
+ lines_added?: number;
182
+ lines_removed?: number;
183
+ lines_modified?: number;
184
+ /** Count of output-producing tool calls (Write, Edit, WebFetch, WebSearch, Skill, Agent). */
185
+ artifact_count?: number;
186
+ /** Inferred session type based on tool distribution. */
187
+ session_type?: SessionType;
159
188
  duration_ms?: number;
160
189
  model?: string;
161
190
  started_at?: string;
@@ -290,6 +319,8 @@ export interface ExecutionMetrics {
290
319
  errors_encountered: number;
291
320
  skills_triggered: string[];
292
321
  transcript_chars: number;
322
+ artifact_count?: number;
323
+ session_type?: SessionType;
293
324
  }
294
325
 
295
326
  // ---------------------------------------------------------------------------
@@ -823,6 +854,55 @@ export interface ComposabilityReportV2 extends ComposabilityReport {
823
854
  synergy_count: number;
824
855
  }
825
856
 
857
+ // ---------------------------------------------------------------------------
858
+ // Skill family overlap / consolidation types
859
+ // ---------------------------------------------------------------------------
860
+
861
+ export interface SkillFamilyOverlapMember {
862
+ skill_name: string;
863
+ skill_path?: string;
864
+ positive_query_count: number;
865
+ }
866
+
867
+ export interface SkillFamilyOverlapPair {
868
+ skill_a: string;
869
+ skill_b: string;
870
+ overlap_pct: number;
871
+ shared_query_count: number;
872
+ shared_queries: string[];
873
+ consolidation_pressure: "low" | "medium" | "high";
874
+ }
875
+
876
+ export interface SkillFamilyRefactorWorkflow {
877
+ workflow_name: string;
878
+ source_skill: string;
879
+ suggested_path: string;
880
+ }
881
+
882
+ export interface SkillFamilyRefactorProposal {
883
+ parent_skill_name: string;
884
+ family_prefix?: string;
885
+ internal_workflows: SkillFamilyRefactorWorkflow[];
886
+ compatibility_aliases: Array<{ skill_name: string; target_workflow: string }>;
887
+ migration_notes: string[];
888
+ }
889
+
890
+ export interface SkillFamilyOverlapReport {
891
+ family_prefix?: string;
892
+ analyzed_skills: string[];
893
+ members: SkillFamilyOverlapMember[];
894
+ pairs: SkillFamilyOverlapPair[];
895
+ total_pairs_analyzed: number;
896
+ overlap_count: number;
897
+ overlap_density: number;
898
+ average_overlap_pct: number;
899
+ consolidation_candidate: boolean;
900
+ recommendation: string;
901
+ rationale: string[];
902
+ refactor_proposal?: SkillFamilyRefactorProposal;
903
+ generated_at: string;
904
+ }
905
+
826
906
  // ---------------------------------------------------------------------------
827
907
  // Workflow Support types
828
908
  // ---------------------------------------------------------------------------
@@ -0,0 +1,43 @@
1
+ export function normalizeSkillName(value: string): string {
2
+ return value.trim().toLowerCase();
3
+ }
4
+
5
+ export function getInternalPromptTargetSkill(
6
+ text: string,
7
+ knownSkillNames: Iterable<string>,
8
+ ): string | null {
9
+ if (!text) return null;
10
+ const isInternalSkillPrompt =
11
+ text.includes("You are a skill description optimizer") ||
12
+ text.includes("You are an evaluation assistant") ||
13
+ text.includes("Given this skill description");
14
+ if (!isInternalSkillPrompt) return null;
15
+
16
+ const candidates = [
17
+ /Skill Name:\s*([^\n]+)/i,
18
+ /Propose an improved description for the "([^"]+)" skill/i,
19
+ /would each query trigger the "([^"]+)" skill/i,
20
+ ];
21
+ for (const pattern of candidates) {
22
+ const match = text.match(pattern);
23
+ const rawSkillName = match?.[1]?.trim();
24
+ if (!rawSkillName) continue;
25
+ const normalizedTarget = normalizeSkillName(rawSkillName);
26
+ for (const skillName of knownSkillNames) {
27
+ if (normalizeSkillName(skillName) === normalizedTarget) {
28
+ return skillName;
29
+ }
30
+ }
31
+ return rawSkillName;
32
+ }
33
+ return null;
34
+ }
35
+
36
+ export function isWrappedNonUserPart(text: string): boolean {
37
+ const trimmed = text.trimStart();
38
+ return (
39
+ trimmed.startsWith("# AGENTS.md instructions for ") ||
40
+ trimmed.startsWith("<environment_context>") ||
41
+ trimmed.startsWith("<INSTRUCTIONS>")
42
+ );
43
+ }