selftune 0.2.8 → 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.
- package/README.md +35 -35
- package/apps/local-dashboard/dist/assets/index-BZVLv70T.js +16 -0
- package/apps/local-dashboard/dist/assets/{index-CRtLkBTi.css → index-Bs3Y4ixf.css} +1 -1
- package/apps/local-dashboard/dist/assets/{vendor-react-BQH_6WrG.js → vendor-react-BXP54cYo.js} +4 -4
- package/apps/local-dashboard/dist/assets/{vendor-table-dK1QMLq9.js → vendor-table-DTF_SXoy.js} +1 -1
- package/apps/local-dashboard/dist/assets/{vendor-ui-CO2mrx6e.js → vendor-ui-CWU0d1wd.js} +66 -66
- package/apps/local-dashboard/dist/index.html +15 -15
- package/bin/selftune.cjs +1 -1
- package/cli/selftune/activation-rules.ts +37 -18
- package/cli/selftune/agent-guidance.ts +16 -16
- package/cli/selftune/alpha-identity.ts +1 -2
- package/cli/selftune/alpha-upload/build-payloads.ts +18 -2
- package/cli/selftune/alpha-upload/flush.ts +2 -2
- package/cli/selftune/alpha-upload/stage-canonical.ts +106 -3
- package/cli/selftune/auth/device-code.ts +32 -0
- package/cli/selftune/auto-update.ts +12 -0
- package/cli/selftune/badge/badge.ts +1 -0
- package/cli/selftune/canonical-export.ts +5 -0
- package/cli/selftune/claude-agents.ts +154 -0
- package/cli/selftune/contribute/bundle.ts +2 -0
- package/cli/selftune/contribute/contribute.ts +1 -0
- package/cli/selftune/cron/setup.ts +2 -2
- package/cli/selftune/dashboard-contract.ts +1 -1
- package/cli/selftune/dashboard-server.ts +11 -52
- package/cli/selftune/eval/hooks-to-evals.ts +13 -6
- package/cli/selftune/eval/import-skillsbench.ts +1 -0
- package/cli/selftune/eval/synthetic-evals.ts +2 -3
- package/cli/selftune/eval/unit-test.ts +1 -0
- package/cli/selftune/evolution/deploy-proposal.ts +1 -0
- package/cli/selftune/evolution/evolve-body.ts +93 -6
- package/cli/selftune/evolution/evolve.ts +0 -1
- package/cli/selftune/evolution/propose-body.ts +3 -2
- package/cli/selftune/evolution/propose-routing.ts +3 -2
- package/cli/selftune/evolution/refine-body.ts +3 -2
- package/cli/selftune/export.ts +1 -0
- package/cli/selftune/grading/auto-grade.ts +1 -0
- package/cli/selftune/grading/grade-session.ts +9 -0
- package/cli/selftune/hooks/auto-activate.ts +6 -0
- package/cli/selftune/hooks/evolution-guard.ts +12 -15
- package/cli/selftune/hooks/prompt-log.ts +1 -0
- package/cli/selftune/hooks/session-stop.ts +34 -40
- package/cli/selftune/hooks/skill-change-guard.ts +1 -0
- package/cli/selftune/hooks/skill-eval.ts +1 -1
- package/cli/selftune/index.ts +23 -14
- package/cli/selftune/ingestors/claude-replay.ts +1 -0
- package/cli/selftune/ingestors/codex-rollout.ts +1 -0
- package/cli/selftune/ingestors/codex-wrapper.ts +1 -0
- package/cli/selftune/ingestors/openclaw-ingest.ts +1 -0
- package/cli/selftune/ingestors/opencode-ingest.ts +1 -0
- package/cli/selftune/init.ts +197 -96
- package/cli/selftune/localdb/db.ts +1 -0
- package/cli/selftune/localdb/direct-write.ts +93 -12
- package/cli/selftune/localdb/materialize.ts +2 -0
- package/cli/selftune/localdb/queries.ts +210 -0
- package/cli/selftune/localdb/schema.ts +72 -1
- package/cli/selftune/monitoring/watch.ts +1 -0
- package/cli/selftune/normalization.ts +4 -0
- package/cli/selftune/observability.ts +14 -7
- package/cli/selftune/orchestrate.ts +15 -37
- package/cli/selftune/repair/skill-usage.ts +7 -3
- package/cli/selftune/routes/orchestrate-runs.ts +1 -0
- package/cli/selftune/routes/overview.ts +1 -0
- package/cli/selftune/routes/skill-report.ts +1 -0
- package/cli/selftune/sync.ts +31 -1
- package/cli/selftune/types.ts +2 -2
- package/cli/selftune/uninstall.ts +412 -0
- package/cli/selftune/utils/canonical-log.ts +2 -0
- package/cli/selftune/utils/jsonl.ts +1 -0
- package/cli/selftune/utils/llm-call.ts +131 -3
- package/cli/selftune/utils/skill-log.ts +1 -0
- package/cli/selftune/utils/transcript.ts +1 -0
- package/cli/selftune/utils/trigger-check.ts +1 -1
- package/cli/selftune/workflows/skill-md-writer.ts +5 -5
- package/cli/selftune/workflows/workflows.ts +1 -0
- package/package.json +38 -33
- package/packages/telemetry-contract/fixtures/golden.test.ts +1 -0
- package/packages/telemetry-contract/package.json +3 -3
- package/packages/telemetry-contract/src/index.ts +0 -1
- package/packages/telemetry-contract/src/schemas.ts +6 -24
- package/packages/telemetry-contract/tests/compatibility.test.ts +1 -0
- package/packages/ui/README.md +35 -34
- package/packages/ui/package.json +3 -3
- package/packages/ui/src/components/ActivityTimeline.tsx +49 -42
- package/packages/ui/src/components/EvidenceViewer.tsx +306 -182
- package/packages/ui/src/components/EvolutionTimeline.tsx +83 -72
- package/packages/ui/src/components/InfoTip.tsx +4 -3
- package/packages/ui/src/components/OrchestrateRunsPanel.tsx +60 -53
- package/packages/ui/src/components/section-cards.tsx +19 -24
- package/packages/ui/src/components/skill-health-grid.tsx +213 -193
- package/packages/ui/src/lib/constants.tsx +1 -0
- package/packages/ui/src/primitives/badge.tsx +12 -15
- package/packages/ui/src/primitives/button.tsx +7 -7
- package/packages/ui/src/primitives/card.tsx +15 -26
- package/packages/ui/src/primitives/checkbox.tsx +7 -8
- package/packages/ui/src/primitives/collapsible.tsx +5 -5
- package/packages/ui/src/primitives/dropdown-menu.tsx +45 -55
- package/packages/ui/src/primitives/label.tsx +6 -6
- package/packages/ui/src/primitives/select.tsx +28 -37
- package/packages/ui/src/primitives/table.tsx +17 -44
- package/packages/ui/src/primitives/tabs.tsx +14 -21
- package/packages/ui/src/primitives/tooltip.tsx +10 -22
- package/skill/SKILL.md +72 -59
- package/skill/Workflows/AlphaUpload.md +4 -4
- package/skill/Workflows/AutoActivation.md +11 -6
- package/skill/Workflows/Badge.md +22 -16
- package/skill/Workflows/Baseline.md +34 -36
- package/skill/Workflows/Composability.md +16 -11
- package/skill/Workflows/Contribute.md +26 -21
- package/skill/Workflows/Cron.md +23 -22
- package/skill/Workflows/Dashboard.md +40 -40
- package/skill/Workflows/Doctor.md +40 -34
- package/skill/Workflows/Evals.md +48 -47
- package/skill/Workflows/EvolutionMemory.md +31 -21
- package/skill/Workflows/Evolve.md +84 -82
- package/skill/Workflows/EvolveBody.md +58 -47
- package/skill/Workflows/Grade.md +16 -13
- package/skill/Workflows/ImportSkillsBench.md +9 -6
- package/skill/Workflows/Ingest.md +36 -21
- package/skill/Workflows/Initialize.md +138 -97
- package/skill/Workflows/Orchestrate.md +22 -16
- package/skill/Workflows/Replay.md +12 -7
- package/skill/Workflows/Rollback.md +13 -6
- package/skill/Workflows/Schedule.md +6 -6
- package/skill/Workflows/Sync.md +18 -11
- package/skill/Workflows/UnitTest.md +28 -17
- package/skill/Workflows/Watch.md +28 -21
- package/skill/agents/diagnosis-analyst.md +11 -0
- package/skill/agents/evolution-reviewer.md +15 -1
- package/skill/agents/integration-guide.md +10 -0
- package/skill/agents/pattern-analyst.md +12 -1
- package/skill/references/grading-methodology.md +23 -24
- package/skill/references/interactive-config.md +7 -7
- package/skill/references/invocation-taxonomy.md +22 -20
- package/skill/references/logs.md +20 -6
- package/skill/references/setup-patterns.md +4 -2
- package/.claude/agents/diagnosis-analyst.md +0 -156
- package/.claude/agents/evolution-reviewer.md +0 -180
- package/.claude/agents/integration-guide.md +0 -212
- package/.claude/agents/pattern-analyst.md +0 -160
- package/apps/local-dashboard/dist/assets/index-Bk9vSHHd.js +0 -15
package/cli/selftune/init.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Usage:
|
|
10
10
|
* selftune init [--agent <type>] [--cli-path <path>] [--force]
|
|
11
|
-
* selftune init --
|
|
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 {
|
|
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
|
// ---------------------------------------------------------------------------
|
|
@@ -456,7 +451,38 @@ export interface InitOptions {
|
|
|
456
451
|
noAlpha?: boolean;
|
|
457
452
|
alphaEmail?: string;
|
|
458
453
|
alphaName?: string;
|
|
459
|
-
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function validateAlphaMetadataFlags(
|
|
457
|
+
alpha: boolean | undefined,
|
|
458
|
+
email?: string,
|
|
459
|
+
name?: string,
|
|
460
|
+
): void {
|
|
461
|
+
if ((email !== undefined || name !== undefined) && !alpha) {
|
|
462
|
+
throw new Error("--alpha-email and --alpha-name require --alpha");
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function assertValidApprovedAlphaCredential(result: {
|
|
467
|
+
api_key: string;
|
|
468
|
+
cloud_user_id: string;
|
|
469
|
+
org_id: string;
|
|
470
|
+
}): void {
|
|
471
|
+
if (!isValidApiKeyFormat(result.api_key)) {
|
|
472
|
+
throw new Error(
|
|
473
|
+
"Device-code approval returned an invalid alpha credential. Re-run `selftune init --alpha`.",
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
if (!result.cloud_user_id?.trim()) {
|
|
477
|
+
throw new Error(
|
|
478
|
+
"Device-code approval did not include a cloud user id. Re-run `selftune init --alpha`.",
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
if (!result.org_id?.trim()) {
|
|
482
|
+
throw new Error(
|
|
483
|
+
"Device-code approval did not include an alpha org id. Re-run `selftune init --alpha`.",
|
|
484
|
+
);
|
|
485
|
+
}
|
|
460
486
|
}
|
|
461
487
|
|
|
462
488
|
// ---------------------------------------------------------------------------
|
|
@@ -469,12 +495,19 @@ export interface InitOptions {
|
|
|
469
495
|
*/
|
|
470
496
|
export async function runInit(opts: InitOptions): Promise<SelftuneConfig> {
|
|
471
497
|
const { configDir, configPath, force } = opts;
|
|
498
|
+
validateAlphaMetadataFlags(opts.alpha, opts.alphaEmail, opts.alphaName);
|
|
472
499
|
|
|
473
|
-
// If config exists and no --force, return existing
|
|
474
|
-
|
|
500
|
+
// If config exists and no --force (and no alpha mutation), return existing
|
|
501
|
+
const hasAlphaMutation =
|
|
502
|
+
opts.alpha || opts.noAlpha || opts.alphaEmail !== undefined || opts.alphaName !== undefined;
|
|
503
|
+
if (!force && !hasAlphaMutation && existsSync(configPath)) {
|
|
475
504
|
const raw = readFileSync(configPath, "utf-8");
|
|
476
505
|
try {
|
|
477
|
-
|
|
506
|
+
const existingConfig = JSON.parse(raw) as SelftuneConfig;
|
|
507
|
+
if (existingConfig.agent_type === "claude_code") {
|
|
508
|
+
installAgentFiles({ homeDir: opts.homeDir });
|
|
509
|
+
}
|
|
510
|
+
return existingConfig;
|
|
478
511
|
} catch (err) {
|
|
479
512
|
throw new Error(
|
|
480
513
|
`Config file at ${configPath} contains invalid JSON. Delete it or use --force to reinitialize. Cause: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -510,81 +543,53 @@ export async function runInit(opts: InitOptions): Promise<SelftuneConfig> {
|
|
|
510
543
|
|
|
511
544
|
let validatedAlphaIdentity: AlphaIdentity | null = null;
|
|
512
545
|
if (opts.alpha) {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
if (!opts.alphaEmail) {
|
|
516
|
-
throw new InitCliError({
|
|
517
|
-
error: "alpha_email_required",
|
|
518
|
-
message:
|
|
519
|
-
"The --alpha-email flag is required when using --alpha-key. Run: selftune init --alpha --alpha-email user@example.com --alpha-key st_live_<key>",
|
|
520
|
-
next_command: "selftune init --alpha --alpha-email <email> --alpha-key st_live_<key>",
|
|
521
|
-
suggested_commands: ["selftune init --alpha", "selftune status"],
|
|
522
|
-
blocking: true,
|
|
523
|
-
code: "alpha_email_required",
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
if (!isValidApiKeyFormat(opts.alphaKey)) {
|
|
528
|
-
throw new InitCliError({
|
|
529
|
-
error: "invalid_api_key_format",
|
|
530
|
-
message: "API key must start with 'st_live_' or 'st_test_'. Check the key and retry.",
|
|
531
|
-
next_command: "selftune init --alpha --alpha-email <email> --alpha-key st_live_<key>",
|
|
532
|
-
suggested_commands: ["selftune status", "selftune doctor"],
|
|
533
|
-
blocking: true,
|
|
534
|
-
code: "invalid_api_key_format",
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
validatedAlphaIdentity = {
|
|
539
|
-
enrolled: true,
|
|
540
|
-
user_id: existingAlphaBeforeOverwrite?.user_id ?? generateUserId(),
|
|
541
|
-
email: opts.alphaEmail,
|
|
542
|
-
display_name: opts.alphaName,
|
|
543
|
-
consent_timestamp: new Date().toISOString(),
|
|
544
|
-
api_key: opts.alphaKey,
|
|
545
|
-
};
|
|
546
|
-
} else {
|
|
547
|
-
// Device-code flow — no key provided, authenticate via browser
|
|
548
|
-
process.stderr.write("[alpha] Starting device-code authentication flow...\n");
|
|
546
|
+
// Device-code flow — authenticate via browser approval
|
|
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
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
}
|
|
562
|
-
)
|
|
552
|
+
// Emit structured JSON for the agent to parse
|
|
553
|
+
console.log(
|
|
554
|
+
JSON.stringify({
|
|
555
|
+
level: "info",
|
|
556
|
+
code: "device_code_issued",
|
|
557
|
+
verification_url: grant.verification_url,
|
|
558
|
+
verification_url_with_code: verificationUrlWithCode,
|
|
559
|
+
user_code: grant.user_code,
|
|
560
|
+
expires_in: grant.expires_in,
|
|
561
|
+
message: `Open ${verificationUrlWithCode} to approve.`,
|
|
562
|
+
}),
|
|
563
|
+
);
|
|
563
564
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
Bun.spawn(["open", url], { stdout: "ignore", stderr: "ignore" });
|
|
565
|
+
// Try to open browser (skip in test environments)
|
|
566
|
+
if (!process.env.BUN_ENV?.includes("test") && !process.env.SELFTUNE_NO_BROWSER) {
|
|
567
|
+
if (tryOpenUrl(verificationUrlWithCode)) {
|
|
568
568
|
process.stderr.write(`[alpha] Browser opened. Waiting for approval...\n`);
|
|
569
|
-
}
|
|
570
|
-
process.stderr.write(
|
|
569
|
+
} else {
|
|
570
|
+
process.stderr.write(
|
|
571
|
+
`[alpha] Could not open browser. Visit ${verificationUrlWithCode} manually.\n`,
|
|
572
|
+
);
|
|
571
573
|
}
|
|
572
|
-
|
|
573
|
-
process.stderr.write(
|
|
574
|
-
const result = await pollDeviceCode(grant.device_code, grant.interval, grant.expires_in);
|
|
575
|
-
process.stderr.write("\n[alpha] Approved!\n");
|
|
576
|
-
|
|
577
|
-
validatedAlphaIdentity = {
|
|
578
|
-
enrolled: true,
|
|
579
|
-
user_id: existingAlphaBeforeOverwrite?.user_id ?? generateUserId(),
|
|
580
|
-
cloud_user_id: result.cloud_user_id,
|
|
581
|
-
cloud_org_id: result.org_id,
|
|
582
|
-
email: opts.alphaEmail,
|
|
583
|
-
display_name: opts.alphaName,
|
|
584
|
-
consent_timestamp: new Date().toISOString(),
|
|
585
|
-
api_key: result.api_key,
|
|
586
|
-
};
|
|
574
|
+
} else {
|
|
575
|
+
process.stderr.write(`[alpha] Visit ${verificationUrlWithCode} to approve.\n`);
|
|
587
576
|
}
|
|
577
|
+
|
|
578
|
+
process.stderr.write("[alpha] Polling");
|
|
579
|
+
const result = await pollDeviceCode(grant.device_code, grant.interval, grant.expires_in);
|
|
580
|
+
assertValidApprovedAlphaCredential(result);
|
|
581
|
+
process.stderr.write("\n[alpha] Approved!\n");
|
|
582
|
+
|
|
583
|
+
validatedAlphaIdentity = {
|
|
584
|
+
enrolled: true,
|
|
585
|
+
user_id: existingAlphaBeforeOverwrite?.user_id ?? generateUserId(),
|
|
586
|
+
cloud_user_id: result.cloud_user_id,
|
|
587
|
+
cloud_org_id: result.org_id,
|
|
588
|
+
email: opts.alphaEmail ?? existingAlphaBeforeOverwrite?.email,
|
|
589
|
+
display_name: opts.alphaName ?? existingAlphaBeforeOverwrite?.display_name,
|
|
590
|
+
consent_timestamp: new Date().toISOString(),
|
|
591
|
+
api_key: result.api_key,
|
|
592
|
+
};
|
|
588
593
|
}
|
|
589
594
|
|
|
590
595
|
const config: SelftuneConfig = {
|
|
@@ -600,11 +605,15 @@ export async function runInit(opts: InitOptions): Promise<SelftuneConfig> {
|
|
|
600
605
|
mkdirSync(configDir, { recursive: true });
|
|
601
606
|
writeSelftuneConfig(configPath, config);
|
|
602
607
|
|
|
603
|
-
// Agent files are bundled in skill/agents/ and read directly by the
|
|
604
|
-
// consuming agent — no installation step needed.
|
|
605
|
-
|
|
606
608
|
// Auto-install hooks into ~/.claude/settings.json (Claude Code only)
|
|
607
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
|
+
|
|
608
617
|
const addedHookKeys = installClaudeCodeHooks({
|
|
609
618
|
settingsPath,
|
|
610
619
|
cliPath,
|
|
@@ -662,12 +671,13 @@ export async function cliMain(): Promise<void> {
|
|
|
662
671
|
"cli-path": { type: "string" },
|
|
663
672
|
force: { type: "boolean", default: false },
|
|
664
673
|
"enable-autonomy": { type: "boolean", default: false },
|
|
674
|
+
"no-sync": { type: "boolean", default: false },
|
|
675
|
+
"no-autonomy": { type: "boolean", default: false },
|
|
665
676
|
"schedule-format": { type: "string" },
|
|
666
677
|
alpha: { type: "boolean", default: false },
|
|
667
678
|
"no-alpha": { type: "boolean", default: false },
|
|
668
679
|
"alpha-email": { type: "string" },
|
|
669
680
|
"alpha-name": { type: "string" },
|
|
670
|
-
"alpha-key": { type: "string" },
|
|
671
681
|
},
|
|
672
682
|
strict: true,
|
|
673
683
|
});
|
|
@@ -675,16 +685,25 @@ export async function cliMain(): Promise<void> {
|
|
|
675
685
|
const configDir = SELFTUNE_CONFIG_DIR;
|
|
676
686
|
const configPath = SELFTUNE_CONFIG_PATH;
|
|
677
687
|
const force = values.force ?? false;
|
|
678
|
-
|
|
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"];
|
|
692
|
+
try {
|
|
693
|
+
validateAlphaMetadataFlags(values.alpha, values["alpha-email"], values["alpha-name"]);
|
|
694
|
+
} catch (error) {
|
|
695
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
679
698
|
|
|
680
699
|
// Check for existing config without force
|
|
681
700
|
const hasAlphaMutation = !!(
|
|
682
701
|
values.alpha ||
|
|
683
702
|
values["no-alpha"] ||
|
|
684
703
|
values["alpha-email"] ||
|
|
685
|
-
values["alpha-name"]
|
|
686
|
-
values["alpha-key"]
|
|
704
|
+
values["alpha-name"]
|
|
687
705
|
);
|
|
706
|
+
let existingConfigDetected = false;
|
|
688
707
|
if (!force && !enableAutonomy && !hasAlphaMutation && existsSync(configPath)) {
|
|
689
708
|
try {
|
|
690
709
|
const raw = readFileSync(configPath, "utf-8");
|
|
@@ -698,6 +717,14 @@ export async function cliMain(): Promise<void> {
|
|
|
698
717
|
);
|
|
699
718
|
}
|
|
700
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
|
+
}
|
|
701
728
|
|
|
702
729
|
const config = await runInit({
|
|
703
730
|
configDir,
|
|
@@ -709,7 +736,6 @@ export async function cliMain(): Promise<void> {
|
|
|
709
736
|
noAlpha: values["no-alpha"] ?? false,
|
|
710
737
|
alphaEmail: values["alpha-email"],
|
|
711
738
|
alphaName: values["alpha-name"],
|
|
712
|
-
alphaKey: values["alpha-key"],
|
|
713
739
|
});
|
|
714
740
|
|
|
715
741
|
// Redact api_key before printing to stdout
|
|
@@ -718,6 +744,9 @@ export async function cliMain(): Promise<void> {
|
|
|
718
744
|
safeConfig.alpha.api_key = "<redacted>";
|
|
719
745
|
}
|
|
720
746
|
console.log(JSON.stringify(safeConfig, null, 2));
|
|
747
|
+
if (existingConfigDetected) {
|
|
748
|
+
console.error("Already initialized. Use --force to reinitialize.");
|
|
749
|
+
}
|
|
721
750
|
|
|
722
751
|
// Alpha enrollment output
|
|
723
752
|
if (values.alpha) {
|
|
@@ -781,6 +810,78 @@ export async function cliMain(): Promise<void> {
|
|
|
781
810
|
}),
|
|
782
811
|
);
|
|
783
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
|
+
|
|
784
885
|
if (enableAutonomy) {
|
|
785
886
|
try {
|
|
786
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";
|
|
@@ -44,6 +48,9 @@ export interface SkillInvocationWriteInput {
|
|
|
44
48
|
platform?: string;
|
|
45
49
|
schema_version?: string;
|
|
46
50
|
normalized_at?: string;
|
|
51
|
+
normalizer_version?: string;
|
|
52
|
+
capture_mode?: string;
|
|
53
|
+
raw_source_ref?: Record<string, unknown>;
|
|
47
54
|
// Extra fields from skill_usage
|
|
48
55
|
query?: string;
|
|
49
56
|
skill_path?: string;
|
|
@@ -84,6 +91,17 @@ function safeWrite(label: string, fn: (db: Database) => void): boolean {
|
|
|
84
91
|
}
|
|
85
92
|
}
|
|
86
93
|
|
|
94
|
+
function safeWriteResult<T>(label: string, fn: (db: Database) => T): T | null {
|
|
95
|
+
try {
|
|
96
|
+
return fn(getDb());
|
|
97
|
+
} catch (err) {
|
|
98
|
+
if (process.env.DEBUG || process.env.NODE_ENV === "development") {
|
|
99
|
+
console.error(`[direct-write] ${label} failed:`, err);
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
87
105
|
// -- Canonical record dispatcher -----------------------------------------------
|
|
88
106
|
|
|
89
107
|
export function writeCanonicalToDb(record: CanonicalRecord): boolean {
|
|
@@ -339,6 +357,41 @@ export function writeQueryToDb(record: {
|
|
|
339
357
|
});
|
|
340
358
|
}
|
|
341
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
|
+
|
|
342
395
|
export function writeImprovementSignalToDb(record: {
|
|
343
396
|
timestamp: string;
|
|
344
397
|
session_id: string;
|
|
@@ -377,7 +430,7 @@ export function updateSignalConsumed(
|
|
|
377
430
|
signalType: string,
|
|
378
431
|
runId: string,
|
|
379
432
|
): boolean {
|
|
380
|
-
|
|
433
|
+
const result = safeWriteResult("signal-consumed", (db) =>
|
|
381
434
|
getStmt(
|
|
382
435
|
db,
|
|
383
436
|
"signal-consumed",
|
|
@@ -386,8 +439,9 @@ export function updateSignalConsumed(
|
|
|
386
439
|
SET consumed = 1, consumed_at = ?, consumed_by_run = ?
|
|
387
440
|
WHERE session_id = ? AND query = ? AND signal_type = ? AND consumed = 0
|
|
388
441
|
`,
|
|
389
|
-
).run(new Date().toISOString(), runId, sessionId, query, signalType)
|
|
390
|
-
|
|
442
|
+
).run(new Date().toISOString(), runId, sessionId, query, signalType),
|
|
443
|
+
);
|
|
444
|
+
return result?.changes > 0;
|
|
391
445
|
}
|
|
392
446
|
|
|
393
447
|
// -- Internal insert helpers (used by cached statements) ----------------------
|
|
@@ -400,8 +454,8 @@ function insertSession(db: Database, s: CanonicalSessionRecord): void {
|
|
|
400
454
|
INSERT INTO sessions
|
|
401
455
|
(session_id, started_at, ended_at, platform, model, completion_status,
|
|
402
456
|
source_session_kind, agent_cli, workspace_path, repo_remote, branch,
|
|
403
|
-
schema_version, normalized_at)
|
|
404
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
457
|
+
schema_version, normalized_at, normalizer_version, capture_mode, raw_source_ref)
|
|
458
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
405
459
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
406
460
|
platform = CASE
|
|
407
461
|
WHEN sessions.platform IS NULL OR sessions.platform = 'unknown'
|
|
@@ -416,7 +470,10 @@ function insertSession(db: Database, s: CanonicalSessionRecord): void {
|
|
|
416
470
|
agent_cli = COALESCE(sessions.agent_cli, excluded.agent_cli),
|
|
417
471
|
repo_remote = COALESCE(sessions.repo_remote, excluded.repo_remote),
|
|
418
472
|
branch = COALESCE(sessions.branch, excluded.branch),
|
|
419
|
-
workspace_path = COALESCE(sessions.workspace_path, excluded.workspace_path)
|
|
473
|
+
workspace_path = COALESCE(sessions.workspace_path, excluded.workspace_path),
|
|
474
|
+
normalizer_version = COALESCE(excluded.normalizer_version, sessions.normalizer_version),
|
|
475
|
+
capture_mode = COALESCE(excluded.capture_mode, sessions.capture_mode),
|
|
476
|
+
raw_source_ref = COALESCE(excluded.raw_source_ref, sessions.raw_source_ref)
|
|
420
477
|
`,
|
|
421
478
|
).run(
|
|
422
479
|
s.session_id,
|
|
@@ -432,6 +489,9 @@ function insertSession(db: Database, s: CanonicalSessionRecord): void {
|
|
|
432
489
|
s.branch ?? null,
|
|
433
490
|
s.schema_version,
|
|
434
491
|
s.normalized_at,
|
|
492
|
+
s.normalizer_version ?? null,
|
|
493
|
+
s.capture_mode ?? null,
|
|
494
|
+
s.raw_source_ref ? JSON.stringify(s.raw_source_ref) : null,
|
|
435
495
|
);
|
|
436
496
|
}
|
|
437
497
|
|
|
@@ -441,8 +501,9 @@ function insertPrompt(db: Database, p: CanonicalPromptRecord): void {
|
|
|
441
501
|
"prompt",
|
|
442
502
|
`
|
|
443
503
|
INSERT OR IGNORE INTO prompts
|
|
444
|
-
(prompt_id, session_id, occurred_at, prompt_kind, is_actionable, prompt_index, prompt_text
|
|
445
|
-
|
|
504
|
+
(prompt_id, session_id, occurred_at, prompt_kind, is_actionable, prompt_index, prompt_text,
|
|
505
|
+
schema_version, platform, normalized_at, normalizer_version, capture_mode, raw_source_ref)
|
|
506
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
446
507
|
`,
|
|
447
508
|
).run(
|
|
448
509
|
p.prompt_id,
|
|
@@ -452,6 +513,12 @@ function insertPrompt(db: Database, p: CanonicalPromptRecord): void {
|
|
|
452
513
|
p.is_actionable ? 1 : 0,
|
|
453
514
|
p.prompt_index ?? null,
|
|
454
515
|
p.prompt_text,
|
|
516
|
+
p.schema_version ?? null,
|
|
517
|
+
p.platform ?? null,
|
|
518
|
+
p.normalized_at ?? null,
|
|
519
|
+
p.normalizer_version ?? null,
|
|
520
|
+
p.capture_mode ?? null,
|
|
521
|
+
p.raw_source_ref ? JSON.stringify(p.raw_source_ref) : null,
|
|
455
522
|
);
|
|
456
523
|
}
|
|
457
524
|
|
|
@@ -483,8 +550,9 @@ function insertSkillInvocation(
|
|
|
483
550
|
INSERT OR IGNORE INTO skill_invocations
|
|
484
551
|
(skill_invocation_id, session_id, occurred_at, skill_name, invocation_mode,
|
|
485
552
|
triggered, confidence, tool_name, matched_prompt_id, agent_type,
|
|
486
|
-
query, skill_path, skill_scope, source
|
|
487
|
-
|
|
553
|
+
query, skill_path, skill_scope, source,
|
|
554
|
+
schema_version, platform, normalized_at, normalizer_version, capture_mode, raw_source_ref)
|
|
555
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
488
556
|
`,
|
|
489
557
|
).run(
|
|
490
558
|
si.skill_invocation_id,
|
|
@@ -501,6 +569,12 @@ function insertSkillInvocation(
|
|
|
501
569
|
ext.skill_path ?? null,
|
|
502
570
|
ext.skill_scope ?? null,
|
|
503
571
|
ext.source ?? null,
|
|
572
|
+
si.schema_version ?? null,
|
|
573
|
+
si.platform ?? null,
|
|
574
|
+
si.normalized_at ?? null,
|
|
575
|
+
ext.normalizer_version ?? null,
|
|
576
|
+
ext.capture_mode ?? null,
|
|
577
|
+
ext.raw_source_ref ? JSON.stringify(ext.raw_source_ref) : null,
|
|
504
578
|
);
|
|
505
579
|
}
|
|
506
580
|
|
|
@@ -512,8 +586,9 @@ function insertExecutionFact(db: Database, ef: CanonicalExecutionFactRecord): vo
|
|
|
512
586
|
INSERT INTO execution_facts
|
|
513
587
|
(session_id, occurred_at, prompt_id, tool_calls_json, total_tool_calls,
|
|
514
588
|
assistant_turns, errors_encountered, input_tokens, output_tokens,
|
|
515
|
-
duration_ms, completion_status
|
|
516
|
-
|
|
589
|
+
duration_ms, completion_status,
|
|
590
|
+
schema_version, platform, normalized_at, normalizer_version, capture_mode, raw_source_ref)
|
|
591
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
517
592
|
`,
|
|
518
593
|
).run(
|
|
519
594
|
ef.session_id,
|
|
@@ -527,5 +602,11 @@ function insertExecutionFact(db: Database, ef: CanonicalExecutionFactRecord): vo
|
|
|
527
602
|
ef.output_tokens ?? null,
|
|
528
603
|
ef.duration_ms ?? null,
|
|
529
604
|
ef.completion_status ?? null,
|
|
605
|
+
ef.schema_version ?? null,
|
|
606
|
+
ef.platform ?? null,
|
|
607
|
+
ef.normalized_at ?? null,
|
|
608
|
+
ef.normalizer_version ?? null,
|
|
609
|
+
ef.capture_mode ?? null,
|
|
610
|
+
ef.raw_source_ref ? JSON.stringify(ef.raw_source_ref) : null,
|
|
530
611
|
);
|
|
531
612
|
}
|
|
@@ -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,
|