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
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
</head>
|
|
14
|
-
<body>
|
|
15
|
-
|
|
16
|
-
</body>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>selftune — Dashboard</title>
|
|
7
|
+
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-BZVLv70T.js"></script>
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BXP54cYo.js">
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-ui-CWU0d1wd.js">
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-table-DTF_SXoy.js">
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Bs3Y4ixf.css">
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<div id="root"></div>
|
|
16
|
+
</body>
|
|
17
17
|
</html>
|
package/bin/selftune.cjs
CHANGED
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
* Default activation rules for the auto-activate hook.
|
|
3
3
|
*
|
|
4
4
|
* Each rule evaluates session context and returns a suggestion string
|
|
5
|
-
* (or null if the rule doesn't fire). Rules must be pure functions
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* (or null if the rule doesn't fire). Rules must be pure functions —
|
|
6
|
+
* no network calls, no imports from evolution/monitoring/grading layers.
|
|
7
|
+
*
|
|
8
|
+
* SQLite is the default read path for log data. JSONL fallback is used
|
|
9
|
+
* only when context paths differ from the well-known constants
|
|
10
|
+
* (test/custom-path override).
|
|
8
11
|
*/
|
|
9
12
|
|
|
10
13
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
11
14
|
import { dirname, join } from "node:path";
|
|
15
|
+
|
|
12
16
|
import { EVOLUTION_AUDIT_LOG, QUERY_LOG } from "./constants.js";
|
|
13
17
|
import { getDb } from "./localdb/db.js";
|
|
14
18
|
import { queryEvolutionAudit, queryQueryLog, querySkillUsageRecords } from "./localdb/queries.js";
|
|
@@ -16,34 +20,44 @@ import type { ActivationContext, ActivationRule } from "./types.js";
|
|
|
16
20
|
import { readJsonl } from "./utils/jsonl.js";
|
|
17
21
|
|
|
18
22
|
// ---------------------------------------------------------------------------
|
|
19
|
-
// Rule: post-session diagnostic
|
|
23
|
+
// Rule: post-session diagnostic (SQLite-first; JSONL for test/custom paths)
|
|
20
24
|
// ---------------------------------------------------------------------------
|
|
21
25
|
|
|
22
26
|
const postSessionDiagnostic: ActivationRule = {
|
|
23
27
|
id: "post-session-diagnostic",
|
|
24
28
|
description: "Suggest `selftune last` when session has >2 unmatched queries",
|
|
25
29
|
evaluate(ctx: ActivationContext): string | null {
|
|
26
|
-
// Count queries for this session
|
|
30
|
+
// Count queries for this session — SQLite is the default path
|
|
27
31
|
let queries: Array<{ session_id: string; query: string }>;
|
|
28
32
|
if (ctx.query_log_path === QUERY_LOG) {
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
try {
|
|
34
|
+
const db = getDb();
|
|
35
|
+
queries = queryQueryLog(db) as Array<{ session_id: string; query: string }>;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
31
39
|
} else {
|
|
40
|
+
// test/custom-path fallback
|
|
32
41
|
queries = readJsonl<{ session_id: string; query: string }>(ctx.query_log_path);
|
|
33
42
|
}
|
|
34
43
|
const sessionQueries = queries.filter((q) => q.session_id === ctx.session_id);
|
|
35
44
|
|
|
36
45
|
if (sessionQueries.length === 0) return null;
|
|
37
46
|
|
|
38
|
-
// Count skill usages for this session
|
|
39
|
-
const skillLogPath = join(dirname(ctx.query_log_path), "skill_usage_log.jsonl");
|
|
47
|
+
// Count skill usages for this session — SQLite is the default path
|
|
40
48
|
let skillUsages: Array<{ session_id: string }>;
|
|
41
49
|
if (ctx.query_log_path === QUERY_LOG) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
(
|
|
45
|
-
|
|
50
|
+
try {
|
|
51
|
+
const db = getDb();
|
|
52
|
+
skillUsages = (querySkillUsageRecords(db) as Array<{ session_id: string }>).filter(
|
|
53
|
+
(s) => s.session_id === ctx.session_id,
|
|
54
|
+
);
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
46
58
|
} else {
|
|
59
|
+
// test/custom-path fallback
|
|
60
|
+
const skillLogPath = join(dirname(ctx.query_log_path), "skill_usage_log.jsonl");
|
|
47
61
|
skillUsages = existsSync(skillLogPath)
|
|
48
62
|
? readJsonl<{ session_id: string }>(skillLogPath).filter(
|
|
49
63
|
(s) => s.session_id === ctx.session_id,
|
|
@@ -100,7 +114,7 @@ const gradingThresholdBreach: ActivationRule = {
|
|
|
100
114
|
};
|
|
101
115
|
|
|
102
116
|
// ---------------------------------------------------------------------------
|
|
103
|
-
// Rule: stale evolution
|
|
117
|
+
// Rule: stale evolution (SQLite-first; JSONL for test/custom paths)
|
|
104
118
|
// ---------------------------------------------------------------------------
|
|
105
119
|
|
|
106
120
|
const staleEvolution: ActivationRule = {
|
|
@@ -110,12 +124,17 @@ const staleEvolution: ActivationRule = {
|
|
|
110
124
|
evaluate(ctx: ActivationContext): string | null {
|
|
111
125
|
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
|
|
112
126
|
|
|
113
|
-
// Check last evolution timestamp
|
|
127
|
+
// Check last evolution timestamp — SQLite is the default path
|
|
114
128
|
let auditEntries: Array<{ timestamp: string; action: string }>;
|
|
115
129
|
if (ctx.evolution_audit_log_path === EVOLUTION_AUDIT_LOG) {
|
|
116
|
-
|
|
117
|
-
|
|
130
|
+
try {
|
|
131
|
+
const db = getDb();
|
|
132
|
+
auditEntries = queryEvolutionAudit(db) as Array<{ timestamp: string; action: string }>;
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
118
136
|
} else {
|
|
137
|
+
// test/custom-path fallback
|
|
119
138
|
auditEntries = readJsonl<{ timestamp: string; action: string }>(ctx.evolution_audit_log_path);
|
|
120
139
|
}
|
|
121
140
|
|
|
@@ -126,7 +145,7 @@ const staleEvolution: ActivationRule = {
|
|
|
126
145
|
: null;
|
|
127
146
|
}
|
|
128
147
|
|
|
129
|
-
const lastEntry = auditEntries[
|
|
148
|
+
const lastEntry = auditEntries[0]; // queryEvolutionAudit returns DESC order
|
|
130
149
|
const lastTimestamp = new Date(lastEntry.timestamp).getTime();
|
|
131
150
|
const ageMs = Date.now() - lastTimestamp;
|
|
132
151
|
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { getAlphaLinkState } from "./alpha-identity.js";
|
|
2
2
|
import type { AgentCommandGuidance, AlphaIdentity, AlphaLinkState } from "./types.js";
|
|
3
3
|
|
|
4
|
-
function
|
|
5
|
-
|
|
4
|
+
function sanitizeAlphaEmail(email?: string): string | null {
|
|
5
|
+
const trimmed = email?.trim();
|
|
6
|
+
if (!trimmed) return null;
|
|
7
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) return null;
|
|
8
|
+
return trimmed;
|
|
6
9
|
}
|
|
7
10
|
|
|
8
|
-
function buildAlphaInitCommand(options?: {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const parts = ["selftune", "init", "--alpha", "--alpha-email", emailArg(options?.email)];
|
|
14
|
-
if (options?.includeKey) {
|
|
15
|
-
parts.push("--alpha-key", "<st_live_key>");
|
|
11
|
+
function buildAlphaInitCommand(options?: { email?: string; force?: boolean }): string {
|
|
12
|
+
const parts = ["selftune", "init", "--alpha"];
|
|
13
|
+
const email = sanitizeAlphaEmail(options?.email);
|
|
14
|
+
if (email) {
|
|
15
|
+
parts.push("--alpha-email", email);
|
|
16
16
|
}
|
|
17
17
|
if (options?.force) {
|
|
18
18
|
parts.push("--force");
|
|
@@ -44,24 +44,24 @@ export function getAlphaGuidanceForState(
|
|
|
44
44
|
case "not_linked":
|
|
45
45
|
return buildGuidance(
|
|
46
46
|
"alpha_cloud_link_required",
|
|
47
|
-
"Alpha upload is not linked.
|
|
48
|
-
buildAlphaInitCommand({ email: options?.email
|
|
47
|
+
"Alpha upload is not linked. Run the init command with --alpha to authenticate via browser.",
|
|
48
|
+
buildAlphaInitCommand({ email: options?.email }),
|
|
49
49
|
true,
|
|
50
50
|
["selftune status", "selftune doctor"],
|
|
51
51
|
);
|
|
52
52
|
case "linked_not_enrolled":
|
|
53
53
|
return buildGuidance(
|
|
54
54
|
"alpha_enrollment_incomplete",
|
|
55
|
-
"Cloud account is linked but alpha enrollment is incomplete.
|
|
56
|
-
buildAlphaInitCommand({ email: options?.email,
|
|
55
|
+
"Cloud account is linked but alpha enrollment is incomplete. Re-run init with --alpha to complete enrollment via browser.",
|
|
56
|
+
buildAlphaInitCommand({ email: options?.email, force: true }),
|
|
57
57
|
true,
|
|
58
58
|
["selftune status", "selftune doctor"],
|
|
59
59
|
);
|
|
60
60
|
case "enrolled_no_credential":
|
|
61
61
|
return buildGuidance(
|
|
62
62
|
"alpha_credential_required",
|
|
63
|
-
"Alpha enrollment exists, but the local upload credential is missing or invalid.",
|
|
64
|
-
buildAlphaInitCommand({ email: options?.email,
|
|
63
|
+
"Alpha enrollment exists, but the local upload credential is missing or invalid. Re-run init with --alpha to re-authenticate via browser.",
|
|
64
|
+
buildAlphaInitCommand({ email: options?.email, force: true }),
|
|
65
65
|
true,
|
|
66
66
|
["selftune status", "selftune doctor"],
|
|
67
67
|
);
|
|
@@ -89,8 +89,7 @@ export function isValidApiKeyFormat(key: string): boolean {
|
|
|
89
89
|
* enrolled, valid api_key -> "ready"
|
|
90
90
|
*
|
|
91
91
|
* cloud_user_id enriches the identity (confirms cloud link) but is not a gate.
|
|
92
|
-
* The
|
|
93
|
-
* that is a valid "ready" state. cloud_user_id can be backfilled later.
|
|
92
|
+
* The device-code flow sets both api_key and cloud_user_id simultaneously.
|
|
94
93
|
*/
|
|
95
94
|
export function getAlphaLinkState(identity: AlphaIdentity | null): AlphaLinkState {
|
|
96
95
|
if (!identity) return "not_linked";
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { Database } from "bun:sqlite";
|
|
13
|
+
|
|
13
14
|
import type { CanonicalRecord } from "@selftune/telemetry-contract";
|
|
15
|
+
|
|
14
16
|
import { buildPushPayloadV2 } from "../canonical-export.js";
|
|
15
17
|
import type { EvolutionEvidenceEntry } from "../types.js";
|
|
16
18
|
|
|
@@ -74,6 +76,8 @@ export function buildV2PushPayload(
|
|
|
74
76
|
const canonicalRecords: CanonicalRecord[] = [];
|
|
75
77
|
const evidenceEntries: EvolutionEvidenceEntry[] = [];
|
|
76
78
|
const orchestrateRuns: Record<string, unknown>[] = [];
|
|
79
|
+
const gradingResults: Record<string, unknown>[] = [];
|
|
80
|
+
const improvementSignals: Record<string, unknown>[] = [];
|
|
77
81
|
let lastParsedSeq: number | null = null;
|
|
78
82
|
let hitMalformedRow = false;
|
|
79
83
|
|
|
@@ -118,6 +122,10 @@ export function buildV2PushPayload(
|
|
|
118
122
|
} else if (row.record_kind === "orchestrate_run") {
|
|
119
123
|
// Orchestrate run records -- pass through as-is
|
|
120
124
|
orchestrateRuns.push(parsed);
|
|
125
|
+
} else if (row.record_kind === "grading_result") {
|
|
126
|
+
gradingResults.push(parsed);
|
|
127
|
+
} else if (row.record_kind === "improvement_signal") {
|
|
128
|
+
improvementSignals.push(parsed);
|
|
121
129
|
} else {
|
|
122
130
|
// Canonical telemetry records -- pass through as-is
|
|
123
131
|
canonicalRecords.push(parsed as unknown as CanonicalRecord);
|
|
@@ -130,12 +138,20 @@ export function buildV2PushPayload(
|
|
|
130
138
|
if (
|
|
131
139
|
canonicalRecords.length === 0 &&
|
|
132
140
|
evidenceEntries.length === 0 &&
|
|
133
|
-
orchestrateRuns.length === 0
|
|
141
|
+
orchestrateRuns.length === 0 &&
|
|
142
|
+
gradingResults.length === 0 &&
|
|
143
|
+
improvementSignals.length === 0
|
|
134
144
|
) {
|
|
135
145
|
return null;
|
|
136
146
|
}
|
|
137
147
|
|
|
138
|
-
const payload = buildPushPayloadV2(
|
|
148
|
+
const payload = buildPushPayloadV2(
|
|
149
|
+
canonicalRecords,
|
|
150
|
+
evidenceEntries,
|
|
151
|
+
orchestrateRuns,
|
|
152
|
+
gradingResults,
|
|
153
|
+
improvementSignals,
|
|
154
|
+
);
|
|
139
155
|
if (lastParsedSeq === null) {
|
|
140
156
|
return null;
|
|
141
157
|
}
|
|
@@ -165,8 +165,8 @@ export async function flushQueue(
|
|
|
165
165
|
if (isAuthError(status)) {
|
|
166
166
|
const authMessage =
|
|
167
167
|
status === 401
|
|
168
|
-
? "Authentication failed: invalid or missing API key. Run 'selftune init --alpha --alpha-
|
|
169
|
-
: "Authorization denied: your API key does not have permission to upload. Run 'selftune doctor' to verify enrollment and cloud link, then re-run 'selftune init --alpha --alpha-email <email> --
|
|
168
|
+
? "Authentication failed: invalid or missing API key. Run 'selftune init --alpha --alpha-email <email>' to re-authenticate via browser."
|
|
169
|
+
: "Authorization denied: your API key does not have permission to upload. Run 'selftune doctor' to verify enrollment and cloud link, then re-run 'selftune init --alpha --alpha-email <email> --force' to re-authenticate.";
|
|
170
170
|
markFailedSafely(authMessage);
|
|
171
171
|
summary.failed++;
|
|
172
172
|
succeeded = true;
|
|
@@ -11,10 +11,18 @@
|
|
|
11
11
|
|
|
12
12
|
import type { Database } from "bun:sqlite";
|
|
13
13
|
import { createHash } from "node:crypto";
|
|
14
|
+
|
|
14
15
|
import type { CanonicalRecord } from "@selftune/telemetry-contract";
|
|
15
16
|
import { isCanonicalRecord } from "@selftune/telemetry-contract";
|
|
17
|
+
|
|
16
18
|
import { CANONICAL_LOG } from "../constants.js";
|
|
17
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
getOrchestrateRuns,
|
|
21
|
+
queryCanonicalRecordsForStaging,
|
|
22
|
+
queryEvolutionEvidence,
|
|
23
|
+
queryGradingResults,
|
|
24
|
+
queryImprovementSignals,
|
|
25
|
+
} from "../localdb/queries.js";
|
|
18
26
|
import { readJsonl } from "../utils/jsonl.js";
|
|
19
27
|
|
|
20
28
|
// -- Helpers ------------------------------------------------------------------
|
|
@@ -43,6 +51,22 @@ export function generateEvidenceId(record: Record<string, unknown>): string {
|
|
|
43
51
|
return `ev_${createHash("sha256").update(key).digest("hex").slice(0, 16)}`;
|
|
44
52
|
}
|
|
45
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Generate a deterministic grading_id from the result's natural key.
|
|
56
|
+
*/
|
|
57
|
+
export function generateGradingId(record: Record<string, unknown>): string {
|
|
58
|
+
const key = `${record.session_id}:${record.skill_name}:${record.graded_at}`;
|
|
59
|
+
return `gr_${createHash("sha256").update(key).digest("hex").slice(0, 16)}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Generate a deterministic signal_id from an improvement signal's natural key.
|
|
64
|
+
*/
|
|
65
|
+
export function generateSignalId(record: Record<string, unknown>): string {
|
|
66
|
+
const key = `${record.session_id}:${record.query}:${record.signal_type}:${record.timestamp}`;
|
|
67
|
+
return `sig_${createHash("sha256").update(key).digest("hex").slice(0, 16)}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
46
70
|
/**
|
|
47
71
|
* Enrich a raw parsed record: if it is an execution_fact missing
|
|
48
72
|
* execution_fact_id, inject a deterministic one.
|
|
@@ -144,8 +168,13 @@ export function stageCanonicalRecords(db: Database, logPath: string = CANONICAL_
|
|
|
144
168
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
145
169
|
`);
|
|
146
170
|
|
|
147
|
-
// 1. Stage canonical records from JSONL (
|
|
148
|
-
const records =
|
|
171
|
+
// 1. Stage canonical records from SQLite (default) or JSONL (custom logPath override)
|
|
172
|
+
const records: CanonicalRecord[] =
|
|
173
|
+
logPath === CANONICAL_LOG
|
|
174
|
+
? (queryCanonicalRecordsForStaging(db)
|
|
175
|
+
.map(enrichRecord)
|
|
176
|
+
.filter(isCanonicalRecord) as CanonicalRecord[])
|
|
177
|
+
: readAndEnrichCanonicalRecords(logPath);
|
|
149
178
|
for (const record of records) {
|
|
150
179
|
const recordId = extractRecordId(record);
|
|
151
180
|
const result = stmt.run(
|
|
@@ -238,5 +267,79 @@ export function stageCanonicalRecords(db: Database, logPath: string = CANONICAL_
|
|
|
238
267
|
}
|
|
239
268
|
}
|
|
240
269
|
|
|
270
|
+
// 4. Stage grading results from SQLite
|
|
271
|
+
try {
|
|
272
|
+
const gradingResults = queryGradingResults(db);
|
|
273
|
+
for (const gr of gradingResults) {
|
|
274
|
+
const recordJson = JSON.stringify({
|
|
275
|
+
grading_id: gr.grading_id,
|
|
276
|
+
session_id: gr.session_id,
|
|
277
|
+
skill_name: gr.skill_name,
|
|
278
|
+
transcript_path: gr.transcript_path,
|
|
279
|
+
graded_at: gr.graded_at,
|
|
280
|
+
pass_rate: gr.pass_rate,
|
|
281
|
+
mean_score: gr.mean_score,
|
|
282
|
+
score_std_dev: gr.score_std_dev,
|
|
283
|
+
passed_count: gr.passed_count,
|
|
284
|
+
failed_count: gr.failed_count,
|
|
285
|
+
total_count: gr.total_count,
|
|
286
|
+
expectations_json: gr.expectations_json,
|
|
287
|
+
claims_json: gr.claims_json,
|
|
288
|
+
eval_feedback_json: gr.eval_feedback_json,
|
|
289
|
+
failure_feedback_json: gr.failure_feedback_json,
|
|
290
|
+
execution_metrics_json: gr.execution_metrics_json,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const result = stmt.run(
|
|
294
|
+
"grading_result",
|
|
295
|
+
gr.grading_id,
|
|
296
|
+
recordJson,
|
|
297
|
+
gr.session_id,
|
|
298
|
+
null, // no prompt_id
|
|
299
|
+
gr.graded_at,
|
|
300
|
+
now,
|
|
301
|
+
);
|
|
302
|
+
if (result.changes > 0) staged++;
|
|
303
|
+
}
|
|
304
|
+
} catch (err) {
|
|
305
|
+
if (process.env.DEBUG || process.env.NODE_ENV === "development") {
|
|
306
|
+
console.error("[stage-canonical] failed to stage grading results:", err);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 5. Stage improvement signals from SQLite
|
|
311
|
+
try {
|
|
312
|
+
const signals = queryImprovementSignals(db);
|
|
313
|
+
for (const sig of signals) {
|
|
314
|
+
const signalId = generateSignalId(sig);
|
|
315
|
+
const recordJson = JSON.stringify({
|
|
316
|
+
signal_id: signalId,
|
|
317
|
+
timestamp: sig.timestamp,
|
|
318
|
+
session_id: sig.session_id,
|
|
319
|
+
query: sig.query,
|
|
320
|
+
signal_type: sig.signal_type,
|
|
321
|
+
mentioned_skill: sig.mentioned_skill,
|
|
322
|
+
consumed: sig.consumed,
|
|
323
|
+
consumed_at: sig.consumed_at,
|
|
324
|
+
consumed_by_run: sig.consumed_by_run,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const result = stmt.run(
|
|
328
|
+
"improvement_signal",
|
|
329
|
+
signalId,
|
|
330
|
+
recordJson,
|
|
331
|
+
sig.session_id,
|
|
332
|
+
null, // no prompt_id
|
|
333
|
+
sig.timestamp,
|
|
334
|
+
now,
|
|
335
|
+
);
|
|
336
|
+
if (result.changes > 0) staged++;
|
|
337
|
+
}
|
|
338
|
+
} catch (err) {
|
|
339
|
+
if (process.env.DEBUG || process.env.NODE_ENV === "development") {
|
|
340
|
+
console.error("[stage-canonical] failed to stage improvement signals:", err);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
241
344
|
return staged;
|
|
242
345
|
}
|
|
@@ -22,6 +22,38 @@ export interface DeviceCodeResult {
|
|
|
22
22
|
org_id: string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export function tryOpenUrl(url: string): boolean {
|
|
26
|
+
const command =
|
|
27
|
+
process.platform === "darwin"
|
|
28
|
+
? ["open", url]
|
|
29
|
+
: process.platform === "linux"
|
|
30
|
+
? ["xdg-open", url]
|
|
31
|
+
: process.platform === "win32"
|
|
32
|
+
? ["cmd", "/c", "start", "", url]
|
|
33
|
+
: null;
|
|
34
|
+
|
|
35
|
+
if (!command) return false;
|
|
36
|
+
if (process.platform !== "win32" && !Bun.which(command[0])) return false;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
Bun.spawn(command, { stdout: "ignore", stderr: "ignore" });
|
|
40
|
+
return true;
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function buildVerificationUrl(verificationUrl: string, userCode: string): string {
|
|
47
|
+
try {
|
|
48
|
+
const url = new URL(verificationUrl);
|
|
49
|
+
url.searchParams.set("code", userCode);
|
|
50
|
+
return url.toString();
|
|
51
|
+
} catch {
|
|
52
|
+
const separator = verificationUrl.includes("?") ? "&" : "?";
|
|
53
|
+
return `${verificationUrl}${separator}code=${encodeURIComponent(userCode)}`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
25
57
|
/**
|
|
26
58
|
* Derive the cloud API base URL from SELFTUNE_ALPHA_ENDPOINT.
|
|
27
59
|
* The endpoint is the push URL (e.g., https://api.selftune.dev/api/v1/push).
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
import { spawnSync } from "node:child_process";
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
11
12
|
import { join } from "node:path";
|
|
13
|
+
|
|
12
14
|
import { SELFTUNE_CONFIG_DIR } from "./constants.js";
|
|
13
15
|
|
|
14
16
|
const UPDATE_CHECK_PATH = join(SELFTUNE_CONFIG_DIR, "update-check.json");
|
|
@@ -118,6 +120,16 @@ async function performUpdate(currentVersion: string, latestVersion: string): Pro
|
|
|
118
120
|
console.error(`[selftune] Updated to v${latestVersion}.`);
|
|
119
121
|
// Update cache to reflect new version
|
|
120
122
|
writeCache({ lastCheck: Date.now(), currentVersion: latestVersion, latestVersion });
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const claudeDir = join(homedir(), ".claude");
|
|
126
|
+
if (existsSync(claudeDir)) {
|
|
127
|
+
const { installAgentFiles } = await import("./claude-agents.js");
|
|
128
|
+
installAgentFiles({ force: true });
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
// Non-critical — updated CLI is usable even if agent sync fails
|
|
132
|
+
}
|
|
121
133
|
} else {
|
|
122
134
|
const stderr = result.stderr?.toString().trim();
|
|
123
135
|
console.error(
|
|
@@ -4,6 +4,7 @@ import { randomUUID } from "node:crypto";
|
|
|
4
4
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
|
+
|
|
7
8
|
import { CANONICAL_LOG, CLAUDE_CODE_PROJECTS_DIR } from "./constants.js";
|
|
8
9
|
import {
|
|
9
10
|
buildCanonicalRecordsFromReplay,
|
|
@@ -84,6 +85,8 @@ export function buildPushPayloadV2(
|
|
|
84
85
|
records: CanonicalRecord[],
|
|
85
86
|
evidenceEntries: EvolutionEvidenceEntry[] = [],
|
|
86
87
|
orchestrateRuns: Record<string, unknown>[] = [],
|
|
88
|
+
gradingResults: Record<string, unknown>[] = [],
|
|
89
|
+
improvementSignals: Record<string, unknown>[] = [],
|
|
87
90
|
): Record<string, unknown> {
|
|
88
91
|
const sessions = records.filter((record) => record.record_kind === "session");
|
|
89
92
|
const prompts = records.filter((record) => record.record_kind === "prompt");
|
|
@@ -120,6 +123,8 @@ export function buildPushPayloadV2(
|
|
|
120
123
|
validation_json: entry.validation,
|
|
121
124
|
})),
|
|
122
125
|
orchestrate_runs: orchestrateRuns,
|
|
126
|
+
grading_results: gradingResults,
|
|
127
|
+
improvement_signals: improvementSignals,
|
|
123
128
|
},
|
|
124
129
|
};
|
|
125
130
|
}
|