diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..5dc6f23 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: tests + +on: + pull_request: + push: + branches: [trunk] + +jobs: + bats: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Install bats-core + run: | + git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats-core + sudo /tmp/bats-core/install.sh /usr/local + + - name: Show bats version + run: bats --version + + - name: Run test suite + run: bats tests/ diff --git a/AGENTS.example.md b/AGENTS.example.md deleted file mode 100644 index a5a5543..0000000 --- a/AGENTS.example.md +++ /dev/null @@ -1,325 +0,0 @@ -# AGENTS.md — WordPress/PHP Example - -> This is an example `AGENTS.md` for WordPress plugin development. -> Copy this file as `AGENTS.md` in your project and adapt it to your tech stack. - ---- - -## Purpose - -This file defines coding standards and patterns for AI coding assistants. When the AI makes recurring mistakes, add a rule here. - ---- - -## Tech Stack - -- **Platform:** WordPress 6.2+ -- **Language:** PHP 8.0+ -- **Optional:** WooCommerce 8.0+ -- **Testing:** PHPUnit + WP_Mock -- **Linter:** PHPCS with WordPress Coding Standards - ---- - -## File Naming - -- Class files must follow pattern: `class-{class-name}.php` (lowercase, hyphen-separated) -- Examples: `class-email.php`, `class-database.php`, `class-gdpr.php` -- **Wrong:** `Email.php`, `Database.php`, `GDPR.php` - ---- - -## Database Security - -### Prepared Statements - -Always use `$wpdb->prepare()` with placeholders. Pass table names via `%i` (WordPress 6.2+): - -```php -// Correct -$wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', $table_name, $id ); - -// Wrong — variable interpolation -$wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $id ); -``` - -### Caching - -Use Object Cache for direct database queries: - -```php -$cache_key = 'rsvp_event_' . $event_id; -$result = wp_cache_get( $cache_key, 'simple-rsvp' ); - -if ( false === $result ) { - $result = $wpdb->get_results( ... ); - wp_cache_set( $cache_key, $result, 'simple-rsvp', HOUR_IN_SECONDS ); -} -``` - ---- - -## Form Security - -Always verify nonce when processing form data: - -```php -if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'action_name' ) ) { - wp_die( 'Security check failed' ); -} -``` - ---- - -## Input/Output Safety - -### Escaping Output - -Always escape output based on context: - -```php -esc_html( $text ); // For HTML content -esc_attr( $attribute ); // For HTML attributes -esc_url( $url ); // For URLs -esc_js( $string ); // For inline JavaScript -wp_kses_post( $html ); // For post content with allowed HTML -``` - -### Sanitizing Input - -Always sanitize input based on expected type: - -```php -sanitize_text_field( $_POST['name'] ); -absint( $_POST['id'] ); -sanitize_email( $_POST['email'] ); -wp_kses_post( $_POST['content'] ); -``` - ---- - -## Formatting - -### Assignment Alignment - -Align `=` signs in assignment blocks: - -```php -$short = 1; -$longer = 2; -$very_long = 3; -``` - -### Parentheses Spacing - -Add spaces inside parentheses for functions and conditions: - -```php -// Correct -if ( $condition ) { - my_function( $arg1, $arg2 ); -} - -// Wrong -if ($condition) { - my_function($arg1, $arg2); -} -``` - -### Multi-line Function Calls - -Place each argument on a separate line with closing parenthesis on its own line: - -```php -$result = some_function( - $argument_one, - $argument_two, - $argument_three -); -``` - ---- - -## WordPress Storage APIs - -Use the appropriate storage mechanism: - -| Storage Type | Use Case | Functions | -|--------------|----------|-----------| -| Options API | Plugin settings, global config | `get_option()`, `update_option()` | -| Post Meta | Data attached to posts/pages | `get_post_meta()`, `update_post_meta()` | -| User Meta | Data attached to users | `get_user_meta()`, `update_user_meta()` | -| Transients | Cached data with expiration | `get_transient()`, `set_transient()` | -| Custom Tables | Large datasets, complex queries | `$wpdb->get_results()`, `dbDelta()` | - ---- - -## Hooks and Filters - -### Registering Actions - -```php -add_action( 'init', array( $this, 'register_post_types' ) ); -add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) ); -``` - -### Registering Filters - -```php -add_filter( 'the_content', array( $this, 'modify_content' ) ); -add_filter( 'query_vars', array( $this, 'add_query_vars' ) ); -``` - -### Creating Custom Hooks - -```php -// Allow other plugins to modify data -$data = apply_filters( 'my_plugin_data', $data, $context ); - -// Allow other plugins to act on events -do_action( 'my_plugin_after_save', $post_id, $data ); -``` - ---- - -## WooCommerce Specifics - -### HPOS Compatibility - -Always use CRUD methods for order data, never direct database access: - -```php -// Correct -$order->get_billing_email(); -$order->get_meta( '_custom_field' ); - -// Wrong -get_post_meta( $order_id, '_billing_email', true ); -``` - -### Declaring HPOS Compatibility - -```php -add_action( 'before_woocommerce_init', function() { - if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { - \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( - 'custom_order_tables', - __FILE__, - true - ); - } -} ); -``` - ---- - -## File Operations - -Use `WP_Filesystem` instead of direct PHP functions: - -```php -global $wp_filesystem; -WP_Filesystem(); - -$wp_filesystem->put_contents( $file, $content ); - -// Instead of: fwrite(), fclose(), file_put_contents() -``` - ---- - -## Debugging - -Never leave `error_log()` in production code. Use conditional logging: - -```php -if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { - error_log( $message ); -} -``` - ---- - -## Dependency Management - -Never edit `composer.json` or `package.json` manually to add dependencies. Use CLI commands instead: - -```bash -# Composer -composer require vendor/package -composer require --dev vendor/package - -# NPM -npm install package-name -npm install --save-dev package-name -``` - -This ensures proper version resolution, lock file updates, and immediate installation. - ---- - -## Testing - -### PHPUnit + WP_Mock - -```php -use WP_Mock; -use PHPUnit\Framework\TestCase; - -class MyTest extends TestCase { - public function setUp(): void { - WP_Mock::setUp(); - } - - public function tearDown(): void { - WP_Mock::tearDown(); - } - - public function test_something(): void { - WP_Mock::userFunction( 'get_option' ) - ->with( 'my_option' ) - ->andReturn( 'value' ); - - // ... test code - } -} -``` - -### Running Tests - -```bash -composer test -# or -./vendor/bin/phpunit -``` - ---- - -## Auto-fixing - -Run `composer phpcbf` to automatically fix style violations (alignment, spacing). - ---- - -## REST API - -### Registering Endpoints - -```php -add_action( 'rest_api_init', function() { - register_rest_route( 'my-plugin/v1', '/items', array( - 'methods' => 'GET', - 'callback' => 'get_items_callback', - 'permission_callback' => function() { - return current_user_can( 'read' ); - }, - ) ); -} ); -``` - -### Response Format - -```php -return new WP_REST_Response( $data, 200 ); -return new WP_Error( 'error_code', 'Error message', array( 'status' => 400 ) ); -``` diff --git a/README.md b/README.md index edd8ef2..9b75963 100644 --- a/README.md +++ b/README.md @@ -1,147 +1,104 @@ # SpecForge -A structured methodology for AI-assisted software development using a two-stage pipeline: **reasoning model → specification → coding model**. +A spec-driven methodology for AI-assisted development, packaged as an +**agent skillset**. SpecForge separates **reasoning** from **implementation** +across three stages: -## The Problem +1. **Explore** — solution-space exploration with a high-reasoning model. +2. **Spec** — generate an unambiguous Execution-Spec. +3. **Execute** — implement strictly from the spec with a coding model. -AI coding assistants often produce inconsistent results when given vague requirements. They may: -- Make architectural decisions "on the fly" -- Add unrequested improvements -- Interpret ambiguous requirements differently each time -- Skip edge cases not explicitly mentioned +The methodology is authored once and runs in any `SKILL.md`-compatible coding +agent. -## The Solution +## Install -SpecForge separates **thinking** from **coding** into two distinct stages: +SpecForge installs with one command via the cross-agent +[`skills` CLI](https://github.com/vercel-labs/skills) — no clone, no config. It +auto-discovers the `skills/` directory in this repo, so there is nothing to set +up on our side. -1. **Solution Space Exploration** — A high-reasoning model asks questions, proposes multiple approaches, compares trade-offs, and helps you choose the best option -2. **Execution** — A coding model implements strictly according to a detailed specification, without interpretation or "improvements at discretion" +**opencode (v1):** -## Workflow - -``` -┌─────────────────┐ -│ Problem │ -│ Statement │ -└────────┬────────┘ - ▼ -┌──────────────────────────────────────┐ -│ Stage 1: Solution Space Exploration │ -│ (High-reasoning model) │ -├──────────────────────────────────────┤ -│ • Clarifies requirements │ -│ • Proposes 2–4 approaches │ -│ • Compares trade-offs │ -│ • You choose one │ -└────────┬─────────────────────────────┘ - ▼ -┌─────────────────┐ -│ Execution-Spec │◄── Detailed, unambiguous specification -└────────┬────────┘ - ▼ -┌──────────────────────────────────────┐ -│ Stage 2: Implementation │ -│ (Coding model) │ -├──────────────────────────────────────┤ -│ • Follows spec exactly │ -│ • No architectural decisions │ -│ • No unrequested improvements │ -│ • Asks if unclear, doesn't guess │ -└────────┬─────────────────────────────┘ - ▼ -┌─────────────────┐ -│ Working Code │ -└─────────────────┘ +```bash +npx skills add cadic/specforge -a opencode -g --skill '*' ``` -## Key Principles +`-a opencode` targets opencode, `-g` installs globally (into +`~/.config/opencode/skills/`), and `--skill '*'` installs all four SpecForge +skills. -- **Separation of concerns**: Architecture decisions are made by humans with AI assistance, not delegated to a coding model -- **Explicit over implicit**: Everything required must be documented; if it's not in the spec, it doesn't exist -- **No "improvements at discretion"**: The executor follows the spec exactly -- **Checkpoints**: Multiple review points before code is written -- **Stack-agnostic**: Core templates work with any tech stack; coding standards live in your `AGENTS.md` +The same command works for any of the 70+ agents the CLI supports — swap the +agent name: -## Files +```bash +npx skills add cadic/specforge -a claude-code -g --skill '*' +npx skills add cadic/specforge -a codex -g --skill '*' +``` -| File | Purpose | -|------|---------| -| `01-problem-statement.md` | Template for describing your task | -| `prompt-solution-space.md` | Prompt for Stage 1: Solution Space Exploration | -| `prompt-execution-spec.md` | Prompt for Stage 2: Generate Execution-Spec | -| `04-execution-spec.md` | Execution-Spec template (stack-agnostic) | -| `AGENTS.example.md` | Example coding standards (WordPress/PHP) | +For Cursor (no global skills dir), install into the project instead (drop `-g`): -## Task Directory Structure +```bash +npx skills add cadic/specforge -a cursor --skill '*' +``` -For each task, create a directory with: +**Manual fallback (zero dependencies):** copy the four `skills/specforge*` +directories into your agent's global skills directory by hand, e.g. +`cp -r skills/specforge* ~/.config/opencode/skills/`. -``` -task-directory/ -├── 01-problem-statement.md # Your raw problem description -├── 02-solution-options.md # Generated: reviewed approaches -├── 03-solution-hld.md # Generated: chosen solution summary -├── 04-execution-spec.md # Generated: detailed specification -├── AGENTS.md # Your coding standards (copy from example) -└── chats/ - ├── 01-solution-options-chat-1.md - └── 02-execution-spec-chat-1.md -``` +## Using it -## Quick Start +1. In your agent, invoke the **specforge** skill and give it a task slug. +2. It scaffolds `docs/specforge//01-problem-statement.md` — fill it in. +3. It detects the phase and routes you through Explore → Spec → Execute, + stopping at checkpoints for your input. -1. Create a task directory -2. Copy `AGENTS.example.md` → `AGENTS.md` and adapt to your tech stack -3. Write your problem statement in `01-problem-statement.md` (stream of consciousness is fine) -4. Copy the templates to your task directory -5. Send `prompt-solution-space.md` to a high-reasoning model -6. Answer questions, review options, choose an approach -7. Send `prompt-execution-spec.md` to generate the detailed Execution-Spec -8. Review the spec carefully -9. Send the spec to a coding model with "implement this specification" -10. Review the implementation +## Configuration -## AGENTS.md +SpecForge needs no configuration to run. By default, task artifacts live in +`docs/specforge//`. -The `AGENTS.md` file contains coding standards and patterns for the AI to follow. The included example covers WordPress/PHP, but you should create your own for your stack: +To put them elsewhere, add a `.specforge.json` at your repo root with a +`task_root` key: -- Naming conventions -- Security patterns -- Formatting rules -- Testing setup -- Common pitfalls to avoid +```json +{ "task_root": "coding-assistant/tasks" } +``` -**When the AI makes recurring mistakes, add a rule to `AGENTS.md`.** +`task_root` is the only recognized key (v1); it defaults to `docs/specforge`. -### Examples for Other Stacks +## How it works -Your `AGENTS.md` might include: +``` +skills/ + specforge/ # orchestrator: scaffolding, phase detection, routing (bash) + specforge-explore/ # Stage 1 (prose, high-reasoning) + specforge-spec/ # Stage 2 (prose, high-reasoning) + 04 template + specforge-execute/ # Stage 3 (prose, coding model) +``` -**TypeScript/React:** -- Component file naming (`PascalCase.tsx`) -- Hook patterns (`use` prefix) -- State management conventions -- Testing with Jest/React Testing Library +- **Skills live in the agent** (its global skills directory). +- **Artifacts live in your project** under the configured task root (see + [Configuration](#configuration)). -**Python/Django:** -- PEP 8 compliance -- Model naming conventions -- View/serializer patterns -- pytest configuration +State lives in the filesystem. `skills/specforge/scripts/sf-status.sh` derives +the current phase from which `0X-*.md` files exist, so you can stop after any +stage and resume later — even in a different agent. -**Go:** -- Package naming -- Error handling patterns -- Interface conventions -- Testing with `go test` +## Coding standards -## Effectiveness +SpecForge does not ship an `AGENTS.md`. Stage 3 follows your project's own +`AGENTS.md`/`CLAUDE.md`. See +[docs/writing-agents-md-for-specforge.md](docs/writing-agents-md-for-specforge.md). -Subjective observations from usage: +## Development -- **Design stage**: More effective than solo work — helps avoid analysis paralysis and documents decisions properly -- **Implementation stage**: Comparable to manual coding when in flow; faster when fatigued -- **vs. junior developers**: Significantly more effective — AI follows existing code patterns better and doesn't need extensive onboarding +Bash helpers are tested with [bats-core](https://github.com/bats-core/bats-core): + +```bash +brew install bats-core +bats tests/ +``` ## License diff --git a/docs/writing-agents-md-for-specforge.md b/docs/writing-agents-md-for-specforge.md new file mode 100644 index 0000000..0c942f7 --- /dev/null +++ b/docs/writing-agents-md-for-specforge.md @@ -0,0 +1,22 @@ +# Writing your AGENTS.md for SpecForge execution + +SpecForge does not ship an `AGENTS.md` template. Most projects already have an +`AGENTS.md` or `CLAUDE.md`, and SpecForge should not try to own that file. + +During **Stage 3 (execute)**, the `specforge-execute` skill follows your +project's own `AGENTS.md`/`CLAUDE.md` for code style, naming, security, and +testing conventions. The methodology-level executor rules (single source of +truth; not-in-spec means it-does-not-exist; ask on ambiguity) live inside the +skill — you do not need to repeat them. + +Put stack-specific standards in your project's `AGENTS.md`, for example: + +- File and class naming conventions. +- Security patterns (input sanitization, output escaping, prepared statements). +- Formatting rules and the auto-fix command. +- Test framework, test locations, and the run command. +- Dependency-management rules (e.g. add deps via CLI, not by hand). +- Recurring mistakes to avoid — add a rule each time the AI repeats one. + +The Execution-Spec references these standards in section 8.3; keep the spec +pointing at your `AGENTS.md` rather than duplicating its contents. diff --git a/prompt-execution-spec.md b/prompt-execution-spec.md deleted file mode 100644 index 78484fb..0000000 --- a/prompt-execution-spec.md +++ /dev/null @@ -1,68 +0,0 @@ -# Prompt: Generate Execution-Spec - -You are a **senior backend architect**. Your task is to fill out the Execution-Spec template for a specific task. - -Requirements: - -- write strictly factually; -- avoid reasoning and alternatives; -- document final decisions only; -- leave no ambiguities; -- if in doubt — choose one solution and document it. - -This document will be used by Codex / low-reasoning models as a specification. Any ambiguity will lead to incorrect implementation. - ---- - -## Working Files (required) - -Task directory: `{{TASK_DIR}}` (path from repository root, e.g., `design-bureau/26/01/05-...`) - -Input: -- `{{TASK_DIR}}/03-solution-hld.md` (must contain the `=== RESULT FOR EXECUTION-SPEC ===` block) - -Template: -- `{{TASK_DIR}}/04-execution-spec.md` (must be fully populated and overwritten) - -If the input block is missing / contradictory / information is insufficient — ask questions and stop. - ---- - -## Task Input Data - -1. Working directory: `{{TASK_DIR}}` -2. RESULT FOR EXECUTION-SPEC: in file `{{TASK_DIR}}/03-solution-hld.md` -3. Execution-Spec template: in file `{{TASK_DIR}}/04-execution-spec.md` - ---- - -## Instructions - -0. Read `03-solution-hld.md` and `04-execution-spec.md`. -1. If facts are missing to fill any section — ask clarifying questions (without proposing solutions) and end your message with: - -``` -=== WAITING FOR ANSWERS (EXECUTION-SPEC) === -``` - -2. After receiving answers (or if information is sufficient from the start), produce the final document and apply the rules below: - -- Fill **all sections** of the Execution-Spec. -- Do not add new sections. -- Do not leave placeholders. -- Use imperative formulations: - - "must" - - "prohibited" - - "only" - - "always / never" -- Self-check: the text must not contain `❌`, `<...>`, `TBD`, `example`, empty list items, or empty table cells. -- Self-check: the specified task aligns with actual code and can be implemented. -- Self-check: the specified task is internally consistent (non-contradictory). -- Write the result to `{{TASK_DIR}}/04-execution-spec.md` **as a single document**, ready for handoff to the executor model. -- Output the exact same text to chat, with no additions. -- Ask if you may save the current chat transcript; if yes — write it to `{{TASK_DIR}}/chats/02-execution-spec-chat-1.md`. - - If you cannot automatically save the transcript — do not fabricate; ask me how to save it. - ---- - -**Output:** fully populated Execution-Spec document. \ No newline at end of file diff --git a/prompt-solution-space.md b/prompt-solution-space.md deleted file mode 100644 index 230107b..0000000 --- a/prompt-solution-space.md +++ /dev/null @@ -1,212 +0,0 @@ -# Prompt: Solution Space Exploration - -> For **thinking / high-reasoning** models. -> Stage *before* generating the Execution Spec. -> -> Goal — explore the solution space, consciously select **one** approach, and prepare input for the Execution Spec. - ---- - -## Working Files (required) - -Task directory: `{{TASK_DIR}}` (path from repository root, e.g., `design-bureau/26/01/05-...`) - -Inside `{{TASK_DIR}}` the following files must exist: -- `01-problem-statement.md` -- `02-solution-options.md` (output) -- `03-solution-hld.md` (output) -- `chats/01-solution-options-chat-1.md` (output) - -If the path or files differ — stop and ask. - ---- - -## Model Role - -You are a **senior backend architect and technical facilitator**. - -We are at the **solution space exploration stage**, not implementation. - -**Prohibited at this stage:** -- writing code; -- generating the Execution Spec; -- premature optimization; -- pushing a solution without comparing alternatives. - -Reasoning, hypotheses, and comparisons are **allowed and required**. - ---- - -## Dialogue Goal - -1. Explore possible approaches to solving the problem -2. Explicitly document trade-offs -3. Consciously select **one** solution -4. Prepare structured input for generating the Execution Spec - ---- - -## Input Data - -### 1. Problem Statement - -Read the file `{{TASK_DIR}}/01-problem-statement.md`. - -### 2. Non-Functional Priorities - -- maintainability -- backward compatibility -- minimal changes -- performance -- implementation speed -- testability - ---- - -## Dialogue Instructions - -Work **strictly through the stages below**. -**Do not skip stages.** -This is a **dialogue**, not a monologue: stop between stages and wait for my response. - -### Hard Checkpoints - -- After **Stage 1**, end your message with: - -``` -=== WAITING FOR ANSWERS (STAGE 1) === -``` - -- After **Stages 2–3**, end your message with: - -``` -=== YOUR CHOICE (STAGES 2–3) === -Select an option: 1 / 2 / 3 / 4 (or suggest revisions to options/criteria) -``` - -⚠️ If information is insufficient, **return to Stage 1** and ask additional questions instead of proceeding. - ---- - -## Stage 1. Problem Clarification - -- Restate the problem in your own words -- Explicitly identify: - - what is *definitely required* - - what is *not required* -- Ask clarifying questions (only about missing facts/constraints) - -At this stage: -- **do not propose solutions** or compare approaches; -- output only: restatement, scope boundaries (required/not required), questions. - -At the end of your message, always output: - -``` -=== WAITING FOR ANSWERS (STAGE 1) === -``` - ---- - -## Stage 2. Solution Space - -Propose **2–4 fundamentally different approaches**. - -For each option, describe: -- core idea (brief); -- what changes are required (modules/files/contracts); -- main risks; -- pros and cons. - -⚠️ **Prohibited:** -- proposing hybrids; -- leaving options without evaluation. - ---- - -## Stage 3. Comparison and Trade-offs - -Compare options against non-functional priorities: - -- maintainability; -- blast radius; -- regression risk; -- long-term cost of changes. - -Explicitly state: -- **what we gain**; -- **what we pay**. - -After Stages 2–3: -- you may give a **recommendation**, but **do not make the final choice for me**; -- frame the choice as a user action: "select option N" or "clarify criteria". - -At the end of your message, always output: - -``` -=== YOUR CHOICE (STAGES 2–3) === -Select an option: 1 / 2 / 3 / 4 (or suggest revisions to options/criteria) -``` - ---- - -## Stage 4. Preparation for Specification - -Produce the summary for the next step: - -- selected approach (1–2 paragraphs); -- key invariants; -- hard constraints; -- boundaries of responsibility. - -⚠️ **Do not generate the Execution Spec.** -⚠️ **Do not write code.** - ---- - -## Final Output - -At the end of the dialogue, output a markdown block: - -``` -=== RESULT FOR EXECUTION-SPEC === - -**Task:** (mandatory problem statement in 1–3 sentences) - -**Selected Approach:** (brief, structured description of the chosen solution) - -**Key Invariants:** -- ... - -**Hard Constraints:** -- ... - -**Boundaries of Responsibility:** -- ... -``` - -Output this block **only at Stage 4**, after I explicitly select an option. - -Formatting: -- use markdown; -- **one sentence per line** (1 sentence = 1 line); -- in list sections (invariants/constraints/boundaries) — one statement per line. - -File Writing: -- At **Stage 4**, after generating the `=== RESULT FOR EXECUTION-SPEC ===` block, write all reviewed options (final form from Stages 2–3) to `{{TASK_DIR}}/02-solution-options.md`; -- At **Stage 4**, after generating the `=== RESULT FOR EXECUTION-SPEC ===` block, write this block to `{{TASK_DIR}}/03-solution-hld.md` (no additional comments); -- Then output the block to chat unchanged; -- Finally, write the current chat transcript to `{{TASK_DIR}}/chats/01-solution-options-chat-1.md`. - - If you cannot automatically save the transcript — do not fabricate; ask me how to save it. - -This block is intended for: -- passing to the **Generate Execution-Spec** prompt; -- subsequent use by the executor model. - ---- - -## Position in the Overall Flow - -1️⃣ Solution Space Exploration ← **this prompt** -2️⃣ Generate Execution-Spec -3️⃣ Executor / Codex (implementation) \ No newline at end of file diff --git a/skills/specforge-execute/SKILL.md b/skills/specforge-execute/SKILL.md new file mode 100644 index 0000000..50d1048 --- /dev/null +++ b/skills/specforge-execute/SKILL.md @@ -0,0 +1,34 @@ +--- +name: specforge-execute +description: Use for SpecForge Stage 3 — implement strictly from 04-execution-spec.md. Treat the spec as the single source of truth; if it is not in the spec it does not exist; ask on ambiguity instead of guessing; follow the project's own AGENTS.md/CLAUDE.md for code style. +--- + +# SpecForge — Stage 3: Execute + +Implement the task strictly from `04-execution-spec.md`. This stage runs on a +coding model. + +## Executor discipline (methodology-level — always applies) + +- The Execution-Spec is the **single source of truth**. +- If a requirement is **not in the spec, it does not exist** — do not add + undocumented behavior, improvements, or optimizations "at your discretion". +- Do not rethink the architecture or deviate from the spec. +- On any ambiguity or contradiction — **ask a question, do not guess**. +- Section 11 (Decision Rationale) is **read-only**: never use it to justify a + change. +- For code style, naming, security, and testing conventions, follow the + **project's own** `AGENTS.md`/`CLAUDE.md`. SpecForge does not own that file. + +## Procedure + +1. Read `04-execution-spec.md` end to end before writing any code. +2. Read the project's `AGENTS.md`/`CLAUDE.md` (if present) for code standards. +3. Implement sections 3, 4, 5, 6, and 8 exactly as written. +4. Respect section 7 (Out of Scope) and section 8.2 (What Not To Do). +5. Write the tests described in section 9. +6. Before claiming completion, run the Definition-of-Done self-verify in + `references/checklist.md`. + +If the spec is internally contradictory or missing facts you need, stop and ask +the user — return to Stage 2 if the spec itself must change. diff --git a/skills/specforge-execute/references/checklist.md b/skills/specforge-execute/references/checklist.md new file mode 100644 index 0000000..065389e --- /dev/null +++ b/skills/specforge-execute/references/checklist.md @@ -0,0 +1,17 @@ +# Stage 3: Definition of Done / Self-Verify + +Confirm every item with evidence before reporting the task complete. Do not +assert success without running the relevant command. + +- [ ] Behavior from spec section 3 (main + alternative scenarios) is implemented. +- [ ] Rules and invariants from section 4 hold. +- [ ] Interfaces and contracts from section 5 match the spec exactly. +- [ ] Data/persistence from section 6 is implemented as specified. +- [ ] Hard constraints from section 1.2 are satisfied. +- [ ] Nothing from section 7 (Out of Scope) or 8.2 (What Not To Do) was added. +- [ ] Tests from section 9 are written and pass — paste the run output. +- [ ] Linter/formatter passes with no errors — paste the run output. +- [ ] No undocumented behavior, files, or dependencies were introduced. + +If any item fails, fix it and re-verify. Report failures honestly with the +command output; do not claim completion on unverified items. diff --git a/skills/specforge-explore/SKILL.md b/skills/specforge-explore/SKILL.md new file mode 100644 index 0000000..0b4a409 --- /dev/null +++ b/skills/specforge-explore/SKILL.md @@ -0,0 +1,32 @@ +--- +name: specforge-explore +description: Use for SpecForge Stage 1 — solution-space exploration with a high-reasoning model. Clarify the problem, propose 2-4 approaches, compare trade-offs, let the user choose, then write 02-solution-options.md and 03-solution-hld.md. No code, no spec generation. +--- + +# SpecForge — Stage 1: Explore + +You are a senior architect and technical facilitator. This is the +**solution-space exploration** stage, not implementation. + +**Prohibited here:** writing code, generating the Execution-Spec, premature +optimization, pushing one solution without comparing alternatives. + +**Model guidance (advisory):** run this stage on a high-reasoning model. + +## Procedure at a glance + +1. **Stage 1 — Clarify.** Restate the problem; state what is required vs. not + required; ask only fact/constraint questions. End with the Stage 1 + checkpoint and wait. +2. **Stage 2 — Solution space.** Propose 2–4 fundamentally different approaches + (no hybrids); for each: core idea, required changes, risks, pros/cons. +3. **Stage 3 — Compare.** Trade-offs against maintainability, blast radius, + regression risk, long-term cost. Recommend, but let the user choose. End + with the choice checkpoint and wait. +4. **Stage 4 — Prepare for spec.** After the user picks an option, write the + two output files and emit the RESULT block. + +Stop between stages and wait for the user — this is a dialogue, not a monologue. + +For the full stage prompts, checkpoint strings, and exact output formats, read +`references/process.md`. diff --git a/skills/specforge-explore/references/process.md b/skills/specforge-explore/references/process.md new file mode 100644 index 0000000..68be25c --- /dev/null +++ b/skills/specforge-explore/references/process.md @@ -0,0 +1,113 @@ +# Stage 1: Solution Space Exploration — Full Process + +> For thinking / high-reasoning models. The stage *before* generating the +> Execution-Spec. + +## Working files + +Work inside the current task directory (the orchestrator resolved it). These +files are involved: + +- `01-problem-statement.md` (input) +- `02-solution-options.md` (output) +- `03-solution-hld.md` (output) + +If the problem statement is missing or empty — stop and ask. + +## Model role + +You are a senior architect and technical facilitator. We are at the +solution-space exploration stage, not implementation. + +**Prohibited:** writing code; generating the Execution-Spec; premature +optimization; pushing a solution without comparing alternatives. + +Reasoning, hypotheses, and comparisons are allowed and required. + +## Non-functional priorities + +maintainability · backward compatibility · minimal changes · performance · +implementation speed · testability + +## Dialogue instructions + +Work strictly through the stages below. Do not skip stages. Stop between stages +and wait for the user's response. + +### Hard checkpoints + +- After Stage 1, end your message with: + +``` +=== WAITING FOR ANSWERS (STAGE 1) === +``` + +- After Stages 2–3, end your message with: + +``` +=== YOUR CHOICE (STAGES 2–3) === +Select an option: 1 / 2 / 3 / 4 (or suggest revisions to options/criteria) +``` + +If information is insufficient, return to Stage 1 and ask more questions instead +of proceeding. + +## Stage 1. Problem clarification + +- Restate the problem in your own words. +- Explicitly identify what is definitely required and what is not required. +- Ask clarifying questions (only about missing facts/constraints). +- Do not propose solutions yet. Output only: restatement, scope boundaries, + questions. End with the Stage 1 checkpoint. + +## Stage 2. Solution space + +Propose 2–4 fundamentally different approaches. For each: core idea (brief); +required changes (modules/files/contracts); main risks; pros and cons. + +Prohibited: proposing hybrids; leaving options without evaluation. + +## Stage 3. Comparison and trade-offs + +Compare options against maintainability, blast radius, regression risk, and +long-term cost of changes. State explicitly what we gain and what we pay. You +may give a recommendation, but do not make the final choice — frame it as a user +action ("select option N" or "clarify criteria"). End with the choice +checkpoint. + +## Stage 4. Preparation for specification + +After the user selects an option, produce the summary: selected approach (1–2 +paragraphs); key invariants; hard constraints; boundaries of responsibility. + +Do not generate the Execution-Spec. Do not write code. + +### Final output (RESULT block) + +``` +=== RESULT FOR EXECUTION-SPEC === + +**Task:** (mandatory problem statement in 1–3 sentences) + +**Selected Approach:** (brief, structured description of the chosen solution) + +**Key Invariants:** +- ... + +**Hard Constraints:** +- ... + +**Boundaries of Responsibility:** +- ... +``` + +Formatting: markdown; one sentence per line; one statement per list item. + +### File writing (Stage 4 only, after the user selects an option) + +1. Write all reviewed options (final form from Stages 2–3) to + `02-solution-options.md`. +2. Write the RESULT block (no extra comments) to `03-solution-hld.md`. +3. Output the RESULT block to chat unchanged. + +This block is the input to Stage 2 (specforge-spec). diff --git a/skills/specforge-spec/SKILL.md b/skills/specforge-spec/SKILL.md new file mode 100644 index 0000000..11c2038 --- /dev/null +++ b/skills/specforge-spec/SKILL.md @@ -0,0 +1,33 @@ +--- +name: specforge-spec +description: Use for SpecForge Stage 2 — generate the Execution-Spec. Consume 03-solution-hld.md, fill the 04 template with final decisions only (no alternatives), then self-check with sf-validate.sh before handoff to the executor. +--- + +# SpecForge — Stage 2: Spec + +You are a senior architect filling out the Execution-Spec template for one task. +This document is consumed by a coding model **without interpretation** — any +ambiguity causes wrong implementation. + +**Rules:** write strictly factually; no reasoning or alternatives; final +decisions only; leave no ambiguities; if in doubt, choose one solution and +document it. + +## Procedure at a glance + +1. Read `03-solution-hld.md` (must contain the `=== RESULT FOR EXECUTION-SPEC + ===` block) and the template `templates/04-execution-spec.md`. +2. If facts are missing, ask clarifying questions and stop — end with + `=== WAITING FOR ANSWERS (EXECUTION-SPEC) ===`. +3. Fill **all** sections of the template. Use imperative wording (must, + prohibited, only, always/never). Add no new sections. Leave no placeholders. +4. Write the result to `04-execution-spec.md` in the task dir. +5. **Self-check** — run the validator: + + `bash /scripts/sf-validate.sh /04-execution-spec.md` + + It must print `sf-validate: OK`. If it reports placeholders, empty cells, or + empty list items, fix them and re-run until clean. +6. Output the same final document to chat with no additions. + +For the full generate-spec prompt, read `references/process.md`. diff --git a/skills/specforge-spec/references/process.md b/skills/specforge-spec/references/process.md new file mode 100644 index 0000000..1ae474f --- /dev/null +++ b/skills/specforge-spec/references/process.md @@ -0,0 +1,56 @@ +# Stage 2: Generate Execution-Spec — Full Process + +You are a senior architect. Your task is to fill out the Execution-Spec template +for a specific task. + +Requirements: write strictly factually; avoid reasoning and alternatives; +document final decisions only; leave no ambiguities; if in doubt, choose one +solution and document it. + +This document will be used by a coding model as a specification. Any ambiguity +will lead to incorrect implementation. + +## Working files + +Work inside the current task directory (the orchestrator resolved it). + +- Input: `03-solution-hld.md` (must contain the `=== RESULT FOR EXECUTION-SPEC + ===` block). +- Template: `templates/04-execution-spec.md` (in this skill) — fill it and write + the result to `04-execution-spec.md` in the task dir. + +If the input block is missing / contradictory / insufficient — ask questions and +stop. + +## Instructions + +0. Read `03-solution-hld.md` and the `04` template. +1. If facts are missing to fill any section, ask clarifying questions (without + proposing solutions) and end your message with: + +``` +=== WAITING FOR ANSWERS (EXECUTION-SPEC) === +``` + +2. After receiving answers (or if information is sufficient), produce the final + document and apply these rules: + +- Fill all sections of the Execution-Spec. +- Do not add new sections. +- Do not leave placeholders. +- Use imperative formulations: "must", "prohibited", "only", "always / never". +- Self-check: the text must not contain `❌`, `<...>`, `TBD`, empty list items, + or empty table cells. +- Self-check: the task aligns with actual code and can be implemented. +- Self-check: the task is internally consistent (non-contradictory). +- Write the result to `04-execution-spec.md` in the task dir as a single + document, ready for handoff to the executor. +- Run the validator and confirm it prints `sf-validate: OK`: + +``` +bash /scripts/sf-validate.sh /04-execution-spec.md +``` + +- Output the exact same text to chat, with no additions. + +**Output:** fully populated Execution-Spec document. diff --git a/04-execution-spec.md b/skills/specforge-spec/templates/04-execution-spec.md similarity index 95% rename from 04-execution-spec.md rename to skills/specforge-spec/templates/04-execution-spec.md index cb980e3..8c94aa2 100644 --- a/04-execution-spec.md +++ b/skills/specforge-spec/templates/04-execution-spec.md @@ -33,7 +33,7 @@ Implement the functionality *strictly in accordance* with this document. - treat this document as the *single source of truth*; - if a requirement is not described — assume it **does not exist**; - in case of ambiguity — **ask a question**, do not guess; -- follow coding standards defined in `AGENTS.md`. +- follow coding standards defined in the project's `AGENTS.md`/`CLAUDE.md`. --- @@ -200,10 +200,10 @@ Explicitly prohibited: ### 8.3 Coding Standards -Follow the coding standards defined in `AGENTS.md`. Key points: +Follow the coding standards defined in the project's `AGENTS.md`/`CLAUDE.md`. Key points: -- -- +- +- --- diff --git a/skills/specforge/SKILL.md b/skills/specforge/SKILL.md new file mode 100644 index 0000000..f6bc240 --- /dev/null +++ b/skills/specforge/SKILL.md @@ -0,0 +1,47 @@ +--- +name: specforge +description: Use when starting or resuming a SpecForge task — the orchestrator that scaffolds the task directory, detects the current phase, and routes to the explore, spec, or execute stage skill. Triggers on "specforge", "execution spec", "spec-driven implementation". +--- + +# SpecForge Orchestrator + +SpecForge separates **reasoning** from **implementation** across three stages: +Explore → Spec → Execute. This skill is the entry point: it runs the +deterministic scripts and routes you to the right stage. It does not contain +stage content — it points to the stage skills. + +## When to use + +- The user asks to start a new SpecForge task, or to continue an existing one. +- You see a `docs/specforge//` directory (or the configured task root) + and need to know what to do next. + +## Scripts (this skill owns them) + +All paths are relative to this skill's `scripts/` directory. Run them from the +**project** repo root (they resolve the repo via git). + +- `scripts/sf-init.sh ` — scaffold the task dir and seed + `01-problem-statement.md`. Refuses to overwrite an existing `01`. +- `scripts/sf-status.sh ` — prints the current phase: `setup`, + `explore`, `spec`, or `execute`. +- `scripts/sf-validate.sh ` — checks a document for unfilled placeholders. + +## Procedure + +1. Determine the task slug (ask the user if unknown). +2. If the task does not exist yet, run `sf-init.sh `, then help the user + write `01-problem-statement.md`. +3. Run `sf-status.sh ` and route by its output: + - `setup` → help author `01-problem-statement.md`, then re-run status. + - `explore` → use the **specforge-explore** skill. + - `spec` → use the **specforge-spec** skill. + - `execute` → use the **specforge-execute** skill. +4. After each stage completes, re-run `sf-status.sh` to confirm the next phase. + +State lives in the filesystem, not in this skill. A developer can stop after any +stage and resume later — even in a different agent — and routing picks up exactly +where the `0X-*.md` files indicate. + +For the full flow and the `.specforge.json` override, read +`references/workflow.md`. diff --git a/skills/specforge/references/workflow.md b/skills/specforge/references/workflow.md new file mode 100644 index 0000000..bad4e02 --- /dev/null +++ b/skills/specforge/references/workflow.md @@ -0,0 +1,48 @@ +# SpecForge Workflow + +## The three stages + +1. **Explore** (`specforge-explore`) — solution-space exploration with a + high-reasoning model. Clarify the problem, propose 2–4 approaches, compare + trade-offs, let the user choose. Writes `02-solution-options.md` and + `03-solution-hld.md`. +2. **Spec** (`specforge-spec`) — fill the Execution-Spec template from + `03-solution-hld.md`. Factual, decision-only. Writes `04-execution-spec.md` + and runs `sf-validate.sh` as a self-check. +3. **Execute** (`specforge-execute`) — implement strictly from + `04-execution-spec.md`, following the project's own `AGENTS.md`/`CLAUDE.md`. + +## Phase detection (the source of truth) + +`sf-status.sh` derives the phase from which files exist: + +| Files present | Phase | Keyword | +| ------------- | ----- | ------- | +| no `01` | help author the problem statement | `setup` | +| `01`, no `03` | Stage 1 | `explore` | +| `03`, no `04` (or `04` still a template copy) | Stage 2 | `spec` | +| `04` populated, placeholder-free | Stage 3 | `execute` | + +## Task directory convention + +- Default root: `docs/specforge//`. +- Override per project with a `.specforge.json` at the repo root: + +```json +{ "task_root": "coding-assistant/tasks" } +``` + +The scripts read `task_root` if present, else use `docs/specforge`. Only the +`task_root` key is recognized in v1. + +## Artifacts per task + +``` +// + 01-problem-statement.md # seeded by sf-init.sh, authored by you + 02-solution-options.md # written by specforge-explore + 03-solution-hld.md # written by specforge-explore (RESULT block) + 04-execution-spec.md # written by specforge-spec +``` + +No `chats/` directory: the agent session is the transcript. diff --git a/skills/specforge/scripts/_lib.sh b/skills/specforge/scripts/_lib.sh new file mode 100644 index 0000000..6029cf6 --- /dev/null +++ b/skills/specforge/scripts/_lib.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Shared helpers for SpecForge orchestrator scripts. +# Sourced by sf-init.sh, sf-status.sh, sf-validate.sh. + +# Print the repository root. Uses git when available, else the current dir. +sf_repo_root() { + if git rev-parse --show-toplevel >/dev/null 2>&1; then + git rev-parse --show-toplevel + else + pwd -P + fi +} + +# Print the task root, relative to repo root. Reads .specforge.json if present. +sf_task_root() { + local config + config="$(sf_repo_root)/.specforge.json" + if [ -f "$config" ]; then + local value + value="$(grep -oE '"task_root"[[:space:]]*:[[:space:]]*"[^"]*"' "$config" \ + | sed -E 's/.*:[[:space:]]*"([^"]*)".*/\1/')" + if [ -n "$value" ]; then + printf '%s\n' "$value" + return 0 + fi + fi + printf '%s\n' "docs/specforge" +} + +# Print the absolute task directory for a slug. +sf_resolve_task_dir() { + local slug="$1" + printf '%s/%s/%s\n' "$(sf_repo_root)" "$(sf_task_root)" "$slug" +} + +# Exit 0 if the file still contains unfilled placeholders / forbidden markers. +sf_has_placeholders() { + local file="$1" + grep -qE '<[^>]+>|TBD|❌' "$file" +} diff --git a/skills/specforge/scripts/sf-init.sh b/skills/specforge/scripts/sf-init.sh new file mode 100755 index 0000000..0798cf6 --- /dev/null +++ b/skills/specforge/scripts/sf-init.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Scaffold a SpecForge task directory and seed the problem statement. +# Usage: sf-init.sh +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=_lib.sh +source "$SCRIPT_DIR/_lib.sh" + +slug="${1:-}" +if [ -z "$slug" ]; then + echo "usage: sf-init.sh " >&2 + exit 2 +fi + +dir="$(sf_resolve_task_dir "$slug")" +target="$dir/01-problem-statement.md" + +if [ -f "$target" ]; then + echo "sf-init: refusing to overwrite existing $target" >&2 + exit 1 +fi + +mkdir -p "$dir" +cp "$SCRIPT_DIR/../templates/01-problem-statement.md" "$target" +printf '%s\n' "$dir" diff --git a/skills/specforge/scripts/sf-status.sh b/skills/specforge/scripts/sf-status.sh new file mode 100755 index 0000000..87a1865 --- /dev/null +++ b/skills/specforge/scripts/sf-status.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Derive the current SpecForge phase from which 0X files exist. +# Usage: sf-status.sh +# Prints exactly one of: setup | explore | spec | execute +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=_lib.sh +source "$SCRIPT_DIR/_lib.sh" + +slug="${1:-}" +if [ -z "$slug" ]; then + echo "usage: sf-status.sh " >&2 + exit 2 +fi + +dir="$(sf_resolve_task_dir "$slug")" +has() { [ -f "$dir/$1" ]; } + +if ! has "01-problem-statement.md"; then + echo "setup" +elif ! has "03-solution-hld.md"; then + echo "explore" +elif ! has "04-execution-spec.md"; then + echo "spec" +elif sf_has_placeholders "$dir/04-execution-spec.md"; then + echo "spec" +else + echo "execute" +fi diff --git a/skills/specforge/scripts/sf-validate.sh b/skills/specforge/scripts/sf-validate.sh new file mode 100755 index 0000000..92699d8 --- /dev/null +++ b/skills/specforge/scripts/sf-validate.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# Check a SpecForge document for unfilled placeholders and empty fields. +# Usage: sf-validate.sh +# Exit 0 = clean, 1 = problems found, 2 = usage/file error. +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=_lib.sh +source "$SCRIPT_DIR/_lib.sh" + +target="${1:-}" +if [ -z "$target" ]; then + echo "usage: sf-validate.sh " >&2 + exit 2 +fi +if [ ! -f "$target" ]; then + echo "sf-validate: file not found: $target" >&2 + exit 2 +fi + +found=0 +report() { + local label="$1" pattern="$2" hits + hits="$(grep -nE "$pattern" "$target" || true)" + if [ -n "$hits" ]; then + found=1 + echo "[$label]" + echo "$hits" + fi +} + +report "angle-placeholder" '<[^>]+>' +report "TBD" 'TBD' +report "forbidden-glyph" '❌' +report "empty-table-cell" '\|[[:space:]]*\|' +report "empty-list-item" '^[[:space:]]*[-*][[:space:]]*$' + +if [ "$found" -ne 0 ]; then + echo "sf-validate: FAIL ($target)" >&2 + exit 1 +fi +echo "sf-validate: OK ($target)" diff --git a/01-problem-statement.md b/skills/specforge/templates/01-problem-statement.md similarity index 100% rename from 01-problem-statement.md rename to skills/specforge/templates/01-problem-statement.md diff --git a/tests/lib.bats b/tests/lib.bats new file mode 100644 index 0000000..5c9b8e6 --- /dev/null +++ b/tests/lib.bats @@ -0,0 +1,49 @@ +#!/usr/bin/env bats + +setup() { + TESTDIR="$(mktemp -d)" + cd "$TESTDIR" + git init -q + LIB="$BATS_TEST_DIRNAME/../skills/specforge/scripts/_lib.sh" +} + +teardown() { + rm -rf "$TESTDIR" +} + +@test "sf_repo_root returns git toplevel" { + run bash -c "source '$LIB'; sf_repo_root" + [ "$status" -eq 0 ] + [ "$output" = "$(cd "$TESTDIR" && pwd -P)" ] +} + +@test "sf_task_root defaults to docs/specforge" { + run bash -c "source '$LIB'; sf_task_root" + [ "$status" -eq 0 ] + [ "$output" = "docs/specforge" ] +} + +@test "sf_task_root honors .specforge.json override" { + printf '{ "task_root": "coding-assistant/tasks" }\n' > .specforge.json + run bash -c "source '$LIB'; sf_task_root" + [ "$status" -eq 0 ] + [ "$output" = "coding-assistant/tasks" ] +} + +@test "sf_resolve_task_dir joins root, task-root, slug" { + run bash -c "source '$LIB'; sf_resolve_task_dir my-task" + [ "$status" -eq 0 ] + [ "$output" = "$(cd "$TESTDIR" && pwd -P)/docs/specforge/my-task" ] +} + +@test "sf_has_placeholders detects angle placeholders" { + printf 'name: <...>\n' > f.md + run bash -c "source '$LIB'; sf_has_placeholders f.md" + [ "$status" -eq 0 ] +} + +@test "sf_has_placeholders passes a clean file" { + printf 'name: real value\n' > f.md + run bash -c "source '$LIB'; sf_has_placeholders f.md" + [ "$status" -ne 0 ] +} diff --git a/tests/sf-init.bats b/tests/sf-init.bats new file mode 100644 index 0000000..47343f9 --- /dev/null +++ b/tests/sf-init.bats @@ -0,0 +1,43 @@ +#!/usr/bin/env bats + +setup() { + TESTDIR="$(mktemp -d)" + cd "$TESTDIR" + git init -q + INIT="$BATS_TEST_DIRNAME/../skills/specforge/scripts/sf-init.sh" +} + +teardown() { + rm -rf "$TESTDIR" +} + +@test "sf-init scaffolds task dir and 01 template" { + run bash "$INIT" my-task + [ "$status" -eq 0 ] + [ -f "docs/specforge/my-task/01-problem-statement.md" ] + grep -q "Problem Statement" "docs/specforge/my-task/01-problem-statement.md" +} + +@test "sf-init prints the task dir" { + run bash "$INIT" my-task + [ "$output" = "$(cd "$TESTDIR" && pwd -P)/docs/specforge/my-task" ] +} + +@test "sf-init refuses to overwrite an existing 01" { + bash "$INIT" my-task + run bash "$INIT" my-task + [ "$status" -ne 0 ] + [[ "$output" == *"refusing to overwrite"* ]] +} + +@test "sf-init honors .specforge.json task root" { + printf '{ "task_root": "tasks" }\n' > .specforge.json + run bash "$INIT" my-task + [ "$status" -eq 0 ] + [ -f "tasks/my-task/01-problem-statement.md" ] +} + +@test "sf-init errors without a slug" { + run bash "$INIT" + [ "$status" -ne 0 ] +} diff --git a/tests/sf-status.bats b/tests/sf-status.bats new file mode 100644 index 0000000..f95aa96 --- /dev/null +++ b/tests/sf-status.bats @@ -0,0 +1,54 @@ +#!/usr/bin/env bats + +setup() { + TESTDIR="$(mktemp -d)" + cd "$TESTDIR" + git init -q + STATUS="$BATS_TEST_DIRNAME/../skills/specforge/scripts/sf-status.sh" + DIR="docs/specforge/t" + mkdir -p "$DIR" +} + +teardown() { + rm -rf "$TESTDIR" +} + +@test "setup when no 01 exists" { + run bash "$STATUS" t + [ "$output" = "setup" ] +} + +@test "explore when 01 present and no 03" { + printf 'x\n' > "$DIR/01-problem-statement.md" + run bash "$STATUS" t + [ "$output" = "explore" ] +} + +@test "spec when 03 present and no 04" { + printf 'x\n' > "$DIR/01-problem-statement.md" + printf 'x\n' > "$DIR/02-solution-options.md" + printf 'x\n' > "$DIR/03-solution-hld.md" + run bash "$STATUS" t + [ "$output" = "spec" ] +} + +@test "spec when 04 still holds template placeholders" { + printf 'x\n' > "$DIR/01-problem-statement.md" + printf 'x\n' > "$DIR/03-solution-hld.md" + printf 'Project: <...>\n' > "$DIR/04-execution-spec.md" + run bash "$STATUS" t + [ "$output" = "spec" ] +} + +@test "execute when 04 is filled and placeholder-free" { + printf 'x\n' > "$DIR/01-problem-statement.md" + printf 'x\n' > "$DIR/03-solution-hld.md" + printf 'Project: Widget API\n' > "$DIR/04-execution-spec.md" + run bash "$STATUS" t + [ "$output" = "execute" ] +} + +@test "errors without a slug" { + run bash "$STATUS" + [ "$status" -eq 2 ] +} diff --git a/tests/sf-validate.bats b/tests/sf-validate.bats new file mode 100644 index 0000000..d673a49 --- /dev/null +++ b/tests/sf-validate.bats @@ -0,0 +1,64 @@ +#!/usr/bin/env bats + +setup() { + TESTDIR="$(mktemp -d)" + cd "$TESTDIR" + VALIDATE="$BATS_TEST_DIRNAME/../skills/specforge/scripts/sf-validate.sh" +} + +teardown() { + rm -rf "$TESTDIR" +} + +@test "passes a fully filled document" { + printf '# Spec\n\nProject: Widget API\n\n- invariant one\n\n| Term | Definition |\n| ---- | ---------- |\n| node | a unit |\n' > clean.md + run bash "$VALIDATE" clean.md + [ "$status" -eq 0 ] + [[ "$output" == *"OK"* ]] +} + +@test "flags angle placeholders" { + printf 'Project: <...>\n' > f.md + run bash "$VALIDATE" f.md + [ "$status" -eq 1 ] + [[ "$output" == *"angle-placeholder"* ]] +} + +@test "flags TBD" { + printf 'Version: TBD\n' > f.md + run bash "$VALIDATE" f.md + [ "$status" -eq 1 ] + [[ "$output" == *"TBD"* ]] +} + +@test "flags the forbidden glyph" { + printf 'Status: ❌\n' > f.md + run bash "$VALIDATE" f.md + [ "$status" -eq 1 ] + [[ "$output" == *"forbidden-glyph"* ]] +} + +@test "flags empty table cells" { + printf '| a | | c |\n' > f.md + run bash "$VALIDATE" f.md + [ "$status" -eq 1 ] + [[ "$output" == *"empty-table-cell"* ]] +} + +@test "flags empty list items" { + printf 'List:\n- \n' > f.md + run bash "$VALIDATE" f.md + [ "$status" -eq 1 ] + [[ "$output" == *"empty-list-item"* ]] +} + +@test "does not flag a filled checkbox list" { + printf -- '- [ ] do a thing\n- [x] done thing\n' > f.md + run bash "$VALIDATE" f.md + [ "$status" -eq 0 ] +} + +@test "errors on a missing file" { + run bash "$VALIDATE" nope.md + [ "$status" -eq 2 ] +}