apps/apiowns the HTTP API surface.apps/cliowns CLI entrypoints and command behavior.packages/sharedowns public wire contracts, Effect schemas, plan metadata, and shared media policy. Production code must import these through@densio/shared; do not duplicate contracts or deep-import another workspace's source.- Application packages must not import one another. Cross-application composition is limited to the
e2eharness. - Ensure new workspaces are covered by both
package.jsonandpnpm-workspace.yaml; existing globs already cover directories underapps/andpackages/. Useworkspace:*for internal dependencies and update the lockfile. Changeturbo.jsononly when task behavior differs from the existing defaults.
- Treat Node.js 22.18 as the compatibility baseline and use pnpm 11.7.0 through Corepack.
- Use
pnpm install --frozen-lockfile. Use pnpm for workspace dependency changes; npm is reserved for the tested CLI packaging and publishing workflow.
- Before changing files under
apps/api, readapps/api/AGENTS.md. - Before changing files under
apps/cli, readapps/cli/AGENTS.md.
- For API contract changes, update the shared schema, Hono handler, OpenAPI metadata and response statuses, and contract tests together.
- When a contract is CLI-visible, also update the CLI decoder and follow the runtime skill synchronization rule below.
- The API remains authoritative for media inspection, codec policy, entitlements, billing, queueing, and cleanup. Do not duplicate server policy in the CLI.
skill-bundle/is the canonical runtime skill served by the API.skills/densio/SKILL.mdis only the stable public bootstrap.- When CLI-visible contracts, commands, flags, workflows, or errors change, update affected CLI help, the relevant
skill-bundle/references/*files, and drift tests together.
- Never commit or expose
.envcontents, auth or refresh tokens, magic-link tokens, Stripe secrets or signatures, SQLite data, or signed artifact URLs outside intended command results. - The CLI defaults to
https://api.densio.sh. Local CLI testing must pass--api-urlexplicitly, and manual local CLI work must also use a disposable--credentialspath. - Use temporary SQLite databases, filesystems, local HTTP servers, and injected clocks for tests. Unit, integration, and local end-to-end tests must not contact deployed Densio, Stripe, Resend, live storage providers, or other external services.
- Test observable behavior through the real implementation, including services and repositories where relevant. Assert outputs, persisted state, and errors rather than internal call sequences.
- Use controlled substitutes at external boundaries such as
EmailSender,StripeGateway, andObjectStore. Exercise provider adapters against local HTTP servers when testing their protocol behavior. - Do not duplicate implementation logic into tests.
- Treat
.dockerignoreas an allowlist. When adding an API runtime asset or workspace dependency, update both.dockerignoreand the Dockerfile copy and install inputs, then validate the image build. - FFmpeg and FFprobe intentionally remain outside the application image. Preserve the read-only
/opt/ffmpeghost mount unless the deployment architecture is explicitly changing.
pnpm testincludes the deterministic local golden journey and requires FFmpeg and FFprobe withlibsvtav1,libvpx-vp9, andlibx265encoders.- Do not add production
E2E_MODEbranches or test-only HTTP routes. Inject local behavior at the provider boundaries described above. - Do not run
pnpm synthetic:stagingorpnpm synthetic:productionas routine validation. Run them only when explicitly requested with a reachable deployment and dedicated credentials.
- Inspect the working-tree changes before editing. Preserve existing work, keep edits scoped to the task, and avoid formatting unrelated files.
- Package-filtered tests and typechecks are appropriate during iteration.
- Read-only reviews do not require formatting or completion checks; run read-only checks only when needed to support the review or explicitly requested.
- For documentation-only changes outside embedded runtime assets, format the changed files with
pnpm exec oxfmt --disable-nested-config --write <files>and verify them withpnpm exec oxfmt --disable-nested-config --check <files>. Tests, typechecks, and builds are not required for prose-only changes. - For code or configuration changes, run
pnpm formatfollowed bypnpm checkfrom the repository root. If root formatting would change unrelated files, format only the task's files with the scoped command above, then runpnpm check. The check command includes typechecks, tests, lint, and formatting verification. - Also run
pnpm buildafter changing entrypoints, package manifests, bundling, Docker inputs, release tooling, or embedded runtime assets. - Report checks run, failures, and checks that could not run with their missing prerequisites. Identify pre-existing failures only when supported by evidence, and report unrelated failures separately without expanding the task to fix them.
- Do not weaken or disable lint rules to bypass failures unless the change is clearly justified by the task.
- Do not bypass the 100-line function limit or 1000-line file limit. Split code into clearer modules or functions instead.
- Keep cohesive logic together. Extract helpers when they name a domain concept, isolate a complex boundary, enable reuse, or keep functions within lint limits.
- Make the main function read as the happy path. Keep supporting helpers close to the code they serve, below the main export when that improves readability; leave simple expressions inline when extraction adds no clarity.
- Avoid
try/catchwhere possible. - Avoid
any. - Rely on type inference when possible; avoid explicit type annotations or interfaces unless necessary for exports or clarity.
- Prefer functional array methods like
map,filter, andflatMapover loops when they make the data flow clearer. - Add comments for non-obvious constraints and surprising behavior, not for obvious assignments or control flow.
- Never alias imports.
- Never use star imports.
- Prefer
constoverlet. - Use ternaries or early returns instead of reassignment.
- Use named intermediate values when they clarify intent, even when used only once. Inline values when doing so keeps the expression easy to read.
- Avoid
elsestatements. - Prefer early returns.