sloppatrol 0.1.0 → 0.1.2

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.
Files changed (73) hide show
  1. package/README.md +111 -160
  2. package/dist/census/index.js +4 -2
  3. package/dist/census/index.js.map +1 -1
  4. package/dist/census/lib/guardrails.js +38 -9
  5. package/dist/census/lib/guardrails.js.map +1 -1
  6. package/dist/census/lib/stack.d.ts +2 -1
  7. package/dist/census/lib/stack.js +8 -4
  8. package/dist/census/lib/stack.js.map +1 -1
  9. package/dist/cli/index.js +20 -4
  10. package/dist/cli/index.js.map +1 -1
  11. package/dist/cli/lib/browser.d.ts +2 -0
  12. package/dist/cli/lib/browser.js +13 -0
  13. package/dist/cli/lib/browser.js.map +1 -0
  14. package/dist/cli/lib/usage.d.ts +1 -1
  15. package/dist/cli/lib/usage.js +4 -3
  16. package/dist/cli/lib/usage.js.map +1 -1
  17. package/dist/config/lib/schema.d.ts +30 -0
  18. package/dist/config/lib/schema.js +16 -0
  19. package/dist/config/lib/schema.js.map +1 -1
  20. package/dist/dashboard/index.d.ts +4 -0
  21. package/dist/dashboard/index.js +6 -2
  22. package/dist/dashboard/index.js.map +1 -1
  23. package/dist/dashboard/lib/snapshot.d.ts +168 -30
  24. package/dist/dashboard/lib/snapshot.js +104 -37
  25. package/dist/dashboard/lib/snapshot.js.map +1 -1
  26. package/dist/dashboard/lib/summary.d.ts +230 -0
  27. package/dist/dashboard/lib/summary.js +398 -0
  28. package/dist/dashboard/lib/summary.js.map +1 -0
  29. package/dist/domain/index.d.ts +2 -0
  30. package/dist/domain/index.js +1 -0
  31. package/dist/domain/index.js.map +1 -1
  32. package/dist/domain/lib/cycle.d.ts +17 -0
  33. package/dist/domain/lib/cycle.js +2 -0
  34. package/dist/domain/lib/cycle.js.map +1 -0
  35. package/dist/{patrol → domain}/lib/pick.d.ts +2 -1
  36. package/dist/{patrol → domain}/lib/pick.js.map +1 -1
  37. package/dist/domain/lib/refactor.d.ts +2 -0
  38. package/dist/gate/index.d.ts +7 -0
  39. package/dist/gate/index.js +15 -0
  40. package/dist/gate/index.js.map +1 -1
  41. package/dist/patrol/lib/attempt.js +10 -2
  42. package/dist/patrol/lib/attempt.js.map +1 -1
  43. package/dist/patrol/lib/base.d.ts +10 -3
  44. package/dist/patrol/lib/base.js +14 -8
  45. package/dist/patrol/lib/base.js.map +1 -1
  46. package/dist/patrol/lib/cycle.d.ts +1 -1
  47. package/dist/patrol/lib/cycle.js +32 -1
  48. package/dist/patrol/lib/cycle.js.map +1 -1
  49. package/dist/patrol/lib/recover.d.ts +2 -1
  50. package/dist/patrol/lib/recover.js +16 -1
  51. package/dist/patrol/lib/recover.js.map +1 -1
  52. package/dist/patrol/lib/scorecard.js +12 -4
  53. package/dist/patrol/lib/scorecard.js.map +1 -1
  54. package/dist/patrol/lib/steps.d.ts +5 -2
  55. package/dist/patrol/lib/steps.js +9 -6
  56. package/dist/patrol/lib/steps.js.map +1 -1
  57. package/dist/playbook/index.js +3 -3
  58. package/dist/roles/lib/prompts.d.ts +1 -0
  59. package/dist/roles/lib/prompts.js +6 -0
  60. package/dist/roles/lib/prompts.js.map +1 -1
  61. package/dist/roles/lib/refactorer.d.ts +1 -0
  62. package/dist/roles/lib/refactorer.js +24 -6
  63. package/dist/roles/lib/refactorer.js.map +1 -1
  64. package/dist/roles/lib/scorecard-text.js +1 -1
  65. package/dist/roles/lib/scorecard-text.js.map +1 -1
  66. package/dist/store/index.d.ts +4 -1
  67. package/dist/store/index.js +7 -0
  68. package/dist/store/index.js.map +1 -1
  69. package/package.json +1 -1
  70. package/ui/app.js +1236 -324
  71. package/ui/index.html +35 -13
  72. package/ui/style.css +1063 -340
  73. /package/dist/{patrol → domain}/lib/pick.js +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../../src/dashboard/lib/snapshot.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../../src/dashboard/lib/snapshot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,230 @@
