From b01bdc3733d30e47fddcb874cb5ad9368fc1dfa4 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Wed, 15 Jul 2026 13:58:34 +0200 Subject: [PATCH 01/16] feat(skills): adding skills for writing skills + rewrite --- .claude/skills/skill-creator/SKILL.md | 45 +++ .../skill-creator/assets/SKILL.template.md | 21 + .../skill-creator/references/checklist.md | 32 ++ .../scripts/validate-metadata.py | 50 +++ forge/skills/add-feature/SKILL.md | 118 +++--- forge/skills/forge-plain/SKILL.md | 358 +++--------------- .../forge-plain/references/phase-1-product.md | 35 ++ .../forge-plain/references/phase-2-tech.md | 24 ++ .../forge-plain/references/phase-3-testing.md | 66 ++++ .../references/phase-4-validate-handoff.md | 49 +++ forge/skills/init-config-file/SKILL.md | 2 + .../skills/load-codeplain-reference/SKILL.md | 188 +++++++++ forge/skills/plain-healthcheck/SKILL.md | 2 +- forge/skills/run-codeplain/SKILL.md | 2 +- 14 files changed, 622 insertions(+), 370 deletions(-) create mode 100644 .claude/skills/skill-creator/SKILL.md create mode 100644 .claude/skills/skill-creator/assets/SKILL.template.md create mode 100644 .claude/skills/skill-creator/references/checklist.md create mode 100644 .claude/skills/skill-creator/scripts/validate-metadata.py create mode 100644 forge/skills/forge-plain/references/phase-1-product.md create mode 100644 forge/skills/forge-plain/references/phase-2-tech.md create mode 100644 forge/skills/forge-plain/references/phase-3-testing.md create mode 100644 forge/skills/forge-plain/references/phase-4-validate-handoff.md create mode 100644 forge/skills/load-codeplain-reference/SKILL.md 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/forge/skills/add-feature/SKILL.md b/forge/skills/add-feature/SKILL.md index 2888ccb..a365c6b 100644 --- a/forge/skills/add-feature/SKILL.md +++ b/forge/skills/add-feature/SKILL.md @@ -1,116 +1,122 @@ --- 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`. + +## Next feature + +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 → …** -## When the User Comes Back with Another Feature +## Error handling -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 → ...** +- **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 +## Validation checklist - [ ] 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 @@ -119,18 +125,14 @@ After completing one feature, the user may immediately describe the next. Start - [ ] 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.) +- [ ] All external interfaces are explicit (endpoint paths, methods, CLI args, formats) - [ ] Acceptance tests are consistent with their parent functional specs - [ ] User approved the final diff - [ ] `plain-healthcheck` returned `PASS` after the final diff was approved - ## 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. - -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/forge-plain/SKILL.md b/forge/skills/forge-plain/SKILL.md index 2f94769..dacaf7b 100644 --- a/forge/skills/forge-plain/SKILL.md +++ b/forge/skills/forge-plain/SKILL.md @@ -1,346 +1,84 @@ --- 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. +## Core loop: one question → one answer → write to disk -## Core principle: one question → one answer → write specs +Every phase runs the same tight loop. Each iteration is a single question followed by an immediate write: -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." +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). 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. -The cycle inside every phase is: +**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. -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. +**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. -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. +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. -**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. +## Review loop -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. +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: -## Quickstart Workflow: QA Session → `***plain` Specs - -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. - -**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: - -- **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. - -```yaml -template_dir: template -``` - -- **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. - -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. - -### 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 +- **Ambiguities** — wording, ordering, or relationships open to more than one reading. -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**: +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. -- 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. +## Phase sequencing -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: +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. -```yaml -unittests-script: test_scripts/run_unittests_. -conformance-tests-script: test_scripts/run_conformance_tests_. -prepare-environment-script: test_scripts/prepare_environment_. -``` +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. -Use `.sh` on macOS/Linux and `.ps1` on Windows, matching what testing scripts. Preserve any existing fields in a `config.yaml` you are updating. +| 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 | -**Hard partition reminder.** Throughout this phase: +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. -- **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 +## Adding features later -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. **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. - -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. - -#### Review the latest additions - -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. - -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. -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. +## 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** (specs not on disk, or not explicitly approved) → do not advance; finish the open phase first. +- **`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. -### 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/` +- Full `***plain` language guide: `PLAIN_REFERENCE.md`. +- 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/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/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/plain-healthcheck/SKILL.md b/forge/skills/plain-healthcheck/SKILL.md index a60573b..3e3c028 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 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 From 306f00f8d568cbebd332cc8495048bf10ebf0121 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Wed, 15 Jul 2026 17:02:12 +0200 Subject: [PATCH 02/16] feat(skills): add workflow self-check checklists to forge-plain, add-feature, init-plain-project --- forge/skills/add-feature/SKILL.md | 12 +---- .../add-feature/references/checklist.md | 31 ++++++++++++ forge/skills/forge-plain/SKILL.md | 6 +++ .../forge-plain/references/checklist.md | 49 +++++++++++++++++++ forge/skills/init-plain-project/SKILL.md | 4 ++ .../references/checklist.md | 31 ++++++++++++ 6 files changed, 122 insertions(+), 11 deletions(-) create mode 100644 forge/skills/add-feature/references/checklist.md create mode 100644 forge/skills/forge-plain/references/checklist.md create mode 100644 forge/skills/init-plain-project/references/checklist.md diff --git a/forge/skills/add-feature/SKILL.md b/forge/skills/add-feature/SKILL.md index a365c6b..67c8f7a 100644 --- a/forge/skills/add-feature/SKILL.md +++ b/forge/skills/add-feature/SKILL.md @@ -118,17 +118,7 @@ After one feature is done, the user may describe the next. Start again from Phas ## Validation checklist -- [ ] 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) -- [ ] Acceptance tests are consistent with their parent functional specs -- [ ] User approved the final diff -- [ ] `plain-healthcheck` returned `PASS` after the final diff was approved +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. ## Question style 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/forge-plain/SKILL.md b/forge/skills/forge-plain/SKILL.md index dacaf7b..5893b49 100644 --- a/forge/skills/forge-plain/SKILL.md +++ b/forge/skills/forge-plain/SKILL.md @@ -57,6 +57,8 @@ When entering a phase, read its reference file and walk its topics **in order** 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. +Before advancing out of any phase, walk the **Self-check checklist** (`references/checklist.md`) and confirm every box for that phase is met. Do not advance on an unmet box. + ## Adding features later 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. @@ -76,6 +78,10 @@ Once the initial specs exist, the user will return with new features. Use the `a - **`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. +## Self-check checklist + +Before advancing out of each phase, read `references/checklist.md` and confirm every box for that phase (plus the loop-iteration block) is met; run the whole list once more before handing off. 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. If a box is unmet, complete that step before advancing. + ## Reference - Full `***plain` language guide: `PLAIN_REFERENCE.md`. diff --git a/forge/skills/forge-plain/references/checklist.md b/forge/skills/forge-plain/references/checklist.md new file mode 100644 index 0000000..0743faa --- /dev/null +++ b/forge/skills/forge-plain/references/checklist.md @@ -0,0 +1,49 @@ +# Forge Plain Workflow Checklist + +Use this to verify the workflow was followed — never as a substitute for it. Run the relevant block **before advancing out of each phase**, and run the whole list once more before handing off. 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; do not advance. + +## Every loop iteration (Phases 1–3) + +- [ ] Asked exactly one focused question via `AskUserQuestion` — not two bundled together. +- [ ] Wrote the snippet to disk immediately after the answer, using the dedicated edit skill — never hand-authored a `***plain` section directly. +- [ ] Reviewed only what just changed (missing parts / extensions / ambiguities), applied the response back to disk, and got explicit approval before moving on. +- [ ] Any answer that contradicted an earlier snippet was fixed in place before the next question — no stale spec left on disk. +- [ ] Stayed within the current phase — no drafting or multi-question detours into later-phase content. + +## Phase 0 — Setup + +- [ ] Invoked `load-plain-reference` first (unless already loaded this session). + +## Phase 1 — What are we building? + +- [ ] 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. +- [ ] `.plain` module structure created with YAML frontmatter; template (if any) has no `***functional specs***`. +- [ ] Every concept authored in `***definitions***` via `add-concept`, defined before use. +- [ ] Every feature authored as functional specs via `add-functional-spec(s)`, each ≤200 LOC, in chronological build order. +- [ ] No `***implementation reqs***`, `***test reqs***`, or `***acceptance tests***` written in this phase. +- [ ] Summarized the full feature list and module/concept layout; got explicit overall confirmation. + +## Phase 2 — What tech should it use? + +- [ ] Read `references/phase-2-tech.md` and walked its topics in order (language, frameworks, storage, external services, structure/architecture, other constraints, anything else). +- [ ] Every requirement authored into `***implementation reqs***` at the right scope (shared → template, module-specific → module). +- [ ] No `***test reqs***` or `***acceptance tests***` written in this phase. +- [ ] Summarized the tech stack and architecture; got explicit overall confirmation. + +## Phase 3 — How is testing done? + +- [ ] Read `references/phase-3-testing.md`; stated the planned `config.yaml` split (one per part) and confirmed it before topic 1. +- [ ] Honored the hard partition — every `:UnitTests:` fact in `***implementation reqs***`, every `:ConformanceTests:` fact in `***test reqs***`; never shared a bullet. +- [ ] Walked topics in order (unit framework, unit types/architecture, conformance decision, prepare-environment decision, layout, execution, other constraints, anything else). +- [ ] Generated the needed scripts under `test_scripts/` via the `implement-*-script` skills and added each `*-script:` entry to the right `config.yaml`. +- [ ] Conformance decision was asked explicitly; if yes, walked every Phase-1 functional spec one at a time for acceptance tests via `add-acceptance-test`. +- [ ] Prepare-environment decision was asked explicitly and recorded. +- [ ] Recapped the testing strategy; got explicit overall confirmation. +- [ ] 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 + +- [ ] Identified the render target — the last module in the dependency chain (or the single module). +- [ ] Ran `init-config-file` to build the final `config.yaml`(s); resolved any precondition gap with the user before validating. +- [ ] Ran `plain-healthcheck`; worked its numbered list to `PASS` — never presented the render command on a `FAIL`. +- [ ] 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/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). From daad3aa4d1b24c8226302a8adf170ce97a70a1bb Mon Sep 17 00:00:00 2001 From: zanjonke Date: Thu, 16 Jul 2026 15:16:14 +0200 Subject: [PATCH 03/16] feat(break-down-func-spec): add Strategy 6 for extracting technical components; standardize example formatting --- forge/skills/break-down-func-spec/SKILL.md | 77 ++++++++++++++++++---- 1 file changed, 63 insertions(+), 14 deletions(-) diff --git a/forge/skills/break-down-func-spec/SKILL.md b/forge/skills/break-down-func-spec/SKILL.md index 749db26..ff06305 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,14 +51,19 @@ 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*** + +- :User: should be able to create, edit, and delete :Recipe: items, with validation on all fields. ``` **After:** ```plain +***functional specs*** + - :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:. ``` @@ -67,15 +73,19 @@ 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 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. ``` **After:** ```plain +***functional specs*** + - 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. ``` @@ -85,14 +95,19 @@ 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*** + +- The system should fetch :Ingredient: data from the external API with pagination, retry on transient errors, and cache results for 10 minutes. ``` **After:** ```plain +***functional specs*** + - 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. ``` @@ -102,18 +117,21 @@ 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*** + +- 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. ``` **After:** ```plain +***functional specs*** + - The system should generate a standard :MealPlan: using round-robin :Recipe: assignment. + - The system should generate a restrictive :MealPlan:. - 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:. ``` @@ -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*** + +- 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. ``` **After:** ```plain +***functional specs*** + - 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:. ``` +### 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*** + +- The system should export a :Report: to PDF, laying out multi-page tables with repeating headers, page numbers, and charts rendered from :Report: data. +``` + +**After:** +```plain +***functional specs*** + +- The system should provide a :PdfRenderer: that lays out multi-page tables from structured content. + - Table headers repeat at the top of each page. + - Each page shows its page number. + +- The system should export a :Report: 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 From b24946bf32c97cefbacf469da4c46215185aa3e0 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Thu, 16 Jul 2026 15:22:18 +0200 Subject: [PATCH 04/16] feat(analyze-if-func-spec-too-complex): flag implied technical components as generated code toward the LOC budget --- .../analyze-if-func-spec-too-complex/SKILL.md | 86 +++++++++++++++---- 1 file changed, 68 insertions(+), 18 deletions(-) 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..a94a81f 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*** + +- :User: should be able to create, edit, delete, and archive :Task: items, with validation on all fields and confirmation dialogs for destructive actions. +``` Acceptable (one behavior each): +```plain +***functional specs*** + - :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:. + ``` ### 2. Number of Concepts Introduced or Modified @@ -59,17 +68,22 @@ 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*** + +- 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. +``` Acceptable (separate the modes): +```plain +***functional specs*** + - 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. - Each sub-order is processed individually. ``` @@ -78,15 +92,20 @@ 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*** + +- 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. +``` Acceptable (separate concerns): +```plain +***functional specs*** - 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. ``` @@ -94,15 +113,21 @@ Acceptable (separate concerns): 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*** + +- 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. +``` Acceptable (build incrementally): +```plain +***functional specs*** + - 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. ``` @@ -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*** + +- The system should export a :Report: 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*** + +- The system should provide a :PdfRenderer: that lays out multi-page tables from structured content. + - Table headers repeat at the top of each page. + - Each page shows its page number. + +- The system should export a :Report: 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) From 3a6db08e7e507d170d79b30e705dc1adacbe685e Mon Sep 17 00:00:00 2001 From: zanjonke Date: Thu, 16 Jul 2026 15:24:51 +0200 Subject: [PATCH 05/16] docs(add-functional-spec): standardize .plain example formatting (section headers + blank-line separation) --- forge/skills/add-functional-spec/SKILL.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/forge/skills/add-functional-spec/SKILL.md b/forge/skills/add-functional-spec/SKILL.md index 175197d..4b216c8 100644 --- a/forge/skills/add-functional-spec/SKILL.md +++ b/forge/skills/add-functional-spec/SKILL.md @@ -51,6 +51,8 @@ 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 +***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:. @@ -67,6 +69,8 @@ Each functional spec must be unambiguous — the renderer should have only one r BAD — bare continuation lines (invalid ***plain syntax, will not render): ```plain +***functional specs*** + - :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(), which returns a list of :EventEnvelope: dicts conforming to the gateway's contract. @@ -75,6 +79,8 @@ BAD — bare continuation lines (invalid ***plain syntax, will not render): GOOD — every line starts with `- `: ```plain +***functional specs*** + - :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. From 50734db12e29a17c2aff21c1fda6826b6cf19b74 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Thu, 16 Jul 2026 15:24:54 +0200 Subject: [PATCH 06/16] docs(add-functional-specs): standardize .plain example formatting (section headers + blank-line separation) --- forge/skills/add-functional-specs/SKILL.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/forge/skills/add-functional-specs/SKILL.md b/forge/skills/add-functional-specs/SKILL.md index f04a3a8..6bbe4eb 100644 --- a/forge/skills/add-functional-specs/SKILL.md +++ b/forge/skills/add-functional-specs/SKILL.md @@ -74,6 +74,8 @@ 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 +***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:. @@ -91,6 +93,8 @@ Each functional spec must be unambiguous — the renderer should have only one r BAD — bare continuation lines (invalid ***plain syntax, will not render): ```plain +***functional specs*** + - :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(), which returns a list of :EventEnvelope: dicts conforming to the gateway's contract. @@ -99,6 +103,8 @@ BAD — bare continuation lines (invalid ***plain syntax, will not render): GOOD — every line starts with `- `: ```plain +***functional specs*** + - :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. From c1832aa91f5bfe08c702cd49602f60a231d57d55 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Thu, 16 Jul 2026 15:24:56 +0200 Subject: [PATCH 07/16] docs(create-import-module): standardize .plain example formatting (section headers + blank-line separation) --- forge/skills/create-import-module/SKILL.md | 7 +++++++ 1 file changed, 7 insertions(+) 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. ``` From 22a59ddcb0174c19a761dc1a745345b66d6caba0 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Thu, 16 Jul 2026 15:24:58 +0200 Subject: [PATCH 08/16] docs(create-requires-module): standardize .plain example formatting (section headers + blank-line separation) --- forge/skills/create-requires-module/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/forge/skills/create-requires-module/SKILL.md b/forge/skills/create-requires-module/SKILL.md index 6de682b..abf25fc 100644 --- a/forge/skills/create-requires-module/SKILL.md +++ b/forge/skills/create-requires-module/SKILL.md @@ -50,6 +50,7 @@ description: Extended module that builds on base_module --- ***definitions*** + - :NewFeature: is a feature added by this module. ***functional specs*** From 2677b5a5e798341998cb387d626a0287323d315a Mon Sep 17 00:00:00 2001 From: zanjonke Date: Thu, 16 Jul 2026 15:28:34 +0200 Subject: [PATCH 09/16] docs(add-concept): standardize .plain example formatting; keep BAD blocks as genuine bare-continuation examples --- forge/skills/add-concept/SKILL.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/forge/skills/add-concept/SKILL.md b/forge/skills/add-concept/SKILL.md index 02ecc86..3d30b58 100644 --- a/forge/skills/add-concept/SKILL.md +++ b/forge/skills/add-concept/SKILL.md @@ -44,12 +44,15 @@ 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) @@ -66,6 +69,8 @@ Attributes and constraints are nested sub-bullets: BAD — bare continuation lines (invalid ***plain syntax, will not render): ```plain +***definitions*** + - :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. @@ -74,6 +79,8 @@ BAD — bare continuation lines (invalid ***plain syntax, will not render): GOOD — every line starts with `- `: ```plain +***definitions*** + - :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. From 7112199c9382adaecbca13076ef42c08a0f20224 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Thu, 16 Jul 2026 15:28:36 +0200 Subject: [PATCH 10/16] docs(add-implementation-requirement): standardize .plain example formatting; keep BAD blocks as genuine bare-continuation examples --- forge/skills/add-implementation-requirement/SKILL.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/forge/skills/add-implementation-requirement/SKILL.md b/forge/skills/add-implementation-requirement/SKILL.md index 8d451b0..c88782d 100644 --- a/forge/skills/add-implementation-requirement/SKILL.md +++ b/forge/skills/add-implementation-requirement/SKILL.md @@ -61,9 +61,13 @@ 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. @@ -78,6 +82,8 @@ Reference defined `:Concepts:` where they add clarity. Implementation reqs in no BAD — bare continuation lines (invalid ***plain syntax, will not render): ```plain +***implementation reqs*** + - :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 @@ -87,6 +93,8 @@ BAD — bare continuation lines (invalid ***plain syntax, will not render): GOOD — every line starts with `- `: ```plain +***implementation reqs*** + - :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. From 0a5e9b37bd0442a14aa961078a89b4a8776e3d44 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Thu, 16 Jul 2026 15:28:39 +0200 Subject: [PATCH 11/16] docs(add-test-requirement): standardize .plain example formatting; keep BAD blocks as genuine bare-continuation examples --- forge/skills/add-test-requirement/SKILL.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/forge/skills/add-test-requirement/SKILL.md b/forge/skills/add-test-requirement/SKILL.md index 32c9386..b8f3477 100644 --- a/forge/skills/add-test-requirement/SKILL.md +++ b/forge/skills/add-test-requirement/SKILL.md @@ -51,9 +51,13 @@ 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. ``` @@ -69,13 +73,17 @@ Reference predefined concepts like `:ConformanceTests:` and any defined `:Concep 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. +***test reqs*** + +- :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 +***test reqs*** + - :ConformanceTests: must mock all external HTTP calls. - The test suite must remain hermetic. - Tests must not depend on network access. From 4b39c1060577ae43424a965aa28d1941995a007d Mon Sep 17 00:00:00 2001 From: zanjonke Date: Thu, 16 Jul 2026 15:29:14 +0200 Subject: [PATCH 12/16] docs(resolve-spec-conflict): standardize .plain example formatting (section headers + blank-line separation) --- forge/skills/resolve-spec-conflict/SKILL.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/forge/skills/resolve-spec-conflict/SKILL.md b/forge/skills/resolve-spec-conflict/SKILL.md index b877f73..300b412 100644 --- a/forge/skills/resolve-spec-conflict/SKILL.md +++ b/forge/skills/resolve-spec-conflict/SKILL.md @@ -45,13 +45,21 @@ 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): +```plain +***functional specs*** + - The system should return all :Resource: items. + - The system should return only active :Resource: items. +``` After (disambiguated): +```plain +***functional specs*** + - 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. ``` From ed71c995b6c68469aa7fc12666fe56ee79ba9c4e Mon Sep 17 00:00:00 2001 From: zanjonke Date: Thu, 16 Jul 2026 15:29:55 +0200 Subject: [PATCH 13/16] docs(rules): document the .plain example presentation convention (section headers + blank-line separation) --- forge/rules/line-length.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/forge/rules/line-length.md b/forge/rules/line-length.md index db80c73..884a14b 100644 --- a/forge/rules/line-length.md +++ b/forge/rules/line-length.md @@ -23,12 +23,16 @@ These rules apply to **every** section — `***definitions***`, `***implementati BAD — line is too long: ```plain +***functional specs*** + - :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 +***functional specs*** + - :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(), which returns a list of :EventEnvelope: dicts conforming to the gateway's contract. @@ -37,6 +41,8 @@ WRONG SYNTAX (AVOID AT ALL COSTS) — bare indented continuation without a leadi GOOD — split at a natural clause boundary into nested `- ` bullets: ```plain +***functional specs*** + - :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. @@ -45,3 +51,8 @@ GOOD — split at a natural clause boundary into nested `- ` bullets: ## 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 + +## Presenting `.plain` examples +- Show every example snippet under its owning section header — e.g. `***functional specs***`, `***definitions***` — so the reader can see which section the lines belong in +- Separate top-level `- ` items with a single blank line; keep nested `- ` clarifications directly under their parent with **no** blank line between parent and child +- These conventions apply to canonical / "good" / "after" / "acceptable" example blocks; BAD / WRONG / `Before:` / `Too complex:` blocks show the header too but may otherwise deviate (that is the point of showing them) From 287a905ddc7515562613bea15e6aea601b446e36 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Thu, 16 Jul 2026 16:11:30 +0200 Subject: [PATCH 14/16] feat(specs): functional specs written as present-tense facts, not imperative commands Add a Voice rule to func-specs.md (present-tense, concrete subject, no imperatives, no 'should', no vague 'The system') and convert every functional-spec example across the rules and skills to match. --- forge/rules/func-specs.md | 37 +++++++++++++-- forge/rules/line-length.md | 6 +-- forge/rules/linked-resources.md | 6 +-- forge/skills/add-acceptance-test/SKILL.md | 8 ++-- forge/skills/add-functional-spec/SKILL.md | 6 +-- forge/skills/add-functional-specs/SKILL.md | 6 +-- forge/skills/add-resource/SKILL.md | 2 +- forge/skills/analyze-2-func-specs/SKILL.md | 20 ++++---- forge/skills/analyze-func-specs/SKILL.md | 20 ++++---- .../analyze-if-func-spec-too-complex/SKILL.md | 40 ++++++++-------- forge/skills/break-down-func-spec/SKILL.md | 46 +++++++++---------- forge/skills/create-requires-module/SKILL.md | 2 +- forge/skills/load-plain-reference/SKILL.md | 33 ++++++------- forge/skills/resolve-spec-conflict/SKILL.md | 8 ++-- 14 files changed, 136 insertions(+), 104 deletions(-) diff --git a/forge/rules/func-specs.md b/forge/rules/func-specs.md index a4daf68..52e69d5 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 @@ -65,11 +96,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/line-length.md b/forge/rules/line-length.md index 884a14b..f1b9ce0 100644 --- a/forge/rules/line-length.md +++ b/forge/rules/line-length.md @@ -25,7 +25,7 @@ BAD — line is too long: ```plain ***functional specs*** -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(), which returns a list of :EventEnvelope: dicts conforming to the gateway's contract. +- :GatewayWebhook: hands 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 `- `: @@ -33,7 +33,7 @@ WRONG SYNTAX (AVOID AT ALL COSTS) — bare indented continuation without a leadi ```plain ***functional specs*** -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(), +- :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(), which returns a list of :EventEnvelope: dicts conforming to the gateway's contract. ``` @@ -43,7 +43,7 @@ GOOD — split at a natural clause boundary into nested `- ` bullets: ```plain ***functional specs*** -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle() +- :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle() - The method returns a list of :EventEnvelope: dicts. - The dicts must conform to the gateway's :EventEnvelope: contract. ``` diff --git a/forge/rules/linked-resources.md b/forge/rules/linked-resources.md index 632a8e3..f87e507 100644 --- a/forge/rules/linked-resources.md +++ b/forge/rules/linked-resources.md @@ -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,9 +56,9 @@ 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 diff --git a/forge/skills/add-acceptance-test/SKILL.md b/forge/skills/add-acceptance-test/SKILL.md index c4e9a16..1519452 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. ``` @@ -55,8 +56,7 @@ 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. + - Processing 250 :Task: items should result in 3 batches with the last batch containing the remaining 50 items. ``` GOOD — every line starts with `- `: @@ -76,7 +76,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-functional-spec/SKILL.md b/forge/skills/add-functional-spec/SKILL.md index 4b216c8..de7ce13 100644 --- a/forge/skills/add-functional-spec/SKILL.md +++ b/forge/skills/add-functional-spec/SKILL.md @@ -53,7 +53,7 @@ Each functional spec must be unambiguous — the renderer should have only one r ```plain ***functional specs*** -- :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:. @@ -71,7 +71,7 @@ BAD — bare continuation lines (invalid ***plain syntax, will not render): ```plain ***functional specs*** -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(), +- :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(), which returns a list of :EventEnvelope: dicts conforming to the gateway's contract. ``` @@ -81,7 +81,7 @@ GOOD — every line starts with `- `: ```plain ***functional specs*** -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(). +- :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(). - The method returns a list of :EventEnvelope: dicts. - The dicts must conform to the gateway's :EventEnvelope: contract. ``` diff --git a/forge/skills/add-functional-specs/SKILL.md b/forge/skills/add-functional-specs/SKILL.md index 6bbe4eb..069135b 100644 --- a/forge/skills/add-functional-specs/SKILL.md +++ b/forge/skills/add-functional-specs/SKILL.md @@ -76,7 +76,7 @@ Each functional spec must be unambiguous — the renderer should have only one r ```plain ***functional specs*** -- :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:. @@ -95,7 +95,7 @@ BAD — bare continuation lines (invalid ***plain syntax, will not render): ```plain ***functional specs*** -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(), +- :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(), which returns a list of :EventEnvelope: dicts conforming to the gateway's contract. ``` @@ -105,7 +105,7 @@ GOOD — every line starts with `- `: ```plain ***functional specs*** -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(). +- :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(). - The method returns a list of :EventEnvelope: dicts. - The dicts must conform to the gateway's :EventEnvelope: contract. ``` diff --git a/forge/skills/add-resource/SKILL.md b/forge/skills/add-resource/SKILL.md index 0f4e1d5..1c96554 100644 --- a/forge/skills/add-resource/SKILL.md +++ b/forge/skills/add-resource/SKILL.md @@ -35,7 +35,7 @@ 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 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 a94a81f..ae832ee 100644 --- a/forge/skills/analyze-if-func-spec-too-complex/SKILL.md +++ b/forge/skills/analyze-if-func-spec-too-complex/SKILL.md @@ -39,20 +39,20 @@ Too complex: ```plain ***functional specs*** -- :User: should be able to create, edit, delete, and archive :Task: items, with validation on all fields and confirmation dialogs for destructive actions. +- 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): ```plain ***functional specs*** -- :User: should be able to create :Task:. Only valid :Task: items can be added. +- A :User: can create :Task:. Only valid :Task: items can be added. -- :User: should be able to edit :Task:. +- A :User: can edit :Task:. -- :User: should be able to delete :Task:. +- A :User: can delete :Task:. -- :User: should be able to archive :Task:. +- A :User: can archive :Task:. ``` @@ -73,18 +73,18 @@ Too complex: ```plain ***functional specs*** -- 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. +- :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): ```plain ***functional specs*** -- The system should process standard :Order: by validating and storing it. +- A standard :Order: is validated and stored. -- The system should process express :Order: by queuing it for immediate dispatch without validation. +- An express :Order: is queued for immediate dispatch without validation. -- The system should process bulk :Order: by splitting it into sub-orders of 100 items each. +- A bulk :Order: is split into sub-orders of 100 items each. - Each sub-order is processed individually. ``` @@ -96,17 +96,17 @@ Too complex: ```plain ***functional specs*** -- 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. +- :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): ```plain ***functional specs*** -- The system should fetch :Resource: items from the external API. +- :Resource: items are fetched from the external API. -- The system should paginate when fetching :Resource: items from the external API. +- :Resource: items are fetched from the external API in pages. -- The system should retry fetching :Resource: on transient errors using exponential backoff. +- Fetching :Resource: items is retried on transient errors using exponential backoff. ``` ### 5. UI Complexity @@ -117,18 +117,18 @@ Too complex: ```plain ***functional specs*** -- 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. +- 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): ```plain ***functional specs*** -- Display a dashboard page for :User:. +- A dashboard page is shown for :User:. -- Show recent :Task: items in a sortable table on the dashboard. +- Recent :Task: items are shown in a sortable table on the dashboard. -- Show a notification indicator with the unread count on the dashboard. +- A notification indicator with the unread count is shown on the dashboard. ``` ### 6. Data Transformation Complexity @@ -146,18 +146,18 @@ Too complex (builds the component and uses it in one spec): ```plain ***functional specs*** -- The system should export a :Report: to PDF, laying out multi-page tables with repeating headers and page numbers, and charts rendered from :Report: data. +- 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*** -- The system should provide a :PdfRenderer: that lays out multi-page tables from structured content. +- :PdfRenderer: lays out multi-page tables from structured content. - Table headers repeat at the top of each page. - Each page shows its page number. -- The system should export a :Report: to PDF using :PdfRenderer:, embedding charts rendered from :Report: data. +- 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. diff --git a/forge/skills/break-down-func-spec/SKILL.md b/forge/skills/break-down-func-spec/SKILL.md index ff06305..564070a 100644 --- a/forge/skills/break-down-func-spec/SKILL.md +++ b/forge/skills/break-down-func-spec/SKILL.md @@ -53,18 +53,18 @@ If the spec bundles multiple independently testable actions, split each into its ```plain ***functional specs*** -- :User: should be able to create, edit, and delete :Recipe: items, with validation on all fields. +- A :User: can create, edit, and delete :Recipe: items, with validation on all fields. ``` **After:** ```plain ***functional specs*** -- :User: should be able to create a :Recipe:. Only valid :Recipe: items can be created. +- A :User: can 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. +- A :User: can edit an existing :Recipe:. Validation rules apply to the edited fields. -- :User: should be able to delete a :Recipe:. +- A :User: can delete a :Recipe:. ``` ### Strategy 2: Separate Setup from Behavior @@ -75,18 +75,18 @@ If the spec introduces a new construct and immediately defines complex behavior ```plain ***functional specs*** -- 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. +- The :MealPlan: screen displays a weekly grid of :Slot: items, allows drag-and-drop reordering, and shows nutritional totals per day. ``` **After:** ```plain ***functional specs*** -- The system should provide a :MealPlan: screen that displays a weekly grid of :Slot: items. +- The :MealPlan: screen 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. +- A :User: can reorder :Slot: items within a day on the :MealPlan: screen using drag-and-drop. -- The :MealPlan: screen should display nutritional totals for each day. +- The :MealPlan: screen displays nutritional totals for each day. ``` ### Strategy 3: Separate Core Logic from Cross-Cutting Concerns @@ -97,18 +97,18 @@ If the spec mixes primary functionality with error handling, retries, caching, p ```plain ***functional specs*** -- The system should fetch :Ingredient: data from the external API with pagination, retry on transient errors, and cache results for 10 minutes. +- :Ingredient: data is fetched from the external API with pagination, retry on transient errors, and caching for 10 minutes. ``` **After:** ```plain ***functional specs*** -- The system should fetch :Ingredient: data from the external API. +- :Ingredient: data is fetched from the external API. -- The system should paginate when fetching :Ingredient: data from the external API. +- :Ingredient: data is fetched from the external API in pages. -- The system should retry fetching :Ingredient: data on transient errors. +- Fetching :Ingredient: data is retried on transient errors. ``` ### Strategy 4: Separate Conditional Paths @@ -119,20 +119,20 @@ If the spec describes different modes or branches, give each its own spec. ```plain ***functional specs*** -- 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. +- :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 ***functional specs*** -- The system should generate a standard :MealPlan: using round-robin :Recipe: assignment. +- A standard :MealPlan: is generated using round-robin :Recipe: assignment. -- The system should generate a restrictive :MealPlan:. +- 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 @@ -143,18 +143,18 @@ If the spec describes a full screen, split into layout + individual interactive ```plain ***functional specs*** -- 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. +- 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 ***functional specs*** -- Display the :Dashboard: screen with a summary card showing :MealFrameStats:. +- The :Dashboard: screen shows a summary card with :MealFrameStats:. -- The :Dashboard: screen should show a scrollable list of recent :MealPlan: items. +- The :Dashboard: screen shows a scrollable list of recent :MealPlan: items. -- The :Dashboard: screen should include a button to create a new :MealPlan:. +- The :Dashboard: screen includes a button to create a new :MealPlan:. ``` ### Strategy 6: Extract a Reusable Technical Component @@ -167,18 +167,18 @@ Define the component concept in `***definitions***` (via `add-concept`) before t ```plain ***functional specs*** -- The system should export a :Report: to PDF, laying out multi-page tables with repeating headers, page numbers, and charts rendered from :Report: data. +- 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*** -- The system should provide a :PdfRenderer: that lays out multi-page tables from structured content. +- :PdfRenderer: lays out multi-page tables from structured content. - Table headers repeat at the top of each page. - Each page shows its page number. -- The system should export a :Report: to PDF using :PdfRenderer:, embedding charts rendered from :Report: data. +- 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. diff --git a/forge/skills/create-requires-module/SKILL.md b/forge/skills/create-requires-module/SKILL.md index abf25fc..2fdda6e 100644 --- a/forge/skills/create-requires-module/SKILL.md +++ b/forge/skills/create-requires-module/SKILL.md @@ -55,7 +55,7 @@ description: Extended module that builds on base_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/load-plain-reference/SKILL.md b/forge/skills/load-plain-reference/SKILL.md index fe6ebfc..04c52c2 100644 --- a/forge/skills/load-plain-reference/SKILL.md +++ b/forge/skills/load-plain-reference/SKILL.md @@ -124,13 +124,13 @@ The renderer has **no knowledge of future functional specs**. When a functional ```plain ***functional specs*** -- Implement the entry point for :App:. +- :App: has an entry point. -- Show :TaskList:. +- The :TaskList: is displayed. -- :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 delete :Task:. +- A :User: can delete a :Task:. ``` @@ -139,7 +139,7 @@ Each functional spec must be unambiguous. If a single line is not enough to full ```plain ***functional specs*** -- :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:. @@ -152,10 +152,11 @@ Nested under individual functional specs to specify how to verify correct implem ```plain ***functional specs*** -- Display "hello, world" +- :App: displays "hello, world". ***acceptance tests*** - :App: should exit with status code 0 indicating successful execution. + - :App: should complete execution in under 1 second. ``` @@ -184,7 +185,7 @@ The frontmatter is enclosed between `---` markers and supports: 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:. +- A :User: can add a :Task:. - The user-interface details are in [task_modal_specification.yaml](task_modal_specification.yaml). ``` @@ -227,7 +228,7 @@ The accompanying spec line should describe the *role* of the artifact ("the requ ***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:. ``` @@ -248,9 +249,9 @@ For example, instead of linking `task_modal_specification.yaml` from two differe ***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:. ``` 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. @@ -420,7 +421,7 @@ For implementation details — the exact step sequence, toolchain checks, langua - **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. + - :DataConverter: is 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. ``` @@ -436,13 +437,13 @@ This rule applies to **every** spec update and to **all** sections — `***defin 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. +- :GatewayWebhook: hands 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(), +- :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(), which returns a list of :EventEnvelope: dicts conforming to the gateway's contract. ``` @@ -450,7 +451,7 @@ WRONG SYNTAX AND BAD (AVOID AT ALL COSTS) — bare indented continuation lines w GOOD — split at a natural clause boundary into nested `- ` bullets: ```plain -- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle() +- :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle() - The method returns a list of :EventEnvelope: dicts. - The dicts must conform to the gateway's :EventEnvelope: contract. ``` @@ -502,7 +503,7 @@ BAD ```***plain ***functional specs*** -- Implement :Message: +- A :User: can send a :Message:. ``` @@ -514,7 +515,7 @@ GOOD ***functional specs*** -- Implement :Message: +- A :User: can send a :Message:. ``` - Cyclic definitons diff --git a/forge/skills/resolve-spec-conflict/SKILL.md b/forge/skills/resolve-spec-conflict/SKILL.md index 300b412..a1bbafe 100644 --- a/forge/skills/resolve-spec-conflict/SKILL.md +++ b/forge/skills/resolve-spec-conflict/SKILL.md @@ -49,18 +49,18 @@ Before (ambiguous): ```plain ***functional specs*** -- The system should return all :Resource: items. +- All :Resource: items are returned. -- The system should return only active :Resource: items. +- Only active :Resource: items are returned. ``` After (disambiguated): ```plain ***functional specs*** -- The system should return all :Resource: items when no filter is specified. +- All :Resource: items are returned when no filter is specified. -- When the "active" filter is specified, the system should return only active :Resource: items. +- 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. From 7b8f4fcfa5aed83e19b8c615450a6dff979ee297 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Thu, 16 Jul 2026 16:12:17 +0200 Subject: [PATCH 15/16] feat(forge-plain): delegate phase-gate validation to a one-shot review agent Drop the interactive phase worker (unreachable as a background fork) and keep authoring inline. At each phase gate spawn a fresh general-purpose review agent with the phase's checklist, touched-file paths, and an evidence record; it verifies [disk]/[record]/[both]-tagged boxes and returns APPROVED or gaps. --- forge/skills/forge-plain/SKILL.md | 26 ++++++-- .../forge-plain/references/checklist.md | 66 +++++++++++-------- 2 files changed, 59 insertions(+), 33 deletions(-) diff --git a/forge/skills/forge-plain/SKILL.md b/forge/skills/forge-plain/SKILL.md index 5893b49..438a2ff 100644 --- a/forge/skills/forge-plain/SKILL.md +++ b/forge/skills/forge-plain/SKILL.md @@ -18,12 +18,29 @@ Always invoke the `load-plain-reference` skill first to load the `***plain` synt 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. +## Agent orchestration + +`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). + +The one thing that **is** delegated is validation at each phase gate: + +- **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. + +### Per-phase loop + +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. + +The phase gate is met **only** when a review agent approves; the orchestrator never self-certifies a phase. + ## Core loop: one question → one answer → write to disk Every phase runs the same tight loop. Each iteration is a single question followed by an immediate write: 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). 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. +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. **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. @@ -57,7 +74,7 @@ When entering a phase, read its reference file and walk its topics **in order** 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. -Before advancing out of any phase, walk the **Self-check checklist** (`references/checklist.md`) and confirm every box for that phase is met. Do not advance on an unmet box. +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. ## Adding features later @@ -73,14 +90,15 @@ Once the initial specs exist, the user will return with new features. Use the `a ## 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** (specs not on disk, or not explicitly approved) → do not advance; finish the open phase first. +- **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. ## Self-check checklist -Before advancing out of each phase, read `references/checklist.md` and confirm every box for that phase (plus the loop-iteration block) is met; run the whole list once more before handing off. 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. If a box is unmet, complete that step before advancing. +`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 diff --git a/forge/skills/forge-plain/references/checklist.md b/forge/skills/forge-plain/references/checklist.md index 0743faa..71d6e22 100644 --- a/forge/skills/forge-plain/references/checklist.md +++ b/forge/skills/forge-plain/references/checklist.md @@ -1,49 +1,57 @@ # Forge Plain Workflow Checklist -Use this to verify the workflow was followed — never as a substitute for it. Run the relevant block **before advancing out of each phase**, and run the whole list once more before handing off. 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; do not advance. +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) -- [ ] Asked exactly one focused question via `AskUserQuestion` — not two bundled together. -- [ ] Wrote the snippet to disk immediately after the answer, using the dedicated edit skill — never hand-authored a `***plain` section directly. -- [ ] Reviewed only what just changed (missing parts / extensions / ambiguities), applied the response back to disk, and got explicit approval before moving on. -- [ ] Any answer that contradicted an earlier snippet was fixed in place before the next question — no stale spec left on disk. -- [ ] Stayed within the current phase — no drafting or multi-question detours into later-phase content. +- [ ] **[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 -- [ ] Invoked `load-plain-reference` first (unless already loaded this session). +- [ ] **[record]** Invoked `load-plain-reference` first (unless already loaded this session). ## Phase 1 — What are we building? -- [ ] 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. -- [ ] `.plain` module structure created with YAML frontmatter; template (if any) has no `***functional specs***`. -- [ ] Every concept authored in `***definitions***` via `add-concept`, defined before use. -- [ ] Every feature authored as functional specs via `add-functional-spec(s)`, each ≤200 LOC, in chronological build order. -- [ ] No `***implementation reqs***`, `***test reqs***`, or `***acceptance tests***` written in this phase. -- [ ] Summarized the full feature list and module/concept layout; got explicit overall confirmation. +- [ ] **[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? -- [ ] Read `references/phase-2-tech.md` and walked its topics in order (language, frameworks, storage, external services, structure/architecture, other constraints, anything else). -- [ ] Every requirement authored into `***implementation reqs***` at the right scope (shared → template, module-specific → module). -- [ ] No `***test reqs***` or `***acceptance tests***` written in this phase. -- [ ] Summarized the tech stack and architecture; got explicit overall confirmation. +- [ ] **[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? -- [ ] Read `references/phase-3-testing.md`; stated the planned `config.yaml` split (one per part) and confirmed it before topic 1. -- [ ] Honored the hard partition — every `:UnitTests:` fact in `***implementation reqs***`, every `:ConformanceTests:` fact in `***test reqs***`; never shared a bullet. -- [ ] Walked topics in order (unit framework, unit types/architecture, conformance decision, prepare-environment decision, layout, execution, other constraints, anything else). -- [ ] Generated the needed scripts under `test_scripts/` via the `implement-*-script` skills and added each `*-script:` entry to the right `config.yaml`. -- [ ] Conformance decision was asked explicitly; if yes, walked every Phase-1 functional spec one at a time for acceptance tests via `add-acceptance-test`. -- [ ] Prepare-environment decision was asked explicitly and recorded. -- [ ] Recapped the testing strategy; got explicit overall confirmation. -- [ ] Ran `check-plain-env`; it returned `PASS`, or `WARN`/`FAIL` with each remaining item explicitly acknowledged by the user (re-invoked after any install). +- [ ] **[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 -- [ ] Identified the render target — the last module in the dependency chain (or the single module). -- [ ] Ran `init-config-file` to build the final `config.yaml`(s); resolved any precondition gap with the user before validating. -- [ ] Ran `plain-healthcheck`; worked its numbered list to `PASS` — never presented the render command on a `FAIL`. -- [ ] Presented the render command only after the dry-run passed, plus every side-channel script actually generated in Phase 3. +- [ ] **[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. From 555239d64bb1237b21ac3a3f71bd583e7b19de16 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Thu, 16 Jul 2026 16:19:10 +0200 Subject: [PATCH 16/16] fix(resources): linked-resource paths resolve from the codeplain run dir; '../' traversal is valid Correct the path-resolution rule in linked-resources.md, add-resource, and load-plain-reference: paths resolve relative to where codeplain is run (cwd), not the .plain file's folder, and may traverse parent dirs (e.g. ../../resources/resource.md). Drop the same-folder/subfolder-only and no-'../' restrictions; the single-text-file / no-URL / no-binary / no-folder constraints stay. --- forge/rules/linked-resources.md | 7 ++++--- forge/skills/add-resource/SKILL.md | 13 ++++++------- forge/skills/load-plain-reference/SKILL.md | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/forge/rules/linked-resources.md b/forge/rules/linked-resources.md index f87e507..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 @@ -62,5 +62,6 @@ A linked resource **must not** be any of the following: ``` ## 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-resource/SKILL.md b/forge/skills/add-resource/SKILL.md index 1c96554..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. @@ -40,11 +40,10 @@ Use standard markdown link syntax inside any spec section: ## 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/load-plain-reference/SKILL.md b/forge/skills/load-plain-reference/SKILL.md index 04c52c2..26c5af9 100644 --- a/forge/skills/load-plain-reference/SKILL.md +++ b/forge/skills/load-plain-reference/SKILL.md @@ -182,7 +182,7 @@ The frontmatter is enclosed between `---` markers and supports: ### 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. +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 directory where `codeplain` is run (the current working directory), and may traverse into parent directories — `../../resources/resource.md` is valid — as long as they resolve to a real text file on disk. ```plain - A :User: can add a :Task:.