Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .ai/rules/coding-style.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ Coding style:
- Favor using real paths (`../lib/schemas.ts`) over aliases (`@/app/lib/schemas`).
- Favor `for (const comment of comments) {` over `comments.forEach((comment) => {`
- Favor named exports over default exports, with the exception of Next.js pages
- Avoid namespace `*` imports unless the API is intentionally consumed as a namespace. Prefer named
imports so usage is explicit and bundlers can optimize.
- Avoid broad file-level lint suppressions. Prefer the narrowest scoped suppression on the exact
line, with a reason when the exception is not obvious.
- Preserve existing dependency import paths and module-system interop unless the change is required
and explained. Do not rewrite package imports to `node_modules/...`, switch ESM/CJS shapes, or add
duplicate export styles just to satisfy local tooling.
- Do not wrap each function body and function call in `try`/`catch` blocks. It pollutes the code.
Assume we will always have an e.g.
`main().catch((err) => { console.error(err); process.exit(1) })` to catch us. I repeat: Avoid
Expand All @@ -28,8 +35,22 @@ Coding style:
- Use Prettier with 100 char line width, single quotes for JS/TS, semi: false
- Use descriptive names: PascalCase for components/types, camelCase for variables/methods/schemas
- Alphabetize imports, group by source type (built-in/external/internal)
- Preserve existing sorted lists and config ordering unless intentionally changing the order.
- Favor US English over UK English, so `summarizeError` over `summarise Error`
- Favor `.replaceAll('a', 'b)` over `.replace(/a/g, 'b')` or `.replace(new RegExp('a', 'g'), 'b')` when the only need for regeses was replacing all strings. That's usually both easier to read and more performant.
- Use typographic characters: ellipsis (`…`) instead of `...`, curly quotes (`'` `"`) instead of straight quotes in user-facing text
- Generated text files should end with a trailing newline. Do not trim serializer output when the
serializer intentionally emits POSIX-style text.
- Comments should explain why code exists or why an exception is needed, not narrate what the next
line already says.
- Do not put TODOs, internal implementation notes, or future-work placeholders in user-facing text
or schema descriptions. Put those in code comments, issues, or docs for maintainers instead.
- Put API keys and secrets in `.env` files, not hardcoded in components
- Do not return raw errors, stack traces, third-party responses, payment objects, database errors, or
credential data to clients. Show sanitized user-facing messages and log only redacted diagnostic
details server-side.
- When wrapping or rethrowing an error, preserve the original value with `new Error(message, {
cause: error })` when possible instead of stringifying it away.
- Remove temporary debug logging/instrumentation before merge. Keep new logs only when they are
intentional, useful in production, and do not expose sensitive data.
- Check for existing hooks before creating new ones (e.g., `useUppy()` for Uppy functionality)
2 changes: 2 additions & 0 deletions .ai/rules/general.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ General:
- Avoid blocking the conversation with terminal commands. For example: A) most of my git commands run through pagers, so pipe their output to `cat` to avoid blocking the
terminal. B) You can use `tail` for logs, but be smart and use `-n` instead of `-f`, or the conversation will block
- Use the `gh` tool to interact with GitHub (search/view an Issue, create a PR).
- When using `fetch()` directly, check `response.ok` before parsing the body, and surface non-2xx
responses as errors with enough context for debugging.
- Treat `AGENTS.md` and `CLAUDE.md` as generated artifacts (single source of truth is `.ai/rules/`), managed by `~/code/content/_scripts/alphalib-sync.ts`; never edit those files directly. If you'd like to make a modification, do it here in `.ai/rules/` and the script will ensure proper preservation and syncing. If you need a rule specific to this repo, add it to `.ai/rules/repo.mdc`.
- All new files are to be in TypeScript. Even if someone suggests: make this new foo3 feature, model it after `foo1.js`, create: `foo3.ts`. Chances are, a `foo2.ts` already exist that you can take a look at also for inspiration.
68 changes: 62 additions & 6 deletions .ai/rules/typescript.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,76 @@ description:
globs:
alwaysApply: true
---

For Typescript:

