selftune 0.2.6 → 0.2.8
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 +1 -0
- package/apps/local-dashboard/dist/assets/index-Bk9vSHHd.js +15 -0
- package/apps/local-dashboard/dist/assets/index-CRtLkBTi.css +1 -0
- package/apps/local-dashboard/dist/assets/vendor-react-BQH_6WrG.js +60 -0
- package/apps/local-dashboard/dist/assets/{vendor-table-B7VF2Ipl.js → vendor-table-dK1QMLq9.js} +1 -1
- package/apps/local-dashboard/dist/assets/{vendor-ui-r2k_Ku_V.js → vendor-ui-CO2mrx6e.js} +60 -65
- package/apps/local-dashboard/dist/index.html +5 -5
- package/cli/selftune/activation-rules.ts +30 -9
- package/cli/selftune/agent-guidance.ts +96 -0
- package/cli/selftune/alpha-identity.ts +157 -0
- package/cli/selftune/alpha-upload/build-payloads.ts +151 -0
- package/cli/selftune/alpha-upload/client.ts +113 -0
- package/cli/selftune/alpha-upload/flush.ts +191 -0
- package/cli/selftune/alpha-upload/index.ts +194 -0
- package/cli/selftune/alpha-upload/queue.ts +252 -0
- package/cli/selftune/alpha-upload/stage-canonical.ts +242 -0
- package/cli/selftune/alpha-upload-contract.ts +52 -0
- package/cli/selftune/auth/device-code.ts +110 -0
- package/cli/selftune/auto-update.ts +130 -0
- package/cli/selftune/badge/badge.ts +19 -9
- package/cli/selftune/canonical-export.ts +16 -3
- package/cli/selftune/constants.ts +28 -8
- package/cli/selftune/contribute/bundle.ts +32 -5
- package/cli/selftune/dashboard-contract.ts +32 -1
- package/cli/selftune/dashboard-server.ts +256 -692
- package/cli/selftune/dashboard.ts +1 -1
- package/cli/selftune/eval/baseline.ts +11 -7
- package/cli/selftune/eval/hooks-to-evals.ts +27 -9
- package/cli/selftune/eval/synthetic-evals.ts +54 -1
- package/cli/selftune/evolution/audit.ts +24 -19
- package/cli/selftune/evolution/constitutional.ts +176 -0
- package/cli/selftune/evolution/evidence.ts +18 -13
- package/cli/selftune/evolution/evolve-body.ts +104 -7
- package/cli/selftune/evolution/evolve.ts +195 -22
- package/cli/selftune/evolution/propose-body.ts +18 -1
- package/cli/selftune/evolution/propose-description.ts +27 -2
- package/cli/selftune/evolution/rollback.ts +11 -15
- package/cli/selftune/export.ts +84 -0
- package/cli/selftune/grading/auto-grade.ts +13 -4
- package/cli/selftune/grading/grade-session.ts +16 -6
- package/cli/selftune/hooks/evolution-guard.ts +26 -9
- package/cli/selftune/hooks/prompt-log.ts +23 -9
- package/cli/selftune/hooks/session-stop.ts +78 -15
- package/cli/selftune/hooks/skill-eval.ts +189 -10
- package/cli/selftune/index.ts +274 -2
- package/cli/selftune/ingestors/claude-replay.ts +48 -21
- package/cli/selftune/init.ts +249 -47
- package/cli/selftune/last.ts +7 -7
- package/cli/selftune/localdb/db.ts +90 -10
- package/cli/selftune/localdb/direct-write.ts +531 -0
- package/cli/selftune/localdb/materialize.ts +296 -42
- package/cli/selftune/localdb/queries.ts +325 -32
- package/cli/selftune/localdb/schema.ts +109 -0
- package/cli/selftune/monitoring/watch.ts +26 -8
- package/cli/selftune/normalization.ts +85 -15
- package/cli/selftune/observability.ts +248 -2
- package/cli/selftune/orchestrate.ts +165 -20
- package/cli/selftune/quickstart.ts +34 -10
- package/cli/selftune/repair/skill-usage.ts +12 -2
- package/cli/selftune/routes/actions.ts +77 -0
- package/cli/selftune/routes/badge.ts +66 -0
- package/cli/selftune/routes/doctor.ts +12 -0
- package/cli/selftune/routes/index.ts +14 -0
- package/cli/selftune/routes/orchestrate-runs.ts +13 -0
- package/cli/selftune/routes/overview.ts +14 -0
- package/cli/selftune/routes/report.ts +293 -0
- package/cli/selftune/routes/skill-report.ts +230 -0
- package/cli/selftune/status.ts +203 -7
- package/cli/selftune/sync.ts +13 -1
- package/cli/selftune/types.ts +50 -0
- package/cli/selftune/utils/jsonl.ts +58 -1
- package/cli/selftune/utils/selftune-meta.ts +38 -0
- package/cli/selftune/utils/skill-log.ts +30 -4
- package/cli/selftune/utils/transcript.ts +15 -0
- package/cli/selftune/workflows/workflows.ts +7 -6
- package/package.json +10 -6
- package/packages/telemetry-contract/fixtures/complete-push.ts +184 -0
- package/packages/telemetry-contract/fixtures/evidence-only-push.ts +58 -0
- package/packages/telemetry-contract/fixtures/golden.json +1 -0
- package/packages/telemetry-contract/fixtures/index.ts +4 -0
- package/packages/telemetry-contract/fixtures/partial-push-no-sessions.ts +40 -0
- package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +79 -0
- package/packages/telemetry-contract/package.json +6 -1
- package/packages/telemetry-contract/src/index.ts +1 -0
- package/packages/telemetry-contract/src/schemas.ts +215 -0
- package/packages/telemetry-contract/src/types.ts +3 -1
- package/packages/telemetry-contract/src/validators.ts +3 -1
- package/packages/telemetry-contract/tests/compatibility.test.ts +144 -0
- package/packages/ui/package.json +4 -0
- package/packages/ui/src/components/ActivityTimeline.tsx +61 -29
- package/packages/ui/src/components/section-cards.tsx +31 -14
- package/packages/ui/src/types.ts +1 -0
- package/skill/SKILL.md +214 -174
- package/skill/Workflows/AlphaUpload.md +45 -0
- package/skill/Workflows/Baseline.md +18 -12
- package/skill/Workflows/Composability.md +3 -3
- package/skill/Workflows/Dashboard.md +44 -91
- package/skill/Workflows/Doctor.md +93 -66
- package/skill/Workflows/Evals.md +49 -40
- package/skill/Workflows/Evolve.md +76 -28
- package/skill/Workflows/EvolveBody.md +37 -38
- package/skill/Workflows/Initialize.md +172 -26
- package/skill/Workflows/Orchestrate.md +11 -2
- package/skill/Workflows/Sync.md +23 -0
- package/skill/Workflows/Watch.md +2 -5
- package/skill/agents/diagnosis-analyst.md +163 -0
- package/skill/agents/evolution-reviewer.md +149 -0
- package/skill/agents/integration-guide.md +154 -0
- package/skill/agents/pattern-analyst.md +149 -0
- package/skill/assets/multi-skill-settings.json +1 -1
- package/skill/assets/single-skill-settings.json +1 -1
- package/skill/references/interactive-config.md +39 -0
- package/skill/references/invocation-taxonomy.md +34 -0
- package/skill/references/logs.md +9 -1
- package/skill/references/setup-patterns.md +3 -3
- package/skill/settings_snippet.json +1 -1
- package/apps/local-dashboard/dist/assets/index-C75H1Q3n.css +0 -1
- package/apps/local-dashboard/dist/assets/index-axE4kz3Q.js +0 -15
- package/apps/local-dashboard/dist/assets/vendor-react-U7zYD9Rg.js +0 -60
|
@@ -32,6 +32,11 @@ import {
|
|
|
32
32
|
SKILL_LOG,
|
|
33
33
|
TELEMETRY_LOG,
|
|
34
34
|
} from "../constants.js";
|
|
35
|
+
import {
|
|
36
|
+
writeQueryToDb,
|
|
37
|
+
writeSessionTelemetryToDb,
|
|
38
|
+
writeSkillCheckToDb,
|
|
39
|
+
} from "../localdb/direct-write.js";
|
|
35
40
|
import {
|
|
36
41
|
appendCanonicalRecords,
|
|
37
42
|
buildCanonicalExecutionFact,
|
|
@@ -47,10 +52,9 @@ import type {
|
|
|
47
52
|
CanonicalRecord,
|
|
48
53
|
QueryLogRecord,
|
|
49
54
|
SessionTelemetryRecord,
|
|
50
|
-
SkillUsageRecord,
|
|
51
55
|
TranscriptMetrics,
|
|
52
56
|
} from "../types.js";
|
|
53
|
-
import {
|
|
57
|
+
import { loadMarker, saveMarker } from "../utils/jsonl.js";
|
|
54
58
|
import { isActionableQueryText } from "../utils/query-filter.js";
|
|
55
59
|
import {
|
|
56
60
|
extractActionableUserQueries,
|
|
@@ -136,9 +140,9 @@ export function parseSession(transcriptPath: string): ParsedSession | null {
|
|
|
136
140
|
export function writeSession(
|
|
137
141
|
session: ParsedSession,
|
|
138
142
|
dryRun = false,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
_queryLogPath: string = QUERY_LOG,
|
|
144
|
+
_telemetryLogPath: string = TELEMETRY_LOG,
|
|
145
|
+
_skillLogPath: string = SKILL_LOG,
|
|
142
146
|
canonicalLogPath: string = CANONICAL_LOG,
|
|
143
147
|
): void {
|
|
144
148
|
if (dryRun) {
|
|
@@ -150,7 +154,7 @@ export function writeSession(
|
|
|
150
154
|
return;
|
|
151
155
|
}
|
|
152
156
|
|
|
153
|
-
// Write ONE query record per user query
|
|
157
|
+
// Write ONE query record per user query to SQLite
|
|
154
158
|
for (const uq of session.user_queries) {
|
|
155
159
|
const queryRecord: QueryLogRecord = {
|
|
156
160
|
timestamp: uq.timestamp || session.timestamp,
|
|
@@ -158,10 +162,14 @@ export function writeSession(
|
|
|
158
162
|
query: uq.query,
|
|
159
163
|
source: "claude_code_replay",
|
|
160
164
|
};
|
|
161
|
-
|
|
165
|
+
try {
|
|
166
|
+
writeQueryToDb(queryRecord);
|
|
167
|
+
} catch {
|
|
168
|
+
/* fail-open */
|
|
169
|
+
}
|
|
162
170
|
}
|
|
163
171
|
|
|
164
|
-
// Write ONE telemetry record per session
|
|
172
|
+
// Write ONE telemetry record per session to SQLite
|
|
165
173
|
const telemetry: SessionTelemetryRecord = {
|
|
166
174
|
timestamp: session.timestamp,
|
|
167
175
|
session_id: session.session_id,
|
|
@@ -178,7 +186,11 @@ export function writeSession(
|
|
|
178
186
|
last_user_query: session.metrics.last_user_query,
|
|
179
187
|
source: "claude_code_replay",
|
|
180
188
|
};
|
|
181
|
-
|
|
189
|
+
try {
|
|
190
|
+
writeSessionTelemetryToDb(telemetry);
|
|
191
|
+
} catch {
|
|
192
|
+
/* fail-open */
|
|
193
|
+
}
|
|
182
194
|
|
|
183
195
|
// Write ONE skill record per invoked/triggered skill.
|
|
184
196
|
// Prefer skills_invoked (actual Skill tool calls) for high-confidence records.
|
|
@@ -189,20 +201,33 @@ export function writeSession(
|
|
|
189
201
|
session.user_queries[session.user_queries.length - 1]?.query.trim() ??
|
|
190
202
|
session.metrics.last_user_query.trim();
|
|
191
203
|
|
|
192
|
-
for (
|
|
204
|
+
for (let i = 0; i < skillSource.length; i++) {
|
|
205
|
+
const skillName = skillSource[i];
|
|
193
206
|
const skillQuery = latestActionableQuery;
|
|
194
207
|
if (!isActionableQueryText(skillQuery)) continue;
|
|
195
208
|
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
209
|
+
const { invocation_mode, confidence } = deriveInvocationMode({
|
|
210
|
+
has_skill_tool_call: invoked.length > 0,
|
|
211
|
+
has_skill_md_read: invoked.length === 0,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
writeSkillCheckToDb({
|
|
216
|
+
skill_invocation_id: deriveSkillInvocationId(session.session_id, skillName, i),
|
|
217
|
+
session_id: session.session_id,
|
|
218
|
+
occurred_at: session.timestamp,
|
|
219
|
+
skill_name: skillName,
|
|
220
|
+
invocation_mode,
|
|
221
|
+
triggered: true,
|
|
222
|
+
confidence,
|
|
223
|
+
platform: "claude_code",
|
|
224
|
+
query: skillQuery,
|
|
225
|
+
skill_path: `(claude_code:${skillName})`,
|
|
226
|
+
source: "claude_code_replay",
|
|
227
|
+
});
|
|
228
|
+
} catch {
|
|
229
|
+
/* fail-open */
|
|
230
|
+
}
|
|
206
231
|
}
|
|
207
232
|
|
|
208
233
|
// --- Canonical normalization records (additive) ---
|
|
@@ -233,7 +258,9 @@ export function buildCanonicalRecordsFromReplay(session: ParsedSession): Canonic
|
|
|
233
258
|
records.push(
|
|
234
259
|
buildCanonicalSession({
|
|
235
260
|
...baseInput,
|
|
236
|
-
started_at: session.timestamp,
|
|
261
|
+
started_at: session.metrics.started_at ?? session.timestamp,
|
|
262
|
+
ended_at: session.metrics.ended_at,
|
|
263
|
+
model: session.metrics.model,
|
|
237
264
|
}),
|
|
238
265
|
);
|
|
239
266
|
|
package/cli/selftune/init.ts
CHANGED
|
@@ -12,11 +12,14 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
|
-
|
|
15
|
+
closeSync,
|
|
16
16
|
existsSync,
|
|
17
|
+
fsyncSync,
|
|
17
18
|
mkdirSync,
|
|
19
|
+
openSync,
|
|
18
20
|
readdirSync,
|
|
19
21
|
readFileSync,
|
|
22
|
+
renameSync,
|
|
20
23
|
writeFileSync,
|
|
21
24
|
} from "node:fs";
|
|
22
25
|
import { homedir } from "node:os";
|
|
@@ -24,12 +27,34 @@ import { dirname, join, resolve } from "node:path";
|
|
|
24
27
|
import { fileURLToPath } from "node:url";
|
|
25
28
|
import { parseArgs } from "node:util";
|
|
26
29
|
|
|
30
|
+
import { getAlphaGuidance } from "./agent-guidance.js";
|
|
31
|
+
import {
|
|
32
|
+
ALPHA_CONSENT_NOTICE,
|
|
33
|
+
generateUserId,
|
|
34
|
+
isValidApiKeyFormat,
|
|
35
|
+
readAlphaIdentity,
|
|
36
|
+
} from "./alpha-identity.js";
|
|
27
37
|
import { TELEMETRY_NOTICE } from "./analytics.js";
|
|
38
|
+
import { pollDeviceCode, requestDeviceCode } from "./auth/device-code.js";
|
|
28
39
|
import { CLAUDE_CODE_HOOK_KEYS, SELFTUNE_CONFIG_DIR, SELFTUNE_CONFIG_PATH } from "./constants.js";
|
|
29
|
-
import type { SelftuneConfig } from "./types.js";
|
|
40
|
+
import type { AgentCommandGuidance, AlphaIdentity, SelftuneConfig } from "./types.js";
|
|
30
41
|
import { hookKeyHasSelftuneEntry } from "./utils/hooks.js";
|
|
31
42
|
import { detectAgent } from "./utils/llm-call.js";
|
|
32
43
|
|
|
44
|
+
interface InitCliErrorPayload extends AgentCommandGuidance {
|
|
45
|
+
error: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class InitCliError extends Error {
|
|
49
|
+
payload: InitCliErrorPayload;
|
|
50
|
+
|
|
51
|
+
constructor(payload: InitCliErrorPayload) {
|
|
52
|
+
super(payload.message);
|
|
53
|
+
this.name = "InitCliError";
|
|
54
|
+
this.payload = payload;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
33
58
|
// ---------------------------------------------------------------------------
|
|
34
59
|
// Agent type detection
|
|
35
60
|
// ---------------------------------------------------------------------------
|
|
@@ -116,6 +141,24 @@ export function determineCliPath(override?: string): string {
|
|
|
116
141
|
return resolve(dirname(import.meta.path), "index.ts");
|
|
117
142
|
}
|
|
118
143
|
|
|
144
|
+
function writeSelftuneConfig(configPath: string, config: SelftuneConfig): void {
|
|
145
|
+
const serialized = JSON.stringify(config, null, 2);
|
|
146
|
+
if (!config.alpha?.api_key?.trim()) {
|
|
147
|
+
writeFileSync(configPath, serialized, "utf-8");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const tempPath = `${configPath}.tmp`;
|
|
152
|
+
const fd = openSync(tempPath, "w", 0o600);
|
|
153
|
+
try {
|
|
154
|
+
writeFileSync(fd, serialized, "utf-8");
|
|
155
|
+
fsyncSync(fd);
|
|
156
|
+
} finally {
|
|
157
|
+
closeSync(fd);
|
|
158
|
+
}
|
|
159
|
+
renameSync(tempPath, configPath);
|
|
160
|
+
}
|
|
161
|
+
|
|
119
162
|
// ---------------------------------------------------------------------------
|
|
120
163
|
// LLM mode determination
|
|
121
164
|
// ---------------------------------------------------------------------------
|
|
@@ -270,41 +313,13 @@ export function installClaudeCodeHooks(options?: {
|
|
|
270
313
|
// Agent file installation
|
|
271
314
|
// ---------------------------------------------------------------------------
|
|
272
315
|
|
|
273
|
-
/** Bundled agent files directory (ships with the npm package). */
|
|
274
|
-
const BUNDLED_AGENTS_DIR = resolve(dirname(import.meta.path), "..", "..", ".claude", "agents");
|
|
275
|
-
|
|
276
316
|
/**
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
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.
|
|
280
320
|
*/
|
|
281
|
-
export function installAgentFiles(
|
|
282
|
-
|
|
283
|
-
const force = options?.force ?? false;
|
|
284
|
-
const targetDir = join(home, ".claude", "agents");
|
|
285
|
-
|
|
286
|
-
if (!existsSync(BUNDLED_AGENTS_DIR)) return [];
|
|
287
|
-
|
|
288
|
-
let sourceFiles: string[];
|
|
289
|
-
try {
|
|
290
|
-
sourceFiles = readdirSync(BUNDLED_AGENTS_DIR).filter((f) => f.endsWith(".md"));
|
|
291
|
-
} catch {
|
|
292
|
-
return [];
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (sourceFiles.length === 0) return [];
|
|
296
|
-
|
|
297
|
-
mkdirSync(targetDir, { recursive: true });
|
|
298
|
-
|
|
299
|
-
const copied: string[] = [];
|
|
300
|
-
for (const file of sourceFiles) {
|
|
301
|
-
const dest = join(targetDir, file);
|
|
302
|
-
if (!force && existsSync(dest)) continue;
|
|
303
|
-
copyFileSync(join(BUNDLED_AGENTS_DIR, file), dest);
|
|
304
|
-
copied.push(file);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return copied;
|
|
321
|
+
export function installAgentFiles(_options?: { homeDir?: string; force?: boolean }): string[] {
|
|
322
|
+
return [];
|
|
308
323
|
}
|
|
309
324
|
|
|
310
325
|
// ---------------------------------------------------------------------------
|
|
@@ -437,6 +452,11 @@ export interface InitOptions {
|
|
|
437
452
|
agentOverride?: string;
|
|
438
453
|
cliPathOverride?: string;
|
|
439
454
|
homeDir?: string;
|
|
455
|
+
alpha?: boolean;
|
|
456
|
+
noAlpha?: boolean;
|
|
457
|
+
alphaEmail?: string;
|
|
458
|
+
alphaName?: string;
|
|
459
|
+
alphaKey?: string;
|
|
440
460
|
}
|
|
441
461
|
|
|
442
462
|
// ---------------------------------------------------------------------------
|
|
@@ -447,7 +467,7 @@ export interface InitOptions {
|
|
|
447
467
|
* Run the init flow. Returns the written (or existing) config.
|
|
448
468
|
* Extracted as a pure function for testability.
|
|
449
469
|
*/
|
|
450
|
-
export function runInit(opts: InitOptions): SelftuneConfig {
|
|
470
|
+
export async function runInit(opts: InitOptions): Promise<SelftuneConfig> {
|
|
451
471
|
const { configDir, configPath, force } = opts;
|
|
452
472
|
|
|
453
473
|
// If config exists and no --force, return existing
|
|
@@ -462,6 +482,9 @@ export function runInit(opts: InitOptions): SelftuneConfig {
|
|
|
462
482
|
}
|
|
463
483
|
}
|
|
464
484
|
|
|
485
|
+
// Capture existing alpha identity before overwriting config (for user_id preservation)
|
|
486
|
+
const existingAlphaBeforeOverwrite = readAlphaIdentity(configPath);
|
|
487
|
+
|
|
465
488
|
// Detect agent type
|
|
466
489
|
const agentType = detectAgentType(opts.agentOverride, opts.homeDir);
|
|
467
490
|
|
|
@@ -485,6 +508,85 @@ export function runInit(opts: InitOptions): SelftuneConfig {
|
|
|
485
508
|
const settingsPath = join(home, ".claude", "settings.json");
|
|
486
509
|
const hooksInstalled = agentType === "claude_code" ? checkClaudeCodeHooks(settingsPath) : false;
|
|
487
510
|
|
|
511
|
+
let validatedAlphaIdentity: AlphaIdentity | null = null;
|
|
512
|
+
if (opts.alpha) {
|
|
513
|
+
if (opts.alphaKey) {
|
|
514
|
+
// Direct key entry path — backward compatible, requires email
|
|
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");
|
|
549
|
+
|
|
550
|
+
const grant = await requestDeviceCode();
|
|
551
|
+
|
|
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
|
+
user_code: grant.user_code,
|
|
559
|
+
expires_in: grant.expires_in,
|
|
560
|
+
message: `Open ${grant.verification_url} and enter code: ${grant.user_code}`,
|
|
561
|
+
}),
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
// Try to open browser
|
|
565
|
+
try {
|
|
566
|
+
const url = `${grant.verification_url}?code=${grant.user_code}`;
|
|
567
|
+
Bun.spawn(["open", url], { stdout: "ignore", stderr: "ignore" });
|
|
568
|
+
process.stderr.write(`[alpha] Browser opened. Waiting for approval...\n`);
|
|
569
|
+
} catch {
|
|
570
|
+
process.stderr.write(`[alpha] Could not open browser. Visit the URL above manually.\n`);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
process.stderr.write("[alpha] Polling");
|
|
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
|
+
};
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
488
590
|
const config: SelftuneConfig = {
|
|
489
591
|
agent_type: agentType,
|
|
490
592
|
cli_path: cliPath,
|
|
@@ -496,13 +598,10 @@ export function runInit(opts: InitOptions): SelftuneConfig {
|
|
|
496
598
|
|
|
497
599
|
// Write config
|
|
498
600
|
mkdirSync(configDir, { recursive: true });
|
|
499
|
-
|
|
601
|
+
writeSelftuneConfig(configPath, config);
|
|
500
602
|
|
|
501
|
-
//
|
|
502
|
-
|
|
503
|
-
if (copiedAgents.length > 0) {
|
|
504
|
-
console.error(`[INFO] Installed agent files: ${copiedAgents.join(", ")}`);
|
|
505
|
-
}
|
|
603
|
+
// Agent files are bundled in skill/agents/ and read directly by the
|
|
604
|
+
// consuming agent — no installation step needed.
|
|
506
605
|
|
|
507
606
|
// Auto-install hooks into ~/.claude/settings.json (Claude Code only)
|
|
508
607
|
if (agentType === "claude_code") {
|
|
@@ -513,7 +612,7 @@ export function runInit(opts: InitOptions): SelftuneConfig {
|
|
|
513
612
|
if (addedHookKeys.length > 0) {
|
|
514
613
|
config.hooks_installed = true;
|
|
515
614
|
// Re-write config with updated hooks_installed flag
|
|
516
|
-
|
|
615
|
+
writeSelftuneConfig(configPath, config);
|
|
517
616
|
console.error(
|
|
518
617
|
`[INFO] Installed ${addedHookKeys.length} selftune hook(s) into ${settingsPath}: ${addedHookKeys.join(", ")}`,
|
|
519
618
|
);
|
|
@@ -521,11 +620,34 @@ export function runInit(opts: InitOptions): SelftuneConfig {
|
|
|
521
620
|
// Re-check in case hooks were already present
|
|
522
621
|
config.hooks_installed = checkClaudeCodeHooks(settingsPath);
|
|
523
622
|
if (config.hooks_installed) {
|
|
524
|
-
|
|
623
|
+
writeSelftuneConfig(configPath, config);
|
|
525
624
|
}
|
|
526
625
|
}
|
|
527
626
|
}
|
|
528
627
|
|
|
628
|
+
if (existingAlphaBeforeOverwrite && !opts.alpha && !opts.noAlpha) {
|
|
629
|
+
config.alpha = existingAlphaBeforeOverwrite;
|
|
630
|
+
writeSelftuneConfig(configPath, config);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Handle alpha enrollment
|
|
634
|
+
if (validatedAlphaIdentity) {
|
|
635
|
+
config.alpha = validatedAlphaIdentity;
|
|
636
|
+
writeSelftuneConfig(configPath, config);
|
|
637
|
+
|
|
638
|
+
const readiness = checkAlphaReadiness(configPath);
|
|
639
|
+
console.error(JSON.stringify({ alpha_readiness: readiness }));
|
|
640
|
+
} else if (opts.noAlpha) {
|
|
641
|
+
if (existingAlphaBeforeOverwrite) {
|
|
642
|
+
const identity: AlphaIdentity = {
|
|
643
|
+
...existingAlphaBeforeOverwrite,
|
|
644
|
+
enrolled: false,
|
|
645
|
+
};
|
|
646
|
+
config.alpha = identity;
|
|
647
|
+
writeSelftuneConfig(configPath, config);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
529
651
|
return config;
|
|
530
652
|
}
|
|
531
653
|
|
|
@@ -541,6 +663,11 @@ export async function cliMain(): Promise<void> {
|
|
|
541
663
|
force: { type: "boolean", default: false },
|
|
542
664
|
"enable-autonomy": { type: "boolean", default: false },
|
|
543
665
|
"schedule-format": { type: "string" },
|
|
666
|
+
alpha: { type: "boolean", default: false },
|
|
667
|
+
"no-alpha": { type: "boolean", default: false },
|
|
668
|
+
"alpha-email": { type: "string" },
|
|
669
|
+
"alpha-name": { type: "string" },
|
|
670
|
+
"alpha-key": { type: "string" },
|
|
544
671
|
},
|
|
545
672
|
strict: true,
|
|
546
673
|
});
|
|
@@ -551,7 +678,14 @@ export async function cliMain(): Promise<void> {
|
|
|
551
678
|
const enableAutonomy = values["enable-autonomy"] ?? false;
|
|
552
679
|
|
|
553
680
|
// Check for existing config without force
|
|
554
|
-
|
|
681
|
+
const hasAlphaMutation = !!(
|
|
682
|
+
values.alpha ||
|
|
683
|
+
values["no-alpha"] ||
|
|
684
|
+
values["alpha-email"] ||
|
|
685
|
+
values["alpha-name"] ||
|
|
686
|
+
values["alpha-key"]
|
|
687
|
+
);
|
|
688
|
+
if (!force && !enableAutonomy && !hasAlphaMutation && existsSync(configPath)) {
|
|
555
689
|
try {
|
|
556
690
|
const raw = readFileSync(configPath, "utf-8");
|
|
557
691
|
const existing = JSON.parse(raw) as SelftuneConfig;
|
|
@@ -565,15 +699,57 @@ export async function cliMain(): Promise<void> {
|
|
|
565
699
|
}
|
|
566
700
|
}
|
|
567
701
|
|
|
568
|
-
const config = runInit({
|
|
702
|
+
const config = await runInit({
|
|
569
703
|
configDir,
|
|
570
704
|
configPath,
|
|
571
705
|
force,
|
|
572
706
|
agentOverride: values.agent,
|
|
573
707
|
cliPathOverride: values["cli-path"],
|
|
708
|
+
alpha: values.alpha ?? false,
|
|
709
|
+
noAlpha: values["no-alpha"] ?? false,
|
|
710
|
+
alphaEmail: values["alpha-email"],
|
|
711
|
+
alphaName: values["alpha-name"],
|
|
712
|
+
alphaKey: values["alpha-key"],
|
|
574
713
|
});
|
|
575
714
|
|
|
576
|
-
|
|
715
|
+
// Redact api_key before printing to stdout
|
|
716
|
+
const safeConfig = structuredClone(config);
|
|
717
|
+
if (safeConfig.alpha?.api_key) {
|
|
718
|
+
safeConfig.alpha.api_key = "<redacted>";
|
|
719
|
+
}
|
|
720
|
+
console.log(JSON.stringify(safeConfig, null, 2));
|
|
721
|
+
|
|
722
|
+
// Alpha enrollment output
|
|
723
|
+
if (values.alpha) {
|
|
724
|
+
console.log(
|
|
725
|
+
JSON.stringify({
|
|
726
|
+
level: "info",
|
|
727
|
+
code: "alpha_enrolled",
|
|
728
|
+
user_id: config.alpha?.user_id,
|
|
729
|
+
email: config.alpha?.email,
|
|
730
|
+
enrolled: true,
|
|
731
|
+
}),
|
|
732
|
+
);
|
|
733
|
+
console.log(
|
|
734
|
+
JSON.stringify({
|
|
735
|
+
level: "info",
|
|
736
|
+
code: "alpha_upload_ready",
|
|
737
|
+
message:
|
|
738
|
+
"Alpha enrollment complete. Uploads will run automatically during 'selftune orchestrate'. To enable scheduled background sync (includes evolve + watch + upload), run: selftune cron setup",
|
|
739
|
+
next_command: "selftune alpha upload",
|
|
740
|
+
optional_autonomy: "selftune cron setup",
|
|
741
|
+
}),
|
|
742
|
+
);
|
|
743
|
+
console.error(ALPHA_CONSENT_NOTICE);
|
|
744
|
+
} else if (values["no-alpha"]) {
|
|
745
|
+
console.log(
|
|
746
|
+
JSON.stringify({
|
|
747
|
+
level: "info",
|
|
748
|
+
code: "alpha_unenrolled",
|
|
749
|
+
enrolled: false,
|
|
750
|
+
}),
|
|
751
|
+
);
|
|
752
|
+
}
|
|
577
753
|
|
|
578
754
|
// Detect workspace type and report
|
|
579
755
|
const workspace = detectWorkspaceType(process.cwd());
|
|
@@ -637,6 +813,28 @@ export async function cliMain(): Promise<void> {
|
|
|
637
813
|
}
|
|
638
814
|
}
|
|
639
815
|
|
|
816
|
+
// ---------------------------------------------------------------------------
|
|
817
|
+
// Alpha readiness check
|
|
818
|
+
// ---------------------------------------------------------------------------
|
|
819
|
+
|
|
820
|
+
export function checkAlphaReadiness(configPath: string): {
|
|
821
|
+
ready: boolean;
|
|
822
|
+
missing: string[];
|
|
823
|
+
guidance: AgentCommandGuidance;
|
|
824
|
+
} {
|
|
825
|
+
const identity = readAlphaIdentity(configPath);
|
|
826
|
+
const missing: string[] = [];
|
|
827
|
+
if (!identity) {
|
|
828
|
+
missing.push("alpha identity not configured");
|
|
829
|
+
return { ready: false, missing, guidance: getAlphaGuidance(identity) };
|
|
830
|
+
}
|
|
831
|
+
if (!identity.enrolled) missing.push("not enrolled");
|
|
832
|
+
if (!identity.api_key) missing.push("api_key not set");
|
|
833
|
+
else if (!isValidApiKeyFormat(identity.api_key))
|
|
834
|
+
missing.push("api_key has invalid format (expected st_live_* or st_test_*)");
|
|
835
|
+
return { ready: missing.length === 0, missing, guidance: getAlphaGuidance(identity) };
|
|
836
|
+
}
|
|
837
|
+
|
|
640
838
|
// Guard: only run when invoked directly
|
|
641
839
|
const isMain =
|
|
642
840
|
(import.meta as Record<string, unknown>).main === true ||
|
|
@@ -644,6 +842,10 @@ const isMain =
|
|
|
644
842
|
|
|
645
843
|
if (isMain) {
|
|
646
844
|
cliMain().catch((err) => {
|
|
845
|
+
if (err instanceof InitCliError) {
|
|
846
|
+
console.error(JSON.stringify(err.payload));
|
|
847
|
+
process.exit(1);
|
|
848
|
+
}
|
|
647
849
|
console.error(`[FATAL] ${err}`);
|
|
648
850
|
process.exit(1);
|
|
649
851
|
});
|
package/cli/selftune/last.ts
CHANGED
|
@@ -4,14 +4,13 @@
|
|
|
4
4
|
* Lightweight, no LLM calls.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { getDb } from "./localdb/db.js";
|
|
8
|
+
import { queryQueryLog, querySessionTelemetry, querySkillUsageRecords } from "./localdb/queries.js";
|
|
8
9
|
import type { QueryLogRecord, SessionTelemetryRecord, SkillUsageRecord } from "./types.js";
|
|
9
|
-
import { readJsonl } from "./utils/jsonl.js";
|
|
10
10
|
import {
|
|
11
11
|
filterActionableQueryRecords,
|
|
12
12
|
filterActionableSkillUsageRecords,
|
|
13
13
|
} from "./utils/query-filter.js";
|
|
14
|
-
import { readEffectiveSkillUsageRecords } from "./utils/skill-log.js";
|
|
15
14
|
|
|
16
15
|
// ---------------------------------------------------------------------------
|
|
17
16
|
// Types
|
|
@@ -79,7 +78,7 @@ export function computeLastInsight(
|
|
|
79
78
|
let recommendation: string;
|
|
80
79
|
const unmatched = unmatchedQueries.length;
|
|
81
80
|
if (unmatched > 0) {
|
|
82
|
-
recommendation = `${unmatched} queries had no skill match. Run 'selftune
|
|
81
|
+
recommendation = `${unmatched} queries had no skill match. Run 'selftune eval generate --list-skills' to investigate.`;
|
|
83
82
|
} else if (errors > 0) {
|
|
84
83
|
recommendation = `${errors} errors encountered. Check logs for details.`;
|
|
85
84
|
} else {
|
|
@@ -132,9 +131,10 @@ export function formatInsight(insight: LastSessionInsight): string {
|
|
|
132
131
|
|
|
133
132
|
/** CLI main: reads logs, prints insight. */
|
|
134
133
|
export function cliMain(): void {
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const
|
|
134
|
+
const db = getDb();
|
|
135
|
+
const telemetry = querySessionTelemetry(db) as SessionTelemetryRecord[];
|
|
136
|
+
const skillRecords = querySkillUsageRecords(db) as SkillUsageRecord[];
|
|
137
|
+
const queryRecords = queryQueryLog(db) as QueryLogRecord[];
|
|
138
138
|
|
|
139
139
|
const insight = computeLastInsight(telemetry, skillRecords, queryRecords);
|
|
140
140
|
if (!insight) {
|