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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { Database } from "bun:sqlite";
|
|
9
|
+
|
|
9
10
|
import type {
|
|
10
11
|
OrchestrateRunReport,
|
|
11
12
|
OverviewPayload,
|
|
@@ -578,6 +579,58 @@ export function queryImprovementSignals(
|
|
|
578
579
|
}));
|
|
579
580
|
}
|
|
580
581
|
|
|
582
|
+
// -- Grading results query ----------------------------------------------------
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Read grading results from SQLite for upload staging.
|
|
586
|
+
*/
|
|
587
|
+
export function queryGradingResults(db: Database): Array<{
|
|
588
|
+
grading_id: string;
|
|
589
|
+
session_id: string;
|
|
590
|
+
skill_name: string;
|
|
591
|
+
transcript_path: string | null;
|
|
592
|
+
graded_at: string;
|
|
593
|
+
pass_rate: number | null;
|
|
594
|
+
mean_score: number | null;
|
|
595
|
+
score_std_dev: number | null;
|
|
596
|
+
passed_count: number | null;
|
|
597
|
+
failed_count: number | null;
|
|
598
|
+
total_count: number | null;
|
|
599
|
+
expectations_json: string | null;
|
|
600
|
+
claims_json: string | null;
|
|
601
|
+
eval_feedback_json: string | null;
|
|
602
|
+
failure_feedback_json: string | null;
|
|
603
|
+
execution_metrics_json: string | null;
|
|
604
|
+
}> {
|
|
605
|
+
return db
|
|
606
|
+
.query(
|
|
607
|
+
`SELECT grading_id, session_id, skill_name, transcript_path, graded_at,
|
|
608
|
+
pass_rate, mean_score, score_std_dev, passed_count, failed_count, total_count,
|
|
609
|
+
expectations_json, claims_json, eval_feedback_json, failure_feedback_json,
|
|
610
|
+
execution_metrics_json
|
|
611
|
+
FROM grading_results
|
|
612
|
+
ORDER BY graded_at DESC`,
|
|
613
|
+
)
|
|
614
|
+
.all() as Array<{
|
|
615
|
+
grading_id: string;
|
|
616
|
+
session_id: string;
|
|
617
|
+
skill_name: string;
|
|
618
|
+
transcript_path: string | null;
|
|
619
|
+
graded_at: string;
|
|
620
|
+
pass_rate: number | null;
|
|
621
|
+
mean_score: number | null;
|
|
622
|
+
score_std_dev: number | null;
|
|
623
|
+
passed_count: number | null;
|
|
624
|
+
failed_count: number | null;
|
|
625
|
+
total_count: number | null;
|
|
626
|
+
expectations_json: string | null;
|
|
627
|
+
claims_json: string | null;
|
|
628
|
+
eval_feedback_json: string | null;
|
|
629
|
+
failure_feedback_json: string | null;
|
|
630
|
+
execution_metrics_json: string | null;
|
|
631
|
+
}>;
|
|
632
|
+
}
|
|
633
|
+
|
|
581
634
|
// -- Canonical record staging query -------------------------------------------
|
|
582
635
|
|
|
583
636
|
/**
|
|
@@ -188,6 +188,28 @@ CREATE TABLE IF NOT EXISTS queries (
|
|
|
188
188
|
source TEXT
|
|
189
189
|
)`;
|
|
190
190
|
|
|
191
|
+
// -- Grading results table (from grade-session output) -----------------------
|
|
192
|
+
|
|
193
|
+
export const CREATE_GRADING_RESULTS = `
|
|
194
|
+
CREATE TABLE IF NOT EXISTS grading_results (
|
|
195
|
+
grading_id TEXT PRIMARY KEY,
|
|
196
|
+
session_id TEXT NOT NULL,
|
|
197
|
+
skill_name TEXT NOT NULL,
|
|
198
|
+
transcript_path TEXT,
|
|
199
|
+
graded_at TEXT NOT NULL,
|
|
200
|
+
pass_rate REAL,
|
|
201
|
+
mean_score REAL,
|
|
202
|
+
score_std_dev REAL,
|
|
203
|
+
passed_count INTEGER,
|
|
204
|
+
failed_count INTEGER,
|
|
205
|
+
total_count INTEGER,
|
|
206
|
+
expectations_json TEXT,
|
|
207
|
+
claims_json TEXT,
|
|
208
|
+
eval_feedback_json TEXT,
|
|
209
|
+
failure_feedback_json TEXT,
|
|
210
|
+
execution_metrics_json TEXT
|
|
211
|
+
)`;
|
|
212
|
+
|
|
191
213
|
// -- Improvement signal table (from signal_log.jsonl) ------------------------
|
|
192
214
|
|
|
193
215
|
export const CREATE_IMPROVEMENT_SIGNALS = `
|
|
@@ -278,6 +300,11 @@ export const CREATE_INDEXES = [
|
|
|
278
300
|
`CREATE INDEX IF NOT EXISTS idx_queries_session ON queries(session_id)`,
|
|
279
301
|
`CREATE INDEX IF NOT EXISTS idx_queries_ts ON queries(timestamp)`,
|
|
280
302
|
`CREATE UNIQUE INDEX IF NOT EXISTS idx_queries_dedup ON queries(session_id, query, timestamp)`,
|
|
303
|
+
// -- Grading results indexes -------------------------------------------------
|
|
304
|
+
`CREATE INDEX IF NOT EXISTS idx_grading_session ON grading_results(session_id)`,
|
|
305
|
+
`CREATE INDEX IF NOT EXISTS idx_grading_skill ON grading_results(skill_name)`,
|
|
306
|
+
`CREATE INDEX IF NOT EXISTS idx_grading_ts ON grading_results(graded_at)`,
|
|
307
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_grading_dedup ON grading_results(session_id, skill_name, graded_at)`,
|
|
281
308
|
// -- Improvement signal indexes ---------------------------------------------
|
|
282
309
|
`CREATE INDEX IF NOT EXISTS idx_signals_session ON improvement_signals(session_id)`,
|
|
283
310
|
`CREATE INDEX IF NOT EXISTS idx_signals_consumed ON improvement_signals(consumed)`,
|
|
@@ -347,6 +374,7 @@ export const ALL_DDL = [
|
|
|
347
374
|
CREATE_SKILL_USAGE,
|
|
348
375
|
CREATE_ORCHESTRATE_RUNS,
|
|
349
376
|
CREATE_QUERIES,
|
|
377
|
+
CREATE_GRADING_RESULTS,
|
|
350
378
|
CREATE_IMPROVEMENT_SIGNALS,
|
|
351
379
|
CREATE_UPLOAD_QUEUE,
|
|
352
380
|
CREATE_UPLOAD_WATERMARKS,
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
writeFileSync,
|
|
25
25
|
} from "node:fs";
|
|
26
26
|
import { basename, dirname } from "node:path";
|
|
27
|
+
|
|
27
28
|
import { CANONICAL_LOG, canonicalSessionStatePath } from "./constants.js";
|
|
28
29
|
import { writeCanonicalBatchToDb, writeCanonicalToDb } from "./localdb/direct-write.js";
|
|
29
30
|
import {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { existsSync, readFileSync } from "node:fs";
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
13
|
import { join } from "node:path";
|
|
14
|
+
|
|
14
15
|
import { getAlphaGuidance } from "./agent-guidance.js";
|
|
15
16
|
import { getAlphaLinkState, readAlphaIdentity } from "./alpha-identity.js";
|
|
16
17
|
import { LOG_DIR, REQUIRED_FIELDS, SELFTUNE_CONFIG_PATH } from "./constants.js";
|
|
@@ -180,7 +180,7 @@ function buildReportHTML(
|
|
|
180
180
|
<tr><th>Metric</th><th>Value</th></tr>
|
|
181
181
|
<tr><td>Total Skills</td><td>${statusResult.skills.length}</td></tr>
|
|
182
182
|
<tr><td>Unmatched Queries</td><td>${statusResult.unmatchedQueries}</td></tr>
|
|
183
|
-
<tr><td>
|
|
183
|
+
<tr><td>Undeployed Proposals</td><td>${statusResult.pendingProposals}</td></tr>
|
|
184
184
|
<tr><td>Last Session</td><td>${escapeHtml(statusResult.lastSession ?? "\u2014")}</td></tr>
|
|
185
185
|
</table>
|
|
186
186
|
</div>
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Route handler: GET /api/v2/skills/:name
|
|
3
3
|
*
|
|
4
|
-
* Returns SQLite-backed per-skill report with evolution audit,
|
|
4
|
+
* Returns SQLite-backed per-skill report with evolution audit, undeployed proposals,
|
|
5
5
|
* invocation details, duration stats, selftune resource usage, prompt samples,
|
|
6
6
|
* and session metadata.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { Database } from "bun:sqlite";
|
|
10
|
+
|
|
10
11
|
import { getPendingProposals, getSkillReportPayload, safeParseJson } from "../localdb/queries.js";
|
|
11
12
|
|
|
12
13
|
export function handleSkillReport(db: Database, skillName: string): Response {
|
package/cli/selftune/status.ts
CHANGED
|
@@ -324,7 +324,7 @@ export function formatStatus(result: StatusResult): string {
|
|
|
324
324
|
|
|
325
325
|
// Summary stats
|
|
326
326
|
lines.push(`Unmatched queries: ${result.unmatchedQueries}`);
|
|
327
|
-
lines.push(`
|
|
327
|
+
lines.push(`Undeployed proposals: ${result.pendingProposals}`);
|
|
328
328
|
|
|
329
329
|
// Last session
|
|
330
330
|
if (result.lastSession) {
|
package/cli/selftune/sync.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { existsSync } from "node:fs";
|
|
|
17
17
|
import { homedir } from "node:os";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { parseArgs } from "node:util";
|
|
20
|
+
|
|
20
21
|
import {
|
|
21
22
|
CLAUDE_CODE_MARKER,
|
|
22
23
|
CLAUDE_CODE_PROJECTS_DIR,
|
|
@@ -504,7 +505,7 @@ function formatStepLine(label: string, step: SyncStepResult, timing?: SyncPhaseT
|
|
|
504
505
|
return ` ${label}: ${parts.join(", ")}${time}`;
|
|
505
506
|
}
|
|
506
507
|
|
|
507
|
-
export function cliMain(): void {
|
|
508
|
+
export async function cliMain(): Promise<void> {
|
|
508
509
|
const { values } = parseArgs({
|
|
509
510
|
options: {
|
|
510
511
|
"projects-dir": { type: "string", default: CLAUDE_CODE_PROJECTS_DIR },
|
|
@@ -633,6 +634,34 @@ Options:
|
|
|
633
634
|
|
|
634
635
|
process.stderr.write(`\nDone in ${formatMs(result.total_elapsed_ms)}\n`);
|
|
635
636
|
}
|
|
637
|
+
|
|
638
|
+
// Trigger alpha upload if enrolled — pushes freshly synced data to cloud
|
|
639
|
+
if (!result.dry_run) {
|
|
640
|
+
try {
|
|
641
|
+
const { readAlphaIdentity } = await import("./alpha-identity.js");
|
|
642
|
+
const { SELFTUNE_CONFIG_PATH } = await import("./constants.js");
|
|
643
|
+
const identity = readAlphaIdentity(SELFTUNE_CONFIG_PATH);
|
|
644
|
+
if (identity?.enrolled && identity.api_key) {
|
|
645
|
+
const { runUploadCycle } = await import("./alpha-upload/index.js");
|
|
646
|
+
const { getDb } = await import("./localdb/db.js");
|
|
647
|
+
const db = getDb();
|
|
648
|
+
const uploadSummary = await runUploadCycle(db, {
|
|
649
|
+
enrolled: true,
|
|
650
|
+
userId: identity.user_id,
|
|
651
|
+
apiKey: identity.api_key,
|
|
652
|
+
});
|
|
653
|
+
if (!jsonOutput) {
|
|
654
|
+
process.stderr.write(
|
|
655
|
+
`\nAlpha upload: prepared=${uploadSummary.prepared}, sent=${uploadSummary.sent}, failed=${uploadSummary.failed}\n`,
|
|
656
|
+
);
|
|
657
|
+
} else {
|
|
658
|
+
console.log(JSON.stringify({ code: "alpha_upload", ...uploadSummary }));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
} catch {
|
|
662
|
+
// fail-open: upload failure should not break sync
|
|
663
|
+
}
|
|
664
|
+
}
|
|
636
665
|
}
|
|
637
666
|
|
|
638
667
|
if (import.meta.main) {
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* selftune uninstall — Clean removal of all selftune data and configuration.
|
|
4
|
+
*
|
|
5
|
+
* Removes:
|
|
6
|
+
* 1. Autonomy scheduling (launchd/cron/systemd + OpenClaw cron)
|
|
7
|
+
* 2. Selftune hooks from ~/.claude/settings.json (surgical — preserves user hooks)
|
|
8
|
+
* 3. Selftune-managed Claude subagents from ~/.claude/agents/
|
|
9
|
+
* 4. JSONL telemetry logs from ~/.claude/
|
|
10
|
+
* 5. Selftune config directory (~/.selftune/)
|
|
11
|
+
* 6. Ingest marker files
|
|
12
|
+
* 7. Optionally: `npm uninstall -g selftune`
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* selftune uninstall [--dry-run] [--keep-logs] [--npm-uninstall]
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { parseArgs } from "node:util";
|
|
22
|
+
|
|
23
|
+
import { removeInstalledAgentFiles } from "./claude-agents.js";
|
|
24
|
+
import {
|
|
25
|
+
CLAUDE_CODE_MARKER,
|
|
26
|
+
CLAUDE_SETTINGS_PATH,
|
|
27
|
+
CODEX_INGEST_MARKER,
|
|
28
|
+
EVOLUTION_AUDIT_LOG,
|
|
29
|
+
EVOLUTION_EVIDENCE_LOG,
|
|
30
|
+
OPENCODE_INGEST_MARKER,
|
|
31
|
+
OPENCLAW_INGEST_MARKER,
|
|
32
|
+
ORCHESTRATE_LOCK,
|
|
33
|
+
ORCHESTRATE_RUN_LOG,
|
|
34
|
+
QUERY_LOG,
|
|
35
|
+
REPAIRED_SKILL_LOG,
|
|
36
|
+
REPAIRED_SKILL_SESSIONS_MARKER,
|
|
37
|
+
SELFTUNE_CONFIG_DIR,
|
|
38
|
+
SIGNAL_LOG,
|
|
39
|
+
SKILL_LOG,
|
|
40
|
+
TELEMETRY_LOG,
|
|
41
|
+
CANONICAL_LOG,
|
|
42
|
+
} from "./constants.js";
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Types
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
interface UninstallResult {
|
|
49
|
+
dryRun: boolean;
|
|
50
|
+
schedule: { removed: boolean; details: string };
|
|
51
|
+
hooks: { removed: number; details: string };
|
|
52
|
+
agents: { removed: number; files: string[] };
|
|
53
|
+
logs: { removed: number; skipped: boolean; files: string[] };
|
|
54
|
+
config: { removed: boolean; path: string };
|
|
55
|
+
markers: { removed: number; files: string[] };
|
|
56
|
+
npm: { uninstalled: boolean; skipped: boolean };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Step 1: Remove autonomy scheduling
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
async function removeScheduling(dryRun: boolean): Promise<{ removed: boolean; details: string }> {
|
|
64
|
+
// Try launchd first (macOS)
|
|
65
|
+
const label = "dev.selftune.orchestrate";
|
|
66
|
+
const plistPath = join(homedir(), "Library", "LaunchAgents", `${label}.plist`);
|
|
67
|
+
|
|
68
|
+
if (existsSync(plistPath)) {
|
|
69
|
+
if (dryRun) {
|
|
70
|
+
return { removed: false, details: `Would remove launchd plist: ${plistPath}` };
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
// Unload before removing
|
|
74
|
+
Bun.spawnSync(["launchctl", "unload", plistPath], { stderr: "pipe" });
|
|
75
|
+
unlinkSync(plistPath);
|
|
76
|
+
return { removed: true, details: `Removed launchd plist: ${plistPath}` };
|
|
77
|
+
} catch (err) {
|
|
78
|
+
return {
|
|
79
|
+
removed: false,
|
|
80
|
+
details: `Failed to remove launchd plist: ${err instanceof Error ? err.message : String(err)}`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Try OpenClaw cron jobs
|
|
86
|
+
if (dryRun) {
|
|
87
|
+
return { removed: false, details: "Would remove cron jobs via selftune cron remove" };
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const proc = Bun.spawnSync(["selftune", "cron", "remove"], {
|
|
91
|
+
stdout: "pipe",
|
|
92
|
+
stderr: "pipe",
|
|
93
|
+
});
|
|
94
|
+
if (proc.exitCode === 0) {
|
|
95
|
+
return { removed: true, details: "Removed cron jobs via selftune cron remove" };
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// selftune cron remove not available or failed — not critical
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { removed: false, details: "No scheduling artifacts found" };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Step 2: Remove selftune hooks from settings.json
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
/** Selftune hook scripts — used to identify which entries to remove. */
|
|
109
|
+
const SELFTUNE_HOOK_SCRIPTS = [
|
|
110
|
+
"hooks/prompt-log.ts",
|
|
111
|
+
"hooks/auto-activate.ts",
|
|
112
|
+
"hooks/skill-change-guard.ts",
|
|
113
|
+
"hooks/evolution-guard.ts",
|
|
114
|
+
"hooks/skill-eval.ts",
|
|
115
|
+
"hooks/session-stop.ts",
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
function isSelfttuneHookEntry(entry: unknown): boolean {
|
|
119
|
+
if (typeof entry !== "object" || entry === null) return false;
|
|
120
|
+
const obj = entry as Record<string, unknown>;
|
|
121
|
+
|
|
122
|
+
// Check direct command
|
|
123
|
+
if (typeof obj.command === "string") {
|
|
124
|
+
return SELFTUNE_HOOK_SCRIPTS.some((script) => obj.command?.includes(script));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check hooks array (the nested structure used in settings.json)
|
|
128
|
+
if (Array.isArray(obj.hooks)) {
|
|
129
|
+
return obj.hooks.some(
|
|
130
|
+
(h: unknown) =>
|
|
131
|
+
typeof h === "object" &&
|
|
132
|
+
h !== null &&
|
|
133
|
+
typeof (h as Record<string, unknown>).command === "string" &&
|
|
134
|
+
SELFTUNE_HOOK_SCRIPTS.some((script) =>
|
|
135
|
+
((h as Record<string, unknown>).command as string).includes(script),
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function removeHooksFromSettings(
|
|
144
|
+
dryRun: boolean,
|
|
145
|
+
settingsPath?: string,
|
|
146
|
+
): { removed: number; details: string } {
|
|
147
|
+
const path = settingsPath ?? CLAUDE_SETTINGS_PATH;
|
|
148
|
+
if (!existsSync(path)) {
|
|
149
|
+
return { removed: 0, details: "No settings.json found" };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let settings: Record<string, unknown>;
|
|
153
|
+
try {
|
|
154
|
+
settings = JSON.parse(readFileSync(path, "utf-8"));
|
|
155
|
+
} catch {
|
|
156
|
+
return { removed: 0, details: "Failed to parse settings.json" };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const hooks = settings.hooks as Record<string, unknown[]> | undefined;
|
|
160
|
+
if (!hooks || typeof hooks !== "object") {
|
|
161
|
+
return { removed: 0, details: "No hooks section in settings.json" };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let totalRemoved = 0;
|
|
165
|
+
|
|
166
|
+
for (const key of Object.keys(hooks)) {
|
|
167
|
+
if (!Array.isArray(hooks[key])) continue;
|
|
168
|
+
|
|
169
|
+
const before = hooks[key].length;
|
|
170
|
+
hooks[key] = hooks[key].filter((entry) => !isSelfttuneHookEntry(entry));
|
|
171
|
+
const removed = before - hooks[key].length;
|
|
172
|
+
totalRemoved += removed;
|
|
173
|
+
|
|
174
|
+
// Clean up empty arrays
|
|
175
|
+
if (hooks[key].length === 0) {
|
|
176
|
+
delete hooks[key];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Clean up empty hooks object
|
|
181
|
+
if (Object.keys(hooks).length === 0) {
|
|
182
|
+
delete settings.hooks;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (totalRemoved > 0 && !dryRun) {
|
|
186
|
+
writeFileSync(path, JSON.stringify(settings, null, 2), "utf-8");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
removed: totalRemoved,
|
|
191
|
+
details: dryRun
|
|
192
|
+
? `Would remove ${totalRemoved} selftune hook entries from ${path}`
|
|
193
|
+
: `Removed ${totalRemoved} selftune hook entries from ${path}`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// Step 3: Remove bundled Claude subagents
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
function removeAgents(dryRun: boolean): { removed: number; files: string[] } {
|
|
202
|
+
return removeInstalledAgentFiles({ dryRun });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
// Step 4: Remove JSONL log files
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
const LOG_FILES = [
|
|
210
|
+
TELEMETRY_LOG,
|
|
211
|
+
SKILL_LOG,
|
|
212
|
+
REPAIRED_SKILL_LOG,
|
|
213
|
+
CANONICAL_LOG,
|
|
214
|
+
QUERY_LOG,
|
|
215
|
+
EVOLUTION_AUDIT_LOG,
|
|
216
|
+
EVOLUTION_EVIDENCE_LOG,
|
|
217
|
+
ORCHESTRATE_RUN_LOG,
|
|
218
|
+
SIGNAL_LOG,
|
|
219
|
+
ORCHESTRATE_LOCK,
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
function removeLogs(dryRun: boolean): { removed: number; files: string[] } {
|
|
223
|
+
const removed: string[] = [];
|
|
224
|
+
|
|
225
|
+
for (const logPath of LOG_FILES) {
|
|
226
|
+
if (existsSync(logPath)) {
|
|
227
|
+
if (!dryRun) {
|
|
228
|
+
try {
|
|
229
|
+
unlinkSync(logPath);
|
|
230
|
+
removed.push(logPath);
|
|
231
|
+
} catch {
|
|
232
|
+
// Skip files we can't remove
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
removed.push(logPath);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return { removed: removed.length, files: removed };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Step 5: Remove config directory
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
function removeConfig(dryRun: boolean): { removed: boolean; path: string } {
|
|
248
|
+
if (!existsSync(SELFTUNE_CONFIG_DIR)) {
|
|
249
|
+
return { removed: false, path: SELFTUNE_CONFIG_DIR };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!dryRun) {
|
|
253
|
+
try {
|
|
254
|
+
rmSync(SELFTUNE_CONFIG_DIR, { recursive: true, force: true });
|
|
255
|
+
return { removed: true, path: SELFTUNE_CONFIG_DIR };
|
|
256
|
+
} catch {
|
|
257
|
+
return { removed: false, path: SELFTUNE_CONFIG_DIR };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return { removed: false, path: SELFTUNE_CONFIG_DIR };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Step 6: Remove ingest marker files
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
const MARKER_FILES = [
|
|
269
|
+
CLAUDE_CODE_MARKER,
|
|
270
|
+
CODEX_INGEST_MARKER,
|
|
271
|
+
OPENCODE_INGEST_MARKER,
|
|
272
|
+
OPENCLAW_INGEST_MARKER,
|
|
273
|
+
REPAIRED_SKILL_SESSIONS_MARKER,
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
function removeMarkers(dryRun: boolean): { removed: number; files: string[] } {
|
|
277
|
+
const removed: string[] = [];
|
|
278
|
+
|
|
279
|
+
for (const markerPath of MARKER_FILES) {
|
|
280
|
+
if (existsSync(markerPath)) {
|
|
281
|
+
if (!dryRun) {
|
|
282
|
+
try {
|
|
283
|
+
unlinkSync(markerPath);
|
|
284
|
+
removed.push(markerPath);
|
|
285
|
+
} catch {
|
|
286
|
+
// Skip files we can't remove
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
removed.push(markerPath);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return { removed: removed.length, files: removed };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
// Step 7: npm uninstall
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
|
|
301
|
+
async function npmUninstall(dryRun: boolean): Promise<{ uninstalled: boolean }> {
|
|
302
|
+
if (dryRun) {
|
|
303
|
+
return { uninstalled: false };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const proc = Bun.spawnSync(["npm", "uninstall", "-g", "selftune"], {
|
|
308
|
+
stdout: "pipe",
|
|
309
|
+
stderr: "pipe",
|
|
310
|
+
});
|
|
311
|
+
return { uninstalled: proc.exitCode === 0 };
|
|
312
|
+
} catch {
|
|
313
|
+
return { uninstalled: false };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// Main orchestrator
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
export interface UninstallOptions {
|
|
322
|
+
dryRun: boolean;
|
|
323
|
+
keepLogs: boolean;
|
|
324
|
+
npmUninstall: boolean;
|
|
325
|
+
settingsPath?: string;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export async function uninstall(options: UninstallOptions): Promise<UninstallResult> {
|
|
329
|
+
const { dryRun, keepLogs, settingsPath } = options;
|
|
330
|
+
|
|
331
|
+
// Step 1: Remove scheduling
|
|
332
|
+
const schedule = await removeScheduling(dryRun);
|
|
333
|
+
|
|
334
|
+
// Step 2: Remove hooks
|
|
335
|
+
const hooks = removeHooksFromSettings(dryRun, settingsPath);
|
|
336
|
+
|
|
337
|
+
// Step 3: Remove bundled Claude subagents
|
|
338
|
+
const agents = removeAgents(dryRun);
|
|
339
|
+
|
|
340
|
+
// Step 4: Remove logs
|
|
341
|
+
const logs = keepLogs
|
|
342
|
+
? { removed: 0, skipped: true, files: [] }
|
|
343
|
+
: { ...removeLogs(dryRun), skipped: false };
|
|
344
|
+
|
|
345
|
+
// Step 5: Remove config directory
|
|
346
|
+
const config = removeConfig(dryRun);
|
|
347
|
+
|
|
348
|
+
// Step 6: Remove ingest markers
|
|
349
|
+
const markers = removeMarkers(dryRun);
|
|
350
|
+
|
|
351
|
+
// Step 7: npm uninstall (optional)
|
|
352
|
+
const npm = options.npmUninstall
|
|
353
|
+
? { ...(await npmUninstall(dryRun)), skipped: false }
|
|
354
|
+
: { uninstalled: false, skipped: true };
|
|
355
|
+
|
|
356
|
+
return { dryRun, schedule, hooks, agents, logs, config, markers, npm };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
// CLI entry point
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
export async function cliMain(): Promise<void> {
|
|
364
|
+
const { values } = parseArgs({
|
|
365
|
+
options: {
|
|
366
|
+
"dry-run": { type: "boolean", default: false },
|
|
367
|
+
"keep-logs": { type: "boolean", default: false },
|
|
368
|
+
"npm-uninstall": { type: "boolean", default: false },
|
|
369
|
+
help: { type: "boolean", default: false },
|
|
370
|
+
},
|
|
371
|
+
strict: true,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
if (values.help) {
|
|
375
|
+
console.log(`selftune uninstall — Clean removal of all selftune data and configuration
|
|
376
|
+
|
|
377
|
+
Usage:
|
|
378
|
+
selftune uninstall [options]
|
|
379
|
+
|
|
380
|
+
Options:
|
|
381
|
+
--dry-run Preview what would be removed without deleting anything
|
|
382
|
+
--keep-logs Preserve JSONL telemetry logs (remove everything else)
|
|
383
|
+
--npm-uninstall Also run 'npm uninstall -g selftune'
|
|
384
|
+
--help Show this help message
|
|
385
|
+
|
|
386
|
+
Removes:
|
|
387
|
+
1. Autonomy scheduling (launchd/cron/systemd)
|
|
388
|
+
2. Selftune hooks from ~/.claude/settings.json (preserves user hooks)
|
|
389
|
+
3. Selftune-managed Claude subagents from ~/.claude/agents/
|
|
390
|
+
4. JSONL telemetry logs from ~/.claude/
|
|
391
|
+
5. Selftune config directory (~/.selftune/)
|
|
392
|
+
6. Ingest marker files
|
|
393
|
+
7. npm global package (with --npm-uninstall)`);
|
|
394
|
+
process.exit(0);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const result = await uninstall({
|
|
398
|
+
dryRun: values["dry-run"] ?? false,
|
|
399
|
+
keepLogs: values["keep-logs"] ?? false,
|
|
400
|
+
npmUninstall: values["npm-uninstall"] ?? false,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
console.log(JSON.stringify(result, null, 2));
|
|
404
|
+
process.exit(0);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (import.meta.main) {
|
|
408
|
+
cliMain().catch((err) => {
|
|
409
|
+
console.error(`[FATAL] ${err}`);
|
|
410
|
+
process.exit(1);
|
|
411
|
+
});
|
|
412
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { existsSync, writeFileSync } from "node:fs";
|
|
2
|
+
|
|
2
3
|
import {
|
|
3
4
|
type CanonicalPlatform,
|
|
4
5
|
type CanonicalRecord,
|
|
5
6
|
type CanonicalRecordKind,
|
|
6
7
|
isCanonicalRecord,
|
|
7
8
|
} from "@selftune/telemetry-contract";
|
|
9
|
+
|
|
8
10
|
import { CANONICAL_LOG } from "../constants.js";
|
|
9
11
|
import { readJsonl } from "./jsonl.js";
|
|
10
12
|
|