- Favor `contentGapItemSchema = z.object()` over `ContentGapItemSchema = z.object()`
- Favor `from './PosterboyCommand.ts'` over `from './PosterboyCommand'`
- Favor `return ideas.filter(isPresent)` over `ideas.filter((idea): idea is Idea => idea !== null)`
- Favor using `.tsx` over `.jsx` file extensions.
- Use Node v24's native typestripping vs `tsx` or `ts-node`. These days you do not even need to pass `--experimental-strip-types`, `node app.ts` will just work.
- Favor `satisfies` over `as`, consider `as` a sin
- Use Node v24's native typestripping vs `tsx` or `ts-node`. These days you do not even need to pass
`--experimental-strip-types`, `node app.ts` will just work.
- In ESM TypeScript, use `import.meta.dirname` / `import.meta.filename` or `URL` objects instead of
rebuilding `__dirname` with `fileURLToPath(import.meta.url)` unless compatibility requires it.
- Use `satisfies` when you need literal preservation or structural conformance while keeping the
expression's inferred type. If the variable should simply have a declared type, use a type
annotation instead.
- Avoid redundant type annotations inside expressions when TypeScript already infers the exact type,
especially callback parameters. Keep explicit return types and public boundary annotations.
- Avoid `as`, consider it a sin. If a cast is unavoidable, keep it as narrow as possible and explain
the upstream type mismatch or runtime invariant.
- For DOM queries and browser APIs, prefer runtime narrowing such as
`element instanceof HTMLAnchorElement` over type casts. Decide explicitly whether a missing element
should throw, return early, or no-op.
- Browser/client code must not use Node-only globals or APIs such as `Buffer`. Use browser platform
APIs such as `btoa`, `TextEncoder`, `Blob`, or `URL`, or isolate the logic in server-only code.
- When browser APIs are missing TypeScript declarations, augment the global interface in
`_types/global.ts` (or the relevant shared global types file) and narrow with checks such as
`typeof navigator.setAppBadge === 'function'` instead of casting `navigator` locally.
- Prefer typed DOM properties such as `element.inert = true` and `element.tabIndex = -1` when the
platform exposes them. Use `setAttribute()` only when you intentionally need raw attribute
semantics.
- Favor `unknown` over `any`, consider `any` a sin
- Avoid `as unknown as ...` and `biome-ignore lint/suspicious/noExplicitAny`. If an upstream library
type forces either, isolate it in a tiny adapter/helper with a comment naming the bad upstream
type.
- Every `@ts-expect-error` must be narrow and include a short explanation of the upstream type gap or
invariant that makes it safe.
- Favor validating data with Zod over using `any` or custom type guards
- For local TypeScript files, import with the `.ts` / `.tsx` extension (not `.js`, not extensionless).
Note: we do not currently enable the TS 5.7 `rewriteRelativeImportExtensions` compiler option,
because it errors on non-relative imports that include `.ts`/`.tsx` (for example via `paths`
aliases like `@/…`). If/when we enable it, we will need to adjust those imports first.
- Extract duplicated Zod object shapes, regexes, and descriptions into reusable schema fragments
when they describe the same domain concept. Keep property-level concerns such as `.optional()` and
`.default()` at the property site unless absence is intrinsic to the reusable fragment.
- Boolean Zod properties should usually use explicit defaults instead of `.optional()` when omission
has normal default behavior. Only leave a boolean optional when `undefined` is semantically
different from `false`.
- Prefer `z.union([...])` over chained `.or()` for multi-branch unions, and prefer `z.enum()` or
literal unions over clever regexes when the accepted values are finite and autocomplete matters.
- Do not duplicate supported values in user-facing schema descriptions when schema metadata such as
enums or suggested values can carry that information.
- In TypeScript files, use TypeScript syntax instead of JSDoc type annotations. In JavaScript files,
prefer JSDoc `@import` / `@param` forms over noisy inline `import('...')` annotations, and make
sure `@type` annotates the expression it is meant to type.
- Avoid hand-written `.d.ts` files when the declaration can come from TypeScript source or
generation. If a declaration file is unavoidable, do not rely on `skipLibCheck` to hide duplicate
or invalid exports.
- Avoid `Reflect.get` for normal object property reads. After narrowing to a record, use
`record[key]` or a small typed reader helper. Only keep `Reflect.get` for exotic receivers such as
proxies or framework objects where its semantics are intentionally required, and document why.
- Prefer `Number.isFinite()` / `Number.isNaN()` over global `isFinite()` / `isNaN()` so numeric
checks do not silently coerce non-numbers.
- `isRecord` style guards must reject arrays:
`typeof value === 'object' && value !== null && !Array.isArray(value)`. Prefer importing a shared
guard when one already exists in the relevant shared layer.
- Type-only refactors must preserve runtime behavior. If the behavior intentionally changes, call it
out in the PR and cover the changed behavior with tests.
- Use ECMAScript `#private` fields for private state. Do not rely on underscore names or TypeScript
`private` to imply runtime privacy.
- Prefer Zod defaults/preprocessing for schema-backed default values instead of duplicating default
objects in runtime code.
- For local TypeScript files, import with the `.ts` / `.tsx` extension (not `.js`, not
extensionless). Note: we do not currently enable the TS 5.7 `rewriteRelativeImportExtensions`
compiler option, because it errors on non-relative imports that include `.ts`/`.tsx` (for example
via `paths` aliases like `@/…`). If/when we enable it, we will need to adjust those imports first.
- Favor defining props as an interface over inline
- Favor explicit return types over inferring them as it makes typescript a lot faster in the editor
on our scale
31 changes: 21 additions & 10 deletions .ai/skills/gh-pr-to-green/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,31 @@ description:

