diff --git a/.agents/skills/skill-creator/SKILL.md b/.agents/skills/skill-creator/SKILL.md new file mode 100644 index 0000000..03495e7 --- /dev/null +++ b/.agents/skills/skill-creator/SKILL.md @@ -0,0 +1,45 @@ +--- +name: skill-creator +description: Authors and structures professional-grade agent skills following the agentskills.io spec. Use when creating new skill directories, drafting procedural instructions, or optimizing metadata for discoverability. Don't use for general documentation, non-agentic library code, or README files. +--- + +# Skill Authoring Procedure + +Follow these steps to generate a skill that adheres to the agentskills.io specification and progressive disclosure principles. + +## Step 1: Initialize and Validate Metadata +1. Define a unique `name`: 1-64 characters, lowercase, numbers, and single hyphens only. +2. Draft a `description`: Max 1,024 characters, written in the third person, including negative triggers. +3. **Execute Validation Script:** Run the validation script to ensure compliance before proceeding: + `python3 scripts/validate-metadata.py --name "[name]" --description "[description]"` +4. If the script returns an error, self-correct the metadata based on the `stderr` output and re-run until successful. + +## Step 2: Structure the Directory +1. Create the root directory using the validated `name`. +2. Initialize the following subdirectories: + * `scripts/`: For tiny CLI tools and deterministic logic. + * `references/`: For flat (one-level deep) context like schemas or API docs. + * `assets/`: For output templates, JSON schemas, or static files. +3. Ensure no human-centric files (README.md, INSTALLATION.md) are created. + +## Step 3: Draft Core Logic (SKILL.md) +1. Use the template in `assets/skill-template.md` as the starting point. +2. Write all instructions in the **third-person imperative** (e.g., "Extract the text," "Run the build"). +3. **Enforce Progressive Disclosure:** + * Keep the main logic under 500 lines. + * If a procedure requires a large schema or complex rule set, move it to `references/`. + * Command the agent to read the specific file only when needed: *"Read references/api-spec.md to identify the correct endpoint."* + +## Step 4: Identify and Bundle Scripts +1. Identify "fragile" tasks (regex, complex parsing, or repetitive boilerplate). +2. Outline a single-purpose script for the `scripts/` directory. +3. Ensure the script uses standard output (stdout/stderr) to communicate success or failure to the agent. + +## Step 5: Final Logic Validation +1. Review the `SKILL.md` for "hallucination gaps" (points where the agent is forced to guess). +2. Verify all file paths are **relative** and use forward slashes (`/`). +3. Cross-reference the final output against `references/checklist.md`. + +## Error Handling +* **Metadata Failure:** If `scripts/validate-metadata.py` fails, identify the specific error (e.g., "STYLE ERROR") and rewrite the field to remove first/second person pronouns. +* **Context Bloat:** If the draft exceeds 500 lines, extract the largest procedural block and move it to a file in `references/`. diff --git a/.agents/skills/skill-creator/assets/SKILL.template.md b/.agents/skills/skill-creator/assets/SKILL.template.md new file mode 100644 index 0000000..cb61cfc --- /dev/null +++ b/.agents/skills/skill-creator/assets/SKILL.template.md @@ -0,0 +1,21 @@ +--- +name: [skill-name] +description: [Action-oriented capability description in the third person. Max 1,024 characters. Include positive triggers. Do not use for [explicit negative triggers].] +--- + +# [Skill Title] + +## Procedures + +**Step 1: [Action Phase]** +1. [Third-person imperative instruction, e.g., "Extract the query parameters..."] +2. [Instruction referencing an asset, e.g., "Read `assets/template.json` to structure the final output."] + +**Step 2: [Action Phase]** +1. [Decision tree/conditional logic, e.g., "If source maps are required, run `scripts/build.sh`. Otherwise, skip to Step 3."] +2. [Instruction requiring JiT loading, e.g., "Read `references/auth-flow.md` to map the specific error codes."] +3. Execute `python scripts/[script-name].py` to [perform deterministic action]. + +## Error Handling +* If `scripts/[script-name].py` fails due to [specific edge case], execute [recovery step]. +* If [condition B occurs], read `references/[troubleshooting-file].md`. diff --git a/.agents/skills/skill-creator/references/checklist.md b/.agents/skills/skill-creator/references/checklist.md new file mode 100644 index 0000000..2c9fcef --- /dev/null +++ b/.agents/skills/skill-creator/references/checklist.md @@ -0,0 +1,32 @@ +# Agent Skill Validation Checklist + +Use this checklist to perform a final audit of the generated skill before deployment. Every item must be marked as "Pass" to ensure the skill is discoverable, lean, and deterministic. + +## 1. Metadata & Discovery +* [ ] **Naming:** The `name` field is 1-64 characters, lowercase, and contains only numbers or single hyphens. +* [ ] **Directory Match:** The `name` field exactly matches the parent directory name. +* [ ] **Description Length:** The description is under 1,024 characters. +* [ ] **Trigger Optimization:** The description includes both use cases ("Use when...") and negative triggers ("Don't use for..."). +* [ ] **Third-Person Tone:** The description avoids "I", "me", "my", "you", or "your". + +## 2. File Structure & Paths +* [ ] **Flat Hierarchy:** All files in `scripts/`, `references/`, and `assets/` are exactly one level deep (no nested subfolders). +* [ ] **Standard Folders:** Only use `scripts/`, `references/`, and `assets/`. +* [ ] **No Human Docs:** The directory contains NO `README.md`, `CHANGELOG.md`, or `INSTALLATION_GUIDE.md`. +* [ ] **Forward Slashes:** All file paths in `SKILL.md` use forward slashes (`/`) regardless of the operating system. + +## 3. Logic & Instructions (SKILL.md) +* [ ] **Lean Context:** The `SKILL.md` file is under 500 lines. +* [ ] **Imperative Mood:** Instructions use direct commands (e.g., "Extract," "Run," "Validate"). +* [ ] **Deterministic Steps:** The workflow is a numbered, chronological sequence with clear decision trees. +* [ ] **Progressive Disclosure:** Large schemas, templates, or rule sets are stored in `references/` or `assets/` and read only when needed. +* [ ] **Specific Terminology:** Uses domain-native terms consistently (e.g., "component" instead of "file"). + +## 4. Scripts & Determinism +* [ ] **CLI Design:** Scripts in `scripts/` are designed as tiny CLIs that take arguments. +* [ ] **Feedback Loop:** Scripts provide descriptive `stdout` for success and `stderr` for failure to allow agent self-correction. +* [ ] **No Library Code:** Scripts are single-purpose; complex logic is offloaded to the repository's standard CLI or external tools. + +## 5. Error Handling +* [ ] **Edge Cases:** The `SKILL.md` includes an "Error Handling" section addressing common failure states or missing configurations. +* [ ] **Validation:** The `SKILL.md` includes a step to run validation scripts where applicable. diff --git a/.agents/skills/skill-creator/scripts/validate-metadata.py b/.agents/skills/skill-creator/scripts/validate-metadata.py new file mode 100644 index 0000000..24b77bd --- /dev/null +++ b/.agents/skills/skill-creator/scripts/validate-metadata.py @@ -0,0 +1,50 @@ +import re +import sys +import argparse + +def validate_metadata(name, description): + errors = [] + + # 1. Validate Name Length + if not (1 <= len(name) <= 64): + errors.append(f"NAME ERROR: '{name}' is {len(name)} characters. Must be between 1-64.") + + # 2. Validate Name Characters (lowercase, numbers, single hyphens) + # Regex: Starts/ends with alphanumeric, allows single hyphens in between + if not re.match(r"^[a-z0-9]+(-[a-z0-9]+)*$", name): + errors.append( + f"NAME ERROR: '{name}' contains invalid characters. " + "Use only lowercase letters, numbers, and single hyphens. " + "No consecutive hyphens, and cannot start/end with a hyphen." + ) + + # 3. Validate Description Length + if len(description) > 1024: + errors.append( + f"DESCRIPTION ERROR: Description is {len(description)} characters. " + "Must be 1,024 characters or fewer." + ) + + # 4. Check for Third-Person Perspective (Basic Heuristic) + first_person_words = {"i", "me", "my", "we", "our", "you", "your"} + desc_words = set(re.findall(r'\b\w+\b', description.lower())) + found_forbidden = first_person_words.intersection(desc_words) + if found_forbidden: + errors.append( + f"STYLE WARNING: Description contains first/second person terms: {found_forbidden}. " + "Use third-person imperative (e.g., 'Creates...', 'Updates...')." + ) + + if errors: + print("\n".join(errors), file=sys.stderr) + sys.exit(1) + else: + print("SUCCESS: Metadata is valid and optimized for discovery.") + sys.exit(0) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Validate skill metadata name and description") + parser.add_argument("--name", required=True, help="Skill name (lowercase, hyphen-separated)") + parser.add_argument("--description", default="", help="Skill description") + args = parser.parse_args() + validate_metadata(args.name, args.description) diff --git a/.claude/skills/skill-creator/SKILL.md b/.claude/skills/skill-creator/SKILL.md new file mode 100644 index 0000000..03495e7 --- /dev/null +++ b/.claude/skills/skill-creator/SKILL.md @@ -0,0 +1,45 @@ +--- +name: skill-creator +description: Authors and structures professional-grade agent skills following the agentskills.io spec. Use when creating new skill directories, drafting procedural instructions, or optimizing metadata for discoverability. Don't use for general documentation, non-agentic library code, or README files. +--- + +# Skill Authoring Procedure + +Follow these steps to generate a skill that adheres to the agentskills.io specification and progressive disclosure principles. + +## Step 1: Initialize and Validate Metadata +1. Define a unique `name`: 1-64 characters, lowercase, numbers, and single hyphens only. +2. Draft a `description`: Max 1,024 characters, written in the third person, including negative triggers. +3. **Execute Validation Script:** Run the validation script to ensure compliance before proceeding: + `python3 scripts/validate-metadata.py --name "[name]" --description "[description]"` +4. If the script returns an error, self-correct the metadata based on the `stderr` output and re-run until successful. + +## Step 2: Structure the Directory +1. Create the root directory using the validated `name`. +2. Initialize the following subdirectories: + * `scripts/`: For tiny CLI tools and deterministic logic. + * `references/`: For flat (one-level deep) context like schemas or API docs. + * `assets/`: For output templates, JSON schemas, or static files. +3. Ensure no human-centric files (README.md, INSTALLATION.md) are created. + +## Step 3: Draft Core Logic (SKILL.md) +1. Use the template in `assets/skill-template.md` as the starting point. +2. Write all instructions in the **third-person imperative** (e.g., "Extract the text," "Run the build"). +3. **Enforce Progressive Disclosure:** + * Keep the main logic under 500 lines. + * If a procedure requires a large schema or complex rule set, move it to `references/`. + * Command the agent to read the specific file only when needed: *"Read references/api-spec.md to identify the correct endpoint."* + +## Step 4: Identify and Bundle Scripts +1. Identify "fragile" tasks (regex, complex parsing, or repetitive boilerplate). +2. Outline a single-purpose script for the `scripts/` directory. +3. Ensure the script uses standard output (stdout/stderr) to communicate success or failure to the agent. + +## Step 5: Final Logic Validation +1. Review the `SKILL.md` for "hallucination gaps" (points where the agent is forced to guess). +2. Verify all file paths are **relative** and use forward slashes (`/`). +3. Cross-reference the final output against `references/checklist.md`. + +## Error Handling +* **Metadata Failure:** If `scripts/validate-metadata.py` fails, identify the specific error (e.g., "STYLE ERROR") and rewrite the field to remove first/second person pronouns. +* **Context Bloat:** If the draft exceeds 500 lines, extract the largest procedural block and move it to a file in `references/`. diff --git a/.claude/skills/skill-creator/assets/SKILL.template.md b/.claude/skills/skill-creator/assets/SKILL.template.md new file mode 100644 index 0000000..cb61cfc --- /dev/null +++ b/.claude/skills/skill-creator/assets/SKILL.template.md @@ -0,0 +1,21 @@ +--- +name: [skill-name] +description: [Action-oriented capability description in the third person. Max 1,024 characters. Include positive triggers. Do not use for [explicit negative triggers].] +--- + +# [Skill Title] + +## Procedures + +**Step 1: [Action Phase]** +1. [Third-person imperative instruction, e.g., "Extract the query parameters..."] +2. [Instruction referencing an asset, e.g., "Read `assets/template.json` to structure the final output."] + +**Step 2: [Action Phase]** +1. [Decision tree/conditional logic, e.g., "If source maps are required, run `scripts/build.sh`. Otherwise, skip to Step 3."] +2. [Instruction requiring JiT loading, e.g., "Read `references/auth-flow.md` to map the specific error codes."] +3. Execute `python scripts/[script-name].py` to [perform deterministic action]. + +## Error Handling +* If `scripts/[script-name].py` fails due to [specific edge case], execute [recovery step]. +* If [condition B occurs], read `references/[troubleshooting-file].md`. diff --git a/.claude/skills/skill-creator/references/checklist.md b/.claude/skills/skill-creator/references/checklist.md new file mode 100644 index 0000000..2c9fcef --- /dev/null +++ b/.claude/skills/skill-creator/references/checklist.md @@ -0,0 +1,32 @@ +# Agent Skill Validation Checklist + +Use this checklist to perform a final audit of the generated skill before deployment. Every item must be marked as "Pass" to ensure the skill is discoverable, lean, and deterministic. + +## 1. Metadata & Discovery +* [ ] **Naming:** The `name` field is 1-64 characters, lowercase, and contains only numbers or single hyphens. +* [ ] **Directory Match:** The `name` field exactly matches the parent directory name. +* [ ] **Description Length:** The description is under 1,024 characters. +* [ ] **Trigger Optimization:** The description includes both use cases ("Use when...") and negative triggers ("Don't use for..."). +* [ ] **Third-Person Tone:** The description avoids "I", "me", "my", "you", or "your". + +## 2. File Structure & Paths +* [ ] **Flat Hierarchy:** All files in `scripts/`, `references/`, and `assets/` are exactly one level deep (no nested subfolders). +* [ ] **Standard Folders:** Only use `scripts/`, `references/`, and `assets/`. +* [ ] **No Human Docs:** The directory contains NO `README.md`, `CHANGELOG.md`, or `INSTALLATION_GUIDE.md`. +* [ ] **Forward Slashes:** All file paths in `SKILL.md` use forward slashes (`/`) regardless of the operating system. + +## 3. Logic & Instructions (SKILL.md) +* [ ] **Lean Context:** The `SKILL.md` file is under 500 lines. +* [ ] **Imperative Mood:** Instructions use direct commands (e.g., "Extract," "Run," "Validate"). +* [ ] **Deterministic Steps:** The workflow is a numbered, chronological sequence with clear decision trees. +* [ ] **Progressive Disclosure:** Large schemas, templates, or rule sets are stored in `references/` or `assets/` and read only when needed. +* [ ] **Specific Terminology:** Uses domain-native terms consistently (e.g., "component" instead of "file"). + +## 4. Scripts & Determinism +* [ ] **CLI Design:** Scripts in `scripts/` are designed as tiny CLIs that take arguments. +* [ ] **Feedback Loop:** Scripts provide descriptive `stdout` for success and `stderr` for failure to allow agent self-correction. +* [ ] **No Library Code:** Scripts are single-purpose; complex logic is offloaded to the repository's standard CLI or external tools. + +## 5. Error Handling +* [ ] **Edge Cases:** The `SKILL.md` includes an "Error Handling" section addressing common failure states or missing configurations. +* [ ] **Validation:** The `SKILL.md` includes a step to run validation scripts where applicable. diff --git a/.claude/skills/skill-creator/scripts/validate-metadata.py b/.claude/skills/skill-creator/scripts/validate-metadata.py new file mode 100644 index 0000000..24b77bd --- /dev/null +++ b/.claude/skills/skill-creator/scripts/validate-metadata.py @@ -0,0 +1,50 @@ +import re +import sys +import argparse + +def validate_metadata(name, description): + errors = [] + + # 1. Validate Name Length + if not (1 <= len(name) <= 64): + errors.append(f"NAME ERROR: '{name}' is {len(name)} characters. Must be between 1-64.") + + # 2. Validate Name Characters (lowercase, numbers, single hyphens) + # Regex: Starts/ends with alphanumeric, allows single hyphens in between + if not re.match(r"^[a-z0-9]+(-[a-z0-9]+)*$", name): + errors.append( + f"NAME ERROR: '{name}' contains invalid characters. " + "Use only lowercase letters, numbers, and single hyphens. " + "No consecutive hyphens, and cannot start/end with a hyphen." + ) + + # 3. Validate Description Length + if len(description) > 1024: + errors.append( + f"DESCRIPTION ERROR: Description is {len(description)} characters. " + "Must be 1,024 characters or fewer." + ) + + # 4. Check for Third-Person Perspective (Basic Heuristic) + first_person_words = {"i", "me", "my", "we", "our", "you", "your"} + desc_words = set(re.findall(r'\b\w+\b', description.lower())) + found_forbidden = first_person_words.intersection(desc_words) + if found_forbidden: + errors.append( + f"STYLE WARNING: Description contains first/second person terms: {found_forbidden}. " + "Use third-person imperative (e.g., 'Creates...', 'Updates...')." + ) + + if errors: + print("\n".join(errors), file=sys.stderr) + sys.exit(1) + else: + print("SUCCESS: Metadata is valid and optimized for discovery.") + sys.exit(0) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Validate skill metadata name and description") + parser.add_argument("--name", required=True, help="Skill name (lowercase, hyphen-separated)") + parser.add_argument("--description", default="", help="Skill description") + args = parser.parse_args() + validate_metadata(args.name, args.description) diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..83f454e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,80 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this repo is + +`plain-forge` is an npm package with two distinct halves: + +1. **A tiny installer CLI** (`bin/cli.mjs`) — `npx plain-forge install|update|uninstall`. +2. **The actual product** under `forge/` — a library of AI-agent *skills* and *rules* that teach an agent to author and maintain `***plain` specification files. The CLI copies `forge/` verbatim into an agent directory; there is **no build step** and no generated/committed output. + +So a change here is almost always one of: (a) editing the installer CLI, or (b) editing the instructional content under `forge/skills/` and `forge/rules/`. These need different mindsets — see "Editing `forge/` content" below. + +`plain-forge` only *authors* `.plain` specs. Rendering specs into code is done by a **separate** tool, the `codeplain` CLI (codeplain.ai), which this repo does not contain. + +## Commands + +```bash +npm test # full suite: node --test "test/**/*.test.mjs" +node --test --test-name-pattern="" "test/**/*.test.mjs" # run a single test by name +node --test test/cli.test.mjs # run a single test file +``` + +- Test runner is **Node's built-in `node:test`** (`node:assert/strict`) — not jest/vitest, no test framework dependency. Requires Node ≥18. +- `test/cli.test.mjs` mixes unit tests (importing named exports from `bin/cli.mjs`) with black-box integration tests that `spawnSync` the real CLI into isolated temp `HOME`/cwd dirs. +- **There is no working build or lint step.** `package.json` declares `build`/`clean` scripts (`tsx bin/forge-build.ts`) and `tsconfig.json` references `bin/**/*.ts` + `runtimes/**/*.ts`, but **those files do not exist** — `npm run build`/`npm run clean` error out. The TS toolchain (tsx/typescript) is vestigial; the CLI is plain `.mjs` run directly by Node. Don't try to build. + +## The installer CLI (`bin/cli.mjs`) + +A single self-contained ESM file with **zero runtime dependencies**; it exports its internals so the test suite can import them without running `main()` (guarded by `isInvokedDirectly()`, which realpath-compares `argv[1]` to `__filename` — needed because the global bin is a symlink). Key model: + +- `AGENTS` maps agent name → content dir: `claude→.claude`, + `codex|copilot|universal→.agents`, `forgecode→.forge`, and `opencode→.opencode`. + `SCOPES`: `project` (cwd) / `global` (`$HOME`). Global ForgeCode and OpenCode paths have explicit + exceptions in `resolveBaseDir`. `CONTENT_DIRS = [skills, rules, docs]` (missing source dirs are + silently skipped). +- **install** writes `forge/{skills,rules,docs}` into `/`, recording every written file in `/.plain-forge/manifest.json`. It **refuses** (exit 1) if a manifest or a "forge signature" already exists — install never overwrites in place; you use `update` for that. +- **update** auto-detects every install across both scopes × all agents, re-copies the fresh tree, and **prunes** files that were in the old manifest but no longer ship (confirmed individually unless `--yes`). Only manifest-recorded files are ever prune candidates, so the user's own/third-party files are never touched. +- **uninstall** deletes exactly `manifest.files` then the manifest; refuses (exit 1) on a manifest-less install rather than guessing which files are its own. +- Legacy (manifest-less) installs are recognized only when **all** of `FORGE_SIGNATURE_SKILLS` (`forge-plain`, `add-feature`, `debug-specs`, `load-plain-reference`) are present, then refreshed and given a manifest going forward. + +When changing install/update/uninstall behavior, update `test/cli.test.mjs` accordingly — it is the spec for this CLI. + +## Editing `forge/` content (skills + rules) + +This is the heart of the product. Everything under `forge/` is **instructional text consumed by an AI agent at author-time**, not code that executes. Four consequences: + +- **`forge/rules/` is the sole source of truth for writing `.plain`.** Every syntax rule, + section-ownership rule, constraint, and canonical `.plain` example belongs in the applicable rule + file. Do not duplicate authoring guidance in skills or skill references; duplication drifts and + creates contradictory instructions. +- **`load-plain-reference` is a router, not a language guide.** Its `SKILL.md` stays concise and maps + the current task to only the applicable files under `forge/rules/`. It may route to focused + operational references for project layout or rendering/testing, but those references must not + restate `.plain` authoring rules. Integration rules load only for integration work. Claude skips + rereading rules already supplied natively; other agents read only rules not already in context. +- **Example hygiene is load-bearing.** The rules teach `.plain` syntax, and a `.plain` example written the wrong way *leaks* — agents imitate it. Bare-continuation examples may appear **only** inside blocks explicitly labeled BAD / WRONG / `Before:` / `Too complex:`. Every "good"/"after"/"acceptable"/canonical/`## Format` example must make each line a valid `- ` list item. (`forge/rules/bullet-continuation.md` is the canonical rule.) +- **Skills are narrow and chained.** Each `forge/skills//SKILL.md` has YAML frontmatter (`name` = dir name, `description` = a `>-` block ending in a "Use when…" trigger) and a prose body. Most spec-touching skills invoke `load-plain-reference` first so it can select the relevant rules. Large or operational detail belongs in a skill's `references/` directory and loads only when needed. Only the three test-script generators (`implement-{unit-testing,conformance-testing,prepare-environment}-script`) carry an `assets/` dir of reference `.sh`/`.ps1` scripts. + +### The `.plain` model the content describes + +A `.plain` file is a module: YAML frontmatter (`description`, `import:`, `requires:`, `exported_concepts:`, `required_concepts:`) plus up to five triple-asterisk sections. **Where a fact lives is strict and enforced** — the renderer reads each concept only from its owning section, so a misplaced fact is silently ignored: + +| Section | Owns | +|---|---| +| `***definitions***` | concepts as `:CamelCaseToken:`, define-before-use, globally unique, no cycles | +| `***implementation reqs***` | HOW it's built (tech/architecture) **and everything about `:UnitTests:`** | +| `***functional specs***` | WHAT it does — observable, language-agnostic; each spec ≤200 changed LOC; rendered incrementally top-to-bottom | +| `***test reqs***` | **everything about `:ConformanceTests:`** (framework, run command, mocking/network policy) | +| `***acceptance tests***` | nested under a functional spec (not top-level); full end-to-end workflows | + +Other constraints the rules enforce (see `forge/rules/`): functional specs are mandatorily routed through `add-functional-spec(s)` (never hand-authored), checked for the 200-LOC limit (`analyze-if-func-spec-too-complex` → `break-down-func-spec`) and for conflicts (`analyze-func-specs` → `resolve-spec-conflict`); external artifacts (JSON Schema, OpenAPI, payloads) are **linked** as single local text files under `resources/`, never transcribed and never folders/URLs/binaries; generated code under `plain_modules/`/`conformance_tests/` is read-only — fixes go back into the spec and re-render. + +### The skill lifecycle (orchestration) + +`forge-plain` is the top-level orchestrator: a gated four-phase, **one-question-at-a-time, write-to-disk-immediately** interview — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (`codeplain … --dry-run` gate) then hand off the render command. `add-feature` is the same loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold; `run-codeplain` supervises a live `codeplain --headless` render (tails `codeplain.log` as ground truth, classifies retry loops, hands off to edit skills). The installer ships `forge/rules/*.md` beside the skills; native rule consumers load them directly, while other agents reach them through `load-plain-reference`. + +## Memory / persistent context + +- Generated artifacts and project scratch (`plain_modules/`, `conformance_tests/`, `test_scripts/`, `*.yaml`, `codeplain.log`, env files) are gitignored. diff --git a/CLAUDE.md b/CLAUDE.md index 88e5d1b..83f454e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,7 +29,11 @@ node --test test/cli.test.mjs # run a single test f A single self-contained ESM file with **zero runtime dependencies**; it exports its internals so the test suite can import them without running `main()` (guarded by `isInvokedDirectly()`, which realpath-compares `argv[1]` to `__filename` — needed because the global bin is a symlink). Key model: -- `AGENTS` maps agent name → dot-dir: `claude→.claude`, `codex→.codex`, `forgecode→.forgecode`, `universal→.agents`. `SCOPES`: `project` (cwd) / `global` (`$HOME`). `CONTENT_DIRS = [skills, rules, docs]` (note: `forge/docs/` does not currently exist on disk and is silently skipped). +- `AGENTS` maps agent name → content dir: `claude→.claude`, + `codex|copilot|universal→.agents`, `forgecode→.forge`, and `opencode→.opencode`. + `SCOPES`: `project` (cwd) / `global` (`$HOME`). Global ForgeCode and OpenCode paths have explicit + exceptions in `resolveBaseDir`. `CONTENT_DIRS = [skills, rules, docs]` (missing source dirs are + silently skipped). - **install** writes `forge/{skills,rules,docs}` into `/`, recording every written file in `/.plain-forge/manifest.json`. It **refuses** (exit 1) if a manifest or a "forge signature" already exists — install never overwrites in place; you use `update` for that. - **update** auto-detects every install across both scopes × all agents, re-copies the fresh tree, and **prunes** files that were in the old manifest but no longer ship (confirmed individually unless `--yes`). Only manifest-recorded files are ever prune candidates, so the user's own/third-party files are never touched. - **uninstall** deletes exactly `manifest.files` then the manifest; refuses (exit 1) on a manifest-less install rather than guessing which files are its own. @@ -39,10 +43,19 @@ When changing install/update/uninstall behavior, update `test/cli.test.mjs` acco ## Editing `forge/` content (skills + rules) -This is the heart of the product. Everything under `forge/` is **instructional text consumed by an AI agent at author-time**, not code that executes. Two consequences: +This is the heart of the product. Everything under `forge/` is **instructional text consumed by an AI agent at author-time**, not code that executes. Four consequences: -- **Example hygiene is load-bearing.** The rules teach `.plain` syntax, and a `.plain` example written the wrong way *leaks* — agents imitate it. The rule is: bare-continuation / line-wrapped `.plain` examples may appear **only** inside blocks explicitly labeled BAD / WRONG / `Before:` / `Too complex:`. Every "good"/"after"/"acceptable"/canonical/`## Format` example must use proper nested `- ` bullets, each line a `- ` list item, ≤120 chars. (`forge/rules/line-length.md` is the canonical rule.) -- **Skills are narrow and chained.** Each `forge/skills//SKILL.md` has YAML frontmatter (`name` = dir name, `description` = a `>-` block ending in a "Use when…" trigger) and a prose body. Most spec-touching skills open by invoking `load-plain-reference` first ("only if you haven't done so yet"). Only the three test-script generators (`implement-{unit-testing,conformance-testing,prepare-environment}-script`) carry an `assets/` dir of reference `.sh`/`.ps1` scripts. +- **`forge/rules/` is the sole source of truth for writing `.plain`.** Every syntax rule, + section-ownership rule, constraint, and canonical `.plain` example belongs in the applicable rule + file. Do not duplicate authoring guidance in skills or skill references; duplication drifts and + creates contradictory instructions. +- **`load-plain-reference` is a router, not a language guide.** Its `SKILL.md` stays concise and maps + the current task to only the applicable files under `forge/rules/`. It may route to focused + operational references for project layout or rendering/testing, but those references must not + restate `.plain` authoring rules. Integration rules load only for integration work. Claude skips + rereading rules already supplied natively; other agents read only rules not already in context. +- **Example hygiene is load-bearing.** The rules teach `.plain` syntax, and a `.plain` example written the wrong way *leaks* — agents imitate it. Bare-continuation examples may appear **only** inside blocks explicitly labeled BAD / WRONG / `Before:` / `Too complex:`. Every "good"/"after"/"acceptable"/canonical/`## Format` example must make each line a valid `- ` list item. (`forge/rules/bullet-continuation.md` is the canonical rule.) +- **Skills are narrow and chained.** Each `forge/skills//SKILL.md` has YAML frontmatter (`name` = dir name, `description` = a `>-` block ending in a "Use when…" trigger) and a prose body. Most spec-touching skills invoke `load-plain-reference` first so it can select the relevant rules. Large or operational detail belongs in a skill's `references/` directory and loads only when needed. Only the three test-script generators (`implement-{unit-testing,conformance-testing,prepare-environment}-script`) carry an `assets/` dir of reference `.sh`/`.ps1` scripts. ### The `.plain` model the content describes @@ -60,7 +73,7 @@ Other constraints the rules enforce (see `forge/rules/`): functional specs are m ### The skill lifecycle (orchestration) -`forge-plain` is the top-level orchestrator: a gated four-phase, **one-question-at-a-time, write-to-disk-immediately** interview — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (`codeplain … --dry-run` gate) then hand off the render command. `add-feature` is the same loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold; `run-codeplain` supervises a live `codeplain --headless` render (tails `codeplain.log` as ground truth, classifies retry loops, hands off to edit skills). `forge/rules/*.md` carry `globs: "**/*.plain"` and are installed as workspace instructions. +`forge-plain` is the top-level orchestrator: a gated four-phase, **one-question-at-a-time, write-to-disk-immediately** interview — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (`codeplain … --dry-run` gate) then hand off the render command. `add-feature` is the same loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold; `run-codeplain` supervises a live `codeplain --headless` render (tails `codeplain.log` as ground truth, classifies retry loops, hands off to edit skills). The installer ships `forge/rules/*.md` beside the skills; native rule consumers load them directly, while other agents reach them through `load-plain-reference`. ## Memory / persistent context diff --git a/README.md b/README.md index 6bd0b05..6cf6f15 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Pick whichever entry point matches how much upfront design you want: ### 2. Grow an existing project - **`add-feature`** — takes a feature request in plain English and runs the same one-question-at-a-time loop that `forge-plain` uses, scoped to a single feature against an existing `.plain` file. -- **Per-section authoring skills** — `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `add-test-requirement`, `add-concept`, `add-acceptance-test`, `add-resource`, `add-template`. Each enforces the relevant ***plain syntax rules (concept uniqueness, complexity limits, line-length, linked-resource constraints, …) so hand-authoring doesn't drift from the language. +- **Per-section authoring skills** — `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `add-test-requirement`, `add-concept`, `add-acceptance-test`, `add-resource`, `add-template`. Each enforces the relevant ***plain syntax rules (concept uniqueness, complexity limits, bullet syntax, linked-resource constraints, …) so hand-authoring doesn't drift from the language. - **Module-management skills** — `create-import-module`, `create-requires-module`, `refactor-module`, `consolidate-concepts` for restructuring as the project grows. ### 3. Validate and maintain @@ -62,11 +62,15 @@ npx plain-forge install --agent claude --scope project |-----------|---------------|----------| | `claude` | `.claude/` · `~/.claude/` | You use Claude Code | | `codex` | `.agents/` · `~/.agents/` | You use the OpenAI Codex CLI | +| `copilot` | `.agents/` · `~/.agents/` | You use GitHub Copilot | | `forgecode` | `.forge/` · `~/forge/` | You use ForgeCode | | `opencode` | `.opencode/` · `~/.config/opencode/` | You use OpenCode | | `universal` | `.agents/` · `~/.agents/` | You want a runtime-neutral layout that any agent reading from `.agents/` can pick up | -> **Why `codex` and `forgecode` don't install into `.codex/` or `.forgecode/`.** Those tools load *skills* from specific directories that are **not** named after the tool. Per the current (2026) docs, Codex reads skills from `.agents/skills` (`~/.agents/skills` globally) — `.codex/` is config-only — and ForgeCode reads them from `.forge/skills` (`~/forge/skills` globally). plain-forge installs where the skills will actually load. As a result `codex` and `universal` share the `.agents/` layout; the install manifest records which agent you chose so `update`/`uninstall` behave correctly. +> **Why several agents share `.agents/`.** Codex and GitHub Copilot both discover skills from +> `.agents/skills` (`~/.agents/skills` globally), which is also the runtime-neutral `universal` +> layout. ForgeCode instead reads `.forge/skills` (`~/forge/skills` globally). The manifest records +> which agent label you chose so `update` and `uninstall` report it correctly. **Scope options:** @@ -88,16 +92,19 @@ Each install writes three subfolders under the chosen directory: #### How the rules get applied per agent -The `skills/` are discovered natively by every supported agent. The `rules/` (the `.plain`-authoring guidance) are applied differently because each agent loads instruction files its own way — so plain-forge does a bit of extra, agent-specific wiring: +The `skills/` are discovered natively by every supported agent. The `load-plain-reference` skill +loads only the rules relevant to the current task and skips rules already present in context. Some +agents additionally support native rules wiring: | Agent | How rules are applied | |-------|-----------------------| | **Claude Code** | Native — Claude Code auto-loads `.claude/rules/*.md` at launch. Each rule carries `paths: "**/*.plain"` frontmatter so Claude scopes it to `.plain` files. No extra wiring. | | **OpenCode** | OpenCode doesn't auto-load a `rules/` dir; it reads globs from the `instructions` array in `opencode.json`. plain-forge **merges** a rules glob there — `.opencode/rules/*.md` (project, written to `./opencode.json`) or the absolute `~/.config/opencode/rules/*.md` (global, written to `~/.config/opencode/opencode.json`; absolute because OpenCode doesn't expand `~`). | -| **Codex / ForgeCode** | Neither auto-loads a `rules/` dir; both read custom instructions only from `AGENTS.md`. plain-forge appends a fenced, **managed pointer block** to `AGENTS.md` telling the agent to read the installed rule files. Project → repo-root `./AGENTS.md`; global → `~/.codex/AGENTS.md` (Codex) or `~/forge/AGENTS.md` (ForgeCode). | -| **Universal** | Verbatim copy only — the rules land in `.agents/rules/`, and it's up to whichever agent reads `.agents/` to consume them. No config is generated. | +| **ForgeCode** | plain-forge also appends a fenced managed pointer block to `AGENTS.md`: repo-root `./AGENTS.md` for project installs or `~/forge/AGENTS.md` globally. | +| **Codex / GitHub Copilot / Universal** | No extra instruction file is generated. Their discovered `load-plain-reference` skill loads applicable files from `.agents/rules/` when needed. | -In every case where plain-forge touches a config or `AGENTS.md` file, it **merges** into what's already there — adding only its own glob/block and leaving all your other content intact — and it never writes into a malformed config (it warns instead). `uninstall` reverses the wiring: it removes only plain-forge's glob/block, deleting the file only if it contained nothing else. +When plain-forge touches `opencode.json` or ForgeCode's `AGENTS.md`, it merges into existing content +and leaves unrelated configuration intact. `uninstall` removes only plain-forge's glob or block. `install` refuses to run if plain-forge is already present at the target directory — it prints a message pointing you at `update` and exits non-zero. Use `update` (below) to refresh an existing install. @@ -129,7 +136,7 @@ npx plain-forge uninstall --agent claude --scope global | Flag | Default | Values | |------|---------|--------| -| `--agent` | `*` (all agents) | `claude`, `codex`, `forgecode`, `opencode`, `universal`, or `*` | +| `--agent` | `*` (all agents) | `claude`, `codex`, `copilot`, `forgecode`, `opencode`, `universal`, or `*` | | `--scope` | `project` | `project` (cwd) or `global` (home directory) | If an install has **no manifest** (e.g. one that predates manifests), `uninstall` cannot tell which files are plain-forge's, so it refuses to delete anything: it prints an error, lists the directories to clean up by hand, and exits non-zero. Refresh such an install with `update` first (which writes a manifest going forward), then `uninstall`. @@ -209,7 +216,7 @@ test/ package.json # ships only `bin/cli.mjs` and `forge/` to npm ``` -On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them into the chosen agent directory (`.claude/`, `.agents/` for Codex/universal, `.forge/`, or `.opencode/` — see the per-agent table above for global-scope paths), recording every file it wrote in `/.plain-forge/manifest.json` so `update` can later refresh and prune precisely. For agents that need it, it also wires the rules into `opencode.json` or `AGENTS.md` (see [How the rules get applied per agent](#how-the-rules-get-applied-per-agent)). +On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them into the chosen agent directory (`.claude/`, `.agents/` for Codex/Copilot/universal, `.forge/`, or `.opencode/` — see the per-agent table above for global-scope paths), recording every file it wrote in `/.plain-forge/manifest.json` so `update` can later refresh and prune precisely. For agents that need it, it also wires the rules into `opencode.json` or ForgeCode's `AGENTS.md` (see [How the rules get applied per agent](#how-the-rules-get-applied-per-agent)). ## Available Skills diff --git a/bin/cli.mjs b/bin/cli.mjs index ef86bcf..0b25bfa 100755 --- a/bin/cli.mjs +++ b/bin/cli.mjs @@ -13,7 +13,8 @@ const forgeDir = path.join(pkgRoot, "forge"); // These are the directories each tool actually loads skills from (verified // against the 2026 docs), NOT necessarily a dir named after the tool: // - Codex reads skills from .agents/skills (never .codex/skills); .codex is -// config-only. So codex shares the .agents layout with `universal`. +// config-only. So codex shares the .agents layout with `copilot` and +// `universal`. // - ForgeCode reads skills from .forge/skills (never .forgecode/skills); its // global dir is ~/forge (no dot) — see resolveBaseDir. // - OpenCode reads project skills from .opencode; global is ~/.config/opencode @@ -23,6 +24,7 @@ const forgeDir = path.join(pkgRoot, "forge"); const AGENTS = { claude: ".claude", codex: ".agents", + copilot: ".agents", forgecode: ".forge", opencode: ".opencode", universal: ".agents", @@ -53,16 +55,32 @@ function hasForgeSignature(baseDir) { ); } -const BANNER = `\x1b[38;2;224;255;110m██████╗ ██╗ █████╗ ██╗███╗ ██╗ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗ +const BANNER = `██████╗ ██╗ █████╗ ██╗███╗ ██╗ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗ ██╔══██╗██║ ██╔══██╗██║████╗ ██║ ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██╔════╝ ██████╔╝██║ ███████║██║██╔██╗ ██║█████╗█████╗ ██║ ██║██████╔╝██║ ███╗█████╗ ██╔═══╝ ██║ ██╔══██║██║██║╚██╗██║╚════╝██╔══╝ ██║ ██║██╔══██╗██║ ██║██╔══╝ ██║ ███████╗██║ ██║██║██║ ╚████║ ██║ ╚██████╔╝██║ ██║╚██████╔╝███████╗ -╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝\x1b[0m +╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ `; const TAGLINE = "turn ideas into ***plain specs."; +function terminalHasLightBackground(env = process.env) { + const parts = String(env.COLORFGBG ?? "").split(";"); + const background = Number(parts.at(-1)); + if (!Number.isInteger(background)) return false; + return background === 7 || background >= 9; +} + +function terminalPalette(env = process.env) { + return terminalHasLightBackground(env) + ? { brand: "76;92;0", plain: "0;105;55", link: "0;85;170" } + : { brand: "224;255;110", plain: "121;252;150", link: "95;175;255" }; +} + +const color = (rgb, text, extra = "") => + `\x1b[${extra}38;2;${rgb}m${text}\x1b[0m`; + function stripAnsi(s) { return s.replace(/\x1b\[[0-9;]*m/g, ""); } @@ -75,13 +93,14 @@ function bannerWidth() { function printBanner() { if (!process.stdout.isTTY) return; - process.stdout.write("\n" + BANNER + "\n"); + const palette = terminalPalette(); + process.stdout.write("\n" + color(palette.brand, BANNER) + "\n"); const width = bannerWidth(); const pad = Math.max(0, Math.floor((width - TAGLINE.length) / 2)); const tag = TAGLINE.replace( "***plain", - "\x1b[38;2;121;252;150m***plain\x1b[0m", + color(palette.plain, "***plain"), ); process.stdout.write(" ".repeat(pad) + tag + "\n\n"); } @@ -95,7 +114,7 @@ Commands: uninstall Remove a plain-forge install using its manifest Install options: - --agent + --agent Target agent layout --scope Install into cwd or $HOME -h, --help Show this help @@ -105,7 +124,7 @@ Update options: confirming each one Uninstall options: - --agent + --agent Which install to remove (default: * — every agent) --scope Where to look (default: project) @@ -462,10 +481,10 @@ function reportOpencodeUnmerge(result) { } } -// --- Codex / ForgeCode instructions wiring ---------------------------------- -// Neither tool auto-reads a rules/ directory; both load custom instructions -// only from AGENTS.md (Codex: repo AGENTS.md or ~/.codex/AGENTS.md; ForgeCode: -// repo AGENTS.md or ~/forge/AGENTS.md). AGENTS.md has no include/glob mechanism, +// --- ForgeCode instructions wiring ------------------------------------------ +// ForgeCode does not auto-read a rules/ directory; it loads custom instructions +// from AGENTS.md (repo AGENTS.md or ~/forge/AGENTS.md). AGENTS.md has no +// include/glob mechanism, // so we append a managed pointer block that tells the agent to read our rules // dir when touching .plain files. The block is fenced by markers so it can be // refreshed or removed idempotently without disturbing the user's own content. @@ -476,7 +495,7 @@ const AGENTS_MD_END = ""; // Agents whose rules are delivered through AGENTS.md rather than a natively // auto-loaded rules/ dir (claude) or a config glob (opencode). function usesAgentsMd(agent) { - return agent === "codex" || agent === "forgecode"; + return agent === "forgecode"; } const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); @@ -485,8 +504,10 @@ const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // root (cwd) for both tools; global scope differs per tool. function agentsMdPath(agent, scope) { if (scope === "project") return path.join(process.cwd(), "AGENTS.md"); - if (agent === "forgecode") return path.join(os.homedir(), "forge", "AGENTS.md"); - return path.join(os.homedir(), ".codex", "AGENTS.md"); // codex global + // Retained only so update/uninstall can remove blocks created by older Codex + // installs. New Codex installs do not create this file. + if (agent === "codex") return path.join(os.homedir(), ".codex", "AGENTS.md"); + return path.join(os.homedir(), "forge", "AGENTS.md"); } // Glob the pointer block references — the installed rules dir. Project scope is @@ -603,6 +624,10 @@ function wireRules(agent, scope) { reportOpencodeMerge(mergeOpencodeInstructions(scope)); } else if (usesAgentsMd(agent)) { reportAgentsMdMerge(mergeAgentsMd(agent, scope)); + } else if (agent === "codex") { + // Migrate installs from the former Codex AGENTS.md wiring. Rules are now + // loaded by load-plain-reference itself. + reportAgentsMdUnmerge(unmergeAgentsMd(agent, scope)); } } catch (err) { warnRulesWiring("wire", agent, err); @@ -616,7 +641,7 @@ function unwireRules(agent, scope) { try { if (agent === "opencode") { reportOpencodeUnmerge(unmergeOpencodeInstructions(scope)); - } else if (usesAgentsMd(agent)) { + } else if (usesAgentsMd(agent) || agent === "codex") { reportAgentsMdUnmerge(unmergeAgentsMd(agent, scope)); } } catch (err) { @@ -655,8 +680,8 @@ function writeManifest(baseDir, files, agent) { const target = manifestPathFor(baseDir); fs.mkdirSync(path.dirname(target), { recursive: true }); // `agent` records which agent layout produced this install. It matters when - // two agents resolve to the same dir (codex and universal both use .agents): - // it lets detect/update/uninstall know whether AGENTS.md rule-wiring applies. + // Multiple agents resolve to the same .agents dir, so the manifest preserves + // the selected label for detection, updates, and uninstall output. const manifest = { name: "plain-forge", version: readPkgVersion(), @@ -714,7 +739,7 @@ function detectInstalls() { for (const scope of SCOPES) { for (const agent of Object.keys(AGENTS)) { const baseDir = resolveBaseDir(agent, scope); - // Some agents share a directory (codex and universal both use .agents). + // Some agents share the .agents directory. // Only inspect each physical dir once so a single install isn't detected // — and later updated/pruned — twice. if (seen.has(baseDir)) continue; @@ -929,8 +954,7 @@ async function cmdUninstall(args) { ); // Undo the rules wiring (opencode.json / AGENTS.md). Keyed off the agent - // recorded at install time, not the requested name — codex and universal - // share the .agents dir, and only the codex install wired AGENTS.md. Done + // recorded at install time, not the requested name. Done // before the dir cleanup so a now-orphaned config file living inside baseDir // (e.g. forgecode-global ~/forge/AGENTS.md) can be removed and let baseDir // itself be pruned. @@ -960,11 +984,12 @@ async function cmdUninstall(args) { } function printNextSteps(agent) { - const bold = (s) => `\x1b[1;97m${s}\x1b[0m`; + const palette = terminalPalette(); + const bold = (s) => `\x1b[1m${s}\x1b[0m`; const dim = (s) => `\x1b[2m${s}\x1b[0m`; - const plain = (s) => `\x1b[38;2;121;252;150m${s}\x1b[0m`; - const codeplain = (s) => `\x1b[38;2;224;255;110m${s}\x1b[0m`; - const link = (s) => `\x1b[4;38;2;95;175;255m${s}\x1b[0m`; + const plain = (s) => color(palette.plain, s); + const codeplain = (s) => color(palette.brand, s); + const link = (s) => color(palette.link, s, "4;"); console.log(`\x1b[1mnext steps:\x1b[0m`); console.log( @@ -996,6 +1021,8 @@ function agentLabel(agent) { return "Claude Code"; case "codex": return "Codex"; + case "copilot": + return "GitHub Copilot"; case "forgecode": return "ForgeCode"; case "opencode": @@ -1090,4 +1117,6 @@ export { deleteForgeFile, detectInstalls, promptConfirm, + terminalHasLightBackground, + terminalPalette, }; diff --git a/forge/rules/bullet-continuation.md b/forge/rules/bullet-continuation.md new file mode 100644 index 0000000..63bb904 --- /dev/null +++ b/forge/rules/bullet-continuation.md @@ -0,0 +1,59 @@ +--- +description: Bullet and continuation syntax for every section in .plain files +--- + +# Rules for bullets and continuations in `.plain` files + +These rules apply to every section and to concept explanations. + +## No line-length limit + +- `.plain` does not impose a maximum number of characters per line +- Do not split a valid line merely to satisfy an arbitrary formatting width +- Prefer clear, precise wording; concision must never remove required detail + +## Never use bare continuation lines + +- Every line inside a section must be a list item beginning with `- ` +- An indented continuation without `- ` is invalid syntax +- If content is intentionally separated across lines, express the additional lines as nested + bullet items so each line remains syntactically valid + +WRONG — bare continuation lines: + +```plain +***functional specs*** + +- :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(), + which returns a list of :EventEnvelope: dicts conforming to the gateway contract. +``` + +GOOD — one valid list item: + +```plain +***functional specs*** + +- :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(), which returns a list of :EventEnvelope: dicts conforming to the gateway contract. +``` + +BEST — separate clarifications expressed as nested bullets: + +```plain +***functional specs*** + +- :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(). + - The method returns a list of :EventEnvelope: dicts. + - The dicts conform to the gateway's :EventEnvelope: contract. +``` + +## Presenting `.plain` examples + +- Show every example under its owning section header +- Separate top-level list items with one blank line +- Keep nested clarifications directly under their parent without a blank line +- BAD, WRONG, `Before:`, and `Too complex:` examples may intentionally demonstrate invalid syntax + +## Content that belongs in resources + +Long URLs, schema fragments, and example payloads belong in `resources/` because they are external +artifacts, not because of their character count. Follow `linked-resources.md`. diff --git a/forge/rules/definitions.md b/forge/rules/definitions.md index 838626a..2ff021a 100644 --- a/forge/rules/definitions.md +++ b/forge/rules/definitions.md @@ -44,14 +44,20 @@ When writing or editing a `***definitions***` section in a `.plain` file, always Bad — circular: ```plain +***definitions*** + - :Customer: is a user who has placed at least one :Order:. + - :Order: is placed by :Customer: and contains :OrderItem: entries. ``` `:Order:` references `:Customer:`, and `:Customer:` references `:Order:`. Fix by removing the back-reference: ```plain +***definitions*** + - :Customer: is a user of the system. + - :Order: is placed by :Customer: and contains :OrderItem: entries. ``` @@ -71,13 +77,21 @@ Bad — circular: Example of defining technical components alongside their behavior: ```plain +***definitions*** + - :CsvToJsonConverter: bidirectionally converts csv and json strings. + - :CsvToJson: of :CsvToJsonConverter: converts a list of csv strings (e.g., "value1,value2,value3") to a list of json strings (e.g., "{\"key1\":\"value1\"}"). + - :JsonToCsv: of :CsvToJsonConverter: converts a list of json strings (e.g., "{\"key1\":\"value1\"}") to a list of csv strings (e.g., "value1,value2"). - :StorageService: is an abstraction for a remote blob storage backend. + - :CsvFileWriter: writes CSV rows to local files, automatically rotates to new files when size limits are reached, and flushes completed files to :StorageService:. + - :CsvFileReader: reads CSV rows in a batched manner. + + - :CsvJoiner: joins csvs and uses :CsvFileWriter: to create the merged CSV. ``` @@ -85,6 +99,7 @@ Example of defining technical components alongside their behavior: ```plain ***definitions*** + - :ConceptName: is a description of what it represents. - Attribute one (required) - Attribute two (optional) diff --git a/forge/rules/func-specs.md b/forge/rules/func-specs.md index a4daf68..0bd3c15 100644 --- a/forge/rules/func-specs.md +++ b/forge/rules/func-specs.md @@ -8,6 +8,37 @@ paths: When writing or editing a `***functional specs***` section in a `.plain` file, always follow these rules: +## Voice +- Write each functional spec as a **statement of fact** — present tense, indicative mood — describing what the software *is* or *does* once built, not as a command telling a developer what to build +- **Name a concrete subject**: an actor (`A :User: can …`), a named component or concept (`:PdfRenderer: lays out …`, `:Order: processing depends on …`), or the affected data in passive voice (`All :Resource: items are returned`) +- **Avoid the vague subject "The system …"** — it says nothing about *which* part does the work. Prefer the concrete component/concept, or passive voice with the affected concept as the subject +- **Never open a spec with an imperative** like "Implement …", "Add …", "Create …", "Show …", "Display …", "Build …" — those read as instructions, not specifications +- Do **not** use "should" / "should be able to" in a functional spec — that is requirement-modal voice; state the fact directly + +BAD — imperative commands, or the vague "The system": + +```plain +***functional specs*** + +- Implement the entry point for :App:. + +- The system shows :TaskList:. + +- Add :Task:. +``` + +GOOD — statements of fact with concrete subjects: + +```plain +***functional specs*** + +- :App: has an entry point. + +- The :TaskList: is displayed. + +- A :User: can add a :Task:. Only valid :Task: items are added. +``` + ## Complexity limit - Each functional spec must imply a **maximum of 200 changed lines of code** - If a spec is too large, use `break-down-func-spec` to split it into multiple smaller, independent specs @@ -48,10 +79,6 @@ When writing or editing a `***functional specs***` section in a `.plain` file, a - `requires` modules only receive functional specs — do not rely on implementation reqs to convey behavior - Behavior that downstream modules need must be expressed in functional specs, not elsewhere -## Line length -- See [`line-length.md`](line-length.md) — applies to every section, but bites hardest here because functional specs trend long -- Hard limit: 120 characters. When a line gets too long, split at a natural clause boundary into nested `- ` bullets — **never** use bare indented continuation lines (invalid ***plain syntax) - ## Acceptance tests - Nest `***acceptance tests***` under a functional spec when verification criteria are needed - Each acceptance test must be a **full workflow test** — a specific scenario that exercises the functional spec end-to-end, not a unit-level check of a single field or condition @@ -65,11 +92,11 @@ When writing or editing a `***functional specs***` section in a `.plain` file, a ```plain ***functional specs*** -- Implement the entry point for :App:. +- :App: has an entry point. -- :User: should be able to add :Task:. Only valid :Task: items can be added. +- A :User: can add a :Task:. Only valid :Task: items are added. -- :User: should be able to send a :Message: to a :Conversation:. +- A :User: can send a :Message: to a :Conversation:. - A :Message: must have non-empty content. - The :Message: is appended to the end of the :Conversation:. - All :Participant: members of the :Conversation: can see the new :Message:. diff --git a/forge/rules/integration-embedded-testing.md b/forge/rules/integration-embedded-testing.md index bdd482c..f856e18 100644 --- a/forge/rules/integration-embedded-testing.md +++ b/forge/rules/integration-embedded-testing.md @@ -6,7 +6,10 @@ paths: # Rules for embedded-integration test scripts -When an embedded integration ships its three `test_scripts/` (prepare-environment, unit, conformance) — whether you author them by hand or via the `implement-*-testing-script` skills — these rules apply on top of the shared testing-script rules in PLAIN_REFERENCE.md (exit-code conventions, the activate-only vs install-inline conformance distinction, `VERBOSE=1`, etc.). +When an embedded integration ships its three `test_scripts/` (prepare-environment, unit, +conformance), these rules apply on top of the contracts in the corresponding +`implement-*-testing-script` skills. Use `load-plain-reference` for the rendering and testing +workflow overview when needed. ## Staging model (read this first) diff --git a/forge/rules/line-length.md b/forge/rules/line-length.md deleted file mode 100644 index db80c73..0000000 --- a/forge/rules/line-length.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -description: Line-length and bullet-continuation rules for every section in .plain files -paths: - - "**/*.plain" ---- - -# Rules for line length in `.plain` files - -These rules apply to **every** section — `***definitions***`, `***implementation reqs***`, `***test reqs***`, `***functional specs***`, `***acceptance tests***` — and to concept explanations alike. - -## Hard limit: 120 characters per line -- If a line exceeds 120 characters, split it at a natural clause boundary into nested `- ` bullets -- Concision is in service of clarity, never the other way around — wordy-but-precise always beats terse-but-ambiguous -- Prefer short, direct sentences and plain words; if a 10-cent word and a 50-cent word say the same thing, use the 10-cent one - -## Never use bare continuation lines (invalid ***plain syntax) -- ***plain syntax requires every line inside a section to be its own list item starting with `- ` -- Indented continuation lines without a leading `- ` are syntactically invalid — they look reasonable to a human reader but the renderer cannot parse them -- When a sentence is too long, **break it into multiple bullet items**, each on its own line, nested under the parent bullet so the meaning stays grouped - -## Examples - -BAD — line is too long: - -```plain -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(), which returns a list of :EventEnvelope: dicts conforming to the gateway's contract. -``` - -WRONG SYNTAX (AVOID AT ALL COSTS) — bare indented continuation without a leading `- `: - -```plain -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(), - which returns a list of :EventEnvelope: dicts conforming to the gateway's - contract. -``` - -GOOD — split at a natural clause boundary into nested `- ` bullets: - -```plain -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle() - - The method returns a list of :EventEnvelope: dicts. - - The dicts must conform to the gateway's :EventEnvelope: contract. -``` - -## What never goes inline -- Long URLs, schema fragments, or example payloads — those belong in `resources/` per [`linked-resources.md`](linked-resources.md) -- If you find yourself pasting a multi-line block into a spec line, stop and link the file instead diff --git a/forge/rules/linked-resources.md b/forge/rules/linked-resources.md index 632a8e3..02b2b64 100644 --- a/forge/rules/linked-resources.md +++ b/forge/rules/linked-resources.md @@ -25,7 +25,7 @@ A linked resource **must not** be any of the following: - Litmus test: "Would the renderer benefit from reading the bytes at this URL / folder?" If yes, save it to a file and link the file. If no (it's a runtime value the code carries forward), it can stay as plain text ## Structured protocol artifacts must be linked, never transcribed -- JSON Schema, OpenAPI / Swagger, GraphQL SDL, Protobuf `.proto`, Avro / Thrift schemas, XML XSDs, AsyncAPI specs, JSON-RPC method definitions, wire-protocol descriptions, payload examples — anything with a formal machine-readable shape — belongs in a file under `resources/` (or a subfolder of the `.plain` file's directory) +- JSON Schema, OpenAPI / Swagger, GraphQL SDL, Protobuf `.proto`, Avro / Thrift schemas, XML XSDs, AsyncAPI specs, JSON-RPC method definitions, wire-protocol descriptions, payload examples — anything with a formal machine-readable shape — belongs in a file under `resources/` (or another directory within the project) - The spec line should describe the *role* of the artifact ("the request body conforms to ...", "the public API surface is defined in ...") rather than its contents - Reasons: one source of truth (no drift between prose and schema); the renderer and the generated code can both consume the file directly; schema changes show up cleanly as diffs @@ -39,7 +39,7 @@ A linked resource **must not** be any of the following: ***functional specs*** -- :User: should be able to add :Task: by POSTing :TaskCreateRequest: to the `POST /tasks` endpoint of :TasksAPI:. +- A :User: can add a :Task: by POSTing :TaskCreateRequest: to the `POST /tasks` endpoint of :TasksAPI:. - The endpoint responds per :TasksAPI:. ``` @@ -56,11 +56,12 @@ A linked resource **must not** be any of the following: ***functional specs*** -- :User: should be able to add :Task: using :TaskModalSpec:. +- A :User: can add a :Task: using :TaskModalSpec:. -- :User: should be able to edit :Task: using :TaskModalSpec:. +- A :User: can edit a :Task: using :TaskModalSpec:. ``` ## File location and path resolution -- Paths are resolved relative to the `.plain` file's directory -- The conventional location is `resources/` under the `.plain` file's directory +- Paths are resolved relative to the directory where `codeplain` is run (the current working directory) +- Relative paths may traverse into parent directories — `[resource](../../resources/resource.md)` is valid — as long as they resolve to a real text file on disk +- The conventional location is a `resources/` directory; keep the resource inside the project so the path stays stable from wherever `codeplain` is run diff --git a/forge/skills/add-acceptance-test/SKILL.md b/forge/skills/add-acceptance-test/SKILL.md index c4e9a16..78b8d0b 100644 --- a/forge/skills/add-acceptance-test/SKILL.md +++ b/forge/skills/add-acceptance-test/SKILL.md @@ -32,10 +32,11 @@ Acceptance tests are nested under the functional spec they verify, using a `***a ```plain ***functional specs*** -- The system should process :Task: items in batches of 100. +- :Task: items are processed in batches of 100. ***acceptance tests*** - Processing 250 :Task: items should result in 3 batches. + - Each batch should contain at most 100 items. ``` @@ -44,29 +45,6 @@ Key formatting rules: - Each test bullet is indented under the `***acceptance tests***` header. - Multiple acceptance tests can be listed under a single functional spec. -### Line syntax (hard rule) - -**Every line inside `***acceptance tests***` must be its own list item starting with `- `.** ***plain has no concept of bare continuation lines — indented prose without a leading `- ` is **invalid syntax** and the renderer will reject it. - -- Hard limit: 120 characters per line. If a sentence is too long, **split it at a natural clause boundary into nested `- ` bullets** — never wrap onto an unprefixed line. -- Nested clarifications are also `- ` items, indented under the parent. The indentation alone is not enough; the leading `- ` is required. - -BAD — bare continuation lines (invalid ***plain syntax, will not render): - -```plain - ***acceptance tests*** - - Processing 250 :Task: items should result in 3 batches with the - last batch containing the remaining 50 items. -``` - -GOOD — every line starts with `- `: - -```plain - ***acceptance tests*** - - Processing 250 :Task: items should result in 3 batches. - - The first two batches each contain 100 items. - - The last batch contains the remaining 50 items. -``` ## Rules @@ -76,7 +54,7 @@ An acceptance test is essentially an **example that illustrates** the functional ``` Functional spec: -- The system should return :Resource: items sorted by creation date in descending order. +- :Resource: items are returned sorted by creation date in descending order. Good (consistent): - The first :Resource: in the response should have the most recent creation date. diff --git a/forge/skills/add-concept/SKILL.md b/forge/skills/add-concept/SKILL.md index 02ecc86..9591a41 100644 --- a/forge/skills/add-concept/SKILL.md +++ b/forge/skills/add-concept/SKILL.md @@ -44,40 +44,21 @@ A concept definition is a bullet in `***definitions***` that starts with the con ```plain ***definitions*** + - :ConceptName: is a description of what it represents. ``` Attributes and constraints are nested sub-bullets: ```plain +***definitions*** + - :Task: describes an activity that needs to be done by :User:. :Task: has: - Name - a short description (required) - Notes - additional details (optional) - Due Date - completion deadline (optional) ``` -## Line syntax (hard rule) - -**Every line inside `***definitions***` must be its own list item starting with `- `.** ***plain has no concept of bare continuation lines — indented prose without a leading `- ` is **invalid syntax** and the renderer will reject it. - -- Hard limit: 120 characters per line. If a sentence is too long, **split it at a natural clause boundary into nested `- ` bullets** — never wrap onto an unprefixed line. -- Nested attributes are also `- ` items, indented under the parent. The indentation alone is not enough; the leading `- ` is required. - -BAD — bare continuation lines (invalid ***plain syntax, will not render): - -```plain -- :Task: describes an activity that needs to be done by :User:. - - Name is a short description that the user provides when creating - the task and is shown in the task list. -``` - -GOOD — every line starts with `- `: - -```plain -- :Task: describes an activity that needs to be done by :User:. - - Name is a short description provided when creating the task. - - The name is shown in the task list. -``` ## Validation Checklist diff --git a/forge/skills/add-feature/SKILL.md b/forge/skills/add-feature/SKILL.md index 2888ccb..67c8f7a 100644 --- a/forge/skills/add-feature/SKILL.md +++ b/forge/skills/add-feature/SKILL.md @@ -1,136 +1,128 @@ --- name: add-feature description: >- - End-to-end feature addition: takes a feature request in plain English and - incrementally writes ***plain specs (concepts, implementation reqs, functional - specs, acceptance tests) one functionality at a time, asking, authoring, and reviewing - per functionality. Use when the user wants to add a new feature to an existing project. + End-to-end feature addition on an existing ***plain project: takes a feature + request in plain English and, one question at a time, incrementally writes + ***plain specs (concepts, implementation reqs, functional specs, test reqs, + acceptance tests) to disk — asking, authoring, and reviewing per + functionality — then closes with a plain-healthcheck gate. Use when the user + wants to add a feature to an existing project. Not for bootstrapping a new + project from scratch (use forge-plain) or for editing generated code (the + .plain specs are the source of truth). --- # Add Feature Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -This skill is the continuous-loop counterpart of the full QA workflow in `forge-plain`. Where that workflow bootstraps an entire project from scratch, `add-feature` adds a single feature to an **existing** set of `.plain` specs. +`add-feature` is the continuous-loop counterpart of `forge-plain`. Where `forge-plain` bootstraps an entire project from scratch, `add-feature` adds a single feature to an **existing** set of `.plain` specs. It runs the same core loop, scoped to one feature. -## Core principle: one question → one answer → write specs +## Core loop: one question → one answer → write to disk -This skill runs as a tight, granular loop. **Each iteration is a single question to the user, followed by an immediate write to a `.plain` file.** No multi-question batches, no upfront interview, no "I'll gather a few things and then author." The cycle is: +Each iteration is a single question followed by an immediate write: -1. Ask **one** focused question. -2. User answers. -3. **Immediately** write the resulting spec snippet to disk — even if you suspect it's incomplete or partly wrong. -4. Ask the next question, which often refines, extends, or corrects what you just wrote. +1. **Ask** one focused question via `AskUserQuestion` — never bundle two. Shape it so any plausible answer maps directly to one writable snippet: a single behavior, concept, attribute, edge case, or constraint — not an open-ended design question. Bad shape: "How should the feature behave?" Good shape: "When the user submits an empty title, should the request be rejected with HTTP 400, accepted with a default title, or something else?" Offer concrete options plus a free-form catch-all whenever the answer space is predictable. +2. **Author immediately** — the moment the user answers, write the snippet to disk (see *2b* for which skill to route to). Do not wait for "enough" context; eager writes are the point. A snippet that is wrong on the first try is expected — the next question corrects it, and the user can read exactly where things stand after every step. +3. **Refine on the next question**, which often extends or corrects what was just written. -Writing eagerly is the point. A spec that's mostly right and gets corrected two questions later is better than a spec that waits for "enough" context before being written. The user can read what's on disk after every step and see exactly where things stand. Wrong-on-first-attempt specs are expected and welcome — you'll fix them in place on the next iteration. The questions themselves should be **shaped to produce immediately writable content**: a single attribute, a single behavior, a single edge case, a single constraint — not open-ended design questions that can't be turned into a snippet. +**One question per call, but drill as deep as the topic needs.** "One question" governs the `AskUserQuestion` call, not the topic. If an answer is vague or leaves real choices open, the *next* question drills into the same topic — another iteration of the loop — until it is concrete enough to write. Stopping early and writing on top of a vague answer is worse than one more focused follow-up. -**One question at a time — but dig as deep as the topic needs.** "One question per iteration" is a rule about the AskUserQuestion call, not about the topic. If the user's answer is vague, ambiguous, or leaves real choices open, your **next** question should drill into that same topic — same loop, just another iteration. Keep drilling until the topic is concrete enough to produce a writable snippet. Only then move on to the next topic. Stopping too early and writing on top of a vague answer is worse than asking one more focused follow-up on the same thing. +**Fix contradictions in place.** If a later answer refines or contradicts a snippet already on disk, edit that snippet right now. Never leave stale intent on disk; surface a non-trivial change in the next question. ## Input -A feature request from the user — anything from a one-liner ("add dark mode") to a detailed description. The request may be vague; the functionality loop in Phase 2 will sharpen it as you go. +A feature request from the user — anything from a one-liner ("add dark mode") to a detailed description. The request may be vague; the loop in Phase 2 sharpens it as it goes. ## Phase 1 — Scope -Keep this phase short. The goal is to know enough to ask the **very first** writable question — not to design the entire feature on paper. +Keep this short. The goal is to know enough to ask the **very first** writable question — not to design the whole feature on paper. -1. **Read the request.** Identify what is being asked for at a high level and which existing `.plain` file(s) the feature most likely belongs to. -2. **Read the target `.plain` file(s).** Follow their `import` and `requires` chains so you understand the existing definitions, implementation reqs, functional specs, test reqs, and acceptance tests. You need this context to recognize impact when it surfaces in Phase 2. -3. **Pick the target module** with one question — only if it's actually ambiguous which file to modify. Otherwise skip this and start authoring immediately. +1. **Read the request.** Identify what is being asked at a high level and which existing `.plain` file(s) the feature most likely belongs to. +2. **Read the target `.plain` file(s)**, following their `import` and `requires` chains, so the existing definitions, implementation reqs, functional specs, test reqs, and acceptance tests are in context. This is what lets Phase 2 recognize impact when it surfaces. +3. **Pick the target module with one question — only if it is genuinely ambiguous** which file to modify. Otherwise skip it and start authoring immediately. -End Phase 1 the moment you can name the file you'll write into and a single concrete starter question. Do **not** ask framing questions, scope questions, or multi-part design questions here. +End Phase 1 the moment the target file and a single concrete starter question are clear. Do **not** ask framing, scope, or multi-part design questions here. ## Phase 2 — One-question loop -This phase is a single, repeating cycle. Each iteration is **exactly one question** to the user followed by **an immediate write** to a `.plain` file. The loop ends when the user says the feature is fully covered. +A single repeating cycle: exactly one question, then an immediate write. The loop ends when the user says the feature is fully covered. ### 2a. Ask one question -Use **AskUserQuestion** with **one** question per call. The question must be **writable**: phrase it so that any plausible answer maps directly to a concrete spec snippet you can insert. Bad shape: "How should the feature behave?" Good shape: "When the user submits an empty title, should the request be rejected with HTTP 400, accepted with a default title, or something else?" - -Each question targets exactly one of: +Ask one writable question per `AskUserQuestion` call, targeting exactly one of: - **Behavior** — a single trigger and its outcome. -- **A concept** — does this introduce a new concept, or extend an existing one? Which single attribute? -- **A single edge case** — one invalid input, empty state, boundary value. +- **A concept** — a new concept, or one single attribute of an existing one. +- **A single edge case** — one invalid input, empty state, or boundary value. - **A single constraint** — one business rule, permission, ordering, or size limit. -- **Implementation guidance** — only when the functionality requires technology / library / pattern not already in the file or its imports. +- **Implementation guidance** — only when the functionality needs technology / a library / a pattern not already in the file or its imports. - **Verification** — only when the *Conformance gate* below is satisfied: one concrete outcome that proves this functionality works. -Always offer concrete options when the answer space is predictable, plus a free-form catch-all. Never bundle a second question into the prompt; never ask a question whose answer doesn't translate into a writable snippet on its own. +Never bundle a second question into the prompt; never ask a question whose answer doesn't translate into a writable snippet on its own. ### 2b. Write immediately -The moment the user answers, write the resulting snippet to disk. Do **not** wait for additional context. Do **not** batch with the next question's output. Eager writes are the point — they may be wrong on the first try and that's expected. The next question will let the user correct them. +Route each answer to the right edit skill — never hand-author a `***plain` section: -- **New concept** → use `add-concept` to add to `***definitions***`. Define before any reference. -- **New functional spec** → use `add-functional-spec`. That skill runs `analyze-if-func-spec-too-complex` and `analyze-func-specs` for you; let it. **Never hand-author functional specs.** If the skill reports the spec is too complex, ask the user a follow-up question to split it (the next iteration of the loop) — don't break it down on your own. -- **New implementation req** → use `add-implementation-requirement`. Only when the answer introduces technology / library / data format / architectural pattern not already present. -- **New acceptance test** → use `add-acceptance-test` under the relevant functional spec. Only when the *Conformance gate* is satisfied and the answer describes a concrete verification. -- **New test req** → use `add-test-requirement`. Only when conformance testing is configured and this answer changes how conformance tests are run. +- **New concept** → `add-concept` (into `***definitions***`; define before any reference). +- **New functional spec** → `add-functional-spec`. It runs `analyze-if-func-spec-too-complex` and `analyze-func-specs` for you — let it. **Never hand-author functional specs.** If it reports the spec is too complex, ask the user a follow-up question to split it (the next loop iteration) — do not break it down alone. +- **New implementation req** → `add-implementation-requirement`. Only when the answer introduces technology / a library / a data format / an architectural pattern not already present. +- **New acceptance test** → `add-acceptance-test`, under the relevant functional spec. Only when the *Conformance gate* is satisfied and the answer describes a concrete verification. +- **New test req** → `add-test-requirement`. Only when conformance testing is configured and the answer changes how conformance tests are run. -If the user's answer **contradicts or refines** something you wrote in a previous iteration, fix the existing snippet in place right now — edit the spec, the concept, or the requirement directly. This is the explicit "correct on the next pass" behavior. Surface the change in the next question if it's non-trivial. +If the answer contradicts or refines an earlier snippet, fix that snippet in place now (per *Fix contradictions in place* above). ### 2c. Handle conflicts just-in-time -If `add-functional-spec` (via the analyzers it calls) reports a conflict with an existing spec, or if the snippet you just wrote would **break** (contradict, invalidate) or **augment** (change the meaning of, add behavior to) an existing concept / functional spec / implementation req / test req / acceptance test, your **next question** to the user must be about that conflict. - -Show the exact existing snippet in the question and offer: +If `add-functional-spec` (via its analyzers) reports a conflict, or the snippet just written would **break** (contradict, invalidate) or **augment** (change the meaning of, add behavior to) an existing concept / functional spec / implementation req / test req / acceptance test, the **next question** must be about that conflict. Show the exact existing snippet in the question and offer: -- **(a) keep** the existing spec — back out or narrow what you just wrote, +- **(a) keep** the existing spec — back out or narrow what was just written, - **(b) augment** the existing spec — embed the proposed new wording in the question, - **(c) replace** the existing spec. -Apply the user's choice the instant they answer. If they augmented a concept, walk every spec that references it and update each in place; limit changes to the approved scope. Never silently rewrite prior intent. +Apply the choice the instant the user answers. If they augmented a concept, walk every spec that references it and update each in place, limited to the approved scope. Never silently rewrite prior intent. ### 2d. Decide what's next -Ask the user whether the feature is fully covered. If yes, go to Phase 3. If no, return to 2a with the next single question — which often refines what you just wrote, or starts the next behavior, concept, edge case, or constraint. +Ask the user whether the feature is fully covered. Yes → Phase 3. No → return to 2a with the next single question. ### Conformance gate -Steps 2a–2d above only generate `***test reqs***` and `***acceptance tests***` when the project has a `config.yaml` with a valid `conformance-tests-script` entry pointing at an existing conformance-test script in `test_scripts/`. Check the relevant `config.yaml` (the one that covers this module — there may be more than one in multi-part projects) and confirm the referenced script file exists. If conformance testing is not configured, skip those authoring paths entirely; functional specs, concepts, and implementation reqs still get authored normally. +Author `***test reqs***` and `***acceptance tests***` **only** when the project has a `config.yaml` with a valid `conformance-tests-script` entry pointing at an existing script in `test_scripts/`. Check the `config.yaml` that covers this module (multi-part projects may have several) and confirm the referenced script exists. If conformance testing is not configured, skip those two authoring paths entirely — concepts, functional specs, and implementation reqs are still authored normally. ## Phase 3 — Final review -Most checks have already happened in the one-question loop. Phase 3 is a slim consistency pass, and its **final automated step is always `plain-healthcheck`**. +Most checks already happened in the loop; this is a slim consistency pass whose final automated step is always `plain-healthcheck`. 1. Read the modified `.plain` file(s) in full. 2. Verify: - - All new concepts are defined before use and have no circular references. - - Chronological ordering is correct end-to-end (no new spec depends on something that comes after it). + - Every new concept is defined before use, with no circular references. + - Chronological ordering is correct end-to-end — no spec depends on something that comes after it. - Functional specs are language-agnostic. - - All external interfaces are explicit (endpoint paths, methods, CLI args, formats, etc.). + - Every external interface is explicit (endpoint paths, methods, CLI args, formats). - Acceptance tests (if any) are consistent with their parent specs. -3. Present the final diff for the modified file(s) to the user for approval. -4. If the user requests changes, drop **straight back into the one-question loop** to fix them — one question, one write, one fix at a time. Do not restart the whole loop from scratch. -5. **Final automated step — run `plain-healthcheck`.** This is the last thing the skill does before handing control back to the user. After the user approves the final diff, invoke the `plain-healthcheck` skill. It validates every `config.yaml` and dry-runs every top module, so a feature is never considered finished while the project would fail to render. If `plain-healthcheck` returns `FAIL`, work through the numbered list it produced (fix only `.plain` files / `config.yaml` / scripts — never generated code) by dropping back into the one-question loop, and then re-run `plain-healthcheck`. Repeat until it returns `PASS`. The skill is not done until `plain-healthcheck` has returned `PASS` — only then tell the user the feature is ready and remind them to re-render with `codeplain .plain`. +3. Present the final diff for the modified file(s) and get the user's approval. +4. If the user requests changes, drop **straight back into the one-question loop** — one question, one write, one fix at a time. Do not restart the loop from scratch. +5. **Run `plain-healthcheck`** — the last thing this skill does. It validates every `config.yaml` and dry-runs every top module, so a feature is never finished while the project would fail to render. If it returns `FAIL`, work through its numbered list (fixing only `.plain` files / `config.yaml` / scripts — never generated code) by dropping back into the loop, then re-run. Repeat until `PASS`. Only then tell the user the feature is ready and remind them to re-render with `codeplain .plain`. -## When the User Comes Back with Another Feature +## Next feature -After completing one feature, the user may immediately describe the next. Start again from Phase 1. This creates a continuous loop: **scope → functionality loop → final review → scope → ...** +After one feature is done, the user may describe the next. Start again from Phase 1 — a continuous loop: **scope → one-question loop → final review → scope → …** -## Validation Checklist +## Error handling -- [ ] Target `.plain` file(s) and their `import`/`requires` chain were read before authoring -- [ ] Every iteration asked exactly one question and wrote to disk immediately after the answer -- [ ] Every functional spec was authored via `add-functional-spec` (never hand-written) -- [ ] New concepts defined before they are referenced -- [ ] No circular concept references -- [ ] Every conflict / break / augment surfaced by the analyzers was put to the user as the next question and resolved before continuing -- [ ] Functional specs are language-agnostic -- [ ] All external interfaces are explicit (endpoint paths, methods, CLI args, formats, etc.) -- [ ] Acceptance tests are consistent with their parent functional specs -- [ ] User approved the final diff -- [ ] `plain-healthcheck` returned `PASS` after the final diff was approved +- **A user answer contradicts prior specs** → edit the affected snippet in place immediately; surface a non-trivial change in the next question. +- **`add-functional-spec` reports "too complex"** → ask the user a follow-up question to split the spec (next loop iteration); never split it alone. +- **A conflict / break / augment surfaces** → make it the next question (2c) and resolve it before continuing. +- **`plain-healthcheck` returns `FAIL`** → do not declare the feature done; fix the listed `.plain` / `config.yaml` / script items via the loop and re-run until `PASS`. +## Validation checklist -## Question style +Run the loop block in `references/checklist.md` on each iteration, and walk the whole list during Phase 3 before declaring the feature done. It is a self-audit of this workflow — never a substitute for it. A box only counts as met when the spec is on disk and explicitly approved; complete any unmet step before finishing. -The questions you put to the user must use simple grammatical structures: +## Question style - Prefer short, direct sentences over compound or nested clauses. - Use plain words over jargon when both convey the same meaning. -- One idea per sentence. If a sentence needs a comma-separated list of clauses, split it. - -Simpler grammar must not come at the cost of detail. Keep every constraint, edge case, option, and piece of context the user needs to answer accurately. If simplifying a sentence would drop a detail, split it into more sentences instead. +- One idea per sentence; split a comma-chained list of clauses into separate sentences. +- Never drop detail to simplify — keep every constraint, edge case, and option the user needs, splitting into more sentences instead. diff --git a/forge/skills/add-feature/references/checklist.md b/forge/skills/add-feature/references/checklist.md new file mode 100644 index 0000000..398858d --- /dev/null +++ b/forge/skills/add-feature/references/checklist.md @@ -0,0 +1,31 @@ +# Add Feature Workflow Checklist + +Use this to verify the workflow was followed — never as a substitute for it. Run the loop block on each iteration, and run the whole list once more during Phase 3 before declaring the feature done. A box only counts as met when the spec is on disk and explicitly approved, not merely discussed. If a box is unmet, go back and complete the step. + +## Phase 0 — Setup + +- [ ] Invoked `load-plain-reference` first (unless already loaded this session). + +## Phase 1 — Scope + +- [ ] Read the feature request and identified the likely target `.plain` file(s). +- [ ] Read the target `.plain` file(s) and followed their `import`/`requires` chain before authoring. +- [ ] Picked the target module with a question only when genuinely ambiguous — otherwise started authoring immediately (no framing/scope/multi-part design questions here). + +## Every loop iteration (Phase 2) + +- [ ] Asked exactly one focused question via `AskUserQuestion` — not two bundled together — targeting one writable snippet (behavior, one concept/attribute, one edge case, one constraint, implementation guidance, or verification). +- [ ] Wrote the snippet to disk immediately after the answer via the dedicated edit skill — never hand-authored a `***plain` section. +- [ ] Every functional spec was authored via `add-functional-spec` (never hand-written); if it reported "too complex", split it via a follow-up question rather than alone. +- [ ] New concepts defined before they are referenced, with no circular references. +- [ ] Any answer that contradicted an earlier snippet was fixed in place before the next question — no stale intent left on disk. +- [ ] Every conflict / break / augment surfaced by the analyzers was put to the user as the *next* question (keep / augment / replace) and resolved before continuing. +- [ ] `***test reqs***` / `***acceptance tests***` authored only when the Conformance gate holds (a `config.yaml` with a valid `conformance-tests-script` pointing at an existing script). +- [ ] Asked the user whether the feature is fully covered before leaving the loop. + +## Phase 3 — Final review + +- [ ] Read the modified `.plain` file(s) in full. +- [ ] Verified: concepts defined before use, no cycles, chronological ordering correct end-to-end, functional specs language-agnostic, every external interface explicit (endpoints, methods, CLI args, formats), acceptance tests consistent with their parent specs. +- [ ] Presented the final diff and got the user's approval (change requests dropped back into the one-question loop, not a restart). +- [ ] Ran `plain-healthcheck`; worked its numbered list to `PASS` (fixing only `.plain`/`config.yaml`/scripts — never generated code) before declaring the feature ready and reminding the user to re-render with `codeplain .plain`. diff --git a/forge/skills/add-functional-spec/SKILL.md b/forge/skills/add-functional-spec/SKILL.md index 175197d..c89b051 100644 --- a/forge/skills/add-functional-spec/SKILL.md +++ b/forge/skills/add-functional-spec/SKILL.md @@ -51,34 +51,14 @@ The new spec must not contradict any existing functional spec. Conflicting specs Each functional spec must be unambiguous — the renderer should have only one reasonable interpretation. If a single line is not enough to fully disambiguate the behavior, use **nested sub-bullets** to add detail. Nested lines clarify the parent spec; they do not introduce separate functionality. Even with nested detail, the spec must still imply ≤ 200 LOC. ```plain -- :User: should be able to send a :Message: to a :Conversation:. +***functional specs*** + +- A :User: can send a :Message: to a :Conversation:. - A :Message: must have non-empty content. - The :Message: is appended to the end of the :Conversation:. - All :Participant: members of the :Conversation: can see the new :Message:. ``` -### Line syntax (hard rule) - -**Every line inside `***functional specs***` must be its own list item starting with `- `.** ***plain has no concept of bare continuation lines — indented prose without a leading `- ` is **invalid syntax** and the renderer will reject it. - -- Hard limit: 120 characters per line. If a sentence is too long, **split it at a natural clause boundary into nested `- ` bullets** — never wrap onto an unprefixed line. -- Nested clarifications are also `- ` items, indented under the parent. The indentation alone is not enough; the leading `- ` is required. - -BAD — bare continuation lines (invalid ***plain syntax, will not render): - -```plain -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(), - which returns a list of :EventEnvelope: dicts conforming to the gateway's - contract. -``` - -GOOD — every line starts with `- `: - -```plain -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(). - - The method returns a list of :EventEnvelope: dicts. - - The dicts must conform to the gateway's :EventEnvelope: contract. -``` ### Deterministic Interface diff --git a/forge/skills/add-functional-specs/SKILL.md b/forge/skills/add-functional-specs/SKILL.md index f04a3a8..9118ff1 100644 --- a/forge/skills/add-functional-specs/SKILL.md +++ b/forge/skills/add-functional-specs/SKILL.md @@ -74,35 +74,14 @@ That coverage is achieved by running `analyze-func-specs` **once per spec being Each functional spec must be unambiguous — the renderer should have only one reasonable interpretation. If a single line is not enough to fully disambiguate the behavior, use **nested sub-bullets** to add detail. Nested lines clarify the parent spec; they do not introduce separate functionality. Even with nested detail, the spec must still imply ≤ 200 LOC. ```plain -- :User: should be able to send a :Message: to a :Conversation:. +***functional specs*** + +- A :User: can send a :Message: to a :Conversation:. - A :Message: must have non-empty content. - The :Message: is appended to the end of the :Conversation:. - All :Participant: members of the :Conversation: can see the new :Message:. ``` -### Line syntax (hard rule, per spec) - -**Every line inside `***functional specs***` must be its own list item starting with `- `.** ***plain has no concept of bare continuation lines — indented prose without a leading `- ` is **invalid syntax** and the renderer will reject the whole file. - -- Hard limit: 120 characters per line. If a sentence is too long, **split it at a natural clause boundary into nested `- ` bullets** — never wrap onto an unprefixed line. -- Nested clarifications are also `- ` items, indented under the parent. The indentation alone is not enough; the leading `- ` is required. -- This rule applies to **every** spec in the batch — one bad continuation line invalidates the entire insert. - -BAD — bare continuation lines (invalid ***plain syntax, will not render): - -```plain -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(), - which returns a list of :EventEnvelope: dicts conforming to the gateway's - contract. -``` - -GOOD — every line starts with `- `: - -```plain -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(). - - The method returns a list of :EventEnvelope: dicts. - - The dicts must conform to the gateway's :EventEnvelope: contract. -``` ### Deterministic Interface diff --git a/forge/skills/add-implementation-requirement/SKILL.md b/forge/skills/add-implementation-requirement/SKILL.md index 8d451b0..72d8d50 100644 --- a/forge/skills/add-implementation-requirement/SKILL.md +++ b/forge/skills/add-implementation-requirement/SKILL.md @@ -61,36 +61,17 @@ Implementation reqs are bullet points in the `***implementation reqs***` section ```plain ***implementation reqs*** -- :Implementation: should be in Python 3.12. -- :Implementation: should use pip for dependency management. -- When writing CSV files, :Implementation: should use streaming writes to avoid holding large datasets in memory. -``` - -Reference defined `:Concepts:` where they add clarity. Implementation reqs in non-leaf sections apply to all subsections. - -## Line syntax (hard rule) -**Every line inside a section must be its own list item starting with `- `.** ***plain has no concept of bare continuation lines — indented prose without a leading `- ` is **invalid syntax** and the renderer will reject it. +- :Implementation: should be in Python 3.12. -- Hard limit: 120 characters per line. If a sentence is too long, **split it at a natural clause boundary into nested `- ` bullets** — never wrap onto an unprefixed line. -- Nested detail is also a `- ` item, indented under its parent. The indentation alone is not enough; the leading `- ` is required. +- :Implementation: should use pip for dependency management. -BAD — bare continuation lines (invalid ***plain syntax, will not render): +- When writing CSV files, :Implementation: should use streaming writes to avoid holding large datasets in memory. -```plain -- :Implementation: tech stack will be finalized in Phase 2. - - Until then, treat this section as a placeholder so the renderer accepts - the file. Phase 2 will replace this with language, framework, HTTP - client, packaging, and architecture decisions. ``` -GOOD — every line starts with `- `: +Reference defined `:Concepts:` where they add clarity. Implementation reqs in non-leaf sections apply to all subsections. -```plain -- :Implementation: tech stack will be finalized in Phase 2. - - Until then, treat this section as a placeholder so the renderer accepts the file. - - Phase 2 will replace it with language, framework, HTTP client, packaging, and architecture decisions. -``` ## Encapsulation Warning diff --git a/forge/skills/add-resource/SKILL.md b/forge/skills/add-resource/SKILL.md index 0f4e1d5..c48cc68 100644 --- a/forge/skills/add-resource/SKILL.md +++ b/forge/skills/add-resource/SKILL.md @@ -15,7 +15,7 @@ Linked resources are external files referenced from within a `.plain` spec using ## Workflow -1. **Identify or create the resource file.** It should be in the `resources/` directory or in the same folder (or a subfolder) as the `.plain` file. +1. **Identify or create the resource file.** Keep it inside the project — the `resources/` directory is the conventional home; it does not have to sit next to the `.plain` file. 2. **Add the markdown link** at the appropriate place in the spec. 3. **Verify the file path** is relative to the `.plain` file location. 4. **Read the file back** to confirm correct link syntax and path. @@ -35,16 +35,15 @@ Use standard markdown link syntax inside any spec section: ***functional specs*** -- The system should expose an API conforming to the [API specification](resources/api_spec.yaml). +- An API conforming to the [API specification](resources/api_spec.yaml) is exposed. ``` ## Path Rules -- Paths are resolved **relative to the `.plain` file location**. -- Only files in the same folder or subfolders are supported. -- No absolute paths. +- Paths are resolved **relative to the directory where `codeplain` is run** (the current working directory). +- Relative paths may traverse into parent directories — `[resource](../../resources/resource.md)` is valid — as long as they resolve to a real text file on disk. - **No external URLs** — only local file references. -- **No folder paths** — only local file references. +- **No folder paths** — the target must be a file, not a directory. - Only text-based files are supported. ### A linked resource MUST be a single, text-based file that exists on disk @@ -70,7 +69,7 @@ The **only** exceptions are URLs and paths that are *values the produced softwar ### What a linked resource CAN be -- A single file in the same folder as the `.plain` file, or in any subfolder of it (`resources/` is the conventional home). +- A single file inside the project, reachable by a path relative to where `codeplain` is run — parent-directory traversal (`../`) is allowed (`resources/` is the conventional home). - A **text-based** file the renderer can read end-to-end: JSON, YAML, XML, HTML, Markdown, plain text, CSV, TSV, source code in any language (`.py`, `.js`, `.ts`, `.go`, `.java`, `.rb`, `.rs`, `.kt`, `.swift`, `.c`, `.cpp`, `.cs`, …), shell scripts, SQL, JSON Schema, OpenAPI, AsyncAPI, Protobuf `.proto`, GraphQL SDL, `.jolt`, `.env.example`, `.toml`, `.ini`, `.proto`, `Dockerfile`, etc. ## Common Resource Types @@ -102,6 +101,6 @@ The **only** exceptions are URLs and paths that are *values the produced softwar - [ ] **Target is a text-based file** (no binary extensions: `.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.tiff`, `.webp`, `.ico`, `.pdf`, `.docx`, `.xlsx`, `.pptx`, `.zip`, `.tar`, `.gz`, `.mp3`, `.mp4`, `.wav`, `.exe`, `.so`, `.dylib`, `.class`, `.wasm`, …) - [ ] **No URLs or folder paths anywhere in the surrounding `.plain` content** (not as link targets, not in body prose), with the sole exception of URLs / paths that are runtime values the generated software itself uses - [ ] Path is relative to the `.plain` file, not absolute -- [ ] File is in the same folder or a subfolder (no `../` references) +- [ ] Path resolves to a real text file on disk from where `codeplain` is run (`../` traversal is allowed) - [ ] Markdown link syntax is correct: `[display text](relative/path)` - [ ] Resource content is relevant and adds value beyond what the spec text says diff --git a/forge/skills/add-test-requirement/SKILL.md b/forge/skills/add-test-requirement/SKILL.md index 32c9386..3f72814 100644 --- a/forge/skills/add-test-requirement/SKILL.md +++ b/forge/skills/add-test-requirement/SKILL.md @@ -51,35 +51,18 @@ Test reqs are bullet points in the `***test reqs***` section: ```plain ***test reqs*** + - :ConformanceTests: should be implemented using pytest framework. + - :ConformanceTests: will be run using "pytest" command. + - :ConformanceTests: must be implemented and executed - do not skip tests. + - :ConformanceTests: should mock all external HTTP calls. ``` Reference predefined concepts like `:ConformanceTests:` and any defined `:Concepts:` where they add clarity. -## Line syntax (hard rule) - -**Every line inside `***test reqs***` must be its own list item starting with `- `.** ***plain has no concept of bare continuation lines — indented prose without a leading `- ` is **invalid syntax** and the renderer will reject it. - -- Hard limit: 120 characters per line. If a sentence is too long, **split it at a natural clause boundary into nested `- ` bullets** — never wrap onto an unprefixed line. -- Nested clarifications are also `- ` items, indented under the parent. The indentation alone is not enough; the leading `- ` is required. - -BAD — bare continuation lines (invalid ***plain syntax, will not render): - -```plain -- :ConformanceTests: must mock all external HTTP calls so that the - test suite remains hermetic and does not depend on network access. -``` - -GOOD — every line starts with `- `: - -```plain -- :ConformanceTests: must mock all external HTTP calls. - - The test suite must remain hermetic. - - Tests must not depend on network access. -``` ## Validation Checklist diff --git a/forge/skills/analyze-2-func-specs/SKILL.md b/forge/skills/analyze-2-func-specs/SKILL.md index 4bf789d..0ca61e0 100644 --- a/forge/skills/analyze-2-func-specs/SKILL.md +++ b/forge/skills/analyze-2-func-specs/SKILL.md @@ -31,8 +31,8 @@ Work through each question. If any answer is "yes", the specs likely conflict. Do the two specs make mutually exclusive assertions about the same behavior? ``` -Spec A: The system should return :Resource: items sorted by name in ascending order. -Spec B: The system should return :Resource: items sorted by creation date in descending order. +Spec A: :Resource: items are returned sorted by name in ascending order. +Spec B: :Resource: items are returned sorted by creation date in descending order. Verdict: CONFLICTING — both define the sort order for the same response, but specify different fields and directions. A single implementation cannot @@ -44,8 +44,8 @@ satisfy both unless scoped to different contexts. Does one spec set a state or value that the other spec assumes is different? ``` -Spec A: :TaskList: should initially be empty. -Spec B: :TaskList: should contain a default "Welcome" :Task: on first load. +Spec A: :TaskList: is initially empty. +Spec B: :TaskList: contains a default "Welcome" :Task: on first load. Verdict: CONFLICTING — both define the initial state of :TaskList: differently. ``` @@ -55,8 +55,8 @@ Verdict: CONFLICTING — both define the initial state of :TaskList: differently Does the later spec silently replace behavior established by the earlier spec without acknowledging it? ``` -Spec A: The system should validate :User: credentials using an API key. -Spec B: The system should validate :User: credentials using OAuth 2.0. +Spec A: :User: credentials are validated using an API key. +Spec B: :User: credentials are validated using OAuth 2.0. Verdict: CONFLICTING — both define the authentication mechanism but pick different approaches. The later spec overrides the earlier one. @@ -67,8 +67,8 @@ different approaches. The later spec overrides the earlier one. Are the two specs ambiguous enough that a renderer could interpret them as conflicting, even if the user intends them to be complementary? ``` -Spec A: The system should return all :Resource: items. -Spec B: The system should return only active :Resource: items. +Spec A: All :Resource: items are returned. +Spec B: Only active :Resource: items are returned. Verdict: CONFLICTING (ambiguous) — "all" vs "only active" appear contradictory. Could be resolved by scoping each to different conditions (e.g., filtered vs unfiltered). @@ -79,8 +79,8 @@ Could be resolved by scoping each to different conditions (e.g., filtered vs unf Do both specs impose constraints on the same `:Concept:` that cannot coexist? ``` -Spec A: :BatchSize: should be 100 items. -Spec B: :BatchSize: should be 50 items for :Resource: types with attachments. +Spec A: :BatchSize: is 100 items. +Spec B: :BatchSize: is 50 items for :Resource: types with attachments. Verdict: COMPATIBLE — Spec B adds a conditional refinement, not a contradiction. ``` diff --git a/forge/skills/analyze-func-specs/SKILL.md b/forge/skills/analyze-func-specs/SKILL.md index 1ad20d1..89f05cd 100644 --- a/forge/skills/analyze-func-specs/SKILL.md +++ b/forge/skills/analyze-func-specs/SKILL.md @@ -41,8 +41,8 @@ Work through each question for the pair under inspection. If any answer is "yes" Do the two specs make mutually exclusive assertions about the same behavior? ``` -Spec A: The system should return :Resource: items sorted by name in ascending order. -Spec B: The system should return :Resource: items sorted by creation date in descending order. +Spec A: :Resource: items are returned sorted by name in ascending order. +Spec B: :Resource: items are returned sorted by creation date in descending order. Verdict: CONFLICTING — both define the sort order for the same response, but specify different fields and directions. A single implementation cannot @@ -54,8 +54,8 @@ satisfy both unless scoped to different contexts. Does one spec set a state or value that the other spec assumes is different? ``` -Spec A: :TaskList: should initially be empty. -Spec B: :TaskList: should contain a default "Welcome" :Task: on first load. +Spec A: :TaskList: is initially empty. +Spec B: :TaskList: contains a default "Welcome" :Task: on first load. Verdict: CONFLICTING — both define the initial state of :TaskList: differently. ``` @@ -65,8 +65,8 @@ Verdict: CONFLICTING — both define the initial state of :TaskList: differently Does the later spec silently replace behavior established by the earlier spec without acknowledging it? ``` -Spec A: The system should validate :User: credentials using an API key. -Spec B: The system should validate :User: credentials using OAuth 2.0. +Spec A: :User: credentials are validated using an API key. +Spec B: :User: credentials are validated using OAuth 2.0. Verdict: CONFLICTING — both define the authentication mechanism but pick different approaches. The later spec overrides the earlier one. @@ -77,8 +77,8 @@ different approaches. The later spec overrides the earlier one. Are the two specs ambiguous enough that a renderer could interpret them as conflicting, even if the user intends them to be complementary? ``` -Spec A: The system should return all :Resource: items. -Spec B: The system should return only active :Resource: items. +Spec A: All :Resource: items are returned. +Spec B: Only active :Resource: items are returned. Verdict: CONFLICTING (ambiguous) — "all" vs "only active" appear contradictory. Could be resolved by scoping each to different conditions (e.g., filtered vs unfiltered). @@ -89,8 +89,8 @@ Could be resolved by scoping each to different conditions (e.g., filtered vs unf Do both specs impose constraints on the same `:Concept:` that cannot coexist? ``` -Spec A: :BatchSize: should be 100 items. -Spec B: :BatchSize: should be 50 items for :Resource: types with attachments. +Spec A: :BatchSize: is 100 items. +Spec B: :BatchSize: is 50 items for :Resource: types with attachments. Verdict: COMPATIBLE — Spec B adds a conditional refinement, not a contradiction. ``` diff --git a/forge/skills/analyze-if-func-spec-too-complex/SKILL.md b/forge/skills/analyze-if-func-spec-too-complex/SKILL.md index 3af52cc..ae832ee 100644 --- a/forge/skills/analyze-if-func-spec-too-complex/SKILL.md +++ b/forge/skills/analyze-if-func-spec-too-complex/SKILL.md @@ -35,16 +35,25 @@ Work through each indicator. A single "yes" does not automatically mean the spec Does the spec describe more than one independently testable behavior? -``` Too complex: -- :User: should be able to create, edit, delete, and archive :Task: items, - with validation on all fields and confirmation dialogs for destructive actions. +```plain +***functional specs*** + +- A :User: can create, edit, delete, and archive :Task: items, with validation on all fields and confirmation dialogs for destructive actions. +``` Acceptable (one behavior each): -- :User: should be able to create :Task:. Only valid :Task: items can be added. -- :User: should be able to edit :Task:. -- :User: should be able to delete :Task:. -- :User: should be able to archive :Task:. +```plain +***functional specs*** + +- A :User: can create :Task:. Only valid :Task: items can be added. + +- A :User: can edit :Task:. + +- A :User: can delete :Task:. + +- A :User: can archive :Task:. + ``` ### 2. Number of Concepts Introduced or Modified @@ -59,18 +68,23 @@ Does the spec require introducing new data structures, UI components, API endpoi Does the spec describe multiple conditional paths, modes, or special cases? -``` + Too complex: -- The system should process :Order: differently based on :OrderType:. - Standard orders are validated and stored. Express orders skip validation - and are queued for immediate dispatch. Bulk orders are split into - sub-orders of 100 items each, validated individually, and processed - in parallel with progress tracking. +```plain +***functional specs*** + +- :Order: processing depends on :OrderType:. Standard orders are validated and stored. Express orders skip validation and are queued for immediate dispatch. Bulk orders are split into sub-orders of 100 items each, validated individually, and processed in parallel with progress tracking. +``` Acceptable (separate the modes): -- The system should process standard :Order: by validating and storing it. -- The system should process express :Order: by queuing it for immediate dispatch without validation. -- The system should process bulk :Order: by splitting it into sub-orders of 100 items each. +```plain +***functional specs*** + +- A standard :Order: is validated and stored. + +- An express :Order: is queued for immediate dispatch without validation. + +- A bulk :Order: is split into sub-orders of 100 items each. - Each sub-order is processed individually. ``` @@ -78,32 +92,43 @@ Acceptable (separate the modes): Does the spec bundle core functionality with cross-cutting concerns like error handling, logging, retry logic, pagination, or caching? -``` Too complex: -- The system should fetch :Resource: items from the external API with - pagination support, retry on transient errors with exponential backoff, - cache results for 5 minutes, and log all API calls. +```plain +***functional specs*** + +- :Resource: items are fetched from the external API with pagination support, retry on transient errors with exponential backoff, caching for 5 minutes, and logging of all API calls. +``` Acceptable (separate concerns): -- The system should fetch :Resource: items from the external API. -- The system should paginate when fetching :Resource: items from the external API. -- The system should retry fetching :Resource: on transient errors using exponential backoff. +```plain +***functional specs*** +- :Resource: items are fetched from the external API. + +- :Resource: items are fetched from the external API in pages. + +- Fetching :Resource: items is retried on transient errors using exponential backoff. ``` ### 5. UI Complexity Does the spec describe a complete screen or page with multiple interactive elements, layouts, and state transitions? -``` Too complex: -- Display a dashboard showing :User: profile, recent :Task: items in a - sortable table, a notification bell with unread count, and a sidebar - with navigation links that highlights the active page. +```plain +***functional specs*** + +- The dashboard shows :User: profile, recent :Task: items in a sortable table, a notification bell with unread count, and a sidebar with navigation links that highlights the active page. +``` Acceptable (build incrementally): -- Display a dashboard page for :User:. -- Show recent :Task: items in a sortable table on the dashboard. -- Show a notification indicator with the unread count on the dashboard. +```plain +***functional specs*** + +- A dashboard page is shown for :User:. + +- Recent :Task: items are shown in a sortable table on the dashboard. + +- A notification indicator with the unread count is shown on the dashboard. ``` ### 6. Data Transformation Complexity @@ -113,11 +138,36 @@ Does the spec involve complex data mapping, aggregation, or transformation acros - Simple field mapping or filtering → likely fine - Multi-step transformations, joins across entities, or aggregations → likely too complex -### 7. Rough LOC Estimation +### 7. Implied Technical Component + +Does the spec imply building a substantial technical component — an engine, parser, scheduler, layout algorithm, state machine, or sync mechanism — **in addition to** the behavior it describes? A component is not a free primitive: code must be generated for it too, so it counts toward the 200-LOC budget and must be evaluated as part of the estimate. A spec that needs a component **and** wires up behavior on top of it is really two bodies of code in one spec — a strong "too complex" signal. Count the component's implementation, not just the glue that calls it. + +Too complex (builds the component and uses it in one spec): +```plain +***functional specs*** + +- A :Report: is exported to PDF, laying out multi-page tables with repeating headers and page numbers, and charts rendered from :Report: data. +``` + +Acceptable (component in its own spec, then referenced): +```plain +***functional specs*** + +- :PdfRenderer: lays out multi-page tables from structured content. + - Table headers repeat at the top of each page. + - Each page shows its page number. + +- A :Report: is exported to PDF using :PdfRenderer:, embedding charts rendered from :Report: data. +``` + +When this indicator fires, the fix is `break-down-func-spec` Strategy 6 — extract the component into its own earlier spec and reference it. + +### 8. Rough LOC Estimation Mentally estimate the implementation. Consider: - New files that need to be created - New functions/methods +- Code for any implied technical component (engine, parser, algorithm), not just the glue that uses it - Data model changes (schema, migrations, types) - UI components (if applicable) - Test setup and assertions (unit tests are auto-generated alongside) diff --git a/forge/skills/break-down-func-spec/SKILL.md b/forge/skills/break-down-func-spec/SKILL.md index 749db26..564070a 100644 --- a/forge/skills/break-down-func-spec/SKILL.md +++ b/forge/skills/break-down-func-spec/SKILL.md @@ -31,6 +31,7 @@ The functional spec to break down, plus the full `.plain` file it belongs to. - Cross-cutting concerns mixed with core functionality? - A full UI screen described in one spec? - Complex data transformations across multiple entities? + - Implies one substantial technical component (engine, parser, scheduler, algorithm, state machine, sync mechanism) that must be built in code? 4. **Identify the split boundaries** — find the natural seams where the spec can be divided. Each resulting spec must be: - Independently meaningful (makes sense on its own with previous specs as context) - Self-contained (does not require a later spec to be useful) @@ -50,15 +51,20 @@ If the spec bundles multiple independently testable actions, split each into its **Before:** ```plain -- :User: should be able to create, edit, and delete :Recipe: items, with - validation on all fields. +***functional specs*** + +- A :User: can create, edit, and delete :Recipe: items, with validation on all fields. ``` **After:** ```plain -- :User: should be able to create a :Recipe:. Only valid :Recipe: items can be created. -- :User: should be able to edit an existing :Recipe:. Validation rules apply to the edited fields. -- :User: should be able to delete a :Recipe:. +***functional specs*** + +- A :User: can create a :Recipe:. Only valid :Recipe: items can be created. + +- A :User: can edit an existing :Recipe:. Validation rules apply to the edited fields. + +- A :User: can delete a :Recipe:. ``` ### Strategy 2: Separate Setup from Behavior @@ -67,16 +73,20 @@ If the spec introduces a new construct and immediately defines complex behavior **Before:** ```plain -- The system should provide a :MealPlan: screen that displays a weekly grid of - :Slot: items, allows drag-and-drop reordering, and shows nutritional totals - per day. +***functional specs*** + +- The :MealPlan: screen displays a weekly grid of :Slot: items, allows drag-and-drop reordering, and shows nutritional totals per day. ``` **After:** ```plain -- The system should provide a :MealPlan: screen that displays a weekly grid of :Slot: items. -- :User: should be able to reorder :Slot: items within a day on the :MealPlan: screen using drag-and-drop. -- The :MealPlan: screen should display nutritional totals for each day. +***functional specs*** + +- The :MealPlan: screen displays a weekly grid of :Slot: items. + +- A :User: can reorder :Slot: items within a day on the :MealPlan: screen using drag-and-drop. + +- The :MealPlan: screen displays nutritional totals for each day. ``` ### Strategy 3: Separate Core Logic from Cross-Cutting Concerns @@ -85,15 +95,20 @@ If the spec mixes primary functionality with error handling, retries, caching, p **Before:** ```plain -- The system should fetch :Ingredient: data from the external API with - pagination, retry on transient errors, and cache results for 10 minutes. +***functional specs*** + +- :Ingredient: data is fetched from the external API with pagination, retry on transient errors, and caching for 10 minutes. ``` **After:** ```plain -- The system should fetch :Ingredient: data from the external API. -- The system should paginate when fetching :Ingredient: data from the external API. -- The system should retry fetching :Ingredient: data on transient errors. +***functional specs*** + +- :Ingredient: data is fetched from the external API. + +- :Ingredient: data is fetched from the external API in pages. + +- Fetching :Ingredient: data is retried on transient errors. ``` ### Strategy 4: Separate Conditional Paths @@ -102,19 +117,22 @@ If the spec describes different modes or branches, give each its own spec. **Before:** ```plain -- The system should process :MealPlan: generation differently based on :DietType:. - Standard plans use round-robin assignment. Restrictive plans filter out - excluded ingredients first, then apply round-robin. Custom plans allow manual - slot-by-slot selection. +***functional specs*** + +- :MealPlan: generation depends on :DietType:. Standard plans use round-robin assignment. Restrictive plans filter out excluded ingredients first, then apply round-robin. Custom plans allow manual slot-by-slot selection. ``` **After:** ```plain -- The system should generate a standard :MealPlan: using round-robin :Recipe: assignment. -- The system should generate a restrictive :MealPlan:. +***functional specs*** + +- A standard :MealPlan: is generated using round-robin :Recipe: assignment. + +- A restrictive :MealPlan: is generated. - Excluded :Ingredient: items are filtered out first. - Round-robin :Recipe: assignment is then applied. -- The system should allow :User: to manually assign :Recipe: items to :Slot: items for a custom :MealPlan:. + +- A :User: can manually assign :Recipe: items to :Slot: items for a custom :MealPlan:. ``` ### Strategy 5: Build UI Incrementally @@ -123,18 +141,48 @@ If the spec describes a full screen, split into layout + individual interactive **Before:** ```plain -- Display the :Dashboard: screen showing a summary card with stats, a scrollable - list of recent :MealPlan: items, a floating action button to create a new plan, - and a bottom navigation bar. +***functional specs*** + +- The :Dashboard: screen shows a summary card with stats, a scrollable list of recent :MealPlan: items, a floating action button to create a new plan, and a bottom navigation bar. ``` **After:** ```plain -- Display the :Dashboard: screen with a summary card showing :MealFrameStats:. -- The :Dashboard: screen should show a scrollable list of recent :MealPlan: items. -- The :Dashboard: screen should include a button to create a new :MealPlan:. +***functional specs*** + +- The :Dashboard: screen shows a summary card with :MealFrameStats:. + +- The :Dashboard: screen shows a scrollable list of recent :MealPlan: items. + +- The :Dashboard: screen includes a button to create a new :MealPlan:. ``` +### Strategy 6: Extract a Reusable Technical Component + +Sometimes a spec is too complex not because it bundles many behaviors, but because it implies building one substantial technical component inline — an engine, parser, scheduler, layout algorithm, state machine, or sync mechanism. Splitting the behavior sideways does not help when the weight sits in that single component. Instead, pull the component out: define a concept for it, build it in its own dedicated spec placed **earlier** in the chain, then rewrite the original to use the component **by reference**. Because specs render incrementally top-to-bottom, the original now implies far less code — it wires up a component that already exists rather than implementing it from scratch. + +Define the component concept in `***definitions***` (via `add-concept`) before the first spec that references it. + +**Before:** +```plain +***functional specs*** + +- A :Report: is exported to PDF, laying out multi-page tables with repeating headers, page numbers, and charts rendered from :Report: data. +``` + +**After:** +```plain +***functional specs*** + +- :PdfRenderer: lays out multi-page tables from structured content. + - Table headers repeat at the top of each page. + - Each page shows its page number. + +- A :Report: is exported to PDF using :PdfRenderer:, embedding charts rendered from :Report: data. +``` + +The extracted component spec and the rewritten original must together still cover 100% of the original functionality (step 6): the original must **reference** the component, never re-describe it. Keep the component's technology and architecture choices in `***implementation reqs***`, not in the functional spec. If the component is a self-contained subsystem or will be reused across modules, promote it to its own module with `create-requires-module` / `refactor-module` instead of a sibling spec. + ## Preserving Chronological Order The replacement specs take the position of the original spec. Earlier specs remain unchanged. The first replacement spec should make sense given only the specs above it. Each subsequent replacement spec can reference behavior from the ones before it. @@ -151,6 +199,7 @@ If the original spec had acceptance tests, redistribute them to the most appropr - [ ] Replacement specs are in correct chronological order - [ ] Each replacement spec is independently meaningful - [ ] All `:Concepts:` referenced in replacement specs are defined +- [ ] Any extracted technical component is defined as a concept, built by its own earlier spec, and only **referenced** (not re-described) by the original - [ ] Replacement specs are language-agnostic - [ ] All external interfaces remain explicit - [ ] Acceptance tests (if any) have been redistributed or rewritten diff --git a/forge/skills/create-import-module/SKILL.md b/forge/skills/create-import-module/SKILL.md index 04be994..6d416cc 100644 --- a/forge/skills/create-import-module/SKILL.md +++ b/forge/skills/create-import-module/SKILL.md @@ -44,14 +44,19 @@ description: Shared API definitions and reqs --- ***definitions*** + - :ApiClient: is the HTTP client used to communicate with external services. + - :ApiResponse: is the response returned by :ApiClient:. ***implementation reqs*** + - :Implementation: should handle HTTP errors by raising appropriate exceptions. + - :ApiClient: should support configurable timeouts. ***test reqs*** + - :ConformanceTests: should mock all external HTTP calls made by :ApiClient:. ``` @@ -71,7 +76,9 @@ import: > So this module must define them: ***definitions*** + - :AuthSchema: is the JSON schema describing the authentication data structure. + - :AuthApiSpec: is the OpenAPI specification for the external service's auth endpoint. ``` diff --git a/forge/skills/create-requires-module/SKILL.md b/forge/skills/create-requires-module/SKILL.md index 6de682b..2fdda6e 100644 --- a/forge/skills/create-requires-module/SKILL.md +++ b/forge/skills/create-requires-module/SKILL.md @@ -50,11 +50,12 @@ description: Extended module that builds on base_module --- ***definitions*** + - :NewFeature: is a feature added by this module. ***functional specs*** -- The system should support :NewFeature:. +- :NewFeature: is available. ``` A module can use both `requires` and `import` together. `requires` points to other root-level modules; `import` resolves from the default `template/` directory (no prefix needed). diff --git a/forge/skills/forge-plain/SKILL.md b/forge/skills/forge-plain/SKILL.md index 2f94769..73b99ad 100644 --- a/forge/skills/forge-plain/SKILL.md +++ b/forge/skills/forge-plain/SKILL.md @@ -1,346 +1,108 @@ --- name: forge-plain description: >- - End-to-end `***plain` spec authoring workflow: runs a structured QA interview - (product, tech stack, behavior) then produces complete .plain specification - files with automated review. Use when the user wants to build something new - from scratch or asks to start a new project. + End-to-end `***plain` spec authoring workflow: a gated, one-question-at-a-time + interview (product, tech stack, testing) that writes complete .plain + specification files to disk incrementally, reviews each addition, and + validates the specs with a dry-run before handoff. Use when the user starts a + new project or wants to build something new from scratch. Not for adding a + feature to an existing project (use add-feature) or for editing generated code + (the .plain specs are the source of truth). --- # Forge Plain -Always use the skill `load-plain-reference` to retrieve the `***plain` syntax rules — but only if you haven't done so yet. +Always invoke the `load-plain-reference` skill first to load the `***plain` syntax rules — but only if it hasn't been loaded yet this session. -## Your Role +## Role -You are a `***plain` spec writer. Your primary output is `.plain` specification files — not code. Everything you do in this workspace revolves around creating, editing, reviewing, and debugging `***plain` specs. Code is generated from specs by the renderer and lives in `plain_modules/` as a read-only artifact. You never write or edit code directly. +Act as a `***plain` spec writer. The only output is `.plain` specification files — never code. Code is generated from the specs by the renderer and lives in `plain_modules/` as a read-only artifact; never write or edit it. Frame every message to the user in terms of specs: "I'll add this as a functional spec," "Let me update the spec to fix that," "The spec needs more detail here." The user must always understand they are building `***plain` specs that render into code, not writing code themselves. -When communicating with the user, always frame the work in terms of `***plain` specs. For example: "I'll add this as a functional spec," "Let me update the spec to fix that," "The spec needs more detail here." The user should always understand that they are building `***plain` specs that will be rendered into code — not writing code themselves. +## Agent orchestration -## Core principle: one question → one answer → write specs +`forge-plain` runs the interview **itself** — it owns the user conversation from start to finish, asks every `AskUserQuestion`, and authors every snippet inline via the edit skills. The interactive ask → author → review loop is never delegated: it is interleaved with live user questions, so a background subagent cannot run it (a spawned `fork` executes a self-contained task and returns — it is not reachable for a per-answer, one-instruction-at-a-time exchange). -This skill runs as a tight, granular loop — the same shape as `add-feature`, just spanning all four phases of a new project instead of a single feature. **Each iteration is a single question to the user, followed by an immediate write to a `.plain` file** (or, in Phase 3, to a script / `config.yaml`). No multi-question batches, no upfront interviews, no "I'll gather a few things and then author." +The one thing that **is** delegated is validation at each phase gate: -The cycle inside every phase is: +- **Review agent** — spawned fresh at each phase gate via the `Agent` tool as a new `general-purpose` agent (a fresh agent, **not** a fork — it audits with an independent eye). Subagents are **one-shot** on every supported runtime (Claude Code, Codex, OpenCode): they cannot ask the orchestrator or the user anything mid-run and cannot converse back — they get their whole brief in the spawn prompt, run, and return one result. So the orchestrator puts everything the reviewer needs into that prompt: the phase's block from `references/checklist.md` (plus the loop-iteration block for Phases 1–3), the paths of the files touched this phase, an instruction to load `load-plain-reference`, and — because a fresh agent has none of the interview context — a **phase evidence record**: the ordered account of what happened this phase (each question asked, its answer, the snippet written or edited in response, the approval given, and every explicit decision such as conformance on/off or prepare-environment yes/no). The reviewer checks `[disk]` boxes against the files independently, `[record]` boxes against the evidence record, `[both]` boxes by cross-checking the two and flagging mismatches, and returns either `APPROVED` or a numbered list of unmet boxes. Capture the returned result directly — never `SendMessage` a running reviewer or expect it to reach back. -1. Ask **one** focused question via `AskUserQuestion`. -2. User answers. -3. **Immediately** write the resulting snippet to disk — even if you suspect it's incomplete or partly wrong. -4. Ask the next question, which often refines, extends, or corrects what you just wrote. +### Per-phase loop -Writing eagerly is the point. A spec that's mostly right and gets corrected two questions later is better than a spec that waits for "enough" context before being written. The user can read what's on disk after every step and see exactly where things stand. Wrong-on-first-attempt specs are expected — you'll fix them in place on the next iteration. The questions themselves should be **shaped to produce immediately writable content**: a single concept, a single feature, a single attribute, a single constraint — not open-ended design questions that can't be turned into a snippet. +1. Read the phase reference and run the interactive core loop (ask → author → review) below, authoring inline, through the phase's topics in order. +2. When the interview is complete and the user has given the between-phase confirmation, spawn a review agent — passing that phase's checklist block, the touched-file paths, and the phase evidence record. +3. **If the review agent returns unmet boxes, do not advance.** Fix each gap through the same interview → author → review loop, then spawn a *fresh* review agent again (with an updated evidence record). Repeat until a review agent returns `APPROVED`. +4. Advance to the next phase. -**One question at a time — but dig as deep as the topic needs.** "One question per iteration" is a rule about the `AskUserQuestion` call, not about the topic. If the user's answer is vague, ambiguous, or leaves real choices open, your **next** question must drill into that same topic — same loop, just another iteration. Keep drilling until the topic is concrete enough to produce a writable snippet. Only then move on to the next topic. Stopping too early and writing on top of a vague answer is worse than asking one more focused follow-up on the same thing. +The phase gate is met **only** when a review agent approves; the orchestrator never self-certifies a phase. -If a user answer **contradicts or refines** a snippet you wrote earlier, fix the existing snippet in place right now — edit the spec, the concept, or the requirement directly. Surface the change in the next question if it's non-trivial. Never silently leave a stale spec on disk. +## Core loop: one question → one answer → write to disk -## Quickstart Workflow: QA Session → `***plain` Specs +Every phase runs the same tight loop. Each iteration is a single question followed by an immediate write: -When the user starts a new session or asks to build something, run the **QA workflow** below. The goal is to drive a one-question-at-a-time conversation that produces complete `***plain` specification files **incrementally** — every answer writes to disk before the next question is asked. +1. **Ask** one focused question via `AskUserQuestion` — never bundle two. Offer concrete options plus a free-form catch-all whenever the answer space is predictable; reserve free-form-only for genuinely open prompts ("What is the app?"). Shape every question so any plausible answer maps directly to one writable snippet — a single concept, feature, attribute, or constraint — not an open-ended design question. +2. **Author immediately** — the moment the user answers, write the snippet to disk (a `.plain` section, a script, or a `config.yaml` entry) using the right edit skill. Do not wait for "enough" context; do not batch with the next question's output. Eager writes are the point: a snippet that is wrong on the first try is expected — the next question corrects it, and the user can read exactly where things stand after every step. +3. **Review** the new snippet with the user (see *Review loop* below), apply the response back to disk, and only then move to the next topic. -**Do not skip ahead.** Each phase must be **finished** before the next one starts. Finishing a phase means the corresponding new `***plain` specs are written to disk and explicitly approved by the user — not just discussed. Concretely: +**One question per call, but drill as deep as the topic needs.** "One question" governs the `AskUserQuestion` call, not the topic. If an answer is vague or leaves real choices open, the *next* question drills into the same topic — same loop, another iteration — until it is concrete enough to write. Stopping early and writing on top of a vague answer is worse than one more focused follow-up. -- **Phase 1** is finished when the new `***definitions***` and `***functional specs***` for this session are on disk and approved. -- **Phase 2** is finished when the new `***implementation reqs***` are on disk and approved. -- **Phase 3** is finished when the new `***test reqs***` (and, if conformance testing is enabled, `***acceptance tests***`) are on disk, the testing scripts and `config.yaml` files exist, and the environment verification has passed or each gap has been explicitly acknowledged by the user. Make sure to add the template_dir field to the config.yaml if any import modules or templates have been added eg. +**Fix contradictions in place.** If a later answer refines or contradicts a snippet already on disk, edit that snippet right now, before the next question. Never leave a stale spec on disk. Surface a non-trivial change in the next question. -```yaml -template_dir: template -``` +Use the dedicated edit skills for every write — never hand-author a `***plain` section directly. Each phase reference names the right skill for each kind of snippet. -- **Phase 4** is finished when `codeplain .plain --dry-run` has been run by you (the agent) against the final render target and it exits successfully, and the user has been given the render command plus the full list of side-channel commands they need to run. +## Review loop -If you find yourself drafting later-phase content (e.g. picking a framework while still in Phase 1, or writing test reqs while still in Phase 2), stop and finish the current phase first. The same rule applies to **questions**: do not ask the user about anything that belongs to a later phase. While a phase is still open, only ask questions whose answers shape that phase's deliverable. If a user answer drifts into later-phase territory, acknowledge it briefly, note it for later, and steer back to the current phase — do **not** branch into a multi-question detour about, say, the testing framework while you are still nailing down functional specs. Each phase's output is a concrete change to the `.plain` files (and, in Phase 3, to `test_scripts/` and `config.yaml`). Talk is not output — the specs are. +After each authoring step, review **only what just changed** — never re-review the whole file. Pick the single most relevant snippet (one concept, one functional spec, the module frontmatter, one requirement, one acceptance test, one script change, one `config.yaml` entry) and embed it directly in the `AskUserQuestion` prompt so the user sees exactly what they approve. Frame each question around one of: -### Your tools - -**AskUserQuestion** — use this tool to ask the user **exactly one** structured question per call. Never bundle a second question into the prompt. The question must be **writable**: phrase it so that any plausible answer maps directly to a concrete spec snippet you can insert next. Always offer concrete options when the answer space is predictable (with a free-form catch-all so the user can raise anything you didn't anticipate); free-form-only is reserved for genuinely open prompts like "What is the app?". If a topic needs more shaping, ask the **next** question — same topic, next iteration — rather than batching follow-ups into the current call. - ---- - -### Phase 1 — What are we building? - -Understand the product. This is the most important phase and needs to be done thoroughly. Drill into the behavior of the app. - -This phase is **incremental and one-question-at-a-time**. Do **not** ask everything up front and then write all the specs at the end. Walk through the topics below in order, and for each topic run a tight loop where **every iteration is one question followed by an immediate write to disk**: - -1. **Ask** — use **AskUserQuestion** with **exactly one** question per call. Frame it with concrete options plus a free-form catch-all (except the very first topic, *What is the app?*, which is free-form only). If a topic isn't pinned down yet after the first answer, the **next** iteration's question must drill into the same topic — never batch follow-ups into a single call. -2. **Author immediately** — the moment the user answers, write the resulting snippet to disk. Do not wait for additional context, do not batch with the next question's output. Eager writes are the point; they may be wrong on the first try and that's expected. The next question lets the user correct them. Depending on the topic, the write goes to: - - **Module structure** — create or update the `.plain` file(s) (single module, template + modules, or chained modules). Set up YAML frontmatter (`import`, `requires`, description). If you use a template, create it in `template/` without `***functional specs***`. Use the `create-import-module` skill where applicable. - - **`***definitions***`** — add or refine concepts (entities, attributes, relationships). Define every concept before it is referenced. Use the `add-concept` skill. - - **`***functional specs***`** — translate the answer into a single chronological, incremental spec (≤200 lines of code change, language-agnostic, no conflicts). Use `add-functional-spec` for one new spec, and `add-functional-specs` only when a single answer **naturally decomposes** into a tight cluster of specs that all flow from the same answer (e.g. "list / create / delete" CRUD on a single entity). Never hand-author the `***functional specs***` section directly. - Do **not** add `***implementation reqs***`, `***test reqs***`, or `***acceptance tests***` in this phase — they belong to later phases. - - If a later answer contradicts or refines content already on disk, **update the affected snippet in place right now**, before the next question. -3. **Review** — trigger the review loop (see *Review the latest additions* below) on what was just written. Apply the user's response back to the `.plain` files and re-surface any snippet that materially changed. Only move on to the next topic once every flagged snippet has been explicitly approved. - -Walk through these topics in order, running ask → author → review for each. Skip a topic only if it genuinely doesn't apply, and say so explicitly: - -1. **What is the app?** — one-sentence description. What problem does it solve? Free-form, not multiple choice. Author: a stub `.plain` file with the description in the YAML frontmatter and the proposed module name. Review: the frontmatter and module split. -2. **Who uses it?** — target users or personas; is it a CLI tool, web app, API, desktop app, mobile app, library, or something else? Author: any user/persona concepts that emerge. Review: those concepts. -3. **What is the scope?** — MVP, prototype, or full product? What is explicitly out of scope? Author: tighten the description and (if needed) split modules to keep MVP scope cohesive. Review: the resulting module structure. -4. **Core entities** — the main "things" in the system (Users, Tasks, Orders, Messages, …), their attributes, and relationships. Author: one concept per entity in `***definitions***`. Review: each concept snippet. -5. **Key features** — every distinct thing the app should do. For each feature capture trigger, expected outcome, and edge cases / validation rules. Author: one or more functional specs per feature, in chronological build order, each ≤200 LOC. Break large features into smaller specs together with the user. Review: each new functional spec (or tight group of related specs). -6. **User flows** — walk through the app from the user's perspective: what happens first, what happens next, decision points. Author: ordering and any missing intermediate functional specs. Review: the affected sequence of specs. -7. **Constraints and rules** — business rules, validation, permissions, error handling behavior. Author: fold these into the relevant functional specs (and add concepts where they are first-class entities, e.g. roles). Review: the updated specs. -8. **Optional — user interface.** Skip entirely if the project doesn't have a UI; otherwise ask: - - How does the UI look and feel? - - Where are the key UI elements located? - - What do the key UI elements do? - - What is the layout and design of the UI? - Author: UI-behavior functional specs (still language- and framework-agnostic). Review: those specs. -9. **Anything else** — anything the user wants to add or change that hasn't already been covered. - -Keep asking follow-ups within a topic until every feature is specific enough to become a single `***plain` functional spec (implying ≤200 lines of code change each). If a feature is too large, break it down together with the user before authoring. - -When all topics are complete, summarize the full feature list and the final module/concept layout, and get an explicit overall confirmation before moving to Phase 2. - -#### Review the latest additions - -This is the review loop you trigger after each authoring step above. Walk through **only what just changed** with the user using **AskUserQuestion**, **one snippet at a time**. Do **not** re-review the whole file every iteration — pick the **single most relevant snippet** that warrants a decision (one concept, one functional spec, the module frontmatter) and embed it directly in the question prompt so the user sees exactly what they are approving. - -For each snippet you raise, frame the question around one of: - -- **Missing parts** — anything that should be in the spec but isn't (an attribute, a validation rule, an edge case, a missing concept). +- **Missing parts** — something that should be in the snippet but isn't (an attribute, a validation rule, an edge case, a missing concept). - **Possible extensions** — behavior or detail that could reasonably be expanded. -- **Ambiguities** — wording, ordering, or relationships that could be read multiple ways. - -Each `AskUserQuestion` call offers concrete options such as "Approve as written", "Extend with …", "Clarify …", plus a free-form catch-all. Never batch multiple review questions into one call — if more than one snippet needs review, ask about them sequentially, applying edits between each. - -Apply the user's response back to the `.plain` files (using the appropriate edit skills) **immediately after each answer**, even if the edit is partial or you expect a follow-up to refine it. Re-surface any snippet that materially changed. Continue until every snippet you flagged has been explicitly approved before returning to the topic loop and moving on to the next topic. - ---- - -### Phase 2 — What technologies should it use? - -Gather the technical stack **and** the project's structure/architecture. This phase only affects `***implementation reqs***` — testing-related concerns are handled later. - -This phase is **incremental and one-question-at-a-time**, just like Phase 1. Walk through the topics below in order, and for each topic run a tight loop where **every iteration is one question followed by an immediate write to disk**: - -1. **Ask** — use **AskUserQuestion** with **exactly one** question per call. Frame it with concrete options plus a free-form catch-all. When the user has no preference, propose a sensible default that fits earlier answers and ask them to confirm. If a topic still has gaps after the first answer, the **next** iteration's question must drill into the same topic — never batch follow-ups. -2. **Author immediately** — the moment the user answers, write the resulting requirement to `***implementation reqs***`: - - If a template module exists, put shared stack-wide reqs (language, framework, architecture, coding standards) there. - - Put module-specific reqs (e.g. data storage choices, external service integrations only one module uses) on the module that needs them. - Do **not** add `***test reqs***` or `***acceptance tests***` in this phase — they belong to later phases. - - If a later answer contradicts or refines a requirement already on disk, **update the affected req in place right now**, before the next question. -3. **Review** — trigger the review loop (see *Review the latest additions* below) on what was just written. Apply the user's response back to the `.plain` files and re-surface any snippet that materially changed. Only move on to the next topic once every flagged snippet has been explicitly approved. - -Walk through these topics in order, running ask → author → review for each. Skip a topic only if it genuinely doesn't apply, and say so explicitly: - -1. **Programming language** — e.g. Python, TypeScript, Java, Go. Author: a language requirement at the appropriate scope (template if shared across modules, otherwise on the module). Review: that requirement snippet. -2. **Frameworks** — e.g. Flask, FastAPI, Next.js, Spring Boot, Express, React, Vue. Author: framework requirement(s) and any framework-specific architectural conventions. Review: the new framework reqs. -3. **Data storage** — e.g. PostgreSQL, SQLite, file-based, in-memory, none. Author: storage requirement on the module that owns persistence (or template if shared). Review: that snippet. -4. **External services or APIs** — anything the app talks to (auth providers, payment gateways, email/SMS, third-party APIs, internal services). Author: one requirement per integration on the module that uses it. Review: each integration snippet. -5. **Project structure & architecture** — the architectural style and the layers/components the project should be organized into (e.g. managers, services, models, repositories, controllers, views, adapters, DTOs). Discuss naming conventions, directory layout, and the responsibilities/boundaries of each layer. If the user has no preference, propose a layout that fits the language, framework, and feature set, and confirm it. Author: architecture/layering reqs in the template (if shared) and any module-specific deviations on the module. Review: the architecture reqs and the resulting layer split. -6. **Other constraints** — deployment target, OS requirements, performance needs, coding standards, security policies, observability, anything stack-wide that hasn't already been covered. Author: each constraint as its own requirement at the appropriate scope. Review: the new constraint snippets. -7. **Anything else** — anything the user wants to add or change that hasn't already been covered. - -When all topics are complete, summarize the full tech stack and the chosen architecture, and get an explicit overall confirmation before moving to Phase 3. - -#### Review the latest additions - -Same shape as the Phase 1 review loop. Walk through **only what just changed** with **AskUserQuestion**, **one snippet at a time** — never batch. Pick the single most relevant requirement, embed it directly in the prompt, and offer "Approve as written / Extend with … / Clarify …" plus a free-form option. Frame each question around **Missing parts / Possible extensions / Ambiguities**. - -Apply the user's response back to the `.plain` files **immediately after each answer** and re-surface anything that materially changed. Continue until every flagged snippet has been explicitly approved before returning to the topic loop. - ---- - -### Phase 3 — How should testing be done? - -Gather the testing strategy. This phase covers `***test reqs***`, `***acceptance tests***`, the testing scripts under `test_scripts/`, and the `config.yaml` file(s) that wire them in. - -This phase is **incremental and one-question-at-a-time**, just like Phase 1 and Phase 2. Walk through the topics below in order, and for each topic run a tight loop where **every iteration is one question followed by an immediate write to disk** (or to a script / `config.yaml`): - -1. **Ask** — use **AskUserQuestion** with **exactly one** question per call. Frame it with concrete options plus a free-form catch-all. When the user has no preference, propose a sensible default that fits the language and stack chosen in Phase 2 and ask them to confirm. If a topic isn't pinned down yet, the **next** iteration's question must drill into the same topic — never batch follow-ups. -2. **Author immediately** — the moment the user answers, translate it into the right place: - - **`***test reqs***`** for testing rules and expectations (framework, layout, conventions, coverage, constraints). Use `add-test-requirement`. Put shared reqs on the template module if one exists; module-specific reqs (e.g. integration tests bound to a particular external service) go on the module that needs them. - - **`***acceptance tests***`** under the relevant functional spec, authored via `add-acceptance-test`. Only author these when conformance testing is opted in (see topic 3). - - **Scripts under `test_scripts/`** - - Use skill `implement-unit-testing-script` to implement the unit testing script (Determine during 1. **Ask**) - - Use skill `implement-prepare-environment-script` to implement prepare environment script (Determine during 1. **Ask**) - - Use skill `implement-conformance-testing-script` to implement conformance testing script (Determine during 1. **Ask**) - - **`config.yaml`** entries — every time a script is generated, update the relevant `config.yaml`(s) to point at it. Only include entries for scripts that were actually generated. -3. **Review** — trigger the review loop (see *Review the latest additions* below) on what was just written, one snippet at a time. Apply the user's response back to the `.plain` files, the scripts, and the `config.yaml`(s), and re-surface anything that materially changed. Only move on to the next topic once every flagged snippet has been explicitly approved. - - If a later answer contradicts or refines content already on disk (a script, a `config.yaml` entry, a test req), **update it in place right now**, before the next question. - -#### Plan the `config.yaml` split - -Before topic 1, decide how many `config.yaml` files this project needs. The rule is **one `config.yaml` per part of the system that has its own testing scripts**: - -- A single-stack project (e.g. one Python service) gets one `config.yaml` at the project root. -- A multi-part project gets one `config.yaml` **per part**. For example, a backend in Python/FastAPI and a frontend in React end up with two: `backend/config.yaml` referencing the Python scripts and `frontend/config.yaml` referencing the JS scripts. Each config only references its own scripts; do not mix them. -- The split should follow the module boundaries from Phase 1 / Phase 2: if a module has its own language, framework, and test scripts, it gets its own `config.yaml` next to that module. - -State the planned split to the user (e.g. "I'll create `backend/config.yaml` and `frontend/config.yaml`") and confirm. The config files themselves don't need to exist yet — each one will be created the first time a script is generated for its part, and entries will accumulate as you walk the topics below. For reference, valid keys are: - -```yaml -unittests-script: test_scripts/run_unittests_. -conformance-tests-script: test_scripts/run_conformance_tests_. -prepare-environment-script: test_scripts/prepare_environment_. -``` +- **Ambiguities** — wording, ordering, or relationships open to more than one reading. -Use `.sh` on macOS/Linux and `.ps1` on Windows, matching what testing scripts. Preserve any existing fields in a `config.yaml` you are updating. +Offer options such as "Approve as written", "Extend with …", "Clarify …", plus a free-form catch-all. Ask about one snippet at a time — never batch review questions. Apply each answer back to the `.plain` files (and scripts / `config.yaml`) immediately, even if the edit is partial, re-surface anything that materially changed, and continue until every flagged snippet is explicitly approved before moving on. -**Hard partition reminder.** Throughout this phase: +## Phase sequencing -- **Everything about `:UnitTests:`** (framework, layout, packages, conventions, execution command, coverage, mocking policy — every fact) is authored into `***implementation reqs***` via `add-implementation-requirement`. The unit-test generator reads only that section -- **Everything about `:ConformanceTests:`** (framework, layout, packages, execution command, mocking policy, environment prereqs — every fact) is authored into `***test reqs***` via `add-test-requirement`. The conformance-test generator reads only that section -- A topic that mixes both kinds of facts is split: unit facts go to impl reqs, conformance facts go to test reqs. They never share a bullet +The workflow is four gated phases. **Finish each phase — its specs on disk *and* explicitly approved — before starting the next.** Do not draft, or even *ask about*, later-phase content while a phase is open: if an answer drifts ahead (e.g. picking a framework while still on functional specs), acknowledge it briefly, note it for later, and steer back. Do not branch into a multi-question detour about later-phase topics. Talk is not output; the `.plain` files are. -Walk through these topics in order, running ask → author → review for each. Skip a topic only if it genuinely doesn't apply, and say so explicitly: +When entering a phase, read its reference file and walk its topics **in order** using the core loop and review loop above. Skip a topic only if it genuinely does not apply, and say so explicitly. -1. **Unit-test framework** — e.g. pytest, Jest, JUnit, Go's `testing` package. If the user has no preference, suggest one that fits the language chosen in Phase 2. - - Author: a `:UnitTests:` framework requirement in `***implementation reqs***` at the appropriate scope (template if shared, otherwise on the module) — e.g. "`:UnitTests:` should use pytest" plus "`:UnitTests:` are run via `pytest tests/`". Generate `run_unittests` (and any framework config files it needs, e.g. `pytest.ini`, `jest.config.js`) via `implement-unit-testing-script`. Add the `unittests-script:` entry to the relevant `config.yaml`(s), creating each file if it doesn't exist yet. - - Review: the framework req, the generated script paths, and the new `config.yaml` entry. -2. **Unit-test types and architecture mapping** — unit tests and integration tests. Which combinations does the user want? How do tests map to the architectural layers established in Phase 2 (e.g. one test module per service, repository tests with an in-memory store, etc.)? - - Author: a `:UnitTests:` scope / architecture requirement in `***implementation reqs***` describing which types are in scope and how they map to the architecture — phrased in terms of `:UnitTests:` so the partition is visible. - - Review: that requirement. -3. **Conformance testing** — explicitly ask whether conformance/end-to-end tests should be part of the project. Conformance testing drives whether `run_conformance_tests` is generated and whether `***acceptance tests***` are authored. If the user is unsure, briefly explain the tradeoff (extra scripts + per-spec acceptance tests vs. lighter setup) and let them choose. - - Author (if yes): - - A conformance-testing requirement in `***test reqs***` (framework, execution command, any constraints). - - `run_conformance_tests` via `implement-conformance-testing-script`. - - The `conformance-tests-script:` entry in the relevant `config.yaml`(s). - - **Walk every functional spec authored in Phase 1, one at a time.** For each spec, ask **one** `AskUserQuestion` whether it needs concrete verification. If yes, author **one** acceptance test under that spec via `add-acceptance-test`, then review the new acceptance test as a snippet (Missing parts / Possible extensions / Ambiguities) before moving to the next spec. Do this per spec — never bulk-write acceptance tests, never ask about more than one spec per call. - - Author (if no): record the decision; skip the conformance script, the conformance config entry, and acceptance-test authoring entirely. - - Review: the conformance req (if any), the new script and config entry (if any), and each acceptance test snippet (if any). -4. **Environment preparation script** — explicitly ask whether a `prepare_environment` script should be generated. This is the single entry point for installing dependencies and setting up fixtures/services before tests run. If the user is unsure, briefly explain that it's recommended when there are dependencies to install or services to start, and skippable when the project genuinely has nothing to prepare. - - Author (if yes): `prepare_environment` via `implement-prepare-environment-script`; add the `prepare-environment-script:` entry to the relevant `config.yaml`(s); if the script's responsibilities are non-trivial and worth pinning in the spec, also add a brief `***test reqs***` entry describing what `prepare_environment` is responsible for. - - Author (if no): record the decision; skip the script and the config entry. - - Review: the script (if any), the new config entry (if any), and the test req (if any). -5. **Test layout & conventions** — directory layout, naming conventions, fixtures / mocks strategy, anything that constrains the *shape* of test code beyond what topics 1–4 already established. Ask about both kinds of tests where applicable; keep their facts in separate reqs in separate sections. - - Author: `:UnitTests:` layout / convention requirements in `***implementation reqs***`; `:ConformanceTests:` layout / convention requirements in `***test reqs***` (only when conformance is enabled). Phrase each one with the predefined concept it shapes so the partition is visible. - - Review: each requirement snippet. -6. **Execution & tooling** — how tests are run (commands, runners, options), coverage targets, CI integration, any environment setup tests rely on beyond `prepare_environment`. Split by concept the same way as topic 5. If the agreed execution command or options differ from what the script generated in topic 1 (or 3, or 4) currently uses, update the affected script(s) now. - - Author: `:UnitTests:` execution requirements in `***implementation reqs***`; `:ConformanceTests:` execution requirements in `***test reqs***`. Update any affected scripts under `test_scripts/`. - - Review: each requirement snippet and any modified script. -7. **Other testing constraints** — performance/load expectations, deterministic seeds, network isolation, secrets handling, anything stack-wide that constrains *how* tests are written and that hasn't already been covered. - - Author: each constraint as its own requirement at the appropriate scope. - - Review: each constraint snippet. -8. **Anything else** — anything the user wants to add or change that hasn't already been covered. +| Phase | Reference | Finished when | +|---|---|---| +| 1 — What are we building? | `references/phase-1-product.md` | the new `***definitions***` and `***functional specs***` are on disk and approved | +| 2 — What tech should it use? | `references/phase-2-tech.md` | the new `***implementation reqs***` are on disk and approved | +| 3 — How is testing done? | `references/phase-3-testing.md` | the `***test reqs***` (and `***acceptance tests***` if conformance is on) are on disk, the `test_scripts/` and `config.yaml`(s) exist, and `check-plain-env` passed or each gap was acknowledged | +| 4 — Validate and hand off | `references/phase-4-validate-handoff.md` | the agent ran `codeplain .plain --dry-run` successfully against the render target, and the user has the render command plus every side-channel command | -When all topics are complete, briefly recap the full testing strategy: which `config.yaml`(s) exist, which scripts each one points at, the framework, test types in scope, the conformance and prepare-environment decisions, and any cross-cutting constraints. Get an explicit overall confirmation before moving to environment verification. +Between phases, summarize what was built and get an explicit overall confirmation before continuing — the full feature list and module/concept layout after Phase 1; the tech stack and architecture after Phase 2; the testing strategy (config files, scripts, framework, test types, conformance/prepare-environment decisions) after Phase 3. -#### Review the latest additions +Before advancing out of any phase, spawn a **review agent** to run that phase's block of the **Self-check checklist** (`references/checklist.md`) and loop (fix → re-review) until it returns `APPROVED` — see *Agent orchestration*. Do not advance on an unmet box, and never self-certify the gate. -Same shape as the Phase 1 and Phase 2 review loops. Ask **one** review question at a time via `AskUserQuestion`, framed around **Missing parts / Possible extensions / Ambiguities**. Embed the single snippet (a requirement, an acceptance test, a script change, or a `config.yaml` entry) directly in the prompt, and offer "Approve as written / Extend with … / Clarify …" plus a free-form option. Never batch. +## Adding features later -Apply the user's response back to the `.plain` files, the scripts, and the `config.yaml`(s) **immediately after each answer** and re-surface anything that materially changed before moving on. - -#### Verify the user's environment - -Once the review is complete, delegate environment verification to the **`check-plain-env`** skill. Do **not** probe the user's machine inline here — `check-plain-env` is the single source of truth for "can this machine render and test this project?" and it derives the requirement list at runtime from the project's `.plain` files, `test_scripts/`, `config.yaml`(s), and `resources/`. - -What `check-plain-env` does on your behalf: - -- Detects the host OS. -- Builds the requirement list at runtime (language toolchains + their package managers, external services, system binaries that language packages wrap, hardware / drivers / accelerators, `codeplain` itself, credentials) — the layers a package manager **cannot** install. -- Probes each requirement with an actual version / availability command. -- Never probes individual language packages (`torch`, `numpy`, `FastAPI`, `react`, JARs, gems, ...) — those are installed by the project's own `prepare_environment` / unit-test scripts the moment they run. -- Emits a `PASS` / `WARN` / `FAIL` report with OS-specific install commands for any gaps. Read-only — never installs anything itself. - -Invoke `check-plain-env`, then act on its return value: - -- **`PASS`** — the machine is ready. Continue to Phase 4. -- **`WARN`** — everything required is present but at least one soft warning (e.g. service binary present but daemon not running, language version mismatch). Show the warnings to the user; let them decide whether to address each one now or proceed knowing the corresponding scripts will surface the issue later. -- **`FAIL`** — at least one required item is missing. For every gap, the report already includes **what** is missing, **why** the project needs it, and **how to install it** for the detected OS. Walk the gaps with the user and, for each one, ask whether they want to install it now, swap to an alternative (which would mean revising the Phase 2 / Phase 3 decisions), or proceed knowing the corresponding scripts will fail. Re-invoke `check-plain-env` after the user installs anything so the report reflects the current state of the machine. - -Do not move on to Phase 4 until either `check-plain-env` returns `PASS` / `WARN` with the user's explicit acknowledgement of each warning, or the user has explicitly acknowledged each remaining `FAIL`. - ---- - -### Phase 4 — Validate and hand off - -Phase 4 has two halves. First, **you** (the agent) validate every spec end-to-end with a dry-run of the `codeplain` CLI so the user doesn't waste a real render — or any debugging time — on a fixable static error. Only after that passes do you **hand off** the render command (plus any side-channel commands) to the user. - -#### 4a. Identify the render target - -Find the **last module in the dependency chain** — the module that is not `requires`-ed by any other module. If there is only one module, use it. Call this module ``. - -Examples: - -- Chain `base.plain → features.plain → integrations.plain` → render target is `integrations.plain`. -- Single module `my_app.plain` → render target is `my_app.plain`. - -#### 4b. Build the final `config.yaml` with `init-config-file` - -Before validation, finalize the project's `config.yaml` file(s). Phase 3 may have written provisional entries as scripts were generated; **this** is where they're consolidated into the canonical form the renderer expects. - -Invoke the **`init-config-file`** skill. It: - -- enumerates every part of the project (one `config.yaml` per part — single-stack → root config; multi-part → one config per part), -- assembles only the **valid** config keys derived from the `codeplain` CLI parser, -- emits a clean YAML file per part (script paths first, then template/build folders, then copy/log settings), -- verifies every `*-script` value resolves to a real file on disk, -- refuses to write secrets (`api-key`) or per-invocation flags (`dry-run`, `full-plain`, `render-range`, `render-from`, `replay-with`) into the config. - -If `init-config-file` stops because a precondition isn't met (e.g. `prepare-environment-script` exists but no conformance script does), resolve the gap with the user before continuing — do **not** hand the project to `plain-healthcheck` with a known-broken config. - -#### 4c. Validate the project with `plain-healthcheck` - -Before handing off to the user, run the **`plain-healthcheck`** skill. It is the single source of truth for "is this project ready to render?" — it: - -- inventories every `.plain` module and identifies every top module, -- validates every `config.yaml` (existence, parseability, script paths actually point at files in `test_scripts/`, no mixed stacks, etc.), and -- runs `codeplain .plain --dry-run` for **every** top module with the correct `--config-name` for multi-part projects. - -Do **not** run the dry-run inline here. Delegate to `plain-healthcheck`. The skill handles the full detect → fix → re-run loop on its own (syntax errors, undefined concepts, broken `import` / `requires` chains, cyclic definitions, missing templates, complexity violations, conflicting reqs, config drift, missing scripts, …) and only returns once everything passes or a gap genuinely needs the user. - -Then: - -- **`plain-healthcheck` returns `PASS`** → move on to step 4d. -- **`plain-healthcheck` returns `FAIL`** → do **not** ask the user to render. Work through the numbered list it produced (each item references a specific `.plain` file, `config.yaml`, or script), resolve each one with the appropriate edit skill, and re-run `plain-healthcheck` until it returns `PASS`. Any item the skill could not auto-resolve will name the concrete question to put to the user. -- **Environment failure** (e.g. `codeplain` not on PATH, `CODEPLAIN_API_KEY` not set) → `plain-healthcheck` will surface this with a clearly-marked environment failure. Tell the user exactly what's missing and how to fix it before continuing. Do not pretend the healthcheck passed. - -For the full list of `codeplain` flags `plain-healthcheck` may use, see the CLI reference at the end of this section. - -#### 4d. Present the render command - -Only after the dry-run passes, tell the user their specs are ready and present the render command: - -``` -codeplain .plain -``` - -Examples: - -- Chain `base.plain → features.plain → integrations.plain`: - - ``` - codeplain integrations.plain - ``` - -- Single module with no chain (e.g. `my_app.plain`): - - ``` - codeplain my_app.plain - ``` - -Also remind the user of any **side-channel commands** they may want to run themselves per the testing strategy locked in during Phase 3 — for example `./test_scripts/run_unittests.sh `, `./test_scripts/prepare_environment.sh `, or `./test_scripts/run_conformance_tests.sh `. Only mention the scripts that were actually generated in Phase 3. - -### Adding features to an existing project - -Once the initial `***plain` specs are written, the user will come back with new features. Use the `add-feature` skill for this — it runs the same interview → implement → review loop but scoped to a single feature against an existing `.plain` file. Always communicate that you are updating the `***plain` specs, not the generated code. This keeps the conversation continuous: the user describes a feature, you ask clarifying questions, write the `***plain` specs, and repeat. - ---- +Once the initial specs exist, the user will return with new features. Use the `add-feature` skill — the same interview → author → review loop scoped to a single feature on an existing `.plain` file. Keep framing the work as updating the specs, not the generated code. ## Question style -The questions you put to the user must use simple grammatical structures: - - Prefer short, direct sentences over compound or nested clauses. - Use plain words over jargon when both convey the same meaning. -- One idea per sentence. If a sentence needs a comma-separated list of clauses, split it. +- One idea per sentence; split a comma-chained list of clauses into separate sentences. +- Never drop detail to simplify — keep every constraint, edge case, option, and piece of context the user needs to answer accurately, splitting into more sentences instead. + +## Error handling + +- **A user answer contradicts prior specs** → edit the affected snippet in place immediately, then continue the loop; surface a non-trivial change in the next question. +- **A phase gate is not met** (a review agent returned unmet boxes) → do not advance; fix each gap through the interview → author → review loop, then spawn a fresh review agent and repeat until `APPROVED`. +- **A review agent can't be spawned or returns nothing usable** → spawn a fresh one; do not self-certify the gate. Never `SendMessage` a running reviewer — capture its returned result. If spawning genuinely fails in this environment, walk the phase's checklist block inline as a fallback and tell the user the review was self-run. +- **`check-plain-env` returns `FAIL`** (Phase 3) → walk each gap with the user; install, swap to an alternative, or explicitly acknowledge it before Phase 4. Re-invoke after any install. +- **`plain-healthcheck` returns `FAIL`** (Phase 4) → do not present the render command; work through its numbered list with the right edit skill and re-run until it passes. +- **Environment failure** (`codeplain` not on PATH, `CODEPLAIN_API_KEY` unset) → tell the user exactly what is missing and how to fix it; never pretend the check passed. -Simpler grammar must not come at the cost of detail. Keep every constraint, edge case, option, and piece of context the user needs to answer accurately. If simplifying a sentence would drop a detail, split it into more sentences instead. +## Self-check checklist ---- +`references/checklist.md` is the **review agent's** one-shot script, not the orchestrator's. At each phase gate the orchestrator spawns a fresh review agent (see *Agent orchestration*) and puts everything it needs in the spawn prompt: that phase's block, the loop-iteration block (Phases 1–3), the touched-file paths, and a **phase evidence record** of what happened in the interview. The reviewer verifies each box by its tag — `[disk]` against the files, `[record]` against the evidence record, `[both]` cross-checked with mismatches flagged — and returns `APPROVED` or a numbered gap list. The orchestrator loops (fix → re-review with a fresh agent) until a review agent approves, and never advances on an unmet box or self-certifies the gate. -### Reference +## Reference -- Full ``***plain`` language guide: PLAIN_REFERENCE.md -- Skills for editing specs are in `.claude/skills/` -- Templates go in `template/`, but import paths omit the `template/` prefix. Resources go in `resources/` -- Generated code lands in `plain_modules/` (read-only, never edit) -- Test scripts are in `test_scripts/` +- Applicable language rules and operational references: `load-plain-reference`. +- Spec-editing skills live in `.claude/skills/`. +- Templates go in `template/`, but import paths omit the `template/` prefix. Resources go in `resources/`. +- Generated code lands in `plain_modules/` (read-only, never edit). Test scripts live in `test_scripts/`. diff --git a/forge/skills/forge-plain/references/checklist.md b/forge/skills/forge-plain/references/checklist.md new file mode 100644 index 0000000..71d6e22 --- /dev/null +++ b/forge/skills/forge-plain/references/checklist.md @@ -0,0 +1,57 @@ +# Forge Plain Workflow Checklist + +This is the **review agent's** script, and the review agent is **one-shot**: it gets everything in its spawn prompt, runs to completion, and returns a single result. It cannot ask the orchestrator or the user anything mid-run — no supported runtime (Claude Code, Codex, OpenCode) lets a subagent converse back. So at each phase gate the orchestrator supplies, in the spawn prompt: this block for the phase being closed, the loop-iteration block (Phases 1–3 only), the paths of the files touched this phase, and a **phase evidence record** — the ordered account of what happened in the interview (each question, its answer, the snippet written or edited, the approval given, and every explicit decision). Load the `***plain` rules with `load-plain-reference` before judging, so boxes like ≤200 LOC, define-before-use, and language-agnostic can be assessed. + +Verify each box by its tag: + +- **[disk]** — check independently against the files on disk; ignore the record. +- **[record]** — check against the evidence record; the disk cannot show it. +- **[both]** — the record claims it and the disk must corroborate; **flag any mismatch** (e.g. the record says conformance was declined but a conformance script exists on disk, or claims a spec was approved that is not on disk). + +A box is met only when its source confirms it. Return `APPROVED` only when every box in the relevant blocks passes; otherwise return a numbered list of the unmet boxes, each naming the exact file (and line where relevant) or the record item at fault. The orchestrator loops (fix → re-review with a fresh agent) until a review agent returns `APPROVED`; it never advances on an unmet box. + +## Every loop iteration (Phases 1–3) + +- [ ] **[record]** Asked exactly one focused question via `AskUserQuestion` each iteration — not two bundled together. +- [ ] **[both]** Wrote the snippet to disk immediately after the answer, using the dedicated edit skill — never hand-authored a `***plain` section directly. +- [ ] **[record]** Reviewed only what just changed (missing parts / extensions / ambiguities), applied the response back to disk, and got explicit approval before moving on. +- [ ] **[both]** Any answer that contradicted an earlier snippet was fixed in place before the next question — no stale spec left on disk. +- [ ] **[both]** Stayed within the current phase — no drafting of later-phase content on disk, no multi-question detours into later-phase topics. + +## Phase 0 — Setup + +- [ ] **[record]** Invoked `load-plain-reference` first (unless already loaded this session). + +## Phase 1 — What are we building? + +- [ ] **[record]** Read `references/phase-1-product.md` and walked its topics in order (app, users, scope, entities, features, flows, constraints, UI if any, anything else) — skipped topics called out explicitly. +- [ ] **[disk]** `.plain` module structure created with YAML frontmatter; template (if any) has no `***functional specs***`. +- [ ] **[both]** Every concept authored in `***definitions***` via `add-concept`, defined before use. +- [ ] **[both]** Every feature authored as functional specs via `add-functional-spec(s)`, each ≤200 LOC, in chronological build order. +- [ ] **[disk]** No `***implementation reqs***`, `***test reqs***`, or `***acceptance tests***` written in this phase. +- [ ] **[record]** Summarized the full feature list and module/concept layout; got explicit overall confirmation. + +## Phase 2 — What tech should it use? + +- [ ] **[record]** Read `references/phase-2-tech.md` and walked its topics in order (language, frameworks, storage, external services, structure/architecture, other constraints, anything else). +- [ ] **[disk]** Every requirement authored into `***implementation reqs***` at the right scope (shared → template, module-specific → module). +- [ ] **[disk]** No `***test reqs***` or `***acceptance tests***` written in this phase. +- [ ] **[record]** Summarized the tech stack and architecture; got explicit overall confirmation. + +## Phase 3 — How is testing done? + +- [ ] **[record]** Read `references/phase-3-testing.md`; stated the planned `config.yaml` split (one per part) and confirmed it before topic 1. +- [ ] **[disk]** Honored the hard partition — every `:UnitTests:` fact in `***implementation reqs***`, every `:ConformanceTests:` fact in `***test reqs***`; never shared a bullet. +- [ ] **[record]** Walked topics in order (unit framework, unit types/architecture, conformance decision, prepare-environment decision, layout, execution, other constraints, anything else). +- [ ] **[disk]** Generated the needed scripts under `test_scripts/` via the `implement-*-script` skills and added each `*-script:` entry to the right `config.yaml`. +- [ ] **[both]** Conformance decision was asked explicitly; if yes, walked every Phase-1 functional spec one at a time for acceptance tests via `add-acceptance-test` (acceptance tests present on disk when the decision was yes). +- [ ] **[record]** Prepare-environment decision was asked explicitly and recorded. +- [ ] **[record]** Recapped the testing strategy; got explicit overall confirmation. +- [ ] **[record]** Ran `check-plain-env`; it returned `PASS`, or `WARN`/`FAIL` with each remaining item explicitly acknowledged by the user (re-invoked after any install). + +## Phase 4 — Validate and hand off + +- [ ] **[disk]** Identified the render target — the last module in the dependency chain (or the single module). +- [ ] **[both]** Ran `init-config-file` to build the final `config.yaml`(s); resolved any precondition gap with the user before validating. +- [ ] **[record]** Ran `plain-healthcheck`; worked its numbered list to `PASS` — never presented the render command on a `FAIL`. +- [ ] **[record]** Presented the render command only after the dry-run passed, plus every side-channel script actually generated in Phase 3. diff --git a/forge/skills/forge-plain/references/phase-1-product.md b/forge/skills/forge-plain/references/phase-1-product.md new file mode 100644 index 0000000..fee9886 --- /dev/null +++ b/forge/skills/forge-plain/references/phase-1-product.md @@ -0,0 +1,35 @@ +# Phase 1 — What are we building? + +Understand the product. This is the most important phase; do it thoroughly and drill into the app's behavior. Run the core loop (ask → author → review) from `SKILL.md` for each topic below, in order. This phase is incremental and one-question-at-a-time — never ask everything up front and write all the specs at the end. + +## Author targets for this phase + +The moment the user answers, write the resulting snippet to disk using the right skill: + +- **Module structure** — create or update the `.plain` file(s) (single module, template + modules, or chained modules). Set up YAML frontmatter (`import`, `requires`, `description`) and the proposed module name. If a template is used, create it in `template/` **without** a `***functional specs***` section. Use the `create-import-module` skill where applicable. +- **`***definitions***`** — add or refine concepts (entities, attributes, relationships). Define every concept before it is referenced. Use the `add-concept` skill. +- **`***functional specs***`** — translate each answer into a single chronological, incremental spec (≤200 lines of code change, language-agnostic, no conflicts). Use `add-functional-spec` for one new spec; use `add-functional-specs` only when a single answer naturally decomposes into a tight cluster of specs flowing from the same answer (e.g. list / create / delete CRUD on one entity). **Never hand-author the `***functional specs***` section directly.** + +Do **not** add `***implementation reqs***`, `***test reqs***`, or `***acceptance tests***` in this phase — they belong to later phases. + +## Topics (in order) + +1. **What is the app?** — one sentence: what problem does it solve? Free-form, not multiple choice. Author a stub `.plain` file with the description in the YAML frontmatter and the proposed module name. Review the frontmatter and the module split. +2. **Who uses it?** — target users or personas; is it a CLI tool, web app, API, desktop app, mobile app, library, or something else? Author any user/persona concepts that emerge. Review those concepts. +3. **What is the scope?** — MVP, prototype, or full product? What is explicitly out of scope? Tighten the description and, if needed, split modules to keep MVP scope cohesive. Review the resulting module structure. +4. **Core entities** — the main "things" in the system (Users, Tasks, Orders, Messages, …), their attributes, and relationships. Author one concept per entity in `***definitions***`. Review each concept snippet. +5. **Key features** — every distinct thing the app should do. For each feature capture the trigger, the expected outcome, and edge cases / validation rules. Author one or more functional specs per feature, in chronological build order, each ≤200 LOC. Break large features into smaller specs together with the user. Review each new functional spec (or tight group of related specs). +6. **User flows** — walk through the app from the user's perspective: what happens first, what happens next, and at each decision point. Author the ordering and any missing intermediate functional specs. Review the affected sequence of specs. +7. **Constraints and rules** — business rules, validation, permissions, error-handling behavior. Fold these into the relevant functional specs, and add concepts where they are first-class entities (e.g. roles). Review the updated specs. +8. **User interface (optional)** — skip entirely if the project has no UI. Otherwise ask, one question per iteration: + - How does the UI look and feel? + - Where are the key UI elements located? + - What do the key UI elements do? + - What is the layout and design of the UI? + + Author UI-behavior functional specs (still language- and framework-agnostic). Review those specs. +9. **Anything else** — anything the user wants to add or change that hasn't already been covered. + +Keep asking follow-ups within a topic until every feature is specific enough to become a single functional spec (implying ≤200 lines of code change each). If a feature is too large, break it down together with the user before authoring. + +When all topics are complete, summarize the full feature list and the final module/concept layout, and get an explicit overall confirmation before moving to Phase 2. diff --git a/forge/skills/forge-plain/references/phase-2-tech.md b/forge/skills/forge-plain/references/phase-2-tech.md new file mode 100644 index 0000000..e32183e --- /dev/null +++ b/forge/skills/forge-plain/references/phase-2-tech.md @@ -0,0 +1,24 @@ +# Phase 2 — What technologies should it use? + +Gather the technical stack **and** the project's structure/architecture. This phase affects only `***implementation reqs***` — testing concerns come later. Run the core loop (ask → author → review) from `SKILL.md` for each topic below, in order. When the user has no preference, propose a sensible default that fits earlier answers and ask them to confirm. + +## Author target for this phase + +Write each requirement into `***implementation reqs***`: + +- Shared, stack-wide reqs (language, framework, architecture, coding standards) go on the template module if one exists. +- Module-specific reqs (e.g. a data-storage choice or an external integration only one module uses) go on the module that needs them. + +Do **not** add `***test reqs***` or `***acceptance tests***` in this phase — they belong to later phases. + +## Topics (in order) + +1. **Programming language** — e.g. Python, TypeScript, Java, Go. Author a language requirement at the appropriate scope (template if shared across modules, otherwise on the module). Review that requirement snippet. +2. **Frameworks** — e.g. Flask, FastAPI, Next.js, Spring Boot, Express, React, Vue. Author framework requirement(s) and any framework-specific architectural conventions. Review the new framework reqs. +3. **Data storage** — e.g. PostgreSQL, SQLite, file-based, in-memory, none. Author a storage requirement on the module that owns persistence (or the template if shared). Review that snippet. +4. **External services or APIs** — anything the app talks to: auth providers, payment gateways, email/SMS, third-party APIs, internal services. Author one requirement per integration on the module that uses it. Review each integration snippet. +5. **Project structure & architecture** — the architectural style and the layers/components the project should be organized into (managers, services, models, repositories, controllers, views, adapters, DTOs, …). Discuss naming conventions, directory layout, and the responsibilities/boundaries of each layer. If the user has no preference, propose a layout that fits the language, framework, and feature set, and confirm it. Author architecture/layering reqs in the template (if shared) and any module-specific deviations on the module. Review the architecture reqs and the resulting layer split. +6. **Other constraints** — deployment target, OS requirements, performance needs, coding standards, security policies, observability — anything stack-wide that hasn't already been covered. Author each constraint as its own requirement at the appropriate scope. Review the new constraint snippets. +7. **Anything else** — anything the user wants to add or change that hasn't already been covered. + +When all topics are complete, summarize the full tech stack and the chosen architecture, and get an explicit overall confirmation before moving to Phase 3. diff --git a/forge/skills/forge-plain/references/phase-3-testing.md b/forge/skills/forge-plain/references/phase-3-testing.md new file mode 100644 index 0000000..68abb2f --- /dev/null +++ b/forge/skills/forge-plain/references/phase-3-testing.md @@ -0,0 +1,66 @@ +# Phase 3 — How should testing be done? + +Gather the testing strategy. This phase covers `***test reqs***`, `***acceptance tests***`, the scripts under `test_scripts/`, and the `config.yaml` file(s) that wire them in. Run the core loop (ask → author → review) from `SKILL.md` for each topic below, in order. When the user has no preference, propose a default that fits the language and stack chosen in Phase 2 and ask them to confirm. + +## Hard partition (applies to every topic) + +- **Everything about `:UnitTests:`** — framework, layout, packages, conventions, execution command, coverage, mocking policy (every fact) — is authored into `***implementation reqs***` via `add-implementation-requirement`. The unit-test generator reads only that section. +- **Everything about `:ConformanceTests:`** — framework, layout, packages, execution command, mocking policy, environment prereqs (every fact) — is authored into `***test reqs***` via `add-test-requirement`. The conformance-test generator reads only that section. +- A topic that mixes both kinds of facts is split: unit facts go to impl reqs, conformance facts go to test reqs. They never share a bullet. + +## Plan the `config.yaml` split (before topic 1) + +Decide how many `config.yaml` files this project needs. The rule is **one `config.yaml` per part of the system that has its own testing scripts**: + +- A single-stack project (e.g. one Python service) gets one `config.yaml` at the project root. +- A multi-part project gets one `config.yaml` **per part**. A Python/FastAPI backend and a React frontend end up with two: `backend/config.yaml` referencing the Python scripts and `frontend/config.yaml` referencing the JS scripts. Each config references only its own scripts; never mix them. +- The split follows the module boundaries from Phases 1–2: a module with its own language, framework, and test scripts gets its own `config.yaml` next to it. + +State the planned split to the user (e.g. "I'll create `backend/config.yaml` and `frontend/config.yaml`") and confirm. The files need not exist yet — each is created the first time a script is generated for its part, and entries accumulate as the topics below are walked. Valid script keys: + +```yaml +unittests-script: test_scripts/run_unittests_. +conformance-tests-script: test_scripts/run_conformance_tests_. +prepare-environment-script: test_scripts/prepare_environment_. +``` + +Use `.sh` on macOS/Linux and `.ps1` on Windows, matching the generated testing scripts. Preserve any existing fields in a `config.yaml` being updated. Add a `template_dir` field when any import modules or templates have been added, e.g.: + +```yaml +template_dir: template +``` + +## Topics (in order) + +1. **Unit-test framework** — e.g. pytest, Jest, JUnit, Go's `testing` package. Suggest one that fits the Phase 2 language if the user has no preference. + - Author a `:UnitTests:` framework requirement in `***implementation reqs***` at the appropriate scope (template if shared, otherwise on the module) — e.g. "`:UnitTests:` should use pytest" plus "`:UnitTests:` are run via `pytest tests/`". Generate `run_unittests` (and any framework config it needs, e.g. `pytest.ini`, `jest.config.js`) via `implement-unit-testing-script`. Add the `unittests-script:` entry to the relevant `config.yaml`(s), creating each file if it doesn't exist yet. + - Review the framework req, the generated script paths, and the new `config.yaml` entry. +2. **Unit-test types and architecture mapping** — which of unit tests and integration tests the user wants, and how tests map to the Phase 2 architectural layers (e.g. one test module per service, repository tests with an in-memory store). Author a `:UnitTests:` scope / architecture requirement in `***implementation reqs***`, phrased in terms of `:UnitTests:` so the partition is visible. Review that requirement. +3. **Conformance testing** — explicitly ask whether conformance / end-to-end tests should be part of the project. This drives whether `run_conformance_tests` is generated and whether `***acceptance tests***` are authored. If the user is unsure, briefly explain the tradeoff (extra scripts + per-spec acceptance tests vs. lighter setup) and let them choose. + - If **yes**: author a conformance-testing requirement in `***test reqs***` (framework, execution command, any constraints); generate `run_conformance_tests` via `implement-conformance-testing-script`; add the `conformance-tests-script:` entry to the relevant `config.yaml`(s); then **walk every functional spec authored in Phase 1, one at a time** — for each spec, ask one `AskUserQuestion` whether it needs concrete verification, and if yes author one acceptance test under that spec via `add-acceptance-test`, then review that acceptance test as a snippet before moving to the next spec. Never bulk-write acceptance tests; never ask about more than one spec per call. + - If **no**: record the decision; skip the conformance script, its config entry, and acceptance-test authoring entirely. + - Review the conformance req (if any), the new script and config entry (if any), and each acceptance test snippet (if any). +4. **Environment preparation script** — explicitly ask whether a `prepare_environment` script should be generated. It is the single entry point for installing dependencies and setting up fixtures/services before tests run. If the user is unsure, note it is recommended when there are dependencies to install or services to start, and skippable when the project genuinely has nothing to prepare. + - If **yes**: generate `prepare_environment` via `implement-prepare-environment-script`; add the `prepare-environment-script:` entry to the relevant `config.yaml`(s); if the script's responsibilities are non-trivial and worth pinning in the spec, add a brief `***test reqs***` entry describing what `prepare_environment` is responsible for. + - If **no**: record the decision; skip the script and the config entry. + - Review the script (if any), the new config entry (if any), and the test req (if any). +5. **Test layout & conventions** — directory layout, naming conventions, fixtures / mocks strategy — anything that constrains the *shape* of test code beyond what topics 1–4 established. Ask about both kinds of tests where applicable; keep their facts in separate reqs in separate sections. Author `:UnitTests:` layout / convention requirements in `***implementation reqs***` and `:ConformanceTests:` layout / convention requirements in `***test reqs***` (only when conformance is enabled), each phrased with the predefined concept it shapes so the partition is visible. Review each requirement snippet. +6. **Execution & tooling** — how tests are run (commands, runners, options), coverage targets, CI integration, any environment setup tests rely on beyond `prepare_environment`. Split by concept the same way as topic 5. If the agreed execution command or options differ from what a script generated in topic 1, 3, or 4 currently uses, update the affected script(s) now. Author `:UnitTests:` execution requirements in `***implementation reqs***` and `:ConformanceTests:` execution requirements in `***test reqs***`. Review each requirement snippet and any modified script. +7. **Other testing constraints** — performance / load expectations, deterministic seeds, network isolation, secrets handling — anything stack-wide that constrains *how* tests are written and hasn't already been covered. Author each constraint as its own requirement at the appropriate scope. Review each constraint snippet. +8. **Anything else** — anything the user wants to add or change that hasn't already been covered. + +When all topics are complete, recap the full testing strategy: which `config.yaml`(s) exist, which scripts each points at, the framework, the test types in scope, the conformance and prepare-environment decisions, and any cross-cutting constraints. Get an explicit overall confirmation, then verify the environment. + +## Verify the environment + +Delegate environment verification to the `check-plain-env` skill — do **not** probe the machine inline. It is the single source of truth for "can this machine render and test this project?" and derives the requirement list at runtime from the project's `.plain` files, `test_scripts/`, `config.yaml`(s), and `resources/`. + +`check-plain-env` detects the host OS, builds the requirement list (language toolchains + their package managers, external services, system binaries that language packages wrap, hardware / drivers / accelerators, `codeplain` itself, credentials — the layers a package manager **cannot** install), probes each with a real version / availability command, and emits a `PASS` / `WARN` / `FAIL` report with OS-specific install commands for any gaps. It never probes individual language packages (`torch`, `numpy`, `FastAPI`, `react`, JARs, gems) — those are installed by the project's own `prepare_environment` / unit-test scripts the moment they run. It is read-only and installs nothing itself. + +Act on the return value: + +- **`PASS`** — the machine is ready. Continue to Phase 4. +- **`WARN`** — everything required is present but there is at least one soft warning (e.g. a service binary present but its daemon not running, a language-version mismatch). Show the warnings to the user; let them decide whether to address each one now or proceed knowing the corresponding scripts will surface the issue later. +- **`FAIL`** — at least one required item is missing. For every gap the report already gives what is missing, why the project needs it, and how to install it for the detected OS. Walk the gaps with the user: install now, swap to an alternative (which means revising the Phase 2 / Phase 3 decisions), or proceed knowing the corresponding scripts will fail. Re-invoke `check-plain-env` after the user installs anything so the report reflects the current state of the machine. + +Do not move on to Phase 4 until `check-plain-env` returns `PASS`, or `WARN` / `FAIL` with the user's explicit acknowledgement of each remaining item. diff --git a/forge/skills/forge-plain/references/phase-4-validate-handoff.md b/forge/skills/forge-plain/references/phase-4-validate-handoff.md new file mode 100644 index 0000000..058c3a0 --- /dev/null +++ b/forge/skills/forge-plain/references/phase-4-validate-handoff.md @@ -0,0 +1,49 @@ +# Phase 4 — Validate and hand off + +Two halves. First **the agent** validates every spec end-to-end with a `codeplain` dry-run so the user never wastes a real render — or any debugging time — on a fixable static error. Only after that passes does the agent **hand off** the render command (plus any side-channel commands) to the user. + +## 4a. Identify the render target + +Find the **last module in the dependency chain** — the module that is not `requires`-ed by any other module. If there is only one module, use it. Call this module ``. + +- Chain `base.plain → features.plain → integrations.plain` → render target is `integrations.plain`. +- Single module `my_app.plain` → render target is `my_app.plain`. + +## 4b. Build the final `config.yaml` with `init-config-file` + +Finalize the project's `config.yaml` file(s) before validation. Phase 3 may have written provisional entries as scripts were generated; **this** is where they are consolidated into the canonical form the renderer expects. Invoke the `init-config-file` skill. It: + +- enumerates every part of the project (one `config.yaml` per part — single-stack → root config; multi-part → one config per part), +- assembles only the **valid** config keys derived from the `codeplain` CLI parser, +- emits a clean YAML file per part (script paths first, then template/build folders, then copy/log settings), +- verifies every `*-script` value resolves to a real file on disk, +- refuses to write secrets (`api-key`) or per-invocation flags (`dry-run`, `full-plain`, `render-range`, `render-from`, `replay-with`) into the config. + +If `init-config-file` stops because a precondition isn't met (e.g. a `prepare-environment-script` exists but no conformance script does), resolve the gap with the user before continuing — do **not** hand a known-broken config to `plain-healthcheck`. + +## 4c. Validate the project with `plain-healthcheck` + +Run the `plain-healthcheck` skill — the single source of truth for "is this project ready to render?". Do **not** run the dry-run inline. It: + +- inventories every `.plain` module and identifies every top module, +- validates every `config.yaml` (existence, parseability, script paths actually pointing at files in `test_scripts/`, no mixed stacks), and +- runs `codeplain .plain --dry-run` for **every** top module with the correct `--config-name` for multi-part projects. + +The skill runs the full detect → fix → re-run loop itself (syntax errors, undefined concepts, broken `import` / `requires` chains, cyclic definitions, missing templates, complexity violations, conflicting reqs, config drift, missing scripts) and returns only once everything passes or a gap genuinely needs the user. Then: + +- **`PASS`** → move on to step 4d. +- **`FAIL`** → do **not** ask the user to render. Work through the numbered list it produced (each item references a specific `.plain` file, `config.yaml`, or script), resolve each one with the appropriate edit skill, and re-run `plain-healthcheck` until it returns `PASS`. Any item the skill could not auto-resolve will name the concrete question to put to the user. +- **Environment failure** (e.g. `codeplain` not on PATH, `CODEPLAIN_API_KEY` not set) → `plain-healthcheck` surfaces this as a clearly-marked environment failure. Tell the user exactly what's missing and how to fix it before continuing. Do not pretend the healthcheck passed. + +## 4d. Present the render command + +Only after the dry-run passes, tell the user their specs are ready and present the render command: + +``` +codeplain .plain +``` + +- Chain `base.plain → features.plain → integrations.plain` → `codeplain integrations.plain`. +- Single module `my_app.plain` → `codeplain my_app.plain`. + +Also remind the user of any **side-channel commands** they may want to run themselves per the Phase 3 testing strategy — for example `./test_scripts/run_unittests.sh `, `./test_scripts/prepare_environment.sh `, or `./test_scripts/run_conformance_tests.sh `. Mention only the scripts that were actually generated in Phase 3. diff --git a/forge/skills/init-config-file/SKILL.md b/forge/skills/init-config-file/SKILL.md index 121b10e..52d4467 100644 --- a/forge/skills/init-config-file/SKILL.md +++ b/forge/skills/init-config-file/SKILL.md @@ -13,6 +13,8 @@ description: >- # Init Config File +Always use the skill `load-codeplain-reference` to retrieve the full `codeplain` CLI reference (every flag, the `config.yaml` mapping, and the secrets / per-invocation keys that must never be written to config) — but only if you haven't done so yet. This skill assembles `config.yaml` from the valid CLI-derived keys, so the CLI reference is required. + This skill is the **single authoritative writer** of `config.yaml` for a ***plain project. Anything that ends up in `config.yaml` should go through this skill. The renderer (`codeplain`) reads each key listed below into the same argparse namespace it uses for CLI flags — so a value set in `config.yaml` is exactly equivalent to passing the corresponding `--flag` on the command line. ## When to run diff --git a/forge/skills/init-plain-project/SKILL.md b/forge/skills/init-plain-project/SKILL.md index 68a2985..56b55e4 100644 --- a/forge/skills/init-plain-project/SKILL.md +++ b/forge/skills/init-plain-project/SKILL.md @@ -121,6 +121,10 @@ Tell the user what was created and what's next: - Suggested next steps: add concepts with `add-concept`, add functional specs with `add-functional-spec` or `add-functional-specs`, or jump straight into `add-feature`. - Mention that `codeplain .plain --dry-run` has not been run — they can run `plain-healthcheck` when they want validation. +## Validation checklist + +Before the recap, walk `references/checklist.md` and confirm every box is met. It is a self-audit of this workflow — never a substitute for it. A box only counts as met when the file is on disk as described; complete any unmet step before recapping. + ## Question style Use simple grammatical structures: short direct sentences, one idea per sentence, plain words over jargon. Keep every constraint and edge case the user needs to answer accurately. diff --git a/forge/skills/init-plain-project/references/checklist.md b/forge/skills/init-plain-project/references/checklist.md new file mode 100644 index 0000000..1640ed8 --- /dev/null +++ b/forge/skills/init-plain-project/references/checklist.md @@ -0,0 +1,31 @@ +# Init Plain Project Workflow Checklist + +Use this to verify the scaffold was produced correctly — never as a substitute for the workflow. Run it once before the recap. This skill is intentionally minimal: it writes a runnable skeleton, not a complete spec. If a box is unmet, go back and complete the step. + +## Setup + +- [ ] Invoked `load-plain-reference` first (unless already loaded this session). + +## Ask the basics + +- [ ] Asked project basics in one `AskUserQuestion` batch (project name, base technology, project kind) plus a free-form catch-all. +- [ ] Asked testing in one `AskUserQuestion` batch (unit-test framework, conformance on/off, and prepare-environment only if conformance was enabled). + +## Author the modules + +- [ ] Created `template/base.plain` via `create-import-module` with `***implementation reqs***` (language/version, framework, package manager, project kind, and everything about `:UnitTests:` — framework + run command). +- [ ] Added `***test reqs***` to `template/base.plain` with the `:ConformanceTests:` rules **only** when conformance testing is enabled — and kept unit-test facts out of it. +- [ ] No `***definitions***`, no `***functional specs***`, and no project-specific concepts in `template/base.plain`; only predefined concepts (`:Implementation:`, `:ConformanceTests:`) used; no `required_concepts` declared. +- [ ] Created `.plain` at the repo root with frontmatter only (`import: [base]`, `description`) and **no body sections** — the file exists on disk. + +## Generate scripts and config + +- [ ] Generated `implement-unit-testing-script` (always). +- [ ] Generated `implement-conformance-testing-script` only if conformance was enabled (activate-only variant when a prepare-environment script will also exist, otherwise install-inline). +- [ ] Generated `implement-prepare-environment-script` only if conformance was enabled **and** the user opted in. +- [ ] Wrote `config.yaml` at the project root with only the keys for scripts actually generated, plus `template_dir: template`, using the correct `.sh`/`.ps1` extension for the host OS. +- [ ] Did **not** run `init-config-file`, `plain-healthcheck`, `check-plain-env`, or `codeplain --dry-run` (out of scope for this skill). + +## Recap + +- [ ] Told the user what was written, suggested next steps (`add-concept`, `add-functional-spec(s)`, `add-feature`), and noted that no dry-run was run (they can run `plain-healthcheck` later). diff --git a/forge/skills/load-codeplain-reference/SKILL.md b/forge/skills/load-codeplain-reference/SKILL.md new file mode 100644 index 0000000..20e3af1 --- /dev/null +++ b/forge/skills/load-codeplain-reference/SKILL.md @@ -0,0 +1,188 @@ +--- +name: load-codeplain-reference +description: >- + Loads the full `codeplain` CLI reference into context: the render command and + its positional plain-file argument, every flag (render control, config, + folders, test-script wiring, output copying, API, logging, headless mode), + path-resolution rules, the CODEPLAIN_API_KEY environment variable, the + config.yaml mapping, and the success/failure render banners. Use whenever + running, configuring, resuming, or reasoning about a `codeplain` render or its + config.yaml. Not for the ***plain language syntax itself (use + load-plain-reference) or for supervising a live render (use run-codeplain). +--- + +# CODEPLAIN_CLI_REFERENCE.md + +`codeplain` is the CLI that renders `***plain` specification files into production-ready code. It reads a `.plain` module (and everything it `import`s / `requires`), calls the codeplain API, and writes generated code under `plain_modules/` and conformance tests under `conformance_tests/`. The `.plain` specs are the source of truth; the generated code is a read-only artifact. + +This reference covers the CLI surface only. For the `***plain` language itself use `load-plain-reference`; to supervise a live render use `run-codeplain`; to assemble or validate `config.yaml` use `init-config-file` / `plain-healthcheck`. + +## Usage + +```text +codeplain [options] filename +``` + +```text +usage: codeplain [-h] [--verbose] [--base-folder BASE_FOLDER] + [--build-folder BUILD_FOLDER] + [--log-to-file | --no-log-to-file] + [--log-file-name LOG_FILE_NAME] [--config-name CONFIG_NAME] + [--render-range RENDER_RANGE | --render-from RENDER_FROM] + [--force-render] [--unittests-script UNITTESTS_SCRIPT] + [--conformance-tests-folder CONFORMANCE_TESTS_FOLDER] + [--conformance-tests-script CONFORMANCE_TESTS_SCRIPT] + [--prepare-environment-script PREPARE_ENVIRONMENT_SCRIPT] + [--test-script-timeout TEST_SCRIPT_TIMEOUT] [--api [API]] + [--api-key API_KEY] [--full-plain] [--dry-run] + [--replay-with REPLAY_WITH] [--template-dir TEMPLATE_DIR] + [--copy-build] [--build-dest BUILD_DEST] + [--copy-conformance-tests] + [--conformance-tests-dest CONFORMANCE_TESTS_DEST] + [--render-machine-graph] + [--logging-config-path LOGGING_CONFIG_PATH] [--headless] + filename +``` + +## Positional argument + +- **`filename`** — path to the `.plain` file to render. The directory containing this file has the **highest precedence for template loading**, so custom templates placed there override the defaults (see `--template-dir`). Render the **top module** of the dependency chain — the module not `requires`-ed by any other; its `requires`/`import` graph is pulled in automatically. + +## Path resolution (read this before setting any path flag) + +A path's meaning depends on **where it was written**: + +- Values given **on the command line** resolve against the **current working directory**. +- Values read from **`config.yaml`** resolve against the **config file's directory**. +- **Default** values resolve against the **directory containing the `.plain` file**. +- **Absolute paths** (and paths starting with `~`) are used as-is. + +## Options + +### Render control + +- **`--dry-run`** — preview code generation without making any changes. This is the static-validation gate; `plain-healthcheck` runs `codeplain .plain --dry-run` for every top module before a real render. **Per-invocation only — never store in `config.yaml`.** +- **`--full-plain`** — full preview of the assembled `***plain` specification before code generation. Use to inspect the context of all `***plain` primitives that will be included to render the given module. **Per-invocation only — never in `config.yaml`.** +- **`--render-range RENDER_RANGE`** — render a range of functionalities (e.g. `1`, or `2,3`). A comma separates the start and end IDs; the range is inclusive of both. A single ID renders only that one functionality. Mutually exclusive with `--render-from`. **Per-invocation only — never in `config.yaml`.** +- **`--render-from RENDER_FROM`** — continue generation starting from this functionality ID (inclusive). The ID must match a functionality in the `.plain` file. This is how a run resumes after a spec fix — pass the **first functionality to re-render**. Mutually exclusive with `--render-range`. **Per-invocation only — never in `config.yaml`.** +- **`--force-render`** — force a re-render of all required modules, invalidating cached module renders. Use only when a backward dependency genuinely changed; it costs more credits. +- **`--replay-with REPLAY_WITH`** — replay a previous render. **Per-invocation only — never in `config.yaml`.** + +### Configuration + +- **`--config-name CONFIG_NAME`** — name of the config file to look for. It is looked up in the `.plain` file's directory and in the current working directory. Defaults to `config.yaml`. Use it for multi-part projects (one config per part) or a non-default config file name. + +### Folders + +- **`--base-folder BASE_FOLDER`** — base folder for the build files. +- **`--build-folder BUILD_FOLDER`** — folder for build files (generated code lands under here, per module: `plain_modules//`). +- **`--template-dir TEMPLATE_DIR`** — path to a custom template directory. Templates are searched in this order: 1) the directory containing the `.plain` file, 2) this custom template directory, 3) the built-in `standard_template_library` directory. In `config.yaml` the equivalent key is `template_dir` — set it whenever the project has import modules or templates. + +### Test-script wiring + +- **`--unittests-script UNITTESTS_SCRIPT`** — shell script that runs unit tests on the generated code. Receives the build folder path as its first argument (default: `plain_modules`). `config.yaml` key: `unittests-script`. +- **`--conformance-tests-folder CONFORMANCE_TESTS_FOLDER`** — folder for conformance test files. +- **`--conformance-tests-script CONFORMANCE_TESTS_SCRIPT`** — path to the conformance-tests shell script. It must accept two arguments: 1) a folder containing generated source code (e.g. `plain_modules/module_name`), and 2) a subfolder of the conformance-tests folder containing the test files (e.g. `conformance_tests/subfoldername`). `config.yaml` key: `conformance-tests-script`. +- **`--prepare-environment-script PREPARE_ENVIRONMENT_SCRIPT`** — path to a shell script that prepares the testing environment. It must accept the source-code folder path as its first argument. It runs once per render, only to warm the environment the conformance runner attaches to. `config.yaml` key: `prepare-environment-script`. If this is declared, `conformance-tests-script` must be declared too. +- **`--test-script-timeout TEST_SCRIPT_TIMEOUT`** — timeout for test scripts, in seconds. Defaults to 120 seconds. Raise it if the project's test scripts are slow. + +### Output copying + +- **`--copy-build`** — after a successful render, copy the rendered code in `--base-folder` to `--build-dest`. +- **`--build-dest BUILD_DEST`** — target folder for `--copy-build` (used only when `--copy-build` is set). +- **`--copy-conformance-tests`** — after a successful render, copy the conformance tests in `--conformance-tests-folder` to `--conformance-tests-dest`. Requires `--conformance-tests-script`. +- **`--conformance-tests-dest CONFORMANCE_TESTS_DEST`** — target folder for `--copy-conformance-tests` (used only when it is set). + +### API + +- **`--api [API]`** — alternative base URL for the API. Default: `https://api.codeplain.ai`. +- **`--api-key API_KEY`** — API key used to access the API. If not provided, the `CODEPLAIN_API_KEY` environment variable is used. **Secret — never store it in `config.yaml`.** + +### Logging & output mode + +- **`--verbose`, `-v`** — enable verbose output. Verbose logging captures each test script's stdout/stderr into the log file between the renderer's own wrapper lines, which is what makes a live render inspectable. Keep logging verbose. +- **`--log-to-file` / `--no-log-to-file`** — enable or disable logging to a file. Defaults to `True`; pass `--no-log-to-file` to disable. +- **`--log-file-name LOG_FILE_NAME`** — name of the log file. Defaults to `codeplain.log`. If a file already exists at the resolved path, it is **overwritten** by the current run's logs — the log is therefore per-run, and its default location is the `.plain` file's directory. +- **`--logging-config-path LOGGING_CONFIG_PATH`** — path to a logging configuration file. +- **`--headless`** — run without the TUI: no terminal output except a single render-started message; all logs are written to the log file. **Required for agent-supervised runs** — a terminal tool cannot drive the interactive TUI, so supervision reads `codeplain.log` instead. +- **`--render-machine-graph`** — render the state-machine graph. Use only on explicit request. +- **`-h`, `--help`** — show the help message and exit. + +## Environment variables + +- **`CODEPLAIN_API_KEY`** — the API key used when `--api-key` is not passed on the command line. Export it before rendering (or pass `--api-key`). It is a secret and must never be written into `config.yaml`. + +## `config.yaml` relationship + +`codeplain` reads persistent options from a `config.yaml` (or the file named by `--config-name`), looked up in the `.plain` file's directory and the current working directory. This avoids retyping stable options on every run. + +- **One `config.yaml` per part of the system that has its own test scripts.** A single-stack project has one at the root; a multi-part project (e.g. a Python backend and a React frontend) has one per part, each referencing only its own scripts. +- **Established keys:** `unittests-script`, `conformance-tests-script`, `prepare-environment-script`, `template_dir`, `base-folder`, `build-folder`, plus copy/log settings. `init-config-file` is the canonical assembler and the source of truth for the full valid key set; treat it as authoritative. +- **Constraint:** if `prepare-environment-script` is declared, `conformance-tests-script` must be declared too — a prepare script only exists in service of conformance. +- **Never put in `config.yaml`:** secrets (`api-key`) and per-invocation flags (`dry-run`, `full-plain`, `render-range`, `render-from`, `replay-with`). Supply these on the command line only. +- Remember the path-resolution rule above: a script path written in `config.yaml` resolves against the config file's directory, not the working directory. + +## Render output + +Generated artifacts (gitignored, read-only — never edit): + +- `plain_modules//` — the generated project for each `.plain` spec (implementation + unit tests). +- `conformance_tests///` — generated conformance tests, one subfolder per functionality. +- `codeplain.log` — the per-run log (overwritten each run). + +When a run ends, `codeplain` writes a result banner (to the log, and to the terminal outside headless mode): + +- **Success:** `✓ rendering succeeded` (or `rendering complete`), followed by a metadata block: render id, input file, generated code folder, functionalities count, used credits, render time. +- **Failure:** `✗ rendering failed`, followed by the same metadata block. + +## Test-script exit-code contract + +The three wired scripts (`unittests-script`, `conformance-tests-script`, `prepare-environment-script`) share an exit-code contract that `codeplain`, `plain-healthcheck`, and `check-plain-env` all branch on: + +- **`69`** — unrecoverable error (missing toolchain, bad arguments, cannot enter the working folder, install failed). +- **`1`** — the "no tests discovered" guard in the conformance runner (and bad usage in the unit-test runner). +- **any other non-zero** — propagated verbatim from the underlying test command (a real test failure). + +Author or modify these scripts only through the matching `implement-{unit-testing,conformance-testing,prepare-environment}-script` skill — never by hand. + +## Common invocation patterns + +```bash +# Static-validation gate before a real render (what plain-healthcheck runs) +codeplain .plain --dry-run + +# Full render of the top module +codeplain .plain + +# Multi-part project: render one part with its own config file +codeplain backend/api.plain --config-name config.yaml + +# Agent-supervised render: background + headless, watch codeplain.log +nohup codeplain .plain --headless > /dev/null 2>&1 & + +# Resume after a spec fix: re-render from functionality N (inclusive) +codeplain .plain --render-from + +# Re-render exactly one functionality +codeplain .plain --render-range + +# Force a full re-render (backward dependency changed) — costs more credits +codeplain .plain --force-render +``` + +## Common errors and signals + +- **`codeplain: command not found`** — the CLI is not installed or not on `PATH`. Confirm with `command -v codeplain`. +- **`401` / `403` / `unauthorized`** — `CODEPLAIN_API_KEY` is unset or invalid; export it or pass `--api-key`. This is an API/auth problem, not a spec problem. +- **`429` / `rate limit` / `quota`** — API throttling or credit limit; not a spec problem. +- **`Functional spec too complex!`** — a single functional spec implies more than 200 lines of code; break it down (`break-down-func-spec`). +- **`missing concept` / `unknown definition` / `cyclic` / `not defined` / `cannot resolve`** — a spec-graph error (a concept used before it is defined, a cycle, or a broken `import`/`requires` chain). A `--dry-run` catches these before spending credits. +- **`Traceback (most recent call last):`** — the renderer itself crashed (a bug), not a spec error; capture the stack for a bug report. + +## See also + +- `load-plain-reference` — the `***plain` language syntax and authoring rules. +- `run-codeplain` — supervising a live render (monitor loop, pathologies, resume). +- `plain-healthcheck` — the pre-render dry-run gate across every top module. +- `init-config-file` — canonical `config.yaml` assembly and the full valid key set. +- `implement-{unit-testing,conformance-testing,prepare-environment}-script` — authoring the wired test scripts. diff --git a/forge/skills/load-plain-reference/SKILL.md b/forge/skills/load-plain-reference/SKILL.md index fe6ebfc..88659fb 100644 --- a/forge/skills/load-plain-reference/SKILL.md +++ b/forge/skills/load-plain-reference/SKILL.md @@ -1,646 +1,71 @@ --- name: load-plain-reference description: >- - Loads the full ***plain language reference into context (PLAIN_REFERENCE.md): syntax, section types - (definitions, implementation reqs, test reqs, functional specs, acceptance tests), - concept notation, frontmatter (import/requires/required_concepts/exported_concepts), - templates, linked resources, module model, and authoring best practices. Use whenever - authoring, editing, reviewing, or debugging .plain files, or before invoking any other - skill that reads or writes .plain content. + Loads the applicable ***plain authoring rules and operational references for the current task. + Covers section ownership, concepts, modules, resources, integrations, rendering, testing, and + codeplain CLI behavior without loading unrelated guidance. Use when authoring, editing, reviewing, + or debugging .plain files, or before invoking another skill that reads or writes .plain content. --- -# PLAIN_REFERENCE.md +# Load the ***plain reference -## Project Overview +Load only the rules and references required for the current task. Files under `../../rules/` are +the source of truth for `.plain` syntax and authoring constraints. Do not restate or override them. -\*\*\*plain is a specification-driven language powered by AI that generates production-ready code from `.plain` spec files. +## 1. Account for rules already loaded -The `.plain` files are the source of truth. They describe what the software should do, how it should be built, and how it should be tested. The generated code is a read-only artifact produced by the renderer. +- In Claude Code, applicable `.claude/rules/*.md` files are loaded natively. Do not read those rule + files again. +- In another agent, do not reread a rule whose full content is already present in the current + context. +- Otherwise, read the applicable files from the routing table below before inspecting or changing + `.plain` content. -## ***plain Language Reference +## 2. Load applicable authoring rules -***plain is a specification language designed for writing software requirements in a clear, structured format. It generates production-ready code from `.plain` spec files using AI. Full documentation: https://plainlang.org/docs/language-guide/ +All paths are relative to this `SKILL.md`. -### .plain File Structure +| Task or content | Rule files | +|---|---| +| Definitions or concept usage | `../../rules/definitions.md` | +| Functional specs or acceptance tests | `../../rules/func-specs.md` | +| Implementation requirements or unit tests | `../../rules/impl-reqs.md` | +| Test requirements or conformance tests | `../../rules/test-reqs.md` | +| `import` modules | `../../rules/import-modules.md` | +| `requires` modules | `../../rules/requires-modules.md` | +| `required_concepts` | `../../rules/required-concepts.md` | +| `exported_concepts` | `../../rules/exported-concepts.md` | +| Linked files or `resources/` | `../../rules/linked-resources.md` | +| Any `.plain` text or example | `../../rules/bullet-continuation.md` | -A `.plain` file has a YAML frontmatter section followed by standardized sections marked with `***section name***` headers. There are four types of specification sections: +For a whole-file review or a task spanning several sections, load the union of the applicable rule +files. Do not load every rule by default. -- `***definitions***` — declares concepts used throughout the specification -- `***implementation reqs***` — non-functional requirements about how the software should be built -- `***test reqs***` — requirements for conformance testing -- `***functional specs***` — describes what the software should do +## 3. Load integration rules only for integration work -Every plain source file requires at least one functional spec and an associated implementation req. +For an integration spec, always read: -### Concept Notation +- `../../rules/integrations.md` -Concepts are the building blocks of ***plain specifications. They are written between colons: `:ConceptName:`. Valid characters include letters, digits, plus, minus, dot, and underscore. +Then read the rule matching the integration shape: -Concepts must be defined in `***definitions***` before being referenced in other sections. Concept names must be globally unique across the specification and its imports. Concept references must not form cycles — if concept A references concept B, then concept B must not reference concept A. +- Embedded integration: `../../rules/integration-embedded.md` +- Standalone integration: `../../rules/integration-standalone.md` +- Embedded integration test scripts: `../../rules/integration-embedded-testing.md` -Example: +Do not load integration rules for ordinary non-integration specs. -```plain -***definitions*** +## 4. Load operational references only when needed -- :User: is the user of :App: +Authoring rules remain authoritative if a reference appears to conflict with them. -- :Task: describes an activity that needs to be done by :User:. :Task: has: - - Name - a short description (required) - - Notes - additional details (optional) - - Due Date - completion deadline (optional) +| Need | Reference | +|---|---| +| Project layout, source-of-truth model, templates, or comments | `references/project-model.md` | +| Rendering order, generated artifacts, conformance workflow, or test scripts | `references/rendering-and-testing.md` | +| `codeplain` path resolution or CLI options | Invoke `load-codeplain-reference` | -- :TaskList: is a list of :Task: items. - - Initially :TaskList: should be empty. -``` +## 5. Continue with the task -### Predefined Concepts - -***plain provides predefined concepts available in all specifications without needing to be defined: - -| Concept | Meaning | -|---------|---------| -| `:plainDefinitions:` | Content of the `***definitions***` section | -| `:plainImplementationReqs:` | Content of the `***implementation reqs***` section | -| `:plainFunctionality:` | Content of the `***functional specs***` section | -| `:plainTestReqs:` | Content of the `***test reqs***` section | -| `:Implementation:` | The system implementing `:plainFunctionality:` | -| `:plainImplementationCode:` | The generated implementation code | -| `:UnitTests:` | Auto-generated unit tests for individual functionalities - their usage goes in in the ***implementation reqs*** section | -| `:ConformanceTests:` | Auto-generated tests that verify implementation conforms to specs | -| `:AcceptanceTest:` / `:AcceptanceTests:` | Tests that validate specific aspects of the implementation | - -### Definitions Section - -Declares concepts used throughout the specification. A concept must be defined before it can be referenced in any section. The definition can come from the module's own `***definitions***` section, from an `import`ed module's definitions, or from a `require`d module's `exported_concepts` (but not transitively). Attributes, constraints and clarifications can be nested as sub-bullets. - -```plain -***definitions*** - -- :ConceptName: is a description of the concept. - - Additional details or attributes can be nested - - Multiple attributes can be listed - - Concept clarification also goes here -``` - -### Implementation Reqs Section - -A free-form section for any instructions that steer code generation. Common uses include technology choices, architectural constraints, coding standards, and naming conventions, but it can also contain detailed implementation guidance — data formats, error handling strategies, algorithm descriptions, or any other context the renderer needs to produce correct code. These describe HOW to build the software, not WHAT it should do. Specs about unit testing also go here - it is a common mistake to include them in the `***test reqs***` section. - -```plain -***implementation reqs*** - -- :Implementation: should be in Python. - -- :MainExecutableFile: of :App: should be called "hello_world.py". - -- :Implementation: should include :Unittests: using Unittest framework! -``` - -### Test Reqs Section - -Specifies requirements for conformance testing — test frameworks, execution methods, and testing constraints. Only used when writing and fixing conformance tests (not unit tests). Unit tests should be specified in the `***implementation reqs***` section and NOT HERE. - -```plain -***test reqs*** - -- :ConformanceTests: of :App: should be implemented in Python using Unittest framework. - -- :ConformanceTests: will be run using "python -m unittest discover" command. - -- :ConformanceTests: must be implemented and executed - do not use unittest.skip(). -``` - -### Functional Specs Section - -Describes what the software should do. Each bullet point is a single piece of functionality that will be implemented. Functional specs are rendered incrementally one by one — earlier specs cannot reference later specs. - -Each functional spec must be limited in complexity. If a spec is too complex, the renderer responds with "Functional spec too complex!" and it must be broken down into smaller specs. Complexity is measured in lines of code - each spec should imply more than 200 lines of code. - -Functional specs are in **chronological order** — earlier specs are rendered before later ones. Functional specs defined in `requires` modules are considered **previous functional specs** relative to the current module's specs. This ordering matters for incremental rendering and for detecting conflicts between specs. - -The renderer has **no knowledge of future functional specs**. When a functional spec is being implemented, only the previous functional specs (those already rendered) are in the renderer's context. Specs that come later in the list are invisible to the renderer at that point. This means each spec is implemented without any awareness of what will come next. - -```plain -***functional specs*** - -- Implement the entry point for :App:. - -- Show :TaskList:. - -- :User: should be able to add :Task:. Only valid :Task: items can be added. - -- :User: should be able to delete :Task:. - -``` - -Each functional spec must be unambiguous. If a single line is not enough to fully disambiguate the behavior, use nested sub-bullets to add detail. Nested lines clarify the parent spec — they do not introduce separate functionality. Even with nested detail, the spec must still respect the complexity limit. - -```plain -***functional specs*** - -- :User: should be able to send a :Message: to a :Conversation:. - - A :Message: must have non-empty content. - - The :Message: is appended to the end of the :Conversation:. - - All :Participant: members of the :Conversation: can see the new :Message:. -``` - -### Acceptance Tests - -Nested under individual functional specs to specify how to verify correct implementation. They extend conformance tests and are implemented according to the `***test reqs***` specification. Acceptance tests are only run if conformance tests are enabled. - -```plain -***functional specs*** - -- Display "hello, world" - - ***acceptance tests*** - - :App: should exit with status code 0 indicating successful execution. - - :App: should complete execution in under 1 second. -``` - -### YAML Frontmatter - -The frontmatter is enclosed between `---` markers and supports: - -- **`import`** — includes definitions, implementation reqs, and test reqs from templates. Imported modules must not contain functional specs. The default import directory is `template/` — the `template/` prefix is not needed (e.g., `airplain` resolves to `template/airplain.plain`). -- **`requires`** — specifies dependencies on other root-level modules that must be built first. Unlike `import`, required modules can contain functional specs and represent complete software modules. Requires paths point to root-level modules (e.g., `auth`, `messaging`). -- **`description`** — optional description of the specification. -- **`required_concepts`** — concepts that must be defined by any module that imports this spec. -- **`exported_concepts`** — concepts made available to modules that `require` this one. **Exports are not transitive.** A concept exported from module `A` is visible only to the modules that `requires: A` directly. If module `B` `requires: A` and module `C` `requires: B`, the concepts `A` exports are **not** automatically visible to `C` — only the concepts `B` itself re-exports are. To pass a concept further down the chain, the intermediate module must re-declare it in its own `exported_concepts` list (and define / forward it in its own `***definitions***` as needed). This applies at every hop: each module is responsible for explicitly exporting whatever it wants its own `requires`-ers to see. - - **When a concept needs to live in more than just the immediately next module, don't propagate it by chained re-exports** — that turns every intermediate module into bookkeeping for a concept it doesn't itself use, and any missing hop silently drops the concept from downstream modules. Use a **shared import module** instead: - - 1. Create an import module under `template/` (e.g. `template/shared_domain.plain`) and put the concept's `***definitions***` entry there. Import modules carry definitions, implementation reqs, and test reqs only — never functional specs. - 2. In every module that needs the concept (no matter how deep in the `requires` chain), add the import module to its frontmatter `import:` list. The concept is then visible in that module directly, without any `exported_concepts` plumbing. - 3. None of the `requires`-chained modules need to re-export the concept anymore — each one imports what it actually uses. - - Use the `create-import-module` skill to scaffold this, and `consolidate-concepts` when you discover the same concept has been scattered across several modules and needs to be pulled back into a single shared import. - - Rule of thumb: if a concept crosses **one** hop, `exported_concepts` is fine. If it crosses **two or more** hops, or is needed by sibling modules at the same depth, lift it into an import module. - -### Linked Resources - -Specifications can reference external files using markdown link syntax. The linked resource is passed along with the spec to the renderer. File paths are resolved relative to the `.plain` file location. Only files in the same folder (and subfolders) are supported. - -```plain -- :User: should be able to add :Task:. - - The user-interface details are in [task_modal_specification.yaml](task_modal_specification.yaml). -``` - -#### Hard constraint: a linked resource is always a single, text-based file on disk - -The renderer reads the linked file's bytes verbatim and feeds them into the model alongside the spec. That mechanism only works for a specific shape of target, and violating any of the three rules below is one of the most common and disruptive mistakes in `.plain` authoring — the spec **looks** valid, but the renderer either silently ignores the link, fails to read it, or wastes the model's context window on bytes it cannot interpret. - -A linked resource **must not** be any of the following: - -1. **A folder / directory.** `[integrations](src/integrations/)`, `[schemas](resources/schemas/)`, `[host project](../host_project/)` are all invalid — the renderer cannot ingest a directory. If a whole directory's worth of content is relevant, pick the single most representative **file** inside it (a `README.md`, an exemplar source file, a manifest at the directory root) and link **that**. -2. **A URL / external location.** `[Stripe docs](https://stripe.com/docs/api)`, any `http://` / `https://` / `ftp://` / `git://` / `s3://` / `gs://` target. Linked resources are local-file only. If a URL's content is essential to the spec, fetch it once, save the response to a text file under `resources/` (e.g. `resources/stripe-docs-snapshot.md`, `resources/example-openapi.yaml`), and link **that file**. -3. **A binary file.** PNG, JPG, JPEG, GIF, BMP, TIFF, WebP, ICO, PDF, DOCX, XLSX, PPTX, ZIP, TAR, GZ, MP3, MP4, WAV, compiled binaries (`.exe`, `.so`, `.dylib`, `.class`, `.wasm`), and anything else that isn't human-readable text in its raw form. Binary content cannot be meaningfully consumed by the renderer; linking a screenshot, a PDF spec, or a packaged artifact accomplishes nothing except bloating the context. If the information in a binary asset is essential, transcribe it into a text-based form first — a UI screenshot becomes a Markdown description or a structured YAML wireframe under `resources/`; a PDF spec becomes a Markdown extract or the underlying JSON Schema / OpenAPI; an architecture diagram becomes a Mermaid block inside a Markdown file. - -If the markdown-link target ends with `/`, contains `://`, points at a path that resolves to a directory, or points at a file with one of the binary extensions above, **stop** — it cannot be a linked resource. Convert it to a text file under `resources/` first, then link the converted file. - -#### URLs and folder paths must not appear *anywhere* in `.plain` content - -The constraint above is **not** just about markdown link syntax. URLs (any `http://`, `https://`, `ftp://`, `git://`, `s3://`, … string) and folder paths (`src/integrations/`, `../host_project/`, anything ending with `/`, anything that resolves to a directory) **must not appear anywhere in `.plain` content** — not as link targets, not in concept body prose, not in functional-spec text, not in implementation reqs, not in test reqs. Mentioning a URL or a folder in prose is a critical and common mistake because: - -- **The renderer cannot follow URLs or open folders.** A URL or folder reference in prose is a *ghost* dependency: it looks meaningful to a human reader, but it contributes nothing to code generation. Worse, downstream readers (and future you) assume the renderer used the referenced content, so the spec silently drifts from reality. -- **The fix is always the same**: if external content matters, fetch it (or pick one canonical file out of the directory), save it as a text file under `resources/`, and refer to it through a normal linked resource. The concept or spec then names the content through the linked file, not through a URL or folder path string. - -The **only** exception is for URLs and paths that are *values the produced software itself uses at runtime* — the base URL the integration calls, a database connection path, a CLI argument default. Those are configuration values, not external references, and they belong in the spec because the generated code needs them. A useful litmus test: "Would the renderer benefit from reading the bytes at this URL / folder?" If yes, save it to a text file and link the file. If no (it's a runtime value the generated code carries forward), it can stay as plain text in the spec. - -**Structured protocol artifacts must be linked resources, never transcribed into prose.** Anything that has a formal machine-readable shape which includes but is not limited to — JSON Schema, OpenAPI / Swagger specs, GraphQL SDL, Protobuf / gRPC `.proto` files, Avro / Thrift schemas, XML XSDs, AsyncAPI specs, JSON-RPC method definitions, wire-protocol descriptions, payload examples, etc. — belongs in a file under `resources/` (or a subfolder of the `.plain` file's directory), and the spec refers to it via a markdown link. Do **not** restate the schema's fields, types, or constraints inline in functional specs, implementation reqs, or definitions. Reasons: - -- **One source of truth.** A re-typed copy of a schema in prose drifts as soon as the real schema evolves. Both the renderer *and* downstream tooling (codegen, validators, API clients, IDE plugins) need the same canonical file. -- **Machine-readable.** The renderer and the generated code can both consume the file directly — a JSON Schema linked from a spec can drive request/response validation in the implementation *and* assertions in conformance tests, with no prose-to-code translation step in between. -- **Reviewable as a diff.** Schema changes show up cleanly in PRs as edits to a single file, instead of as a scatter of edits across multiple `.plain` sections. - -The accompanying spec line should describe the *role* of the artifact ("the request body conforms to ...", "the public API surface is defined in ...") rather than its contents. If the artifact is referenced from more than one place, follow the [single-reference + concept](#linked-resources) rule below. - -```plain -***definitions*** - -- :TaskCreateRequest: is the JSON payload for creating a task. - - It is defined by [resources/task_create_request.schema.json](resources/task_create_request.schema.json). -- :TasksAPI: is the public HTTP surface for tasks. - - It is defined by [resources/tasks_openapi.yaml](resources/tasks_openapi.yaml). - -***functional specs*** - -- :User: should be able to add :Task: by POSTing :TaskCreateRequest: to the `POST /tasks` endpoint of :TasksAPI:. - - The endpoint responds per :TasksAPI:. -``` - -**Each linked resource must be referenced from exactly one place** in the project — a single functional spec, implementation requirement, or `***definitions***` entry. Linking the same file from two functional specs (or from a functional spec *and* a requirement, etc.) creates two independent sources of truth: any later edit to one site silently diverges from the other, and the renderer has no way to know which one is canonical. - -If a resource needs to inform multiple parts of the project, **don't repeat the link** — instead, attach the resource to a **concept** and let the rest of the project refer to that concept: - -1. Define a concept under `***definitions***` whose explanation links the resource exactly once. -2. Use the concept token (`:ConceptName:`) wherever the resource was previously inlined. - -For example, instead of linking `task_modal_specification.yaml` from two different functional specs: - -```plain -***definitions*** - -- :TaskModalSpec: is the user-interface contract for the task modal. - - It is fully described in [task_modal_specification.yaml](task_modal_specification.yaml). - -***functional specs*** - -- :User: should be able to add :Task: using :TaskModalSpec:. - -- :User: should be able to edit :Task: using :TaskModalSpec:. -``` - -This keeps the resource link in one place, makes the dependency explicit through the concept token, and means a change to the file only ever needs to be reconciled against one spec site. If you find yourself about to paste the same `[name](path)` link a second time, **stop** — create the concept first. - -### Template System - -***plain supports template inclusion using `{% include %}` syntax: - -```plain -{% include "python-console-app-template.plain", main_executable_file_name: "my_app.py" %} -``` - -Parameters are passed as key-value pairs. Inside the template, they are accessed using variable syntax (`{{ variable_name }}`). Only variables are supported — conditionals, loops, and other Liquid features are not available. - -### Comments - -Lines starting with `>` are ignored when rendering: - -```plain -> This is a comment in ***plain -``` - -### Best Practices - -1. **Reference concepts consistently** — use `:ConceptName:` notation to disambiguate key concepts -2. **Keep it simple** — specs should be readable by both humans and AI -3. **Leverage templates** — use the standard template library for common patterns -4. **Use acceptance tests** — add them for requirements that need verification (under the condition that conformance tests are enabled) -5. **Be specific** — write clear, testable requirements in functional specs -6. **Define before use** — always define concepts in `***definitions***` before referencing them -7. **Start with imports** — import relevant templates before defining your own concepts - -## Repository Structure - -``` -*.plain # Specification files (the source of truth) -template/*.plain # Reusable template specs imported by module specs -plain_modules/ # Generated code output (one folder per .plain spec) -resources/ # Schemas, API specs, transforms, test fixtures -conformance_tests/ # Generated conformance tests (one folder per module) -test_scripts/ # Scripts for running unit and conformance tests -config.yaml # codeplain CLI configuration -``` - -**Generated artifacts** (gitignored): -- `plain_modules//` — generated project for each `.plain` spec (implementation + unit tests) -- `conformance_tests//` — generated conformance tests for each module - -## How Modules Work - -There are two types of modules: - -### Import Modules - -An import module may live in the **`template/`** directory (other directories are also supported) and contains **only** `***definitions***`, `***implementation reqs***`, and/or optionally `***test reqs***`. It must **not** contain `***functional specs***` and must **not** use `requires`. It may optionally `import` other templates for layered reuse. - -When a module **`import`s** another, it gains access to the imported module's definitions, implementation reqs, and test reqs — but not its functional specs. The default import directory needs to be specified in `config.yaml` - in such a case, the directory prefix is not needed (e.g., `airplain`). - -### Requires Modules - -`requires` establishes a build ordering between modules. The required module is built **before** the current one. This does not necessarily mean the current module extends or depends on the required module's code — it may be completely independent. The `requires` relationship ensures the build order is correct. - -When a module **`requires`** another: -- The required module's generated code (`plain_modules/`) is copied as the starting point. -- The required module's `***functional specs***` become visible as **previous functional specs** - this property IS transitive. -- Only `exported_concepts` from the required module are available (not its full definitions) - this property IS NOT transitive. - -A module can use both `requires` and `import` together. - -**`requires` modules must share the same tech stack.** Because the required module's generated code is copied as the starting point and the renderer continues building on top of it with one language/framework toolchain, two modules can only be linked with `requires` when they target the same language, framework, and runtime. A runtime/network dependency between systems is **not** a reason to use `requires`. For example, a React frontend that talks to a Python/FastAPI backend over HTTP must **not** `requires: [backend]` — the stacks differ. Model that pair as two independent root modules (each with its own `config.yaml` and test scripts), and express the contract between them through a shared API schema in `resources/` or shared concepts in an `import`ed template, NOT through `requires`. - -### Contracts Between Modules - -Modules can use `required_concepts` and `exported_concepts` to enforce contracts between them. An import module declaring `required_concepts` means any module that imports it must define those concepts. A module declaring `exported_concepts` controls which concepts are visible to modules that `require` it - not transitive. - -**Exported concepts are not transitive.** If module A exports a concept and module B `requires` A, module B can use that concept — but if module C `requires` B, it does **not** automatically gain access to A's exported concepts. If a concept needs to be shared across multiple `requires` modules, define it in a common import module and have each module `import` that shared template. - -## Conformance Test Workflow - -Each functional spec in a module has its own set of conformance tests, generated per functional spec per module. After a new functional spec is rendered (i.e., its implementation code is generated), conformance tests for that spec are also rendered. Before proceeding, **all previous conformance tests** (from earlier functional specs in the same module) are run. Ideally, all conformance tests of all previous functional specs pass without any changes. If any previously passing conformance test now fails, the failure must be resolved before moving on. Resolution means one of three things: fixing the conformance test, fixing the implementation code (by adjusting the spec), or identifying conflicting specs. - -If conformance tests of a previous functional spec need to be changed in order to pass, this is a strong indicator that the functional specs themselves may need to be amended. Needing to modify earlier conformance tests suggests the new functional spec has introduced behavior that is inconsistent with what was previously specified — the specs should be reviewed and clarified to eliminate the ambiguity or conflict. - -## Running Tests - -Test scripts live in `test_scripts/` and are run from the repo root: - -```bash -# Run all unit tests for a module -./test_scripts/run_unittests.sh plain_modules/ - -# Prepare environment for conformance tests -./test_scripts/prepare_environment.sh plain_modules/ - -# Run conformance tests for a specific functionality in a module -./test_scripts/run_conformance_tests.sh plain_modules/ conformance_tests// -``` - -## Testing Scripts - -Every ***plain project ships shell scripts under `test_scripts/` that the user (and the renderer) call into to verify the generated code. There are three kinds, each authored by a dedicated skill — use the corresponding skill as the source of truth whenever you create, regenerate, or adapt a script. - -### Why these scripts exist (and why they're shaped the way they are) - -The **primary** purpose of these scripts is **automated execution by the renderer** to validate the generated code and validate that all the previous functionalities work as expected, not manual invocation by a developer. The user *can* run them by hand (see [Running Tests](#running-tests)), but the renderer runs them many times more often — once for every functional spec it processes — as part of its incremental rendering loop. The contract between the scripts and the renderer is shaped by that execution model: - -- **Conformance tests are per-functional-spec.** Each functional spec in a module has its own folder under `conformance_tests///`. After the renderer finishes generating code for a new functional spec and the unit tests and refactoring passes, it runs the conformance tests of **all previous functional spec** to detect regressions — see [Conformance Test Workflow](#conformance-test-workflow). For a single module (with 0 `requires` modules) with N functional specs, the conformance script gets invoked **on the order of N times per render**, each invocation pointing at a different spec's test folder. -- **Each per-spec invocation is independent.** The conformance script does not know that it's the second invocation in a long sequence; from its point of view, each invocation is a cold start against a single spec's tests. -- **Per-spec independence is also what makes dependency installation expensive.** A naive conformance runner would re-install all of the project's runtime dependencies (Python venv + `pip install`, Maven dependency tree, `npm ci`, `cargo build`, ...) on every one of those N invocations. That's `N × install-cost` of wasted work for every render. -- **That is exactly why `prepare_environment_` exists.** Its **only** job is to amortize the install cost: install once at the start of a render, populate `.tmp/_/` with the warmed dependency cache and build artifacts, then let the conformance runner **attach** to that working folder on each of the N per-spec invocations instead of re-installing. The conformance runner's [activate-only variant](../implement-conformance-testing-script/SKILL.md#variant-decision-install-inline-vs-activate-only) does precisely that. When no prepare script exists, the conformance runner falls back to the install-inline variant and pays the install cost N times — acceptable for tiny projects, costly for anything realistic. -- **The unit-test runner has a different execution model, because unit tests live in a different place.** Unit tests are part of the generated codebase itself — they sit directly inside `plain_modules//` next to the implementation they exercise — whereas conformance tests live *outside* the codebase, in their own per-spec folders under `conformance_tests///`. As a result, the unit-test runner doesn't have a per-spec axis to iterate over: it just runs against the whole `plain_modules//` build in one go, gets invoked far fewer times per render, and has no amortization gain to chase. That's why the unit-test runner is always self-contained and there is no `prepare_environment`-equivalent for it. - -Keep this framing in mind when you author or adapt any of these scripts. The decisions about working-folder paths, isolation locations, exit codes, and the activate-only-vs-install-inline split are not arbitrary house style — they are what makes the renderer's per-spec loop tractable. - -### The three scripts - -- **`run_unittests_.sh` / `.ps1`** — runs the auto-generated unit tests in `plain_modules//`. Authored by the [`implement-unit-testing-script`](../implement-unit-testing-script/SKILL.md) skill. Receives one positional argument: the source build folder name. Invoked roughly once per render. **Fully self-contained:** it installs its own dependencies inline (via `pip install -r requirements.txt`, `npm ci`, `mvn`, `cargo fetch`, etc.) and never relies on any other script having run first. -- **`run_conformance_tests_.sh` / `.ps1`** — runs the conformance tests in `conformance_tests///` against the generated implementation. Authored by the [`implement-conformance-testing-script`](../implement-conformance-testing-script/SKILL.md) skill. Receives two positional arguments: the source build folder and the conformance tests folder. **Invoked once per previous functional spec on every render** — i.e. roughly N times for a module with N functional specs. -- **`prepare_environment_.sh` / `.ps1`** — *optional* one-time setup that runs **before** the conformance script and **only the conformance script**. Invoked **once per render** to install the build's dependencies and pre-warm build artifacts into `.tmp/_/` so the N subsequent conformance invocations can attach to the warmed environment instead of re-installing. Authored by the [`implement-prepare-environment-script`](../implement-prepare-environment-script/SKILL.md) skill. Receives one positional argument: the source build folder name. **It does not feed the unit-test script** — see [`prepare_environment` is conformance-only](#prepare_environment-is-conformance-only-common-mistake) below. - -### `prepare_environment` is conformance-only (common mistake) - -It is a **common and costly mistake** to assume that `prepare_environment_` is a generic "warm up the environment for all the testing scripts" step that the unit-test runner can also lean on. It is not. The hard rule: - -> `prepare_environment_` exists **solely** to set up the working folder that `run_conformance_tests_` then attaches to (the activate-only variant). The unit-test runner (`run_unittests_`) is **completely independent and complete** of it — it does not read from `prepare`'s working folder, does not require `prepare` to have run, and must install whatever dependencies it needs on its own. - -Why: - -- **Unit tests run against `plain_modules//`, conformance tests run against `.tmp/_/`.** The two scripts stage into different places. `prepare_environment` populates `.tmp/_/` for conformance; the unit-test script does its own staging into its **own** `.tmp/_/` working folder and installs its own dependencies there. -- **The unit-test runner must work in isolation.** Users and CI systems run unit tests as a quick smoke check without ever invoking conformance. If `run_unittests_` depended on `prepare_environment` having run, those one-off unit-test invocations would silently fail (or be "fixed" by a misguided edit to make it depend on `prepare`). -- **The skill contract enforces it.** [`implement-unit-testing-script`](../implement-unit-testing-script/SKILL.md) emits a fully self-contained script every time: toolchain check → stage → install dependencies inline → run tests. It never emits an activate-only variant. The two-variant pattern is exclusive to the conformance runner. - -If you find yourself authoring (or asked to author) a `prepare_environment` script that handles unit-test dependencies too, **stop**. The unit-test script handles its own dependencies. Adding a unit-test path into `prepare_environment` couples scripts that should stay independent, and breaks the activate-only contract between `prepare` and `conformance`. - -### Shared rules across all three scripts - -Anything not listed here is documented in the individual skill file: - -- **Input folders are read-only — hard rule.** The build folder (and, for conformance, the conformance tests folder too) is **input only**. Every install, build artifact, cache, log, JUnit XML, coverage report, compiled test class, and temp file must land inside `.tmp/_`, never inside the input folder. The build folder is shared with the renderer and with the user's version control; writing into it corrupts both. If you find yourself about to issue a command whose `cwd` is an input folder, or whose target path starts with the input folder, **stop** — the write has to go into `.tmp/_`. -- **Shell flavor matches the host.** `.sh` on macOS / Linux, `.ps1` on Windows. A project intended for both OSes ships both files in matching pairs (`prepare` + `conformance` for each language must agree on working-folder name and isolation paths). -- **Exit codes are part of the contract.** `69` for unrecoverable errors (missing toolchain, bad args, can't enter the working folder, install failed); `1` for the "no tests discovered" guard in the conformance runner (and bad usage in the unit-test runner); any other non-zero code is propagated from the underlying test command. Other skills — notably [`plain-healthcheck`](../plain-healthcheck/SKILL.md) and [`check-plain-env`](../check-plain-env/SKILL.md) — branch on these codes. -- **Wired in via `config.yaml`.** Each script that is actually generated must be referenced from the relevant `config.yaml` via the `unittests-script:`, `conformance-tests-script:`, and `prepare-environment-script:` keys respectively. See the [`init-config-file`](../init-config-file/SKILL.md) skill for the canonical assembly. **If `prepare-environment-script` is declared, `conformance-tests-script` must be declared too** — a prepare script only makes sense in service of conformance, and `plain-healthcheck` will hard-fail a project that violates this. -- **Conformance scripts come in two variants — unit-test scripts do not.** When a `prepare_environment_` script exists, the conformance script is the **activate-only** variant (it attaches to the env prepare populated in `.tmp/`). When no prepare exists, the conformance script is the **install-inline** variant (it stages and installs in one shot). The `implement-conformance-testing-script` skill picks the right variant automatically based on whether a prepare script is already on disk. **The unit-test script has no activate-only variant** — it is always self-contained, regardless of whether a `prepare_environment_` script exists. -- **Dependency isolation is project-local.** Each language's package cache / virtual env / build repo lives inside the working folder (`./.venv` for Python, `./node_modules` for Node, `./.m2` for Java, `./.gocache` for Go, `./.cargo` for Rust, `./.pub-cache` for Flutter, ...) — never in the user's home directory. The conformance script reads from the same project-local location prepare wrote to; the unit-test script uses its **own** working folder and its **own** copy of the isolated dependencies. -- **No language-package checks live in these scripts.** The scripts themselves install language packages via `pip install -r requirements.txt`, `npm ci`, `mvn -Dmaven.repo.local=...`, `go mod download`, `cargo fetch`, etc. They do **not** pre-verify individual packages; that's the package manager's job. The host-level checks for the toolchains and external dependencies belong in `check-plain-env`, not in these scripts. -- **Scripts are verbose**. They print out every step they take, including toolchain checks, dependency installations, and test results. This makes it easier to debug and understand what's going on. - -For implementation details — the exact step sequence, toolchain checks, language-specific install / test commands, working-folder lifecycle, anti-patterns — open the corresponding `implement-*-testing-script` skill. Do not hand-author a testing script from scratch; route every creation or modification through the matching skill so the shared rules above are enforced uniformly. - -## Writing Functional Specs - -- Each functional spec must imply a **maximum of 200 changed lines of code**. This is a hard limit — if a spec would result in more than 200 lines of changes, it must be broken down into smaller, independent specs. This limit also helps avoid "Functional spec too complex!" errors from the renderer. -- **Conflicting specs must be avoided at all costs.** Functional specs should be written so that no conflicts exist between them. If two specs appear to conflict, they must be clarified by adding more detail and context to the specs until all possible conflicts are resolved. Prevention is always preferable to debugging conflicts after rendering. -- **Specs should be language-agnostic.** Avoid using programming language-specific terminology (e.g., generics syntax, framework annotations, language-specific collection types, decorator syntax, language-specific base classes or type keywords like "POJO" or "dataclass") in functional specs and definitions. Write specs in terms of behavior, concepts, and domain logic — not implementation constructs. General technical terms that are not language-specific are fine (e.g., null values, JSON types, HTTP status codes, REST api endpoints etc.). The `***implementation reqs***` section is the appropriate place for language-specific guidance. - - Naming concrete *components* — classes, methods, functions, fields — is encouraged and not in conflict with this rule. A functional spec should freely refer to concrete domain components, services, or entities (e.g., `:PaymentProcessor:`, `:UserRepository:`, `:DataConverter:`) and their operations (e.g., `:ChargeCard:`, `:FindById:`), pinning down their inputs, outputs, and error behaviors, and treat those names as part of the public contract. What it must *not* do is bake in how that contract is realized in a particular language: no `@staticmethod` decorators, no `class Foo extends Bar` phrasing, no `List` or `Optional` syntax, no "POJO with static methods" framing. - - The litmus test: if you switched the project from Python to Java (or vice versa), would the functional spec still read correctly with only `***implementation reqs***` updated? If yes, the spec is language-agnostic. If the functional spec itself would need rewording because it referenced a language-specific construct, the construct belongs in implementation reqs instead. The component name (`:DataConverter:`) is the same across languages; the syntax used to express "static method on a class" is not. -- **Keep sentences short and clear — but never at the cost of ambiguity.** Spec lines should be easy to read and understand at a glance. Prefer short, direct sentences and plain words over long sentences and jargon — if a 10-cent word and a 50-cent word say the same thing, use the 10-cent one. This applies to every spec section, not only functional specs: `***definitions***`, `***implementation reqs***`, `***test reqs***`, and `***acceptance tests***` should all be as concise as they can be while staying unambiguous. The hard constraint is in the second half of that rule: **wordy-but-precise always beats terse-but-ambiguous.** If trimming a clause, a qualifier, or a sub-bullet would leave the spec open to more than one reasonable interpretation, leave it in. When a sentence starts to grow because the behavior is genuinely complex, split it into two short sentences (or into a parent line + sub-bullets) rather than dropping detail. Concision is in service of clarity, never the other way around. -- **Specs must be deterministic enough to both *run* and *use* the software without reading the generated code.** A developer should be able to figure out, from the specs alone, two distinct things: - - 1. **How to run the built software** — the entry-point command (e.g. `python -m app`, `uvicorn app.main:app`, `./my-cli`), prerequisites (required runtime versions, package managers, system binaries), required environment variables, ports the software listens on, configuration file paths and shapes, and any default arguments. - 2. **How to use the running software** — the full interaction surface. For a REST API: every endpoint path, HTTP method, request body shape, response body shape, status codes, and authentication scheme. For a CLI tool: every command, its arguments and flags, the expected output (including exit codes), and the input it reads (stdin, files, env vars). For a library: every public function/class, its signature, the inputs it accepts, the outputs it returns, and the errors it can raise. - - Concretely, a reader should never have to open `plain_modules/` to answer "how do I start this?" or "how do I call this endpoint?" — those answers must already live in the specs. **Never leave runtime or interface details up to the renderer's discretion** — if the spec doesn't pin them down, two renders can produce two different shapes, and any human or automated consumer of the software is now coupled to an undocumented choice. -- **Encapsulate functionality in functional specs.** `requires` modules import only functional specs. It is therefore important that the functionality is encapsulated in the functional specs and not in implementation reqs, as those will not be in the context of future functional specs when fixing previous conformance tests of previous functional specs. -- **Specs must define programmatic interfaces.** Any runtime or interface details must be defined in the functional specs, not in implementation reqs. This means functional specs name the concrete *components* a caller will reach for — utilities, services, methods, functions, fields — and pin down their inputs, outputs, and error behavior, so that callers can use the software without reading the generated code. For example, a spec can require: - - ```plain - - Implement :DataConverter: as a stateless utility component exposing two operations. - - :FormatData: takes a raw data string and a format type, returns a formatted string. Infer and convert value types. Empty inputs must be converted to null. Null values must be preserved — keys with null values must appear in the result, not be omitted. Handle escaping logic. Raise an error if the format type is not supported. - - :ParseData: takes a formatted string and returns a structured object. Output an empty structure for null or missing fields. Unrecognized extra keys should be silently ignored. - ``` - - This rule is complementary to the earlier "specs should be language-agnostic" guideline, not in conflict with it. Component names (`:DataConverter:`, `:FormatData:`, `:ParseData:`) and behavioral contracts belong in functional specs because they survive a language switch unchanged. Language-specific realizations of those contracts — "POJO class with static methods", "Python module with module-level functions", `@staticmethod`, `class Foo`, exception types like `IllegalArgumentException` vs `ValueError`, choice of test framework (`pytest` vs `JUnit`), mocking library, fixture style, assertion syntax — belong in `***implementation reqs***` and `***test reqs***`, because those are exactly what changes when the target language changes. Use `***implementation reqs***` for *how the production code is realized* (language, frameworks, libraries, syntax, error types) and `***test reqs***` for *how the tests are realized* (test framework, test runner, mocking and fixture conventions, parametrization style, naming conventions, file layout). The goal is that swapping languages requires editing only `***implementation reqs***` and `***test reqs***`; the functional spec for `:DataConverter:` should read identically whether the project is in Python, Java, or anything else. - -## Line Length Rule - -**Keep every line in the `.plain` short.** When a sentence is too long, **do not** soft-wrap it across continuation lines — ***plain syntax requires every line inside a section to be its own list item starting with `- ` (with the possibility for nested bullet items). Instead, break the sentence down into multiple bullet items, each on its own line and each prefixed with `- `, nested under the parent bullet so the meaning stays grouped. - -This rule applies to **every** spec update and to **all** sections — `***definitions***`, `***implementation reqs***`, `***test reqs***`, `***functional specs***`, `***acceptance tests***`, and concept explanations alike. - -BAD — line is too long: - -```plain -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(), which returns a list of :EventEnvelope: dicts conforming to the gateway's contract. -``` - -WRONG SYNTAX AND BAD (AVOID AT ALL COSTS) — bare indented continuation lines without a leading `- ` are invalid ***plain syntax: - -```plain -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(), - which returns a list of :EventEnvelope: dicts conforming to the gateway's - contract. -``` - -GOOD — split at a natural clause boundary into nested `- ` bullets: - -```plain -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle() - - The method returns a list of :EventEnvelope: dicts. - - The dicts must conform to the gateway's :EventEnvelope: contract. -``` - -If you find yourself writing a line longer than **120 characters**, stop and split it at a natural clause boundary into nested `- ` bullets (as in the GOOD example above) before moving on. Never use bare indented continuation lines without a leading `- ` — that is invalid ***plain syntax. - -Do not paste long URLs, schema fragments, or example payloads inline either — those belong in `resources/` per the [Linked Resources](#linked-resources) rule above. - -## Conflicting Specs and Conformance Test Debugging - -The renderer can detect conflicting specs. Two functional specs may be in conflict if conformance tests for a previously passing spec begin to fail after a new spec is rendered. When a conformance test failure occurs, the first step is to determine **where the issue lies**. There are three possible outcomes: - -1. **The implementation is incorrect** — the generated code does not correctly implement the functional spec. Fix the spec to clarify intent and re-render. -2. **The conformance tests are incorrect** — the generated tests do not accurately verify the spec. Adjust `***test reqs***` or `***acceptance tests***` to guide better test generation and re-render. -3. **The requirements conflict** — the two functional specs are inherently contradictory. One or both specs must be revised to resolve the conflict before re-rendering. - -Conflicting specs are the most costly outcome and should be **prevented proactively**. When writing or modifying functional specs, carefully consider how each spec interacts with all previous specs. If ambiguity exists, add explicit detail to the spec to eliminate any possible interpretation that could conflict with earlier specs. - -## Working with Specs - -- The `.plain` files are the source of truth. Modify specs to change behavior, then re-render. -- The `resources/` directory contains schemas, API specs, transforms, and test fixtures referenced by the specs. -- Generated code in `plain_modules/` should not be manually edited — changes will be overwritten on the next render. - -## Read-Only Generated Artifacts - -All code in `plain_modules/` and `conformance_tests/` is generated and **must never be modified directly** — not the implementation code, not the unit tests, not the conformance tests. These artifacts can only be: - -- **Read** — to understand what the generated code does, inspect behavior, and identify ambiguities in the specs. -- **Tested** — unit tests and conformance tests can be executed to verify correctness. -- **Debugged** — test failures and unexpected behavior should be traced through the generated code to understand root causes, but fixes must always be applied in the `.plain` specs, never in the generated code. - -Each module has its own folder under `plain_modules//` containing the generated project (implementation + unit tests). Each module also has its own folder under `conformance_tests//`, with individual subfolders per functionality for conformance tests. Conformance tests may also include generated `***acceptance tests***` — these are equally read-only and serve the same purpose: gathering information and debugging the specs. - -To change the generated code, **only the corresponding `.plain` spec files may be edited**: -- To change implementation or unit tests → modify the `***functional specs***`, `***implementation reqs***` or `***definitions***` sections of the spec. -- To guide conformance test generation → modify the `***test reqs***` section of the spec. -- To guide acceptance test generation → modify the `***acceptance tests***` subsections under functional specs. - -The `test_scripts/` folder contains shell scripts for running unit tests and conformance tests against the generated code. These scripts are the entry point for test execution — see the [Running Tests](#running-tests) section for usage. - -The workflow is: read the generated code to understand what it does, identify what is ambiguous or incorrect in the specs, then make changes exclusively in the `.plain` files and re-render. - -## Common mistakes - -- Usage of concepts before defining them - -BAD -```***plain -***functional specs*** - -- Implement :Message: - -``` - -GOOD -```***plain -***definitions*** - -- :Message: is an interface of communication between two users. - -***functional specs*** - -- Implement :Message: -``` - -- Cyclic definitons - -BAD -```***plain -***definitions*** - -- :Message: has an :Author: - -- :Author: can create a :Message: -``` - -GOOD -```***plain -***definitions*** - -- :Message: is an interface of communication between two users. - -- :Author: can create a :Message: -``` - -- Conflicting implementation requirements - -BAD — both reqs in the same module - -```***plain -***implementation reqs*** - -- :Implementation: should be in python - -- :Implementation: should be in react -``` - -GOOD — split into two independent root modules - -`backend.plain` -```***plain -***implementation reqs*** - -- :Implementation: should be in python - -``` - -`frontend.plain` -```***plain -***implementation reqs*** - -- :Implementation: should be in react -``` - - -## `codeplain` CLI reference - -```txt -Render ***plain specs to target code. - -positional arguments: - filename Path to the plain file to render. The directory containing this file has highest precedence for template loading, so - you can place custom templates here to override the defaults. See --template-dir for more details about template - loading. - -options: - -h, --help show this help message and exit - --verbose, -v Enable verbose output - --base-folder BASE_FOLDER - Base folder for the build files - --build-folder BUILD_FOLDER - Folder for build files - --log-to-file, --no-log-to-file - Enable logging to a file. Defaults to True. Set to False to disable. - --log-file-name LOG_FILE_NAME - Name of the log file. Defaults to 'codeplain.log'.Always resolved relative to the plain file directory.If file on - this path already exists, the already existing log file will be overwritten by the current logs. - --render-range RENDER_RANGE - Specify a range of functionalities to render (e.g. `1` , `2`, `3`). Use comma to separate start and end IDs. If only - one functionality ID is provided, only that functionality is rendered. Range is inclusive of both start and end IDs. - --render-from RENDER_FROM - Continue generation starting from this specific functionality (e.g. `2`). The functionality with this ID will be - included in the output. The functionality ID must match one of the functionalities in your plain file. - --force-render Force re-render of all the required modules. - --unittests-script UNITTESTS_SCRIPT - Shell script to run unit tests on generated code. Receives the build folder path as its first argument (default: - 'plain_modules'). - --conformance-tests-folder CONFORMANCE_TESTS_FOLDER - Folder for conformance test files - --conformance-tests-script CONFORMANCE_TESTS_SCRIPT - Path to conformance tests shell script. Every conformance test script should accept two arguments: 1) Path to a - folder (e.g. `plain_modules/module_name`) containing generated source code, 2) Path to a subfolder of the conformance - tests folder (e.g. `conformance_tests/subfoldername`) containing test files. - --prepare-environment-script PREPARE_ENVIRONMENT_SCRIPT - Path to a shell script that prepares the testing environment. The script should accept the source code folder path as - its first argument. - --test-script-timeout TEST_SCRIPT_TIMEOUT - Timeout for test scripts in seconds. If not provided, the default timeout of 120 seconds is used. - --api [API] Alternative base URL for the API. Default: `https://api.codeplain.ai` - --api-key API_KEY API key used to access the API. If not provided, the `CODEPLAIN_API_KEY` environment variable is used. - --full-plain Full preview ***plain specification before code generation.Use when you want to preview context of all ***plain - primitives that are going to be included in order to render the given module. - --dry-run Dry run preview of the code generation (without actually making any changes). - --replay-with REPLAY_WITH - --template-dir TEMPLATE_DIR - Path to a custom template directory. Templates are searched in the following order: 1) Directory containing the plain - file, 2) Custom template directory (if provided through this argument), 3) Built-in standard_template_library - directory - --copy-build If set, copy the rendered contents of code in `--base-folder` folder to `--build-dest` folder after successful - rendering. - --build-dest BUILD_DEST - Target folder to copy rendered contents of code to (used only if --copy-build is set). - --copy-conformance-tests - If set, copy the conformance tests of code in `--conformance-tests-folder` folder to `--conformance-tests-dest` - folder successful rendering. Requires --conformance-tests-script. - --conformance-tests-dest CONFORMANCE_TESTS_DEST - Target folder to copy conformance tests of code to (used only if --copy-conformance-tests is set). - --render-machine-graph - If set, render the state machine graph. - --logging-config-path LOGGING_CONFIG_PATH - Path to the logging configuration file. - --headless Run in headless mode: no TUI, no terminal output except a single render-started message. All logs are written to the - log file. - -configuration: - --config-name CONFIG_NAME - Name of the config file to look for. Looked up in the plain file directory and the current working directory. - Defaults to config.yaml. - -``` - ---- +After loading the applicable material, return to the invoking skill or user request. Keep generated +code read-only and route every correction back to the appropriate `.plain` source. diff --git a/forge/skills/load-plain-reference/references/project-model.md b/forge/skills/load-plain-reference/references/project-model.md new file mode 100644 index 0000000..47221e0 --- /dev/null +++ b/forge/skills/load-plain-reference/references/project-model.md @@ -0,0 +1,47 @@ +# ***plain project model + +Use this reference for project structure and language features not owned by a section-specific +authoring rule. + +## Source-of-truth model + +`.plain` files are the source of truth. They describe observable behavior, implementation choices, +and testing requirements. The renderer produces generated implementation and test artifacts from +those specifications. + +## Typical repository structure + +```text +*.plain # Root specification modules +template/*.plain # Reusable import modules +resources/ # Linked text artifacts such as schemas and fixtures +plain_modules/ # Generated implementation and unit tests +conformance_tests/ # Generated conformance tests, grouped by module and functionality +test_scripts/ # Unit, environment-preparation, and conformance runners +config.yaml # codeplain CLI configuration +``` + +The exact template directory can be configured. Follow the module and linked-resource rules for +what these directories may contain and how a spec refers to them. + +## Template inclusion + +In addition to frontmatter `import`, ***plain supports parameterized template inclusion: + +```plain +{% include "python-console-app-template.plain", main_executable_file_name: "my_app.py" %} +``` + +Parameters are key-value pairs. Templates access them with `{{ variable_name }}`. Only variable +substitution is supported; conditionals, loops, and other Liquid features are not available. + +## Comments + +Lines beginning with `>` are ignored during rendering: + +```plain +> This is a comment in ***plain. +``` + +Comments explain the specification to human authors. Do not use comments to carry requirements the +renderer must implement. diff --git a/forge/skills/load-plain-reference/references/rendering-and-testing.md b/forge/skills/load-plain-reference/references/rendering-and-testing.md new file mode 100644 index 0000000..fc33daa --- /dev/null +++ b/forge/skills/load-plain-reference/references/rendering-and-testing.md @@ -0,0 +1,57 @@ +# Rendering and testing workflow + +## Incremental rendering + +Functional specs render from top to bottom. When rendering one functional spec, the renderer knows +the previous specs but not future specs. A required module's functional specs also count as previous +specs according to the module rules. + +Each functional spec receives its own conformance-test folder. After a new spec renders, the +renderer runs conformance tests for previous specs to detect regressions. A new failure can mean: + +1. The generated implementation does not satisfy the specification. +2. The generated conformance test does not accurately test the specification. +3. The new and previous specifications conflict. + +Diagnose which case applies, then correct the `.plain` source. Use the dedicated conflict-analysis +and debugging skills rather than editing generated output. + +## Generated artifacts are read-only + +Everything under `plain_modules/` and `conformance_tests/` is generated. It may be read, executed, +and debugged, but never edited directly. Apply fixes as follows: + +- Behavior or implementation/unit-test guidance: edit definitions, functional specs, or + implementation requirements as appropriate. +- Conformance-test guidance: edit test requirements. +- Acceptance-test guidance: edit the acceptance tests nested under their owning functional spec. + +Re-render after correcting the source specification. + +## Test-script roles + +Test scripts are renderer entry points as well as developer utilities: + +- `run_unittests_` receives the generated build folder and runs the complete generated unit + suite. It is self-contained and does not depend on environment preparation. +- `prepare_environment_` optionally prepares a reusable system-temporary environment for + conformance testing once per render. +- `run_conformance_tests_` receives the generated build folder and one functionality's + conformance-test folder. It may run repeatedly during a render. + +Invoke the corresponding `implement-*-script` skill when creating or changing a test script. Those +skills own exact staging, cleanup, dependency-installation, shell, and exit-code contracts. + +## Common invocation shape + +Run project scripts from the repository root, using the filenames configured for that project: + +```bash +./test_scripts/run_unittests.sh plain_modules/ +./test_scripts/prepare_environment.sh plain_modules/ +./test_scripts/run_conformance_tests.sh \ + plain_modules/ conformance_tests// +``` + +The renderer resolves script arguments to absolute paths. Test scripts must treat input directories +as read-only and stage writes in their designated system-temporary working directory. diff --git a/forge/skills/plain-healthcheck/SKILL.md b/forge/skills/plain-healthcheck/SKILL.md index a60573b..af4e39b 100644 --- a/forge/skills/plain-healthcheck/SKILL.md +++ b/forge/skills/plain-healthcheck/SKILL.md @@ -13,7 +13,7 @@ description: >- # Plain Healthcheck -Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Always use the skill `load-codeplain-reference` to retrieve the full `codeplain` CLI reference (every flag, path-resolution rules, `config.yaml` mapping, render banners, exit codes), and the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. This skill runs `codeplain … --dry-run` and validates every `config.yaml`, so the CLI reference is required. ## When to run @@ -54,7 +54,7 @@ For each `config.yaml` in the inventory, check **all** of the following. Collect - The path is a string ending in `.sh` (macOS/Linux) or `.ps1` (Windows). The extension must match the rest of the project — do not mix `.sh` and `.ps1` in a single config. - The referenced file actually exists on disk under `test_scripts/`. - On Unix, the script has the executable bit set (`-x`). If not, that is a fixable failure. -4. **No mixed stacks per config.** Every script referenced from a single `config.yaml` must target the same language/stack. For example, `backend/config.yaml` should not reference `run_unittests_js.sh`. If a config crosses stacks, that is a failure — the project should have been split into multiple configs per the rule in `PLAIN_REFERENCE.md`. +4. **No mixed stacks per config.** Every script referenced from a single `config.yaml` must target the same language/stack. For example, `backend/config.yaml` should not reference `run_unittests_js.sh`. If a config crosses stacks, that is a failure — the project should have been split into multiple configs per the applicable module rules loaded by `load-plain-reference`. 5. **No dangling fields.** Any `*-script` field whose target file does not exist is a failure. 6. **`prepare-environment-script` implies `conformance-tests-script`.** A `prepare-environment-script` only makes sense in service of conformance tests — the environment is what those tests run against. If a `config.yaml` declares `prepare-environment-script` but does **not** declare `conformance-tests-script`, that is a failure. Surface it to the user and offer to either (a) invoke `implement-conformance-testing-script` to add the missing script, or (b) remove the `prepare-environment-script` field if it was added in error. Do not auto-pick. 7. **No orphan scripts.** Every script under `test_scripts/` should be referenced by *some* `config.yaml`. If a script is never referenced, surface it as a **warning** (not a hard failure — the user may be in the middle of authoring). diff --git a/forge/skills/resolve-spec-conflict/SKILL.md b/forge/skills/resolve-spec-conflict/SKILL.md index b877f73..a1bbafe 100644 --- a/forge/skills/resolve-spec-conflict/SKILL.md +++ b/forge/skills/resolve-spec-conflict/SKILL.md @@ -45,14 +45,22 @@ When the requirements truly conflict, choose a resolution strategy: **Strategy A: Add detail to disambiguate.** Often the specs aren't contradictory — they're just ambiguous enough that the renderer picks a conflicting interpretation. Adding explicit context to one or both specs eliminates the ambiguity. -``` Before (ambiguous): -- The system should return all :Resource: items. -- The system should return only active :Resource: items. +```plain +***functional specs*** + +- All :Resource: items are returned. + +- Only active :Resource: items are returned. +``` After (disambiguated): -- The system should return all :Resource: items when no filter is specified. -- When the "active" filter is specified, the system should return only active :Resource: items. +```plain +***functional specs*** + +- All :Resource: items are returned when no filter is specified. + +- When the "active" filter is specified, only active :Resource: items are returned. ``` **Strategy B: Revise the newer spec.** If the new spec introduced the conflict, rewrite it to be compatible with the established behavior from earlier specs. diff --git a/forge/skills/run-codeplain/SKILL.md b/forge/skills/run-codeplain/SKILL.md index 562312c..a35a75a 100644 --- a/forge/skills/run-codeplain/SKILL.md +++ b/forge/skills/run-codeplain/SKILL.md @@ -12,7 +12,7 @@ description: >- # Run Codeplain -Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Always use the skill `load-codeplain-reference` to retrieve the full `codeplain` CLI reference (every flag, path-resolution rules, `config.yaml` mapping, render banners, exit codes), and the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. This skill drives the `codeplain` CLI, so the CLI reference is required before launching or resuming any render. ## What this skill is diff --git a/test/cli.test.mjs b/test/cli.test.mjs index fa60561..4bbc60c 100644 --- a/test/cli.test.mjs +++ b/test/cli.test.mjs @@ -25,6 +25,8 @@ import { readManifest, removeEmptyDirsUpward, resolveBaseDir, + terminalHasLightBackground, + terminalPalette, unmergeAgentsMd, unmergeOpencodeInstructions, usesAgentsMd, @@ -90,6 +92,42 @@ describe("toPosix", () => { }); }); +describe("terminal theme colors", () => { + test("detects common light and dark COLORFGBG values", () => { + assert.equal(terminalHasLightBackground({ COLORFGBG: "0;15" }), true); + assert.equal(terminalHasLightBackground({ COLORFGBG: "15;0" }), false); + assert.equal(terminalHasLightBackground({ COLORFGBG: "0;7" }), true); + assert.equal(terminalHasLightBackground({}), false); + }); + + test("uses darker colors on light backgrounds", () => { + assert.notDeepEqual( + terminalPalette({ COLORFGBG: "0;15" }), + terminalPalette({ COLORFGBG: "15;0" }), + ); + }); +}); + +describe("load-plain-reference content", () => { + const skillDir = path.join(repoRoot, "forge", "skills", "load-plain-reference"); + const skill = fs.readFileSync(path.join(skillDir, "SKILL.md"), "utf8"); + + test("keeps the entrypoint concise and routes to rules instead of duplicating them", () => { + assert.ok(skill.split("\n").length < 500); + assert.match(skill, /source of truth/); + assert.match(skill, /\.\.\/\.\.\/rules\/func-specs\.md/); + assert.doesNotMatch(skill, /Each functional spec must imply/); + assert.doesNotMatch(skill, /PLAIN_REFERENCE\.md/); + }); + + test("ships the operational references named by the skill", () => { + assert.ok(fs.existsSync(path.join(skillDir, "references", "project-model.md"))); + assert.ok( + fs.existsSync(path.join(skillDir, "references", "rendering-and-testing.md")), + ); + }); +}); + describe("compareVersions", () => { test("orders dotted numeric versions", () => { assert.equal(compareVersions("1.0.10", "1.0.9"), 1); @@ -246,11 +284,15 @@ describe("resolveBaseDir", () => { resolveBaseDir("codex", "global"), path.join(os.homedir(), ".agents"), ); - // codex and universal share a directory. + // codex, copilot, and universal share a directory. assert.equal( resolveBaseDir("codex", "project"), resolveBaseDir("universal", "project"), ); + assert.equal( + resolveBaseDir("copilot", "project"), + resolveBaseDir("universal", "project"), + ); }); test("forgecode is .forge (project) and ~/forge (global, no dot)", () => { @@ -373,12 +415,13 @@ describe("opencode instructions merge/unmerge", () => { }); }); -describe("AGENTS.md merge/unmerge (codex/forgecode)", () => { +describe("AGENTS.md merge/unmerge (forgecode)", () => { const realCwd = process.cwd(); after(() => process.chdir(realCwd)); - test("only codex and forgecode use AGENTS.md wiring", () => { - assert.equal(usesAgentsMd("codex"), true); + test("only forgecode uses AGENTS.md wiring", () => { + assert.equal(usesAgentsMd("codex"), false); + assert.equal(usesAgentsMd("copilot"), false); assert.equal(usesAgentsMd("forgecode"), true); assert.equal(usesAgentsMd("claude"), false); assert.equal(usesAgentsMd("opencode"), false); @@ -387,16 +430,10 @@ describe("AGENTS.md merge/unmerge (codex/forgecode)", () => { test("project AGENTS.md is repo-root; globs are relative and match the layout", () => { process.chdir(mkTmp()); - assert.equal(agentsMdPath("codex", "project"), path.join(process.cwd(), "AGENTS.md")); - assert.equal(agentsMdRulesGlob("codex", "project"), ".agents/rules/*.md"); assert.equal(agentsMdRulesGlob("forgecode", "project"), ".forge/rules/*.md"); }); test("global AGENTS.md lives in the tool's config dir; glob is absolute", () => { - assert.equal( - agentsMdPath("codex", "global"), - path.join(os.homedir(), ".codex", "AGENTS.md"), - ); assert.equal( agentsMdPath("forgecode", "global"), path.join(os.homedir(), "forge", "AGENTS.md"), @@ -408,19 +445,19 @@ describe("AGENTS.md merge/unmerge (codex/forgecode)", () => { test("creates AGENTS.md with a fenced managed block when none exists", () => { process.chdir(mkTmp()); - const res = mergeAgentsMd("codex", "project"); + const res = mergeAgentsMd("forgecode", "project"); assert.equal(res.status, "created"); - const md = fs.readFileSync(agentsMdPath("codex", "project"), "utf8"); + const md = fs.readFileSync(agentsMdPath("forgecode", "project"), "utf8"); assert.match(md, /BEGIN plain-forge/); assert.match(md, /END plain-forge/); - assert.match(md, /\.agents\/rules\/\*\.md/); + assert.match(md, /\.forge\/rules\/\*\.md/); }); test("appends the block to an existing AGENTS.md, preserving user content", () => { process.chdir(mkTmp()); - const p = agentsMdPath("codex", "project"); + const p = agentsMdPath("forgecode", "project"); fs.writeFileSync(p, "# My project\n\nBuild with `make`.\n"); - const res = mergeAgentsMd("codex", "project"); + const res = mergeAgentsMd("forgecode", "project"); assert.equal(res.status, "merged"); const md = fs.readFileSync(p, "utf8"); assert.match(md, /# My project/); @@ -430,27 +467,27 @@ describe("AGENTS.md merge/unmerge (codex/forgecode)", () => { test("merge is idempotent — second merge reports present, single block", () => { process.chdir(mkTmp()); - mergeAgentsMd("codex", "project"); - const res = mergeAgentsMd("codex", "project"); + mergeAgentsMd("forgecode", "project"); + const res = mergeAgentsMd("forgecode", "project"); assert.equal(res.status, "present"); - const md = fs.readFileSync(agentsMdPath("codex", "project"), "utf8"); + const md = fs.readFileSync(agentsMdPath("forgecode", "project"), "utf8"); assert.equal(md.match(/BEGIN plain-forge/g).length, 1, "no duplicate blocks"); }); test("unmerge deletes an AGENTS.md that was only our block", () => { process.chdir(mkTmp()); - mergeAgentsMd("codex", "project"); - const res = unmergeAgentsMd("codex", "project"); + mergeAgentsMd("forgecode", "project"); + const res = unmergeAgentsMd("forgecode", "project"); assert.equal(res.status, "removed"); - assert.equal(fs.existsSync(agentsMdPath("codex", "project")), false); + assert.equal(fs.existsSync(agentsMdPath("forgecode", "project")), false); }); test("unmerge strips our block but keeps the user's AGENTS.md content", () => { process.chdir(mkTmp()); - const p = agentsMdPath("codex", "project"); + const p = agentsMdPath("forgecode", "project"); fs.writeFileSync(p, "# My project\n\nBuild with `make`.\n"); - mergeAgentsMd("codex", "project"); - const res = unmergeAgentsMd("codex", "project"); + mergeAgentsMd("forgecode", "project"); + const res = unmergeAgentsMd("forgecode", "project"); assert.equal(res.status, "updated"); const md = fs.readFileSync(p, "utf8"); assert.match(md, /# My project/); @@ -500,7 +537,7 @@ describe("detectInstalls", () => { process.chdir(project); process.env.HOME = mkTmp(); - // codex and universal both resolve to .agents; the manifest records which. + // codex, copilot, and universal resolve to .agents; the manifest records which. writeManifest(path.join(project, ".agents"), ["skills/x.md"], "codex"); const found = detectInstalls(); @@ -561,7 +598,7 @@ describe("cli install (integration)", () => { assert.match(again.stderr, /already installed/); // A different agent into the same folder still works. Codex installs into - // .agents/ (the dir Codex actually reads) and wires an AGENTS.md pointer. + // .agents/ (the dir Codex actually reads) without creating AGENTS.md. const codex = runCli(["install", "--agent", "codex", "--scope", "project"], { cwd: project, home, @@ -574,8 +611,7 @@ describe("cli install (integration)", () => { ); const codexManifest = readManifest(path.join(project, ".agents")); assert.equal(codexManifest.agent, "codex", "manifest records the agent"); - const agentsMd = fs.readFileSync(path.join(project, "AGENTS.md"), "utf8"); - assert.match(agentsMd, /\.agents\/rules\/\*\.md/, "AGENTS.md points at the rules"); + assert.equal(fs.existsSync(path.join(project, "AGENTS.md")), false); // opencode is a supported agent and installs into .opencode/. const opencode = runCli( @@ -594,24 +630,51 @@ describe("cli install (integration)", () => { ); }); - test("a failure wiring the rules warns but does not fail the install", () => { + test("a failure wiring forgecode rules warns but does not fail the install", () => { const project = mkTmp(); const home = mkTmp(); - // Make AGENTS.md an (unreadable) directory so the codex rules wiring throws. + // Make AGENTS.md a directory so the ForgeCode rules wiring throws. fs.mkdirSync(path.join(project, "AGENTS.md")); - const res = runCli(["install", "--agent", "codex", "--scope", "project"], { + const res = runCli(["install", "--agent", "forgecode", "--scope", "project"], { cwd: project, home, }); // Install still succeeds and the skills/rules/manifest are on disk. assert.equal(res.status, 0, res.stderr); - assert.ok(fs.existsSync(path.join(project, ".agents", "skills"))); - assert.ok(readManifest(path.join(project, ".agents"))); + assert.ok(fs.existsSync(path.join(project, ".forge", "skills"))); + assert.ok(readManifest(path.join(project, ".forge"))); // ...but the user is warned that the rules wiring didn't complete. - assert.match(res.stderr, /warning: could not wire up the codex rules/); + assert.match(res.stderr, /warning: could not wire up the forgecode rules/); + }); + + test("copilot installs the universal .agents layout without AGENTS.md", () => { + const project = mkTmp(); + const home = mkTmp(); + const res = runCli(["install", "--agent", "copilot", "--scope", "project"], { + cwd: project, + home, + }); + + assert.equal(res.status, 0, res.stderr); + const base = path.join(project, ".agents"); + assert.ok(fs.existsSync(path.join(base, "skills", "load-plain-reference"))); + assert.ok( + fs.existsSync( + path.join( + base, + "skills", + "load-plain-reference", + "references", + "project-model.md", + ), + ), + ); + assert.ok(fs.existsSync(path.join(base, "rules", "bullet-continuation.md"))); + assert.equal(readManifest(base).agent, "copilot"); + assert.equal(fs.existsSync(path.join(project, "AGENTS.md")), false); }); });