acuvo-code 0.2.0

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 (154) hide show
  1. package/CHANGELOG.md +328 -0
  2. package/ENTERPRISE.md +927 -0
  3. package/LICENSE +120 -0
  4. package/README.md +1245 -0
  5. package/ROADMAP.md +556 -0
  6. package/bin/acuvo-mcp.mjs +208 -0
  7. package/bin/acuvo.mjs +3108 -0
  8. package/lib/acceptance-consent.mjs +168 -0
  9. package/lib/acceptance.mjs +859 -0
  10. package/lib/account.mjs +226 -0
  11. package/lib/acuvo-dir.mjs +72 -0
  12. package/lib/acuvo-models.mjs +141 -0
  13. package/lib/apply-patch.mjs +570 -0
  14. package/lib/ask-user.mjs +173 -0
  15. package/lib/audit.mjs +530 -0
  16. package/lib/auto-lease.mjs +174 -0
  17. package/lib/background.mjs +842 -0
  18. package/lib/best-of.mjs +334 -0
  19. package/lib/board.mjs +232 -0
  20. package/lib/breaker.mjs +93 -0
  21. package/lib/budget.mjs +1375 -0
  22. package/lib/builtin-skills.mjs +135 -0
  23. package/lib/cache-floor.mjs +204 -0
  24. package/lib/chain.mjs +303 -0
  25. package/lib/changed-paths.mjs +84 -0
  26. package/lib/chat.mjs +434 -0
  27. package/lib/checkpoint.mjs +637 -0
  28. package/lib/child-lifetime.mjs +71 -0
  29. package/lib/cli-args.mjs +1255 -0
  30. package/lib/code-review.mjs +1382 -0
  31. package/lib/colour.mjs +82 -0
  32. package/lib/command.mjs +2847 -0
  33. package/lib/compact.mjs +1151 -0
  34. package/lib/completion.mjs +515 -0
  35. package/lib/creative-engines.mjs +779 -0
  36. package/lib/db-inspect.mjs +1624 -0
  37. package/lib/delete.mjs +111 -0
  38. package/lib/design-loop.mjs +570 -0
  39. package/lib/diff-preview.mjs +1044 -0
  40. package/lib/doctor.mjs +2139 -0
  41. package/lib/dropped.mjs +216 -0
  42. package/lib/edit-diagnostics.mjs +277 -0
  43. package/lib/edit.mjs +460 -0
  44. package/lib/env-file.mjs +250 -0
  45. package/lib/escalate.mjs +702 -0
  46. package/lib/evaluate.mjs +284 -0
  47. package/lib/fetch-text.mjs +952 -0
  48. package/lib/fleet-budget.mjs +256 -0
  49. package/lib/gh.mjs +1536 -0
  50. package/lib/git.mjs +1341 -0
  51. package/lib/github.mjs +261 -0
  52. package/lib/h2.mjs +194 -0
  53. package/lib/handoff.mjs +417 -0
  54. package/lib/hooks.mjs +626 -0
  55. package/lib/http-probe.mjs +907 -0
  56. package/lib/image-director.mjs +322 -0
  57. package/lib/image-edit.mjs +522 -0
  58. package/lib/imagegen.mjs +998 -0
  59. package/lib/interrupt.mjs +234 -0
  60. package/lib/learned.mjs +353 -0
  61. package/lib/lease-watch.mjs +115 -0
  62. package/lib/lease.mjs +868 -0
  63. package/lib/localize.mjs +834 -0
  64. package/lib/log-tail.mjs +1052 -0
  65. package/lib/login.mjs +157 -0
  66. package/lib/lsp.mjs +1613 -0
  67. package/lib/mcp-consent.mjs +377 -0
  68. package/lib/mcp-defaults.mjs +780 -0
  69. package/lib/mcp-server.mjs +1343 -0
  70. package/lib/mcp.mjs +1263 -0
  71. package/lib/media.mjs +1283 -0
  72. package/lib/memory-workspace.mjs +179 -0
  73. package/lib/model-json.mjs +132 -0
  74. package/lib/model-tier.mjs +171 -0
  75. package/lib/model.mjs +1445 -0
  76. package/lib/parallel.mjs +144 -0
  77. package/lib/perchance.mjs +210 -0
  78. package/lib/plan-coherence.mjs +1461 -0
  79. package/lib/plan-ledger.mjs +981 -0
  80. package/lib/plan.mjs +461 -0
  81. package/lib/policy.mjs +783 -0
  82. package/lib/prefix-order.mjs +38 -0
  83. package/lib/project-memory.mjs +127 -0
  84. package/lib/prompt.mjs +109 -0
  85. package/lib/python.mjs +862 -0
  86. package/lib/rcfile.mjs +853 -0
  87. package/lib/read-window.mjs +743 -0
  88. package/lib/refute-tools.mjs +34 -0
  89. package/lib/refute.mjs +806 -0
  90. package/lib/repl-driver.mjs +264 -0
  91. package/lib/repl.mjs +324 -0
  92. package/lib/replay.mjs +1218 -0
  93. package/lib/repo-map.mjs +1101 -0
  94. package/lib/report.mjs +419 -0
  95. package/lib/search-rank.mjs +99 -0
  96. package/lib/search.mjs +659 -0
  97. package/lib/secret-paths.mjs +54 -0
  98. package/lib/session.mjs +1017 -0
  99. package/lib/skills.mjs +703 -0
  100. package/lib/slash.mjs +356 -0
  101. package/lib/spawn-argv.mjs +1151 -0
  102. package/lib/spend.mjs +250 -0
  103. package/lib/steer.mjs +280 -0
  104. package/lib/stream.mjs +253 -0
  105. package/lib/stuck.mjs +712 -0
  106. package/lib/subagent.mjs +749 -0
  107. package/lib/terminal-graphics.mjs +171 -0
  108. package/lib/tool-prefix.mjs +226 -0
  109. package/lib/tool-shortlist.mjs +162 -0
  110. package/lib/tools.mjs +2333 -0
  111. package/lib/tsserver.mjs +423 -0
  112. package/lib/turn.mjs +5672 -0
  113. package/lib/untrusted-block.mjs +271 -0
  114. package/lib/verify-claim.mjs +299 -0
  115. package/lib/vision.mjs +330 -0
  116. package/lib/voice-task.mjs +561 -0
  117. package/lib/warm-provider.mjs +255 -0
  118. package/lib/websearch.mjs +401 -0
  119. package/lib/workspace.mjs +928 -0
  120. package/lib/write-approval.mjs +235 -0
  121. package/lib/write-many.mjs +162 -0
  122. package/package.json +62 -0
  123. package/scripts/bundle.mjs +768 -0
  124. package/scripts/cache-floor.mjs +176 -0
  125. package/scripts/machine.mjs +226 -0
  126. package/scripts/test.mjs +139 -0
  127. package/skills/accessibility.md +87 -0
  128. package/skills/acuvo-design-system.md +123 -0
  129. package/skills/animation.md +84 -0
  130. package/skills/api-design.md +82 -0
  131. package/skills/auth-and-sessions.md +78 -0
  132. package/skills/build-with-a-framework.md +101 -0
  133. package/skills/colour-and-contrast.md +112 -0
  134. package/skills/creative-engines.md +81 -0
  135. package/skills/css-layout.md +85 -0
  136. package/skills/data-and-charts.md +77 -0
  137. package/skills/debugging.md +76 -0
  138. package/skills/designing-by-looking.md +84 -0
  139. package/skills/error-handling.md +78 -0
  140. package/skills/forms-and-validation.md +93 -0
  141. package/skills/nextjs-app-router.md +75 -0
  142. package/skills/page-composition.md +103 -0
  143. package/skills/performance.md +77 -0
  144. package/skills/plan-before-building.md +52 -0
  145. package/skills/planning-and-delegating.md +72 -0
  146. package/skills/refactoring.md +70 -0
  147. package/skills/security-basics.md +76 -0
  148. package/skills/state-management.md +73 -0
  149. package/skills/supabase-multitenant.md +72 -0
  150. package/skills/typescript-strict.md +90 -0
  151. package/skills/typography.md +135 -0
  152. package/skills/verify-your-own-work.md +62 -0
  153. package/skills/web-app-quality.md +62 -0
  154. package/skills/working-in-the-background.md +64 -0