## Workflow

- Get context from PR with `gh`, make sure if have all comments
- Write them as a markdown todolist in `repodocs/prompts/${YYYY}-${MM}-${DD}-${sluggedSemanticTopic}.md` and commit it. If the `repodocs/prompts/` dir does not exist, use the `docs/prompts/` dir.
- Get context from PR with `gh`; make sure all review comments, threads, CI failures, and PR
description updates are included.
- Write them as a markdown todolist in
`repodocs/prompts/${YYYY}-${MM}-${DD}-${sluggedSemanticTopic}.md` and commit it. If the
`repodocs/prompts/` dir does not exist, use the `docs/prompts/` dir.
- Merge the latest main into our branch and resolve any conflict carefully.
- Determine which review items are correct.
- Keep working until all items are checked off, either by fixing the issue, or explaining why not in the markdown list as well as comments in code where applicable/sensible
- commit and push
- Keep working until all items are checked off, either by fixing the issue, or explaining why not in
the markdown list as well as comments in code where applicable/sensible.
- Commit and push.
- Invoke `council-review`
- Run the repo-required checks (often `yarn check`)
- Fix any issue that you deem related and worth fixing
- commit and push
- If the PR has UI/UX impact, invoke `claude-usertest` instead of duplicating browser-test
instructions here. Treat its output as reviewer input, reconcile findings yourself, and fold valid
fixes back into this PR-to-green loop.
- Run the repo-required checks (often `yarn check`).
- Fix any issue that you deem related and worth fixing.
- Commit and push.
- Monitor CI until green:
- Prefer `gh run watch` (works everywhere with `gh`).
- If `gh-run-watch.ts` exists in the repo, it's fine to use that too.
- Fix any issue that you deem related and worth fixing
- Rinse and repeat until CI is green, or there are only 100% unrelated issues remaining
- Fix any issue that you deem related and worth fixing.
- Rinse and repeat until CI is green, or there are only 100% unrelated issues remaining.

Report back with a list of all changes made, and offer a link to the PR for inspection and merge. Offer to squash merge with `--admin` if the human thinks a last manual review is not needed.
This skill is PR orchestration only. Do not add inline UX/browser/user-test instructions here; use
dedicated validation skills for those workflows.

Report back with a list of all changes made, and offer a link to the PR for inspection and merge.
Offer to squash merge with `--admin` if the human thinks a last manual review is not needed.
8 changes: 4 additions & 4 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
nodeLinker: node-modules

npmMinimalAgeGate: 2880

packageExtensions:
"@oxc-resolver/binding-wasm32-wasi@*":
dependencies:
"@emnapi/core": "1.10.0"
"@emnapi/runtime": "1.10.0"

npmMinimalAgeGate: 2880
"@emnapi/core": 1.10.0
"@emnapi/runtime": 1.10.0
Loading