selftune 0.2.30 → 0.2.32
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 +83 -56
- package/apps/local-dashboard/dist/assets/index-B-ut4w0B.js +15 -0
- package/apps/local-dashboard/dist/assets/index-BFGfCVrL.css +1 -0
- package/apps/local-dashboard/dist/assets/vendor-ui-DfowE3Hu.js +1 -0
- package/apps/local-dashboard/dist/index.html +3 -3
- package/cli/selftune/command-surface.ts +613 -2
- package/cli/selftune/create/baseline.ts +429 -0
- package/cli/selftune/create/check.ts +35 -0
- package/cli/selftune/create/init.ts +115 -0
- package/cli/selftune/create/package-candidate-state.ts +771 -0
- package/cli/selftune/create/package-evaluator.ts +710 -0
- package/cli/selftune/create/package-fingerprint.ts +142 -0
- package/cli/selftune/create/package-search.ts +377 -0
- package/cli/selftune/create/publish.ts +431 -0
- package/cli/selftune/create/readiness.ts +495 -0
- package/cli/selftune/create/replay.ts +330 -0
- package/cli/selftune/create/report.ts +74 -0
- package/cli/selftune/create/scaffold.ts +121 -0
- package/cli/selftune/create/skills-ref-adapter.ts +177 -0
- package/cli/selftune/create/status.ts +33 -0
- package/cli/selftune/create/templates.ts +249 -0
- package/cli/selftune/cron/setup.ts +1 -1
- package/cli/selftune/dashboard-action-events.ts +4 -1
- package/cli/selftune/dashboard-action-result.ts +789 -24
- package/cli/selftune/dashboard-action-stream.ts +80 -0
- package/cli/selftune/dashboard-contract.ts +146 -3
- package/cli/selftune/dashboard-server.ts +5 -4
- package/cli/selftune/eval/hooks-to-evals.ts +58 -35
- package/cli/selftune/eval/synthetic-evals.ts +145 -17
- package/cli/selftune/evolution/bounded-mutations.ts +1045 -0
- package/cli/selftune/evolution/evolve-body.ts +9 -36
- package/cli/selftune/evolution/evolve.ts +8 -72
- package/cli/selftune/evolution/stopping-criteria.ts +5 -13
- package/cli/selftune/evolution/unblock-suggestions.ts +0 -16
- package/cli/selftune/evolution/validate-host-replay.ts +115 -15
- package/cli/selftune/improve.ts +206 -0
- package/cli/selftune/index.ts +123 -6
- package/cli/selftune/init.ts +1 -1
- package/cli/selftune/localdb/queries/dashboard.ts +30 -0
- package/cli/selftune/localdb/schema.ts +52 -0
- package/cli/selftune/monitoring/watch.ts +257 -23
- package/cli/selftune/orchestrate/execute.ts +300 -1
- package/cli/selftune/orchestrate/finalize.ts +14 -0
- package/cli/selftune/orchestrate/plan.ts +22 -5
- package/cli/selftune/orchestrate/prepare.ts +59 -4
- package/cli/selftune/orchestrate/report.ts +1 -1
- package/cli/selftune/orchestrate.ts +34 -1
- package/cli/selftune/publish.ts +35 -0
- package/cli/selftune/registry/github-install.ts +256 -0
- package/cli/selftune/registry/index.ts +1 -1
- package/cli/selftune/registry/install.ts +58 -7
- package/cli/selftune/routes/actions.ts +81 -15
- package/cli/selftune/routes/overview.ts +1 -1
- package/cli/selftune/routes/skill-report.ts +147 -2
- package/cli/selftune/run.ts +18 -0
- package/cli/selftune/schedule.ts +3 -3
- package/cli/selftune/search-run.ts +703 -0
- package/cli/selftune/status.ts +35 -11
- package/cli/selftune/testing-readiness.ts +431 -40
- package/cli/selftune/types.ts +316 -0
- package/cli/selftune/utils/eval-readiness.ts +1 -0
- package/cli/selftune/utils/json-output.ts +11 -0
- package/cli/selftune/utils/lifecycle-surface.ts +48 -0
- package/cli/selftune/utils/query-filter.ts +82 -1
- package/cli/selftune/utils/tui.ts +85 -2
- package/cli/selftune/verify.ts +205 -0
- package/cli/selftune/workflows/proposals.ts +1 -1
- package/cli/selftune/workflows/skill-scaffold.ts +141 -63
- package/cli/selftune/workflows/workflows.ts +4 -4
- package/package.json +1 -1
- package/packages/dashboard-core/src/routes/manifest.ts +2 -2
- package/packages/ui/src/components/SkillReportPanels.tsx +7 -7
- package/packages/ui/src/primitives/button.tsx +5 -0
- package/skill/SKILL.md +148 -85
- package/skill/references/cli-quick-reference.md +16 -1
- package/skill/references/creator-playbook.md +31 -10
- package/skill/workflows/Baseline.md +8 -9
- package/skill/workflows/Contributions.md +4 -4
- package/skill/workflows/Create.md +173 -0
- package/skill/workflows/CreateTestDeploy.md +34 -30
- package/skill/workflows/Cron.md +2 -2
- package/skill/workflows/Dashboard.md +3 -3
- package/skill/workflows/Evals.md +13 -7
- package/skill/workflows/Evolve.md +75 -32
- package/skill/workflows/EvolveBody.md +22 -15
- package/skill/workflows/Hook.md +1 -1
- package/skill/workflows/Improve.md +168 -0
- package/skill/workflows/Initialize.md +3 -3
- package/skill/workflows/Orchestrate.md +49 -12
- package/skill/workflows/Publish.md +100 -0
- package/skill/workflows/Registry.md +19 -13
- package/skill/workflows/Run.md +72 -0
- package/skill/workflows/Schedule.md +2 -2
- package/skill/workflows/SearchRun.md +89 -0
- package/skill/workflows/SignalsDashboard.md +2 -2
- package/skill/workflows/UnitTest.md +13 -4
- package/skill/workflows/Verify.md +136 -0
- package/skill/workflows/Watch.md +114 -47
- package/skill/workflows/Workflows.md +13 -8
- package/apps/local-dashboard/dist/assets/index-BcXquWFB.css +0 -1
- package/apps/local-dashboard/dist/assets/index-Coq42hE4.js +0 -15
- package/apps/local-dashboard/dist/assets/vendor-ui-B0H8s1mP.js +0 -1
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
|
|
4
|
+
import { PUBLIC_COMMAND_SURFACES, renderCommandHelp } from "../command-surface.js";
|
|
5
|
+
import type { WatchResult } from "../monitoring/watch.js";
|
|
6
|
+
import { computeWatchTrustScore } from "../monitoring/watch.js";
|
|
7
|
+
import {
|
|
8
|
+
readCanonicalPackageEvaluationArtifact,
|
|
9
|
+
writeCanonicalPackageEvaluation,
|
|
10
|
+
} from "../testing-readiness.js";
|
|
11
|
+
import type {
|
|
12
|
+
CreatePackageEvaluationSummary,
|
|
13
|
+
CreatePackageEvaluationWatchSummary,
|
|
14
|
+
} from "../types.js";
|
|
15
|
+
import { CLIError, handleCLIError } from "../utils/cli-error.js";
|
|
16
|
+
import { extractJsonObject } from "../utils/json-output.js";
|
|
17
|
+
import { refreshPackageCandidateEvaluationObservation } from "./package-candidate-state.js";
|
|
18
|
+
import {
|
|
19
|
+
attachCreatePackageWatchSummary,
|
|
20
|
+
runCreatePackageEvaluation,
|
|
21
|
+
} from "./package-evaluator.js";
|
|
22
|
+
import { computeCreateCheckResult } from "./readiness.js";
|
|
23
|
+
|
|
24
|
+
export interface CreatePublishResult {
|
|
25
|
+
skill: string;
|
|
26
|
+
skill_path: string;
|
|
27
|
+
published: boolean;
|
|
28
|
+
watch_started: boolean;
|
|
29
|
+
watch_gate_blocked: boolean;
|
|
30
|
+
next_command: string | null;
|
|
31
|
+
package_evaluation: CreatePackageEvaluationSummary | null;
|
|
32
|
+
replay_exit_code: number | null;
|
|
33
|
+
baseline_exit_code: number | null;
|
|
34
|
+
watch_exit_code: number | null;
|
|
35
|
+
watch_result: WatchResult | null;
|
|
36
|
+
watch_stdout: string;
|
|
37
|
+
watch_stderr: string;
|
|
38
|
+
watch_gate_passed: boolean | null;
|
|
39
|
+
watch_gate_warnings: string[];
|
|
40
|
+
watch_trust_score: number | null;
|
|
41
|
+
watch_gate_bypassed: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface CreatePublishDeps {
|
|
45
|
+
spawnSync?: typeof Bun.spawnSync;
|
|
46
|
+
computeCreateCheckResult?: typeof computeCreateCheckResult;
|
|
47
|
+
runCreatePackageEvaluation?: typeof runCreatePackageEvaluation;
|
|
48
|
+
refreshPackageCandidateEvaluationObservation?: typeof refreshPackageCandidateEvaluationObservation;
|
|
49
|
+
writeCanonicalPackageEvaluation?: typeof writeCanonicalPackageEvaluation;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function hydrateWatchResult(summary: CreatePackageEvaluationWatchSummary): WatchResult {
|
|
53
|
+
return {
|
|
54
|
+
snapshot: summary.snapshot,
|
|
55
|
+
alert: summary.alert,
|
|
56
|
+
rolledBack: summary.rolled_back,
|
|
57
|
+
recommendation: summary.recommendation,
|
|
58
|
+
recommended_command: summary.recommended_command,
|
|
59
|
+
gradeAlert: summary.grade_alert,
|
|
60
|
+
gradeRegression: summary.grade_regression,
|
|
61
|
+
...(summary.efficiency_alert || summary.efficiency_regression
|
|
62
|
+
? {
|
|
63
|
+
efficiencyAlert: summary.efficiency_alert ?? null,
|
|
64
|
+
efficiencyRegression: summary.efficiency_regression ?? null,
|
|
65
|
+
}
|
|
66
|
+
: {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function runSelftuneCommand(
|
|
71
|
+
command: string[],
|
|
72
|
+
deps: CreatePublishDeps = {},
|
|
73
|
+
): { exitCode: number | null; stdout: string; stderr: string } {
|
|
74
|
+
const spawnSync = deps.spawnSync ?? Bun.spawnSync;
|
|
75
|
+
const indexPath = join(import.meta.dir, "..", "index.ts");
|
|
76
|
+
const result = spawnSync(["bun", "run", indexPath, ...command], {
|
|
77
|
+
stdout: "pipe",
|
|
78
|
+
stderr: "pipe",
|
|
79
|
+
env: process.env,
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
exitCode: result.exitCode,
|
|
83
|
+
stdout: Buffer.from(result.stdout).toString("utf-8"),
|
|
84
|
+
stderr: Buffer.from(result.stderr).toString("utf-8"),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseWatchResult(stdout: string): WatchResult | null {
|
|
89
|
+
const parsed = extractJsonObject(stdout);
|
|
90
|
+
if (!parsed) return null;
|
|
91
|
+
if (!parsed["snapshot"] || typeof parsed["snapshot"] !== "object") return null;
|
|
92
|
+
return parsed as WatchResult;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function runCreatePublish(
|
|
96
|
+
options: { skillPath: string; watch?: boolean; ignoreWatchAlerts?: boolean },
|
|
97
|
+
deps: CreatePublishDeps = {},
|
|
98
|
+
): Promise<CreatePublishResult> {
|
|
99
|
+
const check = await (deps.computeCreateCheckResult ?? computeCreateCheckResult)(
|
|
100
|
+
options.skillPath,
|
|
101
|
+
);
|
|
102
|
+
if (check.state !== "ready_to_publish") {
|
|
103
|
+
throw new CLIError(
|
|
104
|
+
`Draft package "${check.skill}" is not ready to publish (${check.state}).`,
|
|
105
|
+
"INVALID_STATUS",
|
|
106
|
+
check.next_command ?? `selftune create check --skill-path ${check.skill_path}`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const evaluation = await (deps.runCreatePackageEvaluation ?? runCreatePackageEvaluation)({
|
|
111
|
+
skillPath: check.skill_path,
|
|
112
|
+
});
|
|
113
|
+
const replayExitCode = evaluation.replay.failed === 0 ? 0 : 1;
|
|
114
|
+
const baselineExitCode =
|
|
115
|
+
evaluation.summary.status === "replay_failed" ? null : evaluation.baseline.adds_value ? 0 : 1;
|
|
116
|
+
|
|
117
|
+
if (!evaluation.summary.evaluation_passed) {
|
|
118
|
+
return {
|
|
119
|
+
skill: check.skill,
|
|
120
|
+
skill_path: check.skill_path,
|
|
121
|
+
published: false,
|
|
122
|
+
watch_started: false,
|
|
123
|
+
watch_gate_blocked: false,
|
|
124
|
+
next_command: evaluation.summary.next_command,
|
|
125
|
+
package_evaluation: evaluation.summary,
|
|
126
|
+
replay_exit_code: replayExitCode,
|
|
127
|
+
baseline_exit_code: baselineExitCode,
|
|
128
|
+
watch_exit_code: null,
|
|
129
|
+
watch_result: null,
|
|
130
|
+
watch_stdout: "",
|
|
131
|
+
watch_stderr: "",
|
|
132
|
+
watch_gate_passed: null,
|
|
133
|
+
watch_gate_warnings: [],
|
|
134
|
+
watch_trust_score: null,
|
|
135
|
+
watch_gate_bypassed: false,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!options.watch) {
|
|
140
|
+
return {
|
|
141
|
+
skill: check.skill,
|
|
142
|
+
skill_path: check.skill_path,
|
|
143
|
+
published: true,
|
|
144
|
+
watch_started: false,
|
|
145
|
+
watch_gate_blocked: false,
|
|
146
|
+
next_command: `selftune watch --skill ${check.skill} --skill-path ${check.skill_path}`,
|
|
147
|
+
package_evaluation: evaluation.summary,
|
|
148
|
+
replay_exit_code: replayExitCode,
|
|
149
|
+
baseline_exit_code: baselineExitCode,
|
|
150
|
+
watch_exit_code: null,
|
|
151
|
+
watch_result: null,
|
|
152
|
+
watch_stdout: "",
|
|
153
|
+
watch_stderr: "",
|
|
154
|
+
watch_gate_passed: null,
|
|
155
|
+
watch_gate_warnings: [],
|
|
156
|
+
watch_trust_score: null,
|
|
157
|
+
watch_gate_bypassed: false,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const priorWatch = readCanonicalPackageEvaluationArtifact(check.skill)?.summary.watch;
|
|
162
|
+
const watch = runSelftuneCommand(
|
|
163
|
+
["watch", "--skill", check.skill, "--skill-path", check.skill_path, "--sync-first"],
|
|
164
|
+
deps,
|
|
165
|
+
);
|
|
166
|
+
const watchResult = parseWatchResult(watch.stdout);
|
|
167
|
+
const packageEvaluationResult = watchResult
|
|
168
|
+
? {
|
|
169
|
+
...evaluation,
|
|
170
|
+
summary: attachCreatePackageWatchSummary(evaluation.summary, watchResult),
|
|
171
|
+
}
|
|
172
|
+
: evaluation;
|
|
173
|
+
const packageEvaluation = packageEvaluationResult.summary;
|
|
174
|
+
|
|
175
|
+
if (watchResult) {
|
|
176
|
+
try {
|
|
177
|
+
(
|
|
178
|
+
deps.refreshPackageCandidateEvaluationObservation ??
|
|
179
|
+
refreshPackageCandidateEvaluationObservation
|
|
180
|
+
)(packageEvaluationResult);
|
|
181
|
+
} catch {
|
|
182
|
+
// Fail-open: candidate observation refresh should not block publish/watch results.
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const recentWatchResults = [
|
|
187
|
+
...(priorWatch ? [hydrateWatchResult(priorWatch)] : []),
|
|
188
|
+
...(watchResult ? [watchResult] : []),
|
|
189
|
+
];
|
|
190
|
+
const baseWatchGate = checkPublishWatchGate({
|
|
191
|
+
skillName: check.skill,
|
|
192
|
+
recentWatchResults,
|
|
193
|
+
ignoreWatchAlerts: options.ignoreWatchAlerts,
|
|
194
|
+
});
|
|
195
|
+
const watchGate =
|
|
196
|
+
watchResult == null
|
|
197
|
+
? {
|
|
198
|
+
passed: options.ignoreWatchAlerts === true,
|
|
199
|
+
warnings: [
|
|
200
|
+
`Watch for "${check.skill}" did not return structured JSON output${watch.exitCode != null ? ` (exit ${watch.exitCode})` : ""}. Re-run watch before publishing.`,
|
|
201
|
+
...baseWatchGate.warnings,
|
|
202
|
+
],
|
|
203
|
+
trustScore: baseWatchGate.trustScore,
|
|
204
|
+
bypassed: options.ignoreWatchAlerts === true,
|
|
205
|
+
}
|
|
206
|
+
: baseWatchGate;
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
(deps.writeCanonicalPackageEvaluation ?? writeCanonicalPackageEvaluation)(
|
|
210
|
+
check.skill,
|
|
211
|
+
packageEvaluation,
|
|
212
|
+
);
|
|
213
|
+
} catch {
|
|
214
|
+
// Fail-open: post-watch persistence should not block publish/watch results.
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const watchGateBlocked = !watchGate.passed;
|
|
218
|
+
const watchRemediationCommand = `selftune watch --skill ${check.skill} --skill-path ${check.skill_path}`;
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
skill: check.skill,
|
|
222
|
+
skill_path: check.skill_path,
|
|
223
|
+
published: !watchGateBlocked,
|
|
224
|
+
watch_started: watch.exitCode === 0 && watchResult != null,
|
|
225
|
+
watch_gate_blocked: watchGateBlocked,
|
|
226
|
+
next_command: watchGateBlocked
|
|
227
|
+
? watchRemediationCommand
|
|
228
|
+
: watch.exitCode === 0
|
|
229
|
+
? null
|
|
230
|
+
: (watchResult?.recommended_command ?? watchRemediationCommand),
|
|
231
|
+
package_evaluation: packageEvaluation,
|
|
232
|
+
replay_exit_code: replayExitCode,
|
|
233
|
+
baseline_exit_code: baselineExitCode,
|
|
234
|
+
watch_exit_code: watch.exitCode,
|
|
235
|
+
watch_result: watchResult,
|
|
236
|
+
watch_stdout: watch.stdout,
|
|
237
|
+
watch_stderr: watch.stderr,
|
|
238
|
+
watch_gate_passed: watchGate.passed,
|
|
239
|
+
watch_gate_warnings: watchGate.warnings,
|
|
240
|
+
watch_trust_score: watchGate.trustScore,
|
|
241
|
+
watch_gate_bypassed: watchGate.bypassed,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function formatCreatePublishResult(result: CreatePublishResult): string {
|
|
246
|
+
const replayStatus =
|
|
247
|
+
result.package_evaluation == null
|
|
248
|
+
? "not run"
|
|
249
|
+
: result.package_evaluation.replay.failed === 0
|
|
250
|
+
? "passed"
|
|
251
|
+
: "failed";
|
|
252
|
+
const baselineStatus =
|
|
253
|
+
result.package_evaluation == null
|
|
254
|
+
? "not run"
|
|
255
|
+
: result.package_evaluation.baseline.adds_value
|
|
256
|
+
? "passed"
|
|
257
|
+
: "failed";
|
|
258
|
+
|
|
259
|
+
return [
|
|
260
|
+
`Skill: ${result.skill}`,
|
|
261
|
+
result.package_evaluation
|
|
262
|
+
? `Evaluation source: ${
|
|
263
|
+
result.package_evaluation.evaluation_source === "artifact_cache"
|
|
264
|
+
? "cached artifact"
|
|
265
|
+
: result.package_evaluation.evaluation_source === "candidate_cache"
|
|
266
|
+
? "accepted candidate cache"
|
|
267
|
+
: "fresh"
|
|
268
|
+
}`
|
|
269
|
+
: "Evaluation source: n/a",
|
|
270
|
+
result.package_evaluation?.candidate_id
|
|
271
|
+
? `Package candidate: ${result.package_evaluation.candidate_id} (generation ${result.package_evaluation.candidate_generation ?? 0}, parent ${result.package_evaluation.parent_candidate_id ?? "root"})`
|
|
272
|
+
: "Package candidate: n/a",
|
|
273
|
+
result.package_evaluation?.candidate_acceptance
|
|
274
|
+
? `Candidate acceptance: ${result.package_evaluation.candidate_acceptance.decision} vs ${result.package_evaluation.candidate_acceptance.compared_to_candidate_id ?? "root"} | ${result.package_evaluation.candidate_acceptance.rationale}`
|
|
275
|
+
: "Candidate acceptance: n/a",
|
|
276
|
+
`Package replay: ${replayStatus}`,
|
|
277
|
+
`Package baseline: ${baselineStatus}`,
|
|
278
|
+
result.package_evaluation
|
|
279
|
+
? `Package lift: ${result.package_evaluation.baseline.lift.toFixed(3)}`
|
|
280
|
+
: "Package lift: n/a",
|
|
281
|
+
result.package_evaluation?.evidence
|
|
282
|
+
? `Package wins: ${result.package_evaluation.evidence.baseline_wins} | Replay failures: ${result.package_evaluation.evidence.replay_failures}`
|
|
283
|
+
: "Package wins: n/a",
|
|
284
|
+
result.package_evaluation?.efficiency
|
|
285
|
+
? `Package runtime: ${(result.package_evaluation.efficiency.with_skill.total_duration_ms / 1000).toFixed(1)}s with skill / ${(result.package_evaluation.efficiency.without_skill.total_duration_ms / 1000).toFixed(1)}s without skill`
|
|
286
|
+
: "Package runtime: n/a",
|
|
287
|
+
result.watch_result
|
|
288
|
+
? `Watch pass rate: ${result.watch_result.snapshot.pass_rate.toFixed(3)} vs baseline ${result.watch_result.snapshot.baseline_pass_rate.toFixed(3)}`
|
|
289
|
+
: "Watch pass rate: n/a",
|
|
290
|
+
result.watch_result?.alert
|
|
291
|
+
? `Watch alert: ${result.watch_result.alert}`
|
|
292
|
+
: result.watch_result
|
|
293
|
+
? `Watch alert: none (${result.watch_result.recommendation})`
|
|
294
|
+
: "Watch alert: n/a",
|
|
295
|
+
result.watch_gate_passed != null
|
|
296
|
+
? `Watch gate: ${result.watch_gate_passed ? "passed" : result.watch_gate_bypassed ? "alert bypassed" : "alert"}`
|
|
297
|
+
: "Watch gate: n/a",
|
|
298
|
+
result.watch_trust_score != null
|
|
299
|
+
? `Watch trust score: ${result.watch_trust_score.toFixed(2)}`
|
|
300
|
+
: "Watch trust score: n/a",
|
|
301
|
+
...result.watch_gate_warnings.map(
|
|
302
|
+
(warning, index) => `Watch gate warning ${index + 1}: ${warning}`,
|
|
303
|
+
),
|
|
304
|
+
...(result.watch_gate_blocked ? ["Watch gate blocked publish: yes"] : []),
|
|
305
|
+
`Published: ${result.published ? "yes" : "no"}`,
|
|
306
|
+
`Watch started: ${result.watch_started ? "yes" : "no"}`,
|
|
307
|
+
result.next_command ? `Next command: ${result.next_command}` : "Next command: none",
|
|
308
|
+
].join("\n");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export async function cliMain(): Promise<void> {
|
|
312
|
+
const { values } = parseArgs({
|
|
313
|
+
options: {
|
|
314
|
+
"skill-path": { type: "string" },
|
|
315
|
+
watch: { type: "boolean", default: false },
|
|
316
|
+
"ignore-watch-alerts": { type: "boolean", default: false },
|
|
317
|
+
json: { type: "boolean", default: false },
|
|
318
|
+
help: { type: "boolean", short: "h", default: false },
|
|
319
|
+
},
|
|
320
|
+
strict: true,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
if (values.help) {
|
|
324
|
+
console.log(renderCommandHelp(PUBLIC_COMMAND_SURFACES.createPublish));
|
|
325
|
+
process.exit(0);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const result = await runCreatePublish({
|
|
329
|
+
skillPath: values["skill-path"] ?? "",
|
|
330
|
+
watch: values.watch,
|
|
331
|
+
ignoreWatchAlerts: values["ignore-watch-alerts"],
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
if (values.json || !process.stdout.isTTY) {
|
|
335
|
+
console.log(JSON.stringify(result, null, 2));
|
|
336
|
+
} else {
|
|
337
|
+
console.log(formatCreatePublishResult(result));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
process.exit(result.published ? 0 : 1);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (import.meta.main) {
|
|
344
|
+
cliMain().catch(handleCLIError);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
// Publish watch gate — advisory gate before publish
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
export interface PublishWatchGateOptions {
|
|
352
|
+
skillName: string;
|
|
353
|
+
/** Recent watch results to evaluate. Empty array means no watch data. */
|
|
354
|
+
recentWatchResults: WatchResult[];
|
|
355
|
+
/** Bypass watch alerts (expert use). */
|
|
356
|
+
ignoreWatchAlerts?: boolean;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export interface PublishWatchGateResult {
|
|
360
|
+
/** Whether the gate passed (true = safe to publish). */
|
|
361
|
+
passed: boolean;
|
|
362
|
+
/** Warning messages for the user (empty if passed cleanly). */
|
|
363
|
+
warnings: string[];
|
|
364
|
+
/** The computed trust score from the most recent watch, or null if no data. */
|
|
365
|
+
trustScore: number | null;
|
|
366
|
+
/** Whether the user bypassed alerts with --ignore-watch-alerts. */
|
|
367
|
+
bypassed: boolean;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Skills below this trust score get a publish warning. */
|
|
371
|
+
const PUBLISH_TRUST_WARNING_THRESHOLD = 0.7;
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Check whether recent watch results indicate safe-to-publish.
|
|
375
|
+
*
|
|
376
|
+
* This is the publish-time watch safety gate. Active watch alerts produce
|
|
377
|
+
* warnings that block publish unless --ignore-watch-alerts is set. Missing or
|
|
378
|
+
* malformed watch output is handled by the caller as a hard publish failure.
|
|
379
|
+
*/
|
|
380
|
+
export function checkPublishWatchGate(options: PublishWatchGateOptions): PublishWatchGateResult {
|
|
381
|
+
const { skillName, recentWatchResults, ignoreWatchAlerts = false } = options;
|
|
382
|
+
|
|
383
|
+
// No watch data — pass with advisory
|
|
384
|
+
if (recentWatchResults.length === 0) {
|
|
385
|
+
return {
|
|
386
|
+
passed: true,
|
|
387
|
+
warnings: [
|
|
388
|
+
`No watch data for "${skillName}". Consider running "selftune watch" before publishing.`,
|
|
389
|
+
],
|
|
390
|
+
trustScore: null,
|
|
391
|
+
bypassed: false,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const latestWatch = recentWatchResults[recentWatchResults.length - 1]!;
|
|
396
|
+
const trustScore = computeWatchTrustScore(latestWatch);
|
|
397
|
+
const warnings: string[] = [];
|
|
398
|
+
|
|
399
|
+
// Check for active alerts in any recent watch result
|
|
400
|
+
const activeAlerts = recentWatchResults.filter((r) => r.alert != null).map((r) => r.alert!);
|
|
401
|
+
|
|
402
|
+
if (activeAlerts.length > 0) {
|
|
403
|
+
warnings.push(
|
|
404
|
+
`Active watch alerts for "${skillName}":\n${activeAlerts.map((a) => ` - ${a}`).join("\n")}`,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Check trust score threshold
|
|
409
|
+
if (trustScore < PUBLISH_TRUST_WARNING_THRESHOLD) {
|
|
410
|
+
warnings.push(
|
|
411
|
+
`Watch trust score for "${skillName}" is ${trustScore.toFixed(2)}, below threshold ${PUBLISH_TRUST_WARNING_THRESHOLD.toFixed(2)}.`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Check for recent rollbacks
|
|
416
|
+
const hasRollback = recentWatchResults.some((r) => r.rolledBack);
|
|
417
|
+
if (hasRollback) {
|
|
418
|
+
warnings.push(
|
|
419
|
+
`"${skillName}" was recently rolled back. Ensure the issue is resolved before publishing.`,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const passed = warnings.length === 0 || ignoreWatchAlerts;
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
passed,
|
|
427
|
+
warnings,
|
|
428
|
+
trustScore,
|
|
429
|
+
bypassed: ignoreWatchAlerts && warnings.length > 0,
|
|
430
|
+
};
|
|
431
|
+
}
|