selftune 0.2.14 → 0.2.16
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/apps/local-dashboard/dist/assets/index-DOu3iLD9.js +16 -0
- package/apps/local-dashboard/dist/assets/vendor-ui-DIwlrGlb.js +12 -0
- package/apps/local-dashboard/dist/index.html +2 -2
- package/bin/run-hook.cjs +36 -0
- package/cli/selftune/analytics.ts +13 -11
- package/cli/selftune/badge/badge.ts +13 -9
- package/cli/selftune/canonical-export.ts +6 -6
- package/cli/selftune/contribute/contribute.ts +2 -1
- package/cli/selftune/cron/setup.ts +3 -1
- package/cli/selftune/dashboard-contract.ts +10 -0
- package/cli/selftune/dashboard.ts +10 -5
- package/cli/selftune/eval/baseline.ts +20 -30
- package/cli/selftune/eval/hooks-to-evals.ts +22 -12
- package/cli/selftune/eval/import-skillsbench.ts +21 -8
- package/cli/selftune/eval/unit-test-cli.ts +22 -11
- package/cli/selftune/evolution/description-quality.ts +224 -0
- package/cli/selftune/evolution/evolve-body.ts +17 -10
- package/cli/selftune/evolution/evolve.ts +94 -59
- package/cli/selftune/evolution/rollback.ts +7 -6
- package/cli/selftune/evolution/unblock-suggestions.ts +159 -0
- package/cli/selftune/grading/auto-grade.ts +24 -22
- package/cli/selftune/grading/grade-session.ts +21 -17
- package/cli/selftune/hooks/auto-activate.ts +12 -3
- package/cli/selftune/hooks/prompt-log.ts +7 -1
- package/cli/selftune/index.ts +66 -69
- package/cli/selftune/ingestors/claude-replay.ts +29 -14
- package/cli/selftune/ingestors/codex-rollout.ts +6 -1
- package/cli/selftune/init.ts +212 -36
- package/cli/selftune/monitoring/watch.ts +32 -16
- package/cli/selftune/orchestrate.ts +18 -17
- package/cli/selftune/routes/skill-report.ts +17 -0
- package/cli/selftune/schedule.ts +23 -9
- package/cli/selftune/sync.ts +7 -3
- package/cli/selftune/types.ts +45 -10
- package/cli/selftune/utils/cli-error.ts +102 -0
- package/cli/selftune/utils/hooks.ts +12 -2
- package/cli/selftune/workflows/workflows.ts +23 -17
- package/package.json +1 -1
- package/skill/SKILL.md +1 -1
- package/skill/Workflows/AutoActivation.md +1 -1
- package/skill/Workflows/Evolve.md +4 -0
- package/skill/Workflows/Initialize.md +8 -8
- package/skill/settings_snippet.json +35 -12
- package/apps/local-dashboard/dist/assets/index-DIrdlu2_.js +0 -16
- package/apps/local-dashboard/dist/assets/vendor-ui-7xD7fNEU.js +0 -12
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* unblock-suggestions.ts
|
|
3
|
+
*
|
|
4
|
+
* Generates targeted, per-failure-reason suggestions when evolve doesn't deploy.
|
|
5
|
+
* Each suggestion is a concrete next CLI command or manual action that helps the
|
|
6
|
+
* agent (or user) unblock the evolution pipeline.
|
|
7
|
+
*
|
|
8
|
+
* Pure function — no I/O, no LLM calls. Depends only on EvolveResult fields and
|
|
9
|
+
* the scoreDescription heuristic.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { scoreDescription } from "./description-quality.js";
|
|
13
|
+
import type { EvolveResult } from "./evolve.js";
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Quality hint helper
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Append description quality improvement hints if the score reveals weak criteria.
|
|
21
|
+
* Only fires when composite < 0.7 to avoid noise on already-good descriptions.
|
|
22
|
+
* Skips when descriptionText is empty (no proposal was generated).
|
|
23
|
+
*/
|
|
24
|
+
function appendQualityHints(
|
|
25
|
+
suggestions: string[],
|
|
26
|
+
descriptionText: string,
|
|
27
|
+
skillName: string,
|
|
28
|
+
): void {
|
|
29
|
+
if (!descriptionText) return;
|
|
30
|
+
const score = scoreDescription(descriptionText, skillName);
|
|
31
|
+
if (score.composite >= 0.7) return;
|
|
32
|
+
|
|
33
|
+
const weak: string[] = [];
|
|
34
|
+
if (score.criteria.trigger_context < 0.5) weak.push("add when/if/after trigger context");
|
|
35
|
+
if (score.criteria.vagueness < 0.7) weak.push("remove vague words (various, general, etc)");
|
|
36
|
+
if (score.criteria.specificity < 0.5) weak.push("add concrete action verbs");
|
|
37
|
+
if (score.criteria.length < 0.7) weak.push("adjust length (ideal: 80-300 chars)");
|
|
38
|
+
if (score.criteria.not_just_name < 0.5) weak.push("differentiate from skill name");
|
|
39
|
+
|
|
40
|
+
if (weak.length > 0) {
|
|
41
|
+
suggestions.push(
|
|
42
|
+
`Description quality: ${Math.round(score.composite * 100)}% — improve by: ${weak.join(", ")}`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Main suggestion builder
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generate targeted suggestions based on the specific failure reason.
|
|
53
|
+
* Each suggestion is a concrete next CLI command or manual action.
|
|
54
|
+
*/
|
|
55
|
+
export function buildUnblockSuggestions(result: EvolveResult, skillName: string): string[] {
|
|
56
|
+
const reason = result.reason;
|
|
57
|
+
const suggestions: string[] = [];
|
|
58
|
+
const descText = result.proposal?.original_description ?? "";
|
|
59
|
+
|
|
60
|
+
// --- Path/config failures ---
|
|
61
|
+
if (reason.includes("SKILL.md not found")) {
|
|
62
|
+
suggestions.push("Verify the --skill-path flag points to a valid SKILL.md file");
|
|
63
|
+
suggestions.push("Run: selftune init (to re-bootstrap config if paths changed)");
|
|
64
|
+
return suggestions;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (reason.includes("Failed to load eval set") || reason.includes("not a JSON array")) {
|
|
68
|
+
suggestions.push("Run: selftune sync (to rebuild source-truth telemetry)");
|
|
69
|
+
suggestions.push(`Then: selftune evolve --skill ${skillName} (to retry with fresh evals)`);
|
|
70
|
+
return suggestions;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// --- No signal failures ---
|
|
74
|
+
if (reason.includes("No failure patterns found")) {
|
|
75
|
+
suggestions.push("This skill may already be routing well — check: selftune status");
|
|
76
|
+
suggestions.push("If undertriggering, add more sessions so evolve has signal to work with");
|
|
77
|
+
if (result.descriptionQualityBefore != null && result.descriptionQualityBefore < 0.5) {
|
|
78
|
+
suggestions.push(
|
|
79
|
+
`Description quality is ${Math.round(result.descriptionQualityBefore * 100)}% — manually improving the description may help generate patterns`,
|
|
80
|
+
);
|
|
81
|
+
appendQualityHints(suggestions, descText, skillName);
|
|
82
|
+
}
|
|
83
|
+
return suggestions;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// --- Confidence failures (specific before general) ---
|
|
87
|
+
if (reason.includes("No candidates met confidence")) {
|
|
88
|
+
suggestions.push(`Lower the threshold: selftune evolve --skill ${skillName} --confidence 0.4`);
|
|
89
|
+
suggestions.push(
|
|
90
|
+
`Or increase candidates: selftune evolve --skill ${skillName} --pareto --candidates 5`,
|
|
91
|
+
);
|
|
92
|
+
appendQualityHints(suggestions, descText, skillName);
|
|
93
|
+
return suggestions;
|
|
94
|
+
}
|
|
95
|
+
if (reason.toLowerCase().includes("confidence") && reason.includes("threshold")) {
|
|
96
|
+
suggestions.push(`Lower the threshold: selftune evolve --skill ${skillName} --confidence 0.4`);
|
|
97
|
+
suggestions.push("Or add more eval entries so the LLM has more context for proposals");
|
|
98
|
+
appendQualityHints(suggestions, descText, skillName);
|
|
99
|
+
return suggestions;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// --- Validation failures (proposals regressed) ---
|
|
103
|
+
if (reason.includes("Validation failed after")) {
|
|
104
|
+
suggestions.push(
|
|
105
|
+
`The eval set may be contradictory — review with: selftune evolve --skill ${skillName} --verbose`,
|
|
106
|
+
);
|
|
107
|
+
suggestions.push(
|
|
108
|
+
`Try: selftune evolve --skill ${skillName} --pareto --candidates 5 (more diverse proposals)`,
|
|
109
|
+
);
|
|
110
|
+
if (result.validation && result.validation.regressions.length > 0) {
|
|
111
|
+
suggestions.push(
|
|
112
|
+
`${result.validation.regressions.length} regressions detected — check if negative eval entries are too broad`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
appendQualityHints(suggestions, descText, skillName);
|
|
116
|
+
return suggestions;
|
|
117
|
+
}
|
|
118
|
+
if (reason.includes("No Pareto candidates improved")) {
|
|
119
|
+
suggestions.push("All candidates regressed — the eval set may need rebalancing");
|
|
120
|
+
suggestions.push(`Try: selftune sync --force && selftune evolve --skill ${skillName}`);
|
|
121
|
+
return suggestions;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// --- Gate failures ---
|
|
125
|
+
if (reason.includes("Baseline gate failed")) {
|
|
126
|
+
suggestions.push("Improvement was too marginal to justify deployment");
|
|
127
|
+
suggestions.push("Collect more session data, then retry — small gains compound over time");
|
|
128
|
+
return suggestions;
|
|
129
|
+
}
|
|
130
|
+
if (reason.includes("Gate validation failed")) {
|
|
131
|
+
suggestions.push("The gate model rejected the proposal — it may be too aggressive");
|
|
132
|
+
suggestions.push(
|
|
133
|
+
`Try: selftune evolve --skill ${skillName} --full-model (disables cheap-loop gate)`,
|
|
134
|
+
);
|
|
135
|
+
return suggestions;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// --- Constitutional rejection ---
|
|
139
|
+
if (reason.includes("Constitutional")) {
|
|
140
|
+
suggestions.push("The proposed description violated safety constraints");
|
|
141
|
+
suggestions.push("Review constitutional rules and manually adjust the description if needed");
|
|
142
|
+
return suggestions;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// --- Dry run (not really a failure) ---
|
|
146
|
+
if (reason.includes("Dry run")) {
|
|
147
|
+
suggestions.push(`Deploy: selftune evolve --skill ${skillName} (remove --dry-run to deploy)`);
|
|
148
|
+
return suggestions;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- Catch-all for unexpected errors ---
|
|
152
|
+
if (reason.includes("Error during evolution")) {
|
|
153
|
+
suggestions.push("Re-run with --verbose for full stack trace");
|
|
154
|
+
suggestions.push("Run: selftune doctor (to check system health)");
|
|
155
|
+
return suggestions;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return suggestions;
|
|
159
|
+
}
|
|
@@ -17,6 +17,7 @@ import { AGENT_CANDIDATES, TELEMETRY_LOG } from "../constants.js";
|
|
|
17
17
|
import { getDb } from "../localdb/db.js";
|
|
18
18
|
import { querySessionTelemetry, querySkillUsageRecords } from "../localdb/queries.js";
|
|
19
19
|
import type { GradingResult, SessionTelemetryRecord, SkillUsageRecord } from "../types.js";
|
|
20
|
+
import { CLIError, handleCLIError } from "../utils/cli-error.js";
|
|
20
21
|
import { detectAgent as _detectAgent } from "../utils/llm-call.js";
|
|
21
22
|
import { readExcerpt } from "../utils/transcript.js";
|
|
22
23
|
import {
|
|
@@ -62,8 +63,7 @@ Options:
|
|
|
62
63
|
|
|
63
64
|
const skill = values.skill;
|
|
64
65
|
if (!skill) {
|
|
65
|
-
|
|
66
|
-
process.exit(1);
|
|
66
|
+
throw new CLIError("--skill is required", "MISSING_FLAG", "selftune auto-grade --skill <name>");
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// --- Determine agent ---
|
|
@@ -71,10 +71,11 @@ Options:
|
|
|
71
71
|
const validAgents = [...AGENT_CANDIDATES];
|
|
72
72
|
if (values.agent) {
|
|
73
73
|
if (!validAgents.includes(values.agent)) {
|
|
74
|
-
|
|
75
|
-
`
|
|
74
|
+
throw new CLIError(
|
|
75
|
+
`Invalid --agent '${values.agent}'. Expected one of: ${validAgents.join(", ")}`,
|
|
76
|
+
"INVALID_FLAG",
|
|
77
|
+
`selftune auto-grade --skill <name> --agent ${validAgents[0]}`,
|
|
76
78
|
);
|
|
77
|
-
process.exit(1);
|
|
78
79
|
}
|
|
79
80
|
agent = values.agent;
|
|
80
81
|
} else {
|
|
@@ -82,11 +83,11 @@ Options:
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
if (!agent) {
|
|
85
|
-
|
|
86
|
-
`
|
|
87
|
-
|
|
86
|
+
throw new CLIError(
|
|
87
|
+
`No supported agent CLI (${AGENT_CANDIDATES.join("/")}) found in PATH`,
|
|
88
|
+
"AGENT_NOT_FOUND",
|
|
89
|
+
"Install one of the supported agent CLIs",
|
|
88
90
|
);
|
|
89
|
-
process.exit(1);
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
console.error(`[INFO] Auto-grade via agent: ${agent}`);
|
|
@@ -104,21 +105,22 @@ Options:
|
|
|
104
105
|
sessionId = values["session-id"];
|
|
105
106
|
const resolved = resolveSessionById(telRecords, sessionId);
|
|
106
107
|
if (!resolved) {
|
|
107
|
-
|
|
108
|
-
`
|
|
109
|
-
|
|
108
|
+
throw new CLIError(
|
|
109
|
+
`Session '${sessionId}' not found in telemetry or recoverable transcript data`,
|
|
110
|
+
"MISSING_DATA",
|
|
111
|
+
"Check the session ID or omit --session-id to auto-select the latest matching session",
|
|
110
112
|
);
|
|
111
|
-
process.exit(1);
|
|
112
113
|
}
|
|
113
114
|
telemetry = resolved.telemetry;
|
|
114
115
|
transcriptPath = resolved.transcriptPath;
|
|
115
116
|
} else {
|
|
116
117
|
const resolved = resolveLatestSessionForSkill(telRecords, skillUsageRecords, skill);
|
|
117
118
|
if (!resolved) {
|
|
118
|
-
|
|
119
|
-
`
|
|
119
|
+
throw new CLIError(
|
|
120
|
+
`No session found for skill '${skill}'`,
|
|
121
|
+
"MISSING_DATA",
|
|
122
|
+
"Run the skill first, or pass --session-id",
|
|
120
123
|
);
|
|
121
|
-
process.exit(1);
|
|
122
124
|
}
|
|
123
125
|
telemetry = resolved.telemetry;
|
|
124
126
|
sessionId = resolved.sessionId ?? "unknown";
|
|
@@ -159,8 +161,11 @@ Options:
|
|
|
159
161
|
agent,
|
|
160
162
|
});
|
|
161
163
|
} catch (err) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
throw new CLIError(
|
|
165
|
+
`Grading failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
166
|
+
"OPERATION_FAILED",
|
|
167
|
+
"Check agent availability and try again",
|
|
168
|
+
);
|
|
164
169
|
}
|
|
165
170
|
|
|
166
171
|
const outputPath = values.output ?? buildDefaultGradingOutputPath(sessionId);
|
|
@@ -193,8 +198,5 @@ Options:
|
|
|
193
198
|
|
|
194
199
|
// Guard: only run when invoked directly
|
|
195
200
|
if (import.meta.main) {
|
|
196
|
-
cliMain().catch(
|
|
197
|
-
console.error(`[FATAL] ${err}`);
|
|
198
|
-
process.exit(1);
|
|
199
|
-
});
|
|
201
|
+
cliMain().catch(handleCLIError);
|
|
200
202
|
}
|
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
SessionTelemetryRecord,
|
|
29
29
|
SkillUsageRecord,
|
|
30
30
|
} from "../types.js";
|
|
31
|
+
import { CLIError, handleCLIError } from "../utils/cli-error.js";
|
|
31
32
|
import {
|
|
32
33
|
detectAgent as _detectAgent,
|
|
33
34
|
stripMarkdownFences as _stripMarkdownFences,
|
|
@@ -743,8 +744,7 @@ Options:
|
|
|
743
744
|
|
|
744
745
|
const skill = values.skill;
|
|
745
746
|
if (!skill) {
|
|
746
|
-
|
|
747
|
-
process.exit(1);
|
|
747
|
+
throw new CLIError("--skill is required", "MISSING_FLAG", "selftune grade --skill <name>");
|
|
748
748
|
}
|
|
749
749
|
|
|
750
750
|
// --- Determine agent ---
|
|
@@ -752,10 +752,11 @@ Options:
|
|
|
752
752
|
const validAgents = [...AGENT_CANDIDATES];
|
|
753
753
|
if (values.agent) {
|
|
754
754
|
if (!validAgents.includes(values.agent)) {
|
|
755
|
-
|
|
756
|
-
`
|
|
755
|
+
throw new CLIError(
|
|
756
|
+
`Invalid --agent '${values.agent}'. Expected one of: ${validAgents.join(", ")}`,
|
|
757
|
+
"INVALID_FLAG",
|
|
758
|
+
`selftune grade --skill <name> --agent ${validAgents[0]}`,
|
|
757
759
|
);
|
|
758
|
-
process.exit(1);
|
|
759
760
|
}
|
|
760
761
|
agent = values.agent;
|
|
761
762
|
} else {
|
|
@@ -763,11 +764,11 @@ Options:
|
|
|
763
764
|
}
|
|
764
765
|
|
|
765
766
|
if (!agent) {
|
|
766
|
-
|
|
767
|
-
`
|
|
768
|
-
|
|
767
|
+
throw new CLIError(
|
|
768
|
+
`No supported agent CLI (${AGENT_CANDIDATES.join("/")}) found in PATH`,
|
|
769
|
+
"AGENT_NOT_FOUND",
|
|
770
|
+
"Install claude, codex, or opencode CLI, then retry",
|
|
769
771
|
);
|
|
770
|
-
process.exit(1);
|
|
771
772
|
}
|
|
772
773
|
|
|
773
774
|
console.error(`[INFO] Grading via agent: ${agent}`);
|
|
@@ -777,8 +778,11 @@ Options:
|
|
|
777
778
|
if (values["evals-json"] && values["eval-id"] != null) {
|
|
778
779
|
const evalIdNum = Number(values["eval-id"]);
|
|
779
780
|
if (!Number.isFinite(evalIdNum) || !Number.isInteger(evalIdNum)) {
|
|
780
|
-
|
|
781
|
-
|
|
781
|
+
throw new CLIError(
|
|
782
|
+
`--eval-id must be a finite integer, got: ${values["eval-id"]}`,
|
|
783
|
+
"INVALID_FLAG",
|
|
784
|
+
"selftune grade --eval-id <integer>",
|
|
785
|
+
);
|
|
782
786
|
}
|
|
783
787
|
expectations = loadExpectationsFromEvalsJson(values["evals-json"], evalIdNum);
|
|
784
788
|
} else if (values.expectations?.length) {
|
|
@@ -863,8 +867,11 @@ Options:
|
|
|
863
867
|
agent,
|
|
864
868
|
});
|
|
865
869
|
} catch (err) {
|
|
866
|
-
|
|
867
|
-
|
|
870
|
+
throw new CLIError(
|
|
871
|
+
`Grading failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
872
|
+
"OPERATION_FAILED",
|
|
873
|
+
"Check agent availability and try again",
|
|
874
|
+
);
|
|
868
875
|
}
|
|
869
876
|
|
|
870
877
|
const outputPath = values.output ?? buildDefaultGradingOutputPath(sessionId);
|
|
@@ -888,8 +895,5 @@ Options:
|
|
|
888
895
|
|
|
889
896
|
// Guard: only run when invoked directly
|
|
890
897
|
if (import.meta.main) {
|
|
891
|
-
cliMain().catch(
|
|
892
|
-
console.error(`[FATAL] ${err}`);
|
|
893
|
-
process.exit(1);
|
|
894
|
-
});
|
|
898
|
+
cliMain().catch(handleCLIError);
|
|
895
899
|
}
|
|
@@ -179,9 +179,18 @@ if (import.meta.main) {
|
|
|
179
179
|
const statePath = sessionStatePath(sessionId);
|
|
180
180
|
const suggestions = evaluateRules(DEFAULT_RULES, ctx, statePath);
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
// Output
|
|
184
|
-
|
|
182
|
+
if (suggestions.length > 0) {
|
|
183
|
+
// Output as JSON with additionalContext — Claude Code adds this to
|
|
184
|
+
// Claude's context on UserPromptSubmit (more reliable than stderr)
|
|
185
|
+
const context = suggestions.map((s) => `[selftune] Suggestion: ${s}`).join("\n");
|
|
186
|
+
process.stdout.write(
|
|
187
|
+
JSON.stringify({
|
|
188
|
+
hookSpecificOutput: {
|
|
189
|
+
hookEventName: "UserPromptSubmit",
|
|
190
|
+
additionalContext: context,
|
|
191
|
+
},
|
|
192
|
+
}),
|
|
193
|
+
);
|
|
185
194
|
}
|
|
186
195
|
}
|
|
187
196
|
} catch {
|
|
@@ -154,7 +154,13 @@ export async function processPrompt(
|
|
|
154
154
|
promptStatePath?: string,
|
|
155
155
|
_signalLogPath?: string,
|
|
156
156
|
): Promise<QueryLogRecord | null> {
|
|
157
|
-
const
|
|
157
|
+
const rawPrompt =
|
|
158
|
+
typeof payload.prompt === "string"
|
|
159
|
+
? payload.prompt
|
|
160
|
+
: typeof payload.user_prompt === "string"
|
|
161
|
+
? payload.user_prompt
|
|
162
|
+
: "";
|
|
163
|
+
const query = rawPrompt.trim();
|
|
158
164
|
|
|
159
165
|
if (!query) return null;
|
|
160
166
|
|
package/cli/selftune/index.ts
CHANGED
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
* selftune hook <name> — Run a hook by name (prompt-log, session-stop, etc.)
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
+
import { CLIError, handleCLIError } from "./utils/cli-error.js";
|
|
33
|
+
|
|
34
|
+
process.on("uncaughtException", handleCLIError);
|
|
35
|
+
process.on("unhandledRejection", handleCLIError);
|
|
36
|
+
|
|
32
37
|
const command = process.argv[2];
|
|
33
38
|
|
|
34
39
|
if (command === "--help" || command === "-h") {
|
|
@@ -84,6 +89,7 @@ if (!command) {
|
|
|
84
89
|
// Show status by default — same as `selftune status`
|
|
85
90
|
const { cliMain: statusMain } = await import("./status.js");
|
|
86
91
|
statusMain();
|
|
92
|
+
process.exit(0);
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
// Route to the appropriate subcommand module.
|
|
@@ -142,10 +148,11 @@ Run 'selftune ingest <agent> --help' for agent-specific options.`);
|
|
|
142
148
|
break;
|
|
143
149
|
}
|
|
144
150
|
default:
|
|
145
|
-
|
|
146
|
-
`Unknown ingest agent: ${sub}
|
|
151
|
+
throw new CLIError(
|
|
152
|
+
`Unknown ingest agent: ${sub}`,
|
|
153
|
+
"UNKNOWN_COMMAND",
|
|
154
|
+
"selftune ingest --help",
|
|
147
155
|
);
|
|
148
|
-
process.exit(1);
|
|
149
156
|
}
|
|
150
157
|
break;
|
|
151
158
|
}
|
|
@@ -182,10 +189,11 @@ Run 'selftune grade <subcommand> --help' for subcommand-specific options.`);
|
|
|
182
189
|
break;
|
|
183
190
|
}
|
|
184
191
|
default:
|
|
185
|
-
|
|
186
|
-
`Unknown grade mode: ${sub}
|
|
192
|
+
throw new CLIError(
|
|
193
|
+
`Unknown grade mode: ${sub}`,
|
|
194
|
+
"UNKNOWN_COMMAND",
|
|
195
|
+
"selftune grade --help",
|
|
187
196
|
);
|
|
188
|
-
process.exit(1);
|
|
189
197
|
}
|
|
190
198
|
}
|
|
191
199
|
break;
|
|
@@ -223,10 +231,11 @@ Run 'selftune evolve <subcommand> --help' for subcommand-specific options.`);
|
|
|
223
231
|
break;
|
|
224
232
|
}
|
|
225
233
|
default:
|
|
226
|
-
|
|
227
|
-
`Unknown evolve target: ${sub}
|
|
234
|
+
throw new CLIError(
|
|
235
|
+
`Unknown evolve target: ${sub}`,
|
|
236
|
+
"UNKNOWN_COMMAND",
|
|
237
|
+
"selftune evolve --help",
|
|
228
238
|
);
|
|
229
|
-
process.exit(1);
|
|
230
239
|
}
|
|
231
240
|
}
|
|
232
241
|
break;
|
|
@@ -289,13 +298,18 @@ Run 'selftune eval <action> --help' for action-specific options.`);
|
|
|
289
298
|
}));
|
|
290
299
|
} catch (error) {
|
|
291
300
|
const message = error instanceof Error ? error.message : String(error);
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
301
|
+
throw new CLIError(
|
|
302
|
+
`Invalid arguments: ${message}`,
|
|
303
|
+
"INVALID_FLAG",
|
|
304
|
+
"selftune eval composability --help",
|
|
305
|
+
);
|
|
295
306
|
}
|
|
296
307
|
if (!values.skill) {
|
|
297
|
-
|
|
298
|
-
|
|
308
|
+
throw new CLIError(
|
|
309
|
+
"--skill <name> is required.",
|
|
310
|
+
"MISSING_FLAG",
|
|
311
|
+
"selftune eval composability --skill <name>",
|
|
312
|
+
);
|
|
299
313
|
}
|
|
300
314
|
const logPath = values["telemetry-log"] ?? TELEMETRY_LOG;
|
|
301
315
|
let telemetry: unknown[];
|
|
@@ -316,8 +330,11 @@ Run 'selftune eval <action> --help' for action-specific options.`);
|
|
|
316
330
|
}
|
|
317
331
|
const rawWindow = values.window as string | undefined;
|
|
318
332
|
if (rawWindow !== undefined && !/^[1-9]\d*$/.test(rawWindow)) {
|
|
319
|
-
|
|
320
|
-
|
|
333
|
+
throw new CLIError(
|
|
334
|
+
"Invalid --window value. Use a positive integer number of days.",
|
|
335
|
+
"INVALID_FLAG",
|
|
336
|
+
"selftune eval composability --skill <name> --window 30",
|
|
337
|
+
);
|
|
321
338
|
}
|
|
322
339
|
const windowSize = rawWindow === undefined ? undefined : Number(rawWindow);
|
|
323
340
|
const report = analyzeComposability(values.skill, telemetry, windowSize);
|
|
@@ -325,10 +342,11 @@ Run 'selftune eval <action> --help' for action-specific options.`);
|
|
|
325
342
|
break;
|
|
326
343
|
}
|
|
327
344
|
default:
|
|
328
|
-
|
|
329
|
-
`Unknown eval action: ${sub}
|
|
345
|
+
throw new CLIError(
|
|
346
|
+
`Unknown eval action: ${sub}`,
|
|
347
|
+
"UNKNOWN_COMMAND",
|
|
348
|
+
"selftune eval --help",
|
|
330
349
|
);
|
|
331
|
-
process.exit(1);
|
|
332
350
|
}
|
|
333
351
|
break;
|
|
334
352
|
}
|
|
@@ -457,10 +475,11 @@ Run 'selftune cron <subcommand> --help' for subcommand-specific options.`);
|
|
|
457
475
|
break;
|
|
458
476
|
}
|
|
459
477
|
default:
|
|
460
|
-
|
|
461
|
-
`Unknown cron subcommand: ${sub}
|
|
478
|
+
throw new CLIError(
|
|
479
|
+
`Unknown cron subcommand: ${sub}`,
|
|
480
|
+
"UNKNOWN_COMMAND",
|
|
481
|
+
"selftune cron --help",
|
|
462
482
|
);
|
|
463
|
-
process.exit(1);
|
|
464
483
|
}
|
|
465
484
|
break;
|
|
466
485
|
}
|
|
@@ -505,9 +524,7 @@ Run 'selftune cron <subcommand> --help' for subcommand-specific options.`);
|
|
|
505
524
|
}));
|
|
506
525
|
} catch (error) {
|
|
507
526
|
const message = error instanceof Error ? error.message : String(error);
|
|
508
|
-
|
|
509
|
-
console.error("Run 'selftune export --help' for usage.");
|
|
510
|
-
process.exit(1);
|
|
527
|
+
throw new CLIError(`Invalid arguments: ${message}`, "INVALID_FLAG", "selftune export --help");
|
|
511
528
|
}
|
|
512
529
|
if (values.help) {
|
|
513
530
|
console.log(`selftune export — Export SQLite data to JSONL files
|
|
@@ -544,9 +561,7 @@ Options:
|
|
|
544
561
|
}
|
|
545
562
|
} catch (err: unknown) {
|
|
546
563
|
const message = err instanceof Error ? err.message : String(err);
|
|
547
|
-
|
|
548
|
-
console.error("Ensure the SQLite database exists. Run 'selftune sync' first if needed.");
|
|
549
|
-
process.exit(1);
|
|
564
|
+
throw new CLIError(`Export failed: ${message}`, "OPERATION_FAILED", "selftune sync");
|
|
550
565
|
}
|
|
551
566
|
break;
|
|
552
567
|
}
|
|
@@ -590,8 +605,11 @@ Run 'selftune alpha <subcommand> --help' for subcommand-specific options.`);
|
|
|
590
605
|
}));
|
|
591
606
|
} catch (error) {
|
|
592
607
|
const message = error instanceof Error ? error.message : String(error);
|
|
593
|
-
|
|
594
|
-
|
|
608
|
+
throw new CLIError(
|
|
609
|
+
`Invalid arguments: ${message}`,
|
|
610
|
+
"INVALID_FLAG",
|
|
611
|
+
"selftune alpha upload --help",
|
|
612
|
+
);
|
|
595
613
|
}
|
|
596
614
|
if (values.help) {
|
|
597
615
|
console.log(`selftune alpha upload — Run a manual alpha data upload cycle
|
|
@@ -619,44 +637,20 @@ Output:
|
|
|
619
637
|
const identity = readAlphaIdentity(SELFTUNE_CONFIG_PATH);
|
|
620
638
|
if (!identity?.enrolled) {
|
|
621
639
|
const guidance = getAlphaGuidance(identity);
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
prepared: 0,
|
|
627
|
-
sent: 0,
|
|
628
|
-
failed: 0,
|
|
629
|
-
skipped: 0,
|
|
630
|
-
guidance,
|
|
631
|
-
},
|
|
632
|
-
null,
|
|
633
|
-
2,
|
|
634
|
-
),
|
|
640
|
+
throw new CLIError(
|
|
641
|
+
`[alpha upload] ${guidance.message}`,
|
|
642
|
+
"OPERATION_FAILED",
|
|
643
|
+
guidance.next_command,
|
|
635
644
|
);
|
|
636
|
-
console.error(`[alpha upload] ${guidance.message}`);
|
|
637
|
-
console.error(`[alpha upload] Next: ${guidance.next_command}`);
|
|
638
|
-
process.exit(1);
|
|
639
645
|
}
|
|
640
646
|
|
|
641
647
|
if (!identity.user_id?.trim() || !identity.api_key?.trim()) {
|
|
642
648
|
const guidance = getAlphaGuidance(identity);
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
prepared: 0,
|
|
648
|
-
sent: 0,
|
|
649
|
-
failed: 0,
|
|
650
|
-
skipped: 0,
|
|
651
|
-
guidance,
|
|
652
|
-
},
|
|
653
|
-
null,
|
|
654
|
-
2,
|
|
655
|
-
),
|
|
649
|
+
throw new CLIError(
|
|
650
|
+
`[alpha upload] ${guidance.message}`,
|
|
651
|
+
"OPERATION_FAILED",
|
|
652
|
+
guidance.next_command,
|
|
656
653
|
);
|
|
657
|
-
console.error(`[alpha upload] ${guidance.message}`);
|
|
658
|
-
console.error(`[alpha upload] Next: ${guidance.next_command}`);
|
|
659
|
-
process.exit(1);
|
|
660
654
|
}
|
|
661
655
|
|
|
662
656
|
const db = getDb();
|
|
@@ -742,10 +736,11 @@ Output:
|
|
|
742
736
|
break;
|
|
743
737
|
}
|
|
744
738
|
default:
|
|
745
|
-
|
|
746
|
-
`Unknown alpha subcommand: ${sub}
|
|
739
|
+
throw new CLIError(
|
|
740
|
+
`Unknown alpha subcommand: ${sub}`,
|
|
741
|
+
"UNKNOWN_COMMAND",
|
|
742
|
+
"selftune alpha --help",
|
|
747
743
|
);
|
|
748
|
-
process.exit(1);
|
|
749
744
|
}
|
|
750
745
|
break;
|
|
751
746
|
}
|
|
@@ -767,8 +762,11 @@ Output:
|
|
|
767
762
|
};
|
|
768
763
|
if (!hookName || !HOOK_MAP[hookName]) {
|
|
769
764
|
const available = Object.keys(HOOK_MAP).join(", ");
|
|
770
|
-
|
|
771
|
-
|
|
765
|
+
throw new CLIError(
|
|
766
|
+
`Unknown hook: ${hookName ?? "(none)"}. Available: ${available}`,
|
|
767
|
+
"UNKNOWN_COMMAND",
|
|
768
|
+
"selftune hook prompt-log",
|
|
769
|
+
);
|
|
772
770
|
}
|
|
773
771
|
const { resolve, dirname } = await import("node:path");
|
|
774
772
|
const { fileURLToPath } = await import("node:url");
|
|
@@ -782,6 +780,5 @@ Output:
|
|
|
782
780
|
break;
|
|
783
781
|
}
|
|
784
782
|
default:
|
|
785
|
-
|
|
786
|
-
process.exit(1);
|
|
783
|
+
throw new CLIError(`Unknown command: ${command}`, "UNKNOWN_COMMAND", "selftune --help");
|
|
787
784
|
}
|