@@ -0,0 +1,256 @@
1
+ /**
2
+ * ── ⭐ THE FLEET CEILING — "CHEAP" HAS TO BE TRUE IN AGGREGATE, NOT PER WORKER ──
3
+ *
4
+ * The product direction is seven terminals working one repository at once. Every
5
+ * piece of that is measured and working except one thing nobody had checked:
6
+ * `lib/budget.mjs` caps a RUN. Seven runs multiply the spend by seven, so the
7
+ * ceiling a person actually chose ($0.02) silently becomes a different number
8
+ * ($0.14) as soon as they do the thing the product tells them to do.
9
+ *
10
+ * ⚠️ That is the same defect as the per-turn-vs-per-run bug fixed on 2026-08-12,
11
+ * scaled by the fleet: a limit enforced at the wrong granularity is not a limit,
12
+ * it is a unit of measurement. And the governor — *tell me the price before it
13
+ * runs and stop at the number I gave you* — is the one differentiator a public
14
+ * company's finance function structurally cannot copy. A fleet is exactly where
15
+ * it has to hold, or it was never the claim we said it was.
16
+ *
17
+ * ── ⭐⭐ THERE IS NO NEW LEDGER HERE, AND THAT IS THE POINT ───────────────────
18
+ *
19
+ * The obvious build is a shared `.acuvo/fleet-budget.json` with a lock around
20
+ * it: a new file, a new write path, a new race between seven processes, and a
21
+ * new way to be wrong about money. None of that is necessary, because the
22
+ * ledger already exists and every run already writes to it:
23
+ *
24
+ * .acuvo/audit/YYYY-MM-DD.jsonl ← appended by EVERY run in this workspace
25
+ * with `run.costUsd`, one JSON line each
26
+ *
27
+ * Seven terminals in one repository are already writing to one audit directory,
28
+ * in day files, with O_APPEND. So the fleet's spend is a QUESTION asked of data
29
+ * that is already on disk, not a second book to keep in sync with the first.
30
+ * `spend.mjs` already sums it — this module only decides what the sum means.
31
+ *
32
+ * ⭐ And the day file makes the window obvious: the fleet ceiling is a spend cap
33
+ * for this workspace for today, across every terminal. That is the number
34
+ * somebody needs before they dare leave seven workers running overnight, and it
35
+ * composes with the per-run ceiling rather than replacing it — one bounds the
36
+ * blast radius of a single mistake, the other bounds the blast radius of the
37
+ * whole fleet.
38
+ *
39
+ * ── ⚠️ WHAT THIS CANNOT SEE, STATED PLAINLY RATHER THAN GLOSSED ─────────────
40
+ *
41
+ * A run appends its audit record when it FINISHES. So at any moment the log
42
+ * knows what the fleet has SPENT, not what it is CURRENTLY spending: six other
43
+ * workers may each be mid-run with money already committed and nothing written
44
+ * down yet.
45
+ *
46
+ * The overshoot that allows is bounded and worth naming, because a bound you can
47
+ * state is a different thing from a hole:
48
+ *
49
+ * worst case = fleetLimit + (other live workers × their own per-run ceiling)
50
+ *
51
+ * With the default $0.02 per-run ceiling and seven terminals that is at most
52
+ * about twelve cents past the line. ⚠️ It is NOT bounded for a worker running
53
+ * `--budget none`, which is precisely why `fleetVerdict` refuses to report a
54
+ * comfortable number when it knows its inputs are incomplete: see `unknown` and
55
+ * `damaged` below.
56
+ */
57
+
58
+ import { readAuditFiles, summariseSpend } from './spend.mjs';
59
+ import { formatUsd, USD_EPSILON, FLEET_STOP_REASONS } from './budget.mjs';
60
+
61
+ /**
62
+ * Reasons a fleet check can come back.
63
+ *
64
+ * ⚠️ THE STOPS ARE SPREAD IN FROM `budget.mjs`, NOT RETYPED. They have to
65
+ * appear in `BUDGET_REASONS` too — that is how `escalate.mjs` learns a fleet
66
+ * stop is a budget stop — and two hand-maintained copies of the same strings is
67
+ * how a reason ends up named in one place and never emitted from the other.
68
+ * The import direction is fleet → budget only; budget never imports this file.
69
+ */
70
+ export const FLEET_REASONS = ['ok', 'no-fleet-budget', ...FLEET_STOP_REASONS];
71
+
72
+ /**
73
+ * The UTC start of the day a moment falls in — the same boundary `dayFileName`
74
+ * uses, so the window and the files it reads can never disagree.
75
+ *
76
+ * ⚠️ UTC, not local. The audit files are named in UTC; a local-midnight window
77
+ * would ask for "today" and be handed two half-days for anyone east or west of
78
+ * Greenwich, twice reporting a fleet total that is not the one the day file
79
+ * holds. Being consistently wrong about which 24 hours is far better than being
80
+ * inconsistently right.
81
+ */
82
+ export function startOfUtcDay(now = new Date()) {
83
+ const at = now instanceof Date ? now : new Date(now);
84
+ return new Date(Date.UTC(at.getUTCFullYear(), at.getUTCMonth(), at.getUTCDate()));
85
+ }
86
+
87
+ /**
88
+ * What has this workspace spent since `since`, across every terminal?
89
+ *
90
+ * Thin by design: the reading and the summing already exist and are already
91
+ * tested. `readImpl` is injected for the same reason every other disk touch in
92
+ * this package is — a money decision must be testable without a filesystem.
93
+ *
94
+ * @param {string} root
95
+ * @param {{ since?: Date|null, readImpl?: (root: string) => {name: string, text: string}[] }} [opts]
96
+ */
97
+ export function readFleetSpend(root, { since = null, readImpl = readAuditFiles } = {}) {
98
+ let files;
99
+ try {
100
+ files = readImpl(root);
101
+ } catch (err) {
102
+ /**
103
+ * ⚠️ AN UNREADABLE LEDGER IS NOT A ZERO. Returning 0 here would make an
104
+ * unreadable audit directory look like a fresh, unspent day and hand the
105
+ * fleet its entire ceiling again — the failure mode where the safety
106
+ * feature pays for the accident. The verdict below refuses instead.
107
+ */
108
+ return { ok: false, error: `could not read the fleet ledger: ${err?.message ?? err}`, totalUsd: 0, runs: 0, unknown: 0, damaged: 0 };
109
+ }
110
+ const summary = summariseSpend(files, { since });
111
+ return {
112
+ ok: true,
113
+ totalUsd: summary.totalUsd,
114
+ runs: summary.runs,
115
+ /** Runs whose cost is genuinely unknown — never counted as zero. */
116
+ unknown: summary.unknown,
117
+ /** Lines that would not parse. Seven appenders can interleave; say so. */
118
+ damaged: summary.damaged,
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Does the fleet have room for what this run is about to do?
124
+ *
125
+ * Pure. Everything that touches the disk happened in `readFleetSpend`, so every
126
+ * branch below is reachable from a test with three numbers and no temp
127
+ * directory.
128
+ *
129
+ * @param {object} args
130
+ * @param {number|null} args.fleetLimitUsd the ceiling across all terminals. null = off.
131
+ * @param {object} args.fleetSpend what `readFleetSpend` returned.
132
+ * @param {number} args.thisRunUsd what THIS run has spent so far (not yet in the log).
133
+ * @param {number} args.projectedUsd what the next round is projected to cost.
134
+ */
135
+ export function fleetVerdict({ fleetLimitUsd = null, fleetSpend, thisRunUsd = 0, projectedUsd = 0 } = {}) {
136
+ if (fleetLimitUsd === null || fleetLimitUsd === undefined) {
137
+ return { ok: true, reason: 'no-fleet-budget', message: '', fleetSpentUsd: 0, fleetRemainingUsd: Infinity };
138
+ }
139
+
140
+ if (!fleetSpend || fleetSpend.ok !== true) {
141
+ return {
142
+ ok: false,
143
+ reason: 'fleet-limit-reached',
144
+ message:
145
+ `the fleet ceiling of ${formatUsd(fleetLimitUsd)} cannot be enforced because the shared ledger could not be read `
146
+ + `(${fleetSpend?.error ?? 'no reason given'}). Refusing rather than assuming nothing has been spent — `
147
+ + 'fix the workspace .acuvo/audit directory, or drop --fleet-budget to run without a fleet ceiling.',
148
+ fleetSpentUsd: 0,
149
+ fleetRemainingUsd: 0,
150
+ };
151
+ }
152
+
153
+ /**
154
+ * ⚠️ THIS RUN'S OWN SPEND IS ADDED SEPARATELY, AND IT MUST BE. Its audit
155
+ * record is not written until the run ends, so it is absent from the log by
156
+ * construction — leaving it out would let a single long run spend the fleet's
157
+ * whole ceiling while the ledger insisted the fleet had spent nothing.
158
+ */
159
+ const fleetSpentUsd = fleetSpend.totalUsd + thisRunUsd;
160
+ const fleetRemainingUsd = fleetLimitUsd - fleetSpentUsd;
161
+
162
+ /**
163
+ * ⭐ THE HONESTY CLAUSE. `unknown` runs had no reported price and were never
164
+ * summed as zero; `damaged` lines could not be parsed at all. Either means the
165
+ * true total is HIGHER than the one being enforced, so the number gets a floor
166
+ * marker rather than being presented as exact. A budget that quietly
167
+ * undercounts is a budget that overspends.
168
+ */
169
+ const incomplete = fleetSpend.unknown > 0 || fleetSpend.damaged > 0;
170
+ const floorNote = incomplete
171
+ ? ` (at least — ${fleetSpend.unknown} run${fleetSpend.unknown === 1 ? '' : 's'} reported no price`
172
+ + `${fleetSpend.damaged > 0 ? ` and ${fleetSpend.damaged} ledger line${fleetSpend.damaged === 1 ? '' : 's'} could not be read` : ''}, `
173
+ + 'so the real total is higher than this)'
174
+ : '';
175
+
176
+ /** Every refusal names the flag that lifts it. */
177
+ const wayOut = ' Raise it with --fleet-budget, or drop the flag to bound each terminal only.';
178
+
179
+ if (fleetRemainingUsd <= USD_EPSILON) {
180
+ return {
181
+ ok: false,
182
+ reason: 'fleet-limit-reached',
183
+ message:
184
+ `the fleet has spent ${formatUsd(fleetSpentUsd)}${floorNote} of its ${formatUsd(fleetLimitUsd)} ceiling `
185
+ + `across ${fleetSpend.runs} run${fleetSpend.runs === 1 ? '' : 's'} in this workspace today. `
186
+ + `This terminal has spent ${formatUsd(thisRunUsd)} of that.${wayOut}`,
187
+ fleetSpentUsd,
188
+ fleetRemainingUsd: Math.max(0, fleetRemainingUsd),
189
+ incomplete,
190
+ };
191
+ }
192
+
193
+ if (fleetSpentUsd + projectedUsd > fleetLimitUsd + USD_EPSILON) {
194
+ return {
195
+ ok: false,
196
+ reason: 'fleet-would-exceed',
197
+ message:
198
+ `stopping on the fleet ceiling: ${formatUsd(fleetSpentUsd)}${floorNote} of ${formatUsd(fleetLimitUsd)} is spent `
199
+ + `across every terminal in this workspace today, and the next round is projected at ~${formatUsd(projectedUsd)}, `
200
+ + `which would cross the line.${wayOut}`,
201
+ fleetSpentUsd,
202
+ fleetRemainingUsd,
203
+ incomplete,
204
+ };
205
+ }
206
+
207
+ return {
208
+ ok: true,
209
+ reason: 'ok',
210
+ message: `${formatUsd(fleetRemainingUsd)} left of the ${formatUsd(fleetLimitUsd)} fleet ceiling${floorNote}`,
211
+ fleetSpentUsd,
212
+ fleetRemainingUsd,
213
+ incomplete,
214
+ };
215
+ }
216
+
217
+ /**
218
+ * The one line a caller needs: turn a `--fleet-budget` value into the gate
219
+ * `createBudget` accepts, or `null` when the flag was never given.
220
+ *
221
+ * ⭐ RETURNING `null` WHEN OFF IS THE WHOLE ERGONOMIC. `createBudget` guards on
222
+ * `if (fleetGate)`, so "no fleet ceiling" costs exactly one falsy check and not
223
+ * a single disk read — the feature is invisible to everyone who has not asked
224
+ * for it, which is the bar for adding anything to a path that spends money.
225
+ *
226
+ * ⚠️ THE LEDGER IS RE-READ ON EVERY CALL, DELIBERATELY. Six other terminals
227
+ * finish work while this one is thinking; a total captured once at start-up
228
+ * would be stale by the second round and would drift further the longer and
229
+ * more expensive the run got — worst exactly when it matters most. A directory
230
+ * read per round is nothing beside the model call it is gating.
231
+ *
232
+ * @param {string} root
233
+ * @param {{ fleetLimitUsd?: number|null, now?: () => Date, readImpl?: Function }} [opts]
234
+ */
235
+ export function createFleetGate(root, { fleetLimitUsd = null, since = null, now = () => new Date(), readImpl } = {}) {
236
+ if (fleetLimitUsd === null || fleetLimitUsd === undefined) return null;
237
+ return ({ projectedUsd = 0, thisRunUsd = 0 } = {}) => fleetVerdict({
238
+ fleetLimitUsd,
239
+ /**
240
+ * ── ⭐ THE WINDOW, AND WHY IT MATTERS FOR ANYTHING UNATTENDED ────────────
241
+ *
242
+ * The default is today, because the audit log is written in UTC day files
243
+ * and a window that disagrees with the file it reads is a bug waiting to be
244
+ * argued about. That is the right default for a person at a keyboard.
245
+ *
246
+ * ⚠️ It is the WRONG one for a cron. Point a schedule at this and every
247
+ * fire is a new run with a fresh per-run ceiling, so the number somebody
248
+ * chose stops being a limit and becomes a rate — $0.02 an hour is $14 a
249
+ * month, and nobody typed $14. `--budget-window 7d` makes the ceiling mean
250
+ * what an unattended operator actually needs it to mean: a total.
251
+ */
252
+ fleetSpend: readFleetSpend(root, { since: since ?? startOfUtcDay(now()), readImpl }),
253
+ thisRunUsd,
254
+ projectedUsd,
255
+ });
256
+ }