selftune 0.2.9 → 0.2.10

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 (130) hide show
  1. package/README.md +35 -35
  2. package/apps/local-dashboard/dist/assets/index-BZVLv70T.js +16 -0
  3. package/apps/local-dashboard/dist/assets/{vendor-react-BQH_6WrG.js → vendor-react-BXP54cYo.js} +4 -4
  4. package/apps/local-dashboard/dist/assets/{vendor-table-dK1QMLq9.js → vendor-table-DTF_SXoy.js} +1 -1
  5. package/apps/local-dashboard/dist/assets/{vendor-ui-CO2mrx6e.js → vendor-ui-CWU0d1wd.js} +66 -66
  6. package/apps/local-dashboard/dist/index.html +15 -15
  7. package/bin/selftune.cjs +1 -1
  8. package/cli/selftune/activation-rules.ts +1 -0
  9. package/cli/selftune/alpha-upload/build-payloads.ts +18 -2
  10. package/cli/selftune/alpha-upload/stage-canonical.ts +94 -0
  11. package/cli/selftune/auth/device-code.ts +32 -0
  12. package/cli/selftune/auto-update.ts +12 -0
  13. package/cli/selftune/badge/badge.ts +1 -0
  14. package/cli/selftune/canonical-export.ts +5 -0
  15. package/cli/selftune/claude-agents.ts +154 -0
  16. package/cli/selftune/contribute/bundle.ts +1 -0
  17. package/cli/selftune/contribute/contribute.ts +1 -0
  18. package/cli/selftune/cron/setup.ts +2 -2
  19. package/cli/selftune/dashboard-server.ts +1 -0
  20. package/cli/selftune/eval/hooks-to-evals.ts +1 -0
  21. package/cli/selftune/eval/import-skillsbench.ts +1 -0
  22. package/cli/selftune/eval/synthetic-evals.ts +2 -3
  23. package/cli/selftune/eval/unit-test.ts +1 -0
  24. package/cli/selftune/evolution/deploy-proposal.ts +1 -0
  25. package/cli/selftune/evolution/evolve-body.ts +93 -6
  26. package/cli/selftune/evolution/evolve.ts +0 -1
  27. package/cli/selftune/evolution/propose-body.ts +3 -2
  28. package/cli/selftune/evolution/propose-routing.ts +3 -2
  29. package/cli/selftune/evolution/refine-body.ts +3 -2
  30. package/cli/selftune/export.ts +1 -0
  31. package/cli/selftune/grading/grade-session.ts +8 -0
  32. package/cli/selftune/hooks/auto-activate.ts +1 -0
  33. package/cli/selftune/hooks/evolution-guard.ts +1 -1
  34. package/cli/selftune/hooks/prompt-log.ts +1 -0
  35. package/cli/selftune/hooks/session-stop.ts +34 -40
  36. package/cli/selftune/hooks/skill-change-guard.ts +1 -0
  37. package/cli/selftune/hooks/skill-eval.ts +1 -1
  38. package/cli/selftune/index.ts +23 -14
  39. package/cli/selftune/ingestors/claude-replay.ts +1 -0
  40. package/cli/selftune/ingestors/codex-rollout.ts +1 -0
  41. package/cli/selftune/ingestors/codex-wrapper.ts +1 -0
  42. package/cli/selftune/ingestors/openclaw-ingest.ts +1 -0
  43. package/cli/selftune/ingestors/opencode-ingest.ts +1 -0
  44. package/cli/selftune/init.ts +121 -29
  45. package/cli/selftune/localdb/db.ts +1 -0
  46. package/cli/selftune/localdb/direct-write.ts +39 -0
  47. package/cli/selftune/localdb/materialize.ts +2 -0
  48. package/cli/selftune/localdb/queries.ts +53 -0
  49. package/cli/selftune/localdb/schema.ts +28 -0
  50. package/cli/selftune/normalization.ts +1 -0
  51. package/cli/selftune/observability.ts +1 -0
  52. package/cli/selftune/repair/skill-usage.ts +1 -0
  53. package/cli/selftune/routes/orchestrate-runs.ts +1 -0
  54. package/cli/selftune/routes/overview.ts +1 -0
  55. package/cli/selftune/routes/skill-report.ts +1 -0
  56. package/cli/selftune/sync.ts +30 -1
  57. package/cli/selftune/uninstall.ts +412 -0
  58. package/cli/selftune/utils/canonical-log.ts +2 -0
  59. package/cli/selftune/utils/jsonl.ts +1 -0
  60. package/cli/selftune/utils/llm-call.ts +131 -3
  61. package/cli/selftune/utils/skill-log.ts +1 -0
  62. package/cli/selftune/utils/transcript.ts +1 -0
  63. package/cli/selftune/utils/trigger-check.ts +1 -1
  64. package/cli/selftune/workflows/skill-md-writer.ts +5 -5
  65. package/cli/selftune/workflows/workflows.ts +1 -0
  66. package/package.json +37 -33
  67. package/packages/telemetry-contract/fixtures/golden.test.ts +1 -0
  68. package/packages/telemetry-contract/package.json +1 -1
  69. package/packages/telemetry-contract/src/schemas.ts +1 -0
  70. package/packages/telemetry-contract/tests/compatibility.test.ts +1 -0
  71. package/packages/ui/README.md +35 -34
  72. package/packages/ui/package.json +3 -3
  73. package/packages/ui/src/components/ActivityTimeline.tsx +49 -42
  74. package/packages/ui/src/components/EvidenceViewer.tsx +306 -182
  75. package/packages/ui/src/components/EvolutionTimeline.tsx +83 -72
  76. package/packages/ui/src/components/InfoTip.tsx +4 -3
  77. package/packages/ui/src/components/OrchestrateRunsPanel.tsx +60 -53
  78. package/packages/ui/src/components/section-cards.tsx +19 -24
  79. package/packages/ui/src/components/skill-health-grid.tsx +213 -193
  80. package/packages/ui/src/lib/constants.tsx +1 -0
  81. package/packages/ui/src/primitives/badge.tsx +12 -15
  82. package/packages/ui/src/primitives/button.tsx +7 -7
  83. package/packages/ui/src/primitives/card.tsx +15 -26
  84. package/packages/ui/src/primitives/checkbox.tsx +7 -8
  85. package/packages/ui/src/primitives/collapsible.tsx +5 -5
  86. package/packages/ui/src/primitives/dropdown-menu.tsx +45 -55
  87. package/packages/ui/src/primitives/label.tsx +6 -6
  88. package/packages/ui/src/primitives/select.tsx +28 -37
  89. package/packages/ui/src/primitives/table.tsx +17 -44
  90. package/packages/ui/src/primitives/tabs.tsx +14 -21
  91. package/packages/ui/src/primitives/tooltip.tsx +10 -22
  92. package/skill/SKILL.md +70 -57
  93. package/skill/Workflows/AlphaUpload.md +4 -4
  94. package/skill/Workflows/AutoActivation.md +11 -6
  95. package/skill/Workflows/Badge.md +22 -16
  96. package/skill/Workflows/Baseline.md +34 -36
  97. package/skill/Workflows/Composability.md +16 -11
  98. package/skill/Workflows/Contribute.md +26 -21
  99. package/skill/Workflows/Cron.md +23 -22
  100. package/skill/Workflows/Dashboard.md +32 -27
  101. package/skill/Workflows/Doctor.md +33 -27
  102. package/skill/Workflows/Evals.md +48 -47
  103. package/skill/Workflows/EvolutionMemory.md +31 -21
  104. package/skill/Workflows/Evolve.md +84 -82
  105. package/skill/Workflows/EvolveBody.md +58 -47
  106. package/skill/Workflows/Grade.md +16 -13
  107. package/skill/Workflows/ImportSkillsBench.md +9 -6
  108. package/skill/Workflows/Ingest.md +36 -21
  109. package/skill/Workflows/Initialize.md +108 -40
  110. package/skill/Workflows/Orchestrate.md +22 -16
  111. package/skill/Workflows/Replay.md +12 -7
  112. package/skill/Workflows/Rollback.md +13 -6
  113. package/skill/Workflows/Schedule.md +6 -6
  114. package/skill/Workflows/Sync.md +18 -11
  115. package/skill/Workflows/UnitTest.md +28 -17
  116. package/skill/Workflows/Watch.md +28 -21
  117. package/skill/agents/diagnosis-analyst.md +11 -0
  118. package/skill/agents/evolution-reviewer.md +15 -1
  119. package/skill/agents/integration-guide.md +10 -0
  120. package/skill/agents/pattern-analyst.md +12 -1
  121. package/skill/references/grading-methodology.md +23 -24
  122. package/skill/references/interactive-config.md +7 -7
  123. package/skill/references/invocation-taxonomy.md +22 -20
  124. package/skill/references/logs.md +14 -6
  125. package/skill/references/setup-patterns.md +4 -2
  126. package/.claude/agents/diagnosis-analyst.md +0 -156
  127. package/.claude/agents/evolution-reviewer.md +0 -180
  128. package/.claude/agents/integration-guide.md +0 -212
  129. package/.claude/agents/pattern-analyst.md +0 -160
  130. package/apps/local-dashboard/dist/assets/index-C4UYGWKr.js +0 -15
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * Usage:
10
10
  * selftune init [--agent <type>] [--cli-path <path>] [--force]
