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,176 @@
1
+ /**
2
+ * ── ⭐⭐ MEASURE THE CACHE FLOOR. IT COSTS $0.00 AND IT IS THE MARGIN. ───────
3
+ *
4
+ * node scripts/cache-floor.mjs [repoA] [repoB] ...
5
+ *
6
+ * `PRICING.md` §5 states `cache floor = sharedHead ÷ typicalPrompt` and closes
7
+ * with the open item *"measure the achieved cache floor"*. This is that
8
+ * measurement, and it runs offline: the model is SCRIPTED, so what is being read
9
+ * is the bytes WE send — the half of the cache contract we control, and the only
10
+ * half that can be checked on every commit without a key or a bill.
11
+ *
12
+ * It prints two things a live `acuvo --json` reading cannot tell apart on its
13
+ * own:
14
+ *
15
+ * 1. **the floor across tenants** — how much of a brand-new user's very first
16
+ * request the provider has already seen, because some other tenant sent the
17
+ * same head. This is the number the ladder is sized on.
18
+ * 2. **the floor across configurations** — what one machine's `--no-run`, TTY
19
+ * or `MODAL_*` setting does to that head. Two tenants whose configs differ
20
+ * are two cache pools, however perfect the prefix is inside each.
21
+ *
22
+ * ⚠️ IT DOES NOT MEASURE WHAT THE PROVIDER DID. A perfect prefix still misses if
23
+ * the request lands on a cold upstream — `lib/model.mjs` measured 46.7% unpinned
24
+ * against 95.8% pinned on the same bytes. That reading comes from
25
+ * `acuvo --json`'s `.cache` block against a real key. ⭐ The point of this
26
+ * script is that it makes that reading INTERPRETABLE: a live 0% beside a
27
+ * measured 99.9% prefix is a routing finding, and a live 0% beside a measured
28
+ * 4% prefix is ours.
29
+ */
30
+
31
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
32
+ import { tmpdir } from 'node:os';
33
+ import { join, basename } from 'node:path';
34
+
35
+ import { runSession } from '../lib/turn.mjs';
36
+ import { createLocalExecutor } from '../lib/workspace.mjs';
37
+ import { toolNamesForRounds, toolSchemasFor } from '../lib/tools.mjs';
38
+ import { orderForCachePrefix } from '../lib/tool-prefix.mjs';
39
+ import { sharedPrefixBytes, describeDivergence, wireBytes } from '../lib/cache-floor.mjs';
40
+
41
+ const ROUNDS = 8;
42
+ const made = [];
43
+
44
+ /** A throwaway repository, so the script has something to measure with no arguments. */
45
+ function scratchRepo(name, files) {
46
+ const root = mkdtempSync(join(tmpdir(), `acuvo-floor-${name}-`));
47
+ made.push(root);
48
+ writeFileSync(join(root, 'package.json'), `{"name":"${name}","version":"1.0.0"}\n`);
49
+ mkdirSync(join(root, 'src'), { recursive: true });
50
+ for (let i = 0; i < files; i += 1) {
51
+ writeFileSync(join(root, 'src', `mod${i}.js`), `export function fn${i}(x) {\n return x + ${i};\n}\n`);
52
+ }
53
+ return root;
54
+ }
55
+
56
+ /**
57
+ * Round 1 of a FRESH invocation, which is the round the floor is a claim about.
58
+ * ⚠️ Rounds 2+ are a different measurement — they append tool results the
59
+ * provider has by definition never seen, so their miss is arithmetic.
60
+ */
61
+ async function firstRequest(root, task) {
62
+ let captured = null;
63
+ const callModelImpl = async (opts) => {
64
+ if (!captured) captured = { tools: opts.tools ?? null, messages: opts.messages };
65
+ return {
66
+ ok: true, content: 'done', toolCalls: [],
67
+ usage: { cost: 0, total_tokens: 0 }, finishReason: 'stop', model: 'scripted/none',
68
+ };
69
+ };
70
+ await runSession({
71
+ task,
72
+ executor: createLocalExecutor(root),
73
+ config: { apiKey: 'scripted', model: 'scripted/none' },
74
+ maxRounds: ROUNDS,
75
+ allowRun: false,
76
+ callModelImpl,
77
+ onEvent: () => {},
78
+ });
79
+ return captured;
80
+ }
81
+
82
+ const kb = (n) => `${(n / 1024).toFixed(1)}k`;
83
+ const pct = (n) => (n === null ? ' — ' : `${(n * 100).toFixed(1)}%`);
84
+
85
+ const args = process.argv.slice(2).filter((p) => existsSync(p));
86
+ const roots = args.length >= 2
87
+ ? args.map((p) => [p, `describe what ${basename(p)} does`])
88
+ : [
89
+ [scratchRepo('alpha', 30), 'add a test for src/mod3.js'],
90
+ [scratchRepo('beta', 60), 'rename fn7 to seven and update the callers'],
91
+ [scratchRepo('gamma', 12), 'write a README'],
92
+ ];
93
+
94
+ console.log('── 1. THE FLOOR ACROSS TENANTS ─────────────────────────────────────');
95
+ console.log(' round 1 of a fresh invocation, one scripted round, $0.00\n');
96
+
97
+ const caps = [];
98
+ for (const [root, task] of roots) {
99
+ const cap = await firstRequest(root, task);
100
+ if (!cap) { console.log(` ${root}: no request was made`); continue; }
101
+ const bytes = wireBytes(cap);
102
+ caps.push({ root, bytes, cap });
103
+ console.log(
104
+ ` ${basename(root).padEnd(28)} prompt ${kb(bytes.length).padStart(7)}`
105
+ + ` tools ${kb(JSON.stringify(cap.tools).length).padStart(7)} (${cap.tools.length})`
106
+ + ` system ${kb(String(cap.messages[0]?.content ?? '').length).padStart(7)}`,
107
+ );
108
+ }
109
+
110
+ console.log('\n pair shared head floor');
111
+ let worst = null;
112
+ for (let i = 0; i < caps.length; i += 1) {
113
+ for (let j = i + 1; j < caps.length; j += 1) {
114
+ const d = describeDivergence(caps[i].bytes, caps[j].bytes);
115
+ const label = `${basename(caps[i].root)} vs ${basename(caps[j].root)}`;
116
+ console.log(` ${label.padEnd(52)} ${kb(d.sharedPrefix).padStart(8)} ${pct(d.floor)}`);
117
+ if (worst === null || d.floor < worst.floor) worst = { ...d, label };
118
+ }
119
+ }
120
+ if (worst) {
121
+ console.log(`\n ⚠️ the head ends at byte ${worst.at} (${worst.label}), where the two requests read:`);
122
+ console.log(` A …${JSON.stringify(worst.aroundA.slice(100, 200))}`);
123
+ console.log(` B …${JSON.stringify(worst.aroundB.slice(100, 200))}`);
124
+ /**
125
+ * ⚠️ THE TWO NUMBERS ARE PRINTED WHETHER OR NOT THEY DISAGREE, and the reading
126
+ * says which case this is. Printing them only when they diverge would teach a
127
+ * reader that "shared" and "cacheable" are normally the same word, which is
128
+ * the exact confusion that turns a 98.9%-shared / 0%-cached probe into a
129
+ * hunt for a prefix bug that is not there.
130
+ */
131
+ const drift = (worst.sharedFraction ?? 0) - (worst.floor ?? 0);
132
+ console.log(`\n shared bytes ${pct(worst.sharedFraction)} · cacheable prefix ${pct(worst.floor)} — `
133
+ + (drift > 0.02
134
+ ? '⚠️ SHARED BYTES ARE NOT A SHARED PREFIX. What they have in common is behind the divergence, so none of it bills as cache.'
135
+ : 'healthy: what they share is at the front, so the cache can reach all of it.'));
136
+ }
137
+
138
+ if (args.length < 2) {
139
+ console.log('\n ⚠️ THESE ARE SCRATCH REPOSITORIES AND THEY FLATTER THE FLOOR. A repo map is');
140
+ console.log(' the per-tenant half of the prompt, and a 30-file scratch repo has almost none');
141
+ console.log(' of one. Measured on three REAL repositories in this tree the same day, the head');
142
+ console.log(' was 22.9k against a 41.9k prompt — a 54.6% floor. Pass real paths:');
143
+ console.log(' node scripts/cache-floor.mjs ../acuvo-code ../acuvo-gateway ../slopscore');
144
+ }
145
+
146
+ console.log('\n── 2. THE FLOOR ACROSS CONFIGURATIONS ──────────────────────────────');
147
+ console.log(' one machine\'s settings against another\'s, tools block only\n');
148
+
149
+ const probeRoot = caps[0]?.root ?? roots[0][0];
150
+ const variants = [
151
+ ['reference (bare machine)', { allowRun: true, env: {}, root: probeRoot }],
152
+ ['--no-run', { allowRun: false, env: {}, root: probeRoot }],
153
+ ['ACUVO_ALLOW_PUSH=1', { allowRun: true, env: { ACUVO_ALLOW_PUSH: '1' }, root: probeRoot }],
154
+ ['MODAL_TTS_URL set', { allowRun: true, env: { MODAL_TTS_URL: 'https://x' }, root: probeRoot }],
155
+ ['stdin is a TTY (ask_user)', { allowRun: true, env: {}, root: probeRoot, interactive: true }],
156
+ ['running as a subagent', { allowRun: true, env: {}, root: probeRoot, subagent: true }],
157
+ ];
158
+
159
+ const block = (opts, ordered) => {
160
+ const picked = toolSchemasFor(toolNamesForRounds(ROUNDS, opts));
161
+ return JSON.stringify(ordered ? orderForCachePrefix(picked, { maxRounds: ROUNDS }) : picked);
162
+ };
163
+ const refOpts = variants[0][1];
164
+
165
+ console.log(' difference from the reference registry order cache order');
166
+ for (const [label, opts] of variants) {
167
+ const before = sharedPrefixBytes(block(refOpts, false), block(opts, false));
168
+ const after = sharedPrefixBytes(block(refOpts, true), block(opts, true));
169
+ const gain = before > 0 ? ` (${(after / before).toFixed(1)}x)` : '';
170
+ console.log(` ${label.padEnd(34)} ${String(before).padStart(9)} B ${String(after).padStart(9)} B${gain}`);
171
+ }
172
+
173
+ console.log('\n ⭐ the cache order is a PERMUTATION — every tool above is still offered.');
174
+ console.log(' ⚠️ what a provider then did with these bytes is `acuvo --json` .cache, not this.\n');
175
+
176
+ for (const d of made) { try { rmSync(d, { recursive: true, force: true }); } catch { /* */ } }
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ── ⚠️⚠️ THIS EXISTS BECAUSE A BENCHMARK RUN MADE THE OWNER'S LAPTOP UNUSABLE ─
4
+ *
5
+ * 2026-08-12, verbatim: *"we have to figure out a way to stop hammering my
6
+ * laptop dude, it's fucked. I cannot even watch Netflix, I can't even change
7
+ * tabs."* A Terminal-Bench run had twenty Docker containers on an 8-core machine
8
+ * with no `.wslconfig`, so WSL2 — which is what Docker Desktop runs on — helped
9
+ * itself to every logical processor and most of the RAM.
10
+ *
11
+ * ⭐ AND THE INSTRUCTION WAS NOT "DO LESS WORK." It was *"that doesn't mean
12
+ * workload goes lower, we need to be able to control my laptop."* So this is
13
+ * about a CEILING and a KILL SWITCH, never about scaling ambition down. Heavy
14
+ * work runs flat out inside a box it cannot climb out of.
15
+ *
16
+ * ── THE THREE CONTROLS, IN ORDER OF HOW MUCH THEY MATTER ────────────────────
17
+ *
18
+ * 1. `~/.wslconfig` — the real one. Caps the VM at 4 of 8 processors and 6GB.
19
+ * Without it there is no limit at all, which is the state this machine was
20
+ * in. ⚠️ Needs `wsl --shutdown` to take effect; editing it does nothing to
21
+ * a VM already running.
22
+ * 2. Priority — our long jobs run BELOW NORMAL, so the desktop always wins a
23
+ * contested core. A build that takes 10% longer and never stutters the UI
24
+ * is the correct trade on somebody's personal machine.
25
+ * 3. This script — `status` to see what we are costing, `stop` to end all of
26
+ * it in one command, without hunting pids.
27
+ *
28
+ * Zero dependencies, like everything else here.
29
+ *
30
+ * node scripts/machine.mjs status
31
+ * node scripts/machine.mjs stop
32
+ */
33
+
34
+ import { execFileSync, spawnSync } from 'node:child_process';
35
+ import { existsSync, readFileSync } from 'node:fs';
36
+ import { homedir } from 'node:os';
37
+ import { join } from 'node:path';
38
+
39
+ const WSLCONFIG = join(homedir(), '.wslconfig');
40
+
41
+ /** Processes that are OURS and heavy. Never the user's editor or browser. */
42
+ const OURS = /harbor|acuvo|terminal-bench/i;
43
+
44
+ function sh(file, args, { quiet = false } = {}) {
45
+ try {
46
+ return execFileSync(file, args, {
47
+ encoding: 'utf8', timeout: 20_000, windowsHide: true,
48
+ stdio: quiet ? ['ignore', 'pipe', 'ignore'] : ['ignore', 'pipe', 'inherit'],
49
+ });
50
+ } catch (e) {
51
+ return e?.stdout ?? '';
52
+ }
53
+ }
54
+
55
+ function dockerContainers() {
56
+ const out = sh('docker', ['ps', '--format', '{{.ID}}\t{{.Names}}\t{{.Image}}'], { quiet: true });
57
+ return out.split('\n').map((l) => l.trim()).filter(Boolean).map((l) => {
58
+ const [id, name, image] = l.split('\t');
59
+ return { id, name, image };
60
+ });
61
+ }
62
+
63
+ /**
64
+ * ⚠️ `docker stats` WITHOUT `--no-stream`, ON WINDOWS, NEVER RETURNS. It streams
65
+ * until interrupted, and a script that hangs while you are trying to reclaim
66
+ * your machine is the opposite of this file's job.
67
+ */
68
+ function dockerLoad() {
69
+ const out = sh('docker', ['stats', '--no-stream', '--format', '{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}']);
70
+ return out.split('\n').map((l) => l.trim()).filter(Boolean);
71
+ }
72
+
73
+ function wslCap() {
74
+ if (!existsSync(WSLCONFIG)) {
75
+ return {
76
+ ok: false,
77
+ detail: 'NO ~/.wslconfig — WSL2 (and therefore Docker) may take EVERY core and most of your RAM. '
78
+ + 'This is the state that made the machine unusable.',
79
+ };
80
+ }
81
+ const text = readFileSync(WSLCONFIG, 'utf8');
82
+ const procs = /processors\s*=\s*(\d+)/i.exec(text)?.[1] ?? null;
83
+ const mem = /memory\s*=\s*(\S+)/i.exec(text)?.[1] ?? null;
84
+ return {
85
+ ok: procs !== null && mem !== null,
86
+ procs,
87
+ mem,
88
+ detail: procs && mem
89
+ ? `capped at ${procs} processors and ${mem}`
90
+ : 'present but does not set BOTH processors and memory — the unset one is uncapped',
91
+ };
92
+ }
93
+
94
+ function heavyProcesses() {
95
+ // ⚠️ Windows only, and it fails quietly elsewhere rather than pretending.
96
+ if (process.platform !== 'win32') return [];
97
+ /**
98
+ * ⚠️⚠️ IT MATCHED ITSELF. The query's OWN command line contains the words it
99
+ * searches for, so every `status` reported one phantom powershell.exe and told
100
+ * the owner something of ours was still running when the machine was idle. A
101
+ * monitor that cannot report "all clear" is worse than no monitor — this one
102
+ * exists precisely to be believed.
103
+ *
104
+ * Excluded by pid (our own child) and by the give-away that only the detector
105
+ * mentions BOTH patterns in one command line.
106
+ */
107
+ const ps = sh('powershell', ['-NoProfile', '-Command',
108
+ "Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -match 'harbor|terminal-bench' } "
109
+ + '| Select-Object ProcessId,Name,CommandLine | ConvertTo-Json -Compress'], { quiet: true });
110
+ try {
111
+ const parsed = JSON.parse(ps || '[]');
112
+ const list = Array.isArray(parsed) ? parsed : [parsed];
113
+ return list.filter((p) => {
114
+ if (!p || p.ProcessId === process.pid) return false;
115
+ const cl = String(p.CommandLine ?? '');
116
+ // The detector is the only thing that names both patterns at once.
117
+ return !(cl.includes('harbor|terminal-bench'));
118
+ });
119
+ } catch { return []; }
120
+ }
121
+
122
+ function status() {
123
+ const cap = wslCap();
124
+ console.log('');
125
+ console.log(` WSL / Docker ceiling ${cap.ok ? '✔' : '✖'} ${cap.detail}`);
126
+ if (!cap.ok) {
127
+ console.log(' → write ~/.wslconfig with processors= and memory=, then `wsl --shutdown`');
128
+ }
129
+
130
+ const containers = dockerContainers();
131
+ console.log(` containers running ${containers.length === 0 ? '✔ none' : `⚠ ${containers.length}`}`);
132
+ for (const c of containers) console.log(` ${c.name} (${c.image})`);
133
+
134
+ if (containers.length > 0) {
135
+ for (const line of dockerLoad()) console.log(` ${line}`);
136
+ }
137
+
138
+ const procs = heavyProcesses();
139
+ console.log(` our heavy processes ${procs.length === 0 ? '✔ none' : `⚠ ${procs.length}`}`);
140
+ for (const p of procs) console.log(` pid ${p.ProcessId} ${p.Name}`);
141
+
142
+ const quiet = containers.length === 0 && procs.length === 0;
143
+ console.log('');
144
+ console.log(quiet
145
+ ? ' Nothing of ours is running. The machine is yours.'
146
+ : ' Run `node scripts/machine.mjs stop` to end all of it.');
147
+ console.log('');
148
+ return quiet ? 0 : 1;
149
+ }
150
+
151
+ function stop() {
152
+ /**
153
+ * ⚠️ CONTAINERS FIRST, THEN THE ORCHESTRATOR. Killing harbor first leaves its
154
+ * containers running with nothing supervising them — orphans, which is the
155
+ * exact failure `background.mjs` was written to avoid, at a larger size.
156
+ */
157
+ const containers = dockerContainers();
158
+ if (containers.length > 0) {
159
+ console.log(` killing ${containers.length} container${containers.length === 1 ? '' : 's'}…`);
160
+ spawnSync('docker', ['kill', ...containers.map((c) => c.id)], { stdio: 'ignore', timeout: 60_000, windowsHide: true });
161
+ }
162
+
163
+ const procs = heavyProcesses();
164
+ for (const p of procs) {
165
+ console.log(` killing pid ${p.ProcessId} (${p.Name})…`);
166
+ spawnSync('taskkill', ['/T', '/F', '/PID', String(p.ProcessId)], { stdio: 'ignore', timeout: 30_000, windowsHide: true });
167
+ }
168
+
169
+ /**
170
+ * ⭐ AND THE VM ITSELF, because a shut-down WSL2 releases its RAM immediately
171
+ * instead of holding it until the next reboot. This is the line that actually
172
+ * gives the memory back.
173
+ */
174
+ console.log(' shutting down the WSL VM to release its memory…');
175
+ spawnSync('wsl', ['--shutdown'], { stdio: 'ignore', timeout: 60_000, windowsHide: true });
176
+
177
+ console.log('');
178
+ console.log(' Done. The machine is yours.');
179
+ return 0;
180
+ }
181
+
182
+ /**
183
+ * ── ⭐ RUN SOMETHING HEAVY WITHOUT IT OWNING THE MACHINE ────────────────────
184
+ *
185
+ * `machine.mjs run -- <command…>` starts a command at BELOW NORMAL priority, so
186
+ * the desktop wins every contested core. A benchmark that takes 10% longer and
187
+ * never stutters a video is the correct trade on somebody's personal laptop —
188
+ * and it is the difference between "workload goes lower" (which was explicitly
189
+ * NOT the instruction) and "workload stays out of your way".
190
+ *
191
+ * ⚠️ PRIORITY IS NOT A SUBSTITUTE FOR THE `.wslconfig` CAP. Docker's work does
192
+ * not happen in the child we launch; it happens inside the WSL VM, which this
193
+ * cannot reach. Both controls are needed and they cover different things:
194
+ * priority governs OUR processes, the cap governs the VM.
195
+ */
196
+ function runLow(argv) {
197
+ if (argv.length === 0) {
198
+ console.error('usage: node scripts/machine.mjs run -- <command> [args…]');
199
+ return 64;
200
+ }
201
+ const cap = wslCap();
202
+ if (!cap.ok) {
203
+ // ⚠️ Refuse rather than warn. This is the exact configuration that made the
204
+ // machine unusable, and a warning at the top of a two-hour run is a warning
205
+ // nobody is present to read.
206
+ console.error(` ✖ refusing to start heavy work: ${cap.detail}`);
207
+ console.error(' Write ~/.wslconfig with processors= and memory=, then `wsl --shutdown`.');
208
+ return 1;
209
+ }
210
+ console.log(` starting at BELOW NORMAL priority (VM ${cap.detail})`);
211
+ const r = process.platform === 'win32'
212
+ ? spawnSync('cmd', ['/c', 'start', '/b', '/belownormal', '/wait', ...argv], { stdio: 'inherit', windowsHide: true })
213
+ : spawnSync('nice', ['-n', '15', ...argv], { stdio: 'inherit' });
214
+ return r.status ?? 0;
215
+ }
216
+
217
+ const cmd = process.argv[2] ?? 'status';
218
+ if (cmd === 'stop' || cmd === 'panic' || cmd === 'kill') process.exit(stop());
219
+ else if (cmd === 'status') process.exit(status());
220
+ else if (cmd === 'run') {
221
+ const sep = process.argv.indexOf('--');
222
+ process.exit(runLow(sep === -1 ? process.argv.slice(3) : process.argv.slice(sep + 1)));
223
+ } else {
224
+ console.error('usage: node scripts/machine.mjs [status|stop|run -- <cmd>]');
225
+ process.exit(64);
226
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * ── ⚠️⚠️ ZERO TESTS EXITED 0, AND THAT IS THE WORST POSSIBLE GREEN ──────────
3
+ *
4
+ * `SHAKEDOWN.md` §1.2, measured on an INSTALLED copy of this package:
5
+ *
6
+ * npm test
7
+ * # tests 0 / pass 0 / fail 0 — exit 0
8
+ *
9
+ * The old script was `node --test --test-timeout=180000 test/*.test.mjs`, and
10
+ * the glob is expanded by the SHELL. On a machine whose shell does not expand it
11
+ * (cmd.exe), or in an install where the pattern matches nothing, node is handed
12
+ * a literal `test/*.test.mjs`, finds no such file, runs **nothing**, and reports
13
+ * success. ⭐ Someone auditing this package — which `ENTERPRISE.md` explicitly
14
+ * invites, and which is the entire reason `test/` is in the published files
15
+ * allowlist — would run `npm test`, see green, and conclude the suite passed.
16
+ *
17
+ * ⭐ THIS IS THE SAME DEFECT CLASS AS "watch the total, not the failures": a
18
+ * test file that fails to COMPILE also contributes zero tests, and the run still
19
+ * says passed. Counting failures can never catch either one. **Only the total
20
+ * can**, so the total is what this asserts.
21
+ *
22
+ * ⚠️ AND IT DOES NOT GLOB. Node's own directory discovery is used, so the
23
+ * behaviour no longer depends on which shell invoked npm.
24
+ */
25
+
26
+ import { spawnSync } from 'node:child_process';
27
+ import { existsSync, readdirSync } from 'node:fs';
28
+ import { cpus } from 'node:os';
29
+ import { fileURLToPath } from 'node:url';
30
+ import { dirname, join } from 'node:path';
31
+
32
+ const root = dirname(dirname(fileURLToPath(import.meta.url)));
33
+ const testDir = join(root, 'test');
34
+
35
+ /**
36
+ * ⚠️ A MISSING `test/` IS A FAILURE, NOT A SKIP. An install that dropped the
37
+ * directory is exactly the case this script exists to catch, and "there was
38
+ * nothing to run" must never be reported as "everything passed".
39
+ */
40
+ if (!existsSync(testDir)) {
41
+ console.error(`✖ ${testDir} does not exist — this copy of acuvo-code ships no tests, so nothing was verified.`);
42
+ process.exit(1);
43
+ }
44
+
45
+ /**
46
+ * ⚠️ A FLOOR, NOT AN EXACT COUNT. Pinning the precise number would fail every
47
+ * commit that adds a test — a check that fails correct work, which this repo has
48
+ * paid for repeatedly. The floor only ever catches the collapse this exists for:
49
+ * hundreds of tests becoming a handful because a glob broke or a file stopped
50
+ * compiling. Raise it deliberately, never automatically.
51
+ */
52
+ const MINIMUM_TESTS = 500;
53
+
54
+ /**
55
+ * ⚠️⚠️ THE FILES ARE ENUMERATED HERE, NOT GLOBBED AND NOT DISCOVERED.
56
+ *
57
+ * · A shell glob (`test/*.test.mjs`) is what broke: cmd.exe does not expand it,
58
+ * so node received the literal string, matched nothing, and exited 0.
59
+ * · Passing the DIRECTORY does not work either — measured on Node 22.17:
60
+ * `node --test test/` resolves `test/` as a MODULE and dies with
61
+ * `Cannot find module …\test`, which the runner then reports as one failing
62
+ * test. Directory discovery is not what that argument means.
63
+ *
64
+ * ⭐ `readdirSync` depends on neither the shell nor a node version's glob
65
+ * support, and the list it produces can be counted before anything runs.
66
+ */
67
+ const files = readdirSync(testDir)
68
+ .filter((n) => n.endsWith('.test.mjs'))
69
+ .map((n) => `test/${n}`)
70
+ .sort();
71
+
72
+ if (files.length === 0) {
73
+ console.error(`✖ ${testDir} contains no *.test.mjs files — nothing was verified.`);
74
+ process.exit(1);
75
+ }
76
+
77
+ /**
78
+ * ── ⚠️⚠️ A CONCURRENCY CEILING, BECAUSE THIS RUNS ON SOMEBODY'S LAPTOP ──────
79
+ *
80
+ * `node --test` defaults to one worker PER CORE. With 189 test files that is
81
+ * eight processes on this machine — fine alone, and not fine at all in the
82
+ * configuration this repo actually runs in: **two terminals plus three
83
+ * background agents**, each firing the same suite. 8 cores, up to 24 workers.
84
+ *
85
+ * Measured consequence, 2026-08-16, from the person paying for the laptop:
86
+ * *"we are absolutely fucking the shit out of my laptop so hard I can't even
87
+ * open my Google tabs."* ⭐ THE INSTRUCTION WAS EXPLICITLY NOT "DO LESS WORK" —
88
+ * it was that the machine has to stay usable while the work happens. So this
89
+ * is a ceiling, not a reduction: the same tests run, they just cannot take
90
+ * every core at once.
91
+ *
92
+ * ⚠️ HALF THE CORES, MINUS ONE, FLOOR OF 2. Half leaves room for a second
93
+ * agent; the minus-one leaves a core for the interactive session, which is the
94
+ * thing a human actually notices. A floor of 2 stops a small machine
95
+ * serialising a 189-file suite into something nobody will wait for.
96
+ *
97
+ * ⭐ Override with `ACUVO_TEST_CONCURRENCY` when the machine is idle and you
98
+ * want the suite back at full speed — the ceiling exists for the shared case,
99
+ * not because more is wrong.
100
+ */
101
+ const cores = Math.max(1, cpus().length);
102
+ const concurrency = (() => {
103
+ const asked = Number(process.env.ACUVO_TEST_CONCURRENCY);
104
+ if (Number.isInteger(asked) && asked > 0) return asked;
105
+ return Math.max(2, Math.floor(cores / 2) - 1);
106
+ })();
107
+
108
+ // ⚠️ TAP, not spec: the machine-readable `# tests N` line is the thing being
109
+ // asserted below, and the spec reporter writes a decorated `ℹ tests N` instead.
110
+ const args = ['--test', `--test-concurrency=${concurrency}`, '--test-timeout=180000', '--test-reporter=tap', ...files];
111
+ const run = spawnSync(process.execPath, args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
112
+
113
+ const out = `${run.stdout ?? ''}${run.stderr ?? ''}`;
114
+ process.stdout.write(out);
115
+
116
+ /**
117
+ * ⚠️ READ FROM THE REPORTER'S OWN TOTAL, never recomputed by counting lines.
118
+ * `# tests N` is what node itself concluded; a line count is our opinion about
119
+ * its output, and the two drift the moment the reporter changes.
120
+ */
121
+ const total = Number(/^# tests (\d+)$/m.exec(out)?.[1] ?? NaN);
122
+ const failed = Number(/^# fail (\d+)$/m.exec(out)?.[1] ?? NaN);
123
+
124
+ if (!Number.isFinite(total)) {
125
+ console.error('\n✖ could not read a test total out of the runner output — treating that as a failure, '
126
+ + 'because an unreadable result is not a passing one.');
127
+ process.exit(1);
128
+ }
129
+
130
+ if (total < MINIMUM_TESTS) {
131
+ console.error(`\n✖ only ${total} tests ran, and this package expects at least ${MINIMUM_TESTS}.`);
132
+ console.error(' Nothing here failed — that is the point. A suite that collects no tests reports success,');
133
+ console.error(' so the TOTAL is the check. Usually this means the test directory did not ship, or a test');
134
+ console.error(' file failed to compile and silently contributed zero tests.');
135
+ process.exit(1);
136
+ }
137
+
138
+ if (Number.isFinite(failed) && failed > 0) process.exit(1);
139
+ process.exit(run.status === null ? 1 : run.status);
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: accessibility
3
+ description: Semantic HTML, keyboard paths, focus and contrast — the parts that break real usage
4
+ when: When building any interactive UI, a modal, a menu, or anything you can click
5
+ ---
6
+
7
+ # Accessibility
8
+
9
+ Beyond the basics in `web-app-quality`. These are the failures that make an
10
+ interface genuinely unusable rather than merely imperfect.
11
+
12
+ ## ⚠️⚠️ The right element does most of the work for free
13
+
14
+ ```html
15
+ ✗ <div class="btn" onclick="save()">Save</div>
16
+ ✓ <button type="button" onclick="save()">Save</button>
17
+ ```
18
+
19
+ The `<div>` cannot be focused, cannot be triggered by Enter or Space, is not
20
+ announced as a button, and does not participate in forms. The `<button>` does
21
+ all four with no code. Recreating that with `tabindex`, `role` and key handlers
22
+ is a lot of work to arrive back where you started — and it is usually incomplete.
23
+
24
+ Same for `<a>` for navigation, `<label>` for inputs, `<nav>`/`<main>`/`<h1>`
25
+ for structure. **Reach for ARIA only when no element exists for the job.** Bad
26
+ ARIA is worse than none, because it overrides what the browser knew.
27
+
28
+ ## Every path must work from the keyboard
29
+
30
+ Tab through the whole thing without touching the mouse. If you cannot reach a
31
+ control, open a menu, or close a dialog, it is broken — for keyboard users, for
32
+ screen readers, and for anyone whose trackpad died.
33
+
34
+ - `Escape` closes overlays
35
+ - `Enter`/`Space` activate
36
+ - Arrow keys move within a composite widget (menu, tabs), not between pages
37
+
38
+ ⚠️ **Tab order follows the DOM, not the CSS.** Reordering visually with grid or
39
+ flex leaves the tab order where the markup put it. Never use positive
40
+ `tabindex` values to patch that — fix the markup order.
41
+
42
+ ## Focus is not decoration
43
+
44
+ ```css
45
+ ✗ *:focus { outline: none; } /* the keyboard user is now lost */
46
+ ✓ :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
47
+ ```
48
+
49
+ `:focus-visible` shows the ring for keyboard users and not for mouse clicks, so
50
+ there is no reason left to remove it.
51
+
52
+ ⭐ **Move focus deliberately.** Open a dialog → focus inside it and trap it
53
+ there. Close it → return focus to the control that opened it. Delete a row →
54
+ move focus somewhere sensible, not to `<body>`.
55
+
56
+ ## Contrast, and never colour alone
57
+
58
+ - Body text at **4.5:1**, large text at **3:1**. Grey-on-grey placeholder text
59
+ is the usual offender.
60
+ - About 1 in 12 men cannot separate red from green — so an error state needs an
61
+ icon or words, not just a red border. Same for chart series (`data-and-charts`).
62
+
63
+ ## Images and icons
64
+
65
+ `alt` describes the *purpose*: `alt="Search"` on a magnifier, not
66
+ `alt="magnifying glass icon"`. Decorative images take `alt=""` so screen readers
67
+ skip them — omitting `alt` entirely makes them read the filename instead.
68
+
69
+ ## ⚠️ Tell people when something changed
70
+
71
+ A screen reader does not notice a div appearing. Announce it:
72
+
73
+ ```html
74
+ <div role="status" aria-live="polite">Invoice saved</div>
75
+ ```
76
+
77
+ Use `assertive` only for genuine interruptions — an error that stops the task.
78
+
79
+ ## Respect the settings people already chose
80
+
81
+ ```css
82
+ @media (prefers-reduced-motion: reduce) { *, *::before, *::after {
83
+ animation-duration: .01ms !important; transition-duration: .01ms !important; } }
84
+ ```
85
+
86
+ Also honour `prefers-color-scheme`. These are the user telling you what they
87
+ need; overriding them is a decision you do not have the information to make.