diff --git a/.agents/justifications/.gitkeep b/.agents/justifications/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/.agents/plans/.gitkeep b/.agents/plans/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/.agents/scratches/.gitkeep b/.agents/scratches/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/.opencode/commands/commit.md b/.opencode/commands/commit.md new file mode 100644 index 000000000..d4382f18c --- /dev/null +++ b/.opencode/commands/commit.md @@ -0,0 +1,80 @@ +--- +description: Add and commit changes using conventional commits +allowed-tools: Bash, Read, Glob, Grep +--- + +Create a git commit for the current changes using the Conventional Commits standard. + +## Process + +1. **Analyze the changes** by running: + - `git status` to see all modified/untracked files + - `git diff` to see unstaged changes + - `git diff --staged` to see already-staged changes + - `git log --oneline -5` to see recent commit style + +2. **Stage appropriate files**: + - Stage all related changes with `git add` + - Do NOT stage files that appear to contain secrets (.env, credentials, API keys, tokens) + - If you detect potential secrets, warn the user and skip those files + +3. **Determine the commit type** based on the changes: + - `feat`: New feature or capability + - `fix`: Bug fix + - `docs`: Documentation only + - `style`: Formatting, whitespace (not CSS) + - `refactor`: Code restructuring without behavior change + - `perf`: Performance improvement + - `test`: Adding or updating tests + - `build`: Build system or dependencies + - `ci`: CI/CD configuration + - `chore`: Maintenance tasks, tooling, config + + NOTE: Do not use a scope for commits + +4. **Write the commit message**: + - **Subject line**: `: ` + - Use imperative mood ("add" not "added") + - Lowercase, no period at end + - Max 50 characters if possible, 72 hard limit + - **Body** (if needed): Explain _why_, not _what_ + - Wrap at 72 characters + - Separate from subject with blank line + +## Commit Format + +``` +[scope]: + +[optional body explaining WHY this change was made] +``` + +## Examples + +Simple change: + +``` +fix: handle empty input in parser without throwing +``` + +With body: + +``` +feat: add streaming response support + +Large responses were causing memory issues in production. +Streaming allows processing chunks incrementally. +``` + +## Rules + +- NEVER commit files that may contain secrets +- NEVER use `git commit --amend` unless the user explicitly requests it +- NEVER use `--no-verify` to skip hooks +- If the pre-commit hook fails, fix the issues and create a NEW commit +- If there are no changes to commit, inform the user and stop +- Use a HEREDOC to pass the commit message to ensure proper formatting + +## Execute + +Run the git commands to analyze, stage, and commit the changes now. diff --git a/.opencode/commands/continue.md b/.opencode/commands/continue.md new file mode 100644 index 000000000..bedc43c33 --- /dev/null +++ b/.opencode/commands/continue.md @@ -0,0 +1,112 @@ +--- +description: Continue implementing a spec from a previous session +argument-hint: +--- + +You are continuing implementation of a specification that was started in a previous session. Work autonomously until the feature is complete and tests pass. + +## Your Task + +1. **Read the spec** at `$ARGUMENTS` +2. **Read CODE_STYLE.md** for formatting conventions +3. **Assess current state**: + - Check git status for uncommitted changes + - Run tests to see what's passing/failing (if E2E tests exist) + - Review any existing implementation +4. **Determine what remains** by comparing the spec to the current state +5. **Plan remaining work** using TodoWrite +6. **Continue implementing** until complete + +## Assessing Current State + +Run these commands to understand where the previous session left off: + +```bash +git status # See uncommitted changes +git log --oneline -10 # See recent commits +npm run typecheck -w @documenso/remix # Check for type errors +npm run lint:fix # Check for linting issues +``` + +Review the code that's already been written to understand: + +- What's already implemented +- What's partially done +- What's not started yet + +## Implementation Guidelines + +### During Implementation + +- Follow CODE_STYLE.md strictly (2-space indent, double quotes, braces always, etc.) +- Follow workspace rules for TypeScript, React, TRPC patterns, and Remix conventions +- Mark todos complete as you finish each task +- Commit logical chunks of work + +### Code Quality + +- No stubbed implementations +- Handle edge cases and error conditions +- Include descriptive error messages with context +- Use async/await for all I/O operations +- Use AppError class when throwing errors +- Use Zod for validation and react-hook-form for forms + +### Testing + +**Important**: E2E tests are time-consuming. Only write tests for non-trivial functionality. + +- Write E2E tests in `packages/app-tests/e2e/` using Playwright +- Test critical user flows and edge cases +- Follow existing E2E test patterns in the codebase +- Use descriptive test names that explain what is being tested +- Skip tests for trivial changes (simple UI tweaks, minor refactors, etc.) + +## Autonomous Workflow + +Work continuously through these steps: + +1. **Implement** - Write the code for the current task +2. **Typecheck** - Run `npm run typecheck -w @documenso/remix` to verify types +3. **Lint** - Run `npm run lint:fix` to fix linting issues +4. **Test** - If non-trivial, run E2E tests: `npm run test:dev -w @documenso/app-tests` +5. **Fix** - If tests fail, fix and re-run +6. **Repeat** - Move to next task + +## Stopping Conditions + +**Stop and report success when:** + +- All spec requirements are implemented +- Typecheck passes +- Lint passes +- E2E tests pass (if written for non-trivial functionality) + +**Stop and ask for help when:** + +- The spec is ambiguous and you need clarification +- You encounter a blocking issue you cannot resolve +- You need to make a decision that significantly deviates from the spec +- External dependencies are missing + +## Commands + +```bash +# Type checking +npm run typecheck -w @documenso/remix + +# Linting +npm run lint:fix + +# E2E Tests (only for non-trivial work) +npm run test:dev -w @documenso/app-tests # Run E2E tests in dev mode +npm run test-ui:dev -w @documenso/app-tests # Run E2E tests with UI +npm run test:e2e # Run full E2E test suite + +# Development +npm run dev # Start dev server +``` + +## Begin + +Read the spec file and CODE_STYLE.md, assess the current implementation state, then continue where the previous session left off. Use TodoWrite to track your progress throughout. diff --git a/.opencode/commands/create-justification.md b/.opencode/commands/create-justification.md new file mode 100644 index 000000000..f36a04f15 --- /dev/null +++ b/.opencode/commands/create-justification.md @@ -0,0 +1,75 @@ +--- +description: Create a new justification file in .agents/justifications/ +argument-hint: [content] +--- + +You are creating a new justification file in the `.agents/justifications/` directory. + +## Your Task + +1. **Determine the slug** - Use `$ARGUMENTS` as the file slug (kebab-case recommended) +2. **Gather content** - Collect or generate the justification content +3. **Create the file** - Use the create-justification script to generate the file + +## Usage + +The script will automatically: +- Generate a unique three-word ID (e.g., `swift-emerald-river`) +- Create frontmatter with current date and formatted title +- Save the file as `{id}-{slug}.md` in `.agents/justifications/` + +## Creating the File + +### Option 1: Direct Content + +If you have the content ready, run: + +```bash +npx tsx scripts/create-justification.ts "$ARGUMENTS" "Your justification content here" +``` + +### Option 2: Multi-line Content (Heredoc) + +For multi-line content, use heredoc: + +```bash +npx tsx scripts/create-justification.ts "$ARGUMENTS" << HEREDOC +Your multi-line +justification content +goes here +HEREDOC +``` + +### Option 3: Pipe Content + +You can also pipe content: + +```bash +echo "Your content" | npx tsx scripts/create-justification.ts "$ARGUMENTS" +``` + +## File Format + +The created file will have: + +```markdown +--- +date: 2026-01-13 +title: Justification Title +--- + +Your content here +``` + +The title is automatically formatted from the slug (e.g., `architecture-decision` → `Architecture Decision`). + +## Guidelines + +- Use descriptive slugs in kebab-case (e.g., `tech-stack-choice`, `api-design-rationale`) +- Include clear reasoning and context for the decision +- The unique ID ensures no filename conflicts +- Files are automatically dated for organization + +## Begin + +Create a justification file using the slug from `$ARGUMENTS` and appropriate content documenting the reasoning or justification. diff --git a/.opencode/commands/create-plan.md b/.opencode/commands/create-plan.md new file mode 100644 index 000000000..cde144440 --- /dev/null +++ b/.opencode/commands/create-plan.md @@ -0,0 +1,76 @@ +--- +description: Create a new plan file in .agents/plans/ +argument-hint: [content] +--- + +You are creating a new plan file in the `.agents/plans/` directory. + +## Your Task + +1. **Determine the slug** - Use `$ARGUMENTS` as the file slug (kebab-case recommended) +2. **Gather content** - Collect or generate the plan content +3. **Create the file** - Use the create-plan script to generate the file + +## Usage + +The script will automatically: + +- Generate a unique three-word ID (e.g., `happy-blue-moon`) +- Create frontmatter with current date and formatted title +- Save the file as `{id}-{slug}.md` in `.agents/plans/` + +## Creating the File + +### Option 1: Direct Content + +If you have the content ready, run: + +```bash +npx tsx scripts/create-plan.ts "$ARGUMENTS" "Your plan content here" +``` + +### Option 2: Multi-line Content (Heredoc) + +For multi-line content, use heredoc: + +```bash +npx tsx scripts/create-plan.ts "$ARGUMENTS" << HEREDOC +Your multi-line +plan content +goes here +HEREDOC +``` + +### Option 3: Pipe Content + +You can also pipe content: + +```bash +echo "Your content" | npx tsx scripts/create-plan.ts "$ARGUMENTS" +``` + +## File Format + +The created file will have: + +```markdown +--- +date: 2026-01-13 +title: Plan Title +--- + +Your content here +``` + +The title is automatically formatted from the slug (e.g., `my-feature` → `My Feature`). + +## Guidelines + +- Use descriptive slugs in kebab-case (e.g., `user-authentication`, `api-integration`) +- Include clear, actionable plan content +- The unique ID ensures no filename conflicts +- Files are automatically dated for organization + +## Begin + +Create a plan file using the slug from `$ARGUMENTS` and appropriate content for the planning task. diff --git a/.opencode/commands/create-scratch.md b/.opencode/commands/create-scratch.md new file mode 100644 index 000000000..1f4138944 --- /dev/null +++ b/.opencode/commands/create-scratch.md @@ -0,0 +1,75 @@ +--- +description: Create a new scratch file in .agents/scratches/ +argument-hint: [content] +--- + +You are creating a new scratch file in the `.agents/scratches/` directory. + +## Your Task + +1. **Determine the slug** - Use `$ARGUMENTS` as the file slug (kebab-case recommended) +2. **Gather content** - Collect or generate the scratch content +3. **Create the file** - Use the create-scratch script to generate the file + +## Usage + +The script will automatically: +- Generate a unique three-word ID (e.g., `calm-teal-cloud`) +- Create frontmatter with current date and formatted title +- Save the file as `{id}-{slug}.md` in `.agents/scratches/` + +## Creating the File + +### Option 1: Direct Content + +If you have the content ready, run: + +```bash +npx tsx scripts/create-scratch.ts "$ARGUMENTS" "Your scratch content here" +``` + +### Option 2: Multi-line Content (Heredoc) + +For multi-line content, use heredoc: + +```bash +npx tsx scripts/create-scratch.ts "$ARGUMENTS" << HEREDOC +Your multi-line +scratch content +goes here +HEREDOC +``` + +### Option 3: Pipe Content + +You can also pipe content: + +```bash +echo "Your content" | npx tsx scripts/create-scratch.ts "$ARGUMENTS" +``` + +## File Format + +The created file will have: + +```markdown +--- +date: 2026-01-13 +title: Scratch Title +--- + +Your content here +``` + +The title is automatically formatted from the slug (e.g., `quick-notes` → `Quick Notes`). + +## Guidelines + +- Use descriptive slugs in kebab-case (e.g., `exploration-ideas`, `temporary-notes`) +- Scratch files are for temporary notes, explorations, or ideas +- The unique ID ensures no filename conflicts +- Files are automatically dated for organization + +## Begin + +Create a scratch file using the slug from `$ARGUMENTS` and appropriate content for notes or exploration. diff --git a/.opencode/commands/document.md b/.opencode/commands/document.md new file mode 100644 index 000000000..72ca40259 --- /dev/null +++ b/.opencode/commands/document.md @@ -0,0 +1,201 @@ +--- +description: Generate MDX documentation for a module or feature +argument-hint: +--- + +You are creating proper MDX documentation for a module or feature in Documenso using Nextra. + +## Your Task + +1. **Identify the scope** - What does `$ARGUMENTS` refer to? (file, directory, or feature name) +2. **Read the source code** - Understand the public API, types, and behavior +3. **Read existing docs** - Check if there's documentation to update or reference +4. **Write comprehensive documentation** - Create or update MDX docs in the appropriate location +5. **Update navigation** - Add entry to `_meta.js` if creating a new page + +## Documentation Structure + +Create documentation in the appropriate location: + +- **Developer docs**: `apps/documentation/pages/developers/` +- **User docs**: `apps/documentation/pages/users/` + +### File Format + +All documentation files must be `.mdx` files with frontmatter: + +```mdx +--- +title: Page Title +description: Brief description for SEO and meta tags +--- + +# Page Title + +Content starts here... +``` + +### Navigation + +Each directory should have a `_meta.js` file that defines the navigation structure: + +```javascript +export default { + index: 'Introduction', + 'feature-name': 'Feature Name', + 'another-feature': 'Another Feature', +}; +``` + +If creating a new page, add it to the appropriate `_meta.js` file. + +### Documentation Format + +````mdx +--- +title: +description: Brief description of what this does and when to use it +--- + +# + +Brief description of what this module/feature does and when to use it. + +## Installation + +If there are specific packages or imports needed: + +```bash +npm install @documenso/package-name +``` + +## Quick Start + +```jsx +// Minimal working example +import { Component } from '@documenso/package'; + +const Example = () => { + return ; +}; +``` + +## API Reference + +### Component/Function Name + +Description of what it does. + +#### Props/Parameters + +| Prop/Param | Type | Description | +| ---------- | -------------------- | ------------------------- | +| prop | `string` | Description of the prop | +| optional | `boolean` (optional) | Optional prop description | + +#### Example + +```jsx +import { Component } from '@documenso/package'; + +; +``` + +### Types + +#### `TypeName` + +```typescript +type TypeName = { + property: string; + optional?: boolean; +}; +``` + +## Examples + +### Common Use Case + +```jsx +// Full working example +``` + +### Advanced Usage + +```jsx +// More complex example +``` + +## Related + +- [Link to related documentation](/developers/path) +- [Another related page](/users/path) +```` + +## Guidelines + +### Content Quality + +- **Be accurate** - Verify behavior by reading the code +- **Be complete** - Document all public API surface +- **Be practical** - Include real, working examples +- **Be concise** - Don't over-explain obvious things +- **Be user-focused** - Write for the target audience (developers or users) + +### Code Examples + +- Use appropriate language tags: `jsx`, `tsx`, `typescript`, `bash`, `json` +- Show imports when not obvious +- Include expected output in comments where helpful +- Progress from simple to complex +- Use real examples from the codebase when possible + +### Formatting + +- Always include frontmatter with `title` and `description` +- Use proper markdown headers (h1 for title, h2 for sections) +- Use tables for props/parameters documentation (matching existing style) +- Use code fences with appropriate language tags +- Use Nextra components when appropriate: + - `` for notes + - `` for step-by-step instructions +- Use relative links for internal documentation (e.g., `/developers/embedding/react`) + +### Nextra Components + +You can import and use Nextra components: + +```jsx +import { Callout, Steps } from 'nextra/components'; + + + This is an informational note. + + + + First step + Second step + +``` + +### Maintenance + +- Include types inline so docs don't get stale +- Reference source file locations for complex behavior +- Keep examples up-to-date with the codebase +- Update `_meta.js` when adding new pages + +## Process + +1. **Explore the code** - Read source files to understand the API +2. **Identify the audience** - Is this for developers or users? +3. **Check existing docs** - Look for similar pages to match style +4. **Draft the structure** - Outline sections before writing +5. **Write content** - Fill in each section with frontmatter +6. **Add examples** - Create working code samples +7. **Update navigation** - Add to `_meta.js` if needed +8. **Review** - Read through for clarity and accuracy + +## Begin + +Analyze `$ARGUMENTS`, read the relevant source code, check existing documentation patterns, and create comprehensive MDX documentation following the Documenso documentation style. diff --git a/.opencode/commands/implement.md b/.opencode/commands/implement.md new file mode 100644 index 000000000..e9ac84f8c --- /dev/null +++ b/.opencode/commands/implement.md @@ -0,0 +1,100 @@ +--- +description: Implement a spec from the plans directory +argument-hint: +--- + +You are implementing a specification from the `.agents/plans/` directory. Work autonomously until the feature is complete and tests pass. + +## Your Task + +1. **Read the spec** at `$ARGUMENTS` +2. **Read CODE_STYLE.md** for formatting conventions +3. **Plan the implementation** using the TodoWrite tool to break down the work +4. **Implement the feature** following the spec and code style +5. **Write E2E tests** only for non-trivial functionality (E2E tests are time-consuming) +6. **Run tests** and fix any failures +7. **Run typecheck and lint** and fix any issues + +## Implementation Guidelines + +### Before Coding + +- Understand the spec's goals and scope +- Identify the desired API from usage examples in the spec +- Review related existing code to understand patterns +- Break the work into discrete tasks using TodoWrite + +### During Implementation + +- Follow CODE_STYLE.md strictly (2-space indent, double quotes, braces always, etc.) +- Follow workspace rules for TypeScript, React, TRPC patterns, and Remix conventions +- Mark todos complete as you finish each task +- Commit logical chunks of work + +### Code Quality + +- No stubbed implementations +- Handle edge cases and error conditions +- Include descriptive error messages with context +- Use async/await for all I/O operations +- Use AppError class when throwing errors +- Use Zod for validation and react-hook-form for forms + +### Testing + +**Important**: E2E tests are time-consuming. Only write tests for non-trivial functionality. + +- Write E2E tests in `packages/app-tests/e2e/` using Playwright +- Test critical user flows and edge cases +- Follow existing E2E test patterns in the codebase +- Use descriptive test names that explain what is being tested +- Skip tests for trivial changes (simple UI tweaks, minor refactors, etc.) + +## Autonomous Workflow + +Work continuously through these steps: + +1. **Implement** - Write the code for the current task +2. **Typecheck** - Run `npm run typecheck -w @documenso/remix` to verify types +3. **Lint** - Run `npm run lint:fix` to fix linting issues +4. **Test** - If non-trivial, run E2E tests: `npm run test:dev -w @documenso/app-tests` +5. **Fix** - If tests fail, fix and re-run +6. **Repeat** - Move to next task + +## Stopping Conditions + +**Stop and report success when:** + +- All spec requirements are implemented +- Typecheck passes +- Lint passes +- E2E tests pass (if written for non-trivial functionality) + +**Stop and ask for help when:** + +- The spec is ambiguous and you need clarification +- You encounter a blocking issue you cannot resolve +- You need to make a decision that significantly deviates from the spec +- External dependencies are missing + +## Commands + +```bash +# Type checking +npm run typecheck -w @documenso/remix + +# Linting +npm run lint:fix + +# E2E Tests (only for non-trivial work) +npm run test:dev -w @documenso/app-tests # Run E2E tests in dev mode +npm run test-ui:dev -w @documenso/app-tests # Run E2E tests with UI +npm run test:e2e # Run full E2E test suite + +# Development +npm run dev # Start dev server +``` + +## Begin + +Read the spec file and CODE_STYLE.md, then start implementing. Use TodoWrite to track your progress throughout. diff --git a/.opencode/commands/interview.md b/.opencode/commands/interview.md new file mode 100644 index 000000000..e84e8f37d --- /dev/null +++ b/.opencode/commands/interview.md @@ -0,0 +1,57 @@ +--- +description: Deep-dive interview to flesh out a spec or design document +agent: build +argument-hint: +--- + +You are conducting a thorough interview to help flesh out and complete a specification or design document. + +## Your Task + +1. **Read the document** at `$ARGUMENTS` +2. **Analyze it deeply** - identify gaps, ambiguities, unexplored edge cases, and areas needing clarification +3. **Interview the user** by providing a question with some pre-determined options +4. **Write the completed spec** back to the file when the interview is complete + +## Interview Guidelines + +### Question Quality +- Ask **non-obvious, insightful questions** - avoid surface-level queries +- Focus on: technical implementation details, architectural decisions, edge cases, error handling, UX implications, security considerations, performance tradeoffs, integration points, migration strategies, rollback plans +- Each question should reveal something that would otherwise be missed +- Challenge assumptions embedded in the document +- Explore second and third-order consequences of design decisions +- Use the Web Search and other tools where required to ground questions (e.g. package recommendations) + +### Question Strategy +- Start by identifying the 3-5 most critical unknowns or ambiguities +- Use the AskUserQuestion tool with well-crafted options that represent real tradeoffs +- When appropriate, offer multiple valid approaches with their pros/cons as options +- Don't ask about things that are already clearly specified +- Probe deeper when answers reveal new areas of uncertainty + +### Topics to Explore (as relevant) +- **Technical**: Data models, API contracts, state management, concurrency, caching, validation +- **UX**: Error states, loading states, empty states, edge cases, accessibility, mobile considerations +- **Operations**: Deployment, monitoring, alerting, debugging, logging, feature flags +- **Security**: Auth, authz, input validation, rate limiting, audit trails +- **Scale**: Performance bottlenecks, data growth, traffic spikes, graceful degradation +- **Integration**: Dependencies, backwards compatibility, versioning, migration path +- **Failure modes**: What happens when X fails? How do we recover? What's the blast radius? + +### Interview Flow +1. Ask 2-4 questions at a time (use multiple questions in one when they're related) +2. After each round, incorporate answers and identify follow-up questions +3. Continue until all critical areas are addressed +4. Signal when you believe the interview is complete, but offer to go deeper + +## Output + +When the interview is complete: +1. Synthesize all gathered information +2. Rewrite/expand the original document with the new details +3. Preserve the document's original structure where sensible, but reorganize if needed +4. Add new sections for areas that weren't originally covered +5. Write the completed spec back to `$ARGUMENTS` + +Begin by reading the file and identifying your first set of deep questions. diff --git a/.opencode/skill/create-justification/SKILL.md b/.opencode/skill/create-justification/SKILL.md new file mode 100644 index 000000000..78a2aaea9 --- /dev/null +++ b/.opencode/skill/create-justification/SKILL.md @@ -0,0 +1,56 @@ +--- +name: create-justification +description: Create a new justification file in .agents/justifications/ with a unique three-word ID, frontmatter, and formatted title +license: MIT +compatibility: opencode +metadata: + audience: agents + workflow: decision-making +--- + +## What I do + +I help you create new justification files in the `.agents/justifications/` directory. Each justification file gets: + +- A unique three-word identifier (e.g., `swift-emerald-river`) +- Frontmatter with the current date and formatted title +- Content you provide + +## How to use + +Run the script with a slug and content: + +```bash +npx tsx scripts/create-justification.ts "decision-name" "Justification content here" +``` + +Or use heredoc for multi-line content: + +```bash +npx tsx scripts/create-justification.ts "decision-name" << HEREDOC +Multi-line +justification content +goes here +HEREDOC +``` + +## File format + +Files are created as: `{three-word-id}-{slug}.md` + +Example: `swift-emerald-river-decision-name.md` + +The file includes frontmatter: + +```markdown +--- +date: 2026-01-13 +title: Decision Name +--- + +Your content here +``` + +## When to use me + +Use this skill when you need to document the reasoning or justification for a decision, approach, or architectural choice. The unique ID ensures no filename conflicts, and the frontmatter provides metadata for organization. diff --git a/.opencode/skill/create-plan/SKILL.md b/.opencode/skill/create-plan/SKILL.md new file mode 100644 index 000000000..8ceb2ef8c --- /dev/null +++ b/.opencode/skill/create-plan/SKILL.md @@ -0,0 +1,56 @@ +--- +name: create-plan +description: Create a new plan file in .agents/plans/ with a unique three-word ID, frontmatter, and formatted title +license: MIT +compatibility: opencode +metadata: + audience: agents + workflow: planning +--- + +## What I do + +I help you create new plan files in the `.agents/plans/` directory. Each plan file gets: + +- A unique three-word identifier (e.g., `happy-blue-moon`) +- Frontmatter with the current date and formatted title +- Content you provide + +## How to use + +Run the script with a slug and content: + +```bash +npx tsx scripts/create-plan.ts "feature-name" "Plan content here" +``` + +Or use heredoc for multi-line content: + +```bash +npx tsx scripts/create-plan.ts "feature-name" << HEREDOC +Multi-line +plan content +goes here +HEREDOC +``` + +## File format + +Files are created as: `{three-word-id}-{slug}.md` + +Example: `happy-blue-moon-feature-name.md` + +The file includes frontmatter: + +```markdown +--- +date: 2026-01-13 +title: Feature Name +--- + +Your content here +``` + +## When to use me + +Use this skill when you need to create a new plan document for a feature, task, or project. The unique ID ensures no filename conflicts, and the frontmatter provides metadata for organization. diff --git a/.opencode/skill/create-scratch/SKILL.md b/.opencode/skill/create-scratch/SKILL.md new file mode 100644 index 000000000..e44e4779d --- /dev/null +++ b/.opencode/skill/create-scratch/SKILL.md @@ -0,0 +1,56 @@ +--- +name: create-scratch +description: Create a new scratch file in .agents/scratches/ with a unique three-word ID, frontmatter, and formatted title +license: MIT +compatibility: opencode +metadata: + audience: agents + workflow: exploration +--- + +## What I do + +I help you create new scratch files in the `.agents/scratches/` directory. Each scratch file gets: + +- A unique three-word identifier (e.g., `calm-teal-cloud`) +- Frontmatter with the current date and formatted title +- Content you provide + +## How to use + +Run the script with a slug and content: + +```bash +npx tsx scripts/create-scratch.ts "note-name" "Scratch content here" +``` + +Or use heredoc for multi-line content: + +```bash +npx tsx scripts/create-scratch.ts "note-name" << HEREDOC +Multi-line +scratch content +goes here +HEREDOC +``` + +## File format + +Files are created as: `{three-word-id}-{slug}.md` + +Example: `calm-teal-cloud-note-name.md` + +The file includes frontmatter: + +```markdown +--- +date: 2026-01-13 +title: Note Name +--- + +Your content here +``` + +## When to use me + +Use this skill when you need to create a temporary note, exploration document, or scratch pad for ideas. The unique ID ensures no filename conflicts, and the frontmatter provides metadata for organization. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe2bb8304..501458cd4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,3 +52,53 @@ You can build the project with: ```bash npm run build ``` + +## AI-Assisted Development with OpenCode + +We use [OpenCode](https://opencode.ai) for AI-assisted development. OpenCode provides custom commands and skills to help maintain consistency and streamline common workflows. + +OpenCode works with most major AI providers (Anthropic, OpenAI, Google, etc.) or you can use [Zen](https://opencode.ai/zen) for optimized coding models. Configure your preferred provider in the OpenCode settings. + +> **Important**: All AI-generated code must be thoroughly reviewed by the contributor before submitting a PR. You are responsible for understanding and validating every line of code you submit. If we detect that contributors are simply throwing AI-generated code over the wall without proper review, they will be blocked from the repository. + +### Getting Started + +1. Install OpenCode (see [opencode.ai](https://opencode.ai) for other install methods): + ```bash + curl -fsSL https://opencode.ai/install | bash + ``` +2. Configure your AI provider (or use Zen for optimized models) +3. Run `opencode` in the project root + +### Available Commands + +Use these commands in OpenCode by typing the command name: + +| Command | Description | +| ------------------------------ | -------------------------------------------------------- | +| `/implement ` | Implement a spec from `.agents/plans/` autonomously | +| `/continue ` | Continue implementing a spec from a previous session | +| `/interview ` | Deep-dive interview to flesh out a spec or design | +| `/document ` | Generate MDX documentation for a module or feature | +| `/commit` | Create a conventional commit for staged changes | +| `/create-plan ` | Create a new plan file in `.agents/plans/` | +| `/create-scratch ` | Create a scratch file for notes in `.agents/scratches/` | +| `/create-justification ` | Create a justification file in `.agents/justifications/` | + +### Typical Workflow + +1. **Create a plan**: Use `/create-plan my-feature` to draft a spec for a new feature +2. **Flesh out the spec**: Use `/interview .agents/plans/.md` to refine requirements +3. **Implement**: Use `/implement .agents/plans/.md` to build the feature +4. **Continue if needed**: Use `/continue .agents/plans/.md` to pick up where you left off +5. **Commit**: Use `/commit` to create a conventional commit + +### Agent Files + +The `.agents/` directory stores AI-generated artifacts: + +- **`.agents/plans/`** - Feature specs and implementation plans +- **`.agents/scratches/`** - Temporary notes and explorations +- **`.agents/justifications/`** - Decision rationale and technical justifications + +These files use a unique ID format (`{word}-{word}-{word}-{slug}.md`) to prevent conflicts. diff --git a/scripts/create-justification.ts b/scripts/create-justification.ts new file mode 100644 index 000000000..ea0fd246e --- /dev/null +++ b/scripts/create-justification.ts @@ -0,0 +1,74 @@ +#!/usr/bin/env node +import { readFileSync } from 'fs'; +import { mkdirSync, writeFileSync } from 'fs'; +import { join } from 'path'; + +import { generateId } from './utils/generate-id'; + +const JUSTIFICATIONS_DIR = join(process.cwd(), '.agents', 'justifications'); + +const main = () => { + const args = process.argv.slice(2); + + if (args.length === 0) { + console.error('Usage: npx tsx scripts/create-justification.ts "file-slug" [content]'); + console.error(' or: npx tsx scripts/create-justification.ts "file-slug" << HEREDOC'); + process.exit(1); + } + + const slug = args[0]; + let content = ''; + + // Check if content is provided as second argument + if (args.length > 1) { + content = args.slice(1).join(' '); + } else { + // Read from stdin (heredoc) + try { + const stdin = readFileSync(0, 'utf-8'); + content = stdin.trim(); + } catch (error) { + console.error('Error reading from stdin:', error); + process.exit(1); + } + } + + if (!content) { + console.error('Error: No content provided'); + process.exit(1); + } + + // Generate unique ID + const id = generateId(); + const filename = `${id}-${slug}.md`; + const filepath = join(JUSTIFICATIONS_DIR, filename); + + // Format title from slug (kebab-case to Title Case) + const title = slug + .split('-') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + + // Get current date in ISO format + const date = new Date().toISOString().split('T')[0]; + + // Create frontmatter + const frontmatter = `--- +date: ${date} +title: ${title} +--- + +`; + + // Ensure directory exists + mkdirSync(JUSTIFICATIONS_DIR, { recursive: true }); + + // Write file with frontmatter + writeFileSync(filepath, frontmatter + content, 'utf-8'); + + console.log(`Created justification: ${filepath}`); + console.log(`ID: ${id}`); + console.log(`Filename: ${filename}`); +}; + +main(); diff --git a/scripts/create-plan.ts b/scripts/create-plan.ts new file mode 100644 index 000000000..bf4f3de1d --- /dev/null +++ b/scripts/create-plan.ts @@ -0,0 +1,74 @@ +#!/usr/bin/env node +import { readFileSync } from 'fs'; +import { mkdirSync, writeFileSync } from 'fs'; +import { join } from 'path'; + +import { generateId } from './utils/generate-id'; + +const PLANS_DIR = join(process.cwd(), '.agents', 'plans'); + +const main = () => { + const args = process.argv.slice(2); + + if (args.length === 0) { + console.error('Usage: npx tsx scripts/create-plan.ts "file-slug" [content]'); + console.error(' or: npx tsx scripts/create-plan.ts "file-slug" << HEREDOC'); + process.exit(1); + } + + const slug = args[0]; + let content = ''; + + // Check if content is provided as second argument + if (args.length > 1) { + content = args.slice(1).join(' '); + } else { + // Read from stdin (heredoc) + try { + const stdin = readFileSync(0, 'utf-8'); + content = stdin.trim(); + } catch (error) { + console.error('Error reading from stdin:', error); + process.exit(1); + } + } + + if (!content) { + console.error('Error: No content provided'); + process.exit(1); + } + + // Generate unique ID + const id = generateId(); + const filename = `${id}-${slug}.md`; + const filepath = join(PLANS_DIR, filename); + + // Format title from slug (kebab-case to Title Case) + const title = slug + .split('-') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + + // Get current date in ISO format + const date = new Date().toISOString().split('T')[0]; + + // Create frontmatter + const frontmatter = `--- +date: ${date} +title: ${title} +--- + +`; + + // Ensure directory exists + mkdirSync(PLANS_DIR, { recursive: true }); + + // Write file with frontmatter + writeFileSync(filepath, frontmatter + content, 'utf-8'); + + console.log(`Created plan: ${filepath}`); + console.log(`ID: ${id}`); + console.log(`Filename: ${filename}`); +}; + +main(); diff --git a/scripts/create-scratch.ts b/scripts/create-scratch.ts new file mode 100644 index 000000000..5bb3353e4 --- /dev/null +++ b/scripts/create-scratch.ts @@ -0,0 +1,74 @@ +#!/usr/bin/env node +import { readFileSync } from 'fs'; +import { mkdirSync, writeFileSync } from 'fs'; +import { join } from 'path'; + +import { generateId } from './utils/generate-id'; + +const SCRATCHES_DIR = join(process.cwd(), '.agents', 'scratches'); + +const main = () => { + const args = process.argv.slice(2); + + if (args.length === 0) { + console.error('Usage: npx tsx scripts/create-scratch.ts "file-slug" [content]'); + console.error(' or: npx tsx scripts/create-scratch.ts "file-slug" << HEREDOC'); + process.exit(1); + } + + const slug = args[0]; + let content = ''; + + // Check if content is provided as second argument + if (args.length > 1) { + content = args.slice(1).join(' '); + } else { + // Read from stdin (heredoc) + try { + const stdin = readFileSync(0, 'utf-8'); + content = stdin.trim(); + } catch (error) { + console.error('Error reading from stdin:', error); + process.exit(1); + } + } + + if (!content) { + console.error('Error: No content provided'); + process.exit(1); + } + + // Generate unique ID + const id = generateId(); + const filename = `${id}-${slug}.md`; + const filepath = join(SCRATCHES_DIR, filename); + + // Format title from slug (kebab-case to Title Case) + const title = slug + .split('-') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + + // Get current date in ISO format + const date = new Date().toISOString().split('T')[0]; + + // Create frontmatter + const frontmatter = `--- +date: ${date} +title: ${title} +--- + +`; + + // Ensure directory exists + mkdirSync(SCRATCHES_DIR, { recursive: true }); + + // Write file with frontmatter + writeFileSync(filepath, frontmatter + content, 'utf-8'); + + console.log(`Created scratch: ${filepath}`); + console.log(`ID: ${id}`); + console.log(`Filename: ${filename}`); +}; + +main(); diff --git a/scripts/utils/generate-id.ts b/scripts/utils/generate-id.ts new file mode 100644 index 000000000..d2c950076 --- /dev/null +++ b/scripts/utils/generate-id.ts @@ -0,0 +1,84 @@ +/** + * Generates a unique identifier using three simple words. + * Falls back to unix timestamp if word generation fails. + */ +export const generateId = (): string => { + const adjectives = [ + 'happy', + 'bright', + 'swift', + 'calm', + 'bold', + 'clever', + 'gentle', + 'quick', + 'sharp', + 'warm', + 'cool', + 'fresh', + 'solid', + 'clear', + 'sweet', + 'wild', + 'quiet', + 'loud', + 'smooth', + ]; + + const nouns = [ + 'moon', + 'star', + 'ocean', + 'river', + 'forest', + 'mountain', + 'cloud', + 'wave', + 'stone', + 'flower', + 'bird', + 'wind', + 'light', + 'shadow', + 'fire', + 'earth', + 'sky', + 'tree', + 'leaf', + 'rock', + ]; + + const colors = [ + 'blue', + 'red', + 'green', + 'yellow', + 'purple', + 'orange', + 'pink', + 'cyan', + 'amber', + 'emerald', + 'violet', + 'indigo', + 'coral', + 'teal', + 'gold', + 'silver', + 'copper', + 'bronze', + 'ivory', + 'jade', + ]; + + try { + const randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)]; + const randomColor = colors[Math.floor(Math.random() * colors.length)]; + const randomNoun = nouns[Math.floor(Math.random() * nouns.length)]; + + return `${randomAdjective}-${randomColor}-${randomNoun}`; + } catch { + // Fallback to unix timestamp if something goes wrong + return Date.now().toString(); + } +};