diff --git a/forge/rules/bullet-continuation.md b/forge/rules/bullet-continuation.md index 63bb904..cac0b15 100644 --- a/forge/rules/bullet-continuation.md +++ b/forge/rules/bullet-continuation.md @@ -12,14 +12,23 @@ These rules apply to every section and to concept explanations. - Do not split a valid line merely to satisfy an arbitrary formatting width - Prefer clear, precise wording; concision must never remove required detail -## Never use bare continuation lines - -- Every line inside a section must be a list item beginning with `- ` -- An indented continuation without `- ` is invalid syntax -- If content is intentionally separated across lines, express the additional lines as nested - bullet items so each line remains syntactically valid - -WRONG — bare continuation lines: +## One statement per line + +This is the preferred `.plain` authoring convention, not something the renderer enforces. A line +break in a section means one of exactly two things: a new `- ` statement, or a nested `- ` +clarification of the statement above it. There is no third, typographic reason to break a line. + +- Write each statement on a single line, however long +- Never break a line just for width: a bare continuation (an indented line without `- `) adds a + line break that carries no structure — to every future reader and editing agent it is + indistinguishable from a deliberate sub-point +- The test: if content deserves its own line, it deserves its own `- ` bullet; if it doesn't, + keep it on the parent's line +- What the renderer does enforce is the top level: every statement in a section must be a `- ` + list item — a flush-left prose line is a syntax error. A bare continuation, by contrast, is + accepted and folded into its parent item verbatim + +BAD — line broken for width (bare continuation): ```plain ***functional specs*** diff --git a/forge/rules/definitions.md b/forge/rules/definitions.md index d9a81a4..ec4ad66 100644 --- a/forge/rules/definitions.md +++ b/forge/rules/definitions.md @@ -13,7 +13,7 @@ When writing or editing a `***definitions***` section in a `.plain` file, always ## Uniqueness - Concept names must be globally unique across the spec and all its imports -- Check for collisions with imported templates, `import` and `requires` modules before adding +- Check for collisions with concepts coming from `import` and `requires` modules before adding ## Predefined concepts (do not redefine) - ***plain ships several predefined concepts that are available in every module without being defined @@ -89,7 +89,6 @@ Example of defining technical components alongside their behavior: - :CsvFileReader: reads CSV rows in a batched manner. - - :CsvJoiner: joins csvs and uses :CsvFileWriter: to create the merged CSV. ``` diff --git a/forge/rules/func-specs.md b/forge/rules/func-specs.md index d7c5592..e4fee18 100644 --- a/forge/rules/func-specs.md +++ b/forge/rules/func-specs.md @@ -38,7 +38,9 @@ GOOD — statements of fact with concrete subjects: ``` ## Complexity limit -- Each functional spec must imply a **maximum of 200 changed lines of code** +- The complexity limit is about breaking software into small, independently understandable behavior slices that people and AI can reason about, implement, and test reliably +- The current mechanism for enforcing the complexity limit is that each functional spec must imply a **maximum of 200 changed lines of code**. +- Smaller slices produce focused test failures, reduce hidden coupling and regression surface, and make review and incremental rendering more predictable - If a spec is too large, use `break-down-func-spec` to split it into multiple smaller, independent specs - Use `analyze-if-func-spec-too-complex` to verify before inserting - Use `analyze-func-specs` to check a spec (or a batch of specs) against all relevant existing specs in a single batched call; use `resolve-spec-conflict` for each conflicting pair it reports @@ -92,7 +94,7 @@ GOOD — statements of fact with concrete subjects: ## Encapsulation - Functionality must be self-contained in the spec text -- `requires` modules only receive functional specs — do not rely on implementation reqs to convey behavior +- `requires` modules only pass on functional specs and `exported_concepts` — never implementation reqs, so do not rely on implementation reqs to convey behavior - Behavior that downstream modules need must be expressed in functional specs, not elsewhere ## Acceptance tests diff --git a/forge/rules/impl-reqs.md b/forge/rules/impl-reqs.md index 8ba9732..745f69b 100644 --- a/forge/rules/impl-reqs.md +++ b/forge/rules/impl-reqs.md @@ -41,17 +41,16 @@ When writing or editing an `***implementation reqs***` section in a `.plain` fil - Unit testing run scripts should be linked here as a linked resource ## Encapsulation warning -- `requires` modules only receive functional specs from their dependencies — not implementation reqs +- `requires` modules only pass on functional specs and `exported_concepts` — never implementation reqs - If downstream modules need certain behavior to be visible, express it in functional specs, not here ## No duplication - Do not duplicate guidance already present in the file or its imports -- Check imported templates before adding a new req +- Check imported modules before adding a new req ## Concept references - Reference defined `:Concepts:` where they add clarity - All referenced concepts must already be defined in `***definitions***` -- Implementation reqs in non-leaf sections apply to all subsections ## Format diff --git a/forge/rules/import-modules.md b/forge/rules/import-modules.md index 56f22de..b4ab4ee 100644 --- a/forge/rules/import-modules.md +++ b/forge/rules/import-modules.md @@ -10,18 +10,16 @@ When creating or editing a `.plain` file that uses `import` or is intended to be - An import module contains **only** `***definitions***`, `***implementation reqs***`, and/or `***test reqs***` - It must **not** contain `***functional specs***` - It must **not** use `requires` in its frontmatter -- It must live in the **`template/`** directory -- It may optionally `import` other modules or templates for layered reuse +- It may optionally `import` other import modules for layered reuse ## What import does - `import` pulls in `***definitions***`, `***implementation reqs***`, and `***test reqs***` from the target module - It does **not** pull in `***functional specs***` -- The default import directory is `template/` — the `template/` prefix is not needed in import paths - Import paths omit the `.plain` extension ## Concept visibility -- All concepts defined in the imported module become available in the importing module -- Check for concept name collisions between imports and local definitions before adding +- All concepts defined in the imported module become available in the importing module — including concepts the imported module itself imports (layered imports are transitive) +- Check for concept name collisions between imports and local definitions before adding — a collision is rejected as a syntax error ("Concepts were defined multiple times") ## Format diff --git a/forge/rules/linked-resources.md b/forge/rules/linked-resources.md index 09362c8..fb5c562 100644 --- a/forge/rules/linked-resources.md +++ b/forge/rules/linked-resources.md @@ -13,14 +13,15 @@ A linked resource **must not** be any of the following: 1. **A folder / directory.** `[integrations](src/integrations/)`, `[host project](../host_project/)` are invalid — the renderer cannot ingest a directory. Pick the single most representative file inside (a `README.md`, an exemplar source file, a manifest) and link **that**. 2. **A URL / external location.** `[Stripe docs](https://stripe.com/docs/api)`, any `http://` / `https://` / `ftp://` / `git://` / `s3://` / `gs://` target. Linked resources are local-file only. If a URL's content is essential, fetch it once, save the response to a text file under `resources/`, and link the saved file. 3. **A binary file.** PNG, JPG, GIF, PDF, DOCX, XLSX, ZIP, MP3, MP4, compiled binaries (`.exe`, `.so`, `.class`, `.wasm`), and anything else that isn't human-readable text in its raw form. Binary content cannot be meaningfully consumed by the renderer — transcribe it into a text-based form first (a UI screenshot becomes a Markdown description, a PDF spec becomes a Markdown extract or the underlying JSON Schema / OpenAPI, an architecture diagram becomes a Mermaid block). -4. **Another `.plain` file.** `[Auth Module](auth.plain)` is invalid. To reference another module, use the `require` or `import` frontmatter directives. Do not link it as a markdown resource; the `plain-dry-run` will flag this as a syntax error. - - -## URLs and folder paths must not appear *anywhere* in `.plain` content -- The constraint is not only about markdown links — URLs and folder paths must not appear **anywhere** in `.plain` content (concept body prose, functional-spec text, implementation reqs, test reqs) -- The renderer cannot follow URLs or open folders; a URL in prose is a *ghost dependency* — it looks meaningful to a human reader but contributes nothing to code generation, and the spec silently drifts from reality -- **The only exception** is for URLs and paths that are *values the produced software itself uses at runtime* — the base URL an integration calls, a database connection path, a CLI argument default. Those are configuration values, not external references -- 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 +4. **Another `.plain` file.** `[Auth Module](auth.plain)` is invalid. To reference another module, use the `require` or `import` frontmatter directives. Do not link it as a markdown resource; the renderer will flag this as a syntax error. + +## Never use a URL or folder path as a *reference* in prose +- The hard constraint above is enforced only on markdown links. A bare URL or folder path in prose (concept body, functional-spec text, implementation reqs, test reqs) passes validation and is fed to the renderer verbatim as part of the spec text +- That is exactly why it is dangerous as a *reference*: the renderer cannot fetch the URL or open the folder, so the model sees only the address, not the content behind it. To a human reader the line looks grounded in a source; to the renderer it is a *ghost dependency*, and the spec silently drifts from whatever the URL actually says +- A URL or path in prose is fine when the *text itself* is the complete fact: + - **Runtime values** the produced software uses — the base URL an integration calls, a database connection path, a CLI argument default, an output directory layout + - **Provenance notes** for a snapshotted resource — recording the canonical documentation URL a `resources/docs/…` snapshot was fetched from (as the integration rules require) +- 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 (the address itself is the fact), 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 another directory within the project) diff --git a/forge/rules/module-structure.md b/forge/rules/module-structure.md new file mode 100644 index 0000000..84e159c --- /dev/null +++ b/forge/rules/module-structure.md @@ -0,0 +1,103 @@ +--- +description: File-level structure of .plain modules — frontmatter, sections, ordering, and section ownership +--- + +# Rules for `.plain` module structure + +When creating a new `.plain` file or reviewing the structure of a whole file, always follow these rules. Section *content* rules live in the per-section rule files linked below — this file covers only file-level structure. + +## File skeleton +- A `.plain` file is one **module**: optional YAML frontmatter between `---` markers, followed by sections marked with `***section name***` headers +- The frontmatter, when present, must be the first thing in the file +- Every content line inside a section must be a `- ` list item — see `bullet-continuation.md` + +## Frontmatter fields + +| Field | Purpose | Rule file | +|---|---|---| +| `description` | One-line summary of the module; recommended on every module | — | +| `import` | Pull definitions, implementation reqs, and test reqs from import modules | `import-modules.md` | +| `requires` | Attach the module to the build chain of another root module | `requires-modules.md` | +| `exported_concepts` | Declare which of this module's concepts are visible to modules that `require` it | `exported-concepts.md` | +| `required_concepts` | Declare concepts an import module expects the importing module to define | `required-concepts.md` | + +## Sections +- There are exactly four top-level section markers: `***definitions***`, `***implementation reqs***`, `***test reqs***`, and `***functional specs***` +- Each section appears **at most once** per file; all are optional (which ones are allowed depends on the module kind below) +- `***acceptance tests***` is **never a top-level section** — it appears only nested under a single functional spec (see `func-specs.md`) +- Write sections in canonical order: `***definitions***` → `***implementation reqs***` → `***test reqs***` → `***functional specs***` + - Definitions come first because every concept must be defined before it is referenced + - Functional specs come last because they are rendered incrementally and their nested acceptance tests close the file + +## Module kinds +- A **root module** lives at the repository root and carries behavior: it contains `***functional specs***` +- To be renderable, a root module needs at least one functional spec plus the implementation reqs to build it — its own or from an `import`ed module (implementation reqs are not inherited through `requires`). A frontmatter-only file is a valid scaffold, but it cannot be rendered until a functional spec is added +- A **requires module** is a root module that is used by another root module via `requires` directive (see `requires-modules.md`) +- An **import module** provides shared content that other modules pull in via `import`: it contains only `***definitions***`, `***implementation reqs***`, and/or `***test reqs***` — never `***functional specs***`, never `requires` (see `import-modules.md`) + +## Section ownership (where a fact lives) +- The renderer reads each kind of fact **only from its owning section** — a fact placed in the wrong section is silently ignored, not flagged +- Before writing any requirement, place it by content: + +| Content | Owning section | Rule file | +|---|---|---| +| Concepts (`:CamelCaseToken:`) | `***definitions***` | `definitions.md` | +| HOW the software is built — tech stack, architecture, coding standards — and **everything about `:UnitTests:`** | `***implementation reqs***` | `impl-reqs.md` | +| **Everything about `:ConformanceTests:`** — framework, run command, mocking and network policy | `***test reqs***` | `test-reqs.md` | +| WHAT the software does — observable, language-agnostic behavior | `***functional specs***` | `func-specs.md` | +| End-to-end workflow verification of one functional spec | nested `***acceptance tests***` | `func-specs.md` | + +## Comments +- Lines beginning with `>` are ignored when rendering + +```plain +> This is a comment in ***plain. +``` + +- Comments explain the specification to human authors — never use a comment to carry a requirement the renderer must implement + +## Liquid templates +- `.plain` files are processed with [Liquid](https://shopify.github.io/liquid/) before parsing — the full Liquid syntax (variables, filters, conditionals, loops) is available in any section +- Expansion is **textual** and happens before the specification is interpreted — unlike `import`, which includes another module semantically (see `import-modules.md`) + +```plain +{% assign max_tags = 3 %} + +***functional specs*** + +- A :Widget: can be created with up to {{ max_tags }} tags. +{% if max_tags > 1 %} +- A :Widget: can have its tags listed. +{% endif %} +``` + +## Format + +```plain +--- +description: Command-line task manager. +import: + - airplain +--- + +***definitions*** + +- :Task: is an activity tracked by the application. + - Name (required) + +- :TaskList: is the collection of all :Task: items. Initially empty. + +***implementation reqs*** + +- :Implementation: keeps :TaskList: in memory. + +***test reqs*** + +- :ConformanceTests: must not access the network. + +***functional specs*** + +- A :Task: can be added to the :TaskList:. + +- The :TaskList: is displayed. +``` diff --git a/forge/rules/required-concepts.md b/forge/rules/required-concepts.md index 844a398..4f68ec2 100644 --- a/forge/rules/required-concepts.md +++ b/forge/rules/required-concepts.md @@ -9,14 +9,14 @@ When adding or editing `required_concepts` in a `.plain` file's frontmatter, alw ## What required_concepts does - `required_concepts` declares concepts that any module importing this file **must** define - It creates a contract: the import module references these concepts but does not define them — the importing module is responsible for providing definitions -- This is used exclusively on import modules and templates +- This is used exclusively on import modules ## When to use it -- Use `required_concepts` when an import module or template (in `template/`) references concepts that vary per project or per importing module +- Use `required_concepts` when an import module references concepts that vary per project or per importing module - The import module can reference these concepts in its definitions, implementation reqs, or test reqs — but their actual definitions come from whoever imports the file ## Importing module must satisfy the contract -- Every concept listed in `required_concepts` must be defined in the importing module's own `***definitions***` section +- Every concept listed in `required_concepts` must be defined somewhere visible to the importing module — in its own `***definitions***` section or in another module it imports - If the importing module does not define a required concept, the spec is invalid - Check `required_concepts` of all imported modules before finalizing a module @@ -29,7 +29,7 @@ When adding or editing `required_concepts` in a `.plain` file's frontmatter, alw ```plain --- required_concepts: [":AppName:", ":AppConfig:"] -description: Template that requires AppName and AppConfig to be defined by the importer +description: Imported module that requires AppName and AppConfig to be defined by the importer --- ***definitions*** @@ -39,6 +39,6 @@ description: Template that requires AppName and AppConfig to be defined by the i - :MainFile: should load :AppConfig: on startup. ``` -In this example, `:AppName:` and `:AppConfig:` are referenced but not defined — the module that imports this template must define them. +In this example, `:AppName:` and `:AppConfig:` are referenced but not defined — the module that imports this import module must define them. List concepts as a YAML array with each concept in `:ConceptName:` notation. diff --git a/forge/rules/requires-modules.md b/forge/rules/requires-modules.md index dd5494d..f1ce700 100644 --- a/forge/rules/requires-modules.md +++ b/forge/rules/requires-modules.md @@ -4,7 +4,7 @@ description: Rules for creating and using requires modules in .plain files # Rules for requires modules -When creating or editing a `.plain` file that uses `requires`, always follow these rules: +When creating or editing a `.plain` file that uses `requires`, or one that is referenced in another `.plain` file's `requires` directive, always follow these rules: ## What requires does @@ -27,7 +27,7 @@ When creating or editing a `.plain` file that uses `requires`, always follow the - Because the required module's generated code is copied as the starting point and the renderer continues building on top of it with a single toolchain, two modules can only be linked with `requires` when they target the **same language, framework, and runtime** - A runtime / network dependency between systems is **not** a reason to use `requires` - Example of the mistake: a React frontend that talks to a Python/FastAPI backend over HTTP must **not** `requires: [backend]` — the stacks differ -- Model that pair as two independent root modules (each with its own `config.yaml` and test scripts) and express the contract through a shared API schema in `resources/` or shared concepts in an `import`ed template — never through `requires` +- Model that pair as two independent root modules (each with its own `config.yaml` and test scripts) and express the contract through a shared API schema in `resources/` or shared concepts in an `import`ed module — never through `requires` ## Building upon but not necessarily extension @@ -53,8 +53,7 @@ When creating or editing a `.plain` file that uses `requires`, always follow the - Modules that use `requires` live at the **repository root** — they are functional modules with specs - `requires` paths point to other root-level modules (e.g., `auth`, `messaging`) -- The default import directory is `template/` — the `template/` prefix is not needed in import paths (e.g., `airplain`) -- Never `require` a template — templates are for `import` only +- Never `require` an import module — import modules are for `import` only ## Format diff --git a/forge/rules/test-reqs.md b/forge/rules/test-reqs.md index 8e2bf7f..752e49b 100644 --- a/forge/rules/test-reqs.md +++ b/forge/rules/test-reqs.md @@ -28,15 +28,15 @@ When writing or editing a `***test reqs***` section in a `.plain` file, always f ## Test type reference -| Test type | Where to specify | Purpose | -|-----------|-----------------|---------| -| Unit tests | `***implementation reqs***` | Test individual functionalities in isolation | -| Conformance tests | `***test reqs***` | Verify implementation conforms to the full spec | -| Acceptance tests | `***acceptance tests***` under a functional spec | Verify a specific functional spec | +| Test type | Where to specify | Generated | Purpose | +|-----------|-----------------|-----------|---------| +| Unit tests | `***implementation reqs***` | Automatically | Test individual functionalities in isolation | +| Conformance tests | `***test reqs***` | Automatically | Verify each functionality's implementation against its functional spec | +| Acceptance tests | `***acceptance tests***` under a functional spec | Manually | Verify implementation of a functional spec through a specific end-to-end scenario | ## No duplication - Do not duplicate guidance already present in the file or its imports -- Check imported templates before adding a new test req +- Check imported modules before adding a new test req ## Concept references - Reference predefined concepts like `:ConformanceTests:` where appropriate @@ -50,6 +50,6 @@ When writing or editing a `***test reqs***` section in a `.plain` file, always f - :ConformanceTests: will be run using "pytest" command. - :ConformanceTests: must be implemented and executed - do not skip tests. - :ConformanceTests: should mock all external HTTP calls. -- :ConformanceTests: are preapred via the preapre script [test_scripts/prepare_environment_python.sh](test_scripts/prepare_environment_python.sh). +- :ConformanceTests: are prepared via the prepare script [test_scripts/prepare_environment_python.sh](test_scripts/prepare_environment_python.sh). - :ConformanceTests: are executed via the run script [test_scripts/run_conformance_tests_python.sh](test_scripts/run_conformance_tests_python.sh). ``` diff --git a/forge/skills/load-plain-reference/SKILL.md b/forge/skills/load-plain-reference/SKILL.md index 88659fb..e178e9d 100644 --- a/forge/skills/load-plain-reference/SKILL.md +++ b/forge/skills/load-plain-reference/SKILL.md @@ -27,6 +27,7 @@ All paths are relative to this `SKILL.md`. | Task or content | Rule files | |---|---| +| New module, file skeleton, section placement, or comments | `../../rules/module-structure.md` | | Definitions or concept usage | `../../rules/definitions.md` | | Functional specs or acceptance tests | `../../rules/func-specs.md` | | Implementation requirements or unit tests | `../../rules/impl-reqs.md` | @@ -61,7 +62,7 @@ Authoring rules remain authoritative if a reference appears to conflict with the | Need | Reference | |---|---| -| Project layout, source-of-truth model, templates, or comments | `references/project-model.md` | +| Project layout, source-of-truth model, or template inclusion | `references/project-model.md` | | Rendering order, generated artifacts, conformance workflow, or test scripts | `references/rendering-and-testing.md` | | `codeplain` path resolution or CLI options | Invoke `load-codeplain-reference` | diff --git a/forge/skills/load-plain-reference/references/project-model.md b/forge/skills/load-plain-reference/references/project-model.md index 87cf426..95e540b 100644 --- a/forge/skills/load-plain-reference/references/project-model.md +++ b/forge/skills/load-plain-reference/references/project-model.md @@ -35,14 +35,3 @@ In addition to frontmatter `import`, ***plain supports parameterized template in Parameters are key-value pairs. Templates access them with `{{ variable_name }}`. Only variable substitution is supported; conditionals, loops, and other Liquid features are not available. - -## Comments - -Lines beginning with `>` are ignored during rendering: - -```plain -> This is a comment in ***plain. -``` - -Comments explain the specification to human authors. Do not use comments to carry requirements the -renderer must implement.