selftune 0.2.9 → 0.2.12
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-4_dAY17K.js +16 -0
- package/apps/local-dashboard/dist/assets/index-BxV5WZHc.css +2 -0
- package/apps/local-dashboard/dist/assets/rolldown-runtime-Dw2cE7zH.js +1 -0
- package/apps/local-dashboard/dist/assets/vendor-react-CKkiCskZ.js +11 -0
- package/apps/local-dashboard/dist/assets/vendor-table-pHbDxq36.js +8 -0
- package/apps/local-dashboard/dist/assets/vendor-ui-7xD7fNEU.js +12 -0
- package/apps/local-dashboard/dist/index.html +16 -15
- package/bin/selftune.cjs +1 -1
- package/cli/selftune/activation-rules.ts +1 -0
- package/cli/selftune/alpha-upload/build-payloads.ts +18 -2
- package/cli/selftune/alpha-upload/stage-canonical.ts +94 -0
- 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 +1 -0
- package/cli/selftune/contribute/contribute.ts +1 -0
- package/cli/selftune/cron/setup.ts +2 -2
- package/cli/selftune/dashboard-server.ts +1 -0
- package/cli/selftune/eval/hooks-to-evals.ts +1 -0
- 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 +9 -238
- package/cli/selftune/evolution/evolve-body.ts +93 -6
- package/cli/selftune/evolution/evolve.ts +3 -7
- 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/evolution/rollback.ts +1 -1
- package/cli/selftune/export.ts +1 -0
- package/cli/selftune/grading/grade-session.ts +8 -0
- package/cli/selftune/hooks/auto-activate.ts +1 -0
- package/cli/selftune/hooks/evolution-guard.ts +1 -1
- 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 +121 -29
- package/cli/selftune/localdb/db.ts +1 -0
- package/cli/selftune/localdb/direct-write.ts +39 -0
- package/cli/selftune/localdb/materialize.ts +2 -0
- package/cli/selftune/localdb/queries.ts +53 -0
- package/cli/selftune/localdb/schema.ts +28 -0
- package/cli/selftune/normalization.ts +1 -0
- package/cli/selftune/observability.ts +1 -0
- package/cli/selftune/repair/skill-usage.ts +1 -0
- package/cli/selftune/routes/orchestrate-runs.ts +1 -0
- package/cli/selftune/routes/overview.ts +1 -0
- package/cli/selftune/routes/report.ts +1 -1
- package/cli/selftune/routes/skill-report.ts +2 -1
- package/cli/selftune/status.ts +1 -1
- package/cli/selftune/sync.ts +30 -1
- package/cli/selftune/uninstall.ts +412 -0
- package/cli/selftune/utils/canonical-log.ts +2 -0
- package/cli/selftune/utils/frontmatter.ts +50 -7
- 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 +37 -33
- package/packages/telemetry-contract/fixtures/golden.test.ts +1 -0
- package/packages/telemetry-contract/package.json +1 -1
- package/packages/telemetry-contract/src/schemas.ts +1 -0
- 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 +50 -43
- 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 +20 -25
- 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 +70 -57
- 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 +32 -27
- package/skill/Workflows/Doctor.md +33 -27
- 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 +108 -40
- 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 +14 -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-Bs3Y4ixf.css +0 -1
- package/apps/local-dashboard/dist/assets/index-C4UYGWKr.js +0 -15
- package/apps/local-dashboard/dist/assets/vendor-react-BQH_6WrG.js +0 -60
- package/apps/local-dashboard/dist/assets/vendor-table-dK1QMLq9.js +0 -26
- package/apps/local-dashboard/dist/assets/vendor-ui-CO2mrx6e.js +0 -341
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { execSync } from "node:child_process";
|
|
12
|
-
import {
|
|
13
|
-
import { CANONICAL_LOG, ORCHESTRATE_LOCK, TELEMETRY_LOG } from "../constants.js";
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
14
13
|
|
|
14
|
+
import { CANONICAL_LOG, ORCHESTRATE_LOCK, TELEMETRY_LOG } from "../constants.js";
|
|
15
15
|
import {
|
|
16
16
|
appendCanonicalRecords,
|
|
17
17
|
buildCanonicalExecutionFact,
|
|
@@ -25,6 +25,22 @@ import { parseTranscript } from "../utils/transcript.js";
|
|
|
25
25
|
|
|
26
26
|
const LOCK_STALE_MS = 30 * 60 * 1000;
|
|
27
27
|
|
|
28
|
+
interface ReactiveSpawnDeps {
|
|
29
|
+
spawnOrchestrate?: () => boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function hasFreshOrchestrateLock(lockPath: string): boolean {
|
|
33
|
+
try {
|
|
34
|
+
const lockContent = readFileSync(lockPath, "utf8");
|
|
35
|
+
const lock = JSON.parse(lockContent) as { timestamp?: string };
|
|
36
|
+
if (typeof lock.timestamp !== "string") return false;
|
|
37
|
+
const lockAge = Date.now() - new Date(lock.timestamp).getTime();
|
|
38
|
+
return Number.isFinite(lockAge) && lockAge < LOCK_STALE_MS;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
28
44
|
/**
|
|
29
45
|
* Check for pending improvement signals and spawn a focused orchestrate run
|
|
30
46
|
* in the background if warranted. Fire-and-forget — the hook exits immediately.
|
|
@@ -33,6 +49,7 @@ const LOCK_STALE_MS = 30 * 60 * 1000;
|
|
|
33
49
|
*/
|
|
34
50
|
export async function maybeSpawnReactiveOrchestrate(
|
|
35
51
|
lockPath: string = ORCHESTRATE_LOCK,
|
|
52
|
+
deps: ReactiveSpawnDeps = {},
|
|
36
53
|
): Promise<boolean> {
|
|
37
54
|
try {
|
|
38
55
|
// Read pending signals from SQLite (dynamic import to reduce hook startup cost)
|
|
@@ -42,48 +59,25 @@ export async function maybeSpawnReactiveOrchestrate(
|
|
|
42
59
|
const pending = queryImprovementSignals(db, false);
|
|
43
60
|
if (pending.length === 0) return false;
|
|
44
61
|
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
fd = openSync(lockPath, "wx");
|
|
49
|
-
writeFileSync(fd, JSON.stringify({ timestamp: new Date().toISOString(), pid: process.pid }));
|
|
50
|
-
closeSync(fd);
|
|
51
|
-
} catch (lockErr: unknown) {
|
|
52
|
-
// Lock exists — check if stale
|
|
53
|
-
if ((lockErr as NodeJS.ErrnoException).code === "EEXIST") {
|
|
54
|
-
try {
|
|
55
|
-
const lockContent = readFileSync(lockPath, "utf8");
|
|
56
|
-
const lock = JSON.parse(lockContent);
|
|
57
|
-
const lockAge = Date.now() - new Date(lock.timestamp).getTime();
|
|
58
|
-
if (lockAge < LOCK_STALE_MS) return false; // Active lock, skip
|
|
59
|
-
// Stale lock — override
|
|
60
|
-
writeFileSync(
|
|
61
|
-
lockPath,
|
|
62
|
-
JSON.stringify({ timestamp: new Date().toISOString(), pid: process.pid }),
|
|
63
|
-
);
|
|
64
|
-
} catch {
|
|
65
|
-
return false; // Can't read lock, skip
|
|
66
|
-
}
|
|
67
|
-
} else {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
62
|
+
// Do not pre-claim the orchestrate lock here. The spawned process must
|
|
63
|
+
// acquire its own lock or it will immediately self-block on startup.
|
|
64
|
+
if (hasFreshOrchestrateLock(lockPath)) return false;
|
|
71
65
|
|
|
72
66
|
// Spawn orchestrate in background (fire-and-forget)
|
|
73
67
|
try {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
68
|
+
const spawnOrchestrate =
|
|
69
|
+
deps.spawnOrchestrate ??
|
|
70
|
+
(() => {
|
|
71
|
+
const proc = Bun.spawn(["selftune", "orchestrate", "--max-skills", "2"], {
|
|
72
|
+
stdout: "ignore",
|
|
73
|
+
stderr: "ignore",
|
|
74
|
+
stdin: "ignore",
|
|
75
|
+
});
|
|
76
|
+
proc.unref();
|
|
77
|
+
return true;
|
|
78
|
+
});
|
|
79
|
+
if (!spawnOrchestrate()) return false;
|
|
80
80
|
} catch {
|
|
81
|
-
// Spawn failed — release our lock
|
|
82
|
-
try {
|
|
83
|
-
unlinkSync(lockPath);
|
|
84
|
-
} catch {
|
|
85
|
-
/* ignore */
|
|
86
|
-
}
|
|
87
81
|
return false;
|
|
88
82
|
}
|
|
89
83
|
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { existsSync, readFileSync } from "node:fs";
|
|
15
15
|
import { basename, dirname } from "node:path";
|
|
16
|
+
|
|
16
17
|
import { CANONICAL_LOG, SKILL_LOG } from "../constants.js";
|
|
17
18
|
import {
|
|
18
19
|
appendCanonicalRecord,
|
|
@@ -24,7 +25,6 @@ import {
|
|
|
24
25
|
getLatestPromptIdentity,
|
|
25
26
|
} from "../normalization.js";
|
|
26
27
|
import type { PostToolUsePayload, SkillUsageRecord } from "../types.js";
|
|
27
|
-
|
|
28
28
|
import { classifySkillPath } from "../utils/skill-discovery.js";
|
|
29
29
|
import { getLastUserMessage } from "../utils/transcript.js";
|
|
30
30
|
|
package/cli/selftune/index.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* selftune sync — Sync source-truth telemetry across supported agents
|
|
11
11
|
* selftune orchestrate — Run autonomous core loop (sync → status → evolve → watch)
|
|
12
12
|
* selftune init — Initialize agent identity and config
|
|
13
|
+
* selftune uninstall — Clean removal of all selftune data and config
|
|
13
14
|
* selftune status — Show skill health summary
|
|
14
15
|
* selftune watch — Monitor post-deploy skill health
|
|
15
16
|
* selftune doctor — Run health checks
|
|
@@ -44,6 +45,7 @@ Commands:
|
|
|
44
45
|
sync Sync source-truth telemetry across supported agents
|
|
45
46
|
orchestrate Run autonomous core loop (sync → status → evolve → watch)
|
|
46
47
|
init Initialize agent identity and config
|
|
48
|
+
uninstall Clean removal of all selftune data and config
|
|
47
49
|
status Show skill health summary
|
|
48
50
|
watch Monitor post-deploy skill health
|
|
49
51
|
doctor Run health checks
|
|
@@ -338,6 +340,11 @@ Run 'selftune eval <action> --help' for action-specific options.`);
|
|
|
338
340
|
await cliMain();
|
|
339
341
|
break;
|
|
340
342
|
}
|
|
343
|
+
case "uninstall": {
|
|
344
|
+
const { cliMain } = await import("./uninstall.js");
|
|
345
|
+
await cliMain();
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
341
348
|
case "contribute": {
|
|
342
349
|
const { cliMain } = await import("./contribute/contribute.js");
|
|
343
350
|
await cliMain();
|
|
@@ -464,7 +471,7 @@ Run 'selftune cron <subcommand> --help' for subcommand-specific options.`);
|
|
|
464
471
|
}
|
|
465
472
|
case "sync": {
|
|
466
473
|
const { cliMain } = await import("./sync.js");
|
|
467
|
-
cliMain();
|
|
474
|
+
await cliMain();
|
|
468
475
|
break;
|
|
469
476
|
}
|
|
470
477
|
case "workflows": {
|
|
@@ -606,9 +613,8 @@ Output:
|
|
|
606
613
|
const { readAlphaIdentity } = await import("./alpha-identity.js");
|
|
607
614
|
const { getDb } = await import("./localdb/db.js");
|
|
608
615
|
const { runUploadCycle } = await import("./alpha-upload/index.js");
|
|
609
|
-
const { getSelftuneVersion, readConfiguredAgentType } =
|
|
610
|
-
"./utils/selftune-meta.js"
|
|
611
|
-
);
|
|
616
|
+
const { getSelftuneVersion, readConfiguredAgentType } =
|
|
617
|
+
await import("./utils/selftune-meta.js");
|
|
612
618
|
|
|
613
619
|
const identity = readAlphaIdentity(SELFTUNE_CONFIG_PATH);
|
|
614
620
|
if (!identity?.enrolled) {
|
|
@@ -670,36 +676,39 @@ Output:
|
|
|
670
676
|
}
|
|
671
677
|
case "relink": {
|
|
672
678
|
const { SELFTUNE_CONFIG_PATH } = await import("./constants.js");
|
|
673
|
-
const { readAlphaIdentity, writeAlphaIdentity, generateUserId } =
|
|
674
|
-
"./alpha-identity.js"
|
|
675
|
-
|
|
676
|
-
|
|
679
|
+
const { readAlphaIdentity, writeAlphaIdentity, generateUserId } =
|
|
680
|
+
await import("./alpha-identity.js");
|
|
681
|
+
const { buildVerificationUrl, pollDeviceCode, requestDeviceCode, tryOpenUrl } =
|
|
682
|
+
await import("./auth/device-code.js");
|
|
677
683
|
const { chmodSync } = await import("node:fs");
|
|
678
684
|
|
|
679
685
|
const existingIdentity = readAlphaIdentity(SELFTUNE_CONFIG_PATH);
|
|
680
686
|
process.stderr.write("[alpha relink] Starting device-code authentication flow...\n");
|
|
681
687
|
|
|
682
688
|
const grant = await requestDeviceCode();
|
|
689
|
+
const verificationUrlWithCode = buildVerificationUrl(
|
|
690
|
+
grant.verification_url,
|
|
691
|
+
grant.user_code,
|
|
692
|
+
);
|
|
683
693
|
|
|
684
694
|
console.log(
|
|
685
695
|
JSON.stringify({
|
|
686
696
|
level: "info",
|
|
687
697
|
code: "device_code_issued",
|
|
688
698
|
verification_url: grant.verification_url,
|
|
699
|
+
verification_url_with_code: verificationUrlWithCode,
|
|
689
700
|
user_code: grant.user_code,
|
|
690
701
|
expires_in: grant.expires_in,
|
|
691
|
-
message: `Open ${
|
|
702
|
+
message: `Open ${verificationUrlWithCode} to approve.`,
|
|
692
703
|
}),
|
|
693
704
|
);
|
|
694
705
|
|
|
695
706
|
// Try to open browser
|
|
696
|
-
|
|
697
|
-
const url = `${grant.verification_url}?code=${grant.user_code}`;
|
|
698
|
-
Bun.spawn(["open", url], { stdout: "ignore", stderr: "ignore" });
|
|
707
|
+
if (tryOpenUrl(verificationUrlWithCode)) {
|
|
699
708
|
process.stderr.write("[alpha relink] Browser opened. Waiting for approval...\n");
|
|
700
|
-
}
|
|
709
|
+
} else {
|
|
701
710
|
process.stderr.write(
|
|
702
|
-
|
|
711
|
+
`[alpha relink] Could not open browser. Visit ${verificationUrlWithCode} manually.\n`,
|
|
703
712
|
);
|
|
704
713
|
}
|
|
705
714
|
|
|
@@ -25,6 +25,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
|
25
25
|
import { homedir } from "node:os";
|
|
26
26
|
import { basename, join } from "node:path";
|
|
27
27
|
import { parseArgs } from "node:util";
|
|
28
|
+
|
|
28
29
|
import { CANONICAL_LOG, QUERY_LOG, SKILL_LOG, TELEMETRY_LOG } from "../constants.js";
|
|
29
30
|
import {
|
|
30
31
|
appendCanonicalRecords,
|
|
@@ -25,6 +25,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
|
25
25
|
import { homedir } from "node:os";
|
|
26
26
|
import { basename, join } from "node:path";
|
|
27
27
|
import { parseArgs } from "node:util";
|
|
28
|
+
|
|
28
29
|
import { CANONICAL_LOG, QUERY_LOG, SKILL_LOG, TELEMETRY_LOG } from "../constants.js";
|
|
29
30
|
import {
|
|
30
31
|
appendCanonicalRecords,
|
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
|
// ---------------------------------------------------------------------------
|
|
@@ -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
|
-
|
|
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 ${
|
|
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
|
-
|
|
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
|
-
}
|
|
571
|
-
process.stderr.write(
|
|
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
|
-
|
|
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,
|