diff --git a/.gitignore b/.gitignore index 034456d..4fa3931 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,10 @@ ai.local.json plans/ # Beads / Dolt files (added by bd init) +.beads/ .dolt/ *.db .beads-credential-key + +# Local agent-generated config and skills +.agents/ diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 0a01b13..69e655e 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -17,8 +17,12 @@ "ignorePatterns": [ "**/dist/**", "**/node_modules/**", + "**/.agents/**", "**/.aix/**", "**/.astro/**", + "**/.beads/**", + "**/AGENTS.md", + "**/GEMINI.md", "package-lock.json", "packages/cli/oclif.manifest.json" ] diff --git a/.oxlintrc.json b/.oxlintrc.json index 8e1eaaa..1fe6445 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -55,6 +55,10 @@ "**/*.js", "**/*.cjs", "**/*.mjs", - "**/.aix/**" + "**/.agents/**", + "**/.aix/**", + "**/.beads/**", + "**/AGENTS.md", + "**/GEMINI.md" ] } diff --git a/AGENTS.md b/AGENTS.md index d79bf46..9a741ae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,3 +45,1322 @@ git push origin vX.Y.Z ``` Pushing a `v*` tag triggers the GitHub release workflow, which publishes packages and creates the GitHub Release. + + +## general + +## Core Principles + +* Readability and clarity over brevity +* Follow existing patterns in the codebase before inventing new ones +* Do deep research to find existing libraries (NPM, GitHub, Cargo, etc.) that solve + problems instead of writing code + * Code writing is a last resort +* Separate formatting-only changes from functional changes + +## Working Habits + +* Be critical and thorough. Prefer truth and direct feedback over politeness +* Look around and use existing patterns and code when possible. Look for: + * Similar components and use their patterns + * Library code you can reuse + * Existing dependencies from package.json or Cargo.toml that you should use +* Always consider the developer experience: + * Am I placing a burden on the developer with this change? + * Is it as easy to use / execute / import / configure as possible? +* When making _any_ changes: + * Consider the impact on other parts of the codebase + * What tests, documentation, etc. needs to be updated? + * Search for other files that should be changed after what you just did + * How has the context changed now that I've made this change? + * Should I refactor the code to introduce an abstraction to make it more + maintainable? + * Should I delete anything that's now unused? +* Check your work after you finish a task: + * Did I address everything I was asked to? + * Run `npm run standards` (or `tsc` / `eslint` / `commitlint` / `markdownlint` / + `cargo lint-clippy && cargo lint-fmt` as appropriate) + * Test significant changes by: + * Running the tests + * Running the app and manually testing the changes (Tauri MCP/CLI or Playwright MCP/CLI) + +## Naming Conventions (General) + +* Use PascalCase for classes +* Use camelCase for variables, instance functions, and methods +* Use snake_case for static functions +* Use kebab-case for file and directory names +* Use UPPERCASE for environment variables +* Files exporting classes: PascalCase.js (e.g., `User.js`) +* Files exporting functions/objects: kebab-case.js (e.g., `my-function.js`) +* Tests: `ClassTheyAreTesting.test.js` +* Avoid magic numbers and define constants +* When it has an acronym or initialism, use all lowercase or all caps, never mixed-case: + * `url` or `URL`, _never_ `Url` + * `id` or `ID`, _never_ `Id`. Prefer `ID` over `id` when writing docs/sentences + unless documenting a third-party entity or when specifically referring to a code + object with that exact casing (parameter, variable, etc.) + +## Formatting Rules + +### Indentation + +* **3 spaces** (never tabs) +* Wrapped lines: indent one level from first line +* Chained functions: indent one level from chain start + +### Braces & Structure + +* Opening brace at end of line (K&R style) +* Always use braces for conditionals/loops (even single line) +* One blank line between unrelated statements +* One-two blank lines between functions + +### Spacing & Operators + +* Space after control structures: `if (condition)` +* No space between function name and parenthesis: `myFunction()` +* No space between `catch` and parentheses: `catch(error) {` +* Space around operators (except unary: `!`, `++`, `--`) +* Space after commas in arrays/arguments +* Spaces inside array brackets: `[ 'item' ]` and object braces: `{ key: 'value' }` +* Empty arrays/objects: no spaces (`[]`, `{}`) +* Multi-line arrays/objects: always trailing comma + +## Control Structures + +* Avoid deep nesting (low cyclomatic complexity) +* Most common case in `if` (not `else`) +* Use positive logic over negative +* Break complex conditions into variables/functions +* Check error conditions early with early returns +* Do not add defensive empty checks before operations that naturally handle empty inputs + +## Variable Best Practices + +* Declare in lowest possible scope +* Declare at top of scope before statements +* Initialized variables before uninitialized +* Avoid modifying input parameters (except immediate sanitization) +* Always sanitize user input +* Prefer immutability (`readonly`, `as const`, `const`) + +## Function Documentation + +* Document the function purpose in JSDoc format +* Document types or parameters if they are not obvious from the code +* Omit JSDoc entirely when the function name already conveys its purpose + (e.g., do not add `/** Creates the foo */` to `createFoo()`) +* Add JSDoc comments to enum values when the name alone doesn't convey the + domain-specific meaning + +## Comments + +* Only add a comment if: + * The code's rationale is not obvious from naming/context + * The comment answers "why," NOT "what" or "how" + * The surrounding code uses comments in a similar way +* Do not comment on types, parameters, or usage that are clear from code or naming +* Use ASCII in comments, never unicode symbols + +## File Standards + +* End with newline character (not blank line) +* No Windows line endings +* No commented-out code without reason +* Ternary operator only for simple conditions + +## Front-End Development + +* Pay attention to the current version of the component, and use a similar pattern as + set by existing elements +* Consider accessibility / a11y +* Create reusable components rather than ad-hoc solutions + +## Library Usage + +* Use existing libraries to the fullest extent possible +* Always verify function signatures before using + +## css-scss + +* Always use SCSS, not CSS +* If using a component library, use the component's existing props or built-in options + over custom styling. Reuse appropriate CSS classes +* If none are available, prefer pre-existing utility classes over custom styling +* Avoid ad-hoc CSS unless absolutely necessary +* Consider adding a custom utility class to the global SCSS if a pattern is used in + multiple places (e.g. text truncation, screen reader text, grid patterns) +* Use CSS logical properties: `margin-inline-start`, not `margin-left`; + `text-align: start`, not `text-align: left` +* Use CSS variables for theme-able values + +## ABSOLUTELY DO NOT + +* DO NOT use `@extend` +* DO NOT override `line-height` to values other than `1` unless you have a very good + reason + +## SCSS API + +If the project auto-injects SCSS namespaces via its build config (e.g. Vite's +`css.preprocessorOptions`), these are generally libraries and you should prefer these +mixins/vars/functions over hard-coded values. + +Check the project's `vite.config.ts` or equivalent to see what is available. + +## Style Block Structure + +Vue components have **two** optional ` + + +``` + +Usage: + +```ts +import { MyComponentProps }, MyComponent from './MyComponent.vue'; +``` + +## Props + +### TypeScript interface props (preferred) + +Define a TypeScript interface in the non-setup ` + + +``` + +### Discriminated union props + +When a component has mutually-exclusive prop combinations, define separate interfaces and +combine them with a union type. Use `never` to exclude invalid combinations: + +```ts +export interface IconOnlyButtonProps extends BaseButtonProps { + icon: IconName; + label?: never; + ariaLabel: string; // Required when no visible label +} + +export interface LabelOnlyButtonProps extends BaseButtonProps { + icon?: never; + label: string; + ariaLabel?: string; +} + +export type ButtonProps = IconOnlyButtonProps | LabelOnlyButtonProps; +``` + +### `defineModel` + +For v-model bindings, use `defineModel` (Vue 3.4+): + +```ts +const modelValue = defineModel({ default: false }); +``` + +In the template: + +```vue + +``` + +## Emits + +Unlike props and slots interfaces, emits may be defined inline: + +```ts +defineEmits<{ + 'update:selected': [value: boolean]; + select: [event: Event]; +}>(); +``` + +## Slots + +Define slot types in the non-setup ` + + +``` + +### Slot props + +Pass CSS classes and internal state to slot consumers via `v-bind`: + +```vue + + + + + +

