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,123 @@
1
+ ---
2
+ name: acuvo-design-system
3
+ description: How to make a generated page look designed rather than templated, under Acuvo's CSP
4
+ when: Building or restyling any user-facing page, component or app
5
+ ---
6
+
7
+ # Making it look designed
8
+
9
+ ## ⚠️ THE CONSTRAINT THAT DECIDES EVERYTHING: the CSP
10
+
11
+ Generated apps run under a strict Content-Security-Policy. **No external
12
+ stylesheet, script, font host or CDN will load.** The only external origin that
13
+ works is Google Fonts.
14
+
15
+ So:
16
+
17
+ - **Never** `<link>` a CDN stylesheet, never `<script src="https://cdn...">`.
18
+ It fails silently and the page renders unstyled — which reads as "the AI is
19
+ bad at design" when it was a network refusal.
20
+ - **Vendor it instead.** Write the CSS into the project. If a component library
21
+ is genuinely needed, inline the handful of rules actually used.
22
+ - Images and media must be same-origin or `data:` URIs.
23
+
24
+ ⭐ This is the single biggest measured cause of ugly output. It is not a taste
25
+ problem.
26
+
27
+ ## The look
28
+
29
+ **Type.** One display face and one text face, maximum. Google Fonts is allowed —
30
+ use it. A serif display over a clean sans body reads premium; two sans faces read
31
+ like a template. Set `font-feature-settings` and real line-height (1.5–1.65 body,
32
+ 1.05–1.2 display).
33
+
34
+ **Space is the design.** Most weak pages are weak because everything is 16px
35
+ apart. Use a scale (4 / 8 / 12 / 16 / 24 / 32 / 48 / 64) and let the important
36
+ things have room. Section padding should be 2–4× what feels right at first.
37
+
38
+ **Colour.** One ink, one paper, one accent, and greys derived from the ink rather
39
+ than pure `#888`. Pure black on pure white is a tell. Prefer near-black
40
+ (`#1a1713`) on warm paper (`#f3f3f3`) over `#000` on `#fff`.
41
+
42
+ **Depth without drop shadows.** Hairline borders (`1px` at 6–10% ink) plus a very
43
+ soft shadow beats a heavy `box-shadow`. Two competing shadows look amateur.
44
+
45
+ **Motion.** Transitions on hover/focus at 150–300ms, `ease-out`. Never animate
46
+ layout properties (`width`, `top`) — animate `transform` and `opacity`.
47
+ `prefers-reduced-motion` must disable them.
48
+
49
+ ## Designed states — the thing that separates real apps from demos
50
+
51
+ Every list, table and data view needs FOUR states, and they must not look alike:
52
+
53
+ 1. **Loading** — a skeleton with the shape of the real content, not a spinner.
54
+ 2. **Empty** — says what will fill it and how. "No invoices yet — create your
55
+ first one" beats "No data".
56
+ 3. **Error** — says what failed and what to do. Never a blank region.
57
+ 4. **Populated.**
58
+
59
+ ⚠️ An empty state and a failed load must render DIFFERENTLY. If they look the
60
+ same, a broken page looks merely new, and nobody reports it.
61
+
62
+ ## Responsive
63
+
64
+ Design the small screen first. Real content must never cause horizontal page
65
+ scroll — wide tables and code blocks get their own `overflow-x: auto` container
66
+ instead. Use `clamp()` for display type rather than three breakpoints.
67
+
68
+ ## Before calling it done
69
+
70
+ - Does it look like it was made for THIS content, or could the copy be swapped
71
+ for any other product?
72
+ - Is there one clear focal point per screen?
73
+ - Are the four states designed?
74
+ - Does it work at 360px wide?
75
+
76
+ ## ⭐⭐⭐ THE TOOLBOX — two files you can reference for free
77
+
78
+ **Multi-file output only.** Reference either and it is written into the project
79
+ for you; never paste their contents, and never fetch them from a CDN.
80
+
81
+ ```html
82
+ <link rel="stylesheet" href="acuvo-ui.css">
83
+ <script src="acuvo-motion.js" defer></script>
84
+ ```
85
+
86
+ ### `acuvo-ui.css` — the things a page cannot fake
87
+
88
+ A modern reset, a **fluid type scale** (`--step--1` … `--step-4`, all `clamp()`),
89
+ a designed dark mode via `prefers-color-scheme`, and primitives with real states:
90
+ `.card` `.card-hover` `.btn` `.btn-ghost` `.field` `.label` `.chip` `.eyebrow`
91
+ `.lead` `.divider`. Layout helpers: `.wrap` `.wrap-narrow` `.section` `.stack`
92
+ `.row` `.grid` (auto-fit, `--min` to tune).
93
+
94
+ ⭐ **Restyle it with variables, do not fight it.** `--accent`, `--radius`,
95
+ `--font`, `--ink`, `--bg`, `--line` are the knobs. Setting `--accent` and a
96
+ Google font is usually the whole brand.
97
+
98
+ ### `acuvo-motion.js` — the 90% of motion that reads as designed
99
+
100
+ Attributes, no API to learn:
101
+
102
+ | attribute | what it does |
103
+ |---|---|
104
+ | `data-reveal` | fades and lifts in when scrolled into view |
105
+ | `data-stagger="70"` | reveals the element's CHILDREN in sequence, 70ms apart |
106
+ | `data-count="900"` | counts a number up on first view, keeping its prefix/suffix (`$1,250` works) |
107
+ | `data-parallax="0.2"` | gentle transform-only parallax |
108
+
109
+ ⚠️ **Everything is disabled under `prefers-reduced-motion`, and that is not
110
+ negotiable.** Motion someone did not consent to is an accessibility defect.
111
+ `data-count` also keeps the element's original text, so the page reads correctly
112
+ with JavaScript off.
113
+
114
+ ## ⚠️⚠️ WHAT THE TOOLBOX DOES NOT DO — do not reach for a CDN
115
+
116
+ There is no timeline choreography, no physics, no chart library. **A generated
117
+ app may load a stylesheet from Google Fonts and NOTHING ELSE, and no external
118
+ script at all** — that is the Content Security Policy, it is deliberate, and a
119
+ `<script src="https://cdn…">` will be refused by the browser rather than
120
+ silently degrade.
121
+
122
+ ⭐ So build the rest with inline CSS and SVG, which you are good at. A hand-drawn
123
+ SVG bar chart beats a blocked library every time.
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: animation
3
+ description: Motion that reads as quality — what to animate, timings that feel right, and reduced-motion
4
+ when: When adding a transition, a hover state, a loading indicator, or anything that moves
5
+ ---
6
+
7
+ # Animation
8
+
9
+ ## Motion explains a change. It is not decoration.
10
+
11
+ Good motion answers *"what just happened and where did it come from"*. A panel
12
+ that slides from the button that opened it tells the eye where it belongs. The
13
+ same panel fading in from nowhere tells it nothing.
14
+
15
+ If a movement does not explain something, it is delay.
16
+
17
+ ## ⚠️⚠️ Animate only `transform` and `opacity`
18
+
19
+ ```css
20
+ ✗ transition: left .3s, width .3s, height .3s; /* layout every frame */
21
+ ✓ transition: transform .3s, opacity .3s; /* compositor only */
22
+ ```
23
+
24
+ `transform` and `opacity` can be handled without recalculating layout or
25
+ repainting. Animating `left`, `top`, `width`, `height`, `margin` or `box-shadow`
26
+ forces work on every frame and is the usual cause of janky motion on a phone
27
+ (`performance`).
28
+
29
+ To move something: `transform: translateX()`. To resize: `scale()`.
30
+
31
+ ## Timings that feel right
32
+
33
+ | what | duration |
34
+ |---|---|
35
+ | hover, small state change | 100–150ms |
36
+ | dropdown, tooltip, toggle | 150–250ms |
37
+ | panel, modal, page transition | 250–400ms |
38
+ | anything | **never past ~500ms** |
39
+
40
+ ⭐ Under ~100ms reads as instant — fine for feedback, wasted on anything you
41
+ want noticed. Past ~500ms the interface feels like it is thinking, and users
42
+ start clicking again.
43
+
44
+ **Easing:** `ease-out` for things entering (fast then settling — feels
45
+ responsive), `ease-in` for things leaving. Never `linear` for anything physical;
46
+ it reads as mechanical because nothing in the world moves that way.
47
+
48
+ ## Enter and exit are not symmetrical
49
+
50
+ Entrances can afford to be seen. Exits should be quicker — the user has already
51
+ decided, and making them wait to leave is the most irritating kind of animation.
52
+ Roughly: exit at half the entrance duration.
53
+
54
+ ## ⚠️⚠️ Respect `prefers-reduced-motion` — it is a medical setting
55
+
56
+ For some people motion causes nausea or vertigo. This is not a preference to
57
+ override:
58
+
59
+ ```css
60
+ @media (prefers-reduced-motion: reduce) {
61
+ *, *::before, *::after {
62
+ animation-duration: .01ms !important;
63
+ animation-iteration-count: 1 !important;
64
+ transition-duration: .01ms !important;
65
+ }
66
+ }
67
+ ```
68
+
69
+ ⭐ Reduced ≠ removed. Keep the state change instant and legible — a cross-fade
70
+ is usually still fine; it is *movement*, parallax and spin that cause trouble.
71
+
72
+ ## Loading states
73
+
74
+ - Under ~300ms: show nothing. A spinner that flashes is worse than a still
75
+ moment.
76
+ - Longer: a skeleton in the shape of what is coming beats a spinner — it says
77
+ what is arriving, and the layout does not jump when it lands.
78
+ - Long and unknown: say what is happening in words.
79
+
80
+ ## Restraint is the whole skill
81
+
82
+ One considered transition reads as expensive. Six competing ones read as a
83
+ template. If everything moves, nothing is emphasised — which is the same
84
+ argument as using one accent colour rather than seven.
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: api-design
3
+ description: REST shapes, status codes that mean something, idempotency and pagination that survives growth
4
+ when: When adding an endpoint, designing a route, or deciding what a handler returns
5
+ ---
6
+
7
+ # API design
8
+
9
+ ## Nouns in the path, verbs in the method
10
+
11
+ ```
12
+ ✗ POST /createInvoice ✗ GET /getInvoiceById?id=7
13
+ ✓ POST /invoices ✓ GET /invoices/7
14
+ ✓ PATCH /invoices/7 ✓ DELETE /invoices/7
15
+ ```
16
+
17
+ `GET` never changes anything — crawlers, prefetchers and browser history all
18
+ assume that, and one of them will eventually prove it.
19
+
20
+ ## Status codes are the API's error handling
21
+
22
+ | code | meaning | the mistake it prevents |
23
+ |---|---|---|
24
+ | 200 | here it is | — |
25
+ | 201 | created, `Location:` points at it | 200 with a body you have to parse to learn the id |
26
+ | 400 | your request is malformed | using 500 for a typo |
27
+ | 401 | who are you? | conflated with 403 |
28
+ | 403 | I know who you are; no | conflated with 401 |
29
+ | 404 | no such thing | 200 with `{"error":"not found"}` |
30
+ | 409 | conflicts with current state | 400 for a duplicate |
31
+ | 422 | shape is fine, values are not | 400 for everything |
32
+ | 429 | slow down | silence |
33
+ | 500 | **we** broke | blaming the caller |
34
+
35
+ ⚠️ `200 {"success": false}` forces every client to parse a body to discover
36
+ failure, and defeats every retry, cache and monitor in the path.
37
+
38
+ ## ⚠️⚠️ Idempotency: the network will deliver twice
39
+
40
+ A client that times out will retry. Without an idempotency key, the customer is
41
+ charged twice and the receipt is genuine.
42
+
43
+ ```
44
+ POST /payments
45
+ Idempotency-Key: 8f3a… ← client-generated, stored with the result
46
+ ```
47
+
48
+ Same key → return the FIRST result, do not perform the work again. `PUT` and
49
+ `DELETE` are naturally idempotent; `POST` is the one that needs help.
50
+
51
+ ## Pagination: never return everything
52
+
53
+ `GET /invoices` on a table that grows is a timeout waiting for a customer big
54
+ enough to trigger it.
55
+
56
+ - **Offset** (`?page=3&limit=50`) is simple and drifts: rows inserted while
57
+ paging shift the window and items are seen twice or missed.
58
+ - **Cursor** (`?after=<opaque>&limit=50`) is stable and is what to use for
59
+ anything ordered by time.
60
+
61
+ Always cap `limit` server-side. A client asking for 1,000,000 gets your maximum,
62
+ not an outage.
63
+
64
+ ## Consistent shapes
65
+
66
+ Pick one envelope and use it everywhere:
67
+
68
+ ```json
69
+ { "data": [...], "next_cursor": "…" }
70
+ { "error": { "code": "invoice_not_found", "message": "No invoice with id 7" } }
71
+ ```
72
+
73
+ A machine-readable `code` plus a human-readable `message` — clients branch on
74
+ the code, humans read the message. Never make a client match on prose.
75
+
76
+ ## Versioning, dates and money
77
+
78
+ - Version before you need it: `/v1/`. Removing a version is a conversation;
79
+ breaking an unversioned API is an outage.
80
+ - Timestamps in **UTC ISO-8601** with an offset. Never a bare local time.
81
+ - Money in **integer minor units** with a currency (`{"amount": 1250,
82
+ "currency": "AUD"}`). Floating point and money do not belong together.
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: auth-and-sessions
3
+ description: Sessions, refresh, and the re-signin bug — why a user gets logged out mid-click and how to stop it
4
+ when: When building sign-in, protecting a route, or debugging a user being logged out unexpectedly
5
+ ---
6
+
7
+ # Auth and sessions
8
+
9
+ ## ⚠️⚠️ The re-signin bug, which is nearly always the same two causes
10
+
11
+ A user is signed in, clicks something, and lands on the login page. Almost every
12
+ time it is one of these:
13
+
14
+ **1. A failed refresh treated as "signed out".**
15
+
16
+ ```js
17
+ ✗ const { user } = await getUser();
18
+ if (!user) redirect('/login'); // a 500 from the auth server looks identical to a real logout
19
+ ```
20
+
21
+ `getUser()` can fail three ways and they are not the same:
22
+
23
+ | what happened | correct response |
24
+ |---|---|
25
+ | valid session | continue |
26
+ | **401 / 403** — the token is genuinely bad | sign out |
27
+ | **network error, 5xx, timeout** | **do NOT sign out** — retry, or fail the request |
28
+
29
+ Destroying a session because the auth service had a bad second is the bug. Read
30
+ the status, not just the absence of a user. This is `error-handling`'s rule —
31
+ *empty ≠ unreadable* — applied to identity.
32
+
33
+ **2. Two things refreshing the same token at once.** Concurrent refreshes race;
34
+ one rotates the token, the other presents the now-stale one and is rejected.
35
+ Single-flight the refresh: one in-flight promise that every caller awaits.
36
+
37
+ ## Sessions expire. Plan the moment.
38
+
39
+ - Short-lived access token + long-lived refresh token is the standard shape.
40
+ - Refresh **before** expiry, not on the 401 — a refresh triggered by a failure
41
+ means the user already saw an error.
42
+ - When the session really is over, say so and **preserve what they were doing**.
43
+ Sending someone to a bare login screen after they typed a long form is the
44
+ part that makes people angry, not the logout itself.
45
+
46
+ ## ⚠️⚠️ The client cannot be the gate
47
+
48
+ Hiding a button is presentation. The check that matters runs on the server, on
49
+ every request, for every resource — because the client is a program the user
50
+ controls.
51
+
52
+ ```js
53
+ ✗ if (user.role === 'admin') showDeleteButton(); // and the endpoint checks nothing
54
+ ✓ the endpoint verifies the caller may delete THIS row, every time
55
+ ```
56
+
57
+ Route protection in a framework is convenience. An unprotected API under a
58
+ protected page is still an unprotected API.
59
+
60
+ ## Passwords, if you must hold them
61
+
62
+ Never store them recoverable. `bcrypt`, `scrypt` or `argon2` — never a plain
63
+ hash, never your own scheme. Compare with a constant-time function.
64
+
65
+ Better: do not hold them. An OAuth provider or a magic link removes the entire
66
+ class of problem, including the breach you would otherwise have to disclose.
67
+
68
+ ## Cookies
69
+
70
+ `HttpOnly` (JavaScript cannot read it, so XSS cannot steal it), `Secure`,
71
+ `SameSite=Lax` as the default. A token in `localStorage` is readable by every
72
+ script on the page, including one that arrived through a dependency.
73
+
74
+ ## ⚠️ Multi-tenant: the row belongs to a tenant, not to a user
75
+
76
+ Every query filters by tenant, and the filter comes from the SESSION, never from
77
+ a parameter the caller supplied. `?tenant=other-company` is the whole attack.
78
+ See `supabase-multitenant` for enforcing that at the database.
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: build-with-a-framework
3
+ description: The scaffold that is known to build — pinned, base-relative, and the four traps that waste a round each
4
+ when: Before running npm install, or whenever the answer is React/Vue/Svelte/Tailwind rather than hand-written vanilla
5
+ ---
6
+
7
+ # Building with a framework
8
+
9
+ You have a real machine. `npm install` works, a build step works, and the output
10
+ of that build is what ships. This is the scaffold that is **known to work** —
11
+ measured on a live machine: install 11.9s, `vite build` 2.0s, exit 0 both.
12
+
13
+ ## ⭐ Do not re-derive the scaffold. Write `src/App.jsx` and copy the rest.
14
+
15
+ Asking a model to emit `package.json`, `vite.config.js`, `index.html` and
16
+ `main.jsx` correctly on every build spends tokens re-deriving a solved problem
17
+ and fails in ways that are tedious to repair. **These four files are correct,
18
+ and they are free.** The part that is actually the request is `src/App.jsx`.
19
+
20
+ `package.json`
21
+
22
+ ```json
23
+ {
24
+ "name": "app",
25
+ "private": true,
26
+ "type": "module",
27
+ "scripts": { "build": "vite build" },
28
+ "dependencies": { "react": "18.3.1", "react-dom": "18.3.1" },
29
+ "devDependencies": { "vite": "5.4.0", "@vitejs/plugin-react": "4.3.1" }
30
+ }
31
+ ```
32
+
33
+ `vite.config.js`
34
+
35
+ ```js
36
+ import { defineConfig } from 'vite';
37
+ import react from '@vitejs/plugin-react';
38
+ export default defineConfig({ plugins: [react()], base: './' });
39
+ ```
40
+
41
+ `index.html`
42
+
43
+ ```html
44
+ <!doctype html><html lang="en"><head><meta charset="utf-8">
45
+ <meta name="viewport" content="width=device-width,initial-scale=1">
46
+ <title>App</title></head><body><div id="root"></div>
47
+ <script type="module" src="/src/main.jsx"></script></body></html>
48
+ ```
49
+
50
+ `src/main.jsx`
51
+
52
+ ```js
53
+ import React from 'react';
54
+ import { createRoot } from 'react-dom/client';
55
+ import App from './App.jsx';
56
+ createRoot(document.getElementById('root')).render(<App />);
57
+ ```
58
+
59
+ Then:
60
+
61
+ ```
62
+ npm install --no-audit --no-fund && npm run build
63
+ ```
64
+
65
+ ## ⚠️ The four traps, each of which costs a round
66
+
67
+ **1. `"^latest"` is how a build that worked yesterday breaks today.** Every
68
+ version above is pinned on purpose. If you add a dependency, pin it too.
69
+
70
+ **2. `base: './'` is not decoration.** Without it the built `index.html` asks for
71
+ `/assets/…` from the site ROOT, and what ships is served from a path. The page
72
+ loads, the bundle 404s, and you get a blank screen with no error in the build.
73
+
74
+ **3. If your machine is a hosted sandbox, it is shut down while you think.**
75
+ That is true of Acuvo's builder, where the box stops billing for idle time and a
76
+ fresh one starts for your next command: **your files come back; anything you
77
+ INSTALLED does not.** It is NOT true when you are running on someone's own
78
+ computer, where nothing disappears between commands.
79
+
80
+ ⭐ You do not have to know which you are on. If a build suddenly says
81
+ `vite: not found` after it worked, that is this and not your code — re-run the
82
+ install. Where it applies, the command output says so in its first line.
83
+
84
+ **4. What ships is `dist/`, not your source.** `src/App.jsx` is not a web page.
85
+ So the build MUST pass: a project whose `npm run build` fails ships nothing at
86
+ all, and you will be asked to fix it before you can finish.
87
+
88
+ ## ⚠️ Every asset the built page references must exist
89
+
90
+ The built `dist/index.html` names its bundle and its stylesheet. If one of them
91
+ is missing from the tree, the page still validates and renders **nothing** —
92
+ which is the most expensive failure available, because every other signal says
93
+ the build succeeded. After `npm run build`, check that `dist/` contains what
94
+ `dist/index.html` asks for.
95
+
96
+ ## When NOT to reach for this
97
+
98
+ A page a visitor only reads — a landing page, a brochure, a menu — is
99
+ `index.html` + `styles.css`, and a framework makes it slower to load and slower
100
+ to build for no gain. Reach for a framework when there is real state to manage:
101
+ a list that changes, a form with steps, a board you drag things around.
@@ -0,0 +1,112 @@
1
+ ---
2
+ name: colour-and-contrast
3
+ description: Building a palette that looks deliberate — restraint, neutrals, one accent, and contrast that passes
4
+ when: Choosing any colour, theming a page, or when output looks flat, muddy or garish
5
+ ---
6
+
7
+ # Colour
8
+
9
+ Generated pages go wrong in one of two directions: **grey mush** (everything
10
+ `#666` on `#f5f5f5`, nothing to look at) or **carnival** (a purple gradient, a
11
+ teal button, an orange badge, and a red heading). Both come from choosing
12
+ colours one at a time instead of building a set.
13
+
14
+ ## ⭐ The rule that fixes most of it: one accent
15
+
16
+ A page needs **many neutrals and exactly one accent.** The accent is what the
17
+ eye is supposed to find — the primary action, the live figure, the one link that
18
+ matters. The moment there are two accents, there is no accent.
19
+
20
+ ```
21
+ neutrals 8–10 steps, near-grey, subtly tinted toward the accent
22
+ accent 1 hue, 2–3 steps (base, hover, subtle background)
23
+ semantic success / warning / danger — used ONLY for state, never decoration
24
+ ```
25
+
26
+ ⚠️ **Semantic colours are not palette colours.** Green means "it worked". If
27
+ green is also the brand colour, a success message is invisible and a decorative
28
+ green panel reads as a system state.
29
+
30
+ ## Neutrals are the whole page — tint them
31
+
32
+ Pure `#000`/`#888`/`#fff` is the flattest a page can look. Real interfaces use
33
+ neutrals with a few degrees of hue in them, usually pulled toward the accent.
34
+
35
+ ```css
36
+ :root {
37
+ /* accent hue 240 → neutrals carry a trace of it */
38
+ --n-0: hsl(240 20% 99%);
39
+ --n-50: hsl(240 16% 96%);
40
+ --n-100:hsl(240 14% 92%);
41
+ --n-300:hsl(240 10% 76%);
42
+ --n-500:hsl(240 8% 48%);
43
+ --n-700:hsl(240 10% 28%);
44
+ --n-900:hsl(240 18% 11%);
45
+ --accent: hsl(240 76% 56%);
46
+ --accent-weak: hsl(240 76% 96%);
47
+ }
48
+ ```
49
+
50
+ ⭐ **HSL, not hex, while you are choosing.** Same hue, same saturation, moving
51
+ lightness — that is what makes a family look like a family. Hex hides the
52
+ relationship and you end up with nine unrelated colours.
53
+
54
+ The vendored token layer already ships neutrals and an accent. **Prefer those
55
+ tokens.** Introduce new colour only when the brief names a brand colour.
56
+
57
+ ## Contrast is a requirement, not a preference
58
+
59
+ | use | minimum |
60
+ |---|---|
61
+ | body text | **4.5:1** |
62
+ | large text (≥24px, or ≥19px bold) | **3:1** |
63
+ | icons, borders, focus rings, UI edges | **3:1** |
64
+ | disabled text | exempt, but then it must be obviously disabled |
65
+
66
+ ⚠️ **Placeholder text and light-grey captions are where this fails almost every
67
+ time.** `#999` on `#fff` is 2.8:1 — it fails, and it fails for everyone in
68
+ sunlight, not just people with low vision.
69
+
70
+ ⚠️ **White text on a mid-tone accent usually fails.** `#fff` on a 56%-lightness
71
+ blue is around 3.9:1. Either darken the accent to ~45% lightness for buttons, or
72
+ use near-black text on it. Check, do not assume.
73
+
74
+ ⭐ **Never signal with colour alone.** A red border on an invalid field is
75
+ invisible to a colour-blind user and to a screenshot in greyscale. Add an icon,
76
+ a label, or text. Roughly 1 in 12 men cannot distinguish your red from your
77
+ green.
78
+
79
+ ## Where colour goes
80
+
81
+ - **Backgrounds carry almost no saturation.** Depth comes from *slightly*
82
+ different neutrals plus a border, not from colour.
83
+ - **Borders `--n-100`–`--n-300`.** A 1px hairline does more for perceived
84
+ quality than a shadow does.
85
+ - ⚠️ **Gradients: at most one, and keep it subtle.** Two stops, close in hue.
86
+ A purple-to-pink hero gradient is the single most recognisable "AI made this"
87
+ signal in existence. If the brief did not ask for a gradient, do not add one.
88
+ - **Shadows are neutral and soft**, from the elevation ramp
89
+ (`--shadow-1`…`--shadow-6`). A coloured shadow reads as a toy.
90
+
91
+ ## Dark mode, if asked for
92
+
93
+ Not an inversion. Dark surfaces need **less** saturation and **more**
94
+ lightness in the accent, or it vibrates.
95
+
96
+ ```css
97
+ @media (prefers-color-scheme: dark) {
98
+ :root { --bg: hsl(240 14% 8%); --fg: hsl(240 12% 92%); --accent: hsl(240 70% 68%); }
99
+ }
100
+ ```
101
+
102
+ - Surface `#0d0d10`-ish, not `#000`. Pure black with white text causes halation.
103
+ - Body text near `#e8e8ea`, not `#fff`.
104
+ - Elevation in dark mode is a *lighter surface*, not a bigger shadow.
105
+
106
+ ## Before calling it done
107
+
108
+ - One accent. Count them — if there are two, one is wrong.
109
+ - Body text passes 4.5:1; buttons and placeholders were checked, not assumed.
110
+ - Neutrals share a hue; nothing is pure `#000` or pure `#888`.
111
+ - No unrequested gradient. No coloured shadows.
112
+ - Nothing is communicated by colour alone.
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: creative-engines
3
+ description: Image, voice and editing engines — cost first, edit rather than regenerate, check what came back
4
+ when: When asked to make, change, reframe or narrate an image or audio, or asked what something will cost
5
+ ---
6
+
7
+ # Creative Engines
8
+
9
+ Acuvo can generate and edit real media. The engines are good; most bad results
10
+ come from reaching for the wrong verb, not from the model.
11
+
12
+ | verb | use it for |
13
+ |---|---|
14
+ | `list_engines` | what this account can reach, and **what it costs in credits** |
15
+ | `generate_image` | a new image from a prompt |
16
+ | `edit_image` | change **one thing** in an image that already exists |
17
+ | `expand_image` | a new aspect ratio by painting new edges, not cropping |
18
+ | `speak` | text to an audio file, in a fixed voice |
19
+ | `read_image` | LOOK at what you produced |
20
+
21
+ ## ⚠️⚠️ Cost before spending, every time
22
+
23
+ **`list_engines` before you spend, whenever the user asks what something costs,
24
+ asks for "the cheap one", or is about to buy something at volume.** It reports
25
+ what this account can actually reach and the credit price of each engine.
26
+
27
+ Spending someone's credits on an engine they did not choose, at a price they
28
+ never saw, is the worst thing you can do here. If a request implies many images,
29
+ say the total first.
30
+
31
+ ## ⭐⭐ EDIT beats REGENERATE — almost always
32
+
33
+ When an image is nearly right, do **not** generate it again. A regeneration
34
+ throws away everything that was already good and rerolls the composition, the
35
+ lighting and the subject.
36
+
37
+ **`edit_image` names what to replace in plain words:**
38
+ `target: "the sign on the van"` → `replacement: "plain white panel"`.
39
+
40
+ ⭐ This is cheaper, faster, and it *keeps the picture the user already liked*.
41
+ "Make the sign blank" is an edit. "Try again but better" is a reroll, and the
42
+ user will lose the version they wanted.
43
+
44
+ Same reasoning for aspect ratio: **`expand_image` paints new edges** to reach
45
+ 16:9, 9:16 or 4:5. Cropping to reframe throws away the subject; expanding keeps
46
+ it and invents only the margins.
47
+
48
+ ## ⚠️ Use the path the result gives you
49
+
50
+ `generate_image` returns a `.png` **or** a `.jpg` — the extension follows
51
+ whatever the engine produced. **Use the exact path from the result.** Do not
52
+ assume `.png` and do not construct the filename yourself; a hardcoded extension
53
+ is a file-not-found on the next step.
54
+
55
+ ## ⚠️⚠️ `speak` uses a FIXED voice — never offer to clone one
56
+
57
+ `speak` reads in one fixed voice. **It cannot clone anybody.** Do not offer to
58
+ make it sound like the user, like a celebrity, or like anyone named. Leave
59
+ `engine` unset unless there is a reason.
60
+
61
+ Promising a cloned voice from this verb is a promise the tool cannot keep, and
62
+ the user finds out only after they have spent credits.
63
+
64
+ ## ⭐ Always look at what came back
65
+
66
+ **`read_image` on anything you generated**, before you present it. Ask it a
67
+ question if you have a specific worry — spelling is the usual one, because text
68
+ rendered inside a generated image is wrong often enough that it must be checked
69
+ rather than assumed.
70
+
71
+ Presenting a generated image you have not looked at is presenting a guess. See
72
+ the `designing-by-looking` skill — it is the same rule as never shipping an
73
+ unrendered page.
74
+
75
+ ## ⭐ Choosing size
76
+
77
+ Ask what the image is FOR before picking dimensions: a hero is wide, a post is
78
+ square or 4:5, a story is 9:16. Generating square and cropping later loses the
79
+ subject; generating at the right shape, or expanding to it, does not.
80
+
81
+ Related skills: `designing-by-looking`, `acuvo-design-system`.