Commitc315551unknown_key
fix(follow-ups): L1/L9 cleanup, AI commit message audit, autopilot desc
fix(follow-ups): L1/L9 cleanup, AI commit message audit, autopilot desc - sleep-mode.ts (L1) now imports computeHoursSaved from ai-hours-saved (L9) instead of maintaining its own duplicate formula - editor.tsx ai/commit-message endpoint emits ai.commit_message.generated audit event so L9 hours-saved counter stays accurate - admin/autopilot description updated to mention auto-merge-sweep and ai-build-from-issues tasks added in K3 https://claude.ai/code/session_01ACsT2Pc8GRoZwZRF8SK68Y
3 files changed+21−44c31555188e2ca73ba8f1f8082a66724406e7fd64
3 changed files+21−44
Modifiedsrc/lib/sleep-mode.ts+5−42View fileUnifiedSplit
@@ -36,26 +36,10 @@ import { sendEmail, type EmailResult } from "./email";
3636import { config } from "./config";
3737import { __internal as digestInternals } from "./email-digest";
3838import { AI_BUILD_MARKER } from "./ai-build-tasks";
39import { computeHoursSaved } from "./ai-hours-saved";
3940
4041const { escapeHtml } = digestInternals;
4142
42/**
43 * Heuristic constants for the `hoursSaved` metric. These are deliberately
44 * conservative — better to under-promise than to publish absurd "Claude
45 * saved you 40 hours this week" claims.
46 *
47 * PR_AUTO_MERGE_HOURS = 0.30 — minutes-of-merge-button-pressing
48 * AI_BUILT_ISSUE_HOURS = 1.50 — design + plumbing for a small feature
49 * AI_REVIEW_HOURS = 0.25 — one careful PR review
50 * AUTO_FIX_HOURS = 0.50 — secret rotation / gate repair
51 *
52 * Documented in the L9 ticket for the eventual "savings model v2".
53 */
54const PR_AUTO_MERGE_HOURS = 0.3;
55const AI_BUILT_ISSUE_HOURS = 1.5;
56const AI_REVIEW_HOURS = 0.25;
57const AUTO_FIX_HOURS = 0.5;
58
5943/** Default look-back window. Sleep Mode is a daily digest. */
6044const DEFAULT_WINDOW_HOURS = 24;
6145/** Per-tick cap on users we'll email — runaway protection. */
@@ -74,25 +58,6 @@ export type SleepModeReport = {
7458 hoursSaved: number;
7559};
7660
77/**
78 * Pure helper — exported for tests. Given the four count inputs, returns
79 * the hoursSaved value rounded to one decimal.
80 */
81export function computeHoursSaved(input: {
82 prsAutoMerged: number;
83 issuesBuiltByAi: number;
84 aiReviewsPosted: number;
85 securityIssuesAutoFixed: number;
86 gateFailuresAutoRepaired: number;
87}): number {
88 const raw =
89 input.prsAutoMerged * PR_AUTO_MERGE_HOURS +
90 input.issuesBuiltByAi * AI_BUILT_ISSUE_HOURS +
91 input.aiReviewsPosted * AI_REVIEW_HOURS +
92 (input.securityIssuesAutoFixed + input.gateFailuresAutoRepaired) *
93 AUTO_FIX_HOURS;
94 return Math.round(raw * 10) / 10;
95}
9661
9762/**
9863 * Compose a Sleep Mode report for one user. Pulls from:
@@ -281,8 +246,10 @@ export async function composeSleepModeReport(
281246 prsAutoMerged: prsAutoMerged.length,
282247 issuesBuiltByAi: issuesBuiltByAi.length,
283248 aiReviewsPosted,
284 securityIssuesAutoFixed,
285 gateFailuresAutoRepaired,
249 aiTriagesPosted: 0,
250 aiCommitMsgs: 0,
251 secretsAutoRepaired: securityIssuesAutoFixed,
252 gateAutoRepairs: gateFailuresAutoRepaired,
286253 });
287254
288255 return {
@@ -527,10 +494,6 @@ export async function sendSleepModeDigestForUser(
527494
528495/** Test-only surface. */
529496export const __test = {
530 PR_AUTO_MERGE_HOURS,
531 AI_BUILT_ISSUE_HOURS,
532 AI_REVIEW_HOURS,
533 AUTO_FIX_HOURS,
534497 DEFAULT_WINDOW_HOURS,
535498 renderHtml,
536499};
Modifiedsrc/routes/admin.tsx+3−2View fileUnifiedSplit
@@ -3457,8 +3457,9 @@ admin.get("/admin/autopilot", async (c) => {
34573457 </h1>
34583458 <p class="adm-autopilot-sub">
34593459 Periodic platform-maintenance loop — mirror sync, merge-queue
3460 progress, weekly digests, advisory rescans, environment
3461 wait-timer release, and scheduled workflow triggers (cron).
3460 progress, weekly digests, advisory rescans, wait-timer release,
3461 scheduled workflows (cron), AI-gated auto-merge sweep, and
3462 ai:build issue → PR dispatch.
34623463 </p>
34633464 </div>
34643465 <a href="/admin" class="adm-autopilot-back">
Modifiedsrc/routes/editor.tsx+13−0View fileUnifiedSplit
@@ -26,6 +26,8 @@ import { isAiAvailable } from "../lib/ai-client";
2626import { softAuth, requireAuth } from "../middleware/auth";
2727import type { AuthEnv } from "../middleware/auth";
2828import { requireRepoAccess } from "../middleware/repo-access";
29import { audit } from "../lib/notify";
30import { AI_AUDIT_ACTIONS } from "../lib/ai-hours-saved";
2931
3032const editor = new Hono<AuthEnv>();
3133
@@ -1080,6 +1082,17 @@ editor.post(
10801082 // Cap to one line + 100 chars (commit-message convention).
10811083 const oneLine = message.split("\n")[0]!.trim();
10821084 const capped = oneLine.length > 100 ? oneLine.slice(0, 97) + "..." : oneLine;
1085 // Emit audit event so L9 ai-hours-saved counters stay accurate.
1086 const user = c.get("user");
1087 if (user) {
1088 void audit({
1089 userId: user.id,
1090 action: AI_AUDIT_ACTIONS.AI_COMMIT_MESSAGE,
1091 targetType: "repository",
1092 targetId: `${owner}/${repo}`,
1093 metadata: { filePath },
1094 }).catch(() => {});
1095 }
10831096 return c.json({ ok: true, message: capped });
10841097 }
10851098);
10861099