diff --git a/.docs/index.md b/.docs/index.md index 01ae75b..36cc17c 100644 --- a/.docs/index.md +++ b/.docs/index.md @@ -11,6 +11,7 @@ title: Documentation Index - [Config Matching](technical/config-matching.md): How agnos resolves glob-aware documentation ignores and rule fragments. - [MCP Env Resolution](technical/mcp-env-resolution.md): How MCP env key declarations are resolved into secret-bearing agent files. +- [Rules Bootstrap](technical/rules-bootstrap.md): How rule catalogs are discovered and copied into a configured project. - [Skills Reconciliation](technical/skills-reconciliation.md): How declared skill sources are reconciled with materialized skills and lock entries. ### technical-decisions diff --git a/.docs/technical/config-matching.md b/.docs/technical/config-matching.md index 95f4e0a..4db9f4a 100644 --- a/.docs/technical/config-matching.md +++ b/.docs/technical/config-matching.md @@ -18,6 +18,8 @@ timestamp: 2026-07-08T00:00:00Z ## Rule fragments +- Fragment paths resolve relative to `rules.dir` when it is configured. +- Fragment paths resolve relative to the project root when `rules.dir` is absent. - `rules.files` fragment entries may resolve to files, directories, or glob patterns. - Literal files preserve existing single-fragment behavior. - Directory entries expand to Markdown files recursively. diff --git a/.docs/technical/rules-bootstrap.md b/.docs/technical/rules-bootstrap.md new file mode 100644 index 0000000..47b38e1 --- /dev/null +++ b/.docs/technical/rules-bootstrap.md @@ -0,0 +1,36 @@ +--- +type: Technical Doc +title: Rules Bootstrap +description: How rule catalogs are discovered and copied into a configured project. +resource: "" +tags: [configuration, rules, bootstrap] +timestamp: 2026-08-18T00:00:00Z +--- + +# Rules Bootstrap + +## Configuration + +- `agnos.json` declares the bootstrap destination under `rules.dir`. +- Injectable paths are resolved from `rules.dir` when configured and from the project root otherwise. +- Rules initialization configures the directory, adds `.` to the selected canonical rules file, and references the docs index relative to that directory when docs are configured. +- Bootstrap is optional and runs only through the rules command. + +## Catalog discovery + +- The default catalog is the `.rules` directory in the Agnos repository. +- Custom Git and local repository sources use their top-level `.rules` directory. +- An explicit Git repository path replaces the conventional directory. +- Discovery includes Markdown files recursively and presents repository-relative paths in sorted order. + +## Copy behavior + +- Selected files preserve their catalog-relative paths under `rules.dir`. +- Existing files at the same paths are overwritten. +- Bootstrap does not record copied files in project state or lock files. +- Dry runs report copy operations without changing the destination. + +## References + +- Rules command: [bootstrap.ts](../../src/domains/rules/bootstrap.ts). +- Rules configuration: [public.ts](../../src/core/types/public.ts). diff --git a/.rules/general/_flagging.md b/.rules/general/_flagging.md new file mode 100644 index 0000000..df0d921 --- /dev/null +++ b/.rules/general/_flagging.md @@ -0,0 +1,14 @@ +--- +type: Rule +title: Flagging +description: Define the flagging and warning behavior. +resource: "" +tags: [flag, warn, recommendation] +timestamp: 2026-08-19T00:00:00Z +--- + +Whenever asked to flag something, add the flagged content at the end of your response with the following format: + +> ⚠️ ** gap detected:** . +> Recommendation: +> _Consider updating [``](targeted-file-path.md)_. diff --git a/.rules/general/_git.md b/.rules/general/_git.md new file mode 100644 index 0000000..326be76 --- /dev/null +++ b/.rules/general/_git.md @@ -0,0 +1,32 @@ +--- +type: Rule +title: Working with Git +description: Manage branches, commits, history, and pull requests safely. +resource: "" +tags: [git, version-control, workflow] +timestamp: 2026-08-19T00:00:00Z +--- + +### History management + +- Maintain a linear history: Prioritize `rebase` and `squash` over `merge`. +- Never take credit for code authoring. The author is always the developer. +- Don't ever sign commits. + +### Branch management + +- **`main` branch is locked**: Never push to `main` directly, instead create a branch and a PR. +- Strictly adhere to **[conventional branches](conventionalbranch.org/#specification)**. +- Branch names can never be called `/*`. They have to strictly follow the conventional names. + +### Commit strategy + +- Strictly adhere to **[conventional commits](conventionalcommits.org/en/v1.0.0/#specification)** +- Conventional multi-line messages: subject < 70 chars in imperative mood; body explains why. +- Always group related changes logically and split unrelated work. Do not do a big single commit. Do not commit atomically. +- Never amend pushed commits without explicit approval. Never --no-verify. +- Run the full `lint`, `format`, `typecheck`, and `test` suite before committing code. + +### Pull requests + +- Use `gh auth switch -u `: Always use the `GH_USER` secret declared in `.env.agents` to work with the GitHub CLI. diff --git a/.rules/general/_secrets.md b/.rules/general/_secrets.md new file mode 100644 index 0000000..80d5550 --- /dev/null +++ b/.rules/general/_secrets.md @@ -0,0 +1,38 @@ +--- +type: Rule +title: Secrets and Environment Variables +description: Protect environment files and document required variables safely. +resource: "" +tags: [secrets, environment, security] +timestamp: 2026-08-19T00:00:00Z +--- + +- **Never** print secrets in `.env.*` files, nor environmental variables, keys, secrets, etc... +- **Never** commit `.env.*` files except for `.env.example` which is meant to be used for documentation purposes. +- `.env.example` must **never** contain secrets. +- `.env.agents` is meant to host secrets only used by the agents. Its information must not be sensitive, and it may be committed. +- If `.env.*` are not ignored, warn the user with: + > You're about to commit your secrets! Please add the following to `.gitignore`: + > + > ``` + > # ENV + > .env + > .env.* + > !.env.example + > ``` +- Always keep the `.env.example` of every package up to date. +- `.env.example` must be segmented by platform like: + + ```env + # -------------------------------------------------------------------- + # : + + # Purpose: + # Source : + # Path : of -> Menu -> Items -> user -> must -> follow> + # Example: + = + + ... + ... + ``` diff --git a/.rules/general/coding.md b/.rules/general/coding.md new file mode 100644 index 0000000..2eee28d --- /dev/null +++ b/.rules/general/coding.md @@ -0,0 +1,110 @@ +--- +type: Rule +title: Coding Standards +description: Repository-wide language, package, quality, and implementation standards. +resource: "" +tags: + - coding + - typescript +timestamp: 2026-08-19T00:00:00Z +--- + +Enforced repo-wide. Non-negotiable. + +### Package manager + +**Use `pnpm` only.** + +Never use other package managers, like `npm`, `yarn`, or `bun`. + +### Turborepo + +- **TurboRepo with remote caching**: Use remote caching to ensure fast build and deploy times. + - subcommands like `:` should write to the same cache as the parent `` + +### TypeScript + +- End-to-end. +- No `any` without justification. +- Strict mode + noUncheckedIndexedAccess + verbatimModuleSyntax. +- Always use `type`. Avoid `interface` unless necessary. +- Always use `async/await` and `try/catch`. Avoid `.then()` chains. +- Always use named exports. Avoid default exports unless necessary. +- Always use arrow functions. Avoid named functions unless necessary or when explicitly told to use them. +- Always declare function components, never class components. +- Use aliases (`@`) to keep the import paths clean. +- Prefer single-line syntax for brief statements, functions, and control structures. Avoid unnecessary curly braces and line breaks. Examples: + + ```ts + // dont's + const double = (x: number): number => { + return x * 2; + }; + + if (!user) { + return null; + } + + const age = 30; + const user = { + name: name, + age: age, + }; + + type Status = "idle" | "loading" | "success"; + + interface Point { + x: number; + y: number; + } + + let label: string; + + if (isAdmin) { + label = "Admin"; + } else { + label = "User"; + } + + // prefer + const double = (x: number): number => x * 2; + + if (!user) return null; + + const user = { name, age }; + + type Status = "idle" | "loading" | "success"; + + type Point = { x: number; y: number }; + + const label = isAdmin ? "Admin" : "User"; + ``` + +### Comments + +- Write self-documenting code. +- Use JSDoc when appropriate. +- Prefer clear naming over comments. +- Comments are used to explain why, never what. +- Only comment what can not be inferred from code. +- Do not use comments for documentation. Use the documentation for this. +- Do not leave `TODO` or `FIXME` comments unresolved in committed code. +- Do not duplicate information already in the documentation. + +### Errors + +- Throw Error with descriptive messages, preserving causes via { cause: originalErr }. +- Catch only when you can do something useful. Empty try/catch reserved for genuinely optional cleanup (e.g., unlink of a maybe-missing file). +- Return { ok: boolean } from orchestrator-level functions; don't throw across the CLI boundary. + +### Logging + +- Five levels: info, success, warn, error, debug. Use the level that matches the meaning. +- No manual ANSI codes: The logger handles color and TTY detection. +- Hook implementations log without manual indentation prefixes; the orchestrator wraps the logger. +- would: prefix for dry-run output. + +### Code health + +- Use ESLint, Prettier, and `tsc` to validate code. +- Execute linting, formatting, type checking and testing before committing. Always. diff --git a/.rules/general/conventions-base.md b/.rules/general/conventions-base.md new file mode 100644 index 0000000..09b63c0 --- /dev/null +++ b/.rules/general/conventions-base.md @@ -0,0 +1,14 @@ +--- +type: Rule +title: Conventions +description: Define shared reuse, module, and localization conventions. +resource: "" +tags: [conventions, modularity] +timestamp: 2026-08-19T00:00:00Z +--- + +- Idempotency is mandatory for anything that touches the filesystem. +- If a pattern appears twice, extract it. +- Use barrel exports and keep them updated. +- Organize logic by functionality and concern. +- One concept per file. diff --git a/.rules/general/conventions-change-discipline.md b/.rules/general/conventions-change-discipline.md new file mode 100644 index 0000000..d9e873e --- /dev/null +++ b/.rules/general/conventions-change-discipline.md @@ -0,0 +1,30 @@ +--- +type: Rule +title: "Change Discipline" +description: Keep changes intentional, scoped, reversible, and respectful of existing work. +resource: "" +tags: [changes, scope, collaboration] +timestamp: 2026-08-26T14:20:00Z +--- + +- Understand the requested outcome before changing files or external state. +- Inspect relevant or shared code, configuration, documentation, and established patterns first. Codebase is small; grep first. +- Limit changes to the requested outcome and its necessary supporting work. +- No abstractions for hypothetical needs. Build for what's asked; symmetry over flexibility when adding hooks (if there's onAdded, there's probably onRemoved). +- Preserve unrelated edits and user-owned work. +- Do not perform opportunistic refactors, migrations, or cleanup. +- Follow the existing architecture unless the task explicitly changes it. +- Identify generated files and update their source of truth instead of editing generated output directly. + +### Decisions and risk + +- State assumptions that materially affect behavior or scope. +- Ask for direction when unresolved ambiguity would produce meaningfully different outcomes. +- Resolve exact targets before destructive, irreversible, or externally visible actions. +- Preserve compatibility unless the task explicitly authorizes a breaking change. + +### Completion + +- Review the final diff for unrelated changes, accidental formatting, generated noise, and sensitive information. +- Report completed work, verification results, assumptions, and remaining limitations. +- Do not claim completion while required work or verification remains unfinished. diff --git a/.rules/general/conventions-i18n.md b/.rules/general/conventions-i18n.md new file mode 100644 index 0000000..86126ea --- /dev/null +++ b/.rules/general/conventions-i18n.md @@ -0,0 +1,43 @@ +--- +type: Rule +title: "Internationalization: modular i18n" +description: Define shared reuse, module, and localization conventions. +resource: "" +tags: [conventions, i18n, internationalization] +timestamp: 2026-08-19T00:00:00Z +--- + +- Always **prepare for i18n**. +- Hardcoded strings in components are a defect. +- Co-locate dictionaries next to the consuming organism. +- Never bundle all strings into a single dictionary file. Keep one disctionary per supported language. +- Use a schema file to define the `i18nSchema`, the Zod schema for the dictionary. +- Dictionaries (json files) are always fetched over the network and validated with the zod schema once received. + +``` +src/ +└─ ...// + ├─ index.ts + ├─ ... + ├─ .i18n._schema.ts + └─ .i18n..json +``` + +```ts +// .../.i18n._schema.ts +import z from 'zod' + +// declare the shape of the dictionary +export const i18nSchema = z.object({ ... }) + +// .../..ts +import { i18nSchema } from './.i18n._schema.ts' + +export const DomainRole = async () => { + // fetch json dictionary + const json = await fetchI18nLang(lang) + // validate it with the schema + const validDictionary = i18nSchema.safeParse(json) + ... +} +``` diff --git a/.rules/general/conventions-naming.md b/.rules/general/conventions-naming.md new file mode 100644 index 0000000..5030f57 --- /dev/null +++ b/.rules/general/conventions-naming.md @@ -0,0 +1,49 @@ +--- +type: Rule +title: Naming Conventions... +description: Define shared reuse, module, and localization conventions. +resource: "" +tags: [conventions, naming] +timestamp: 2026-08-19T00:00:00Z +--- + +### ... for directories + +Directories use `camelCase` unless explicitly told otherwise. + +### ... for files + +For files we use: + +- `..`: A declarative, **dot-delimited** `cammelCase` file naming strategy with a predictable pattern that encodes the domain and the architectural role, for the pieces that compose a feature. +- `index`: for the barrel files. + +Examples of roles: +| Role | Use for... | +| ------------------------------------------------ | -------------------------------------------------------- | +| module | bundling a feature | +| controller, component | orchestraiton and coordination | +| modal, dialog, drawer, presentation, form, etc.. | ui blocks like overlays, modals, drawers, popups, etc... | +| dto, entity, schema | data shapes, contracts, models, etc... | +| api, service | business logic | +| hook., store, context, repository | data fetching, state, external apis, etc... | +| css stylesheet, css module | styling rules | +| utility, helper, command | pure funtions, commands, directives, parsers, etc... | +| i18n., i18n.\_schema | internationalization | +| test, test.input, test.seed | tests and test files | + +Never use `kebab-case`. + +In the case the files in a directory grow too much due to expanding sub-roles, the roles can be moved to a directory within the domain that logacally represents what it contains, e.g.: + +- `i18n` role has an additional part for language and schema. They can be moved to `i18n/`. +- `tests` role might grow to have too many files. They can be moved to `tests/`. +- multiple roles like `dialog`, `drawer`, and `modal` cover overlapping concerns. They can be moved to `components/`. + +### ... for code + +Whenever writting code, use: + +- `UPPER_SNAKE_CASE` for constants. +- `PascalCase` for type declarations, classes, components, generics, and enums. +- `camelCase` for everything else. diff --git a/.rules/general/dependency-management.md b/.rules/general/dependency-management.md new file mode 100644 index 0000000..1c70b1a --- /dev/null +++ b/.rules/general/dependency-management.md @@ -0,0 +1,31 @@ +--- +type: Rule +title: Dependency Management +description: Add, update, and remove project dependencies deliberately and safely. +resource: "" +tags: [dependencies, packages, maintenance] +timestamp: 2026-08-19T00:00:00Z +--- + +- Inspect the project manifests, lockfiles, workspace configuration, and existing dependencies before making changes. +- Use the package manager and versioning policy already established by the project. +- Do not introduce a competing package manager or lockfile. +- Prefer existing platform capabilities and installed dependencies over adding another package. +- Add a dependency only when it directly supports an approved requirement. +- Keep dependency changes limited to the requested scope. +- Do not perform unrelated upgrades or lockfile refreshes. + +### Selecting dependencies + +- Prefer maintained dependencies with compatible licenses, runtimes, and peer requirements. +- Evaluate security history, release activity, bundle or runtime cost, and transitive dependencies. +- Prefer focused dependencies over broad frameworks when only a narrow capability is required. +- Place dependencies in the narrowest correct production, development, optional, or peer scope. + +### Changing dependencies + +- Update manifests and lockfiles together using the project package manager. +- Review install scripts and generated changes before accepting them. +- Confirm that a dependency is unused before removing it. +- Remove obsolete configuration, imports, and documentation with a removed dependency. +- Run the project checks affected by the dependency change. diff --git a/.rules/general/principles-atomic-design.md b/.rules/general/principles-atomic-design.md new file mode 100644 index 0000000..d843c0b --- /dev/null +++ b/.rules/general/principles-atomic-design.md @@ -0,0 +1,23 @@ +--- +title: "Principle: Atomic Aesign" +--- + +When designing, building, and organizing components, assess their level in the following scale according to their responsibility and composition, and use the level to place them in the correct directory: + +1. **Atoms**: located at `.../atoms/`. The smallest indivisible UI elements, such as buttons, icons, labels, inputs, and typography. They provide basic styling and behavior but little meaning on their own. +2. **Molecules**: located at `.../molecules/`. Small combinations of atoms that perform one focused task. For example, a labeled input, search field with a button, or avatar with a username. +3. **Organisms**: located at `.../organisms/`. Larger, self-contained interface sections composed of atoms and molecules. Examples include navigation headers, product cards, forms, and data tables. +4. **Templates**: located at `.../templates/`. Page-level structures that arrange organisms into a reusable layout. They define content hierarchy and placement without depending on final, page-specific content. +5. **Pages**: located at `.../pages/`. Concrete instances of templates populated with real content and application data. Pages represent what users actually visit and are useful for validating the complete experience. + +The dependency direction (from higher levels to lower ones) is: + +``` +Page + └🡪 Template + └🡪 Organism + └🡪 Molecule + └🡪 Atom +``` + +Where the higher levels may import from lower levels, but lower levels may never import from higher ones. diff --git a/.rules/general/principles-slot-based-composition.md b/.rules/general/principles-slot-based-composition.md new file mode 100644 index 0000000..66c9071 --- /dev/null +++ b/.rules/general/principles-slot-based-composition.md @@ -0,0 +1,165 @@ +--- +title: "Principle: Slot based composition" +--- + +Use slot-based composition when you need a reusable UI component to maintain a fixed structure, styling, or behavior while letting parent elements inject flexible, custom inner content. + +Avoid slots based composition when a component has only one content region. + +### Anatomy of a slot based component + +1. A `host` component to own the layout: structure, order, responsiveness, and accessibility. +2. One or many `slots` components to declare the presentation of each of the layout's regions. + +Consumer components provide the content. Host and slot components concern only with presentation and layouts. + +### Rules for host components + +Host components must: + +- Keep host-specific components and hooks private. +- Be declared using **arrow functions**. +- Use `useComponentSlots` to define the allowed slot components for a parent component. +- Use `index.tsx` to export the compound host. +- Expose slots only through `Host.Slot` from `index.tsx`. Never allow direct imports from `components/` and `hooks/`. + +### Rules for slot components + +Slot components must: + +- Be declared using **named functions**. +- Use semantic slot names such as `Header`, `Content`, `Actions`, `Filters`, etc... +- Not be used for data-driven repeated content. + +Move a slot component to the respective level in the top-most shared components folder of the project (usually `src/components/`) when any of these conditions apply: + +- It gains a second consumer. +- It has an independent purpose outside its parent. +- It no longer depends on its parent’s internals. + +Do not move it based only on size or hypothetical reuse. + +### Folder structure + +```text +src/components/ + └─ + ├─ index.tsx + ├─ .component.ts + ├─ .hook..ts + ├─ .module.css + ├─ .store.ts + └─ components/ + ├─ slotComponentA + │ ├─ .module.css + │ └─ .component.ts + └─ slotComponentB + ├─ .module.css + └─ .component.ts +``` + +### Example with React + +```jsx +// src/components/host/components/slot/slotA.component.tsx +type SlotAProps = PropsWithChildren<{ /* ... */ }> + +// Declare the slot component: +export function SlotA({ ...props }: SlotAProps) {/* ... */} + +// src/components/host/host.component.tsx +import { SlotA } from './components/slot/slotA.component.tsx' + +type HostProps = PropsWithChildren<{ /* ... */ }> + +// Declare the host component: +const Host = ({ ... }: HostProps) => { + const slots = useComponentSlots({ slot: SlotA }, props) + + return
+ {/* ... */} + {slots.slot} + {/* ... */} +
+} + +// Compound and export the host component +Host.SlotA = SlotA +export { Host } + +// Consumer.tsx +import { Host } from "Host" + +const { SlotA } = Host + +// Consume the slot-based component: +export const Consumer = () => ( + + + +) + +// useComponentsSlots.ts +import type { ReactNode } from 'react' +import { Children, isValidElement, useMemo } from 'react' + +type SlotComponent = (props: never) => ReactNode +type SlotDefinition = SlotComponent | SlotComponent[] +type Definitions = Record +type PreparedSlots = Record +interface UseComponentSlotsProps { + definitions: T + children: ReactNode +} + +export function useComponentSlots({ + children, + definitions +}: UseComponentSlotsProps): PreparedSlots { + return useMemo(() => { + let defaultProp = undefined + const preparedSlots = {} as PreparedSlots + const entries: { + key: Extract | undefined + limit: number + definition: SlotComponent[] + }[] = [] + + for (const key in definitions) { + preparedSlots[key] = [] + + const definition = definitions[key] + const isArray = Array.isArray(definition) + if (definition) + entries.push({ + key, + definition: [definition].flat(1), + limit: isArray ? -1 : 1 + }) + + const isDefaultProp = isArray && !!definition.length + if (!isDefaultProp) defaultProp = key + } + + for (const child of Children.toArray(children)) { + if (!isValidElement(child)) continue + + const match = entries.find(({ definition }) => + definition.includes(child.type) + ) + + if (!match && !defaultProp) continue + + const key = match?.key ?? defaultProp + const limit = match?.limit ?? -1 + + if (!key) continue + + if (limit >= 0 && limit >= preparedSlots[key].length) continue + preparedSlots[key].push(child) + } + + return preparedSlots + }, [children, definitions]) +} +``` diff --git a/.rules/general/testing.md b/.rules/general/testing.md new file mode 100644 index 0000000..d7d8620 --- /dev/null +++ b/.rules/general/testing.md @@ -0,0 +1,155 @@ +--- +type: Rule +title: Testing +description: Define when and how end-to-end, unit, and component tests are created. +resource: "" +tags: + - testing +timestamp: 2026-08-26T14:20:00Z +--- + +- Create tests only when: + 1. The user explicitly requests them. + 2. The user explicitly approves an agent's testing proposal. +- Do not create tests based only on risk, convention, coverage, or implementation changes. +- Agents may suggest tests when they would protect critical behavior. +- Present the proposed scope, cases, and test type before requesting approval. +- Run existing relevant tests after implementation changes without requiring approval. +- Write test stories, cases, and descriptions in assertive language. +- Describe the behavior a passing test guarantees. +- Write user stories as: + - `As a , I can to .` + +### Tests structure + +- Co-locate unit tests with the source module they exercise. + +- Define each test with: + 1. A story or description. + 2. The behavior being tested. + 3. Passing and failing cases. +- Prefer the smallest test type that proves the behavior. +- Test observable behavior through public interfaces. +- Do not test implementation details. +- Keep tests deterministic, isolated, and repeatable. +- Give each test one clear reason to fail. +- Do not duplicate the same assertion across test types. + +#### Data for input and seeding + +- Use input data for values supplied during the tested action. +- Use seed data for state created by earlier or unrelated processes. +- Share input and seed files across tests in the same domain. +- Use predictable values and never store secrets. +- Remove records created by a test, including after failures. +- Never change or remove data the test did not create. + +#### Folder structure + +```text +src/ +└─ ...// + ├─ ... + ├─ .test.ts + ├─ .test.input.json + └─ .test.seed.json +``` + +### Writing tests + +1. Name the test file according to what it tests, using a short descriptive filename. +2. Discover the passing, failing, invalid, empty, and boundary cases that matter. +3. Define the observable result for each case. +4. Write the story as JSDoc, for the corresponding group of tests for the flow. +5. Use assertive language to write: + 1. The description of the group, using the result and role. + 2. The description of each test from the action. + +```ts +// /.test.ts + +/** As a [role], I want to [action], to [result] */ +describe(`to [result] as a [role], I...`, () => { + test(`can [passing-action]`, () => {}) + test(`can't [failing-action]`, () => {}) + ... +}) +``` + +### End-to-end tests + +- Use Playwright. +- Assert outcomes visible to the user. +- Validate complete user flows through the browser. +- Validate: + - Layout and visible state. + - User interaction. + - Route changes. + - Application integrations. + - Successful outcomes. + - Expected failures. +- Seed only the state required by the story. +- Keep input and seed data at the nearest shared story scope. +- Use Playwright test agents to plan, generate, and repair tests. +- Do not use a test repair to hide a product defect. + +When writing tests with Playwright, always review these tools and choose the most convenient one: + +- [Playwright CLI](http://playwright.dev/agent-cli/introduction) +- [playwright mcp server](https://playwright.dev/mcp/introduction) +- [playwright agents](https://playwright.dev/docs/test-agents) + +### Unit tests + +- Use unit tests for critical code in isolation. +- Give each unit test group a concise behavior description. +- Test critical: + - Authentication and authorization. + - Permissions. + - Monetary calculations. + - Data integrity. + - Destructive actions. + - State transitions. + - Parsing and normalization. + - Error translation. +- Do not unit test: + - Framework behavior. + - Generated code. + - Static configuration. + - Thin wrappers or adapters. + - Trivial CRUD behavior. + - Constants or type-only code. +- Use static input and seed data for provider-independent logic. +- Do not use fixtures, stubs, or mocks to imitate third-party services. +- Test third-party integrations against the real service in a separate integration suite. +- Use provider test modes, sandboxes, or dedicated test accounts. +- Skip provider tests when required credentials are unavailable. +- Never fall back to mocked provider behavior. + +### Component tests + +- Test components through Storybook stories. +- Use the story, tests, and cases structure. +- Prefer testing atoms and molecules. +- Test larger components only when their owned behavior cannot be proven at a smaller level. +- Do not test organisms, templates, or pages only to confirm that React or Next.js renders them. +- Use render-only stories for static visual states. +- Add Storybook interaction tests only for meaningful interaction. +- Validate: + - Visual states. + - User interaction. + - Keyboard behavior. + - Accessible names, roles, and states. + - Relevant layout changes. +- Keep stories deterministic, isolated, and safe to rerun. +- Do not depend on mutable external state. +- Use Storybook args for component states and cases. +- Keep stories beside their component. + +### Verification + +- Run only the suites relevant to the changed or approved tests. +- Run integration tests only when provider credentials are available. +- Report product defects separately from test defects. +- Report provider outages and rate limits separately from product failures. +- Do not weaken assertions to make a failing test pass. diff --git a/.rules/general/verification.md b/.rules/general/verification.md new file mode 100644 index 0000000..4899ada --- /dev/null +++ b/.rules/general/verification.md @@ -0,0 +1,32 @@ +--- +type: Rule +title: Verification Policy +description: Verify changes proportionally and report evidence without hiding failures. +resource: "" +tags: [verification, quality, delivery] +timestamp: 2026-08-19T00:00:00Z +--- + +## Verification policy + +- Verify every change in proportion to its risk and affected surface. +- Use the project-defined checks and the narrowest relevant validation first. +- Expand to broader checks when changes cross packages, contracts, build boundaries, or deployment behavior. +- Follow the [testing rules](testing.md) when verification requires creating or changing tests. +- Run existing relevant tests without requiring separate approval. +- Verify both successful behavior and expected failure behavior for critical paths. +- Include generated artifacts, configuration, documentation, and packaging checks when they are affected. + +### Failure handling + +- Do not weaken assertions, disable checks, or change expected output only to make verification pass. +- Distinguish failures caused by the change from pre-existing failures and unavailable external systems. +- Investigate unexpected failures before classifying them as unrelated. +- Report any required verification that could not run and explain why. + +### Completion evidence + +- Review the final diff after automated checks complete. +- Record the commands or workflows run and their outcomes. +- Report warnings separately from errors. +- Do not state that verification passed when checks were skipped, incomplete, or failing. diff --git a/.rules/general/writing-docs.md b/.rules/general/writing-docs.md new file mode 100644 index 0000000..0ef2f94 --- /dev/null +++ b/.rules/general/writing-docs.md @@ -0,0 +1,42 @@ +--- +type: Rule +title: Managing and Authoring Documentation and Rules +description: Define when and how project documentation and rule fragments are maintained. +resource: "" +tags: [documentation, rules, authoring] +timestamp: 2026-08-19T00:00:00Z +--- + +Follow these rules whenever writing documentation or rules. + +### Authoring conditions + +- Author rules files when: + - The user asks for a specific pattern to be implemented. For example: "use arrow functions", "names must be camelCased". + - An audit reveals a pattern that can benefit the codebase. + +- Author documentation when a task: + - **changes how a system works**: auth flow, data model, API pattern, subscription logic, etc. + - introduces an **architectural decision**: introducing a **new pattern, library, or architectural approach**. + +### Authoring rules + +When authoring documentation and rules: + +- Always review your skills to author docs and rules correctly. +- Never author docs or rules without explicit approval from the user. +- Use simple and professional language. +- Do not add information that's not needed. +- Update only what's necessary. **Never** edit information that doesn't need to be updated. +- Remove duplicate, redundant, or stale information. +- Any authoring proposal must be flagged. + +### Refreshing files with Agnos + +If the project has an `agnos.json` file at the root, you may use any of the following commands as needed: + +- `npx @luxia/agnos@latest docs --once` regenerate the docs. +- `npx @luxia/agnos@latest rules --once` regenerate the rules. +- `npx @luxia/agnos@latest --once` to regenerate both. + +If using a package manager other than npm, use the correct equivalent to `npx`. diff --git a/.rules/writing-code/api-and-data-changes.md b/.rules/writing-code/api-and-data-changes.md new file mode 100644 index 0000000..c84222f --- /dev/null +++ b/.rules/writing-code/api-and-data-changes.md @@ -0,0 +1,34 @@ +--- +type: Rule +title: API and Data Changes +description: Evolve contracts and persisted data without silent breakage or corruption. +resource: "" +tags: [api, data, migrations] +timestamp: 2026-08-19T00:00:00Z +--- + +## API and data changes + +- Treat public APIs, events, files, schemas, and persisted records as explicit contracts. +- Identify producers, consumers, ownership, and compatibility requirements before changing a contract. +- Validate external data at ingress and serialize responses through defined output contracts. +- Keep contract behavior consistent across runtime types, validation, documentation, and generated clients. +- Do not expose internal storage shapes as public contracts without an explicit decision. + +### Compatibility + +- Prefer additive changes when existing consumers must continue working. +- Do not remove, rename, reinterpret, or narrow fields without an approved migration or version boundary. +- Keep error shapes and status semantics stable unless the contract change explicitly includes them. +- Define deprecation and removal conditions for transitional behavior. +- Remove compatibility paths after their migration window closes. + +### Data migrations + +- Separate schema expansion, data migration, and schema contraction when an immediate cutover is unsafe. +- Make migrations restartable and safe to retry. +- Preserve data until successful migration and verification are confirmed. +- Define rollback or recovery behavior before destructive transformations. +- Use transactions or equivalent consistency controls for related writes. +- Protect concurrent updates from lost writes, duplicate effects, and partial state. +- Record progress for long-running migrations without storing sensitive data. diff --git a/.rules/writing-code/architecture-components-react.md b/.rules/writing-code/architecture-components-react.md new file mode 100644 index 0000000..8bbb06a --- /dev/null +++ b/.rules/writing-code/architecture-components-react.md @@ -0,0 +1,201 @@ +--- +type: Rule +title: "Architecture: React Components" +description: Organize and write React components, slots, and forms. +resource: "" +tags: + - components + - react +timestamp: 2026-08-19T00:00:00Z +--- + +## React component architecture + +Always follow these rules when writing React components: + +- Use the **Atomic Design Principles** to organize components. +- Use **Slot-based composition** when a component has multiple named layout regions, and its parts may not be reused by other components. +- Place each component in its own folder, following the atomic design principles and slot-based composition rules. +- Components are for presentation. Extract complex logic into hooks. + +### Writing components + +- Keep the `props` surface as small as possible. +- Use a store instead of props when: + - a prop has to be passed down more than one level. + - a prop must be consumed by more than one component. + - it helps to avoid prop-drilling. +- Always use `PropsWithChildren`. Avoid manually typing children. +- Modularize components into independent files, unless the components are closely related and their surface is small. +- Do not use thin wrapper components (components that wrap and export another component without handling any logic). + +#### Example + +```jsx +// MyComponent.tsx +type MyComponentProps = PropsWithChildren<{ /* ... */ }> + +export const MyComponent = ({ ...props }: MyComponentProps) => { /* ... */ } +``` + +### Form components + +- Place each form in `src/components/forms/
/`. +- Manage forms with Mantine's `useForm`. +- Use controlled mode. Do not use `form.key()`. +- Provide complete `initialValues` on the first render. +- Do not gate input props on `form.initialized`. +- Use uncontrolled mode only to solve a measured rendering problem. +- Use `FunctionArgs>` for payload types. +- Do not derive form types from `Doc<...>`. +- Use `form.submitting` instead of duplicate submission state. +- Return the submission promise so `form.submitting` remains accurate. +- Use `requestSubmit()` for triggers outside ``. +- Do not add stories to forms. Add stories to their reusable atoms and molecules. + +#### Folder structure + +```text +src/ + components/ + forms/ + / + index.ts + presentation.tsx + bootstrap.ts + actions.ts # optional + schemas.ts # optional + codec.ts # optional + schemas/ + / + .ts + utils/ + forms.ts +``` + +- `index.ts` exports `` and `use`. +- `presentation.tsx` contains fields and layout. +- `bootstrap.ts` configures and exports `use`. +- `actions.ts` contains form-specific submission orchestration. +- Create `actions.ts` only when submission does more than invoke one mutation or function, such as formatting payloads or coordinating operations. +- Do not create `actions.ts` only to import, wrap, or re-export one mutation or function. +- `schemas.ts` contains form-specific presentation and payload schemas. +- `codec.ts` transforms between presentation values and payloads. +- `src/schemas//` contains reusable response schemas. +- `src/utils/forms.ts` contains shared form helpers. +- Keep optional form files private. Move them only when they gain a consumer outside the form. + +#### Data and schemas + +- Type responses. +- Create `src/schemas//.ts` only when the response needs: + - runtime validation. + - normalization or transformation. + - initial or default values. +- Use Zod `.default()` for response defaults. +- Share response schemas across forms, hooks, and views. +- Keep payload types and schemas inside the form. +- Create a payload schema when it needs runtime validation or differs from the presentation shape. +- Use the fetched response shape as the presentation shape when possible. +- Submit values directly when the presentation and payload shapes match. +- Create a transformer only when the shapes differ. +- Prefer a Zod codec for pure, bidirectional transformations. +- Use functions for one-way, lossy, contextual, or effectful transformations. +- Keep complex payload formatting and coordinated persistence in `actions.ts`. + +```tsx +// src/schemas//.ts +export type Response = // ... +export const responseSchema: z.ZodType = z.object({ + : z.string().default(""), +}) + +// src/components/forms//schemas.ts +export type Payload = // ... +export const payloadSchema: z.ZodType = z.object({ + : z.string(), +}) + +// src/components/forms//codec.ts +export const formCodec = z.codec(payloadSchema, responseSchema, { + decode: (payload) => , + encode: (values) => , +}) +``` + +#### Ownership + +- `bootstrap.ts` must: + - configure `initialValues`, `validate`, `transformValues`, and `enhanceGetInputProps`. + - accept data and configuration only. Do not accept callbacks such as `onSubmit`. + - define `submit` directly when it only calls one mutation or function. + - obtain `submit` from `actions.ts` when submission requires orchestration. + - return `{ form, submit }`. Do not bind `form.onSubmit`. + - hydrate asynchronous response data with `setInitialValues`, `setValues`, and `resetDirty`. + - parse response data before hydration when a response schema exists. + - use shared prop enhancers for lifecycle state such as `disabled`. +- When present, `actions.ts` must: + - format the mutation payload and persist it. + - not show notifications, navigate, close UI, or invoke consumer callbacks. +- `presentation.tsx` must: + - receive the form through a `form` prop. + - own field layout, state, and presentation. + - not render ``, fetch data, submit, or own submit triggers. +- The consumer must: + - import the form and hook from `index.ts`. + - render ``. + - declare `handleSubmit` and call `submit` inside it. + - bind `handleSubmit` with `form.onSubmit`. + - own submission triggers and effects. + +```tsx +// src/components/forms//bootstrap.ts +export const use = () => { + const form = useForm({ + initialValues: responseSchema.parse({}), + validate: schemaResolver(responseSchema, { sync: true }), + transformValues: (values) => z.encode(formCodec, values), + enhanceGetInputProps: enhanceInputPropsWithDisable(), + }) + const submit = useMutation(api..) + + return { form, submit } +} + +// src/components/forms//presentation.tsx +export const = ({ form }: Props) => ( + ")} /> +) + +// src/components/forms//index.ts +export { use } from "./bootstrap" +export { } from "./presentation" +``` + +```tsx +// .tsx +export const Consumer = () => { + const formRef = useRef(null); + const { form, submit } = use(); + const handleSubmit = async (payload: Payload) => { + await submit(payload); + // + }; + + return ( + <> + + + +