1
+ import { type AgentStatus, type CheckRun, type Cycle, type Opportunity, type Refactor, type Scorecard, type Strength } from '../../domain/index.js';
2
+ export type Tone = 'pass' | 'warn' | 'fail' | 'info' | 'run';
3
+ /** A status as the patrol wrote it, and whether its process still runs. */
4
+ export type LiveStatus = AgentStatus & {
5
+ alive: boolean;
6
+ stale: boolean;
7
+ };
8
+ /** One step of a refactor's pipeline: done, failed, running, waiting for a person, or not reached. */
9
+ type Step = {
10
+ id: StepId;
11
+ label: string;
12
+ state: 'ok' | 'bad' | 'run' | 'wait' | '';
13
+ };
14
+ type StepId = 'refactorer' | 'guards' | 'checks' | 'mutation' | 'metrics' | 'review' | 'pr';
15
+ /**
16
+ * What became of a refactor, for people: it waits for a person (`waits`),
17
+ * the gate rejected it, it stopped with an error, or Sloppatrol stopped
18
+ * while it ran (`interrupted`).
19
+ */
20
+ export type Outcome = 'working' | 'waits' | 'rejected' | 'error' | 'interrupted' | 'merged' | 'closed';
21
+ export type RefactorRow = Refactor & {
22
+ title: string;
23
+ kind?: Opportunity['kind'];
24
+ side?: Opportunity['side'];
25
+ outcome: Outcome;
26
+ /** The step that rejected it. */
27
+ rejectedAt?: StepId;
28
+ steps: Step[];
29
+ };
30
+ export type CheckRunRow = CheckRun & {
31
+ outcome: {
32
+ tone: 'ok' | 'bad' | 'run' | 'stop';
33
+ text: string;
34
+ };
35
+ /**
36
+ * A result that needs a person: red checks on main, a refactor that the
37
+ * gate failed, or a planted bug the tests missed. A red refactorer try is
38
+ * not one: the refactorer runs the checks to test its own change.
39
+ */
40
+ problem: boolean;
41
+ };
42
+ export type SessionRow = {
43
+ id: string;
44
+ role: string;
45
+ subject?: string;
46
+ startedAt: string;
47
+ endedAt?: string;
48
+ /** `stopped`: it has no end, and no Sloppatrol process runs. */
49
+ state: 'running' | 'done' | 'failed' | 'stopped';
50
+ last: string;
51
+ costUsd: number;
52
+ };
53
+ type AttentionItem = {
54
+ tone: Tone;
55
+ title: string;
56
+ detail: string;
57
+ command?: string;
58
+ /** A page of the dashboard, with a filter or a selected item. */
59
+ target?: {
60
+ tab: string;
61
+ filter?: string;
62
+ select?: string;
63
+ };
64
+ links?: {
65
+ label: string;
66
+ href: string;
67
+ select?: string;
68
+ }[];
69
+ };
70
+ export type ProjectSummary = ReturnType<typeof projectSummary>;
71
+ /**
72
+ * The pipeline of one refactor, from its own record and its check runs.
73
+ * The verdict and the proof hold the result of each step, so an old refactor
74
+ * shows the right steps also when its check runs are no longer on the page.
75
+ */
76
+ export declare function refactorRow(refactor: Refactor, opportunity: Opportunity | undefined, runs: CheckRun[], active: boolean): RefactorRow;
77
+ /**
78
+ * The result of a check run, for people. A mutation run fails its tests on
79
+ * purpose: on the refactor, a failed run means the new tests caught the
80
+ * planted bug. On main, for new coverage, the old tests must miss it.
81
+ */
82
+ export declare function checkRunRow(run: CheckRun, active: boolean): CheckRunRow;
83
+ /** The numbers of the whole project. Each page reads them from here, so two pages cannot disagree. */
84
+ export declare function projectSummary(input: {
85
+ project: string;
86
+ now: Date;
87
+ patrol: {
88
+ lastSha?: string;
89
+ lastCycleAt?: string;
90
+ cycles: number;
91
+ };
92
+ statuses: LiveStatus[];
93
+ opportunities: Opportunity[];
94
+ refactors: RefactorRow[];
95
+ checkRuns: CheckRunRow[];
96
+ scorecards: Scorecard[];
97
+ sessions: SessionRow[];
98
+ cycles: Cycle[];
99
+ }): {
100
+ checks: {
101
+ caught?: number | undefined;
102
+ planted?: number | undefined;
103
+ runs: number;
104
+ problems: number;
105
+ base: {
106
+ runs: number;
107
+ bad: number;
108
+ problems: number;
109
+ };
110
+ gate: {
111
+ runs: number;
112
+ bad: number;
113
+ problems: number;
114
+ };
115
+ refactorer: {
116
+ runs: number;
117
+ bad: number;
118
+ problems: number;
119
+ };
120
+ mutation: {
121
+ runs: number;
122
+ bad: number;
123
+ problems: number;
124
+ };
125
+ };
126
+ sessions: {
127
+ total: number;
128
+ running: number;
129
+ failed: number;
130
+ failedRecent: number;
131
+ stopped: number;
132
+ costUsd: number;
133
+ costRecentUsd: number;
134
+ byRole: {
135
+ [k: string]: number;
136
+ };
137
+ };
138
+ attention: AttentionItem[];
139
+ quality?: {
140
+ /** The files that both of the last two scorecards scored: the change compares these only. */
141
+ compared: number;
142
+ trend: number[];
143
+ metrics: {
144
+ trend: number[];
145
+ delta?: number | undefined;
146
+ id: "tests" | "simplicity" | "modularity" | "injection" | "purity" | "clarity" | "guardrails";
147
+ name: string;
148
+ value: number;
149
+ }[];
150
+ coverage: {
151
+ files: number;
152
+ totalFiles: number;
153
+ share: number;
154
+ };
155
+ guardrails: {
156
+ present: number;
157
+ total: number;
158
+ };
159
+ delta?: number | undefined;
160
+ overall?: number | undefined;
161
+ sha: string;
162
+ at: string;
163
+ rubric: string;
164
+ } | undefined;
165
+ cycles: {
166
+ last?: {
167
+ at: string;
168
+ tone: Tone;
169
+ text: string;
170
+ sha?: string;
171
+ } | undefined;
172
+ count: number;
173
+ };
174
+ refactors: {
175
+ rejectedBy: {
176
+ step: string;
177
+ label: string;
178
+ count: number;
179
+ }[];
180
+ keptRate?: number | undefined;
181
+ total: number;
182
+ working: number;
183
+ waits: number;
184
+ rejected: number;
185
+ error: number;
186
+ merged: number;
187
+ closed: number;
188
+ judged: number;
189
+ kept: number;
190
+ };
191
+ decisions: {
192
+ toPublish: number;
193
+ toReview: number;
194
+ };
195
+ backlog: {
196
+ total: number;
197
+ open: number;
198
+ /** The open opportunities in the order the refactorer picks them. */
199
+ ready: string[];
200
+ /** Open, but a waiting refactor changes one of its files. */
201
+ busy: string[];
202
+ /** Open, and a refactor works on it now. */
203
+ working: string[];
204
+ byStrength: Record<Strength, number>;
205
+ verified: number;
206
+ proposed: number;
207
+ done: number;
208
+ exhausted: number;
209
+ dismissed: number;
210
+ };
211
+ main?: {
212
+ failed?: string | undefined;
213
+ sha: string;
214
+ state: "running" | "failed" | "passed";
215
+ runId: string;
216
+ at: string;
217
+ } | undefined;
218
+ project: string;
219
+ verdict: {
220
+ tone: Tone;
221
+ text: string;
222
+ detail: string;
223
+ };
224
+ activity: {
225
+ state: "running" | "waiting" | "stopped";
226
+ text: string;
227
+ detail: string;
228
+ };
229
+ };
230
+ export {};
@@ -0,0 +1,398 @@
1
+ import { pickOpportunities, } from '../../domain/index.js';
2
+ import { METRICS, meanScores, overallPercent, toPercent } from '../../metrics/index.js';
3
+ const STEP_LABELS = {
4
+ refactorer: 'Refactorer',
5
+ guards: 'Guards',
6
+ checks: 'Checks',
7
+ mutation: 'Mutation',
8
+ metrics: 'Metrics',
9
+ review: 'Review',
10
+ pr: 'Pull request',
11
+ };
12
+ const DAY = 24 * 3_600_000;
13
+ /**
14
+ * The pipeline of one refactor, from its own record and its check runs.
15
+ * The verdict and the proof hold the result of each step, so an old refactor
16
+ * shows the right steps also when its check runs are no longer on the page.
17
+ */
18
+ export function refactorRow(refactor, opportunity, runs, active) {
19
+ const { verdict, proof } = refactor;
20
+ const interrupted = (refactor.status === 'working' && !active) ||
21
+ (refactor.status === 'failed' && /^Interrupted/.test(refactor.reason ?? ''));
22
+ const working = refactor.status === 'working' && !interrupted;
23
+ const mine = runs.filter((r) => r.refactorId === refactor.id);
24
+ const gate = (verdict?.checkRunId && runs.find((r) => r.id === verdict.checkRunId)) ||
25
+ mine.findLast((r) => r.purpose === 'gate');
26
+ const tests = opportunity?.side === 'tests';
27
+ const mutationRun = mine.findLast((r) => r.purpose === 'mutation');
28
+ const mutation = proof?.behaviour.mutation;
29
+ const states = {};
30
+ states.refactorer = refactor.headSha ? 'ok' : working ? 'run' : 'bad';
31
+ states.guards = verdict ? (verdict.guards.length ? 'bad' : 'ok') : gate ? 'ok' : '';
32
+ states.checks = gate
33
+ ? gate.status === 'running'
34
+ ? working
35
+ ? 'run'
36
+ : ''
37
+ : gate.status === 'passed'
38
+ ? 'ok'
39
+ : 'bad'
40
+ : verdict?.checks.some((c) => !c.ok)
41
+ ? 'bad'
42
+ : '';
43
+ if (tests)
44
+ states.mutation = mutation
45
+ ? mutation.caught && !mutation.caughtBefore
46
+ ? 'ok'
47
+ : 'bad'
48
+ : working && mutationRun?.status === 'running'
49
+ ? 'run'
50
+ : '';
51
+ const ready = states.checks === 'ok' && (!tests || states.mutation === 'ok');
52
+ states.metrics = proof?.improvement ? (proof.improvement.improved ? 'ok' : 'bad') : working && ready ? 'run' : '';
53
+ states.review = verdict?.review
54
+ ? verdict.review.approve
55
+ ? 'ok'
56
+ : 'bad'
57
+ : working && states.metrics === 'ok'
58
+ ? 'run'
59
+ : '';
60
+ states.pr = refactor.prUrl
61
+ ? refactor.status === 'closed'
62
+ ? 'bad'
63
+ : 'ok'
64
+ : refactor.status === 'verified'
65
+ ? 'wait'
66
+ : '';
67
+ const order = [
68
+ 'refactorer',
69
+ 'guards',
70
+ 'checks',
71
+ ...(tests ? ['mutation'] : []),
72
+ 'metrics',
73
+ 'review',
74
+ 'pr',
75
+ ];
76
+ const steps = order.map((id) => ({
77
+ id,
78
+ label: id === 'pr' && refactor.status === 'merged' ? 'Merged' : STEP_LABELS[id],
79
+ state: states[id] ?? '',
80
+ }));
81
+ const outcome = interrupted
82
+ ? 'interrupted'
83
+ : working
84
+ ? 'working'
85
+ : refactor.status === 'verified' || refactor.status === 'published'
86
+ ? 'waits'
87
+ : refactor.status === 'failed'
88
+ ? 'error'
89
+ : refactor.status;
90
+ const rejectedAt = refactor.status === 'rejected' ? (steps.find((s) => s.state === 'bad')?.id ?? 'refactorer') : undefined;
91
+ return {
92
+ ...refactor,
93
+ title: opportunity?.title ?? refactor.branch,
94
+ ...(opportunity ? { kind: opportunity.kind, side: opportunity.side } : {}),
95
+ outcome,
96
+ ...(rejectedAt ? { rejectedAt } : {}),
97
+ steps,
98
+ };
99
+ }
100
+ /**
101
+ * The result of a check run, for people. A mutation run fails its tests on
102
+ * purpose: on the refactor, a failed run means the new tests caught the
103
+ * planted bug. On main, for new coverage, the old tests must miss it.
104
+ */
105
+ export function checkRunRow(run, active) {
106
+ const outcome = () => {
107
+ if (run.status === 'running')
108
+ return active ? { tone: 'run', text: 'running' } : { tone: 'stop', text: 'stopped' };
109
+ if (run.purpose !== 'mutation')
110
+ return run.status === 'passed' ? { tone: 'ok', text: 'passed' } : { tone: 'bad', text: 'failed' };
111
+ const caught = run.status === 'failed';
112
+ if (run.branch === 'main')
113
+ return caught ? { tone: 'bad', text: 'old tests catch it' } : { tone: 'ok', text: 'old tests miss it' };
114
+ return caught ? { tone: 'ok', text: 'bug caught' } : { tone: 'bad', text: 'bug missed' };
115
+ };
116
+ const result = outcome();
117
+ return { ...run, outcome: result, problem: result.tone === 'bad' && run.purpose !== 'refactorer' };
118
+ }
119
+ /** The numbers of the whole project. Each page reads them from here, so two pages cannot disagree. */
120
+ export function projectSummary(input) {
121
+ const { refactors, checkRuns, opportunities, sessions } = input;
122
+ const now = input.now.getTime();
123
+ const count = (list, test) => list.filter(test).length;
124
+ const recent = (iso) => !!iso && now - Date.parse(iso) < 7 * DAY;
125
+ // Main: the newest finished run of the checks on the patrolled commit.
126
+ const baseRuns = checkRuns.filter((r) => r.purpose === 'base');
127
+ const lastBase = baseRuns.findLast((r) => r.outcome.tone !== 'stop');
128
+ const main = lastBase
129
+ ? {
130
+ sha: lastBase.sha,
131
+ state: lastBase.status,
132
+ runId: lastBase.id,
133
+ at: lastBase.finishedAt ?? lastBase.startedAt,
134
+ ...(lastBase.status === 'failed' ? { failed: lastBase.results.find((r) => !r.ok)?.name ?? 'a check' } : {}),
135
+ }
136
+ : undefined;
137
+ const blocked = main?.state === 'failed';
138
+ const outcomes = (outcome) => count(refactors, (r) => r.outcome === outcome);
139
+ const judged = refactors.filter((r) => ['waits', 'rejected', 'merged', 'closed'].includes(r.outcome));
140
+ const kept = count(judged, (r) => r.outcome !== 'rejected');
141
+ const rejectedBy = new Map();
142
+ for (const r of refactors)
143
+ if (r.rejectedAt)
144
+ rejectedBy.set(r.rejectedAt, (rejectedBy.get(r.rejectedAt) ?? 0) + 1);
145
+ const toPublish = refactors.filter((r) => r.status === 'verified');
146
+ const toReview = refactors.filter((r) => r.status === 'published');
147
+ const open = opportunities.filter((o) => o.status === 'open' && !toPublish.some((r) => r.opportunityId === o.id));
148
+ const working = new Set(refactors.filter((r) => r.outcome === 'working').map((r) => r.opportunityId));
149
+ const ready = pickOpportunities(opportunities, refactors, {
150
+ count: Number.POSITIVE_INFINITY,
151
+ maxOpen: Number.POSITIVE_INFINITY,
152
+ }).filter((o) => !working.has(o.id));
153
+ const byStrength = { strong: 0, 'worth-exploring': 0, speculative: 0 };
154
+ for (const o of open)
155
+ byStrength[o.strength]++;
156
+ const patrolStatus = input.statuses.find((s) => s.role === 'patrol');
157
+ const runningStatus = input.statuses.find((s) => s.alive && s.state === 'running' && s.role !== 'patrol');
158
+ const activity = patrolStatus?.alive && patrolStatus.state === 'running'
159
+ ? { state: 'running', text: 'Patrol runs', detail: patrolStatus.detail }
160
+ : runningStatus
161
+ ? { state: 'running', text: `${capital(runningStatus.role)} runs`, detail: runningStatus.detail }
162
+ : patrolStatus?.alive
163
+ ? { state: 'waiting', text: 'Patrol waits', detail: patrolStatus.detail }
164
+ : { state: 'stopped', text: 'Not running', detail: 'No Sloppatrol process runs now.' };
165
+ const lastCycle = input.cycles.at(-1);
166
+ const cycle = lastCycle
167
+ ? cycleResult(lastCycle)
168
+ : input.patrol.lastCycleAt
169
+ ? { at: input.patrol.lastCycleAt, tone: 'info', text: 'Finished' }
170
+ : undefined;
171
+ const quality = qualityOf(input.scorecards);
172
+ const failedSessions = sessions.filter((s) => s.state === 'failed' && recent(s.startedAt));
173
+ const stale = input.statuses.filter((s) => s.stale);
174
+ const interrupted = refactors.filter((r) => r.outcome === 'interrupted' && r.status === 'working');
175
+ const attention = [];
176
+ if (main && blocked)
177
+ attention.push({
178
+ tone: 'fail',
179
+ title: `The check ${main.failed} fails on main`,
180
+ detail: `Commit ${main.sha.slice(0, 10)}. No refactor can be verified until the checks pass on main. Fix main, then run the checks again.`,
181
+ command: 'sloppatrol check',
182
+ target: { tab: 'checks', select: `check:${main.runId}` },
183
+ });
184
+ if (interrupted.length || stale.length)
185
+ attention.push({
186
+ tone: 'warn',
187
+ title: 'Sloppatrol stopped during its work',
188
+ detail: interrupted.length
189
+ ? `${plural(interrupted.length, 'refactor')} did not finish. The next patrol cleans up the worktree and tries again.`
190
+ : `The ${stale.map((s) => s.role).join(', ')} stopped without an end. The next patrol cleans up.`,
191
+ command: 'sloppatrol',
192
+ target: { tab: 'refactors', filter: 'error' },
193
+ });
194
+ if (toPublish.length)
195
+ attention.push({
196
+ tone: 'warn',
197
+ title: `${plural(toPublish.length, 'verified refactor')} ${toPublish.length === 1 ? 'waits' : 'wait'} to be published`,
198
+ detail: toPublish.length === 1
199
+ ? 'The gate kept it. Publish it to open its pull request.'
200
+ : 'The gate kept them. Publish them to open a pull request for each.',
201
+ command: 'sloppatrol publish',
202
+ target: { tab: 'refactors', filter: 'waits' },
203
+ links: toPublish.map((r) => ({ label: r.title, href: '', select: `refactor:${r.id}` })),
204
+ });
205
+ if (toReview.length)
206
+ attention.push({
207
+ tone: 'info',
208
+ title: `${plural(toReview.length, 'pull request')} ${toReview.length === 1 ? 'waits' : 'wait'} for your review`,
209
+ detail: 'Merge a pull request to keep the change. Close it to dismiss the opportunity for good.',
210
+ target: { tab: 'refactors', filter: 'waits' },
211
+ links: toReview.map((r) => ({ label: r.title, href: r.prUrl ?? '', select: `refactor:${r.id}` })),
212
+ });
213
+ if (failedSessions.length)
214
+ attention.push({
215
+ tone: 'warn',
216
+ title: `${plural(failedSessions.length, 'agent session')} failed in the last 7 days`,
217
+ detail: `Last error: ${failedSessions.at(-1).last}`,
218
+ command: 'sloppatrol doctor',
219
+ target: { tab: 'sessions', filter: 'failed' },
220
+ });
221
+ if (main && !blocked && !open.length && opportunities.length)
222
+ attention.push({
223
+ tone: 'info',
224
+ title: 'The backlog is empty',
225
+ detail: 'The refactorer has no open opportunity. The surveyor records more when the backlog is low.',
226
+ command: 'sloppatrol survey',
227
+ target: { tab: 'backlog' },
228
+ });
229
+ const started = !!(main ||
230
+ refactors.length ||
231
+ opportunities.length ||
232
+ input.scorecards.length ||
233
+ input.cycles.length);
234
+ const errors = interrupted.length + stale.length + failedSessions.length > 0;
235
+ const waiting = toPublish.length + toReview.length;
236
+ const verdict = !started
237
+ ? { tone: 'info', text: 'Not started', detail: 'Sloppatrol has not run on this repo yet.' }
238
+ : blocked
239
+ ? { tone: 'fail', text: 'Blocked', detail: 'The checks fail on main, so no refactor can be verified.' }
240
+ : errors
241
+ ? { tone: 'warn', text: 'Needs a look', detail: 'Some work stopped with an error.' }
242
+ : waiting
243
+ ? {
244
+ tone: 'warn',
245
+ text: 'Waits for you',
246
+ detail: `${plural(waiting, 'refactor')} ${waiting === 1 ? 'waits' : 'wait'} for a decision.`,
247
+ }
248
+ : !main
249
+ ? { tone: 'info', text: 'No checks yet', detail: 'The checks have not run on main yet.' }
250
+ : { tone: 'pass', text: 'Healthy', detail: 'The checks pass on main, and nothing waits for you.' };
251
+ const mutationRuns = checkRuns.filter((r) => r.purpose === 'mutation' && r.branch !== 'main' && r.outcome.tone !== 'run');
252
+ const byPurpose = (purpose) => {
253
+ const list = checkRuns.filter((r) => r.purpose === purpose);
254
+ return {
255
+ runs: list.length,
256
+ bad: count(list, (r) => r.outcome.tone === 'bad'),
257
+ problems: count(list, (r) => r.problem),
258
+ };
259
+ };
260
+ const cost = (list) => Math.round(list.reduce((sum, s) => sum + s.costUsd, 0) * 100) / 100;
261
+ return {
262
+ project: input.project,
263
+ verdict,
264
+ activity,
265
+ ...(main ? { main } : {}),
266
+ cycles: { count: input.patrol.cycles, ...(cycle ? { last: cycle } : {}) },
267
+ refactors: {
268
+ total: refactors.length,
269
+ working: outcomes('working'),
270
+ waits: outcomes('waits'),
271
+ rejected: outcomes('rejected'),
272
+ error: outcomes('error') + outcomes('interrupted'),
273
+ merged: outcomes('merged'),
274
+ closed: outcomes('closed'),
275
+ judged: judged.length,
276
+ kept,
277
+ ...(judged.length ? { keptRate: Math.round((kept / judged.length) * 100) } : {}),
278
+ rejectedBy: [...rejectedBy]
279
+ .map(([step, n]) => ({ step, label: STEP_LABELS[step], count: n }))
280
+ .sort((a, b) => b.count - a.count),
281
+ },
282
+ decisions: { toPublish: toPublish.length, toReview: toReview.length },
283
+ backlog: {
284
+ total: opportunities.length,
285
+ open: open.length,
286
+ /** The open opportunities in the order the refactorer picks them. */
287
+ ready: ready.map((o) => o.id),
288
+ /** Open, but a waiting refactor changes one of its files. */
289
+ busy: open.filter((o) => !ready.includes(o) && !working.has(o.id)).map((o) => o.id),
290
+ /** Open, and a refactor works on it now. */
291
+ working: [...working],
292
+ byStrength,
293
+ verified: toPublish.length,
294
+ proposed: count(opportunities, (o) => o.status === 'proposed'),
295
+ done: count(opportunities, (o) => o.status === 'done'),
296
+ exhausted: count(opportunities, (o) => o.status === 'exhausted'),
297
+ dismissed: count(opportunities, (o) => o.status === 'dismissed'),
298
+ },
299
+ ...(quality ? { quality } : {}),
300
+ checks: {
301
+ runs: checkRuns.length,
302
+ problems: count(checkRuns, (r) => r.problem),
303
+ base: byPurpose('base'),
304
+ gate: byPurpose('gate'),
305
+ refactorer: byPurpose('refactorer'),
306
+ mutation: byPurpose('mutation'),
307
+ ...(mutationRuns.length
308
+ ? { caught: count(mutationRuns, (r) => r.outcome.tone === 'ok'), planted: mutationRuns.length }
309
+ : {}),
310
+ },
311
+ sessions: {
312
+ total: sessions.length,
313
+ running: count(sessions, (s) => s.state === 'running'),
314
+ failed: count(sessions, (s) => s.state === 'failed'),
315
+ failedRecent: failedSessions.length,
316
+ stopped: count(sessions, (s) => s.state === 'stopped'),
317
+ costUsd: cost(sessions),
318
+ costRecentUsd: cost(sessions.filter((s) => recent(s.startedAt))),
319
+ byRole: Object.fromEntries([...new Set(sessions.map((s) => s.role))].map((role) => [role, count(sessions, (s) => s.role === role)])),
320
+ },
321
+ attention,
322
+ };
323
+ }
324
+ function cycleResult(cycle) {
325
+ const kept = cycle.refactors.filter((r) => r.status === 'verified').length;
326
+ const parts = [
327
+ cycle.surveyed ? `${plural(cycle.surveyed, 'opportunity', 'opportunities')} found` : '',
328
+ cycle.refactors.length ? `${kept} of ${plural(cycle.refactors.length, 'refactor')} kept` : '',
329
+ cycle.published ? `${plural(cycle.published, 'pull request')} opened` : '',
330
+ ].filter(Boolean);
331
+ const base = { at: cycle.finishedAt, ...(cycle.sha ? { sha: cycle.sha } : {}) };
332
+ if (cycle.error)
333
+ return { ...base, tone: 'fail', text: `Failed: ${cycle.error}` };
334
+ if (cycle.blocked)
335
+ return { ...base, tone: 'fail', text: 'Blocked: the checks fail on main' };
336
+ return {
337
+ ...base,
338
+ tone: kept || cycle.published ? 'pass' : 'info',
339
+ text: parts.length ? capital(parts.join(', ')) : 'No new work',
340
+ };
341
+ }
342
+ /**
343
+ * The newest scorecard, with the change since the one before. The change
344
+ * compares like with like: only the files that both scorecards scored, so
345
+ * a file that joins the panel does not read as a change in quality.
346
+ */
347
+ function qualityOf(scorecards) {
348
+ const current = scorecards.at(-1);
349
+ if (!current)
350
+ return undefined;
351
+ const previous = scorecards.at(-2);
352
+ const before = new Map(previous?.units.map((u) => [u.path, u]) ?? []);
353
+ const common = current.units.filter((u) => before.has(u.path));
354
+ const now = meanScores(common);
355
+ const then = meanScores(common.map((u) => before.get(u.path)));
356
+ const shared = Object.keys(now).filter((id) => then[id] !== undefined);
357
+ const pick = (means) => Object.fromEntries(shared.map((id) => [id, means[id]]));
358
+ const overallNow = overallPercent(pick(now));
359
+ const overallThen = overallPercent(pick(then));
360
+ const c = current.coverage;
361
+ const overall = overallPercent(current.means);
362
+ return {
363
+ sha: current.sha,
364
+ at: current.at,
365
+ rubric: current.rubric,
366
+ ...(overall !== undefined ? { overall } : {}),
367
+ ...(overallNow !== undefined && overallThen !== undefined ? { delta: overallNow - overallThen } : {}),
368
+ /** The files that both of the last two scorecards scored: the change compares these only. */
369
+ compared: common.length,
370
+ trend: scorecards.map((card) => overallPercent(card.means)).filter((v) => v !== undefined),
371
+ metrics: METRICS.filter((m) => current.means[m.id] !== undefined).map((m) => ({
372
+ id: m.id,
373
+ name: m.name,
374
+ value: toPercent(current.means[m.id]),
375
+ ...(shared.includes(m.id) ? { delta: toPercent(now[m.id]) - toPercent(then[m.id]) } : {}),
376
+ trend: scorecards
377
+ .map((card) => card.means[m.id])
378
+ .filter((v) => v !== undefined)
379
+ .map(toPercent),
380
+ })),
381
+ coverage: {
382
+ files: c.files,
383
+ totalFiles: c.totalFiles,
384
+ share: c.totalLines ? Math.round((c.lines / c.totalLines) * 100) : 0,
385
+ },
386
+ guardrails: {
387
+ present: (current.guardrails ?? []).filter((g) => g.present).length,
388
+ total: (current.guardrails ?? []).length,
389
+ },
390
+ };
391
+ }
392
+ function plural(n, word, many = `${word}s`) {
393
+ return `${n.toLocaleString('en-US')} ${n === 1 ? word : many}`;
394
+ }
395
+ function capital(text) {
396
+ return text.charAt(0).toUpperCase() + text.slice(1);
397
+ }
398
+ //# sourceMappingURL=summary.js.map