11
- * selftune init --enable-autonomy [--schedule-format cron|launchd|systemd]
11
+ * selftune init [--no-sync] [--no-autonomy] [--schedule-format cron|launchd|systemd]
12
12
  */
13
13
 
14
14
  import {
@@ -35,12 +35,20 @@ import {
35
35
  readAlphaIdentity,
36
36
  } from "./alpha-identity.js";
37
37
  import { TELEMETRY_NOTICE } from "./analytics.js";
38
- import { pollDeviceCode, requestDeviceCode } from "./auth/device-code.js";
38
+ import {
39
+ buildVerificationUrl,
40
+ pollDeviceCode,
41
+ requestDeviceCode,
42
+ tryOpenUrl,
43
+ } from "./auth/device-code.js";
44
+ import { installAgentFiles } from "./claude-agents.js";
39
45
  import { CLAUDE_CODE_HOOK_KEYS, SELFTUNE_CONFIG_DIR, SELFTUNE_CONFIG_PATH } from "./constants.js";
40
46
  import type { AgentCommandGuidance, AlphaIdentity, SelftuneConfig } from "./types.js";
41
47
  import { hookKeyHasSelftuneEntry } from "./utils/hooks.js";
42
48
  import { detectAgent } from "./utils/llm-call.js";
43
49
 
50
+ export { installAgentFiles } from "./claude-agents.js";
51
+
44
52
  interface InitCliErrorPayload extends AgentCommandGuidance {
45
53
  error: string;
46
54
  }
@@ -309,19 +317,6 @@ export function installClaudeCodeHooks(options?: {
309
317
  return addedKeys;
310
318
  }
311
319
 
312
- // ---------------------------------------------------------------------------
313
- // Agent file installation
314
- // ---------------------------------------------------------------------------
315
-
316
- /**
317
- * @deprecated Agent files are now bundled in skill/agents/ and read directly
318
- * by the consuming agent via progressive disclosure. No installation needed.
319
- * Kept as a no-op for backwards compatibility with callers.
320
- */
321
- export function installAgentFiles(_options?: { homeDir?: string; force?: boolean }): string[] {
322
- return [];
323
- }
324
-
325
320
  // ---------------------------------------------------------------------------
326
321
  // Workspace type detection
327
322
  // ---------------------------------------------------------------------------
@@ -508,7 +503,11 @@ export async function runInit(opts: InitOptions): Promise<SelftuneConfig> {
508
503
  if (!force && !hasAlphaMutation && existsSync(configPath)) {
509
504
  const raw = readFileSync(configPath, "utf-8");
510
505
  try {
511
- return JSON.parse(raw) as SelftuneConfig;
506
+ const existingConfig = JSON.parse(raw) as SelftuneConfig;
507
+ if (existingConfig.agent_type === "claude_code") {
508
+ installAgentFiles({ homeDir: opts.homeDir });
509
+ }
510
+ return existingConfig;
512
511
  } catch (err) {
513
512
  throw new Error(
514
513
  `Config file at ${configPath} contains invalid JSON. Delete it or use --force to reinitialize. Cause: ${err instanceof Error ? err.message : String(err)}`,
@@ -548,6 +547,7 @@ export async function runInit(opts: InitOptions): Promise<SelftuneConfig> {
548
547
  process.stderr.write("[alpha] Starting device-code authentication flow...\n");
549
548
 
550
549
  const grant = await requestDeviceCode();
550
+ const verificationUrlWithCode = buildVerificationUrl(grant.verification_url, grant.user_code);
551
551
 
552
552
  // Emit structured JSON for the agent to parse
553
553
  console.log(
@@ -555,25 +555,24 @@ export async function runInit(opts: InitOptions): Promise<SelftuneConfig> {
555
555
  level: "info",
556
556
  code: "device_code_issued",
557
557
  verification_url: grant.verification_url,
558
+ verification_url_with_code: verificationUrlWithCode,
558
559
  user_code: grant.user_code,
559
560
  expires_in: grant.expires_in,
560
- message: `Open ${grant.verification_url} and enter code: ${grant.user_code}`,
561
+ message: `Open ${verificationUrlWithCode} to approve.`,
561
562
  }),
562
563
  );
563
564
 
564
565
  // Try to open browser (skip in test environments)
565
566
  if (!process.env.BUN_ENV?.includes("test") && !process.env.SELFTUNE_NO_BROWSER) {
566
- try {
567
- const url = `${grant.verification_url}?code=${grant.user_code}`;
568
- Bun.spawn(["open", url], { stdout: "ignore", stderr: "ignore" });
567
+ if (tryOpenUrl(verificationUrlWithCode)) {
569
568
  process.stderr.write(`[alpha] Browser opened. Waiting for approval...\n`);
570
- } catch {
571
- process.stderr.write(`[alpha] Could not open browser. Visit the URL above manually.\n`);
569
+ } else {
570
+ process.stderr.write(
571
+ `[alpha] Could not open browser. Visit ${verificationUrlWithCode} manually.\n`,
572
+ );
572
573
  }
573
574
  } else {
574
- process.stderr.write(
575
- `[alpha] Visit ${grant.verification_url}?code=${grant.user_code} to approve.\n`,
576
- );
575
+ process.stderr.write(`[alpha] Visit ${verificationUrlWithCode} to approve.\n`);
577
576
  }
578
577
 
579
578
  process.stderr.write("[alpha] Polling");
@@ -606,11 +605,15 @@ export async function runInit(opts: InitOptions): Promise<SelftuneConfig> {
606
605
  mkdirSync(configDir, { recursive: true });
607
606
  writeSelftuneConfig(configPath, config);
608
607
 
609
- // Agent files are bundled in skill/agents/ and read directly by the
610
- // consuming agent — no installation step needed.
611
-
612
608
  // Auto-install hooks into ~/.claude/settings.json (Claude Code only)
613
609
  if (agentType === "claude_code") {
610
+ const syncedAgentFiles = installAgentFiles({ homeDir: home });
611
+ if (syncedAgentFiles.length > 0) {
612
+ console.error(
613
+ `[INFO] Synced ${syncedAgentFiles.length} selftune agent file(s) into ${join(home, ".claude", "agents")}: ${syncedAgentFiles.join(", ")}`,
614
+ );
615
+ }
616
+
614
617
  const addedHookKeys = installClaudeCodeHooks({
615
618
  settingsPath,
616
619
  cliPath,
@@ -668,6 +671,8 @@ export async function cliMain(): Promise<void> {
668
671
  "cli-path": { type: "string" },
669
672
  force: { type: "boolean", default: false },
670
673
  "enable-autonomy": { type: "boolean", default: false },
674
+ "no-sync": { type: "boolean", default: false },
675
+ "no-autonomy": { type: "boolean", default: false },
671
676
  "schedule-format": { type: "string" },
672
677
  alpha: { type: "boolean", default: false },
673
678
  "no-alpha": { type: "boolean", default: false },
@@ -680,7 +685,10 @@ export async function cliMain(): Promise<void> {
680
685
  const configDir = SELFTUNE_CONFIG_DIR;
681
686
  const configPath = SELFTUNE_CONFIG_PATH;
682
687
  const force = values.force ?? false;
683
- const enableAutonomy = values["enable-autonomy"] ?? false;
688
+ // Sync and autonomy are on by default; opt out with --no-sync / --no-autonomy
689
+ const enableSync = !(values["no-sync"] ?? false);
690
+ // --enable-autonomy is a backward-compatible alias (now default behavior)
691
+ const enableAutonomy = !values["no-autonomy"];
684
692
  try {
685
693
  validateAlphaMetadataFlags(values.alpha, values["alpha-email"], values["alpha-name"]);
686
694
  } catch (error) {
@@ -695,6 +703,7 @@ export async function cliMain(): Promise<void> {
695
703
  values["alpha-email"] ||
696
704
  values["alpha-name"]
697
705
  );
706
+ let existingConfigDetected = false;
698
707
  if (!force && !enableAutonomy && !hasAlphaMutation && existsSync(configPath)) {
699
708
  try {
700
709
  const raw = readFileSync(configPath, "utf-8");
@@ -708,6 +717,14 @@ export async function cliMain(): Promise<void> {
708
717
  );
709
718
  }
710
719
  }
720
+ if (!force && !hasAlphaMutation && existsSync(configPath)) {
721
+ try {
722
+ JSON.parse(readFileSync(configPath, "utf-8")) as SelftuneConfig;
723
+ existingConfigDetected = true;
724
+ } catch {
725
+ existingConfigDetected = false;
726
+ }
727
+ }
711
728
 
712
729
  const config = await runInit({
713
730
  configDir,
@@ -727,6 +744,9 @@ export async function cliMain(): Promise<void> {
727
744
  safeConfig.alpha.api_key = "<redacted>";
728
745
  }
729
746
  console.log(JSON.stringify(safeConfig, null, 2));
747
+ if (existingConfigDetected) {
748
+ console.error("Already initialized. Use --force to reinitialize.");
749
+ }
730
750
 
731
751
  // Alpha enrollment output
732
752
  if (values.alpha) {
@@ -790,6 +810,78 @@ export async function cliMain(): Promise<void> {
790
810
  }),
791
811
  );
792
812
 
813
+ // Backfill historical transcripts into SQLite
814
+ if (enableSync) {
815
+ try {
816
+ const { syncSources } = await import("./sync.js");
817
+ const syncResult = syncSources({
818
+ syncClaude: true,
819
+ syncCodex: true,
820
+ syncOpenCode: true,
821
+ syncOpenClaw: true,
822
+ rebuildSkillUsage: true,
823
+ dryRun: false,
824
+ });
825
+
826
+ const totalSynced =
827
+ (syncResult.sources.claude?.synced ?? 0) +
828
+ (syncResult.sources.codex?.synced ?? 0) +
829
+ (syncResult.sources.opencode?.synced ?? 0) +
830
+ (syncResult.sources.openclaw?.synced ?? 0);
831
+
832
+ console.log(
833
+ JSON.stringify({
834
+ level: "info",
835
+ code: "sync_complete",
836
+ sessions_synced: totalSynced,
837
+ repaired_records: syncResult.repair.repaired_records,
838
+ elapsed_ms: syncResult.total_elapsed_ms,
839
+ }),
840
+ );
841
+ } catch (err) {
842
+ // Fail-open: sync failure should not block init completion
843
+ console.log(
844
+ JSON.stringify({
845
+ level: "warn",
846
+ code: "sync_failed",
847
+ error: err instanceof Error ? err.message : String(err),
848
+ }),
849
+ );
850
+ }
851
+ }
852
+
853
+ // Trigger initial alpha upload if enrolled — push synced data immediately
854
+ if (config.alpha?.enrolled && config.alpha?.api_key) {
855
+ try {
856
+ const { runUploadCycle } = await import("./alpha-upload/index.js");
857
+ const { getDb } = await import("./localdb/db.js");
858
+ const db = getDb();
859
+ const uploadSummary = await runUploadCycle(db, {
860
+ enrolled: true,
861
+ userId: config.alpha.user_id,
862
+ apiKey: config.alpha.api_key,
863
+ });
864
+ console.log(
865
+ JSON.stringify({
866
+ level: "info",
867
+ code: "init_upload_complete",
868
+ prepared: uploadSummary.prepared,
869
+ sent: uploadSummary.sent,
870
+ failed: uploadSummary.failed,
871
+ }),
872
+ );
873
+ } catch (err) {
874
+ // Fail-open: upload failure should not block init
875
+ console.log(
876
+ JSON.stringify({
877
+ level: "warn",
878
+ code: "init_upload_failed",
879
+ error: err instanceof Error ? err.message : String(err),
880
+ }),
881
+ );
882
+ }
883
+ }
884
+
793
885
  if (enableAutonomy) {
794
886
  try {
795
887
  const { installSchedule } = await import("./schedule.js");
@@ -11,6 +11,7 @@
11
11
  import { Database } from "bun:sqlite";
12
12
  import { existsSync, mkdirSync } from "node:fs";
13
13
  import { dirname, join } from "node:path";
14
+
14
15
  import { SELFTUNE_CONFIG_DIR } from "../constants.js";
15
16
  import { ALL_DDL, MIGRATIONS, POST_MIGRATION_INDEXES } from "./schema.js";
16
17
 
@@ -10,6 +10,8 @@
10
10
  */
11
11
 
12
12
  import type { Database } from "bun:sqlite";
13
+ import { createHash } from "node:crypto";
14
+
13
15
  import type {
14
16
  CanonicalExecutionFactRecord,
15
17
  CanonicalPromptRecord,
@@ -17,10 +19,12 @@ import type {
17
19
  CanonicalSessionRecord,
18
20
  CanonicalSkillInvocationRecord,
19
21
  } from "@selftune/telemetry-contract";
22
+
20
23
  import type { OrchestrateRunReport } from "../dashboard-contract.js";
21
24
  import type {
22
25
  EvolutionAuditEntry,
23
26
  EvolutionEvidenceEntry,
27
+ GradingResult,
24
28
  SessionTelemetryRecord,
25
29
  SkillUsageRecord,
26
30
  } from "../types.js";
@@ -353,6 +357,41 @@ export function writeQueryToDb(record: {
353
357
  });
354
358
  }
355
359
 
360
+ export function writeGradingResultToDb(result: GradingResult): boolean {
361
+ const gradingId = `gr_${createHash("sha256").update(`${result.session_id}:${result.skill_name}:${result.graded_at}`).digest("hex").slice(0, 16)}`;
362
+ return safeWrite("grading-result", (db) => {
363
+ getStmt(
364
+ db,
365
+ "grading-result",
366
+ `
367
+ INSERT OR IGNORE INTO grading_results
368
+ (grading_id, session_id, skill_name, transcript_path, graded_at,
369
+ pass_rate, mean_score, score_std_dev, passed_count, failed_count, total_count,
370
+ expectations_json, claims_json, eval_feedback_json, failure_feedback_json,
371
+ execution_metrics_json)
372
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
373
+ `,
374
+ ).run(
375
+ gradingId,
376
+ result.session_id,
377
+ result.skill_name,
378
+ result.transcript_path,
379
+ result.graded_at,
380
+ result.summary.pass_rate,
381
+ result.summary.mean_score ?? null,
382
+ result.summary.score_std_dev ?? null,
383
+ result.summary.passed,
384
+ result.summary.failed,
385
+ result.summary.total,
386
+ JSON.stringify(result.expectations),
387
+ JSON.stringify(result.claims),
388
+ JSON.stringify(result.eval_feedback),
389
+ result.failure_feedback ? JSON.stringify(result.failure_feedback) : null,
390
+ JSON.stringify(result.execution_metrics),
391
+ );
392
+ });
393
+ }
394
+
356
395
  export function writeImprovementSignalToDb(record: {
357
396
  timestamp: string;
358
397
  session_id: string;
@@ -14,6 +14,7 @@
14
14
  // 3. Backfill from batch ingestors that don't yet dual-write
15
15
 
16
16
  import type { Database } from "bun:sqlite";
17
+
17
18
  import {
18
19
  type CanonicalExecutionFactRecord,
19
20
  type CanonicalPromptRecord,
@@ -22,6 +23,7 @@ import {
22
23
  type CanonicalSkillInvocationRecord,
23
24
  isCanonicalRecord,
24
25
  } from "@selftune/telemetry-contract";
26
+
25
27
  import {
26
28
  CANONICAL_LOG,
27
29
  EVOLUTION_AUDIT_LOG,
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { Database } from "bun:sqlite";
9
+
9
10
  import type {
10
11
  OrchestrateRunReport,
11
12
  OverviewPayload,
@@ -578,6 +579,58 @@ export function queryImprovementSignals(
578
579
  }));
579
580
  }
580
581
 
582
+ // -- Grading results query ----------------------------------------------------
583
+
584
+ /**
585
+ * Read grading results from SQLite for upload staging.
586
+ */
587
+ export function queryGradingResults(db: Database): Array<{
588
+ grading_id: string;
589
+ session_id: string;
590
+ skill_name: string;
591
+ transcript_path: string | null;
592
+ graded_at: string;
593
+ pass_rate: number | null;
594
+ mean_score: number | null;
595
+ score_std_dev: number | null;
596
+ passed_count: number | null;
597
+ failed_count: number | null;
598
+ total_count: number | null;
599
+ expectations_json: string | null;
600
+ claims_json: string | null;
601
+ eval_feedback_json: string | null;
602
+ failure_feedback_json: string | null;
603
+ execution_metrics_json: string | null;
604
+ }> {
605
+ return db
606
+ .query(
607
+ `SELECT grading_id, session_id, skill_name, transcript_path, graded_at,
608
+ pass_rate, mean_score, score_std_dev, passed_count, failed_count, total_count,
609
+ expectations_json, claims_json, eval_feedback_json, failure_feedback_json,
610
+ execution_metrics_json
611
+ FROM grading_results
612
+ ORDER BY graded_at DESC`,
613
+ )
614
+ .all() as Array<{
615
+ grading_id: string;
616
+ session_id: string;
617
+ skill_name: string;
618
+ transcript_path: string | null;
619
+ graded_at: string;
620
+ pass_rate: number | null;
621
+ mean_score: number | null;
622
+ score_std_dev: number | null;
623
+ passed_count: number | null;
624
+ failed_count: number | null;
625
+ total_count: number | null;
626
+ expectations_json: string | null;
627
+ claims_json: string | null;
628
+ eval_feedback_json: string | null;
629
+ failure_feedback_json: string | null;
630
+ execution_metrics_json: string | null;
631
+ }>;
632
+ }
633
+
581
634
  // -- Canonical record staging query -------------------------------------------
582
635
 
583
636
  /**
@@ -188,6 +188,28 @@ CREATE TABLE IF NOT EXISTS queries (
188
188
  source TEXT
189
189
  )`;
190
190
 
191
+ // -- Grading results table (from grade-session output) -----------------------
192
+
193
+ export const CREATE_GRADING_RESULTS = `
194
+ CREATE TABLE IF NOT EXISTS grading_results (
195
+ grading_id TEXT PRIMARY KEY,
196
+ session_id TEXT NOT NULL,
197
+ skill_name TEXT NOT NULL,
198
+ transcript_path TEXT,
199
+ graded_at TEXT NOT NULL,
200
+ pass_rate REAL,
201
+ mean_score REAL,
202
+ score_std_dev REAL,
203
+ passed_count INTEGER,
204
+ failed_count INTEGER,
205
+ total_count INTEGER,
206
+ expectations_json TEXT,
207
+ claims_json TEXT,
208
+ eval_feedback_json TEXT,
209
+ failure_feedback_json TEXT,
210
+ execution_metrics_json TEXT
211
+ )`;
212
+
191
213
  // -- Improvement signal table (from signal_log.jsonl) ------------------------
192
214
 
193
215
  export const CREATE_IMPROVEMENT_SIGNALS = `
@@ -278,6 +300,11 @@ export const CREATE_INDEXES = [
278
300
  `CREATE INDEX IF NOT EXISTS idx_queries_session ON queries(session_id)`,
279
301
  `CREATE INDEX IF NOT EXISTS idx_queries_ts ON queries(timestamp)`,
280
302
  `CREATE UNIQUE INDEX IF NOT EXISTS idx_queries_dedup ON queries(session_id, query, timestamp)`,
303
+ // -- Grading results indexes -------------------------------------------------
304
+ `CREATE INDEX IF NOT EXISTS idx_grading_session ON grading_results(session_id)`,
305
+ `CREATE INDEX IF NOT EXISTS idx_grading_skill ON grading_results(skill_name)`,
306
+ `CREATE INDEX IF NOT EXISTS idx_grading_ts ON grading_results(graded_at)`,
307
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_grading_dedup ON grading_results(session_id, skill_name, graded_at)`,
281
308
  // -- Improvement signal indexes ---------------------------------------------
282
309
  `CREATE INDEX IF NOT EXISTS idx_signals_session ON improvement_signals(session_id)`,
283
310
  `CREATE INDEX IF NOT EXISTS idx_signals_consumed ON improvement_signals(consumed)`,
@@ -347,6 +374,7 @@ export const ALL_DDL = [
347
374
  CREATE_SKILL_USAGE,
348
375
  CREATE_ORCHESTRATE_RUNS,
349
376
  CREATE_QUERIES,
377
+ CREATE_GRADING_RESULTS,
350
378
  CREATE_IMPROVEMENT_SIGNALS,
351
379
  CREATE_UPLOAD_QUEUE,
352
380
  CREATE_UPLOAD_WATERMARKS,
@@ -24,6 +24,7 @@ import {
24
24
  writeFileSync,
25
25
  } from "node:fs";
26
26
  import { basename, dirname } from "node:path";
27
+
27
28
  import { CANONICAL_LOG, canonicalSessionStatePath } from "./constants.js";
28
29
  import { writeCanonicalBatchToDb, writeCanonicalToDb } from "./localdb/direct-write.js";
29
30
  import {
@@ -11,6 +11,7 @@
11
11
  import { existsSync, readFileSync } from "node:fs";
12
12
  import { homedir } from "node:os";
13
13
  import { join } from "node:path";
14
+
14
15
  import { getAlphaGuidance } from "./agent-guidance.js";
15
16
  import { getAlphaLinkState, readAlphaIdentity } from "./alpha-identity.js";
16
17
  import { LOG_DIR, REQUIRED_FIELDS, SELFTUNE_CONFIG_PATH } from "./constants.js";
@@ -3,6 +3,7 @@
3
3
  import { existsSync, readFileSync, statSync } from "node:fs";
4
4
  import { basename, dirname, join } from "node:path";
5
5
  import { parseArgs } from "node:util";
6
+
6
7
  import {
7
8
  CLAUDE_CODE_PROJECTS_DIR,
8
9
  QUERY_LOG,
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { Database } from "bun:sqlite";
8
+
8
9
  import { getOrchestrateRuns } from "../localdb/queries.js";
9
10
 
10
11
  export function handleOrchestrateRuns(db: Database, limit: number): Response {
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { Database } from "bun:sqlite";
8
+
8
9
  import { getOverviewPayload, getSkillsList } from "../localdb/queries.js";
9
10
 
10
11
  export function handleOverview(db: Database, version: string): Response {
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { Database } from "bun:sqlite";
10
+
10
11
  import { getPendingProposals, getSkillReportPayload, safeParseJson } from "../localdb/queries.js";
11
12
 
12
13
  export function handleSkillReport(db: Database, skillName: string): Response {
@@ -17,6 +17,7 @@ import { existsSync } from "node:fs";
17
17
  import { homedir } from "node:os";
18
18
  import { join } from "node:path";
19
19
  import { parseArgs } from "node:util";
20
+
20
21
  import {
21
22
  CLAUDE_CODE_MARKER,
22
23
  CLAUDE_CODE_PROJECTS_DIR,
@@ -504,7 +505,7 @@ function formatStepLine(label: string, step: SyncStepResult, timing?: SyncPhaseT
504
505
  return ` ${label}: ${parts.join(", ")}${time}`;
505
506
  }
506
507
 
507
- export function cliMain(): void {
508
+ export async function cliMain(): Promise<void> {
508
509
  const { values } = parseArgs({
509
510
  options: {
510
511
  "projects-dir": { type: "string", default: CLAUDE_CODE_PROJECTS_DIR },
@@ -633,6 +634,34 @@ Options:
633
634
 
634
635
  process.stderr.write(`\nDone in ${formatMs(result.total_elapsed_ms)}\n`);
635
636
  }
637
+
638
+ // Trigger alpha upload if enrolled — pushes freshly synced data to cloud
639
+ if (!result.dry_run) {
640
+ try {
641
+ const { readAlphaIdentity } = await import("./alpha-identity.js");
642
+ const { SELFTUNE_CONFIG_PATH } = await import("./constants.js");
643
+ const identity = readAlphaIdentity(SELFTUNE_CONFIG_PATH);
644
+ if (identity?.enrolled && identity.api_key) {
645
+ const { runUploadCycle } = await import("./alpha-upload/index.js");
646
+ const { getDb } = await import("./localdb/db.js");
647
+ const db = getDb();
648
+ const uploadSummary = await runUploadCycle(db, {
649
+ enrolled: true,
650
+ userId: identity.user_id,
651
+ apiKey: identity.api_key,
652
+ });
653
+ if (!jsonOutput) {
654
+ process.stderr.write(
655
+ `\nAlpha upload: prepared=${uploadSummary.prepared}, sent=${uploadSummary.sent}, failed=${uploadSummary.failed}\n`,
656
+ );
657
+ } else {
658
+ console.log(JSON.stringify({ code: "alpha_upload", ...uploadSummary }));
659
+ }
660
+ }
661
+ } catch {
662
+ // fail-open: upload failure should not break sync
663
+ }
664
+ }
636
665
  }
637
666
 
638
667
  if (import.meta.main) {