{{ title }}

+
+``` + +### Checking slot existence + +Use `useSlots()` or `$slots` to conditionally render wrapper elements: + +```ts +const slots = useSlots(); +``` + +```vue +
+ +
+``` + +## VueUse + +Use `@vueuse/core` (and `@vueuse/components` if installed) instead of raw browser APIs. +VueUse composables handle lifecycle cleanup automatically and are SSR-safe. + +| Instead of | Use | +| --- | --- | +| `addEventListener` / `removeEventListener` | `useEventListener()` | +| `setTimeout` / `clearTimeout` | `useTimeoutFn()` | +| `setInterval` / `clearInterval` | `useIntervalFn()` | +| `new ResizeObserver()` | `useResizeObserver()` | +| `new IntersectionObserver()` | `useIntersectionObserver()` | +| `window.matchMedia()` | `useMediaQuery()` | +| Manual scroll position tracking | `useScroll()` | +| `lodash.debounce` / hand-rolled debounce | `useDebounceFn()` | +| `getComputedStyle` / manual CSS var reads | `useCssVar()` | + +Other commonly used composables: `onClickOutside()`, `useElementSize()`, `useFocusTrap()`, +`useVModel()`. + +## Template Refs + +Use `useTemplateRef` (Vue 3.5+): + +```ts +const thumbnail = useTemplateRef('thumbnail'); +``` + +## Component Composition + +### Dynamic element rendering + +Use `` to switch between element types based on props: + +```ts +const elementType = computed(() => { + return props.href ? 'a' : 'button'; +}); +``` + +```vue + + + +``` + +If using a headless component library (e.g. Reka UI, Radix Vue), consider its +`` component for root-level elements — it provides `asChild` for composability +without extra wrapper elements in the DOM. + +### `inheritAttrs` + +When a component needs manual control over where `$attrs` are applied, disable automatic +attribute inheritance and spread attrs explicitly: + +```ts +defineOptions({ inheritAttrs: false }); +``` + +```vue + +``` + +## Exports + +### Component index + +Every component should be exported from the component index with both a named default +export and a wildcard re-export (for types): + +```ts +export * from './Button.vue'; +export { default as Button } from './Button.vue'; +``` + +### Type exports + +Export all public interfaces, type aliases, and constants from the non-setup ` + + + + + + +``` + +Usage: + +```ts +import { MyComponentProps }, MyComponent from './MyComponent.vue'; +``` + +## Props + +### TypeScript interface props (preferred) + +Define a TypeScript interface in the non-setup ` + + +``` + +### Discriminated union props + +When a component has mutually-exclusive prop combinations, define separate interfaces and +combine them with a union type. Use `never` to exclude invalid combinations: + +```ts +export interface IconOnlyButtonProps extends BaseButtonProps { + icon: IconName; + label?: never; + ariaLabel: string; // Required when no visible label +} + +export interface LabelOnlyButtonProps extends BaseButtonProps { + icon?: never; + label: string; + ariaLabel?: string; +} + +export type ButtonProps = IconOnlyButtonProps | LabelOnlyButtonProps; +``` + +### `defineModel` + +For v-model bindings, use `defineModel` (Vue 3.4+): + +```ts +const modelValue = defineModel({ default: false }); +``` + +In the template: + +```vue + +``` + +## Emits + +Unlike props and slots interfaces, emits may be defined inline: + +```ts +defineEmits<{ + 'update:selected': [value: boolean]; + select: [event: Event]; +}>(); +``` + +## Slots + +Define slot types in the non-setup ` + + +``` + +### Slot props + +Pass CSS classes and internal state to slot consumers via `v-bind`: + +```vue + + + + + +

{{ title }}

+
+``` + +### Checking slot existence + +Use `useSlots()` or `$slots` to conditionally render wrapper elements: + +```ts +const slots = useSlots(); +``` + +```vue +
+ +
+``` + +## VueUse + +Use `@vueuse/core` (and `@vueuse/components` if installed) instead of raw browser APIs. +VueUse composables handle lifecycle cleanup automatically and are SSR-safe. + +| Instead of | Use | +| --- | --- | +| `addEventListener` / `removeEventListener` | `useEventListener()` | +| `setTimeout` / `clearTimeout` | `useTimeoutFn()` | +| `setInterval` / `clearInterval` | `useIntervalFn()` | +| `new ResizeObserver()` | `useResizeObserver()` | +| `new IntersectionObserver()` | `useIntersectionObserver()` | +| `window.matchMedia()` | `useMediaQuery()` | +| Manual scroll position tracking | `useScroll()` | +| `lodash.debounce` / hand-rolled debounce | `useDebounceFn()` | +| `getComputedStyle` / manual CSS var reads | `useCssVar()` | + +Other commonly used composables: `onClickOutside()`, `useElementSize()`, `useFocusTrap()`, +`useVModel()`. + +## Template Refs + +Use `useTemplateRef` (Vue 3.5+): + +```ts +const thumbnail = useTemplateRef('thumbnail'); +``` + +## Component Composition + +### Dynamic element rendering + +Use `` to switch between element types based on props: + +```ts +const elementType = computed(() => { + return props.href ? 'a' : 'button'; +}); +``` + +```vue + + + +``` + +If using a headless component library (e.g. Reka UI, Radix Vue), consider its +`` component for root-level elements — it provides `asChild` for composability +without extra wrapper elements in the DOM. + +### `inheritAttrs` + +When a component needs manual control over where `$attrs` are applied, disable automatic +attribute inheritance and spread attrs explicitly: + +```ts +defineOptions({ inheritAttrs: false }); +``` + +```vue + +``` + +## Exports + +### Component index + +Every component should be exported from the component index with both a named default +export and a wildcard re-export (for types): + +```ts +export * from './Button.vue'; +export { default as Button } from './Button.vue'; +``` + +### Type exports + +Export all public interfaces, type aliases, and constants from the non-setup `