diff --git a/forge/rules/definitions.md b/forge/rules/definitions.md index 8f77ae1..d9a81a4 100644 --- a/forge/rules/definitions.md +++ b/forge/rules/definitions.md @@ -60,8 +60,8 @@ Bad — circular: ``` ## Exported concepts are not transitive -- If module A exports a concept and module B `requires` A, module C `requires` B does **not** gain access to A's exports -- Shared concepts belong in a common import module +- If module A exports a concept and module B `requires` A, a module C with `requires: [B]` does **not** gain access to A's exports +- To use an ancestor's exports, list that ancestor in `requires` as well; concepts shared more broadly belong in a common import module ## Description quality - Descriptions must be clear, concise, and language-agnostic diff --git a/forge/rules/exported-concepts.md b/forge/rules/exported-concepts.md index d926c66..eb68371 100644 --- a/forge/rules/exported-concepts.md +++ b/forge/rules/exported-concepts.md @@ -10,6 +10,7 @@ When adding or editing `exported_concepts` in a `.plain` file's frontmatter, alw - `exported_concepts` declares which concepts from this module are visible to modules that `require` it - Concepts not listed in `exported_concepts` are internal to the module and invisible to downstream modules - Only modules that use `requires` receive exported concepts — `import` gives access to all definitions, not just exports +- An exported concept is visible with its full definition text ## When to use it - Use `exported_concepts` on any module that other modules will `require` @@ -21,8 +22,8 @@ When adding or editing `exported_concepts` in a `.plain` file's frontmatter, alw - Do not export concepts that are not defined in the module ## Exports are not transitive -- If module A exports `:Foo:` and module B `requires` A, module C `requires` B does **not** gain access to `:Foo:` -- If module C also needs `:Foo:`, it must either `require` A directly or get it through a common import module +- If module A exports `:Foo:` and module B `requires` A, a module C with `requires: [B]` does **not** gain access to `:Foo:` +- If module C also needs `:Foo:`, it should list A in its `requires` as well (`requires: [A, B]`) or get `:Foo:` through a common import module ## Format diff --git a/forge/rules/func-specs.md b/forge/rules/func-specs.md index f53dd3f..d7c5592 100644 --- a/forge/rules/func-specs.md +++ b/forge/rules/func-specs.md @@ -57,7 +57,7 @@ GOOD — statements of fact with concrete subjects: - Specs are rendered incrementally, top to bottom - The renderer has **no knowledge of future specs** — only previously rendered specs are in context - A new spec can reference behavior from earlier specs but cannot assume anything about specs that come after it -- Functional specs from `requires` modules are treated as previous requirements +- Functional specs from `requires` modules are treated as previous requirements — transitively, across the whole ancestor chain ## No conflicts - The new spec must not contradict any existing functional spec diff --git a/forge/rules/import-modules.md b/forge/rules/import-modules.md index 24a2fe5..56f22de 100644 --- a/forge/rules/import-modules.md +++ b/forge/rules/import-modules.md @@ -46,5 +46,5 @@ import: ## Combining import with requires - A module can use both `import` and `requires` together - `import` brings in shared definitions and reqs -- `requires` brings in the build dependency chain and functional specs +- `requires` attaches the module to the build chain — accumulated generated code and previous functional specs - An import module itself must **not** use `requires` diff --git a/forge/rules/requires-modules.md b/forge/rules/requires-modules.md index 7b9ebf3..dd5494d 100644 --- a/forge/rules/requires-modules.md +++ b/forge/rules/requires-modules.md @@ -7,33 +7,50 @@ description: Rules for creating and using requires modules in .plain files When creating or editing a `.plain` file that uses `requires`, always follow these rules: ## What requires does -- `requires` establishes a **build ordering** — the required module is built before the current one -- The required module's generated code (`plain_modules/`) is copied as the starting point -- The required module's `***functional specs***` become visible as **previous functional specs** — this property **is transitive** -- Only `exported_concepts` from the required module are available — not its full definitions — and this property **is not transitive** + +- `requires` attaches the current module to the project's **build chain**: the required module is built first, and its generated code is copied as the starting point for the current module +- The copied code is the required module's **accumulated** output — it already contains the code of all of that module's own ancestors +- The `***functional specs***` of the required module **and of all its ancestors** become visible as **previous functional specs** — this visibility **is transitive** down the whole chain +- Of a required module's definitions, only the concepts listed in its `exported_concepts` are visible; the module's other definitions stay internal. Each exported concept arrives with its full definition text +- Export visibility **is not transitive**: an ancestor's exports are visible only if that ancestor is itself listed in `requires` + +## Chain topology + +- All modules connected by `requires` form a single **tree** with a common root: code accumulates along each branch, and branches never reconverge +- **Diamond joins are prohibited.** A module must not require two modules from divergent branches — there is no way to merge two divergent codebases into one starting point +- When `requires` lists multiple modules, every entry must lie on **one root-to-tip ancestor path**: each listed module must be an ancestor of the deepest one +- The **deepest entry is the attachment point** — it alone determines the build order and the starting code. The other entries add nothing to ordering; they exist **only** to make those ancestors' `exported_concepts` visible +- Branching outward is fine: two modules may each require the same parent, creating independent branches (and therefore independent top modules) ## Tech stack must match (hard rule) + - 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` -## Build order, not necessarily dependency -- The current module does not need to extend or depend on the required module's code -- The two modules may be completely independent -- `requires` ensures the build order is correct for the project as a whole +## Building upon but not necessarily extension + +- The current module does not need to functionally extend the required module — it may address a completely different concern +- Even so, the two are never independent artifacts: the current module's output physically contains the required module's code, because both are stages of one accumulating codebase +- Use `requires` to place a module at the right point in that lineage, whether or not it builds on the required module's functionality ## Conflict prevention -- The current module's functional specs must not conflict with the required module's specs -- The required module's specs are treated as previous requirements — the renderer sees them as context -- Review the required module's functional specs before adding new ones -## No access to full definitions -- Only concepts listed in the required module's `exported_concepts` are available -- Other concepts from the required module are internal and invisible -- If you need shared definitions, use `import` for that — not `requires` +- The current module's functional specs must not conflict with the specs of **any module in its ancestor chain** — all of them are previous functional specs +- The renderer sees the whole chain's specs as prior requirements/context when rendering the current module +- Review the ancestor chain's functional specs before adding new ones + +## Exports are the only visible definitions + +- Only concepts listed in a required module's `exported_concepts` are available, and only for modules explicitly listed in `requires` +- Each exported concept is available with its full definition text +- Other concepts from required modules are internal and invisible +- To reference a concept exported by an ancestor deeper in the chain, list that ancestor in `requires` as well +- If you need shared definitions across branches or stacks, use `import` for that — not `requires` ## File locations + - 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`) @@ -47,11 +64,11 @@ requires: - auth import: - airplain -description: Module built after auth, importing shared definitions +description: Module built on top of auth, importing shared definitions --- ``` -A module can require multiple modules: +A module can list multiple required modules **only when they lie on one ancestor path**. Here `messaging` itself requires `auth`, so `auth` is an ancestor of `messaging`; the module attaches after `messaging` and lists `auth` only to see its exports: ```plain --- diff --git a/forge/skills/add-concept/SKILL.md b/forge/skills/add-concept/SKILL.md index 9591a41..240657f 100644 --- a/forge/skills/add-concept/SKILL.md +++ b/forge/skills/add-concept/SKILL.md @@ -36,7 +36,7 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule - CamelCase, starting with an uppercase letter - Valid characters: letters, digits, `+`, `-`, `.`, `_` - Must be globally unique across the spec and all its imports -- Exported concepts from `requires` modules are **not transitive** — if a concept needs to be shared across multiple `requires` modules, define it in a common import module instead +- Exported concepts from `requires` modules are **not transitive** — to reference an ancestor's export, list that ancestor in `requires`; if a concept is shared across branches, define it in a common import module instead ## Definition Format diff --git a/forge/skills/create-import-module/SKILL.md b/forge/skills/create-import-module/SKILL.md index 6d416cc..390d15b 100644 --- a/forge/skills/create-import-module/SKILL.md +++ b/forge/skills/create-import-module/SKILL.md @@ -89,9 +89,9 @@ import: | Pulls in definitions | Yes | No (only `exported_concepts`) | | Pulls in implementation reqs | Yes | No | | Pulls in test reqs | Yes | No | -| Pulls in functional specs | No | Yes (as previous requirements) | -| Copies generated code | No | Yes | -| Typical use | Templates, shared definitions | Build dependency chain | +| Pulls in functional specs | No | Yes (as previous requirements, transitively) | +| Copies generated code | No | Yes (accumulated ancestor chain) | +| Typical use | Templates, shared definitions | Attaching a module to the build chain | ## Validation Checklist diff --git a/forge/skills/create-requires-module/SKILL.md b/forge/skills/create-requires-module/SKILL.md index 2fdda6e..e806a4c 100644 --- a/forge/skills/create-requires-module/SKILL.md +++ b/forge/skills/create-requires-module/SKILL.md @@ -13,28 +13,29 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule ## What Requires Does -`requires` establishes a build ordering between modules. The required module is built **before** the current one. This does not necessarily mean the current module extends or depends on the required module's code — it may be completely independent. The `requires` relationship simply ensures the build order is correct. +`requires` attaches the current module to the project's build chain. The required module is built **before** the current one, and its generated code — the accumulated output of that module and all its ancestors — is copied as the starting point. The current module does not need to functionally extend the required module, but their outputs are never independent: the current module's code physically contains the required module's. When this module is rendered: - The required module's generated code (`plain_modules/`) is copied as the starting point. -- The required module's `***functional specs***` become visible as **previous functional specs**. -- Only `exported_concepts` from the required module are available (not its full definitions). +- The `***functional specs***` of the required module **and all its ancestors** become visible as **previous functional specs**. +- Of the required module's definitions, only the concepts in its `exported_concepts` are visible, each with its full definition text. Export visibility is **not** transitive — to see an ancestor's exports, list that ancestor in `requires` too. + +All modules connected by `requires` form a single tree with a common root. **Diamond joins are prohibited**: multiple `requires` entries must lie on one root-to-tip ancestor path. The deepest entry is the attachment point; the other entries exist only to make those ancestors' exports visible. Use `requires` for: -- Ensuring a module is built after another in the build chain - Building on top of an existing module's functionality -- Extending a base module with additional features +- Attaching a module at the right point in the build chain, even when it addresses a different concern If you only need shared definitions and reqs (no functional specs, no generated code), use `import` instead — see the `create-import-module` skill. ## Workflow -1. **Identify the dependency.** Determine which module this new module builds on. That module must already exist and be renderable. +1. **Identify the attachment point.** Determine which existing module this new module attaches after in the build chain. That module must already exist and be renderable. If listing additional modules, verify each is an ancestor of the attachment point — no diamond joins. 2. **Create the `.plain` file at the repository root** with YAML frontmatter containing the `requires` field. Modules with functional specs live at the root, not in `template/`. -3. **Review the required module's functional specs** — they will be treated as previous requirements. Your new functional specs must not conflict with them. -4. **Review the required module's `exported_concepts`** — only those concepts are available to reference from the required module. +3. **Review the functional specs of the whole ancestor chain** — they will be treated as previous requirements. Your new functional specs must not conflict with any of them. +4. **Review each listed module's `exported_concepts`** — only those concepts are available to reference; an ancestor's exports require listing that ancestor. 5. **Add module-specific content** — definitions, implementation reqs, test reqs, and functional specs unique to this module. -6. **Check for conflicts** between your new functional specs and the required module's specs. +6. **Check for conflicts** between your new functional specs and any spec in the ancestor chain. ## Format @@ -60,6 +61,8 @@ description: Extended module that builds on base_module 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). +Multiple `requires` entries are legal only when they lie on one ancestor path (e.g., `requires: [auth, messaging]` where `messaging` itself requires `auth`). The deepest entry (`messaging`) is the attachment point; `auth` is listed only to make its exports visible. + ## Exported Concepts The required module controls what concepts are visible via `exported_concepts`: @@ -71,17 +74,17 @@ exported_concepts: [":StorageClient:", ":BackupResult:"] --- ``` -Only `:StorageClient:` and `:BackupResult:` would be available to modules that `require` this one. All other concepts from the required module are internal. +Only `:StorageClient:` and `:BackupResult:` would be available to modules that `require` this one, each with its full definition text. All other concepts from the required module are internal. ## Chronological Ordering with Requires -Functional specs from `requires` modules are considered **previous functional specs**. This means: +Functional specs from the whole `requires` ancestor chain are considered **previous functional specs**. This means: - They are already rendered and their code exists. - Your new specs are rendered after them, with full awareness of what they defined. -- Your new specs must not conflict with the required module's specs. -- The renderer sees the required module's functional specs as context when rendering yours. +- Your new specs must not conflict with any spec in the ancestor chain. +- The renderer sees the chain's functional specs as context when rendering yours. -The current module may or may not be functionally related to the required module. In some cases `requires` simply enforces build order — the two modules may be independent pieces of the same project that need to be built in sequence. +The current module may or may not be functionally related to the required module — `requires` places it at the right point in the accumulating build chain either way. ## Import vs Requires @@ -90,16 +93,18 @@ The current module may or may not be functionally related to the required module | Pulls in definitions | Yes | No (only `exported_concepts`) | | Pulls in implementation reqs | Yes | No | | Pulls in test reqs | Yes | No | -| Pulls in functional specs | No | Yes (as previous requirements) | -| Copies generated code | No | Yes | -| Typical use | Templates, shared definitions | Build dependency chain | +| Pulls in functional specs | No | Yes (as previous requirements, transitively) | +| Copies generated code | No | Yes (accumulated ancestor chain) | +| Typical use | Templates, shared definitions | Attaching a module to the build chain | ## Validation Checklist - [ ] Module file is at the repository root (not in `template/`) - [ ] Required module exists and is renderable -- [ ] Required module's `exported_concepts` provide the concepts you need -- [ ] New functional specs do not conflict with the required module's specs +- [ ] All `requires` entries lie on one ancestor path — no diamond joins +- [ ] The deepest `requires` entry is the intended attachment point +- [ ] Every listed module's `exported_concepts` provide the concepts you need +- [ ] New functional specs do not conflict with any spec in the ancestor chain - [ ] Module has at least one functional spec and one implementation req - [ ] Both `requires` and `import` are used correctly (not mixed up) - [ ] YAML frontmatter is correctly formatted between `---` markers diff --git a/forge/skills/plain-healthcheck/SKILL.md b/forge/skills/plain-healthcheck/SKILL.md index 8cb5338..a4ba161 100644 --- a/forge/skills/plain-healthcheck/SKILL.md +++ b/forge/skills/plain-healthcheck/SKILL.md @@ -36,7 +36,7 @@ The skill is a **detect → fix → re-run** loop. It does not stop at the first ### Step 1 — Inventory the project -1. List every `.plain` file in the repo root (and any subdirectories that contain `.plain` files). Build the module graph from each file's YAML frontmatter (`requires`, `import`). +1. List every `.plain` file in the repo root (and any subdirectories that contain `.plain` files). Build the module graph from each file's YAML frontmatter (`requires`, `import`). Verify the `requires` graph forms a tree: every module's multiple `requires` entries must lie on one root-to-tip ancestor path. 2. Identify **top modules** — every module that is not `requires`-ed by any other module. A single-stack project has one top module; a multi-part project (e.g. backend + frontend) has one top module per part. 3. List every `config.yaml` in the repo (root and per-part directories such as `backend/`, `frontend/`). 4. List every script under `test_scripts/`. diff --git a/forge/skills/refactor-module/SKILL.md b/forge/skills/refactor-module/SKILL.md index 63d7f81..ab36fa6 100644 --- a/forge/skills/refactor-module/SKILL.md +++ b/forge/skills/refactor-module/SKILL.md @@ -125,7 +125,7 @@ For each subsequent module in the chain: After all new modules are created: - If the original module is being fully replaced (all specs moved out), delete it or rename it to avoid confusion. -- If the original module was required by other modules, update those modules to `require` the last module in the new chain instead (since it transitively includes all prior modules' specs and generated code). +- If the original module was required by other modules, update those modules to `require` the last module in the new chain instead — its accumulated code and previous functional specs cover the whole chain. Exported concepts are **not** transitive, though: if a downstream module references a concept now exported by an earlier module in the new chain, it must list that earlier module in its `requires` as well. ## Phase 4 — Verify @@ -145,7 +145,7 @@ Verify that the chronological order of all functional specs — when read across ### 4c. Concept availability check For each module, verify: -- [ ] Every `:Concept:` referenced in its specs is either defined locally, available via `import`, or available via `exported_concepts` from the `requires` chain +- [ ] Every `:Concept:` referenced in its specs is either defined locally, available via `import`, or available via `exported_concepts` from a module listed in `requires` - [ ] `exported_concepts` includes every concept that downstream modules need - [ ] No concept name collisions between modules @@ -193,5 +193,5 @@ Acceptance tests are nested under their parent functional spec. When moving a sp - [ ] Each module has at least one functional spec and one implementation req - [ ] `requires` chain is correctly ordered (base → leaf) - [ ] Original module removed or updated -- [ ] Downstream modules (if any) updated to require the correct module +- [ ] Downstream modules (if any) updated to require the chain tip, plus any earlier chain modules whose exported concepts they reference - [ ] User approved the final result