From 918eeb2ddcc8b3944ea7588b27e63aacb4789c38 Mon Sep 17 00:00:00 2001 From: WellDunDun <45949032+WellDunDun@users.noreply.github.com> Date: Sat, 14 Mar 2026 20:22:33 +0300 Subject: [PATCH 1/6] feat: add phased decision report to orchestrator Orchestrate output now explains each decision clearly so users can trust the autonomous loop. Adds formatOrchestrateReport() with 5-phase human report (sync, status, decisions, evolution, watch) and enriched JSON with per-skill decisions array. Supersedes PR #45. Co-Authored-By: Claude Opus 4.6 --- cli/selftune/orchestrate.ts | 223 ++++++++++++++++++++++++++++++++---- tests/orchestrate.test.ts | 223 ++++++++++++++++++++++++++++++++++++ 2 files changed, 426 insertions(+), 20 deletions(-) diff --git a/cli/selftune/orchestrate.ts b/cli/selftune/orchestrate.ts index cd8d69b..7709abc 100644 --- a/cli/selftune/orchestrate.ts +++ b/cli/selftune/orchestrate.ts @@ -76,6 +76,176 @@ export interface OrchestrateResult { }; } +// --------------------------------------------------------------------------- +// Human-readable decision report +// --------------------------------------------------------------------------- + +function formatSyncPhase(syncResult: SyncResult): string[] { + const lines: string[] = ["Phase 1: Sync"]; + const sources: [string, keyof SyncResult["sources"]][] = [ + ["Claude", "claude"], + ["Codex", "codex"], + ["OpenCode", "opencode"], + ["OpenClaw", "openclaw"], + ]; + + for (const [label, key] of sources) { + const s = syncResult.sources[key]; + if (!s.available) { + lines.push(` ${label.padEnd(12)}not available`); + } else if (s.synced > 0) { + lines.push(` ${label.padEnd(12)}scanned ${s.scanned}, synced ${s.synced}`); + } else { + lines.push(` ${label.padEnd(12)}scanned ${s.scanned}, up to date`); + } + } + + if (syncResult.repair.ran && syncResult.repair.repaired_records > 0) { + lines.push( + ` Repair ${syncResult.repair.repaired_records} records across ${syncResult.repair.repaired_sessions} sessions`, + ); + } + + return lines; +} + +function formatStatusPhase(statusResult: StatusResult): string[] { + const lines: string[] = ["Phase 2: Status"]; + const byStatus: Record = {}; + for (const skill of statusResult.skills) { + byStatus[skill.status] = (byStatus[skill.status] ?? 0) + 1; + } + const healthLabel = statusResult.system.healthy ? "healthy" : "UNHEALTHY"; + lines.push(` ${statusResult.skills.length} skills found, system ${healthLabel}`); + + const parts: string[] = []; + for (const s of ["CRITICAL", "WARNING", "HEALTHY", "UNGRADED", "UNKNOWN"]) { + if (byStatus[s]) parts.push(`${byStatus[s]} ${s}`); + } + if (parts.length > 0) lines.push(` ${parts.join(", ")}`); + + return lines; +} + +function formatDecisionPhase(candidates: SkillAction[]): string[] { + const lines: string[] = ["Phase 3: Skill Decisions"]; + if (candidates.length === 0) { + lines.push(" (no skills to evaluate)"); + return lines; + } + + for (const c of candidates) { + const icon = c.action === "skip" ? "⊘" : c.action === "watch" ? "○" : "→"; + const actionLabel = c.action.toUpperCase().padEnd(7); + lines.push(` ${icon} ${c.skill.padEnd(20)} ${actionLabel} ${c.reason}`); + } + + return lines; +} + +function formatEvolutionPhase(candidates: SkillAction[]): string[] { + const evolved = candidates.filter((c) => c.action === "evolve" && c.evolveResult !== undefined); + if (evolved.length === 0) return []; + + const lines: string[] = ["Phase 4: Evolution Results"]; + for (const c of evolved) { + const r = c.evolveResult; + if (!r) continue; + const status = r.deployed ? "deployed" : "not deployed"; + const detail = r.reason; + const validation = r.validation + ? ` (${(r.validation.before_pass_rate * 100).toFixed(0)}% → ${(r.validation.after_pass_rate * 100).toFixed(0)}%)` + : ""; + lines.push(` ${c.skill.padEnd(20)} ${status}${validation}`); + lines.push(` ${"".padEnd(20)} ${detail}`); + } + + return lines; +} + +function formatWatchPhase(candidates: SkillAction[]): string[] { + const watched = candidates.filter((c) => c.action === "watch"); + if (watched.length === 0) return []; + + const lines: string[] = ["Phase 5: Watch"]; + for (const c of watched) { + const snap = c.watchResult?.snapshot; + const passInfo = snap ? `pass_rate=${snap.pass_rate.toFixed(2)}` : ""; + const baseInfo = snap ? `, baseline=${snap.baseline_pass_rate.toFixed(2)}` : ""; + const alertTag = c.watchResult?.alert ? " [ALERT]" : ""; + const rollbackTag = c.watchResult?.rolledBack ? " [ROLLED BACK]" : ""; + lines.push( + ` ${c.skill.padEnd(20)} ${c.reason}${alertTag}${rollbackTag} (${passInfo}${baseInfo})`, + ); + } + + return lines; +} + +export function formatOrchestrateReport(result: OrchestrateResult): string { + const sep = "═".repeat(48); + const lines: string[] = []; + + lines.push(sep); + lines.push("selftune orchestrate — decision report"); + lines.push(sep); + lines.push(""); + + // Mode banner + if (result.summary.dryRun) { + lines.push("Mode: DRY RUN (no mutations applied)"); + } else if (result.summary.approvalMode === "review") { + lines.push("Mode: REVIEW (proposals validated but not deployed)"); + } else { + lines.push("Mode: AUTONOMOUS (validated changes deployed automatically)"); + } + lines.push(""); + + // Phase 1: Sync + lines.push(...formatSyncPhase(result.syncResult)); + lines.push(""); + + // Phase 2: Status + lines.push(...formatStatusPhase(result.statusResult)); + lines.push(""); + + // Phase 3: Skill decisions + lines.push(...formatDecisionPhase(result.candidates)); + lines.push(""); + + // Phase 4: Evolution results (only if any evolve ran) + const evoLines = formatEvolutionPhase(result.candidates); + if (evoLines.length > 0) { + lines.push(...evoLines); + lines.push(""); + } + + // Phase 5: Watch (only if any watched) + const watchLines = formatWatchPhase(result.candidates); + if (watchLines.length > 0) { + lines.push(...watchLines); + lines.push(""); + } + + // Final summary + lines.push("Summary"); + lines.push(` Evaluated: ${result.summary.evaluated} skills`); + lines.push(` Deployed: ${result.summary.deployed}`); + lines.push(` Watched: ${result.summary.watched}`); + lines.push(` Skipped: ${result.summary.skipped}`); + lines.push(` Elapsed: ${(result.summary.elapsedMs / 1000).toFixed(1)}s`); + + if (result.summary.dryRun && result.summary.evaluated > 0) { + lines.push(""); + lines.push(" Rerun without --dry-run to allow validated deployments."); + } else if (result.summary.approvalMode === "review" && result.summary.evaluated > 0) { + lines.push(""); + lines.push(" Rerun without --review-required to allow validated deployments."); + } + + return lines.join("\n"); +} + /** Candidate selection criteria. */ const CANDIDATE_STATUSES = new Set(["CRITICAL", "WARNING", "UNGRADED"]); @@ -506,27 +676,40 @@ Examples: syncForce: values["sync-force"] ?? false, }); - // Print JSON summary to stdout - console.log(JSON.stringify(result.summary, null, 2)); - - // Print human-readable recap to stderr - console.error(`\n${"═".repeat(40)}`); - console.error("selftune orchestrate — summary"); - console.error("═".repeat(40)); - console.error(` Total skills: ${result.summary.totalSkills}`); - console.error(` Evaluated: ${result.summary.evaluated}`); - console.error(` Deployed: ${result.summary.deployed}`); - console.error(` Watched: ${result.summary.watched}`); - console.error(` Skipped: ${result.summary.skipped}`); - console.error(` Dry run: ${result.summary.dryRun}`); - console.error(` Approval mode: ${result.summary.approvalMode}`); - console.error(` Elapsed: ${(result.summary.elapsedMs / 1000).toFixed(1)}s`); + // JSON output: include per-skill decisions for machine consumption + const jsonOutput = { + ...result.summary, + decisions: result.candidates.map((c) => ({ + skill: c.skill, + action: c.action, + reason: c.reason, + ...(c.evolveResult + ? { + deployed: c.evolveResult.deployed, + evolveReason: c.evolveResult.reason, + validation: c.evolveResult.validation + ? { + before: c.evolveResult.validation.before_pass_rate, + after: c.evolveResult.validation.after_pass_rate, + improved: c.evolveResult.validation.improved, + } + : null, + } + : {}), + ...(c.watchResult + ? { + alert: c.watchResult.alert, + rolledBack: c.watchResult.rolledBack, + passRate: c.watchResult.snapshot.pass_rate, + recommendation: c.watchResult.recommendation, + } + : {}), + })), + }; + console.log(JSON.stringify(jsonOutput, null, 2)); - if (result.summary.dryRun && result.summary.evaluated > 0) { - console.error("\n Rerun without --dry-run to allow validated deployments."); - } else if (result.summary.approvalMode === "review" && result.summary.evaluated > 0) { - console.error("\n Rerun without --review-required to allow validated deployments."); - } + // Print human-readable decision report to stderr + console.error(`\n${formatOrchestrateReport(result)}`); process.exit(0); } diff --git a/tests/orchestrate.test.ts b/tests/orchestrate.test.ts index d9f4f3f..212936d 100644 --- a/tests/orchestrate.test.ts +++ b/tests/orchestrate.test.ts @@ -1,7 +1,9 @@ import { describe, expect, test } from "bun:test"; import { + formatOrchestrateReport, type OrchestrateDeps, type OrchestrateOptions, + type OrchestrateResult, orchestrate, selectCandidates, } from "../cli/selftune/orchestrate.js"; @@ -32,6 +34,8 @@ function makeSyncResult(): SyncResult { dry_run: false, sources: { claude: step, codex: step, opencode: step, openclaw: step }, repair: { ran: true, repaired_sessions: 0, repaired_records: 0, codex_repaired_records: 0 }, + timings: [], + total_elapsed_ms: 0, }; } @@ -360,3 +364,222 @@ describe("orchestrate", () => { expect(candidate?.reason).toContain("no agent CLI"); }); }); + +// --------------------------------------------------------------------------- +// formatOrchestrateReport +// --------------------------------------------------------------------------- + +function makeOrchestrateResult(overrides: Partial = {}): OrchestrateResult { + const step: SyncStepResult = { available: true, scanned: 10, synced: 2, skipped: 0 }; + return { + syncResult: { + since: null, + dry_run: false, + sources: { + claude: step, + codex: { available: false, scanned: 0, synced: 0, skipped: 0 }, + opencode: { available: true, scanned: 5, synced: 0, skipped: 0 }, + openclaw: { available: false, scanned: 0, synced: 0, skipped: 0 }, + }, + repair: { ran: true, repaired_sessions: 3, repaired_records: 7, codex_repaired_records: 0 }, + timings: [], + total_elapsed_ms: 500, + }, + statusResult: makeStatusResult([ + makeSkill({ name: "Research", status: "CRITICAL", passRate: 0.35, missedQueries: 8 }), + makeSkill({ name: "Browser", status: "WARNING", passRate: 0.55, missedQueries: 3 }), + makeSkill({ name: "Content", status: "HEALTHY", passRate: 0.9, missedQueries: 0 }), + ]), + candidates: [ + { skill: "Research", action: "evolve", reason: "status=CRITICAL, passRate=35%, missed=8" }, + { skill: "Browser", action: "evolve", reason: "status=WARNING, passRate=55%, missed=3" }, + { skill: "Content", action: "skip", reason: "status=HEALTHY — no action needed" }, + ], + summary: { + totalSkills: 3, + evaluated: 2, + evolved: 0, + deployed: 0, + watched: 0, + skipped: 1, + dryRun: true, + approvalMode: "auto", + elapsedMs: 1200, + }, + ...overrides, + }; +} + +describe("formatOrchestrateReport", () => { + test("includes dry-run mode banner", () => { + const report = formatOrchestrateReport(makeOrchestrateResult()); + expect(report).toContain("DRY RUN"); + }); + + test("includes autonomous mode banner", () => { + const report = formatOrchestrateReport( + makeOrchestrateResult({ + summary: { ...makeOrchestrateResult().summary, dryRun: false, approvalMode: "auto" }, + }), + ); + expect(report).toContain("AUTONOMOUS"); + }); + + test("includes review mode banner", () => { + const report = formatOrchestrateReport( + makeOrchestrateResult({ + summary: { ...makeOrchestrateResult().summary, dryRun: false, approvalMode: "review" }, + }), + ); + expect(report).toContain("REVIEW"); + }); + + test("shows sync sources with availability", () => { + const report = formatOrchestrateReport(makeOrchestrateResult()); + expect(report).toContain("Claude"); + expect(report).toContain("synced 2"); + expect(report).toContain("Codex"); + expect(report).toContain("not available"); + expect(report).toContain("OpenCode"); + expect(report).toContain("up to date"); + }); + + test("shows repair info when records were repaired", () => { + const report = formatOrchestrateReport(makeOrchestrateResult()); + expect(report).toContain("7 records across 3 sessions"); + }); + + test("shows status breakdown by category", () => { + const report = formatOrchestrateReport(makeOrchestrateResult()); + expect(report).toContain("1 CRITICAL"); + expect(report).toContain("1 WARNING"); + expect(report).toContain("1 HEALTHY"); + }); + + test("lists each skill decision with action and reason", () => { + const report = formatOrchestrateReport(makeOrchestrateResult()); + expect(report).toContain("Research"); + expect(report).toContain("EVOLVE"); + expect(report).toContain("status=CRITICAL"); + expect(report).toContain("Content"); + expect(report).toContain("SKIP"); + expect(report).toContain("no action needed"); + }); + + test("includes evolution results when evolve ran", () => { + const result = makeOrchestrateResult({ + candidates: [ + { + skill: "Research", + action: "evolve", + reason: "status=CRITICAL", + evolveResult: { + proposal: null, + validation: { + proposal_id: "test-proposal", + improved: true, + before_pass_rate: 0.35, + after_pass_rate: 0.7, + net_change: 0.35, + regressions: [], + new_passes: [], + per_entry_results: [], + }, + deployed: true, + auditEntries: [], + reason: "Evolution deployed successfully", + llmCallCount: 5, + elapsedMs: 3000, + }, + }, + ], + }); + const report = formatOrchestrateReport(result); + expect(report).toContain("Evolution Results"); + expect(report).toContain("deployed"); + expect(report).toContain("35%"); + expect(report).toContain("70%"); + }); + + test("includes watch results with rollback info", () => { + const result = makeOrchestrateResult({ + candidates: [ + { + skill: "RecentSkill", + action: "watch", + reason: "regression detected", + watchResult: { + snapshot: { + timestamp: new Date().toISOString(), + skill_name: "RecentSkill", + window_sessions: 20, + skill_checks: 10, + pass_rate: 0.4, + false_negative_rate: 0.1, + by_invocation_type: { + explicit: { passed: 2, total: 5 }, + implicit: { passed: 1, total: 5 }, + contextual: { passed: 0, total: 0 }, + negative: { passed: 0, total: 0 }, + }, + regression_detected: true, + baseline_pass_rate: 0.8, + }, + alert: "pass rate dropped from 0.80 to 0.40", + rolledBack: true, + recommendation: "rollback", + }, + }, + ], + }); + const report = formatOrchestrateReport(result); + expect(report).toContain("Watch"); + expect(report).toContain("RecentSkill"); + expect(report).toContain("[ALERT]"); + expect(report).toContain("[ROLLED BACK]"); + expect(report).toContain("pass_rate=0.40"); + }); + + test("shows summary counts", () => { + const report = formatOrchestrateReport(makeOrchestrateResult()); + expect(report).toContain("Evaluated: 2 skills"); + expect(report).toContain("Skipped: 1"); + expect(report).toContain("Elapsed: 1.2s"); + }); + + test("omits evolution and watch phases when empty", () => { + const result = makeOrchestrateResult({ + candidates: [{ skill: "Content", action: "skip", reason: "status=HEALTHY" }], + }); + const report = formatOrchestrateReport(result); + expect(report).not.toContain("Evolution Results"); + expect(report).not.toContain("Phase 5: Watch"); + }); + + test("dry-run includes rerun hint", () => { + const result = makeOrchestrateResult({ + summary: { ...makeOrchestrateResult().summary, dryRun: true, evaluated: 2 }, + }); + const report = formatOrchestrateReport(result); + expect(report).toContain("Rerun without --dry-run"); + }); + + test("review mode includes rerun hint", () => { + const result = makeOrchestrateResult({ + summary: { + ...makeOrchestrateResult().summary, + dryRun: false, + approvalMode: "review", + evaluated: 2, + }, + }); + const report = formatOrchestrateReport(result); + expect(report).toContain("Rerun without --review-required"); + }); + + test("shows (no skills to evaluate) when candidates empty", () => { + const result = makeOrchestrateResult({ candidates: [] }); + const report = formatOrchestrateReport(result); + expect(report).toContain("(no skills to evaluate)"); + }); +}); From 1b4cbcdfc4a5ad97add16b9bb404bcd12e0ecb65 Mon Sep 17 00:00:00 2001 From: WellDunDun <45949032+WellDunDun@users.noreply.github.com> Date: Sun, 15 Mar 2026 10:49:25 +0300 Subject: [PATCH 2/6] docs: update orchestrate workflow docs and changelog for decision report Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 1 + skill/Workflows/Orchestrate.md | 25 +++++++++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3215e8..4b30664 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/). - **SQLite v2 API endpoints** — `GET /api/v2/overview` and `GET /api/v2/skills/:name` backed by materialized SQLite queries (`getOverviewPayload()`, `getSkillReportPayload()`, `getSkillsList()`) - **SQL query optimizations** — Replaced `NOT IN` subqueries with `LEFT JOIN + IS NULL`, moved JS-side dedup to SQL `GROUP BY`, added `LIMIT 200` to unbounded evidence queries - **SPA serving from dashboard server** — Built SPA served at `/` as the supported local dashboard experience +- **Orchestrate decision report** — `selftune orchestrate` now prints a 5-phase human-readable decision report (sync, status, decisions, evolution results, watch) to stderr, and enriched JSON with a per-skill `decisions` array to stdout - **Source-truth-driven pipeline** — Transcripts and rollouts are now the authoritative source; `sync` rebuilds repaired overlays from source data rather than relying solely on hook-time capture - **Telemetry contract package** — `@selftune/telemetry-contract` workspace package with canonical schema types, validators, versioning, metadata, and golden fixture tests - **Test split** — `make test-fast` / `make test-slow` and `bun run test:fast` / `bun run test:slow` for faster development feedback loop diff --git a/skill/Workflows/Orchestrate.md b/skill/Workflows/Orchestrate.md index 8e66dbd..3447a15 100644 --- a/skill/Workflows/Orchestrate.md +++ b/skill/Workflows/Orchestrate.md @@ -59,12 +59,25 @@ Use `--review-required` only when you want a stricter policy for a specific run. ## Output -The command prints: +### Human-readable report (stderr) -- sync results -- candidate-selection reasoning -- evolve/watch actions taken -- skipped skills and why -- a final summary with counts and elapsed time +A phased decision report printed to stderr so you can see exactly what happened and why: + +1. **Phase 1: Sync** — which sources were scanned, how many records synced, repair counts +2. **Phase 2: Status** — skill count, system health, breakdown by status category +3. **Phase 3: Skill Decisions** — each skill with its action (EVOLVE / WATCH / SKIP) and reason +4. **Phase 4: Evolution Results** — validation pass-rate changes (before → after), deployment status +5. **Phase 5: Watch** — post-deploy monitoring with alert and rollback indicators +6. **Summary** — evaluated/deployed/watched/skipped counts and elapsed time + +A mode banner at the top shows DRY RUN, REVIEW, or AUTONOMOUS with rerun hints when applicable. + +### JSON output (stdout) + +Machine-readable JSON with the summary fields plus a `decisions` array containing per-skill: + +- `skill`, `action`, `reason` +- `deployed`, `evolveReason`, `validation` (before/after pass rates, improved flag) — when evolved +- `alert`, `rolledBack`, `passRate`, `recommendation` — when watched This is the recommended runtime for recurring autonomous scheduling. From 328e1db5619cdfa16cc18e715bd9b74ca0bf6165 Mon Sep 17 00:00:00 2001 From: WellDunDun <45949032+WellDunDun@users.noreply.github.com> Date: Sun, 15 Mar 2026 11:08:07 +0300 Subject: [PATCH 3/6] fix: remove redundant null check in formatEvolutionPhase The filter already guarantees evolveResult is defined; use non-null assertion instead of a runtime guard. Co-Authored-By: Claude Opus 4.6 (1M context) --- cli/selftune/orchestrate.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cli/selftune/orchestrate.ts b/cli/selftune/orchestrate.ts index 7709abc..70668af 100644 --- a/cli/selftune/orchestrate.ts +++ b/cli/selftune/orchestrate.ts @@ -149,8 +149,7 @@ function formatEvolutionPhase(candidates: SkillAction[]): string[] { const lines: string[] = ["Phase 4: Evolution Results"]; for (const c of evolved) { - const r = c.evolveResult; - if (!r) continue; + const r = c.evolveResult!; const status = r.deployed ? "deployed" : "not deployed"; const detail = r.reason; const validation = r.validation From ad6808413283465b8417a7b27cd05740e1fa83bb Mon Sep 17 00:00:00 2001 From: WellDunDun <45949032+WellDunDun@users.noreply.github.com> Date: Sun, 15 Mar 2026 11:36:04 +0300 Subject: [PATCH 4/6] fix: add defensive optional chaining for watch snapshot in JSON output Co-Authored-By: Claude Opus 4.6 (1M context) --- cli/selftune/orchestrate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/selftune/orchestrate.ts b/cli/selftune/orchestrate.ts index 70668af..a37e244 100644 --- a/cli/selftune/orchestrate.ts +++ b/cli/selftune/orchestrate.ts @@ -699,7 +699,7 @@ Examples: ? { alert: c.watchResult.alert, rolledBack: c.watchResult.rolledBack, - passRate: c.watchResult.snapshot.pass_rate, + passRate: c.watchResult.snapshot?.pass_rate ?? null, recommendation: c.watchResult.recommendation, } : {}), From f4436122e7f61a5deefe2c664fcd4fa6b197bf69 Mon Sep 17 00:00:00 2001 From: WellDunDun <45949032+WellDunDun@users.noreply.github.com> Date: Sun, 15 Mar 2026 13:29:08 +0300 Subject: [PATCH 5/6] fix: avoid empty parentheses in watch report when snapshot missing Consolidates pass_rate and baseline into a single conditional metrics suffix so lines without a snapshot render cleanly. Addresses CodeRabbit review feedback. Co-Authored-By: Claude Opus 4.6 (1M context) --- cli/selftune/orchestrate.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cli/selftune/orchestrate.ts b/cli/selftune/orchestrate.ts index a37e244..5518408 100644 --- a/cli/selftune/orchestrate.ts +++ b/cli/selftune/orchestrate.ts @@ -169,12 +169,13 @@ function formatWatchPhase(candidates: SkillAction[]): string[] { const lines: string[] = ["Phase 5: Watch"]; for (const c of watched) { const snap = c.watchResult?.snapshot; - const passInfo = snap ? `pass_rate=${snap.pass_rate.toFixed(2)}` : ""; - const baseInfo = snap ? `, baseline=${snap.baseline_pass_rate.toFixed(2)}` : ""; + const metrics = snap + ? ` (pass_rate=${snap.pass_rate.toFixed(2)}, baseline=${snap.baseline_pass_rate.toFixed(2)})` + : ""; const alertTag = c.watchResult?.alert ? " [ALERT]" : ""; const rollbackTag = c.watchResult?.rolledBack ? " [ROLLED BACK]" : ""; lines.push( - ` ${c.skill.padEnd(20)} ${c.reason}${alertTag}${rollbackTag} (${passInfo}${baseInfo})`, + ` ${c.skill.padEnd(20)} ${c.reason}${alertTag}${rollbackTag}${metrics}`, ); } From cddf7fbf363379f7f6671c384f7847aed79320b8 Mon Sep 17 00:00:00 2001 From: WellDunDun <45949032+WellDunDun@users.noreply.github.com> Date: Sun, 15 Mar 2026 13:30:40 +0300 Subject: [PATCH 6/6] fix: resolve biome lint and format errors Replace non-null assertion (!) with type-safe cast to satisfy noNonNullAssertion rule, and collapse single-arg lines.push to one line per biome formatter. Co-Authored-By: Claude Opus 4.6 (1M context) --- cli/selftune/orchestrate.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cli/selftune/orchestrate.ts b/cli/selftune/orchestrate.ts index 5518408..426d747 100644 --- a/cli/selftune/orchestrate.ts +++ b/cli/selftune/orchestrate.ts @@ -149,7 +149,7 @@ function formatEvolutionPhase(candidates: SkillAction[]): string[] { const lines: string[] = ["Phase 4: Evolution Results"]; for (const c of evolved) { - const r = c.evolveResult!; + const r = c.evolveResult as NonNullable; const status = r.deployed ? "deployed" : "not deployed"; const detail = r.reason; const validation = r.validation @@ -174,9 +174,7 @@ function formatWatchPhase(candidates: SkillAction[]): string[] { : ""; const alertTag = c.watchResult?.alert ? " [ALERT]" : ""; const rollbackTag = c.watchResult?.rolledBack ? " [ROLLED BACK]" : ""; - lines.push( - ` ${c.skill.padEnd(20)} ${c.reason}${alertTag}${rollbackTag}${metrics}`, - ); + lines.push(` ${c.skill.padEnd(20)} ${c.reason}${alertTag}${rollbackTag}${metrics}`); } return lines;