Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions forge/rules/bullet-continuation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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***
Expand Down
3 changes: 1 addition & 2 deletions forge/rules/definitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
```

Expand Down
6 changes: 4 additions & 2 deletions forge/rules/func-specs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions forge/rules/impl-reqs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 3 additions & 5 deletions forge/rules/import-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 9 additions & 8 deletions forge/rules/linked-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
103 changes: 103 additions & 0 deletions forge/rules/module-structure.md
Original file line number Diff line number Diff line change
@@ -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.
```
10 changes: 5 additions & 5 deletions forge/rules/required-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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***
Expand All @@ -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.
7 changes: 3 additions & 4 deletions forge/rules/requires-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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

Expand Down
Loading
Loading