From 49957178a052496328bc953103bf030dd22db2ca Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Thu, 15 Oct 2026 10:33:35 +0200 Subject: [PATCH 01/25] Add the semantic-ids rule Every functionality and every test scenario carries a semantic ID written as a # heading. The rule pins the syntax (lowercase letters, digits, hyphens), uniqueness (func-id per module, scenario-id per functionality), the : form, and how coding agents refer to and keep IDs. Refs ENG-190 --- forge/rules/semantic-ids.md | 54 +++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 forge/rules/semantic-ids.md diff --git a/forge/rules/semantic-ids.md b/forge/rules/semantic-ids.md new file mode 100644 index 0000000..ed68251 --- /dev/null +++ b/forge/rules/semantic-ids.md @@ -0,0 +1,54 @@ +--- +description: Syntax, uniqueness, and use of semantic func-ids and scenario-ids in .plain files +--- + +# Rules for semantic IDs + +Every functionality in `***functional specs***` and every scenario in a test plain file carries a semantic ID. The ID is the stable name that test plain files, render commands, render state, change detection, logs, and generated tests use for that functionality or scenario. Always follow these rules when writing or reading one. + +## Two kinds of ID + +| ID | Written as | Identifies | Unique within | +|---|---|---|---| +| func-id | `# ` heading in `***functional specs***` of a core spec | one functionality | its module | +| scenario-id | `# ` heading in `***conformance tests***` or `***acceptance tests***` of a test plain file | one scenario | its functionality, across every test plain file bound to that functionality | + +## Syntax +- Lowercase letters, digits, and hyphens only: `add-task`, `export-csv-2`, `ctrl-d-exit` +- The heading is `# ` (hash, one space) followed by the ID and nothing else +- Nothing else is checked: an ID needs no meaning, no prefix, and no number +- Prefer a short slug that names the behavior (`add-task`, not `fs-3` or `the-user-adds-a-task`), so render logs and test names can be read without the spec open + +## Uniqueness +- A func-id is unique in its module; two modules may use the same func-id +- A scenario-id is unique for its functionality after every test plain file bound to that functionality is merged; two functionalities may use the same scenario-id +- Before adding an ID, search the module (or the functionality's test plain files) for the same heading; a duplicate is a syntax error that `plain-parser check` reports + +## Qualified form +- Where the module is not clear from context, write a func-id as `:`, for example `task-manager:add-task` +- `` is the module name: the `.plain` file name without its extension +- Render ranges, render state, change detection, and logs use the qualified form; a test plain file binds with the frontmatter keys `module:` and `func_id:` instead (see `test-plain-files.md`) + +## Stability +- An ID is an identity, not a label: a functionality keeps its func-id for as long as it exists, even when its text changes +- To the renderer, renaming a func-id is deleting one functionality and adding another; rename only when the user asks, then rename `func_id:` in every test plain file bound to it and move the file to the new conventional path +- Never renumber or reorder IDs; the order of the headings in the file is the render order, the ID carries no order + +## How coding agents use IDs +- Name a functionality by its func-id (`:` when the module is not obvious) in questions to the user, in review snippets, in commit messages, and in render commands; never by position ("the third spec") +- Pick the func-id before writing the functional spec text, check that it is unique in the module, then write the heading and the body (see `func-specs.md`) +- A new functionality is finished only when its test plain file exists at `tests//.plain` with at least one conformance scenario (see `test-plain-files.md`) +- A generated conformance or acceptance test carries the scenario-id of the scenario it implements; when a test fails, the scenario-id in its name leads back to the scenario text +- Everything about one functionality is found through its func-id: the `# ` heading in the core spec, the test plain files whose frontmatter names it, and the generated tests labelled with its scenario-ids + +## Format + +```plain +***functional specs*** + +# add-task +A :User: can add a :Task:. Only valid :Task: items are added. + +# delete-task +A :User: can delete a :Task:. +``` From 42f15cb8f0d546a901e393edd3d9556c4cd4f6f3 Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Thu, 15 Oct 2026 10:33:35 +0200 Subject: [PATCH 02/25] Add the test-plain-files rule A test plain file holds the conformance and acceptance scenarios of one functionality, bound by the frontmatter keys module: and func_id:, by convention at tests//.plain. The renderer generates tests from these scenarios only. plain-forge writes scenarios as given/when/then and keeps at least one conformance scenario per functionality when conformance tests are enabled. Refs ENG-190 --- forge/rules/test-plain-files.md | 103 ++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 forge/rules/test-plain-files.md diff --git a/forge/rules/test-plain-files.md b/forge/rules/test-plain-files.md new file mode 100644 index 0000000..f8f0466 --- /dev/null +++ b/forge/rules/test-plain-files.md @@ -0,0 +1,103 @@ +--- +description: Syntax and authoring rules for test plain files, which hold the conformance and acceptance scenarios of one functionality +--- + +# Rules for test plain files + +A test plain file holds the test scenarios of one functionality. The renderer generates conformance and acceptance tests from these scenarios and from nothing else: it never invents a scenario. Scenarios are written by people or by plain-forge at authoring time and are reviewed like any other spec line. Always follow these rules when creating or editing one. + +## File skeleton +- A test plain file is a `.plain` file with YAML frontmatter and two sections, in this order: `***conformance tests***`, then `***acceptance tests***` +- Both sections are required; `***acceptance tests***` may be empty +- The frontmatter has exactly two keys, both required: + - `module:` the name of the module that holds the functionality (the core spec's file name without `.plain`) + - `func_id:` the func-id of the functionality (see `semantic-ids.md`) +- The pair `(module, func_id)` must name a functionality that exists; a pair with no match is an error +- A test plain file has no `***definitions***`, `***implementation reqs***`, `***test reqs***`, or `***functional specs***` +- Comment lines starting with `>` are allowed, as in a core spec + +## Location +- By convention a test plain file lives at `tests//.plain`, with `tests/` next to the core spec +- The location and the file name are convention only: the renderer discovers test plain files by reading the frontmatter of every `.plain` file under its test folders +- plain-forge always writes to the conventional path, one file per functionality +- More than one test plain file may bind to the same functionality; their scenarios are merged, and scenario-ids must be unique across the merged set. Hand-written files may use this; plain-forge keeps one file per functionality + +## Scenarios +- A scenario is a `# ` heading (see `semantic-ids.md`) followed by free text that says what is done and what result is expected +- A scenario body line may be prose, a `- ` bullet, or any other text, as long as it does not start with `# ` (that starts a new scenario) or with `***` +- A scenario may reference every concept visible to its module: its own definitions, its `imports`, and the `exported_concepts` of its `base` modules. It defines no concepts +- Scenarios are language-agnostic: they describe behavior through the observable interface (CLI, HTTP, files, UI) and never name a function, class, or file of the implementation +- A scenario tests the functionality as a black box; the generated test never imports the implementation's internals +- A scenario is a direct consequence of the functional spec: it does not contradict, narrow, or extend the behavior the spec states +- Each scenario states one concrete, checkable outcome; "works correctly" is not an outcome +- Scenario text follows `concise-specs.md`: no narration, no rationale, no step a reader would assume + +## The shape plain-forge writes +- plain-forge writes every scenario as `given:` / `when:` / `then:` lists; hand-written scenarios may use any shape +- `given:` the state before the action (running app, existing data, configuration), one `- ` item per fact +- `when:` the one action under test +- `then:` the observable results, one `- ` item per result +- Omit `given:` when there is no setup; never omit `when:` or `then:` +- Values that matter to the check are concrete (`named "Buy milk"`, `exits with status 0`), never descriptive (`a valid name`) + +## Conformance scenarios +- `***conformance tests***` holds the scenarios that gate the functionality: it is done only when every conformance scenario passes +- When conformance tests are enabled for the module (its `config.yaml` has a `conformance-tests-script`), every functionality has at least one conformance scenario; an empty section is an error +- Each conformance scenario exercises the functionality of this file alone, through the interface the functional spec names; it does not test other functionalities +- Cover the main path first, then each boundary and error case the functional spec states; stop when every stated behavior has one scenario +- Do not write the same scenario twice in different words; one scenario per distinct outcome + +## Acceptance scenarios +- `***acceptance tests***` holds end-to-end checks that may span several functionalities; the section may be empty +- An acceptance scenario starts from the functionality of this file and may use the behavior of functionalities rendered before it +- It is rendered after the conformance tests of its functionality pass +- Write one only for a workflow that no single conformance scenario shows; do not restate a conformance scenario here + +## Keeping test plain files in step with functional specs +- Every change to a functional spec is followed by a review of its test plain file: scenarios that no longer follow from the spec are removed or rewritten, and new behavior gets a scenario +- When a functionality is split, each new functionality gets its own test plain file; each scenario moves to the functionality it tests +- When a functionality is deleted, its test plain file is deleted +- When a functionality moves to another module, its test plain file moves to that module's folder and its `module:` key is updated +- When a func-id is renamed, `func_id:` is renamed in every test plain file bound to it and the file moves to the new conventional path + +## Relation to `***test reqs***` +- `***test reqs***` of the module say how `:ConformanceTests:` are built and run (framework, command, network policy); the scenarios say what they check +- A fact about the framework or the run command never goes into a scenario, and a scenario never goes into `***test reqs***` + +## Format + +```plain +--- +module: task-manager +func_id: add-task +--- + +***conformance tests*** + +# add-valid-task +given: + - :App: is running with an empty :TaskList: +when: + - :User: adds a :Task: named "Buy milk" +then: + - :TaskList: shows one :Task: named "Buy milk" + +# reject-short-name +given: + - :App: is running with an empty :TaskList: +when: + - :User: adds a :Task: named "Go" +then: + - :App: shows the error "Name must be at least 3 characters" + - :TaskList: stays empty + +***acceptance tests*** + +# add-then-complete +given: + - :App: is running with an empty :TaskList: +when: + - :User: adds a :Task: named "Buy milk" and marks it completed +then: + - :TaskList: shows one :Task: named "Buy milk", marked completed +``` From 6507b8644ad438745bb607a96b939c48105a5ad8 Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Thu, 15 Oct 2026 10:33:35 +0200 Subject: [PATCH 03/25] Write functional specs as # func-id headings without nested acceptance tests Each functionality is a # func-id heading followed by a statement line and optional - clarifications. The nested ***acceptance tests*** subsection is gone; scenarios live in test plain files. module-structure.md names the old syntax and routes to the migrate-plain-syntax skill. Every functional spec example in the rules uses the new shape. Refs ENG-190 --- forge/rules/bullet-continuation.md | 28 +-- forge/rules/concise-specs-examples.md | 167 +++++++++++------- .../concise-specs-integration-examples.md | 63 ++++--- forge/rules/concise-specs.md | 2 +- forge/rules/func-specs.md | 73 ++++---- forge/rules/impl-reqs.md | 2 +- forge/rules/linked-resources.md | 11 +- forge/rules/module-structure.md | 30 +++- forge/rules/test-reqs.md | 12 +- 9 files changed, 235 insertions(+), 153 deletions(-) diff --git a/forge/rules/bullet-continuation.md b/forge/rules/bullet-continuation.md index f55ce6b..ae9047c 100644 --- a/forge/rules/bullet-continuation.md +++ b/forge/rules/bullet-continuation.md @@ -4,7 +4,7 @@ description: Bullet and continuation syntax for every section in .plain files # Rules for bullets and continuations in `.plain` files -These rules apply to every section and to concept explanations. +These rules apply to every `- ` bullet in every section, to concept explanations, and to the bullets under the statement line of a functionality or inside a scenario of a test plain file. ## No line-length limit @@ -25,17 +25,21 @@ clarification of the statement above it. There is no third, typographic reason t 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 +- What the renderer does enforce is the top level of `***definitions***`, `***implementation reqs***`, + and `***test reqs***`: every statement there 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 +- `***functional specs***` has a different top level: each functionality is a `# ` heading, + its first body line is prose, and only the clarifications under it are `- ` bullets (see + `func-specs.md`). A scenario in a test plain file has the same shape (see `test-plain-files.md`) BAD — line broken for width (bare continuation): ```plain ***functional specs*** -- :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(), - which returns a list of :EventEnvelope: dicts conforming to the gateway contract. +# handle-stripe-webhook +:GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(), +which returns a list of :EventEnvelope: dicts conforming to the gateway contract. ``` GOOD — one valid list item: @@ -43,7 +47,8 @@ GOOD — one valid list item: ```plain ***functional specs*** -- :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(), which returns a list of :EventEnvelope: dicts conforming to the gateway contract. +# handle-stripe-webhook +:GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(), which returns a list of :EventEnvelope: dicts conforming to the gateway contract. ``` BEST — separate clarifications expressed as nested bullets: @@ -51,15 +56,16 @@ BEST — separate clarifications expressed as nested bullets: ```plain ***functional specs*** -- :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(). - - The method returns a list of :EventEnvelope: dicts. - - The dicts conform to the gateway's :EventEnvelope: contract. +# handle-stripe-webhook +:GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(). +- The method returns a list of :EventEnvelope: dicts. +- The dicts conform to the gateway's :EventEnvelope: contract. ``` ## Presenting `.plain` examples - Show every example under its owning section header -- Separate top-level list items with one blank line +- Separate top-level list items, functionalities, and scenarios with one blank line - Keep nested clarifications directly under their parent without a blank line - BAD, WRONG, `Before:`, and `Too complex:` examples may intentionally demonstrate invalid syntax diff --git a/forge/rules/concise-specs-examples.md b/forge/rules/concise-specs-examples.md index d6e833e..bc24813 100644 --- a/forge/rules/concise-specs-examples.md +++ b/forge/rules/concise-specs-examples.md @@ -19,11 +19,14 @@ BAD: ```plain ***functional specs*** -- In order to provide users with a robust and seamless experience, :TaskService: exposes a comprehensive endpoint for the creation of new :Task: items. +# create-task +In order to provide users with a robust and seamless experience, :TaskService: exposes a comprehensive endpoint for the creation of new :Task: items. -- The endpoint accepts a request and, provided that the incoming payload is valid and well-formed, proceeds to persist the :Task: appropriately. +# persist-task +The endpoint accepts a request and, provided that the incoming payload is valid and well-formed, proceeds to persist the :Task: appropriately. -- If the payload is not valid, an appropriate error is returned to the caller so that the user is informed of the problem. +# reject-invalid-payload +If the payload is not valid, an appropriate error is returned to the caller so that the user is informed of the problem. ``` GOOD: @@ -31,10 +34,11 @@ GOOD: ```plain ***functional specs*** -- A :User: can create a :Task: via `POST /tasks`. - - The request body is a :Task: without `id`. - - The response is `201` with the created :Task:. - - A request without a name returns `400`. +# create-task +A :User: can create a :Task: via `POST /tasks`. +- The request body is a :Task: without `id`. +- The response is `201` with the created :Task:. +- A request without a name returns `400`. ``` Three bullets became one, and the rewrite names the method, the path, the request shape, and both status codes — none of which the padded version pinned down. @@ -46,7 +50,8 @@ BAD: ```plain ***functional specs*** -- Because support staff frequently need to find customers quickly, and because scanning the full list is slow and error-prone, :CustomerDirectory: offers a search capability that greatly improves their daily workflow. +# search-customers +Because support staff frequently need to find customers quickly, and because scanning the full list is slow and error-prone, :CustomerDirectory: offers a search capability that greatly improves their daily workflow. ``` GOOD: @@ -54,7 +59,8 @@ GOOD: ```plain ***functional specs*** -- :CustomerDirectory: returns :Customer: items whose name or email contains a search term, case-insensitively. +# search-customers +:CustomerDirectory: returns :Customer: items whose name or email contains a search term, case-insensitively. ``` Why the feature exists changes nothing about what is built. What "search" matches does. @@ -86,11 +92,12 @@ BAD: ```plain ***functional specs*** -- :OrderApi: returns a list of :Order: items over HTTP. - - The list contains :Order: items. - - If there are no :Order: items, the list is empty. - - Each :Order: has an `id`, which is unique. - - The caller receives the response after the request is sent. +# list-orders +:OrderApi: returns a list of :Order: items over HTTP. +- The list contains :Order: items. +- If there are no :Order: items, the list is empty. +- Each :Order: has an `id`, which is unique. +- The caller receives the response after the request is sent. ``` GOOD: @@ -98,8 +105,9 @@ GOOD: ```plain ***functional specs*** -- :OrderApi: returns the :Order: items of the authenticated :User: via `GET /orders`, newest first. - - The response is `200` with a JSON array, empty when the :User: has no :Order: items. +# list-orders +:OrderApi: returns the :Order: items of the authenticated :User: via `GET /orders`, newest first. +- The response is `200` with a JSON array, empty when the :User: has no :Order: items. ``` Every cut line was already implied: a list of :Order: items contains :Order: items, an identifier identifies, and a response follows a request. What survives is what a reader could *not* have guessed — the ownership filter, the sort order, and the response shape. @@ -113,11 +121,14 @@ BAD: ```plain ***functional specs*** -- :OrderService: validates all incoming data before processing it. +# validate-input +:OrderService: validates all incoming data before processing it. -- :OrderService: handles errors and returns meaningful error messages. +# handle-errors +:OrderService: handles errors and returns meaningful error messages. -- :OrderService: saves the :Order: to storage so that it is not lost. +# save-order +:OrderService: saves the :Order: to storage so that it is not lost. ``` GOOD: @@ -125,7 +136,8 @@ GOOD: ```plain ***functional specs*** -- :OrderService: rejects an :Order: whose `total` does not equal the sum of its :OrderLine: amounts, with `422`. +# reject-wrong-total +:OrderService: rejects an :Order: whose `total` does not equal the sum of its :OrderLine: amounts, with `422`. ``` Validating input, reporting errors, and persisting on a save operation belong in the `***implementation reqs***` and a baseline is assumed. If there is a specific use case for a particular operation, it should be documented there, not in the `***functional specs***`. The one fact worth stating is *which* rule is enforced and what the caller sees when it fails. @@ -218,7 +230,8 @@ BAD: ```plain ***functional specs*** -- :ReportGenerator: is responsible for facilitating the utilization of a plurality of :DataSource: instances in order to initiate the generation of a :Report: prior to the performance of the validation of the resulting output. +# build-report +:ReportGenerator: is responsible for facilitating the utilization of a plurality of :DataSource: instances in order to initiate the generation of a :Report: prior to the performance of the validation of the resulting output. ``` GOOD: @@ -226,7 +239,8 @@ GOOD: ```plain ***functional specs*** -- :ReportGenerator: builds a :Report: from one or more :DataSource: items, then validates it. +# build-report +:ReportGenerator: builds a :Report: from one or more :DataSource: items, then validates it. ``` Same sequence, plain words. See the substitution table in `concise-specs.md`. @@ -238,10 +252,11 @@ BAD: ```plain ***functional specs*** -- A :User: can archive a :Project:. - - Archiving is an action the user performs on a project. - - The archive action is available to users who have access to the project. - - Once archived, the project is in the archived state. +# archive-project +A :User: can archive a :Project:. +- Archiving is an action the user performs on a project. +- The archive action is available to users who have access to the project. +- Once archived, the project is in the archived state. ``` GOOD: @@ -249,9 +264,10 @@ GOOD: ```plain ***functional specs*** -- A :User: can archive a :Project: they own. - - An archived :Project: is excluded from :ProjectList:. - - Archiving a :Project: that is already archived returns `409`. +# archive-project +A :User: can archive a :Project: they own. +- An archived :Project: is excluded from :ProjectList:. +- Archiving a :Project: that is already archived returns `409`. ``` A sub-bullet earns its line by removing an ambiguity. If it only says the parent again in more words, it is filler. @@ -263,9 +279,10 @@ BAD: ```plain ***functional specs*** -- :SessionStore: expires a :Session: 30 minutes after its last use. - - Sessions do not live forever; they time out. - - The timeout is 30 minutes. +# expire-session +:SessionStore: expires a :Session: 30 minutes after its last use. +- Sessions do not live forever; they time out. +- The timeout is 30 minutes. ``` GOOD: @@ -273,8 +290,9 @@ GOOD: ```plain ***functional specs*** -- :SessionStore: expires a :Session: 30 minutes after its last use. - - A request carrying an expired :Session: returns `401`. +# expire-session +:SessionStore: expires a :Session: 30 minutes after its last use. +- A request carrying an expired :Session: returns `401`. ``` ## Synonym drift @@ -284,11 +302,14 @@ BAD: ```plain ***functional specs*** -- A :User: can upload a :Document:. +# upload-document +A :User: can upload a :Document:. -- The system stores the submitted file in :DocumentStore:. +# store-document +The system stores the submitted file in :DocumentStore:. -- The uploaded asset is retrievable by its identifier. +# retrieve-document +The uploaded asset is retrievable by its identifier. ``` Three names — `:Document:`, "file", "asset" — read as three things. A reader cannot tell whether "asset" is a fourth concept, and the renderer cannot either. @@ -298,9 +319,10 @@ GOOD: ```plain ***functional specs*** -- A :User: can upload a :Document:. - - :DocumentStore: stores the :Document: and assigns it a :DocumentId:. - - A :User: can retrieve a :Document: by its :DocumentId:. +# upload-document +A :User: can upload a :Document:. +- :DocumentStore: stores the :Document: and assigns it a :DocumentId:. +- A :User: can retrieve a :Document: by its :DocumentId:. ``` ## Meta-commentary and scope disclaimers @@ -310,13 +332,17 @@ BAD: ```plain ***functional specs*** -- This spec is intentionally kept small so that it stays within the complexity limit. +# export-csv-size-note +This spec is intentionally kept small so that it stays within the complexity limit. -- As described above, :ExportService: writes a :CsvFile:. +# export-csv-reference +As described above, :ExportService: writes a :CsvFile:. -- No changes are made to the existing :ImportService: or to the user interface. +# export-csv-no-changes +No changes are made to the existing :ImportService: or to the user interface. -- Future specs will extend this behavior with additional formats. +# export-csv-future +Future specs will extend this behavior with additional formats. ``` GOOD: @@ -324,9 +350,10 @@ GOOD: ```plain ***functional specs*** -- :ExportService: writes the :Order: items of a :BillingPeriod: to a :CsvFile:. - - The header row is `id,customer,total,currency`. - - An empty :BillingPeriod: produces a :CsvFile: with only the header row. +# export-csv +:ExportService: writes the :Order: items of a :BillingPeriod: to a :CsvFile:. +- The header row is `id,customer,total,currency`. +- An empty :BillingPeriod: produces a :CsvFile: with only the header row. ``` A spec describes the software, not itself. What the spec does *not* change is everything it does not mention, and what later specs will add belongs to those specs. @@ -338,13 +365,14 @@ BAD: ```plain ***functional specs*** -- :PricingEngine: applies discounts. - The discount matrix is as follows: - | Tier | Volume | Discount | - |---|---|---| - | Bronze | 0-99 | 0% | - | Silver | 100-499 | 5% | - | Gold | 500+ | 12% | +# apply-discounts +:PricingEngine: applies discounts. +The discount matrix is as follows: +| Tier | Volume | Discount | +|---|---|---| +| Bronze | 0-99 | 0% | +| Silver | 100-499 | 5% | +| Gold | 500+ | 12% | ``` GOOD: @@ -352,8 +380,9 @@ GOOD: ```plain ***functional specs*** -- :PricingEngine: applies the :DiscountTier: matrix in [resources/discount-tiers.json](resources/discount-tiers.json) to an :Order: total. - - An :Order: below the lowest tier threshold gets no discount. +# apply-discounts +:PricingEngine: applies the :DiscountTier: matrix in [resources/discount-tiers.json](resources/discount-tiers.json) to an :Order: total. +- An :Order: below the lowest tier threshold gets no discount. ``` A section holds `- ` list items. Matrices, schemas, and payloads are external artifacts and are linked from `resources/` — see `linked-resources.md`. @@ -373,7 +402,8 @@ BAD — everything in one place: ***functional specs*** -- :ImageResizer:, implemented as a Python class using Pillow with a thread pool of 4 workers, resizes an uploaded :Image: to a thumbnail, where a thumbnail is defined as a JPEG at most 256 pixels on its longest edge. +# resize-image +:ImageResizer:, implemented as a Python class using Pillow with a thread pool of 4 workers, resizes an uploaded :Image: to a thumbnail, where a thumbnail is defined as a JPEG at most 256 pixels on its longest edge. ``` GOOD — each fact in the section that owns it: @@ -395,9 +425,10 @@ GOOD — each fact in the section that owns it: ***functional specs*** -- :ImageResizer: produces a :Thumbnail: from an :Image:. - - An :Image: already within the :Thumbnail: bounds is returned unchanged. - - An :Image: that is not a supported format raises :UnsupportedImageError:. +# resize-image +:ImageResizer: produces a :Thumbnail: from an :Image:. +- An :Image: already within the :Thumbnail: bounds is returned unchanged. +- An :Image: that is not a supported format raises :UnsupportedImageError:. ``` Each section now answers one question, and the definition, the mechanism, and the behavior are each one line to check instead of clauses buried in a paragraph about resizing. Use the split whenever a bullet answers more than one of *what is it*, *what does it do*, *how is it built*, and *how is it tested* — see `module-structure.md` for section ownership and canonical section order. @@ -406,14 +437,15 @@ Use `resolve-section-ownership` to perform the split. It is not the same as spli The most common single case of this is a functional spec that states a mechanism rather than a result — a retry schedule, a cache, a token refresh. `impl-reqs.md` owns HOW; a functional spec keeps what a caller observes. `concise-specs-integration-examples.md` works through several. -## Narrated acceptance tests +## Narrated scenarios BAD: ```plain ***acceptance tests*** -- The tester should first open the application and log in with a valid account, then navigate to the projects area where they will be able to see the list of projects, and then they should create a new project by filling in the form, after which they should verify that everything worked as expected and the project shows up correctly. +# create-project +The tester should first open the application and log in with a valid account, then navigate to the projects area where they will be able to see the list of projects, and then they should create a new project by filling in the form, after which they should verify that everything worked as expected and the project shows up correctly. ``` GOOD: @@ -421,10 +453,13 @@ GOOD: ```plain ***acceptance tests*** -- A :User: creates a :Project: and finds it in :ProjectList:. - - Sign in as a :User: with no :Project: items. - - Create a :Project: named `Apollo`. - - :ProjectList: contains exactly one :Project:, named `Apollo`. +# create-project +given: + - a :User: is signed in with no :Project: items +when: + - the :User: creates a :Project: named `Apollo` +then: + - :ProjectList: contains exactly one :Project:, named `Apollo` ``` One narrated paragraph becomes an ordered set of checkable steps, and "everything worked as diff --git a/forge/rules/concise-specs-integration-examples.md b/forge/rules/concise-specs-integration-examples.md index d21d5ca..cda9e2f 100644 --- a/forge/rules/concise-specs-integration-examples.md +++ b/forge/rules/concise-specs-integration-examples.md @@ -31,11 +31,14 @@ BAD: ***functional specs*** -- :ProviderClient: retries the request when :ProviderApi: returns 408, 425, 429, 500, 502, 503, or 504, and also on connection errors, DNS failures, socket timeouts, TLS handshake failures, and connection resets. +# retry-transient-errors +:ProviderClient: retries the request when :ProviderApi: returns 408, 425, 429, 500, 502, 503, or 504, and also on connection errors, DNS failures, socket timeouts, TLS handshake failures, and connection resets. -- :ProviderClient: does not retry on 400, 401, 403, 404, 405, 409, 410, 415, or 422, and raises immediately. +# no-retry-permanent-errors +:ProviderClient: does not retry on 400, 401, 403, 404, 405, 409, 410, 415, or 422, and raises immediately. -- On 429, :ProviderClient: waits and tries again, unless the retry budget is exhausted. +# retry-on-429 +On 429, :ProviderClient: waits and tries again, unless the retry budget is exhausted. ``` GOOD: @@ -91,13 +94,17 @@ BAD: ***functional specs*** -- :ProviderApi: returns `200` with a JSON body when a request succeeds. +# provider-success-response +:ProviderApi: returns `200` with a JSON body when a request succeeds. -- :ProviderApi: returns `404` when the requested :Customer: does not exist. +# provider-not-found +:ProviderApi: returns `404` when the requested :Customer: does not exist. -- :ProviderApi: rate-limits clients and includes the remaining quota in the response headers. +# provider-rate-limit +:ProviderApi: rate-limits clients and includes the remaining quota in the response headers. -- :ProviderApi: requires the `Authorization` header on every request. +# provider-auth-header +:ProviderApi: requires the `Authorization` header on every request. ``` GOOD: @@ -123,7 +130,8 @@ GOOD: ***functional specs*** -- :ProviderClient: fetches a :Customer: from :ProviderApi: by :CustomerId:. +# fetch-customer +:ProviderClient: fetches a :Customer: from :ProviderApi: by :CustomerId:. ``` Every BAD bullet is true, and none of them is deleted — they are moved. The success shape, the `404`, and the `Authorization` requirement are what an OpenAPI file exists to state; the rate-limit model has its own conventional files. Provider behavior with no machine-readable home goes in `resources/-notes.md`. `integrations.md` carries the full artifact table. @@ -147,19 +155,23 @@ BAD — four specs, one operation: ***functional specs*** -- A call to `fetch` fails before any request to :ProviderApi: is sent when `PROVIDER_API_KEY` is unset or empty. - - The failure names `PROVIDER_API_KEY`. +# fetch-requires-api-key +A call to `fetch` fails before any request to :ProviderApi: is sent when `PROVIDER_API_KEY` is unset or empty. +- The failure names `PROVIDER_API_KEY`. -- A call to `fetch` reads the field catalogue from :ProviderApi: once, before requesting any :Record:. - - The catalogue yields the name of every field the account defines. - - A call to `fetch` whose catalogue read fails requests no :Record: items. +# fetch-reads-catalogue +A call to `fetch` reads the field catalogue from :ProviderApi: once, before requesting any :Record:. +- The catalogue yields the name of every field the account defines. +- A call to `fetch` whose catalogue read fails requests no :Record: items. -- A request for :Record: items from :ProviderApi: names every field in the catalogue and asks for at most 100 records. - - Archived :Record: items are not requested. +# fetch-requests-records +A request for :Record: items from :ProviderApi: names every field in the catalogue and asks for at most 100 records. +- Archived :Record: items are not requested. -- A call to `fetch` requests pages until :ProviderApi: returns a response carrying no next-page cursor. - - Each page after the first is requested with the cursor from the previous response. - - The :Record: items of every page are carried forward together. +# fetch-pages-records +A call to `fetch` requests pages until :ProviderApi: returns a response carrying no next-page cursor. +- Each page after the first is requested with the cursor from the previous response. +- The :Record: items of every page are carried forward together. ``` GOOD — one spec, with the result stated first: @@ -175,10 +187,11 @@ GOOD — one spec, with the result stated first: ***functional specs*** -- :RecordSource: `fetch()` returns every non-archived :Record: with every field :ProviderApi: defines, paging under [resources/pagination.yaml](resources/pagination.yaml). - - :RecordSource: reads the field catalogue once per `fetch()`. - - `fetch()` fails before any request when `PROVIDER_API_KEY` is unset or empty, naming it. - - A failed catalogue read fails `fetch()` before any :Record: is requested. +# fetch-records +:RecordSource: `fetch()` returns every non-archived :Record: with every field :ProviderApi: defines, paging under [resources/pagination.yaml](resources/pagination.yaml). +- :RecordSource: reads the field catalogue once per `fetch()`. +- `fetch()` fails before any request when `PROVIDER_API_KEY` is unset or empty, naming it. +- A failed catalogue read fails `fetch()` before any :Record: is requested. ``` Merging is not in tension with the complexity limit. Split a spec when it implies more than 200 changed lines of code (`analyze-if-func-spec-too-complex`, then `break-down-func-spec`); merge when several specs implement one operation that fits inside that budget. Fragment count is not a measure of size. @@ -190,9 +203,11 @@ BAD: ```plain ***functional specs*** -- :ProviderAuth: first reads the client id and the client secret, then builds a form-encoded body containing `grant_type=client_credentials`, the client id, and the client secret, then posts that body to the token endpoint, then parses the JSON response, then extracts the `access_token` and the `expires_in` fields, then stores the token in memory together with the moment it expires, and then attaches it to subsequent requests as a bearer token. +# obtain-token +:ProviderAuth: first reads the client id and the client secret, then builds a form-encoded body containing `grant_type=client_credentials`, the client id, and the client secret, then posts that body to the token endpoint, then parses the JSON response, then extracts the `access_token` and the `expires_in` fields, then stores the token in memory together with the moment it expires, and then attaches it to subsequent requests as a bearer token. -- Before each request, :ProviderAuth: compares the current time to the stored expiry and, if the token has expired or is about to expire, repeats the whole procedure described above to obtain a new token. +# refresh-token +Before each request, :ProviderAuth: compares the current time to the stored expiry and, if the token has expired or is about to expire, repeats the whole procedure described above to obtain a new token. ``` GOOD: diff --git a/forge/rules/concise-specs.md b/forge/rules/concise-specs.md index 1b5d8ed..be6aa79 100644 --- a/forge/rules/concise-specs.md +++ b/forge/rules/concise-specs.md @@ -4,7 +4,7 @@ description: Conciseness and plain-language rules that keep .plain specs reviewa # Rules for keeping `.plain` specs concise -These rules apply to every section, to concept explanations, and to acceptance tests. `concise-specs-examples.md` holds a worked BAD to GOOD pair for each failure mode named here, and `concise-specs-integration-examples.md` covers the failure modes specific to integration specs. +These rules apply to every section, to concept explanations, and to scenarios in test plain files. `concise-specs-examples.md` holds a worked BAD to GOOD pair for each failure mode named here, and `concise-specs-integration-examples.md` covers the failure modes specific to integration specs. ## Why this matters diff --git a/forge/rules/func-specs.md b/forge/rules/func-specs.md index b9a8a24..d833c8e 100644 --- a/forge/rules/func-specs.md +++ b/forge/rules/func-specs.md @@ -1,11 +1,19 @@ --- -description: Rules for writing ***functional specs*** and ***acceptance tests*** in .plain files +description: Rules for writing ***functional specs*** in .plain files --- # Rules for writing `***functional specs***` When writing or editing a `***functional specs***` section in a `.plain` file, always follow these rules: +## Shape +- Each functional spec, called a functionality, is a `# ` heading followed by its body; the func-id follows `semantic-ids.md` +- The first body line is the statement of the behavior, written as prose without a leading `- ` +- Clarifications follow the statement as `- ` bullets, one fact per bullet (see `bullet-continuation.md`) +- No body line starts with `# ` (that starts the next functionality) or with `***` +- One blank line separates functionalities +- The test scenarios of a functionality live in its test plain file, never in this section (see `test-plain-files.md`) + ## 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`) @@ -18,11 +26,14 @@ BAD — imperative commands, or the vague "The system": ```plain ***functional specs*** -- Implement the entry point for :App:. +# entry-point +Implement the entry point for :App:. -- The system shows :TaskList:. +# show-task-list +The system shows :TaskList:. -- Add :Task:. +# add-task +Add :Task:. ``` GOOD — statements of fact with concrete subjects: @@ -30,11 +41,14 @@ GOOD — statements of fact with concrete subjects: ```plain ***functional specs*** -- :App: has an entry point. +# entry-point +:App: has an entry point. -- The :TaskList: is displayed. +# show-task-list +The :TaskList: is displayed. -- A :User: can add a :Task:. Only valid :Task: items are added. +# add-task +A :User: can add a :Task:. Only valid :Task: items are added. ``` ## Conciseness @@ -58,13 +72,13 @@ GOOD — statements of fact with concrete subjects: - The first spec must still imply a testable outcome; setup alone is not a functional spec - Prefer one minimal entry point or public operation with a deterministic result that can be invoked and verified - Defer persistence, integrations, multiple interfaces, branching workflows, and cross-cutting behavior until later specs can build on the generated baseline -- This extra bootstrap cost applies when no previous implementation baseline exists; a module that continues from a `requires` module may already inherit the necessary scaffold +- This extra bootstrap cost applies when no previous implementation baseline exists; a module that continues from a `base` module may already inherit the necessary scaffold ## Chronological ordering - 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 — transitively, across the whole ancestor chain +- Functional specs from `base` modules are treated as previous requirements — transitively, across the whole ancestor chain ## No conflicts - The new spec must not contradict any existing functional spec @@ -80,17 +94,17 @@ GOOD — statements of fact with concrete subjects: ## Disambiguation - 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 lines of code +- If the statement line is not enough to fully disambiguate the behavior, add **`- ` bullets** under it to add detail +- The bullets clarify the statement — they do not introduce separate functionality +- Even with the bullets, the spec must still imply ≤ 200 lines of code ## Testability - Every functional spec must describe behavior that can be verified through an observable interface - State the triggering input or action and the deterministic outcome a test can observe - Include relevant outputs, state changes, errors, or externally visible side effects - Do not rely on generated implementation details as the only way to verify the behavior -- If a black-box conformance test cannot be derived from the spec, make the behavior more explicit -- Testability does not require an acceptance test; acceptance tests are reserved for full workflows +- If a black-box conformance scenario cannot be written from the spec, make the behavior more explicit +- When conformance tests are enabled, every functionality has a test plain file with at least one conformance scenario (see `test-plain-files.md`); write it in the same step as the functional spec ## Deterministic interface - Specs must be detailed enough that a developer can use the built software without reading the generated code @@ -99,31 +113,28 @@ GOOD — statements of fact with concrete subjects: ## Encapsulation - Functionality must be self-contained in the spec text -- `requires` modules only pass on functional specs and `exported_concepts` — never implementation reqs, so do not rely on implementation reqs to convey behavior +- `base` 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 -- Nest `***acceptance tests***` under a functional spec when verification criteria are needed -- Each acceptance test must be a **full workflow test** — a specific scenario that exercises the functional spec end-to-end, not a unit-level check of a single field or condition -- Do not restate the obvious behavior from the functional spec — simple, direct verifications are already auto-generated as conformance tests. Acceptance tests must go beyond that: multi-step workflows, interactions between concepts, edge-case scenarios, or end-to-end sequences that prove the feature works in a realistic context -- Each acceptance test must be a direct logical consequence of the parent spec — it illustrates, not extends -- Acceptance tests must describe concrete, verifiable outcomes — not vague qualities -- Acceptance tests must not contradict, narrow, or extend beyond the parent spec +## Test scenarios live in test plain files +- A core spec never holds a `***acceptance tests***` section, nested or top-level; that is the old syntax (see `module-structure.md`) +- Conformance and acceptance scenarios for a functionality go into `tests//.plain`, written with `add-test-scenarios` (see `test-plain-files.md`) +- A functional spec and its test plain file change together: a spec edit without a scenario review is not finished ## Format ```plain ***functional specs*** -- :App: has an entry point. - -- A :User: can add a :Task:. Only valid :Task: items are added. +# entry-point +:App: has an entry point. -- 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:. +# add-task +A :User: can add a :Task:. Only valid :Task: items are added. - ***acceptance tests*** - - Sending a :Message: to a :Conversation: with three participants should make the message visible to all three. +# send-message +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/impl-reqs.md b/forge/rules/impl-reqs.md index 30ce7c1..e0c422c 100644 --- a/forge/rules/impl-reqs.md +++ b/forge/rules/impl-reqs.md @@ -32,7 +32,7 @@ When writing or editing an `***implementation reqs***` section in a `.plain` fil - Behavior and features → `***functional specs***` - Concept definitions → `***definitions***` - **Conformance-test guidance** → `***test reqs***` -- **Acceptance-test scenarios** → `***acceptance tests***` nested under the relevant functional spec +- **Test scenarios**, conformance and acceptance → the functionality's test plain file (see `test-plain-files.md`) ## Unit tests vs conformance tests (common mistake) - Unit-test guidance (`:UnitTests:` framework, structure, mocking) goes **here**, not in `***test reqs***` diff --git a/forge/rules/linked-resources.md b/forge/rules/linked-resources.md index 624a612..01022ac 100644 --- a/forge/rules/linked-resources.md +++ b/forge/rules/linked-resources.md @@ -38,8 +38,9 @@ A linked resource **must not** be any of the following: ***functional specs*** -- A :User: can add a :Task: by POSTing :TaskCreateRequest: to the `POST /tasks` endpoint of :TasksAPI:. - - The endpoint responds per :TasksAPI:. +# add-task +A :User: can add a :Task: by POSTing :TaskCreateRequest: to the `POST /tasks` endpoint of :TasksAPI:. +- The endpoint responds per :TasksAPI:. ``` ## Each linked resource is referenced from exactly one place @@ -55,9 +56,11 @@ A linked resource **must not** be any of the following: ***functional specs*** -- A :User: can add a :Task: using :TaskModalSpec:. +# add-task +A :User: can add a :Task: using :TaskModalSpec:. -- A :User: can edit a :Task: using :TaskModalSpec:. +# edit-task +A :User: can edit a :Task: using :TaskModalSpec:. ``` ## File location and path resolution diff --git a/forge/rules/module-structure.md b/forge/rules/module-structure.md index 27a30de..83daaaa 100644 --- a/forge/rules/module-structure.md +++ b/forge/rules/module-structure.md @@ -9,7 +9,8 @@ When creating a new `.plain` file or reviewing the structure of a whole file, al ## 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` +- Every content line inside `***definitions***`, `***implementation reqs***`, and `***test reqs***` is a `- ` list item — see `bullet-continuation.md` +- Every functionality in `***functional specs***` is a `# ` heading followed by its body — see `func-specs.md` and `semantic-ids.md` - Every line must be as short and plain as its content allows — see `concise-specs.md` ## Frontmatter fields @@ -23,18 +24,19 @@ When creating a new `.plain` file or reviewing the structure of a whole file, al | `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***` +- A core spec has 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`) +- `***conformance tests***` and `***acceptance tests***` never appear in a core spec, not even nested under a functional spec; they are the two sections of a test plain file (see `test-plain-files.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 + - Functional specs come last because they are rendered incrementally ## 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`) +- A **test plain file** holds the scenarios of one functionality of a root module; its frontmatter `module:` and `func_id:` bind it to that functionality, it lives under `tests/`, and it has no section of a core spec (see `test-plain-files.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 @@ -47,7 +49,7 @@ When creating a new `.plain` file or reviewing the structure of a whole file, al | 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` | +| Test scenarios of one functionality, conformance and acceptance | the test plain file `tests//.plain` | `test-plain-files.md` | ## Comments - Lines beginning with `>` are ignored when rendering @@ -67,9 +69,12 @@ When creating a new `.plain` file or reviewing the structure of a whole file, al ***functional specs*** -- A :Widget: can be created with up to {{ max_tags }} tags. +# create-widget +A :Widget: can be created with up to {{ max_tags }} tags. {% if max_tags > 1 %} -- A :Widget: can have its tags listed. + +# list-widget-tags +A :Widget: can have its tags listed. {% endif %} ``` @@ -99,7 +104,14 @@ import: ***functional specs*** -- A :Task: can be added to the :TaskList:. +# add-task +A :Task: can be added to the :TaskList:. -- The :TaskList: is displayed. +# show-task-list +The :TaskList: is displayed. ``` + +## Old syntax +- A core spec is in the old syntax when any of these holds: a top-level `- ` bullet in `***functional specs***`, a `***acceptance tests***` section anywhere in the file, or a `requires:` or `import:` key in the frontmatter +- The old syntax is not supported: the renderer rejects it, and no skill edits a file written in it +- When a skill meets the old syntax, it stops, tells the user which file and which marker it found, and points to the `migrate-plain-syntax` skill, which converts the whole project in one pass diff --git a/forge/rules/test-reqs.md b/forge/rules/test-reqs.md index 899c1b8..570563b 100644 --- a/forge/rules/test-reqs.md +++ b/forge/rules/test-reqs.md @@ -9,7 +9,7 @@ When writing or editing a `***test reqs***` section in a `.plain` file, always f ## Conformance tests only - Test reqs specify how **conformance tests** should be written and run - Unit test guidance goes in `***implementation reqs***`, not here -- Acceptance tests are nested under individual functional specs, not here +- Test scenarios, conformance and acceptance, live in test plain files, not here — see `test-plain-files.md` - Behavioral requirements go in `***functional specs***`, not here ## `:ConformanceTests:` lives here (hard rule) @@ -28,11 +28,11 @@ When writing or editing a `***test reqs***` section in a `.plain` file, always f ## Test type reference -| 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 | +| Test type | Where to specify | Generated from | Purpose | +|-----------|-----------------|----------------|---------| +| Unit tests | `***implementation reqs***` | the implementation, by the renderer | Test individual functionalities in isolation | +| Conformance tests | how: `***test reqs***`; what: `***conformance tests***` of the functionality's test plain file | the conformance scenarios, one test per scenario-id | Verify one functionality against its conformance scenarios | +| Acceptance tests | how: `***test reqs***`; what: `***acceptance tests***` of the functionality's test plain file | the acceptance scenarios, one test per scenario-id | Verify an end-to-end workflow that may span functionalities | ## No duplication - Do not duplicate guidance already present in the file or its imports From 6f7e252df37704d2d5e91f9ffef66e284ab979d1 Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Thu, 15 Oct 2026 10:35:58 +0200 Subject: [PATCH 04/25] Rename requires and import to base and imports in the rules The frontmatter directives are now base: and imports: (ENG-182). The module kind that another module builds on is a base module; requires-modules.md becomes base-modules.md. Import modules keep their name and their rule file. Refs ENG-190, ENG-182 --- forge/rules/base-modules.md | 81 +++++++++++++++++++++++++++++ forge/rules/concise-specs.md | 6 +-- forge/rules/definitions.md | 8 +-- forge/rules/exported-concepts.md | 12 ++--- forge/rules/impl-reqs.md | 2 +- forge/rules/import-modules.md | 24 ++++----- forge/rules/integration-embedded.md | 4 +- forge/rules/integrations.md | 2 +- forge/rules/linked-resources.md | 4 +- forge/rules/module-structure.md | 16 +++--- forge/rules/requires-modules.md | 80 ---------------------------- 11 files changed, 120 insertions(+), 119 deletions(-) create mode 100644 forge/rules/base-modules.md delete mode 100644 forge/rules/requires-modules.md diff --git a/forge/rules/base-modules.md b/forge/rules/base-modules.md new file mode 100644 index 0000000..2b656e7 --- /dev/null +++ b/forge/rules/base-modules.md @@ -0,0 +1,81 @@ +--- +description: Rules for creating and using base modules in .plain files +--- + +# Rules for base modules + +When creating or editing a `.plain` file that uses `base`, or one that is listed in another `.plain` file's `base` directive, always follow these rules: + +## What base does + +- `base` attaches the current module to the project's **build chain**: the base module is built first, and its generated code is copied as the starting point for the current module +- The copied code is the base module's **accumulated** output — it already contains the code of all of that module's own ancestors +- The `***functional specs***` of the base module **and of all its ancestors** become visible as **previous functional specs** — this visibility **is transitive** down the whole chain +- Of a base 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 under `base` +- The test plain files of a base module stay with that module; the current module never lists scenarios for a base module's functionality (see `test-plain-files.md`) + +## Chain topology + +- All modules connected by `base` 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 list two modules from divergent branches — there is no way to merge two divergent codebases into one starting point +- When `base` 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 list the same parent, creating independent branches (and therefore independent top modules) + +## Tech stack must match (hard rule) + +- Because the base 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 `base` when they target the **same language, framework, and runtime** +- A runtime / network dependency between systems is **not** a reason to use `base` +- Example of the mistake: a React frontend that talks to a Python/FastAPI backend over HTTP must **not** list `base: [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 `imports` module — never through `base` + +## Building upon but not necessarily extension + +- The current module does not need to functionally extend the base module — it may address a completely different concern +- Even so, the two are never independent artifacts: the current module's output physically contains the base module's code, because both are stages of one accumulating codebase +- Use `base` to place a module at the right point in that lineage, whether or not it builds on the base module's functionality + +## Conflict prevention + +- 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 base module's `exported_concepts` are available, and only for modules that explicitly list it under `base` +- Each exported concept is available with its full definition text +- Other concepts from base modules are internal and invisible +- To reference a concept exported by an ancestor deeper in the chain, list that ancestor under `base` as well +- If you need shared definitions across branches or stacks, use `imports` for that — not `base` + +## File locations + +- Modules that use `base` live at the **repository root** — they are functional modules with specs +- `base` entries name other root-level modules (e.g., `auth`, `messaging`) +- Never list an import module under `base` — import modules are for `imports` only + +## Format + +```plain +--- +base: + - auth +imports: + - airplain +description: Module built on top of auth, importing shared definitions +--- +``` + +A module can list multiple base modules **only when they lie on one ancestor path**. Here `messaging` itself has `auth` as its base, so `auth` is an ancestor of `messaging`; the module attaches after `messaging` and lists `auth` only to see its exports: + +```plain +--- +base: + - auth + - messaging +imports: + - airplain +--- +``` diff --git a/forge/rules/concise-specs.md b/forge/rules/concise-specs.md index be6aa79..c689478 100644 --- a/forge/rules/concise-specs.md +++ b/forge/rules/concise-specs.md @@ -23,12 +23,12 @@ Do not write: - Universal software defaults: that invalid input is rejected, that errors are reported, that data is saved when saving is the point of the operation - Rationale, motivation, benefits, or background — the spec states what is, not why it was chosen - Standard behavior of a technology already named in `***implementation reqs***` (an HTTP framework parses HTTP; a database driver opens connections) -- Restatements of a nearby bullet in different words, or of anything already stated in an `import`ed or `require`d module +- Restatements of a nearby bullet in different words, or of anything already stated in an `imports` or `base` module - Anything about work the spec is not about ("no changes to the existing UI") - Meta-commentary about the spec itself ("this spec is intentionally small", "as described above") Worked pairs for every item above — padding, rationale, defaults, technology behavior, -meta-commentary, and duplication of an `import` — are in `concise-specs-examples.md`. +meta-commentary, and duplication of an `imports` module — are in `concise-specs-examples.md`. ## Use plain words @@ -74,7 +74,7 @@ Read the drafted lines once more and apply, in order: 1. **The deletion test** — cut every line whose removal would change nothing about what gets built 2. **The word test** — replace every ornate word with a plain one, and delete every uncheckable adjective, adverb, and hedge -3. **The duplication test** — cut anything already stated in this file, an `import`, or a `requires` chain +3. **The duplication test** — cut anything already stated in this file, an `imports` module, or a `base` chain 4. **The read-aloud test** — a line you cannot read aloud in one breath is either two facts or one padded one If a cut would remove an interface detail, a boundary case, or a disambiguation, keep the fact and shorten the wording instead — see *Do not over-cut* in `concise-specs-examples.md` for what a spec cut past its content looks like. diff --git a/forge/rules/definitions.md b/forge/rules/definitions.md index 0eeac49..b4521bb 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 concepts coming from `import` and `requires` modules before adding +- Check for collisions with concepts coming from `imports` and `base` modules before adding ## Predefined concepts (do not redefine) - ***plain ships several predefined concepts that are available in every module without being defined @@ -37,7 +37,7 @@ When writing or editing a `***definitions***` section in a `.plain` file, always ## Define before use - A concept must be defined before it is referenced in any section (definitions, implementation reqs, functional specs, test reqs) -- Sources of definitions: the module's own `***definitions***`, an `import`ed module's definitions, or a `require`d module's `exported_concepts` +- Sources of definitions: the module's own `***definitions***`, an `imports` module's definitions, or a `base` module's `exported_concepts` ## No circular references - Concept references must not form cycles — if A references B, then B must not reference A (directly or indirectly) @@ -64,8 +64,8 @@ Bad — circular: ``` ## Exported concepts are not transitive -- 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 +- If module A exports a concept and module B lists A under `base`, a module C with `base: [B]` does **not** gain access to A's exports +- To use an ancestor's exports, list that ancestor under `base` 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 eb68371..77b4997 100644 --- a/forge/rules/exported-concepts.md +++ b/forge/rules/exported-concepts.md @@ -7,13 +7,13 @@ description: Rules for using exported_concepts in .plain files When adding or editing `exported_concepts` in a `.plain` file's frontmatter, always follow these rules: ## What exported_concepts does -- `exported_concepts` declares which concepts from this module are visible to modules that `require` it +- `exported_concepts` declares which concepts from this module are visible to modules that list it under `base` - 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 +- Only modules that use `base` receive exported concepts — `imports` 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` +- Use `exported_concepts` on any module that other modules will list under `base` - List only the concepts that downstream modules actually need to reference - Keep the exported surface small — expose only what is necessary @@ -22,14 +22,14 @@ 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, 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 +- If module A exports `:Foo:` and module B lists A under `base`, a module C with `base: [B]` does **not** gain access to `:Foo:` +- If module C also needs `:Foo:`, it should list A under `base` as well (`base: [A, B]`) or get `:Foo:` through a common import module ## Format ```plain --- -import: +imports: - airplain exported_concepts: [":User:", ":JwtToken:"] description: Exports User and JwtToken for downstream modules diff --git a/forge/rules/impl-reqs.md b/forge/rules/impl-reqs.md index e0c422c..8d3fe00 100644 --- a/forge/rules/impl-reqs.md +++ b/forge/rules/impl-reqs.md @@ -41,7 +41,7 @@ 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 pass on functional specs and `exported_concepts` — never implementation reqs +- `base` 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 ## Conciseness diff --git a/forge/rules/import-modules.md b/forge/rules/import-modules.md index b4ab4ee..d0934ce 100644 --- a/forge/rules/import-modules.md +++ b/forge/rules/import-modules.md @@ -4,16 +4,16 @@ description: Rules for creating and using import modules in .plain files # Rules for import modules -When creating or editing a `.plain` file that uses `import` or is intended to be imported by other modules, always follow these rules: +When creating or editing a `.plain` file that uses `imports` or is intended to be imported by other modules, always follow these rules: ## What an import module is - 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 may optionally `import` other import modules for layered reuse +- It must **not** use `base` in its frontmatter +- It may list other import modules under `imports` for layered reuse -## What import does -- `import` pulls in `***definitions***`, `***implementation reqs***`, and `***test reqs***` from the target module +## What imports does +- `imports` pulls in `***definitions***`, `***implementation reqs***`, and `***test reqs***` from the target module - It does **not** pull in `***functional specs***` - Import paths omit the `.plain` extension @@ -25,7 +25,7 @@ When creating or editing a `.plain` file that uses `import` or is intended to be ```plain --- -import: +imports: - airplain description: Module that imports shared definitions and reqs --- @@ -35,14 +35,14 @@ A module can import multiple modules: ```plain --- -import: +imports: - airplain - shared_utils --- ``` -## Combining import with requires -- A module can use both `import` and `requires` together -- `import` brings in shared definitions and reqs -- `requires` attaches the module to the build chain — accumulated generated code and previous functional specs -- An import module itself must **not** use `requires` +## Combining imports with base +- A module can use both `imports` and `base` together +- `imports` brings in shared definitions and reqs +- `base` attaches the module to the build chain — accumulated generated code and previous functional specs +- An import module itself must **not** use `base` diff --git a/forge/rules/integration-embedded.md b/forge/rules/integration-embedded.md index 0a2d876..daefafc 100644 --- a/forge/rules/integration-embedded.md +++ b/forge/rules/integration-embedded.md @@ -47,7 +47,7 @@ This keeps the integration self-contained in one directory the user can `cd` int Run host discovery **before** the first Phase 1 question. Treat the results as ground truth for everything that follows. -1. **Locate the host's `.plain` setup (if any).** Look for an existing `.plain` file or directory, a `config.yaml` declaring `plain_source_dir` / `plain_modules_dir` / `resources_dir` / `test_scripts_dir`, a `plain_modules/`, a `resources/`, and a `test_scripts/`. If the host already has a `.plain` setup, **adopt it verbatim**: the new integration module lands inside the existing `plain_source_dir` and `requires` the relevant base module (use `create-requires-module`). Do not create a parallel layout. +1. **Locate the host's `.plain` setup (if any).** Look for an existing `.plain` file or directory, a `config.yaml` declaring `plain_source_dir` / `plain_modules_dir` / `resources_dir` / `test_scripts_dir`, a `plain_modules/`, a `resources/`, and a `test_scripts/`. If the host already has a `.plain` setup, **adopt it verbatim**: the new integration module lands inside the existing `plain_source_dir` and lists the relevant base module under `base` (use `create-base-module`). Do not create a parallel layout. 2. **Read every existing integration in the host.** For each one, extract: intent and scope, host base class / interface / protocol the integration subclasses or implements, package path and naming convention, configuration pattern (env vars, settings module, secret manager), error / exception hierarchy, logging / metrics / tracing conventions, testing pattern (live vs recorded vs mocked, fixture location, conformance-test layout). The new integration must follow the same patterns unless the user explicitly opts out. 3. **Capture findings in the `host-codebase` concept.** Cite the existing integration by file path so the reasoning is auditable. The concept holds, as facts: - Host codebase root (absolute or project-relative path) @@ -159,7 +159,7 @@ Before declaring an embedded integration done, in addition to the shared checkli ## Anti-patterns specific to embedded integrations -- **Choosing a different language, framework, or dependency manager than the host.** The host stack is inherited; cross-stack `requires` chains are forbidden by [`requires-modules.md`](requires-modules.md) +- **Choosing a different language, framework, or dependency manager than the host.** The host stack is inherited; cross-stack `base` chains are forbidden by [`base-modules.md`](base-modules.md) - **Redefining a host class under `plain_modules/`.** Reference the host symbol by FQN; let the renderer import it - **Inlining a host base class body into the contract spec.** Reference the host file as a linked resource **at its original host-relative path** — do not inline its contents and do not copy it into `resources/` - **Copying host source files into `resources/host/` (or anywhere under `resources/`).** That creates a second source of truth that silently drifts from the host. Link the host file in place; the integration `.plain` module already lives inside the host codebase, so the path resolves naturally diff --git a/forge/rules/integrations.md b/forge/rules/integrations.md index c9d9179..b3c3c3c 100644 --- a/forge/rules/integrations.md +++ b/forge/rules/integrations.md @@ -131,7 +131,7 @@ A production-ready integration spec captures every corner case the API can throw - **Inlining a host base class body into the contract spec.** Add the host file as a linked resource under `resources/host/` and reference it by FQN - **Embedding credentials, tokens, or signing keys in a `.plain` file or in a summary** — credentials are referenced by env-var name only - **Authoring against unverified credentials.** Validate first; if the user has no credentials yet, flag it in the module's frontmatter description and re-validate once credentials arrive -- **`requires`-ing a separate-stack module** (a Python backend `requires`-ing a React frontend, or vice versa) — see [`requires-modules.md`](requires-modules.md). Use a shared API schema in `resources/` instead +- **Listing a separate-stack module under `base`** (a Python backend with a React frontend as its base, or vice versa) — see [`base-modules.md`](base-modules.md). Use a shared API schema in `resources/` instead - **Authoring Phase 1 specs from the docs first and "reconciling" with the live API later.** Probe the API as you reach each topic; the live response is the source of truth from the moment it's captured - **Writing any integration spec from memory of the provider's API instead of web-searching for its documentation and `fetch`-ing every relevant page first.** No matter how well-known the API (Stripe, GitHub, Slack, Salesforce, AWS, OpenAI, …), the canonical pages must be located with web search and then retrieved with `fetch` at spec-authoring time — see *Live API must be cross-checked against the documentation*. Authoring from memory bakes in whatever version of the API was current during training, which is always older than the version the integration will actually call - **Guessing a documentation URL from memory and `fetch`-ing it without searching first.** A remembered URL may 404, redirect to a stale version, or miss the page that actually documents the topic. Search to find the authoritative, current links, then fetch the full set diff --git a/forge/rules/linked-resources.md b/forge/rules/linked-resources.md index 01022ac..6d57e80 100644 --- a/forge/rules/linked-resources.md +++ b/forge/rules/linked-resources.md @@ -13,7 +13,7 @@ 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 renderer will flag this as a syntax error. +4. **Another `.plain` file.** `[Auth Module](auth.plain)` is invalid. To reference another module, use the `base` or `imports` 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 @@ -65,7 +65,7 @@ A :User: can edit a :Task: using :TaskModalSpec:. ## File location and path resolution - Paths are resolved against the directory that contains the `.plain` file passed to the renderer, not the directory where the renderer is run -- This includes links inside imported and required modules — they resolve against that same directory, not against the directory the imported module itself lives in +- This includes links inside imported and base modules — they resolve against that same directory, not against the directory the imported module itself lives in - A path that does not resolve there is then looked up in the `template-dir` directory (if configured), and last in the renderer's built-in template library - Because the `.plain` file's own directory is the anchor, a render gives the same result from any 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 diff --git a/forge/rules/module-structure.md b/forge/rules/module-structure.md index 83daaaa..c76894d 100644 --- a/forge/rules/module-structure.md +++ b/forge/rules/module-structure.md @@ -18,9 +18,9 @@ When creating a new `.plain` file or reviewing the structure of a whole file, al | 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` | +| `imports` | Pull definitions, implementation reqs, and test reqs from import modules | `import-modules.md` | +| `base` | Attach the module to the build chain of another root module | `base-modules.md` | +| `exported_concepts` | Declare which of this module's concepts are visible to modules that list it under `base` | `exported-concepts.md` | | `required_concepts` | Declare concepts an import module expects the importing module to define | `required-concepts.md` | ## Sections @@ -33,9 +33,9 @@ When creating a new `.plain` file or reviewing the structure of a whole file, al ## 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`) +- To be renderable, a root module needs at least one functional spec plus the implementation reqs to build it — its own or from an `imports` module (implementation reqs are not inherited through `base`). A frontmatter-only file is a valid scaffold, but it cannot be rendered until a functional spec is added +- A **base module** is a root module that another root module lists under `base` (see `base-modules.md`) +- An **import module** provides shared content that other modules pull in via `imports`: it contains only `***definitions***`, `***implementation reqs***`, and/or `***test reqs***` — never `***functional specs***`, never `base` (see `import-modules.md`) - A **test plain file** holds the scenarios of one functionality of a root module; its frontmatter `module:` and `func_id:` bind it to that functionality, it lives under `tests/`, and it has no section of a core spec (see `test-plain-files.md`) ## Section ownership (where a fact lives) @@ -62,7 +62,7 @@ When creating a new `.plain` file or reviewing the structure of a whole file, al ## 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`) +- Expansion is **textual** and happens before the specification is interpreted — unlike `imports`, which includes another module semantically (see `import-modules.md`) ```plain {% assign max_tags = 3 %} @@ -83,7 +83,7 @@ A :Widget: can have its tags listed. ```plain --- description: Command-line task manager. -import: +imports: - airplain --- diff --git a/forge/rules/requires-modules.md b/forge/rules/requires-modules.md deleted file mode 100644 index f1ce700..0000000 --- a/forge/rules/requires-modules.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -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`, or one that is referenced in another `.plain` file's `requires` directive, always follow these rules: - -## What requires does - -- `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 module — never through `requires` - -## 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 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`) -- Never `require` an import module — import modules are for `import` only - -## Format - -```plain ---- -requires: - - auth -import: - - airplain -description: Module built on top of auth, importing shared definitions ---- -``` - -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 ---- -requires: - - auth - - messaging -import: - - airplain ---- -``` From b16101083c85482824789b1b37727066027bb766 Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Thu, 15 Oct 2026 10:38:12 +0200 Subject: [PATCH 05/25] Rename requires and import to base and imports in the skills create-requires-module becomes create-base-module. Every other skill and reference now names the base: and imports: directives and the base-modules rule file. Refs ENG-190, ENG-182 --- forge/skills/add-concept/SKILL.md | 4 +- forge/skills/add-feature/SKILL.md | 2 +- .../add-feature/references/checklist.md | 2 +- forge/skills/add-functional-spec/SKILL.md | 6 +- forge/skills/add-functional-specs/SKILL.md | 6 +- .../add-implementation-requirement/SKILL.md | 2 +- forge/skills/add-resource/SKILL.md | 2 +- forge/skills/add-template/SKILL.md | 2 +- forge/skills/analyze-2-func-specs/SKILL.md | 2 +- forge/skills/analyze-func-specs/SKILL.md | 2 +- .../analyze-if-func-spec-too-complex/SKILL.md | 4 +- forge/skills/break-down-func-spec/SKILL.md | 6 +- forge/skills/check-plain-env/SKILL.md | 2 +- forge/skills/consolidate-concepts/SKILL.md | 44 +++---- forge/skills/create-base-module/SKILL.md | 113 ++++++++++++++++++ forge/skills/create-import-module/SKILL.md | 20 ++-- forge/skills/create-requires-module/SKILL.md | 110 ----------------- forge/skills/debug-specs/SKILL.md | 2 +- .../forge-plain/references/phase-1-product.md | 2 +- .../references/phase-4-validate-handoff.md | 6 +- forge/skills/init-config-file/SKILL.md | 4 +- forge/skills/init-plain-project/SKILL.md | 2 +- .../references/checklist.md | 2 +- forge/skills/load-plain-reference/SKILL.md | 4 +- .../references/project-model.md | 2 +- .../references/rendering-and-testing.md | 2 +- forge/skills/plain-healthcheck/SKILL.md | 12 +- forge/skills/refactor-module/SKILL.md | 40 +++---- .../skills/resolve-section-ownership/SKILL.md | 2 +- 29 files changed, 206 insertions(+), 203 deletions(-) create mode 100644 forge/skills/create-base-module/SKILL.md delete mode 100644 forge/skills/create-requires-module/SKILL.md diff --git a/forge/skills/add-concept/SKILL.md b/forge/skills/add-concept/SKILL.md index fceacf9..946cb1d 100644 --- a/forge/skills/add-concept/SKILL.md +++ b/forge/skills/add-concept/SKILL.md @@ -16,7 +16,7 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule 2. **Read the file** to understand existing definitions, imports, and concepts. 3. **Validate the concept name** against the syntax rules below. 4. **Check for uniqueness** — the concept name must not already exist in the file or its imports. -5. **Check referenced concepts** — any `:ConceptName:` used in the definition must already be defined above it (in this file or via `import`/`requires`). Concept references must not form cycles (e.g., A references B and B references A). +5. **Check referenced concepts** — any `:ConceptName:` used in the definition must already be defined above it (in this file or via `imports`/`base`). Concept references must not form cycles (e.g., A references B and B references A). 6. **Check for circular references** — if the new concept references `:B:`, then `:B:` must not reference the new concept (directly or indirectly). Example of a circular definition to avoid: ```plain - :Order: is placed by :Customer: and contains :OrderItem: entries. @@ -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** — 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 +- Exported concepts from `base` modules are **not transitive** — to reference an ancestor's export, list that ancestor under `base`; if a concept is shared across branches, define it in a common import module instead ## Definition Format diff --git a/forge/skills/add-feature/SKILL.md b/forge/skills/add-feature/SKILL.md index 1ce3672..7070152 100644 --- a/forge/skills/add-feature/SKILL.md +++ b/forge/skills/add-feature/SKILL.md @@ -49,7 +49,7 @@ Carry the confirmed brief into every later phase. Before advancing, check the Ph 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 confirmed feature-intent brief.** Identify 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. +2. **Read the target `.plain` file(s)**, following their `imports` and `base` 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 the target file and a single concrete starter question are clear. Do **not** ask framing, scope, or multi-part design questions here. diff --git a/forge/skills/add-feature/references/checklist.md b/forge/skills/add-feature/references/checklist.md index 6608ae2..576d02b 100644 --- a/forge/skills/add-feature/references/checklist.md +++ b/forge/skills/add-feature/references/checklist.md @@ -16,7 +16,7 @@ Use this to verify the workflow was followed — never as a substitute for it. R - [ ] Carried the confirmed feature-intent brief into scope and identified the likely target `.plain` file(s) without restarting intent discovery. -- [ ] Read the target `.plain` file(s) and followed their `import`/`requires` chain before authoring. +- [ ] Read the target `.plain` file(s) and followed their `imports`/`base` 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) diff --git a/forge/skills/add-functional-spec/SKILL.md b/forge/skills/add-functional-spec/SKILL.md index 7c95247..e18c8b7 100644 --- a/forge/skills/add-functional-spec/SKILL.md +++ b/forge/skills/add-functional-spec/SKILL.md @@ -18,10 +18,10 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule ## Workflow 1. **Identify the target `.plain` file.** If ambiguous, ask the user. -2. **Read the entire file** to understand existing definitions, implementation reqs, and all current functional specs (including those in `requires` modules). +2. **Read the entire file** to understand existing definitions, implementation reqs, and all current functional specs (including those in `base` modules). 3. **Draft the functional spec** following the rules below, then run the review pass in `concise-specs.md` over the draft — trim it to the shortest wording that keeps every fact before any other check. 4. **Analyze complexity** — use the `analyze-if-func-spec-too-complex` skill to verify the drafted spec implies ≤ 200 LOC. If too complex, use the `break-down-func-spec` skill to split it, then repeat from step 3 for each resulting spec. -5. **Check for conflicts** with every existing functional spec — this is critical. Run `analyze-func-specs` **once** with the new spec plus all existing specs (in the file and in any `requires` chain) as a single batch. The batched analyzer returns every conflicting pair in one call — do **not** invoke a pair-by-pair analyzer. For each conflicting pair it reports, run `resolve-spec-conflict` on that pair; re-run `analyze-func-specs` over the touched set after each resolution until the verdict is `COMPATIBLE`. +5. **Check for conflicts** with every existing functional spec — this is critical. Run `analyze-func-specs` **once** with the new spec plus all existing specs (in the file and in any `base` chain) as a single batch. The batched analyzer returns every conflicting pair in one call — do **not** invoke a pair-by-pair analyzer. For each conflicting pair it reports, run `resolve-spec-conflict` on that pair; re-run `analyze-func-specs` over the touched set after each resolution until the verdict is `COMPATIBLE`. 6. **Append the spec** to the end of the `***functional specs***` section (specs are chronological; new ones go last). 7. **Read the file again** to confirm correct placement and syntax. @@ -66,7 +66,7 @@ Specs must be detailed enough that a developer can use the built software withou ### Encapsulation -Functionality must be self-contained in the spec text. `requires` modules only import functional specs, so do not rely on implementation reqs to convey behavior — they won't be visible in downstream modules. +Functionality must be self-contained in the spec text. `base` modules only pass on functional specs, so do not rely on implementation reqs to convey behavior — they won't be visible in downstream modules. ## Acceptance Tests diff --git a/forge/skills/add-functional-specs/SKILL.md b/forge/skills/add-functional-specs/SKILL.md index 52866dd..f335ac9 100644 --- a/forge/skills/add-functional-specs/SKILL.md +++ b/forge/skills/add-functional-specs/SKILL.md @@ -27,13 +27,13 @@ If you only need to add a single spec, use the `add-functional-spec` skill inste This skill is `add-functional-spec` applied **one spec at a time, in order**, with no shortcuts. The batch never escapes per-spec rigor. 1. **Identify the target `.plain` file.** If ambiguous, ask the user. -2. **Read the entire file** to understand existing definitions, implementation reqs, and all current functional specs (including those reachable via `import` / `requires`). +2. **Read the entire file** to understand existing definitions, implementation reqs, and all current functional specs (including those reachable via `imports` / `base`). 3. **Draft all the specs you intend to add**, in the chronological order they should appear. Keep the drafts in a working list — nothing is inserted yet. Run the review pass in `concise-specs.md` over every draft before continuing; trim each to the shortest wording that keeps every fact. 4. **For each drafted spec, in order:** 1. **Analyze complexity.** Run `analyze-if-func-spec-too-complex` on the spec. If the verdict is `TOO COMPLEX`, run `break-down-func-spec`, replace the entry in the working list with the resulting smaller specs (preserving order), and restart this step on the first of the replacements. 2. **Check for conflicts.** Run `analyze-func-specs` **once** with this single input set: - the new spec, plus - - every existing functional spec in the file and its `requires` chain, plus + - every existing functional spec in the file and its `base` chain, plus - every spec from this batch that has already been inserted. The batched analyzer returns every conflicting pair in one call — do **not** loop over pairs invoking a per-pair analyzer. For each conflicting pair it reports, run `resolve-spec-conflict`. Apply the resolution — which may revise the new spec, an existing spec, or both — then re-run `analyze-func-specs` once over the touched set until the verdict is `COMPATIBLE`. @@ -89,7 +89,7 @@ Specs must be detailed enough that a developer can use the built software withou ### Encapsulation -Functionality must be self-contained in the spec text. `requires` modules only import functional specs, so do not rely on implementation reqs to convey behavior — they won't be visible in downstream modules. +Functionality must be self-contained in the spec text. `base` modules only pass on functional specs, so do not rely on implementation reqs to convey behavior — they won't be visible in downstream modules. ## Acceptance Tests diff --git a/forge/skills/add-implementation-requirement/SKILL.md b/forge/skills/add-implementation-requirement/SKILL.md index 60a65fd..0b3432b 100644 --- a/forge/skills/add-implementation-requirement/SKILL.md +++ b/forge/skills/add-implementation-requirement/SKILL.md @@ -75,7 +75,7 @@ Reference defined `:Concepts:` where they add clarity. Implementation reqs in no ## Encapsulation Warning -`requires` modules only receive functional specs from their dependencies — not implementation reqs. If downstream modules need certain behavior to be visible, that behavior must be expressed in functional specs, not here. +`base` modules only receive functional specs from their dependencies — not implementation reqs. If downstream modules need certain behavior to be visible, that behavior must be expressed in functional specs, not here. ## Validation Checklist diff --git a/forge/skills/add-resource/SKILL.md b/forge/skills/add-resource/SKILL.md index 6b7ea6c..d10f436 100644 --- a/forge/skills/add-resource/SKILL.md +++ b/forge/skills/add-resource/SKILL.md @@ -41,7 +41,7 @@ Use standard markdown link syntax inside any spec section: ## Path Rules - Paths are resolved **against the directory that contains the `.plain` file passed to the renderer**, not the directory where the renderer is run. -- This includes links inside imported and required modules — they resolve against that same directory, not against the directory the imported module itself lives in. +- This includes links inside imported and base modules — they resolve against that same directory, not against the directory the imported module itself lives in. - A path that does not resolve there is then looked up in the `template-dir` directory (if configured), and last in the renderer's built-in template library. - 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. diff --git a/forge/skills/add-template/SKILL.md b/forge/skills/add-template/SKILL.md index 058c46f..d8e3a55 100644 --- a/forge/skills/add-template/SKILL.md +++ b/forge/skills/add-template/SKILL.md @@ -10,7 +10,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Templates use Liquid syntax (`{% include %}`) to reuse the same spec content across multiple `.plain` files. Each inclusion can pass different parameters, producing tailored output from a single source. This is distinct from `import` and `requires` — templates are about textual reuse with parameterization, not module dependencies. +Templates use Liquid syntax (`{% include %}`) to reuse the same spec content across multiple `.plain` files. Each inclusion can pass different parameters, producing tailored output from a single source. This is distinct from `imports` and `base` — templates are about textual reuse with parameterization, not module dependencies. ## When to Use Templates diff --git a/forge/skills/analyze-2-func-specs/SKILL.md b/forge/skills/analyze-2-func-specs/SKILL.md index 0ca61e0..b5c499f 100644 --- a/forge/skills/analyze-2-func-specs/SKILL.md +++ b/forge/skills/analyze-2-func-specs/SKILL.md @@ -13,7 +13,7 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule ## Input -Two functional specs from a `.plain` file (or across `requires` modules). The user provides them directly or points to them by location in a file. +Two functional specs from a `.plain` file (or across `base` modules). The user provides them directly or points to them by location in a file. ## Workflow diff --git a/forge/skills/analyze-func-specs/SKILL.md b/forge/skills/analyze-func-specs/SKILL.md index 89f05cd..afb6cf1 100644 --- a/forge/skills/analyze-func-specs/SKILL.md +++ b/forge/skills/analyze-func-specs/SKILL.md @@ -16,7 +16,7 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule A **set of two or more functional specs** to check against each other. Callers typically supply one of: - A whole `***functional specs***` section to self-check. -- A new spec (or batch of new specs) **plus** the existing specs from the same file and any `requires` modules. +- A new spec (or batch of new specs) **plus** the existing specs from the same file and any `base` modules. - An explicit list of spec snippets the user wants compared. Each spec must be identifiable. If the caller didn't label them, assign **stable identifiers** in order of appearance: `S1`, `S2`, `S3`, …. Use those identifiers in the output. If the caller provided their own IDs or quoted snippets, use those instead and keep them verbatim. 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 c906565..4b3fa63 100644 --- a/forge/skills/analyze-if-func-spec-too-complex/SKILL.md +++ b/forge/skills/analyze-if-func-spec-too-complex/SKILL.md @@ -22,7 +22,7 @@ A drafted functional spec (not yet inserted into the file), plus the full contex ## Workflow -1. **Read the full `.plain` file** (and any `requires`/`import` modules) to understand the current codebase context — what already exists and what the new spec builds on. +1. **Read the full `.plain` file** (and any `base`/`imports` modules) to understand the current codebase context — what already exists and what the new spec builds on. 2. **Read the drafted functional spec** carefully. 3. **Run the complexity analysis** using the checklist below. 4. **Output the verdict and nothing else** — either `ACCEPTABLE` or `TOO COMPLEX`. No reasoning, no LOC estimate, no breakdown. @@ -34,7 +34,7 @@ Work through each indicator. A single "yes" does not automatically mean the spec ### 0. First-Spec Bootstrap Cost Is this the first functional spec in a new root module with no implementation baseline supplied by -`requires`? If so, include the implicit cost of creating the project structure, package metadata, +`base`? If so, include the implicit cost of creating the project structure, package metadata, framework configuration, entry point, initial source files, and unit-test infrastructure. The first spec must be substantially simpler than a later spec. It should expose one minimal, diff --git a/forge/skills/break-down-func-spec/SKILL.md b/forge/skills/break-down-func-spec/SKILL.md index 96103a6..adbe991 100644 --- a/forge/skills/break-down-func-spec/SKILL.md +++ b/forge/skills/break-down-func-spec/SKILL.md @@ -22,7 +22,7 @@ The functional spec to break down, plus the full `.plain` file it belongs to. ## Workflow -1. **Read the full `.plain` file** — definitions, implementation reqs, and all functional specs (including `requires` modules) to understand context. +1. **Read the full `.plain` file** — definitions, implementation reqs, and all functional specs (including `base` modules) to understand context. 2. **Identify the spec to break down** — the one flagged as too complex or pointed to by the user. 3. **Analyze why it is too complex** — use the indicators from `analyze-if-func-spec-too-complex`: - Multiple distinct behaviors bundled together? @@ -39,7 +39,7 @@ The functional spec to break down, plus the full `.plain` file it belongs to. 5. **Draft the replacement specs** — write the smaller specs in chronological order. The first replacement spec typically sets up the foundation, and subsequent ones layer behavior on top. 6. **Verify functional completeness** — this is critical. The replacement specs taken together must cover **100% of the functionality** described in the original spec. Walk through every behavior, condition, and detail in the original and confirm it appears in exactly one of the replacement specs. Nothing may be lost, weakened, or left implicit. If any functionality from the original is missing, add it to the appropriate replacement spec or create an additional one. 7. **Verify each replacement spec** — run `analyze-if-func-spec-too-complex` on each to confirm it fits within the limit. -8. **Check for conflicts** — run `analyze-func-specs` **once** with the full set of replacement specs plus every existing spec in the file and its `requires` chain. The batched analyzer reports every conflicting pair (replacement × existing and replacement × replacement) in one call — do not loop over pairs. Resolve each reported pair with `resolve-spec-conflict`, then re-run `analyze-func-specs` over the touched set until the verdict is `COMPATIBLE`. +8. **Check for conflicts** — run `analyze-func-specs` **once** with the full set of replacement specs plus every existing spec in the file and its `base` chain. The batched analyzer reports every conflicting pair (replacement × existing and replacement × replacement) in one call — do not loop over pairs. Resolve each reported pair with `resolve-spec-conflict`, then re-run `analyze-func-specs` over the touched set until the verdict is `COMPATIBLE`. 9. **Replace in the file** — remove the original spec and insert the replacement specs in its position, preserving chronological order. 10. **Read the file again** to confirm correct placement and syntax. @@ -181,7 +181,7 @@ Define the component concept in `***definitions***` (via `add-concept`) before t - 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. +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-base-module` / `refactor-module` instead of a sibling spec. ## Preserving Chronological Order diff --git a/forge/skills/check-plain-env/SKILL.md b/forge/skills/check-plain-env/SKILL.md index a84d6a3..88a4511 100644 --- a/forge/skills/check-plain-env/SKILL.md +++ b/forge/skills/check-plain-env/SKILL.md @@ -43,7 +43,7 @@ The skill is a **derive → probe → report** pass. It does not stop at the fir ### Step 1 — Inventory the project 1. **Detect the host OS** first. Run `uname -s 2>/dev/null || echo "$OS"`. Use the result to (a) pick the right script extensions to inspect (`.sh` vs `.ps1`) and (b) pick the right install commands later (Homebrew, apt, dnf, pacman, choco, scoop, winget). -2. **List every `.plain` file** in the repo root and in any subdirectory that contains them. Note which are top modules (not `requires`-ed by anything) and which are import modules (under `template/`). +2. **List every `.plain` file** in the repo root and in any subdirectory that contains them. Note which are top modules (not listed under `base` by any other module) and which are import modules (under `template/`). 3. **List every `config.yaml`** in the repo and read each one. Note the `*-script` keys it sets and the values they point at. 4. **List every script under `test_scripts/`** (both `.sh` and `.ps1` — projects can ship both). 5. **List every file under `resources/`** if the directory exists. Schemas and protocol files there often imply running services (e.g. a Postgres `.sql` schema implies a Postgres server). diff --git a/forge/skills/consolidate-concepts/SKILL.md b/forge/skills/consolidate-concepts/SKILL.md index 54ee9ba..ceff69a 100644 --- a/forge/skills/consolidate-concepts/SKILL.md +++ b/forge/skills/consolidate-concepts/SKILL.md @@ -14,13 +14,13 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule ## When to Use - Multiple modules define the same or closely related concepts independently. -- A `requires` chain uses long `exported_concepts` lists to share definitions that would be simpler as an import. +- A `base` chain uses long `exported_concepts` lists to share definitions that would be simpler as an import. - After a `refactor-module` split, shared concepts ended up duplicated or awkwardly exported. - The user wants a single source of truth for domain concepts used across modules. ## Guiding Principle -This is a structural refactoring. The **behavior** of every affected module must remain **identical**. No functional spec, implementation req, or test req is changed. Only concept definitions move — from scattered locations into one shared import module — and the modules' `import` lists are updated to reference it. +This is a structural refactoring. The **behavior** of every affected module must remain **identical**. No functional spec, implementation req, or test req is changed. Only concept definitions move — from scattered locations into one shared import module — and the modules' `imports` lists are updated to reference it. ## Input @@ -28,7 +28,7 @@ The set of modules to consolidate from, plus optionally the name for the new (or ## Phase 1 — Analyze Concepts Across Modules -1. **Read all source modules.** For each `.plain` file involved, read the full file including frontmatter, definitions, implementation reqs, test reqs, and functional specs. Also read their `import` and `requires` chains. +1. **Read all source modules.** For each `.plain` file involved, read the full file including frontmatter, definitions, implementation reqs, test reqs, and functional specs. Also read their `imports` and `base` chains. 2. **Build a concept inventory.** For each concept defined across all source modules, record: - Where it is defined (which file, which line) - Which modules reference it (in definitions, functional specs, implementation reqs, or test reqs) @@ -70,11 +70,11 @@ For each affected module, determine the frontmatter changes: | Change | When | |--------|------| -| Add the new import to `import` list | Module does not already import the target | +| Add the new import to `imports` list | Module does not already import the target | | Remove concepts from `exported_concepts` | Concepts that were exported only to share definitions — now handled by the import | -| Keep concepts in `exported_concepts` | Concepts that downstream `requires` modules still need via export (because those downstream modules do not import the new template) | +| Keep concepts in `exported_concepts` | Concepts that downstream modules still need via export (because those downstream modules do not import the new template) | -**Important:** `import` provides definitions to the importing module. `exported_concepts` on a `requires` module provides concepts to downstream modules in the build chain. If a downstream module will now `import` the shared template directly, the concept can be removed from `exported_concepts`. If a downstream module does **not** import the template, the concept must remain exported. +**Important:** `imports` provides definitions to the importing module. `exported_concepts` on a base module provides concepts to the modules that list it under `base`. If a downstream module will now list the shared template under `imports`, the concept can be removed from `exported_concepts`. If a downstream module does **not** import the template, the concept must remain exported. ### 2e. Present the plan @@ -82,7 +82,7 @@ Summarize for the user: - Target import module (new or existing) and its path - List of concepts being moved, with source module for each - List of concepts staying local (and why) -- Frontmatter changes per module (`import` additions, `exported_concepts` removals) +- Frontmatter changes per module (`imports` additions, `exported_concepts` removals) - Confirmation that no functional specs, implementation reqs, or test reqs are changing Get explicit user confirmation before proceeding. @@ -92,7 +92,7 @@ Get explicit user confirmation before proceeding. ### 3a. Create or update the import module If creating a new import module: -1. Create `template/.plain` with YAML frontmatter. If it needs concepts from another import, add that to its own `import` list. +1. Create `template/.plain` with YAML frontmatter. If it needs concepts from another import, add that to its own `imports` list. 2. Add a `***definitions***` section with all consolidated concepts in dependency order. 3. Do **not** add `***functional specs***` — import modules must not contain them. @@ -106,14 +106,14 @@ If updating an existing import module: For each module that had concepts moved out: 1. **Remove the concept definitions** from the module's `***definitions***` section. Remove the full definition including all sub-bullets. -2. **Add the import** to the module's YAML frontmatter `import` list (if not already present). +2. **Add the import** to the module's YAML frontmatter `imports` list (if not already present). 3. **Update `exported_concepts`** — remove concepts that no longer need to be exported (see 2d). 4. **Do not touch** `***functional specs***`, `***implementation reqs***`, or `***test reqs***` — concept references (`:ConceptName:`) in these sections remain unchanged since the concept is now provided by the import. ### 3c. Update downstream modules (if applicable) -If concepts were removed from a module's `exported_concepts` and downstream `requires` modules relied on them: -1. Add the new import to the downstream module's `import` list so it gets the concepts directly. +If concepts were removed from a module's `exported_concepts` and downstream modules relied on them: +1. Add the new import to the downstream module's `imports` list so it gets the concepts directly. 2. Verify the downstream module's specs can still resolve all concept references. ## Phase 4 — Verify @@ -121,7 +121,7 @@ If concepts were removed from a module's `exported_concepts` and downstream `req ### 4a. Concept availability check For every affected module (source modules, downstream modules, and the import module itself): -- [ ] Every `:Concept:` referenced in functional specs is resolvable (defined locally, via import, or via exported concepts from requires) +- [ ] Every `:Concept:` referenced in functional specs is resolvable (defined locally, via import, or via exported concepts from a base module) - [ ] Every `:Concept:` referenced in definitions is defined before the concept that references it - [ ] Every `:Concept:` referenced in implementation reqs and test reqs is resolvable @@ -129,7 +129,7 @@ For every affected module (source modules, downstream modules, and the import mo - [ ] No concept is defined in both the import module and a source module - [ ] No concept is defined in both the import module and another import that a source module uses -- [ ] Concept names are globally unique across each module's full resolution scope (local + imports + requires exports) +- [ ] Concept names are globally unique across each module's full resolution scope (local + imports + base exports) ### 4c. Definition integrity check @@ -148,11 +148,11 @@ For every affected module (source modules, downstream modules, and the import mo For the import module: - [ ] File is in the `template/` directory - [ ] Does not contain `***functional specs***` -- [ ] Does not use `requires` +- [ ] Does not use `base` - [ ] YAML frontmatter is correctly formatted For each source module: -- [ ] YAML frontmatter `import` list includes the target import module +- [ ] YAML frontmatter `imports` list includes the target import module - [ ] `exported_concepts` is correct (no over-export, no missing exports) - [ ] Module still has at least one functional spec and one implementation req @@ -165,17 +165,17 @@ Read each modified `.plain` file in full and present the changes to the user for ### Moving a concept that is only used locally If a concept is only referenced within a single module, consolidating it into a shared import adds unnecessary coupling. Keep it local. -### Forgetting to update downstream requires modules -When a concept is removed from `exported_concepts`, any module that `requires` the source module and references that concept will break — unless it now imports the shared template. Always trace the full dependency chain. +### Forgetting to update downstream modules +When a concept is removed from `exported_concepts`, any module that lists the source module under `base` and references that concept will break — unless it now imports the shared template. Always trace the full dependency chain. ### Breaking concept ordering Concepts in the import module must be topologically ordered. If `:Meal:` references `:MealType:` in its definition, then `:MealType:` must appear first. The source modules may have had them in the right order locally, but merging concepts from different modules requires re-establishing a valid order. ### Creating circular imports -An import module cannot `import` a module that directly or indirectly imports it back. If the shared concepts reference concepts from an existing template, the new import module should itself import that template — not the other way around. +An import module cannot list under `imports` a module that directly or indirectly imports it back. If the shared concepts reference concepts from an existing template, the new import module should itself import that template — not the other way around. ### Removing concepts from exported_concepts prematurely -Only remove a concept from `exported_concepts` if **every** downstream `requires` module that needs it will now get it via `import`. If even one downstream module does not import the shared template, the export must stay. +Only remove a concept from `exported_concepts` if **every** downstream module that needs it will now get it via `imports`. If even one downstream module does not import the shared template, the export must stay. ## Validation Checklist @@ -183,11 +183,11 @@ Only remove a concept from `exported_concepts` if **every** downstream `requires - [ ] All moved concepts are defined in the import module with identical text - [ ] Concepts are in valid dependency order in the import module - [ ] No concept is defined in both the import module and a source module -- [ ] Every affected module's `import` list includes the target import module +- [ ] Every affected module's `imports` list includes the target import module - [ ] `exported_concepts` updated correctly — no missing exports, no unnecessary exports - [ ] All concept references in all affected modules resolve correctly - [ ] No functional specs, implementation reqs, test reqs, or acceptance tests were modified -- [ ] Import module is in `template/` and has no functional specs or requires +- [ ] Import module is in `template/` and has no functional specs or base - [ ] No circular imports introduced -- [ ] Downstream requires modules updated if needed +- [ ] Downstream modules updated if needed - [ ] User approved the final result diff --git a/forge/skills/create-base-module/SKILL.md b/forge/skills/create-base-module/SKILL.md new file mode 100644 index 0000000..e3720b0 --- /dev/null +++ b/forge/skills/create-base-module/SKILL.md @@ -0,0 +1,113 @@ +--- +name: create-base-module +description: >- + Create a ***plain module that lists another module under base to build on + it in the build chain. Use when the user wants to create a new .plain file + that builds on top of a previously built module, inheriting its functional + specs and generated code as a starting point. +--- + +# Create Base Module + +Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. + +## What Base Does + +`base` attaches the current module to the project's build chain. The base 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 base module, but their outputs are never independent: the current module's code physically contains the base module's. + +When this module is rendered: +- The base module's generated code (`plain_modules//code`) is copied as the starting point. +- The `***functional specs***` of the base module **and all its ancestors** become visible as **previous functional specs**. +- Of the base 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 under `base` too. +- The base module's test plain files stay with the base module. The new module gets its own test plain files under `tests//`, one per functionality it adds (see `test-plain-files.md`). + +All modules connected by `base` form a single tree with a common root. **Diamond joins are prohibited**: multiple `base` 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 `base` for: +- Building on top of an existing module's functionality +- 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 `imports` instead — see the `create-import-module` skill. + +## Workflow + +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 `base` field. Modules with functional specs live at the root, not in `template/`. +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. Add every functional spec through `add-functional-spec` or `add-functional-specs`, which also create its test plain file. +6. **Check for conflicts** between your new functional specs and any spec in the ancestor chain. + +## Format + +The `base` field is a list of module names in the YAML frontmatter: + +```plain +--- +base: + - core_module +imports: + - shared_template +description: Extended module that builds on core_module +--- + +***definitions*** + +- :NewFeature: is a feature added by this module. + +***functional specs*** + +# new-feature +:NewFeature: is available. +``` + +A module can use both `base` and `imports` together. `base` names other root-level modules; `imports` resolves from the default `template/` directory (no prefix needed). + +Multiple `base` entries are legal only when they lie on one ancestor path (e.g., `base: [auth, messaging]` where `messaging` itself has `auth` as its base). The deepest entry (`messaging`) is the attachment point; `auth` is listed only to make its exports visible. + +## Exported Concepts + +The base module controls what concepts are visible via `exported_concepts`: + +```plain +> In the base module's frontmatter: +--- +exported_concepts: [":StorageClient:", ":BackupResult:"] +--- +``` + +Only `:StorageClient:` and `:BackupResult:` would be available to modules that list this one under `base`, each with its full definition text. All other concepts from the base module are internal. + +## Chronological Ordering with Base + +Functional specs from the whole `base` 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 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 base module — `base` places it at the right point in the accumulating build chain either way. + +## Imports vs Base + +| Aspect | `imports` | `base` | +|--------|-----------|--------| +| 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, 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/`) +- [ ] Base module exists and is renderable +- [ ] All `base` entries lie on one ancestor path — no diamond joins +- [ ] The deepest `base` 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 +- [ ] Every functional spec has its test plain file under `tests//` +- [ ] Both `base` and `imports` are used correctly (not mixed up) +- [ ] YAML frontmatter is correctly formatted between `---` markers diff --git a/forge/skills/create-import-module/SKILL.md b/forge/skills/create-import-module/SKILL.md index 390d15b..6f349f2 100644 --- a/forge/skills/create-import-module/SKILL.md +++ b/forge/skills/create-import-module/SKILL.md @@ -13,7 +13,7 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule ## What an Import Module Is -An import module is a `.plain` file that lives in the **`template/`** directory and contains **only** `***definitions***`, `***implementation reqs***`, and/or `***test reqs***`. It must **not** contain `***functional specs***` and must **not** use `requires` in its frontmatter. Other modules pull in its content via the `import` field in their YAML frontmatter, gaining access to its definitions and reqs. +An import module is a `.plain` file that lives in the **`template/`** directory and contains **only** `***definitions***`, `***implementation reqs***`, and/or `***test reqs***`. It must **not** contain `***functional specs***` and must **not** use `base` in its frontmatter. Other modules pull in its content via the `imports` field in their YAML frontmatter, gaining access to its definitions and reqs. Use import modules for: - Sharing concept definitions across multiple modules @@ -21,23 +21,23 @@ Use import modules for: - Providing common test requirements - Creating reusable foundational structure (templates) -If the module needs functional specs (i.e., it describes what software should do), it is not an import module — see the `create-requires-module` skill. +If the module needs functional specs (i.e., it describes what software should do), it is not an import module — see the `create-base-module` skill. ## Workflow 1. **Determine what shared content this module should provide** — which definitions, implementation reqs, or test reqs will other modules need? -2. **Create the `.plain` file in the `template/` directory** with YAML frontmatter. Use `required_concepts` to declare concepts that importing modules must define. It may optionally `import` other templates for layered reuse, but must **not** use `requires`. +2. **Create the `.plain` file in the `template/` directory** with YAML frontmatter. Use `required_concepts` to declare concepts that importing modules must define. It may list other templates under `imports` for layered reuse, but must **not** use `base`. 3. **Add the shared content** — definitions, implementation reqs, and/or test reqs. 4. **Do not add `***functional specs***`** — import modules must not contain functional specs. 5. **Verify concept availability** — ensure all `:Concepts:` referenced are either defined in this module, provided by its own imports, declared as `required_concepts`, or are predefined concepts. ## Format -The `import` field is a list of module paths in the YAML frontmatter: +The `imports` field is a list of module paths in the YAML frontmatter: ```plain --- -import: +imports: - base_template required_concepts: [":AppName:"] description: Shared API definitions and reqs @@ -68,7 +68,7 @@ If an imported template declares `required_concepts`, the importing module **mus ```plain --- -import: +imports: - auth_template --- @@ -82,10 +82,10 @@ import: - :AuthApiSpec: is the OpenAPI specification for the external service's auth endpoint. ``` -## Import vs Requires +## Imports vs Base -| Aspect | `import` | `requires` | -|--------|----------|------------| +| Aspect | `imports` | `base` | +|--------|-----------|--------| | Pulls in definitions | Yes | No (only `exported_concepts`) | | Pulls in implementation reqs | Yes | No | | Pulls in test reqs | Yes | No | @@ -97,7 +97,7 @@ import: - [ ] Module file is in the `template/` directory - [ ] Module does **not** contain `***functional specs***` -- [ ] Module does **not** use `requires` in its frontmatter +- [ ] Module does **not** use `base` in its frontmatter - [ ] Contains at least one of: definitions, implementation reqs, or test reqs - [ ] All `required_concepts` from any imports this module itself uses are satisfied - [ ] `required_concepts` declared for any concepts referenced but not defined here diff --git a/forge/skills/create-requires-module/SKILL.md b/forge/skills/create-requires-module/SKILL.md deleted file mode 100644 index efbdb85..0000000 --- a/forge/skills/create-requires-module/SKILL.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -name: create-requires-module -description: >- - Create a ***plain module that uses requires to depend on another module in - the build chain. Use when the user wants to create a new .plain file that - builds on top of a previously built module, inheriting its functional specs - and generated code as a starting point. ---- - -# Create Requires Module - -Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - -## What Requires Does - -`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//code`) is copied as the starting point. -- 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: -- Building on top of an existing module's functionality -- 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 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 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 any spec in the ancestor chain. - -## Format - -The `requires` field is a list of module paths in the YAML frontmatter: - -```plain ---- -requires: - - base_module -import: - - shared_template -description: Extended module that builds on base_module ---- - -***definitions*** - -- :NewFeature: is a feature added by this module. - -***functional specs*** - -- :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). - -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`: - -```plain -> In the required module's frontmatter: ---- -exported_concepts: [":StorageClient:", ":BackupResult:"] ---- -``` - -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 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 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 — `requires` places it at the right point in the accumulating build chain either way. - -## Import vs Requires - -| Aspect | `import` | `requires` | -|--------|----------|------------| -| 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, 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 -- [ ] 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/debug-specs/SKILL.md b/forge/skills/debug-specs/SKILL.md index 117c4dc..f862d94 100644 --- a/forge/skills/debug-specs/SKILL.md +++ b/forge/skills/debug-specs/SKILL.md @@ -30,7 +30,7 @@ Generated code in `plain_modules//code/` and `plain_modules//tes ## Phase 1 — Understand the Context -1. **Read the `.plain` file(s)** for the module — frontmatter, definitions, implementation reqs, test reqs, and all functional specs. Also read `import` and `requires` chains. +1. **Read the `.plain` file(s)** for the module — frontmatter, definitions, implementation reqs, test reqs, and all functional specs. Also read `imports` and `base` chains. 2. **Read the user's observation carefully.** Rephrase it back to confirm understanding. Identify: - What is the expected behavior? - What is the actual behavior? diff --git a/forge/skills/forge-plain/references/phase-1-product.md b/forge/skills/forge-plain/references/phase-1-product.md index 73eeb0e..4c19266 100644 --- a/forge/skills/forge-plain/references/phase-1-product.md +++ b/forge/skills/forge-plain/references/phase-1-product.md @@ -8,7 +8,7 @@ for each topic below, in order. 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. +- **Module structure** — create or update the `.plain` file(s) (single module, template + modules, or chained modules). Set up YAML frontmatter (`imports`, `base`, `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.** - **`***implementation reqs***`** — only a requirement that can be stated without naming a technology: an error-handling or retry policy, an idempotency or ordering guarantee, a data-format or precision rule. Use the `add-implementation-requirement` skill, on the module whose behavior it constrains. The moment stating it takes a language, framework, library, storage engine, or deployment target, it is a Phase 2 answer — note it and steer back. diff --git a/forge/skills/forge-plain/references/phase-4-validate-handoff.md b/forge/skills/forge-plain/references/phase-4-validate-handoff.md index 1c881bb..2dc1b4a 100644 --- a/forge/skills/forge-plain/references/phase-4-validate-handoff.md +++ b/forge/skills/forge-plain/references/phase-4-validate-handoff.md @@ -4,7 +4,7 @@ Two halves. First **the agent** validates every spec with `plain-healthcheck` so ## 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 ``. +Find the **last module in the dependency chain** — the module that is not listed under `base` 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`. @@ -27,10 +27,10 @@ Run the `plain-healthcheck` skill — the single source of truth for "is this pr - 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 `plain-parser check` on every top module, which parses it with its whole `import` / `requires` chain (frontmatter, module graph, sections, concepts, linked resources, templates), and +- runs `plain-parser check` on every top module, which parses it with its whole `imports` / `base` chain (frontmatter, module graph, sections, concepts, linked resources, templates), and - checks every module for what the parser can't judge (section ownership, bullets, linked-resource contents). -The skill runs the full detect → fix → re-run loop itself (syntax errors, undefined concepts, broken `import` / `requires` chains, cyclic definitions, missing templates, misplaced facts, config drift, missing scripts) and returns only once everything passes or a gap genuinely needs the user. Then: +The skill runs the full detect → fix → re-run loop itself (syntax errors, undefined concepts, broken `imports` / `base` chains, cyclic definitions, missing templates, misplaced facts, 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. diff --git a/forge/skills/init-config-file/SKILL.md b/forge/skills/init-config-file/SKILL.md index 17d36b3..15c7158 100644 --- a/forge/skills/init-config-file/SKILL.md +++ b/forge/skills/init-config-file/SKILL.md @@ -53,7 +53,7 @@ These keys reflect choices made in Phase 3 of `forge-plain`: | `conformance-tests-script` | path (string) | — | Include when the user opted into conformance testing in Phase 3. | | `prepare-environment-script` | path (string) | — | Include only when both (a) the user opted into a prepare-environment script and (b) `conformance-tests-script` is also set. Setting prepare without conformance is a hard `plain-healthcheck` failure. | | `test-script-timeout` | int (seconds) | `120` | Include only when the user explicitly raised/lowered the default. | -| `template-dir` | path (string) | — | Include whenever the project has an `import` module or a custom template directory (e.g. `template/`). Required for projects with shared templates. `plain-parser check` reads it too, so `plain-healthcheck` finds the templates. | +| `template-dir` | path (string) | — | Include whenever the project has an import module or a custom template directory (e.g. `template/`). Required for projects with shared templates. `plain-parser check` reads it too, so `plain-healthcheck` finds the templates. | | `build-folder` | string | `plain_modules` | Include only when the user picked a non-default folder name. Must differ from `build-dest`. The renderer writes `//code` (implementation + unit tests) and `//tests` (conformance tests, one folder per functional spec). | | `build-dest` | string | `dist` | **Always include with the value `dist`.** This skill pins the output destination explicitly so every project's `config.yaml` has the same, predictable target folder, visible in the file. Must differ from `build-folder`. | @@ -73,7 +73,7 @@ Before emitting anything, state the planned split to the user (e.g. "I'll emit ` ### Step 1 — Inventory -1. List every `.plain` file in the repo and identify the top modules (modules not `requires`-ed by anything else) — same procedure as `plain-healthcheck` Step 1. +1. List every `.plain` file in the repo and identify the top modules (modules not listed under `base` by any other module) — same procedure as `plain-healthcheck` Step 1. 2. For each top module, determine which part it belongs to (single-stack → one part; multi-part → one part per top module). 3. List every script under `test_scripts/` and group them by part (e.g. `*_python.sh` belongs to the backend part, `*_js.sh` belongs to the frontend part). 4. Identify the template directory (typically `template/`) and any custom resource directories (typically `resources/`). diff --git a/forge/skills/init-plain-project/SKILL.md b/forge/skills/init-plain-project/SKILL.md index f0ea5cb..ad088f4 100644 --- a/forge/skills/init-plain-project/SKILL.md +++ b/forge/skills/init-plain-project/SKILL.md @@ -80,7 +80,7 @@ Create it at the repo root with frontmatter: ```yaml --- -import: +imports: - base description: --- diff --git a/forge/skills/init-plain-project/references/checklist.md b/forge/skills/init-plain-project/references/checklist.md index 7c875c1..7674b8b 100644 --- a/forge/skills/init-plain-project/references/checklist.md +++ b/forge/skills/init-plain-project/references/checklist.md @@ -16,7 +16,7 @@ Use this to verify the scaffold was produced correctly — never as a substitute - [ ] 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. +- [ ] Created `.plain` at the repo root with frontmatter only (`imports: [base]`, `description`) and **no body sections** — the file exists on disk. ## Generate scripts and config diff --git a/forge/skills/load-plain-reference/SKILL.md b/forge/skills/load-plain-reference/SKILL.md index b7ba05b..a14e3d8 100644 --- a/forge/skills/load-plain-reference/SKILL.md +++ b/forge/skills/load-plain-reference/SKILL.md @@ -45,8 +45,8 @@ All paths are relative to this `SKILL.md`. | Functional specs or acceptance tests | `../../rules/func-specs.md` | | Implementation requirements or unit tests | `../../rules/impl-reqs.md` | | Test requirements or conformance tests | `../../rules/test-reqs.md` | -| `import` modules | `../../rules/import-modules.md` | -| `requires` modules | `../../rules/requires-modules.md` | +| `imports` and import modules | `../../rules/import-modules.md` | +| `base` and base modules | `../../rules/base-modules.md` | | `required_concepts` | `../../rules/required-concepts.md` | | `exported_concepts` | `../../rules/exported-concepts.md` | | Linked files or `resources/` | `../../rules/linked-resources.md` | diff --git a/forge/skills/load-plain-reference/references/project-model.md b/forge/skills/load-plain-reference/references/project-model.md index 72b74c3..2491739 100644 --- a/forge/skills/load-plain-reference/references/project-model.md +++ b/forge/skills/load-plain-reference/references/project-model.md @@ -27,7 +27,7 @@ what these directories may contain and how a spec refers to them. ## Template inclusion -In addition to frontmatter `import`, ***plain supports parameterized template inclusion: +In addition to frontmatter `imports`, ***plain supports parameterized template inclusion: ```plain {% include "python-console-app-template.plain", main_executable_file_name: "my_app.py" %} diff --git a/forge/skills/load-plain-reference/references/rendering-and-testing.md b/forge/skills/load-plain-reference/references/rendering-and-testing.md index 595f656..8f8ec70 100644 --- a/forge/skills/load-plain-reference/references/rendering-and-testing.md +++ b/forge/skills/load-plain-reference/references/rendering-and-testing.md @@ -3,7 +3,7 @@ ## Incremental rendering Functional specs render from top to bottom. When rendering one functional spec, the renderer knows -the previous specs but not future specs. A required module's functional specs also count as previous +the previous specs but not future specs. A base module's functional specs also count as previous specs according to the module rules. Each functional spec receives its own conformance-test folder. After a new spec renders, the diff --git a/forge/skills/plain-healthcheck/SKILL.md b/forge/skills/plain-healthcheck/SKILL.md index 7336c16..6e6aae0 100644 --- a/forge/skills/plain-healthcheck/SKILL.md +++ b/forge/skills/plain-healthcheck/SKILL.md @@ -22,7 +22,7 @@ Run this skill **whenever anything in the ***plain project is finalized** and th - **End of `forge-plain`** (Phase 4) — before handing off the render target. - **End of `add-feature`** (Phase 3 final review) — before declaring the feature done. - **End of `debug-specs`** — after applying a fix, before telling the user to re-render. -- **After finalizing any single edit** that changes the renderable surface — e.g. after `add-concept`, `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `add-test-requirement`, `add-acceptance-test`, `add-template`, `add-resource`, `resolve-spec-conflict`, `break-down-func-spec`, `consolidate-concepts`, `refactor-module`, `create-import-module`, `create-requires-module`, or any of the `implement-*-testing-script` skills. +- **After finalizing any single edit** that changes the renderable surface — e.g. after `add-concept`, `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `add-test-requirement`, `add-acceptance-test`, `add-template`, `add-resource`, `resolve-spec-conflict`, `break-down-func-spec`, `consolidate-concepts`, `refactor-module`, `create-import-module`, `create-base-module`, or any of the `implement-*-testing-script` skills. - **After hand-editing** a `.plain` file, a `config.yaml`, or anything under `test_scripts/`. - **On demand** — whenever the user asks whether the project is in a renderable state. @@ -36,8 +36,8 @@ 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`); Step 3a validates it. -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. +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 (`base`, `imports`); Step 3a validates it. +2. Identify **top modules** — every module that is not listed under `base` 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/`. 5. Pair each top module with the `config.yaml` that governs it. The pairing rule is: the config file in the same directory as the top module wins; failing that, the repo-root `config.yaml`. A multi-part project must have one config per part — record any top module that has no governing config as a failure. @@ -74,7 +74,7 @@ Step 3 has two halves: a mechanical parse by `plain-parser`, then the checks tha #### 3a. Parse every top module with `plain-parser` -[`plain-parser`](https://pypi.org/project/plain-parser/) is the ***plain parser. It parses a module together with its whole `import` / `requires` chain, so running it on every top module covers every module in the project. +[`plain-parser`](https://pypi.org/project/plain-parser/) is the ***plain parser. It parses a module together with its whole `imports` / `base` chain, so running it on every top module covers every module in the project. 1. **Check it is installed.** Run `plain-parser --help`. If the command is not found, look for it where `plain-forge install` puts it before giving up: `~/.local/bin/plain-parser` (`%USERPROFILE%\.local\bin\plain-parser.exe` on Windows), or the directory printed by `uv tool dir --bin`. If it is there, run it by that full path for the rest of this step and tell the user to add the directory to `PATH`. If it is nowhere, stop and report `FAIL` with the install command (`pipx install plain-parser` or `uv tool install plain-parser`; it needs Python 3.11 or newer). Do **not** fall back to checking these rules by reading the files — without the parser there is no syntax gate. 2. **Run it from the top module's own directory**, once per top module: @@ -86,7 +86,7 @@ Step 3 has two halves: a mechanical parse by `plain-parser`, then the checks tha Add `--config-name ` when the governing config is not named `config.yaml`. `plain-parser` reads `template-dir` from the config next to the `.plain` file or in the working directory, and exits `2` when it finds one in both — running from the module's own directory avoids that in multi-part projects. 3. **Read the result.** Exit `0` prints `: OK`. Exit `1` prints `Error: …` on stderr — record that message verbatim as a failure against the top module. Exit `2` is a usage or config error (ambiguous or malformed config) — record it against the config. -`plain-parser` checks, among other things: frontmatter, that every `import` / `requires` entry exists and the graph has no cycles, that `requires` modules have one fixed order, that imported modules have no functional specs, section headings (allowed, not duplicated, `***definitions***` first, `***acceptance tests***` nested under a functional spec), concepts (defined, exported and required correctly), that a module with functional specs has implementation reqs, and that every linked resource and template resolves to an existing, relative, non-`.plain` file. It does not always report every error in one run, so after fixing what it reported, run it again until it prints `OK`. +`plain-parser` checks, among other things: frontmatter, that every `imports` / `base` entry exists and the graph has no cycles, that `base` modules have one fixed order, that imported modules have no functional specs, section headings (allowed, not duplicated, `***definitions***` first, `***acceptance tests***` nested under a functional spec), concepts (defined, exported and required correctly), that a module with functional specs has implementation reqs, and that every linked resource and template resolves to an existing, relative, non-`.plain` file. It does not always report every error in one run, so after fixing what it reported, run it again until it prints `OK`. #### 3b. Check what the parser can't judge @@ -104,7 +104,7 @@ Treat 3a and 3b together as a hard gate: the healthcheck **only passes** when `p Iterate until it passes: -1. Identify the offending `.plain` file, the line, and the kind of issue: missing or duplicated concept, cyclic definition, broken `import`/`requires`, misplaced fact, bare continuation line, missing resource or template, etc. +1. Identify the offending `.plain` file, the line, and the kind of issue: missing or duplicated concept, cyclic definition, broken `imports`/`base`, misplaced fact, bare continuation line, missing resource or template, etc. 2. Fix only the `.plain` files (or the relevant `config.yaml` / template) using the appropriate edit skill — `add-concept`, `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `resolve-section-ownership`, `resolve-spec-conflict`, `break-down-func-spec`, `consolidate-concepts`, or an inline edit. **Never** modify generated code under `plain_modules//code/` or `plain_modules//tests/`. 3. If you are uncertain about ***plain syntax for the failing construct, re-load `load-plain-reference` before fixing. 4. Re-run the failed checks — for a `plain-parser` failure, re-run `plain-parser check`. Repeat until they pass. diff --git a/forge/skills/refactor-module/SKILL.md b/forge/skills/refactor-module/SKILL.md index ab36fa6..2ec67ed 100644 --- a/forge/skills/refactor-module/SKILL.md +++ b/forge/skills/refactor-module/SKILL.md @@ -2,7 +2,7 @@ name: refactor-module description: >- Split a large ***plain module into smaller modules grouped by logical domain. - The resulting modules are connected via a requires chain so that functionality + The resulting modules are connected via a base chain so that functionality is 100% preserved. Use when a module has grown too large and its functional specs span multiple distinct concerns that would be clearer as separate modules. --- @@ -28,7 +28,7 @@ The `.plain` file to refactor, plus guidance from the user on the desired groupi ## Phase 1 — Analyze the Current Module -1. **Read the entire `.plain` file** — frontmatter, definitions, implementation reqs, test reqs, and all functional specs. Also read any `import` and `requires` chains to understand the full context. +1. **Read the entire `.plain` file** — frontmatter, definitions, implementation reqs, test reqs, and all functional specs. Also read any `imports` and `base` chains to understand the full context. 2. **Inventory the functional specs.** Number each spec and note which `:Concepts:` it references. Build a dependency map: which specs depend on earlier specs (by referencing behavior or state they introduced)? 3. **Inventory the definitions.** For each concept, note which functional specs reference it. Identify concepts that are used across multiple logical groups vs. concepts used only within one group. 4. **Identify logical groups.** Look for natural seams — clusters of functional specs that share concepts, represent a cohesive domain, or describe a single feature area. Common groupings: @@ -45,17 +45,17 @@ Present the proposed split to the user and get explicit confirmation before maki ### 2a. Define the module chain -The modules form a `requires` chain that preserves the original chronological ordering of functional specs. The first module in the chain contains the earliest specs; each subsequent module `requires` the previous one and adds the next logical group. +The modules form a `base` chain that preserves the original chronological ordering of functional specs. The first module in the chain contains the earliest specs; each subsequent module lists the previous one under `base` and adds the next logical group. ``` original-module ↓ splits into -module-group-a (no requires — this is the new base) -module-group-b (requires: module-group-a) -module-group-c (requires: module-group-b) +module-group-a (no base — this is the chain root) +module-group-b (base: module-group-a) +module-group-c (base: module-group-b) ``` -If the original module already had `requires` or `import` dependencies, the first new module inherits them. +If the original module already had `base` or `imports` dependencies, the first new module inherits them. ### 2b. Assign functional specs to modules @@ -87,7 +87,7 @@ If many concepts are shared across all modules, consider creating a new import m Summarize for the user: - Number of resulting modules and their names - Which functional specs go in each module (listed by number or short description) -- The `requires` chain order +- The `base` chain order - Any new import module being created - Which concepts are exported from each module - Confirmation that all original specs are accounted for (none lost) @@ -96,16 +96,16 @@ Get explicit user confirmation before proceeding. ## Phase 3 — Execute the Split -Create the new modules in order, from the base of the chain upward. +Create the new modules in order, from the root of the chain upward. ### 3a. Create or update the import template (if needed) If shared definitions or reqs are being moved to a new import template, create it first using the `create-import-module` skill. -### 3b. Create the base module +### 3b. Create the chain root Create the first module in the chain: -1. Set the YAML frontmatter — inherit the original module's `import` references. Add `exported_concepts` for any concepts that downstream modules need. +1. Set the YAML frontmatter — inherit the original module's `imports` references. Add `exported_concepts` for any concepts that downstream modules need. 2. Add the `***definitions***` section with concepts used by this module's specs and concepts that need to be exported. 3. Add `***implementation reqs***` and `***test reqs***` specific to this module (if not fully covered by the import template). 4. Add the `***functional specs***` assigned to this module, preserving their original order and exact wording. @@ -114,9 +114,9 @@ Create the first module in the chain: ### 3c. Create subsequent modules For each subsequent module in the chain: -1. Set `requires` to point to the previous module in the chain. Set `import` if it uses shared templates. +1. Set `base` to the previous module in the chain. Set `imports` if it uses shared templates. 2. Add `exported_concepts` for any concepts that later modules in the chain need. -3. Add `***definitions***` for concepts local to this module. Do **not** redefine concepts already available via `requires` (exported) or `import`. +3. Add `***definitions***` for concepts local to this module. Do **not** redefine concepts already available via `base` (exported) or `imports`. 4. Add `***implementation reqs***` and `***test reqs***` specific to this module. 5. Add the `***functional specs***` assigned to this module, preserving their original order and exact wording. 6. Carry over any `***acceptance tests***`. @@ -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 — 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. +- If other modules listed the original module under `base`, update them to list 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 under `base` as well. ## Phase 4 — Verify @@ -140,12 +140,12 @@ Checklist: ### 4b. Chronological ordering check -Verify that the chronological order of all functional specs — when read across the requires chain from base to leaf — matches the original ordering exactly. A spec must not appear before a spec it depends on. +Verify that the chronological order of all functional specs — when read across the base chain from root to leaf — matches the original ordering exactly. A spec must not appear before a spec it depends on. ### 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 a module listed in `requires` +- [ ] Every `:Concept:` referenced in its specs is either defined locally, available via `imports`, or available via `exported_concepts` from a module listed under `base` - [ ] `exported_concepts` includes every concept that downstream modules need - [ ] No concept name collisions between modules @@ -154,7 +154,7 @@ For each module, verify: For each new module: - [ ] Has at least one functional spec and one implementation req - [ ] YAML frontmatter is correctly formatted -- [ ] `requires` and `import` paths are correct +- [ ] `base` and `imports` paths are correct - [ ] Module file is at the repository root (not in `template/`) - [ ] Specs are language-agnostic - [ ] All external interfaces remain explicit @@ -169,7 +169,7 @@ Read each new `.plain` file in full and present the final structure to the user Creating many tiny modules with 1–2 specs each adds overhead without clarity. Aim for modules with 3–10 functional specs each, grouped around a cohesive theme. ### Breaking chronological order -Specs that set up foundational behavior (entry point, database init, core CRUD) must stay in the base module even if they touch concepts from multiple domains. Only split when specs are cleanly separable. +Specs that set up foundational behavior (entry point, database init, core CRUD) must stay in the chain root even if they touch concepts from multiple domains. Only split when specs are cleanly separable. ### Forgetting exported_concepts If a concept defined in module A is referenced by a spec in module B (which requires A), the concept must be in A's `exported_concepts`. Missing exports cause rendering failures. @@ -186,12 +186,12 @@ Acceptance tests are nested under their parent functional spec. When moving a sp - [ ] All original functional specs are present in exactly one new module — none lost - [ ] Spec text is identical to the original — no rewording - [ ] Acceptance tests moved with their parent specs -- [ ] Chronological order preserved across the requires chain +- [ ] Chronological order preserved across the base chain - [ ] Every referenced `:Concept:` is available in each module (local, import, or exported) - [ ] `exported_concepts` are correctly set on each module - [ ] No concept name collisions - [ ] Each module has at least one functional spec and one implementation req -- [ ] `requires` chain is correctly ordered (base → leaf) +- [ ] `base` chain is correctly ordered (root → leaf) - [ ] Original module removed or updated - [ ] 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 diff --git a/forge/skills/resolve-section-ownership/SKILL.md b/forge/skills/resolve-section-ownership/SKILL.md index a2b4495..8a0438d 100644 --- a/forge/skills/resolve-section-ownership/SKILL.md +++ b/forge/skills/resolve-section-ownership/SKILL.md @@ -30,7 +30,7 @@ The bullet, section, or `.plain` file to resolve. ## Workflow -1. **Read the full `.plain` file** — every section, plus its `import` and `requires` chains, so an existing home for a fact is visible before a new one is written. +1. **Read the full `.plain` file** — every section, plus its `imports` and `base` chains, so an existing home for a fact is visible before a new one is written. 2. **Identify the target** — one bullet, one section, or the whole file. 3. **Decompose each bullet into atomic facts.** A clause joined by *and*, *which*, *so that*, or a comma often carries a second fact. Keep each fact in the author's own words for now; rewording comes at step 6. 4. **Route each fact** by the *Section ownership* table in `module-structure.md`. Where a fact resists the table, apply the question it encodes: **would a caller of the finished software observe this?** Yes → `***functional specs***`. No, it is how the result is reached → `***implementation reqs***`. It says what a term means → `***definitions***`. It shapes `:ConformanceTests:` → `***test reqs***`. From 8dfba1c9bfb30752091b78d360849116f0a70be8 Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Thu, 15 Oct 2026 10:40:51 +0200 Subject: [PATCH 06/25] Convert functional spec examples in the skills to # func-id headings Refs ENG-190 --- forge/skills/add-functional-spec/SKILL.md | 9 ++- forge/skills/add-functional-specs/SKILL.md | 9 ++- forge/skills/add-resource/SKILL.md | 3 +- .../analyze-if-func-spec-too-complex/SKILL.md | 66 ++++++++++------ forge/skills/break-down-func-spec/SKILL.md | 77 ++++++++++++------- forge/skills/resolve-spec-conflict/SKILL.md | 12 ++- 6 files changed, 113 insertions(+), 63 deletions(-) diff --git a/forge/skills/add-functional-spec/SKILL.md b/forge/skills/add-functional-spec/SKILL.md index e18c8b7..f4cf42b 100644 --- a/forge/skills/add-functional-spec/SKILL.md +++ b/forge/skills/add-functional-spec/SKILL.md @@ -53,10 +53,11 @@ Each functional spec must be unambiguous — the renderer should have only one r ```plain ***functional specs*** -- A :User: can send a :Message: to a :Conversation:. - - A :Message: must have non-empty content. - - The :Message: is appended to the end of the :Conversation:. - - All :Participant: members of the :Conversation: can see the new :Message:. +# send-message +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/skills/add-functional-specs/SKILL.md b/forge/skills/add-functional-specs/SKILL.md index f335ac9..564fbf7 100644 --- a/forge/skills/add-functional-specs/SKILL.md +++ b/forge/skills/add-functional-specs/SKILL.md @@ -76,10 +76,11 @@ Each functional spec must be unambiguous — the renderer should have only one r ```plain ***functional specs*** -- A :User: can send a :Message: to a :Conversation:. - - A :Message: must have non-empty content. - - The :Message: is appended to the end of the :Conversation:. - - All :Participant: members of the :Conversation: can see the new :Message:. +# send-message +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/skills/add-resource/SKILL.md b/forge/skills/add-resource/SKILL.md index d10f436..0620017 100644 --- a/forge/skills/add-resource/SKILL.md +++ b/forge/skills/add-resource/SKILL.md @@ -35,7 +35,8 @@ Use standard markdown link syntax inside any spec section: ***functional specs*** -- An API conforming to the [API specification](resources/api_spec.yaml) is exposed. +# expose-api +An API conforming to the [API specification](resources/api_spec.yaml) is exposed. ``` ## Path Rules 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 4b3fa63..12b2b22 100644 --- a/forge/skills/analyze-if-func-spec-too-complex/SKILL.md +++ b/forge/skills/analyze-if-func-spec-too-complex/SKILL.md @@ -50,20 +50,25 @@ Too complex: ```plain ***functional specs*** -- A :User: can create, edit, delete, and archive :Task: items, with validation on all fields and confirmation dialogs for destructive actions. +# manage-tasks +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*** -- A :User: can create :Task:. Only valid :Task: items can be added. +# create-task +A :User: can create :Task:. Only valid :Task: items can be added. -- A :User: can edit :Task:. +# edit-task +A :User: can edit :Task:. -- A :User: can delete :Task:. +# delete-task +A :User: can delete :Task:. -- A :User: can archive :Task:. +# archive-task +A :User: can archive :Task:. ``` @@ -84,19 +89,23 @@ Too complex: ```plain ***functional specs*** -- :Order: processing depends on :OrderType:. Standard orders are validated and stored. Express orders skip validation and are queued for immediate dispatch. Bulk orders are split into sub-orders of 100 items each, validated individually, and processed in parallel with progress tracking. +# process-order +: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*** -- A standard :Order: is validated and stored. +# process-standard-order +A standard :Order: is validated and stored. -- An express :Order: is queued for immediate dispatch without validation. +# process-express-order +An express :Order: is queued for immediate dispatch without validation. -- A bulk :Order: is split into sub-orders of 100 items each. - - Each sub-order is processed individually. +# split-bulk-order +A bulk :Order: is split into sub-orders of 100 items each. +- Each sub-order is processed individually. ``` ### 4. Cross-Cutting Concerns @@ -107,17 +116,21 @@ Too complex: ```plain ***functional specs*** -- :Resource: items are fetched from the external API with pagination support, retry on transient errors with exponential backoff, caching for 5 minutes, and logging of all API calls. +# fetch-resources +: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*** -- :Resource: items are fetched from the external API. +# fetch-resources +:Resource: items are fetched from the external API. -- :Resource: items are fetched from the external API in pages. +# fetch-resources-paged +:Resource: items are fetched from the external API in pages. -- Fetching :Resource: items is retried on transient errors using exponential backoff. +# retry-resource-fetch +Fetching :Resource: items is retried on transient errors using exponential backoff. ``` ### 5. UI Complexity @@ -128,18 +141,22 @@ Too complex: ```plain ***functional specs*** -- The dashboard shows :User: profile, recent :Task: items in a sortable table, a notification bell with unread count, and a sidebar with navigation links that highlights the active page. +# dashboard +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*** -- A dashboard page is shown for :User:. +# dashboard-page +A dashboard page is shown for :User:. -- Recent :Task: items are shown in a sortable table on the dashboard. +# dashboard-recent-tasks +Recent :Task: items are shown in a sortable table on the dashboard. -- A notification indicator with the unread count is shown on the dashboard. +# dashboard-notifications +A notification indicator with the unread count is shown on the dashboard. ``` ### 6. Data Transformation Complexity @@ -157,18 +174,21 @@ Too complex (builds the component and uses it in one spec): ```plain ***functional specs*** -- A :Report: is exported to PDF, laying out multi-page tables with repeating headers and page numbers, and charts rendered from :Report: data. +# export-report-pdf +A :Report: is exported to PDF, laying out multi-page tables with repeating headers and page numbers, and charts rendered from :Report: data. ``` Acceptable (component in its own spec, then referenced): ```plain ***functional specs*** -- :PdfRenderer: lays out multi-page tables from structured content. - - Table headers repeat at the top of each page. - - Each page shows its page number. +# pdf-renderer-tables +:PdfRenderer: lays out multi-page tables from structured content. +- Table headers repeat at the top of each page. +- Each page shows its page number. -- A :Report: is exported to PDF using :PdfRenderer:, embedding charts rendered from :Report: data. +# export-report-pdf +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 adbe991..09297f9 100644 --- a/forge/skills/break-down-func-spec/SKILL.md +++ b/forge/skills/break-down-func-spec/SKILL.md @@ -53,18 +53,22 @@ If the spec bundles multiple independently testable actions, split each into its ```plain ***functional specs*** -- A :User: can create, edit, and delete :Recipe: items, with validation on all fields. +# manage-recipes +A :User: can create, edit, and delete :Recipe: items, with validation on all fields. ``` **After:** ```plain ***functional specs*** -- A :User: can create a :Recipe:. Only valid :Recipe: items can be created. +# create-recipe +A :User: can create a :Recipe:. Only valid :Recipe: items can be created. -- A :User: can edit an existing :Recipe:. Validation rules apply to the edited fields. +# edit-recipe +A :User: can edit an existing :Recipe:. Validation rules apply to the edited fields. -- A :User: can delete a :Recipe:. +# delete-recipe +A :User: can delete a :Recipe:. ``` ### Strategy 2: Separate Setup from Behavior @@ -75,18 +79,22 @@ If the spec introduces a new construct and immediately defines complex behavior ```plain ***functional specs*** -- The :MealPlan: screen displays a weekly grid of :Slot: items, allows drag-and-drop reordering, and shows nutritional totals per day. +# meal-plan-screen +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 :MealPlan: screen displays a weekly grid of :Slot: items. +# meal-plan-grid +The :MealPlan: screen displays a weekly grid of :Slot: items. -- A :User: can reorder :Slot: items within a day on the :MealPlan: screen using drag-and-drop. +# reorder-slots +A :User: can reorder :Slot: items within a day on the :MealPlan: screen using drag-and-drop. -- The :MealPlan: screen displays nutritional totals for each day. +# daily-nutrition-totals +The :MealPlan: screen displays nutritional totals for each day. ``` ### Strategy 3: Separate Core Logic from Cross-Cutting Concerns @@ -97,18 +105,22 @@ If the spec mixes primary functionality with error handling, retries, caching, p ```plain ***functional specs*** -- :Ingredient: data is fetched from the external API with pagination, retry on transient errors, and caching for 10 minutes. +# fetch-ingredients +:Ingredient: data is fetched from the external API with pagination, retry on transient errors, and caching for 10 minutes. ``` **After:** ```plain ***functional specs*** -- :Ingredient: data is fetched from the external API. +# fetch-ingredients +:Ingredient: data is fetched from the external API. -- :Ingredient: data is fetched from the external API in pages. +# fetch-ingredients-paged +:Ingredient: data is fetched from the external API in pages. -- Fetching :Ingredient: data is retried on transient errors. +# retry-ingredient-fetch +Fetching :Ingredient: data is retried on transient errors. ``` ### Strategy 4: Separate Conditional Paths @@ -119,20 +131,24 @@ If the spec describes different modes or branches, give each its own spec. ```plain ***functional specs*** -- :MealPlan: generation depends on :DietType:. Standard plans use round-robin assignment. Restrictive plans filter out excluded ingredients first, then apply round-robin. Custom plans allow manual slot-by-slot selection. +# generate-meal-plan +: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*** -- A standard :MealPlan: is generated using round-robin :Recipe: assignment. +# generate-standard-plan +A standard :MealPlan: is generated using round-robin :Recipe: assignment. -- A restrictive :MealPlan: is generated. - - Excluded :Ingredient: items are filtered out first. - - Round-robin :Recipe: assignment is then applied. +# generate-restrictive-plan +A restrictive :MealPlan: is generated. +- Excluded :Ingredient: items are filtered out first. +- Round-robin :Recipe: assignment is then applied. -- A :User: can manually assign :Recipe: items to :Slot: items for a custom :MealPlan:. +# assign-custom-plan +A :User: can manually assign :Recipe: items to :Slot: items for a custom :MealPlan:. ``` ### Strategy 5: Build UI Incrementally @@ -143,18 +159,22 @@ If the spec describes a full screen, split into layout + individual interactive ```plain ***functional specs*** -- The :Dashboard: screen shows a summary card with stats, a scrollable list of recent :MealPlan: items, a floating action button to create a new plan, and a bottom navigation bar. +# dashboard-screen +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*** -- The :Dashboard: screen shows a summary card with :MealFrameStats:. +# dashboard-summary-card +The :Dashboard: screen shows a summary card with :MealFrameStats:. -- The :Dashboard: screen shows a scrollable list of recent :MealPlan: items. +# dashboard-recent-plans +The :Dashboard: screen shows a scrollable list of recent :MealPlan: items. -- The :Dashboard: screen includes a button to create a new :MealPlan:. +# dashboard-new-plan-button +The :Dashboard: screen includes a button to create a new :MealPlan:. ``` ### Strategy 6: Extract a Reusable Technical Component @@ -167,18 +187,21 @@ Define the component concept in `***definitions***` (via `add-concept`) before t ```plain ***functional specs*** -- A :Report: is exported to PDF, laying out multi-page tables with repeating headers, page numbers, and charts rendered from :Report: data. +# export-report-pdf +A :Report: is exported to PDF, laying out multi-page tables with repeating headers, page numbers, and charts rendered from :Report: data. ``` **After:** ```plain ***functional specs*** -- :PdfRenderer: lays out multi-page tables from structured content. - - Table headers repeat at the top of each page. - - Each page shows its page number. +# pdf-renderer-tables +:PdfRenderer: lays out multi-page tables from structured content. +- Table headers repeat at the top of each page. +- Each page shows its page number. -- A :Report: is exported to PDF using :PdfRenderer:, embedding charts rendered from :Report: data. +# export-report-pdf +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-base-module` / `refactor-module` instead of a sibling spec. diff --git a/forge/skills/resolve-spec-conflict/SKILL.md b/forge/skills/resolve-spec-conflict/SKILL.md index ec8cb96..9da7389 100644 --- a/forge/skills/resolve-spec-conflict/SKILL.md +++ b/forge/skills/resolve-spec-conflict/SKILL.md @@ -49,18 +49,22 @@ Before (ambiguous): ```plain ***functional specs*** -- All :Resource: items are returned. +# list-resources +All :Resource: items are returned. -- Only active :Resource: items are returned. +# list-active-resources +Only active :Resource: items are returned. ``` After (disambiguated): ```plain ***functional specs*** -- All :Resource: items are returned when no filter is specified. +# list-resources +All :Resource: items are returned when no filter is specified. -- When the "active" filter is specified, only active :Resource: items are returned. +# list-active-resources +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 0ebb67bc0b83836c8ee1c0e70c77bf939a602d1b Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Thu, 15 Oct 2026 10:40:51 +0200 Subject: [PATCH 07/25] Replace add-acceptance-test with add-test-scenarios add-test-scenarios writes and updates the test plain file of one functionality: tests//.plain with given/when/then conformance scenarios that cover the functional spec, and acceptance scenarios for end-to-end workflows. Every skill that invoked add-acceptance-test now invokes add-test-scenarios; forge-plain Phase 3 walks every functionality and writes its test plain file. Refs ENG-190 --- forge/skills/add-acceptance-test/SKILL.md | 101 ----------------- forge/skills/add-feature/SKILL.md | 10 +- .../add-feature/references/checklist.md | 4 +- forge/skills/add-functional-spec/SKILL.md | 4 +- forge/skills/add-functional-specs/SKILL.md | 4 +- forge/skills/add-test-scenarios/SKILL.md | 107 ++++++++++++++++++ forge/skills/forge-plain/SKILL.md | 4 +- .../forge-plain/references/checklist.md | 6 +- .../forge-plain/references/phase-1-product.md | 2 +- .../forge-plain/references/phase-2-tech.md | 2 +- .../forge-plain/references/phase-3-testing.md | 10 +- forge/skills/plain-healthcheck/SKILL.md | 2 +- .../skills/resolve-section-ownership/SKILL.md | 4 +- 13 files changed, 133 insertions(+), 127 deletions(-) delete mode 100644 forge/skills/add-acceptance-test/SKILL.md create mode 100644 forge/skills/add-test-scenarios/SKILL.md diff --git a/forge/skills/add-acceptance-test/SKILL.md b/forge/skills/add-acceptance-test/SKILL.md deleted file mode 100644 index bb95abd..0000000 --- a/forge/skills/add-acceptance-test/SKILL.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -name: add-acceptance-test -description: >- - Add acceptance tests under a functional spec in a ***plain spec file. Use - when the user wants to add verification criteria for a specific functional - spec, or after adding a functional spec that needs testable success criteria. ---- - -# Add Acceptance Test - -Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - -## Workflow - -1. **Identify the target `.plain` file and the functional spec** to attach the acceptance test to. If ambiguous, ask the user. -2. **Read the file** to understand the functional spec, existing acceptance tests (if any), and the `***test reqs***` section (acceptance tests are implemented according to test reqs). -3. **Draft the acceptance test(s)** following the rules below. -4. **Nest them** under the target functional spec using a `***acceptance tests***` subsection. -5. **Read the file again** to confirm correct placement, indentation, and syntax. - -## When to Add Acceptance Tests - -- The functional spec's correct behavior is **non-obvious** or easily misinterpreted. -- The spec involves **edge cases**, boundary conditions, or specific numeric outcomes. -- The user explicitly requests verification criteria. -- The spec was added via the `add-functional-spec` skill and warrants testable success criteria. - -## Format - -Acceptance tests are nested under the functional spec they verify, using a `***acceptance tests***` subsection: - -```plain -***functional specs*** - -- :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. -``` - -Key formatting rules: -- The `***acceptance tests***` header is indented under the functional spec it belongs to. -- Each test bullet is indented under the `***acceptance tests***` header. -- Multiple acceptance tests can be listed under a single functional spec. - - -## Rules - -### Conformance with the Functional Spec - -An acceptance test is essentially an **example that illustrates** the functional spec — it clarifies intent by showing a concrete scenario. It may imply minor code changes, but those should not be substantial. The acceptance test **must be consistent with** the functional spec it is nested under. If the acceptance test asserts behavior that contradicts, narrows, or extends the functional spec in ways not implied by it, the system will reject it as a conflicting acceptance test. Before writing, re-read the parent functional spec and ensure every assertion in the acceptance test is a direct, logical consequence of what the spec states. - -``` -Functional spec: -- :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. - -Bad (contradicts the spec — the spec says descending, not ascending): -- The first :Resource: in the response should have the oldest creation date. - -Bad (extends beyond the spec — the spec says nothing about limiting results): -- The response should contain at most 50 :Resource: items. -``` - -### Scope - -Each acceptance test verifies a specific, observable aspect of the functional spec it's nested under. Do not test behavior from other functional specs. - -### Testability - -Every acceptance test must describe a concrete, verifiable outcome — not a vague quality. The test should be implementable as an automated conformance test. - -``` -Good: - The response should contain exactly 3 items. -Bad: - The response should be correct. -``` - -### Language Agnosticism - -Like functional specs, acceptance tests must be written in terms of behavior and outcomes — not language-specific constructs. - -### Relationship to Test Reqs - -Acceptance tests extend conformance tests and are implemented according to the `***test reqs***` specification. They do not replace test reqs — they add spec-specific verification on top of the general testing framework defined there. - -## Validation Checklist - -- [ ] Every assertion is a direct logical consequence of the parent functional spec -- [ ] No assertion contradicts, narrows, or extends beyond the parent spec -- [ ] Acceptance test illustrates the spec via example, not introducing substantial new behavior -- [ ] Nested under the correct functional spec -- [ ] Indentation is correct (`***acceptance tests***` indented under the spec) -- [ ] Each test describes a concrete, verifiable outcome -- [ ] Trimmed per `concise-specs.md`: plain words, no obvious steps, no restatement of the parent spec -- [ ] Tests are scoped to the parent functional spec only -- [ ] Written in language-agnostic terms -- [ ] No duplication with existing acceptance tests on the same spec diff --git a/forge/skills/add-feature/SKILL.md b/forge/skills/add-feature/SKILL.md index 7070152..07a4d5f 100644 --- a/forge/skills/add-feature/SKILL.md +++ b/forge/skills/add-feature/SKILL.md @@ -4,7 +4,7 @@ description: >- End-to-end feature addition on an existing ***plain project: runs a short feature-intent interview, then takes the confirmed request 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 + test scenarios) 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 @@ -78,14 +78,14 @@ Route each answer to the right edit skill — never hand-author a `***plain` sec - **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`. When the answer states HOW the functionality is built — a retry or error-handling policy, a data format, a library, an architectural pattern — and it is not already in the file or its imports. -- **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 scenario** → `add-test-scenarios`, on the functionality's test plain file. 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 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 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: +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 / test scenario, 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 was just written, - **(b) augment** the existing spec — embed the proposed new wording in the question, @@ -99,7 +99,7 @@ Ask the user whether the feature is fully covered. Yes → Phase 3. No → retur ### Conformance gate -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. +Author `***test reqs***` and test plain files **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 @@ -111,7 +111,7 @@ Most checks already happened in the loop; this is a slim consistency pass whose - Chronological ordering is correct end-to-end — no spec depends on something that comes after it. - Functional specs are language-agnostic. - Every external interface is explicit (endpoint paths, methods, CLI args, formats). - - Acceptance tests (if any) are consistent with their parent specs. + - Every new or changed functionality has a test plain file whose scenarios follow from its spec, with at least one conformance scenario when conformance tests are enabled. 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 check this skill runs. It validates every `config.yaml`, runs `plain-parser check` on every top module, and checks every module against the rules, 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 to render as `.plain`. diff --git a/forge/skills/add-feature/references/checklist.md b/forge/skills/add-feature/references/checklist.md index 576d02b..1c4b93c 100644 --- a/forge/skills/add-feature/references/checklist.md +++ b/forge/skills/add-feature/references/checklist.md @@ -27,12 +27,12 @@ Use this to verify the workflow was followed — never as a substitute for it. R - [ ] 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). +- [ ] `***test reqs***` / test plain files 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. +- [ ] 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), every new or changed functionality's test plain file in step with its spec. - [ ] 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 `.plain`. diff --git a/forge/skills/add-functional-spec/SKILL.md b/forge/skills/add-functional-spec/SKILL.md index f4cf42b..3a98ad2 100644 --- a/forge/skills/add-functional-spec/SKILL.md +++ b/forge/skills/add-functional-spec/SKILL.md @@ -69,9 +69,9 @@ Specs must be detailed enough that a developer can use the built software withou Functionality must be self-contained in the spec text. `base` modules only pass on functional specs, so do not rely on implementation reqs to convey behavior — they won't be visible in downstream modules. -## Acceptance Tests +## Test Scenarios -If the functional spec needs verification, use the `add-acceptance-test` skill to add acceptance tests after inserting the spec. +After inserting the spec, invoke `add-test-scenarios` for it. It writes `tests//.plain` with at least one conformance scenario when conformance tests are enabled for the module. ## Validation Checklist diff --git a/forge/skills/add-functional-specs/SKILL.md b/forge/skills/add-functional-specs/SKILL.md index 564fbf7..c6749cd 100644 --- a/forge/skills/add-functional-specs/SKILL.md +++ b/forge/skills/add-functional-specs/SKILL.md @@ -92,9 +92,9 @@ Specs must be detailed enough that a developer can use the built software withou Functionality must be self-contained in the spec text. `base` modules only pass on functional specs, so do not rely on implementation reqs to convey behavior — they won't be visible in downstream modules. -## Acceptance Tests +## Test Scenarios -If any of the newly added functional specs need verification, use the `add-acceptance-test` skill to add acceptance tests after the batch has been inserted. Do this per spec, not as part of the bulk pass. +After the batch has been inserted, invoke `add-test-scenarios` once per inserted functionality, in order. It writes each `tests//.plain` with at least one conformance scenario when conformance tests are enabled for the module. Do this per spec, not as part of the bulk pass. ## Anti-Patterns diff --git a/forge/skills/add-test-scenarios/SKILL.md b/forge/skills/add-test-scenarios/SKILL.md new file mode 100644 index 0000000..fddcff6 --- /dev/null +++ b/forge/skills/add-test-scenarios/SKILL.md @@ -0,0 +1,107 @@ +--- +name: add-test-scenarios +description: >- + Write or update the test plain file of one functionality: create + tests//.plain, add the conformance scenarios that cover the + functional spec in given/when/then form, and add acceptance scenarios for + end-to-end workflows. Use right after a functional spec is added or changed, + when a functionality has no conformance scenario while conformance tests are + enabled, or when the user asks for a test scenario. This skill replaces the + removed add-acceptance-test skill; nested ***acceptance tests*** under a + functional spec are no longer written. +--- + +# Add Test Scenarios + +Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. This skill needs `test-plain-files.md`, `semantic-ids.md`, `func-specs.md`, and `concise-specs.md`. + +## What this skill writes + +One test plain file per functionality, at `tests//.plain`, with the frontmatter keys `module:` and `func_id:` and the two sections `***conformance tests***` and `***acceptance tests***`. Every scenario is a `# ` heading followed by `given:` / `when:` / `then:` lists. The renderer generates one test per scenario and nothing else, so the scenarios in this file are the only tests the functionality gets. + +## When to Use + +- `add-functional-spec` or `add-functional-specs` inserted a functionality — those skills invoke this one for each new functionality. +- A functional spec changed (edit, split, conflict resolution, move) and its scenarios must be brought back in step. +- A functionality has no test plain file, or an empty `***conformance tests***` section, while conformance tests are enabled. +- The user asks for a conformance or acceptance scenario. + +## Conformance gate + +Conformance tests are enabled for a module when the `config.yaml` that governs it has a `conformance-tests-script` entry pointing at an existing script. When they are enabled, every functionality must end up with at least one conformance scenario. When they are not enabled, write a test plain file only if the user asks for one, and say that conformance tests are off for the module. + +## Workflow + +1. **Identify the functionality.** Take the module name and func-id from the caller or the user. If the user points at a spec by its text, find its `# ` heading. If the core spec has no `# ` headings (old syntax), stop and point the user to `migrate-plain-syntax`. +2. **Read the context.** Read the functional spec (heading and body), the `***definitions***` the module sees (its own, its `imports`, and the `exported_concepts` of its `base` modules), the `***test reqs***`, and the functional specs that come before this one, because a scenario may rely on behavior they established. +3. **Locate or create the test plain file.** Look for `tests//.plain`. Also scan the frontmatter of every other `.plain` file under `tests/` for the same `(module, func_id)` pair; read those files too, so scenario-ids stay unique across the merged set. Create the file with the frontmatter and both sections if it does not exist. +4. **List the outcomes the spec states.** Walk the statement line and every clarification bullet. Write down the main path and each boundary, validation, and error case the spec names. Do not add an outcome the spec does not state: a scenario may not extend the spec. If a needed outcome is missing from the spec, ask the user one question and route the answer into the functional spec through `add-functional-spec` (or an inline edit of the clarification bullets) before writing the scenario. +5. **Draft one conformance scenario per outcome.** Give each a scenario-id per `semantic-ids.md` (short slug, unique for this functionality). Write the body as: + - `given:` the state before the action, one `- ` item per fact; omit when there is no setup. + - `when:` the one action under test. + - `then:` every observable result, one `- ` item per result, with concrete values. + Exercise only this functionality, through the interface the spec names, as a black box. +6. **Draft acceptance scenarios only when they add a workflow.** An acceptance scenario spans this functionality and functionalities rendered before it. Write one only when the user asks for it or when the functionality completes a user-visible workflow that no single conformance scenario shows. Otherwise leave `***acceptance tests***` empty. +7. **Run the review pass** from `concise-specs.md` over every scenario: no narration, no rationale, no step a reader would assume, concrete values, plain words. +8. **Write the file and read it again.** Confirm the frontmatter keys, the section order, at least one conformance scenario when conformance tests are enabled, and that no body line starts with `# ` or `***`. +9. **Review with the user** when running inside `forge-plain` or `add-feature`: show the scenarios as the snippet for that loop iteration and apply the response before moving on. + +## Updating existing scenarios + +- **The spec changed.** Compare every scenario with the new spec text. Remove or rewrite a scenario that no longer follows from the spec; add a scenario for every new outcome. Keep the scenario-id of a scenario whose meaning is unchanged, because the generated test carries it. +- **The functionality was split.** Each new functionality gets its own test plain file; move each scenario to the functionality it tests and write new scenarios for outcomes that have none. +- **The func-id was renamed.** Rename `func_id:` in every test plain file bound to it and move the file to the new conventional path. +- **The functionality was deleted.** Delete its test plain file; confirm with the user first when it holds hand-written scenarios. +- **The functionality moved to another module.** Move the file to that module's folder under `tests/` and update `module:`. + +## Rules + +- A scenario is a direct consequence of the functional spec: it does not contradict, narrow, or extend it. +- A conformance scenario tests this functionality alone; an acceptance scenario may use earlier functionalities. +- Scenarios are language-agnostic and black box: no function, class, or file of the implementation is named. +- Values that matter to the check are concrete (`named "Buy milk"`, `returns 404`); descriptive values (`a valid name`) are not checkable. +- One scenario per distinct outcome; the same outcome is never written twice in different words. +- Framework, run command, and network policy belong in `***test reqs***`, never in a scenario. +- The old nested `***acceptance tests***` subsection under a functional spec is never written. If the core spec still has one, stop and point the user to `migrate-plain-syntax`. + +## Format + +```plain +--- +module: task-manager +func_id: add-task +--- + +***conformance tests*** + +# add-valid-task +given: + - :App: is running with an empty :TaskList: +when: + - :User: adds a :Task: named "Buy milk" +then: + - :TaskList: shows one :Task: named "Buy milk" + +# reject-short-name +given: + - :App: is running with an empty :TaskList: +when: + - :User: adds a :Task: named "Go" +then: + - :App: shows the error "Name must be at least 3 characters" + - :TaskList: stays empty + +***acceptance tests*** +``` + +## Validation Checklist + +- [ ] The file is at `tests//.plain` with `module:` and `func_id:` naming an existing functionality +- [ ] `***conformance tests***` comes first, `***acceptance tests***` second; both sections exist +- [ ] At least one conformance scenario when conformance tests are enabled for the module +- [ ] Every outcome the functional spec states has one conformance scenario; no scenario states an outcome the spec does not +- [ ] Every scenario is a `# ` heading with `given:` / `when:` / `then:` lists and concrete values +- [ ] Scenario-ids follow `semantic-ids.md` and are unique across every test plain file bound to this functionality +- [ ] Scenarios are black box and language-agnostic +- [ ] Trimmed per `concise-specs.md` +- [ ] No nested `***acceptance tests***` was written into the core spec diff --git a/forge/skills/forge-plain/SKILL.md b/forge/skills/forge-plain/SKILL.md index 8752949..a5f2c33 100644 --- a/forge/skills/forge-plain/SKILL.md +++ b/forge/skills/forge-plain/SKILL.md @@ -54,7 +54,7 @@ Use the dedicated edit skills for every write — never hand-author a `***plain` ## Review loop -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: +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 test plain file, 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: - **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. @@ -84,7 +84,7 @@ When entering a phase, read its reference file and walk its topics **in order** | 0 — What is the intent? | `references/phase-0-intent.md` | a concise intent brief covering the problem, primary user and outcome, and initial boundary is explicitly confirmed; nothing is written to disk | | 1 — What are we building? | `references/phase-1-product.md` | the new `***definitions***` and `***functional specs***`, plus any technology-free `***implementation reqs***`, 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 | +| 3 — How is testing done? | `references/phase-3-testing.md` | the `***test reqs***` (and the test plain files 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` | `plain-healthcheck` returned `PASS`, and the user has the render target plus every side-channel command | Between phases, summarize and get explicit overall confirmation before continuing — the intent brief diff --git a/forge/skills/forge-plain/references/checklist.md b/forge/skills/forge-plain/references/checklist.md index ce36afe..b64fdac 100644 --- a/forge/skills/forge-plain/references/checklist.md +++ b/forge/skills/forge-plain/references/checklist.md @@ -37,7 +37,7 @@ A box is met only when its source confirms it. Return `APPROVED` only when every - [ ] **[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 `***test reqs***` or `***acceptance tests***` written in this phase. +- [ ] **[disk]** No `***test reqs***` or test plain files written in this phase. - [ ] **[disk]** Every `***implementation reqs***` bullet written this phase is technology-free policy — none names a language, framework, library, storage engine, or deployment target. - [ ] **[record]** Summarized the full feature list and module/concept layout; got explicit overall confirmation. @@ -45,7 +45,7 @@ A box is met only when its source confirms it. Return `APPROVED` only when every - [ ] **[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. +- [ ] **[disk]** No `***test reqs***` or test plain files written in this phase. - [ ] **[record]** Summarized the tech stack and architecture; got explicit overall confirmation. ## Phase 3 — How is testing done? @@ -54,7 +54,7 @@ A box is met only when its source confirms it. Return `APPROVED` only when every - [ ] **[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). +- [ ] **[both]** Conformance decision was asked explicitly; if yes, walked every Phase-1 functionality one at a time via `add-test-scenarios` (a test plain file with at least one conformance scenario on disk for every functionality 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). diff --git a/forge/skills/forge-plain/references/phase-1-product.md b/forge/skills/forge-plain/references/phase-1-product.md index 4c19266..70737f7 100644 --- a/forge/skills/forge-plain/references/phase-1-product.md +++ b/forge/skills/forge-plain/references/phase-1-product.md @@ -13,7 +13,7 @@ The moment the user answers, write the resulting snippet to disk using the right - **`***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.** - **`***implementation reqs***`** — only a requirement that can be stated without naming a technology: an error-handling or retry policy, an idempotency or ordering guarantee, a data-format or precision rule. Use the `add-implementation-requirement` skill, on the module whose behavior it constrains. The moment stating it takes a language, framework, library, storage engine, or deployment target, it is a Phase 2 answer — note it and steer back. -Do **not** add `***test reqs***` or `***acceptance tests***` in this phase — they belong to later phases. +Do **not** add `***test reqs***` or test plain files in this phase — they belong to later phases. ## Topics (in order) diff --git a/forge/skills/forge-plain/references/phase-2-tech.md b/forge/skills/forge-plain/references/phase-2-tech.md index 7b878e6..eee9674 100644 --- a/forge/skills/forge-plain/references/phase-2-tech.md +++ b/forge/skills/forge-plain/references/phase-2-tech.md @@ -9,7 +9,7 @@ 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. +Do **not** add `***test reqs***` or test plain files in this phase — they belong to later phases. ## Topics (in order) diff --git a/forge/skills/forge-plain/references/phase-3-testing.md b/forge/skills/forge-plain/references/phase-3-testing.md index b627b2f..912f28b 100644 --- a/forge/skills/forge-plain/references/phase-3-testing.md +++ b/forge/skills/forge-plain/references/phase-3-testing.md @@ -1,6 +1,6 @@ # 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. +Gather the testing strategy. This phase covers `***test reqs***`, the test plain files under `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) @@ -36,10 +36,10 @@ template_dir: template - 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). +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 test plain files with conformance scenarios are authored. If the user is unsure, briefly explain the tradeoff (extra scripts + one test plain file per functionality 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 functionality authored in Phase 1, one at a time** — for each one, invoke `add-test-scenarios` to write its test plain file with at least one conformance scenario, then review those scenarios as a snippet with one `AskUserQuestion` and apply the answer before moving to the next functionality. Never bulk-write scenarios; never ask about more than one functionality per call. + - If **no**: record the decision; skip the conformance script, its config entry, and test plain file authoring entirely. + - Review the conformance req (if any), the new script and config entry (if any), and each test plain file 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. diff --git a/forge/skills/plain-healthcheck/SKILL.md b/forge/skills/plain-healthcheck/SKILL.md index 6e6aae0..ba1db60 100644 --- a/forge/skills/plain-healthcheck/SKILL.md +++ b/forge/skills/plain-healthcheck/SKILL.md @@ -22,7 +22,7 @@ Run this skill **whenever anything in the ***plain project is finalized** and th - **End of `forge-plain`** (Phase 4) — before handing off the render target. - **End of `add-feature`** (Phase 3 final review) — before declaring the feature done. - **End of `debug-specs`** — after applying a fix, before telling the user to re-render. -- **After finalizing any single edit** that changes the renderable surface — e.g. after `add-concept`, `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `add-test-requirement`, `add-acceptance-test`, `add-template`, `add-resource`, `resolve-spec-conflict`, `break-down-func-spec`, `consolidate-concepts`, `refactor-module`, `create-import-module`, `create-base-module`, or any of the `implement-*-testing-script` skills. +- **After finalizing any single edit** that changes the renderable surface — e.g. after `add-concept`, `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `add-test-requirement`, `add-test-scenarios`, `add-template`, `add-resource`, `resolve-spec-conflict`, `break-down-func-spec`, `consolidate-concepts`, `refactor-module`, `create-import-module`, `create-base-module`, or any of the `implement-*-testing-script` skills. - **After hand-editing** a `.plain` file, a `config.yaml`, or anything under `test_scripts/`. - **On demand** — whenever the user asks whether the project is in a renderable state. diff --git a/forge/skills/resolve-section-ownership/SKILL.md b/forge/skills/resolve-section-ownership/SKILL.md index 8a0438d..afe2170 100644 --- a/forge/skills/resolve-section-ownership/SKILL.md +++ b/forge/skills/resolve-section-ownership/SKILL.md @@ -36,7 +36,7 @@ The bullet, section, or `.plain` file to resolve. 4. **Route each fact** by the *Section ownership* table in `module-structure.md`. Where a fact resists the table, apply the question it encodes: **would a caller of the finished software observe this?** Yes → `***functional specs***`. No, it is how the result is reached → `***implementation reqs***`. It says what a term means → `***definitions***`. It shapes `:ConformanceTests:` → `***test reqs***`. 5. **Split a fact that carries both a mechanism and its consequence.** The consequence is observable and becomes the functional spec; the mechanism becomes the implementation req. *"Sends the same idempotency key on every retry, so a retried write creates at most one record"* is two facts, not one. 6. **Give each moved fact a subject that exists in the built system.** A fact leaving a functional spec often loses its subject: name the component that owns the behavior, and add it to `***definitions***` if it is not defined yet. `:Implementation:` is the whole codebase — use it only for a fact that holds everywhere. -7. **Author each fact into its owning section** with the right edit skill — `add-concept`, `add-implementation-requirement`, `add-test-requirement`, `add-acceptance-test`. **Never hand-author `***functional specs***`**: a fact moving *into* that section goes through `add-functional-spec`, which runs the complexity and conflict analyzers for you. +7. **Author each fact into its owning section** with the right edit skill — `add-concept`, `add-implementation-requirement`, `add-test-requirement`, `add-test-scenarios`. **Never hand-author `***functional specs***`**: a fact moving *into* that section goes through `add-functional-spec`, which runs the complexity and conflict analyzers for you. 8. **Remove each fact from where it no longer belongs.** If a bullet is left stating nothing observable, delete the bullet; do not leave a stub. Deleting the last bullet of a section deletes the section. 9. **Verify nothing was lost and nothing was duplicated.** Walk every fact from the original text and confirm it appears in exactly one section. A fact that now reads as obvious in its new home is still a cut decision — make it deliberately, per `concise-specs.md`. 10. **Run `plain-healthcheck`.** Moving facts changes the renderable surface; do not finish on a `FAIL`. @@ -48,7 +48,7 @@ The bullet, section, or `.plain` file to resolve. - **`add-functional-spec` reports a conflict** → resolve it with `resolve-spec-conflict` **before** removing the fact from its old section, so no version of it is lost in between. - **A fact appears to belong to two sections** → it is two facts; split it per step 5. If it still resists, it is a mechanism whose consequence has not been written down — write the consequence, and the split follows. - **The owning section does not exist in the file** → create it in canonical order. An import module carries no `***functional specs***` at all (see `import-modules.md`), so an observable fact found there belongs on the root module that imports it. -- **The move empties a functional spec** → delete the spec rather than leaving a placeholder, and check whether any acceptance test nested under it must move or go with it. +- **The move empties a functional spec** → delete the spec rather than leaving a placeholder, and move or delete its test plain file with `add-test-scenarios`. ## Worked examples From ff706cab915f20f0d4a898ed8532150c535da9ee Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Thu, 15 Oct 2026 10:43:38 +0200 Subject: [PATCH 08/25] Keep test plain files in step with functional specs add-functional-spec and add-functional-specs pick a func-id, insert the # heading, and invoke add-test-scenarios. break-down-func-spec, refactor-module, resolve-spec-conflict and debug-specs move, split or rewrite the test plain files of the functionalities they change, and debug-specs gains the incorrect-scenario root cause. add-feature reads tests/ and routes a changed spec to add-test-scenarios. Refs ENG-190 --- forge/skills/add-feature/SKILL.md | 3 ++- forge/skills/add-functional-spec/SKILL.md | 9 ++++++--- forge/skills/add-functional-specs/SKILL.md | 9 ++++++--- forge/skills/add-test-requirement/SKILL.md | 6 +++--- forge/skills/analyze-func-specs/SKILL.md | 2 +- forge/skills/break-down-func-spec/SKILL.md | 11 +++++----- forge/skills/consolidate-concepts/SKILL.md | 4 ++-- forge/skills/debug-specs/SKILL.md | 20 ++++++++++++------- .../forge-plain/references/phase-1-product.md | 2 +- forge/skills/refactor-module/SKILL.md | 18 ++++++++--------- forge/skills/resolve-spec-conflict/SKILL.md | 8 +++++--- 11 files changed, 54 insertions(+), 38 deletions(-) diff --git a/forge/skills/add-feature/SKILL.md b/forge/skills/add-feature/SKILL.md index 07a4d5f..f82b589 100644 --- a/forge/skills/add-feature/SKILL.md +++ b/forge/skills/add-feature/SKILL.md @@ -49,7 +49,7 @@ Carry the confirmed brief into every later phase. Before advancing, check the Ph 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 confirmed feature-intent brief.** Identify which existing `.plain` file(s) the feature most likely belongs to. -2. **Read the target `.plain` file(s)**, following their `imports` and `base` 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. +2. **Read the target `.plain` file(s)**, following their `imports` and `base` chains, and the test plain files under `tests/`, so the existing definitions, implementation reqs, functional specs, test reqs, and scenarios 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 the target file and a single concrete starter question are clear. Do **not** ask framing, scope, or multi-part design questions here. @@ -77,6 +77,7 @@ Route each answer to the right edit skill — never hand-author a `***plain` sec - **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. +- **Changed functional spec** → edit its statement line or `- ` bullets in place, then invoke `add-test-scenarios` so its test plain file follows the new text. - **New implementation req** → `add-implementation-requirement`. When the answer states HOW the functionality is built — a retry or error-handling policy, a data format, a library, an architectural pattern — and it is not already in the file or its imports. - **New test scenario** → `add-test-scenarios`, on the functionality's test plain file. 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. diff --git a/forge/skills/add-functional-spec/SKILL.md b/forge/skills/add-functional-spec/SKILL.md index 3a98ad2..70dae2f 100644 --- a/forge/skills/add-functional-spec/SKILL.md +++ b/forge/skills/add-functional-spec/SKILL.md @@ -19,11 +19,12 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule 1. **Identify the target `.plain` file.** If ambiguous, ask the user. 2. **Read the entire file** to understand existing definitions, implementation reqs, and all current functional specs (including those in `base` modules). -3. **Draft the functional spec** following the rules below, then run the review pass in `concise-specs.md` over the draft — trim it to the shortest wording that keeps every fact before any other check. +3. **Pick the func-id and draft the functional spec.** Choose a func-id per `semantic-ids.md` and confirm no `# ` heading with that id exists in the module. Draft the statement line and its `- ` clarifications following the rules below, then run the review pass in `concise-specs.md` over the draft — trim it to the shortest wording that keeps every fact before any other check. 4. **Analyze complexity** — use the `analyze-if-func-spec-too-complex` skill to verify the drafted spec implies ≤ 200 LOC. If too complex, use the `break-down-func-spec` skill to split it, then repeat from step 3 for each resulting spec. 5. **Check for conflicts** with every existing functional spec — this is critical. Run `analyze-func-specs` **once** with the new spec plus all existing specs (in the file and in any `base` chain) as a single batch. The batched analyzer returns every conflicting pair in one call — do **not** invoke a pair-by-pair analyzer. For each conflicting pair it reports, run `resolve-spec-conflict` on that pair; re-run `analyze-func-specs` over the touched set after each resolution until the verdict is `COMPATIBLE`. -6. **Append the spec** to the end of the `***functional specs***` section (specs are chronological; new ones go last). +6. **Append the spec** to the end of the `***functional specs***` section as a `# ` heading followed by its body, one blank line after the previous functionality (specs are chronological; new ones go last). 7. **Read the file again** to confirm correct placement and syntax. +8. **Write the test plain file.** When conformance tests are enabled for the module, invoke `add-test-scenarios` for the new functionality (see *Test Scenarios* below). ## Rules @@ -48,7 +49,7 @@ The new spec must not contradict any existing functional spec. Conflicting specs ### Disambiguation -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. +Each functional spec must be unambiguous — the renderer should have only one reasonable interpretation. If the statement line is not enough to fully disambiguate the behavior, add **`- ` bullets** under it. The bullets clarify the statement; they do not introduce separate functionality. Even with the bullets, the spec must still imply ≤ 200 LOC. ```plain ***functional specs*** @@ -84,3 +85,5 @@ After inserting the spec, invoke `add-test-scenarios` for it. It writes `tests/< - [ ] Trimmed per `concise-specs.md`: nothing obvious, no rationale, no uncheckable adjectives or hedges, plain words throughout - [ ] No redundancy with existing specs - [ ] Placed in correct chronological position (usually at the end) +- [ ] The func-id follows `semantic-ids.md` and is unique in the module +- [ ] `add-test-scenarios` was invoked for the new functionality when conformance tests are enabled diff --git a/forge/skills/add-functional-specs/SKILL.md b/forge/skills/add-functional-specs/SKILL.md index c6749cd..ee3da89 100644 --- a/forge/skills/add-functional-specs/SKILL.md +++ b/forge/skills/add-functional-specs/SKILL.md @@ -28,7 +28,7 @@ This skill is `add-functional-spec` applied **one spec at a time, in order**, wi 1. **Identify the target `.plain` file.** If ambiguous, ask the user. 2. **Read the entire file** to understand existing definitions, implementation reqs, and all current functional specs (including those reachable via `imports` / `base`). -3. **Draft all the specs you intend to add**, in the chronological order they should appear. Keep the drafts in a working list — nothing is inserted yet. Run the review pass in `concise-specs.md` over every draft before continuing; trim each to the shortest wording that keeps every fact. +3. **Draft all the specs you intend to add**, in the chronological order they should appear, each with its own func-id per `semantic-ids.md`, unique in the module and within the batch. Keep the drafts in a working list — nothing is inserted yet. Run the review pass in `concise-specs.md` over every draft before continuing; trim each to the shortest wording that keeps every fact. 4. **For each drafted spec, in order:** 1. **Analyze complexity.** Run `analyze-if-func-spec-too-complex` on the spec. If the verdict is `TOO COMPLEX`, run `break-down-func-spec`, replace the entry in the working list with the resulting smaller specs (preserving order), and restart this step on the first of the replacements. 2. **Check for conflicts.** Run `analyze-func-specs` **once** with this single input set: @@ -37,8 +37,9 @@ This skill is `add-functional-spec` applied **one spec at a time, in order**, wi - every spec from this batch that has already been inserted. The batched analyzer returns every conflicting pair in one call — do **not** loop over pairs invoking a per-pair analyzer. For each conflicting pair it reports, run `resolve-spec-conflict`. Apply the resolution — which may revise the new spec, an existing spec, or both — then re-run `analyze-func-specs` once over the touched set until the verdict is `COMPATIBLE`. - 3. **Append the spec** to the `***functional specs***` section. Specs are chronological; the new entry goes at the end (unless an earlier existing spec must come *after* it — in that rare case, insert at the correct position). + 3. **Append the spec** to the `***functional specs***` section as a `# ` heading followed by its body. Specs are chronological; the new entry goes at the end (unless an earlier existing spec must come *after* it — in that rare case, insert at the correct position). 5. **Read the file again** to confirm correct placement, ordering, and syntax for the full batch. +6. **Write the test plain files.** When conformance tests are enabled for the module, invoke `add-test-scenarios` once per inserted functionality, in order. Order matters: each new spec is inserted only after its complexity and conflict checks pass, so subsequent specs in the batch are always checked against an already-validated file. @@ -71,7 +72,7 @@ That coverage is achieved by running `analyze-func-specs` **once per spec being ### Disambiguation (per spec) -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. +Each functional spec must be unambiguous — the renderer should have only one reasonable interpretation. If the statement line is not enough to fully disambiguate the behavior, add **`- ` bullets** under it. The bullets clarify the statement; they do not introduce separate functionality. Even with the bullets, the spec must still imply ≤ 200 LOC. ```plain ***functional specs*** @@ -118,3 +119,5 @@ After the batch has been inserted, invoke `add-test-scenarios` once per inserted - [ ] Every spec trimmed per `concise-specs.md`: nothing obvious, no rationale, no uncheckable adjectives or hedges, plain words throughout - [ ] No redundancy with existing specs or within the batch - [ ] Specs placed in correct chronological positions +- [ ] Every func-id follows `semantic-ids.md` and is unique in the module and within the batch +- [ ] `add-test-scenarios` was invoked for every inserted functionality when conformance tests are enabled diff --git a/forge/skills/add-test-requirement/SKILL.md b/forge/skills/add-test-requirement/SKILL.md index 5257c48..0661e34 100644 --- a/forge/skills/add-test-requirement/SKILL.md +++ b/forge/skills/add-test-requirement/SKILL.md @@ -33,7 +33,7 @@ Test reqs specify how **conformance tests** should be written and run. Common co ## What Does NOT Belong Here - **Unit test guidance** — that goes in `***implementation reqs***` -- **Acceptance tests** — those are nested under individual functional specs in `***functional specs***` +- **Test scenarios** — conformance and acceptance scenarios live in test plain files, written with `add-test-scenarios` (see `test-plain-files.md`) - **Behavioral requirements** — those go in `***functional specs***` - **Technology choices for the implementation** — those go in `***implementation reqs***` @@ -42,8 +42,8 @@ Test reqs specify how **conformance tests** should be written and run. Common co | 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 | +| Conformance tests | how: `***test reqs***`; what: `***conformance tests***` of the test plain file | Verify one functionality against its conformance scenarios | +| Acceptance tests | how: `***test reqs***`; what: `***acceptance tests***` of the test plain file | Verify an end-to-end workflow that may span functionalities | ## Format diff --git a/forge/skills/analyze-func-specs/SKILL.md b/forge/skills/analyze-func-specs/SKILL.md index afb6cf1..7e7c03f 100644 --- a/forge/skills/analyze-func-specs/SKILL.md +++ b/forge/skills/analyze-func-specs/SKILL.md @@ -19,7 +19,7 @@ A **set of two or more functional specs** to check against each other. Callers t - A new spec (or batch of new specs) **plus** the existing specs from the same file and any `base` modules. - An explicit list of spec snippets the user wants compared. -Each spec must be identifiable. If the caller didn't label them, assign **stable identifiers** in order of appearance: `S1`, `S2`, `S3`, …. Use those identifiers in the output. If the caller provided their own IDs or quoted snippets, use those instead and keep them verbatim. +Each spec must be identifiable. Use its func-id (`:` when the batch spans modules). A draft that has no func-id yet gets a **stable identifier** in order of appearance: `S1`, `S2`, `S3`, …. Use those identifiers in the output. If the caller provided its own labels or quoted snippets, use those instead and keep them verbatim. The skill also needs the `***definitions***` section (and any imported definitions) so referenced `:Concepts:` can be resolved. diff --git a/forge/skills/break-down-func-spec/SKILL.md b/forge/skills/break-down-func-spec/SKILL.md index 09297f9..2571792 100644 --- a/forge/skills/break-down-func-spec/SKILL.md +++ b/forge/skills/break-down-func-spec/SKILL.md @@ -36,12 +36,13 @@ The functional spec to break down, plus the full `.plain` file it belongs to. - Independently meaningful (makes sense on its own with previous specs as context) - Self-contained (does not require a later spec to be useful) - Within the 200 LOC limit -5. **Draft the replacement specs** — write the smaller specs in chronological order. The first replacement spec typically sets up the foundation, and subsequent ones layer behavior on top. +5. **Draft the replacement specs** — write the smaller specs in chronological order, each with its own func-id per `semantic-ids.md`; the replacement that keeps the original's main behavior may keep the original's func-id. The first replacement spec typically sets up the foundation, and subsequent ones layer behavior on top. 6. **Verify functional completeness** — this is critical. The replacement specs taken together must cover **100% of the functionality** described in the original spec. Walk through every behavior, condition, and detail in the original and confirm it appears in exactly one of the replacement specs. Nothing may be lost, weakened, or left implicit. If any functionality from the original is missing, add it to the appropriate replacement spec or create an additional one. 7. **Verify each replacement spec** — run `analyze-if-func-spec-too-complex` on each to confirm it fits within the limit. 8. **Check for conflicts** — run `analyze-func-specs` **once** with the full set of replacement specs plus every existing spec in the file and its `base` chain. The batched analyzer reports every conflicting pair (replacement × existing and replacement × replacement) in one call — do not loop over pairs. Resolve each reported pair with `resolve-spec-conflict`, then re-run `analyze-func-specs` over the touched set until the verdict is `COMPATIBLE`. -9. **Replace in the file** — remove the original spec and insert the replacement specs in its position, preserving chronological order. -10. **Read the file again** to confirm correct placement and syntax. +9. **Replace in the file** — remove the original functionality (heading and body) and insert the replacement functionalities, each as a `# ` heading with its body, in its position, preserving chronological order. +10. **Bring the test plain files in step** — invoke `add-test-scenarios` for each replacement functionality. It moves every scenario of the original's test plain file to the replacement it tests, writes scenarios for outcomes that have none, and deletes the original's file when its func-id no longer exists. +11. **Read the file again** to confirm correct placement and syntax. ## Splitting Strategies @@ -210,7 +211,7 @@ The extracted component spec and the rewritten original must together still cove 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. -If the original spec had acceptance tests, redistribute them to the most appropriate replacement spec — or drop tests that no longer apply to a single smaller spec and rewrite them. +The original functionality's test plain file is split the same way: each scenario moves to the replacement functionality it tests, a scenario that no longer fits one replacement is rewritten, and each replacement ends with at least one conformance scenario when conformance tests are enabled (see `add-test-scenarios`). ## Validation Checklist @@ -226,4 +227,4 @@ If the original spec had acceptance tests, redistribute them to the most appropr - [ ] Replacement specs are language-agnostic - [ ] Replacement specs trimmed per `concise-specs.md` — a split is not a licence to re-add prose - [ ] All external interfaces remain explicit -- [ ] Acceptance tests (if any) have been redistributed or rewritten +- [ ] Test plain files follow the split: one per replacement functionality, every original scenario moved or rewritten, none bound to a func-id that no longer exists diff --git a/forge/skills/consolidate-concepts/SKILL.md b/forge/skills/consolidate-concepts/SKILL.md index ceff69a..f4e08b7 100644 --- a/forge/skills/consolidate-concepts/SKILL.md +++ b/forge/skills/consolidate-concepts/SKILL.md @@ -141,7 +141,7 @@ For every affected module (source modules, downstream modules, and the import mo - [ ] No `***functional specs***` were modified in any module - [ ] No `***implementation reqs***` were modified in any module - [ ] No `***test reqs***` were modified in any module -- [ ] No `***acceptance tests***` were modified in any module +- [ ] No test plain file was modified ### 4e. Structural validation @@ -186,7 +186,7 @@ Only remove a concept from `exported_concepts` if **every** downstream module th - [ ] Every affected module's `imports` list includes the target import module - [ ] `exported_concepts` updated correctly — no missing exports, no unnecessary exports - [ ] All concept references in all affected modules resolve correctly -- [ ] No functional specs, implementation reqs, test reqs, or acceptance tests were modified +- [ ] No functional specs, implementation reqs, test reqs, or test plain files were modified - [ ] Import module is in `template/` and has no functional specs or base - [ ] No circular imports introduced - [ ] Downstream modules updated if needed diff --git a/forge/skills/debug-specs/SKILL.md b/forge/skills/debug-specs/SKILL.md index f862d94..c63e6cb 100644 --- a/forge/skills/debug-specs/SKILL.md +++ b/forge/skills/debug-specs/SKILL.md @@ -30,7 +30,7 @@ Generated code in `plain_modules//code/` and `plain_modules//tes ## Phase 1 — Understand the Context -1. **Read the `.plain` file(s)** for the module — frontmatter, definitions, implementation reqs, test reqs, and all functional specs. Also read `imports` and `base` chains. +1. **Read the `.plain` file(s)** for the module — frontmatter, definitions, implementation reqs, test reqs, and all functional specs. Also read `imports` and `base` chains, and the test plain files under `tests//`. 2. **Read the user's observation carefully.** Rephrase it back to confirm understanding. Identify: - What is the expected behavior? - What is the actual behavior? @@ -60,13 +60,13 @@ For each relevant file: ### 2c. Check conformance tests and unit tests (if relevant) If the bug manifests as a test failure: -1. Read the failing test — conformance tests in `plain_modules//tests//`, unit tests inside `plain_modules//code/`. +1. Read the failing test — conformance tests in `plain_modules//tests//`, each labelled with the scenario-id it implements, unit tests inside `plain_modules//code/`. 2. Understand what the test expects vs. what the implementation does. 3. Determine whether the test expectation is correct (matches the spec) or incorrect (doesn't match the spec). ## Phase 3 — Diagnose the Root Cause -Trace the issue from the generated code back to the specs. There are five possible root causes — determine which one applies **before** making changes: +Trace the issue from the generated code back to the specs. There are six possible root causes — determine which one applies **before** making changes: | Root Cause | Symptom | Fix | |------------|---------|-----| @@ -74,6 +74,7 @@ Trace the issue from the generated code back to the specs. There are five possib | **Missing spec** | The desired behavior is not covered by any spec. The renderer had no guidance and either did nothing or made an arbitrary choice. | Add a new functional spec using the `add-functional-spec` skill. | | **Conflicting specs** | Two specs contradict each other, causing the renderer to produce inconsistent behavior. | Use the `resolve-spec-conflict` skill. | | **Incorrect spec** | The spec explicitly describes the wrong behavior. The renderer implemented it faithfully, but the spec itself is wrong. | Rewrite the spec to describe the correct behavior. | +| **Incorrect scenario** | The spec is correct and the code matches it, but a scenario in the test plain file expects an outcome the spec does not state, or misses one it states. | Fix the scenario with `add-test-scenarios`. | | **Missing implementation req** | The spec is correct, but the implementation req doesn't provide enough guidance for the renderer to produce the right code (e.g., missing library, missing architectural constraint, missing platform detail). | Add or update the implementation req using the `add-implementation-requirement` skill. | ### How to diagnose @@ -85,7 +86,9 @@ Trace the issue from the generated code back to the specs. There are five possib 2. **Check for conflicts.** Could another spec be overriding or contradicting this behavior? - Read all functional specs that touch the same concepts. - If two specs are in tension → **conflicting specs**. -3. **Check implementation reqs.** Is the renderer missing guidance on how to implement correctly? +3. **Check the scenario.** When a conformance or acceptance test fails, find its scenario by the scenario-id in the test name and compare it with the spec. + - If the scenario contradicts or extends the spec → **incorrect scenario**. +4. **Check implementation reqs.** Is the renderer missing guidance on how to implement correctly? - Does the implementation req specify the right library, pattern, or constraint? - If a platform-specific or framework-specific detail is missing → **missing implementation req**. @@ -95,10 +98,11 @@ Apply the fix based on the diagnosed root cause. Use the appropriate skill: | Root Cause | Skill to Use | |------------|-------------| -| Ambiguous spec | Edit the spec inline — add sub-bullets or reword for clarity | +| Ambiguous spec | Edit the spec inline — add `- ` bullets under the statement line or reword for clarity | | Missing spec | `add-functional-spec` | | Conflicting specs | `resolve-spec-conflict` | | Incorrect spec | Edit the spec inline — rewrite the incorrect part | +| Incorrect scenario | `add-test-scenarios` | | Missing implementation req | `add-implementation-requirement` | ### Fix guidelines @@ -107,6 +111,7 @@ Apply the fix based on the diagnosed root cause. Use the appropriate skill: - **Preserve chronological order.** If adding a new spec, place it correctly relative to existing specs. - **Stay language-agnostic.** Functional specs describe behavior, not implementation. Platform-specific guidance belongs in implementation reqs. - **Respect the 200 LOC limit.** If a fix makes a spec too complex, use `break-down-func-spec` to split it. +- **Keep the test plain file in step.** After changing a functional spec, invoke `add-test-scenarios` for it so its scenarios still follow from the spec. - **Check for new conflicts.** After editing, verify the fix doesn't conflict with other specs by running `analyze-func-specs` once with the edited spec(s) plus every existing spec that touches the same concepts. The batched analyzer reports all conflicting pairs in a single call; resolve each with `resolve-spec-conflict`. ## Phase 5 — Verify and Report @@ -148,7 +153,7 @@ A test is failing. Use the test as the entry point. ``` Read failing test → Understand what it expects → Read the implementation → Determine if the test or the implementation is wrong → Trace back to the spec → -Fix the spec (or test reqs/acceptance tests if the test itself is wrong) +Fix the spec (or the test reqs / the scenario in the test plain file if the test itself is wrong) ``` ### Strategy 4: Differential @@ -179,11 +184,12 @@ A spec fix may affect other specs that reference the same concepts. Always check - [ ] User's observation is clearly understood (expected vs. actual behavior) - [ ] Generated code has been read to understand the current implementation -- [ ] Root cause is diagnosed (ambiguous / missing / conflicting / incorrect / missing impl req) +- [ ] Root cause is diagnosed (ambiguous / missing / conflicting / incorrect / incorrect scenario / missing impl req) - [ ] Fix is applied in the `.plain` file(s) only — no generated code modified - [ ] Fix is minimal and targeted to the observed bug - [ ] Fix does not introduce new conflicts with existing specs - [ ] Modified specs respect the 200 LOC limit - [ ] Modified specs remain language-agnostic - [ ] Chronological ordering is preserved +- [ ] Test plain file of every changed functionality brought in step via `add-test-scenarios` - [ ] Summary of findings and fix presented to the user diff --git a/forge/skills/forge-plain/references/phase-1-product.md b/forge/skills/forge-plain/references/phase-1-product.md index 70737f7..49e67d0 100644 --- a/forge/skills/forge-plain/references/phase-1-product.md +++ b/forge/skills/forge-plain/references/phase-1-product.md @@ -10,7 +10,7 @@ The moment the user answers, write the resulting snippet to disk using the right - **Module structure** — create or update the `.plain` file(s) (single module, template + modules, or chained modules). Set up YAML frontmatter (`imports`, `base`, `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.** +- **`***functional specs***`** — translate each answer into a single chronological, incremental spec (≤200 lines of code change, language-agnostic, no conflicts), headed by a func-id per `semantic-ids.md`. 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.** - **`***implementation reqs***`** — only a requirement that can be stated without naming a technology: an error-handling or retry policy, an idempotency or ordering guarantee, a data-format or precision rule. Use the `add-implementation-requirement` skill, on the module whose behavior it constrains. The moment stating it takes a language, framework, library, storage engine, or deployment target, it is a Phase 2 answer — note it and steer back. Do **not** add `***test reqs***` or test plain files in this phase — they belong to later phases. diff --git a/forge/skills/refactor-module/SKILL.md b/forge/skills/refactor-module/SKILL.md index 2ec67ed..b181f8e 100644 --- a/forge/skills/refactor-module/SKILL.md +++ b/forge/skills/refactor-module/SKILL.md @@ -29,7 +29,7 @@ The `.plain` file to refactor, plus guidance from the user on the desired groupi ## Phase 1 — Analyze the Current Module 1. **Read the entire `.plain` file** — frontmatter, definitions, implementation reqs, test reqs, and all functional specs. Also read any `imports` and `base` chains to understand the full context. -2. **Inventory the functional specs.** Number each spec and note which `:Concepts:` it references. Build a dependency map: which specs depend on earlier specs (by referencing behavior or state they introduced)? +2. **Inventory the functional specs.** List each functionality by its func-id and note which `:Concepts:` it references. Build a dependency map: which specs depend on earlier specs (by referencing behavior or state they introduced)? 3. **Inventory the definitions.** For each concept, note which functional specs reference it. Identify concepts that are used across multiple logical groups vs. concepts used only within one group. 4. **Identify logical groups.** Look for natural seams — clusters of functional specs that share concepts, represent a cohesive domain, or describe a single feature area. Common groupings: - Data model setup and CRUD operations @@ -86,7 +86,7 @@ If many concepts are shared across all modules, consider creating a new import m Summarize for the user: - Number of resulting modules and their names -- Which functional specs go in each module (listed by number or short description) +- Which functional specs go in each module (listed by func-id) - The `base` chain order - Any new import module being created - Which concepts are exported from each module @@ -109,7 +109,7 @@ Create the first module in the chain: 2. Add the `***definitions***` section with concepts used by this module's specs and concepts that need to be exported. 3. Add `***implementation reqs***` and `***test reqs***` specific to this module (if not fully covered by the import template). 4. Add the `***functional specs***` assigned to this module, preserving their original order and exact wording. -5. Carry over any `***acceptance tests***` nested under the moved functional specs. +5. Move the test plain files of the moved functionalities to `tests//` and set their `module:` key to the new module name; func-ids and scenario text stay unchanged. ### 3c. Create subsequent modules @@ -119,7 +119,7 @@ For each subsequent module in the chain: 3. Add `***definitions***` for concepts local to this module. Do **not** redefine concepts already available via `base` (exported) or `imports`. 4. Add `***implementation reqs***` and `***test reqs***` specific to this module. 5. Add the `***functional specs***` assigned to this module, preserving their original order and exact wording. -6. Carry over any `***acceptance tests***`. +6. Move the test plain files of the moved functionalities the same way. ### 3d. Handle the original module file @@ -135,8 +135,8 @@ This is the most critical verification. Walk through every functional spec from Checklist: - [ ] Total spec count across all new modules equals the original spec count -- [ ] Each spec's text is identical to the original (no rewording) -- [ ] Each spec's acceptance tests (if any) are carried over intact +- [ ] Each spec's text is identical to the original (no rewording) and keeps its func-id +- [ ] Each functionality's test plain file moved with it, with `module:` updated and scenarios intact ### 4b. Chronological ordering check @@ -177,15 +177,15 @@ If a concept defined in module A is referenced by a spec in module B (which requ ### Modifying spec text during the split This is a refactoring, not a rewrite. Spec text must be copied verbatim. If a spec needs rewording to work in its new module context, that is a sign the split boundary is wrong — adjust the grouping instead. -### Losing acceptance tests -Acceptance tests are nested under their parent functional spec. When moving a spec to a new module, its acceptance tests must move with it. +### Losing test plain files +A test plain file binds to its functionality by `module:` and `func_id:`. When a functionality moves to a new module, its test plain file moves to `tests//` and its `module:` key changes; otherwise the renderer reports a pair that matches no functionality. ## Validation Checklist - [ ] User confirmed the split plan before execution - [ ] All original functional specs are present in exactly one new module — none lost - [ ] Spec text is identical to the original — no rewording -- [ ] Acceptance tests moved with their parent specs +- [ ] Test plain files moved with their functionalities and rebound to the new module - [ ] Chronological order preserved across the base chain - [ ] Every referenced `:Concept:` is available in each module (local, import, or exported) - [ ] `exported_concepts` are correctly set on each module diff --git a/forge/skills/resolve-spec-conflict/SKILL.md b/forge/skills/resolve-spec-conflict/SKILL.md index 9da7389..aa64d49 100644 --- a/forge/skills/resolve-spec-conflict/SKILL.md +++ b/forge/skills/resolve-spec-conflict/SKILL.md @@ -31,7 +31,7 @@ There are three possible outcomes — determine which one applies **before** mak | Outcome | Symptom | Fix | |---------|---------|-----| | **Implementation is incorrect** | Generated code doesn't match the spec's intent. The specs themselves are fine. | Clarify the ambiguous spec so the renderer interprets it correctly. Re-render. | -| **Conformance tests are incorrect** | The tests don't accurately verify the spec. The implementation is actually correct. | Adjust `***test reqs***` or `***acceptance tests***` to guide better test generation. Re-render. | +| **Conformance tests are incorrect** | The tests don't accurately verify the spec. The implementation is actually correct. | Adjust `***test reqs***`, or the scenarios in the test plain file via `add-test-scenarios`, to guide better test generation. Re-render. | | **Requirements truly conflict** | The two specs are inherently contradictory — no implementation can satisfy both. | One or both specs must be revised. See Step 3. | **How to diagnose:** @@ -71,7 +71,7 @@ When the "active" filter is specified, only active :Resource: items are returned **Strategy C: Revise the older spec.** If the older spec was under-specified and the new requirement reveals a better design, update the older spec. This is more disruptive — all conformance tests for that spec and everything after it may need re-rendering. -**Strategy D: Merge into one spec.** If the two specs describe overlapping behavior, combine them into a single, clear spec. Remove the redundant one. Be mindful of the 200 LOC limit. +**Strategy D: Merge into one spec.** If the two specs describe overlapping behavior, combine them into a single, clear spec. Remove the redundant one and its test plain file, moving any still valid scenario into the kept functionality's file. Be mindful of the 200 LOC limit. ### Step 4: Validate the Resolution @@ -79,7 +79,8 @@ After editing: 1. Re-read the full `***functional specs***` section to confirm no new conflicts were introduced. 2. Check that any revised spec still respects the 200 LOC limit. 3. Check that chronological ordering still makes sense (earlier specs should not depend on later ones). -4. If resolving via test adjustments, verify the `***test reqs***` or `***acceptance tests***` changes are appropriate. +4. If resolving via test adjustments, verify the `***test reqs***` or test plain file changes are appropriate. +5. For every revised functionality, invoke `add-test-scenarios` to bring its test plain file in step with the new text. ## Prevention (for use during add-functional-spec / add-functional-specs) @@ -99,3 +100,4 @@ Before adding a new spec, run through this quick conflict check: - [ ] No new conflicts introduced by the fix - [ ] Chronological ordering preserved - [ ] All referenced `:Concepts:` are still defined +- [ ] Test plain files of the revised functionalities brought in step via `add-test-scenarios` From 212223ed38230fb8378187c94f39e0d7021bb18c Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Thu, 15 Oct 2026 10:46:01 +0200 Subject: [PATCH 09/25] Add the migrate-plain-syntax skill Converts a whole project in one pass: renames requires and import to base and imports, gives every functional spec a # func-id heading approved by the user, moves nested acceptance tests into test plain files as acceptance scenarios, and drafts conformance scenarios with add-test-scenarios. Refs ENG-190 --- forge/skills/migrate-plain-syntax/SKILL.md | 172 +++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 forge/skills/migrate-plain-syntax/SKILL.md diff --git a/forge/skills/migrate-plain-syntax/SKILL.md b/forge/skills/migrate-plain-syntax/SKILL.md new file mode 100644 index 0000000..94b6294 --- /dev/null +++ b/forge/skills/migrate-plain-syntax/SKILL.md @@ -0,0 +1,172 @@ +--- +name: migrate-plain-syntax +description: >- + Convert a ***plain project from the old syntax to the current one: give + every functional spec a # func-id heading, move nested ***acceptance tests*** + into test plain files under tests//, draft conformance scenarios + with the user for every functionality, and rename the frontmatter directives + requires and import to base and imports. Use when a skill or plain-parser + reports the old syntax, or when the user asks to migrate or upgrade old + .plain files. +--- + +# Migrate Plain Syntax + +Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. This skill needs `module-structure.md`, `func-specs.md`, `semantic-ids.md`, `test-plain-files.md`, `base-modules.md`, and `import-modules.md`. + +## What changes + +| Old syntax | Current syntax | +|---|---| +| A functional spec is a top-level `- ` bullet with nested ` - ` sub-bullets | A functional spec is a `# ` heading, a statement line, and `- ` clarification bullets | +| `***acceptance tests***` nested under a functional spec | `***acceptance tests***` section of the functionality's test plain file `tests//.plain` | +| The renderer invents conformance tests | `***conformance tests***` section of the test plain file holds at least one scenario per functionality | +| `requires:` in the frontmatter | `base:` | +| `import:` in the frontmatter | `imports:` | + +There is no backward compatibility: a project in the old syntax does not render, and no other skill edits it. The migration converts the whole project in one pass. + +## Scope + +- Every `.plain` file of the project: the root modules, the import modules in the template directory, and every module reachable through `base` and `imports`. A half-migrated project fails the parser. +- Spec text is copied verbatim; only its shape changes. Rewording, trimming, and splitting are separate work for `add-feature`, `break-down-func-spec`, or `resolve-section-ownership` afterwards. +- Generated output under `plain_modules/` is never edited. Nothing is rendered. + +## Workflow + +### 1. Inventory and confirm + +1. List every `.plain` file in the project. Follow the `requires` / `base` and `import` / `imports` chains from each root module so no module is missed. +2. For each file, record which old-syntax markers it holds: top-level `- ` bullets in `***functional specs***`, nested `***acceptance tests***` sections, `requires:`, `import:`. +3. Read every `config.yaml` to learn whether conformance tests are enabled for each module (a `conformance-tests-script` entry). +4. Show the user the inventory (files, markers, conformance on or off per module) and get explicit confirmation before writing anything. + +### 2. Rename the directives + +In every frontmatter, rename the key `requires:` to `base:` and the key `import:` to `imports:`. Values stay unchanged. `{% include %}` lines and `exported_concepts` / `required_concepts` stay unchanged. + +### 3. Give every functionality a func-id + +For each root module, walk the `***functional specs***` section top to bottom: + +1. For every top-level `- ` bullet, propose a func-id per `semantic-ids.md`: a short slug that names the behavior (`add-task`, `export-csv`), unique in the module. Show the module's full list of proposed ids to the user in one `AskUserQuestion` and apply their edits. +2. Rewrite each functionality in place: + - The `# ` heading on its own line. + - The bullet text, without the leading `- `, as the statement line. A bare continuation line of the bullet is de-indented and kept as part of the statement. + - Each nested ` - ` sub-bullet de-indented by two spaces, so it becomes a `- ` clarification; deeper nesting is de-indented the same way. + - One blank line between functionalities. +3. Keep comment lines (`>`) and Liquid tags where they are. +4. Cut each nested `***acceptance tests***` block out of the core spec and keep its bullets for step 4, together with the func-id they belonged to. + +### 4. Move the acceptance tests into test plain files + +For every functionality that had nested acceptance tests, create `tests//.plain` with the frontmatter keys `module:` (the core spec's file name without `.plain`) and `func_id:`, an empty `***conformance tests***` section, and an `***acceptance tests***` section holding one scenario per old acceptance test: + +- A `# ` heading per test, with a slug derived from the test text, unique in the file. +- The old test text as the scenario body, verbatim. When the text already separates setup, action, and result, write it as `given:` / `when:` / `then:` lists; when a faithful split is not obvious, keep the prose. Free text is valid scenario text. + +Show each created file to the user as a snippet and apply their edits. + +### 5. Draft conformance scenarios + +When conformance tests are enabled for a module, every functionality needs at least one conformance scenario. Walk the module's functionalities in order and invoke `add-test-scenarios` for each one; it creates the test plain file when step 4 did not, drafts the scenarios from the functional spec, and reviews them with the user one functionality at a time. + +When `plain_modules//tests/` holds conformance tests from an earlier render, read the folder of the functionality first: each generated test is a candidate scenario the user can keep, edit, or drop, which is faster than drafting from the spec alone. + +When conformance tests are not enabled for a module, skip this step and say so; the test plain files created in step 4 keep an empty `***conformance tests***` section. + +### 6. Verify + +1. No `***functional specs***` section holds a top-level `- ` bullet; every functionality starts with a `# ` heading. +2. No core spec holds an `***acceptance tests***` section. +3. No frontmatter holds `requires:` or `import:`. +4. Func-ids are unique per module; scenario-ids are unique per functionality. +5. Every test plain file binds to an existing functionality; when conformance tests are enabled, every functionality has at least one conformance scenario. +6. The number of functionalities per module equals the number of top-level bullets before the migration, and every old acceptance test appears once as an acceptance scenario. +7. Run `plain-healthcheck`. Until the plain-parser release that parses the current syntax is installed, its parser step reports that it is suspended; the other checks still run. + +### 7. Report + +Per module: functionalities migrated, directives renamed, acceptance tests moved, test plain files created, conformance scenarios drafted, and anything the user still has to decide. Remind the user that the project renders only with a renderer that reads test plain files. + +## Rules + +- One pass over the whole project; never leave a module in the old syntax. +- Spec text verbatim; the shape changes, the words do not. +- The order of functionalities is unchanged; func-ids carry no order. +- Func-ids are proposed by this skill and approved by the user before they are written. +- Old acceptance tests become acceptance scenarios, never conformance scenarios; conformance scenarios are drafted from the spec with `add-test-scenarios`. +- Nothing under `plain_modules/` is edited. + +## Format + +Before: + +```plain +--- +requires: + - core +import: + - stack +--- + +***functional specs*** + +- A :User: can add a :Task:. Only valid :Task: items are added. + - The name must be at least 3 characters long. + + ***acceptance tests*** + - Adding a :Task: named "Buy milk" to an empty :TaskList: shows one :Task:. +``` + +After, the core spec: + +```plain +--- +base: + - core +imports: + - stack +--- + +***functional specs*** + +# add-task +A :User: can add a :Task:. Only valid :Task: items are added. +- The name must be at least 3 characters long. +``` + +After, `tests//add-task.plain`, with the conformance scenario drafted by `add-test-scenarios`: + +```plain +--- +module: +func_id: add-task +--- + +***conformance tests*** + +# reject-short-name +given: + - :App: is running with an empty :TaskList: +when: + - :User: adds a :Task: named "Go" +then: + - :TaskList: stays empty + +***acceptance tests*** + +# add-to-empty-list +Adding a :Task: named "Buy milk" to an empty :TaskList: shows one :Task:. +``` + +## Validation Checklist + +- [ ] Every `.plain` file of the project was inventoried and the user confirmed the pass +- [ ] `requires:` and `import:` renamed to `base:` and `imports:` in every frontmatter +- [ ] Every functionality has a `# ` heading approved by the user; text verbatim; order unchanged +- [ ] No `***acceptance tests***` section remains in a core spec; every old acceptance test is an acceptance scenario in a test plain file +- [ ] Every functionality has at least one conformance scenario when conformance tests are enabled for its module +- [ ] Func-ids unique per module; scenario-ids unique per functionality +- [ ] `plain-healthcheck` run; its report shown to the user +- [ ] Nothing under `plain_modules/` was edited From bdd3d45f658bd6ab31bf6a8198c2b7429460533f Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Thu, 15 Oct 2026 10:46:01 +0200 Subject: [PATCH 10/25] Warn on the old syntax in every skill that reads a spec Each spec-reading skill checks the target file for top-level - bullets in functional specs, a nested acceptance tests section, or requires: / import: in the frontmatter, and stops with a pointer to migrate-plain-syntax instead of editing the file. Refs ENG-190 --- forge/skills/add-concept/SKILL.md | 2 ++ forge/skills/add-feature/SKILL.md | 2 ++ forge/skills/add-functional-spec/SKILL.md | 2 ++ forge/skills/add-functional-specs/SKILL.md | 2 ++ forge/skills/add-implementation-requirement/SKILL.md | 2 ++ forge/skills/add-resource/SKILL.md | 2 ++ forge/skills/add-template/SKILL.md | 2 ++ forge/skills/add-test-requirement/SKILL.md | 2 ++ forge/skills/analyze-2-func-specs/SKILL.md | 2 ++ forge/skills/analyze-func-specs/SKILL.md | 2 ++ forge/skills/analyze-if-func-spec-too-complex/SKILL.md | 2 ++ forge/skills/break-down-func-spec/SKILL.md | 2 ++ forge/skills/consolidate-concepts/SKILL.md | 2 ++ forge/skills/create-base-module/SKILL.md | 2 ++ forge/skills/create-import-module/SKILL.md | 2 ++ forge/skills/debug-specs/SKILL.md | 2 ++ forge/skills/forge-plain/SKILL.md | 2 ++ forge/skills/init-config-file/SKILL.md | 2 +- forge/skills/load-plain-reference/SKILL.md | 1 + forge/skills/refactor-module/SKILL.md | 2 ++ forge/skills/resolve-section-ownership/SKILL.md | 2 ++ forge/skills/resolve-spec-conflict/SKILL.md | 2 ++ 22 files changed, 42 insertions(+), 1 deletion(-) diff --git a/forge/skills/add-concept/SKILL.md b/forge/skills/add-concept/SKILL.md index 946cb1d..6a29b71 100644 --- a/forge/skills/add-concept/SKILL.md +++ b/forge/skills/add-concept/SKILL.md @@ -10,6 +10,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## Workflow 1. **Identify the target `.plain` file.** If ambiguous, ask the user. diff --git a/forge/skills/add-feature/SKILL.md b/forge/skills/add-feature/SKILL.md index f82b589..9e2394f 100644 --- a/forge/skills/add-feature/SKILL.md +++ b/forge/skills/add-feature/SKILL.md @@ -15,6 +15,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + `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 begins with a short intent phase, then runs the authoring loop scoped to one feature. ## Core loop: one question → one answer → write to disk diff --git a/forge/skills/add-functional-spec/SKILL.md b/forge/skills/add-functional-spec/SKILL.md index 70dae2f..ea6d065 100644 --- a/forge/skills/add-functional-spec/SKILL.md +++ b/forge/skills/add-functional-spec/SKILL.md @@ -15,6 +15,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## Workflow 1. **Identify the target `.plain` file.** If ambiguous, ask the user. diff --git a/forge/skills/add-functional-specs/SKILL.md b/forge/skills/add-functional-specs/SKILL.md index ee3da89..152a683 100644 --- a/forge/skills/add-functional-specs/SKILL.md +++ b/forge/skills/add-functional-specs/SKILL.md @@ -14,6 +14,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## When to Use - The user asks for several functional specs to be added in one go (e.g. "add these three specs", "spec out this feature in a few bullets"). diff --git a/forge/skills/add-implementation-requirement/SKILL.md b/forge/skills/add-implementation-requirement/SKILL.md index 0b3432b..17b7242 100644 --- a/forge/skills/add-implementation-requirement/SKILL.md +++ b/forge/skills/add-implementation-requirement/SKILL.md @@ -12,6 +12,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## Workflow 1. **Identify the target `.plain` file.** If ambiguous, ask the user. diff --git a/forge/skills/add-resource/SKILL.md b/forge/skills/add-resource/SKILL.md index 0620017..30eb85d 100644 --- a/forge/skills/add-resource/SKILL.md +++ b/forge/skills/add-resource/SKILL.md @@ -11,6 +11,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + Linked resources are external files referenced from within a `.plain` spec using markdown link syntax. The file contents are passed to the renderer alongside the spec, providing additional context for code generation. ## Workflow diff --git a/forge/skills/add-template/SKILL.md b/forge/skills/add-template/SKILL.md index d8e3a55..5d83a30 100644 --- a/forge/skills/add-template/SKILL.md +++ b/forge/skills/add-template/SKILL.md @@ -10,6 +10,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + Templates use Liquid syntax (`{% include %}`) to reuse the same spec content across multiple `.plain` files. Each inclusion can pass different parameters, producing tailored output from a single source. This is distinct from `imports` and `base` — templates are about textual reuse with parameterization, not module dependencies. ## When to Use Templates diff --git a/forge/skills/add-test-requirement/SKILL.md b/forge/skills/add-test-requirement/SKILL.md index 0661e34..6f32df7 100644 --- a/forge/skills/add-test-requirement/SKILL.md +++ b/forge/skills/add-test-requirement/SKILL.md @@ -11,6 +11,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## Workflow 1. **Identify the target `.plain` file.** If ambiguous, ask the user. diff --git a/forge/skills/analyze-2-func-specs/SKILL.md b/forge/skills/analyze-2-func-specs/SKILL.md index b5c499f..2be3530 100644 --- a/forge/skills/analyze-2-func-specs/SKILL.md +++ b/forge/skills/analyze-2-func-specs/SKILL.md @@ -11,6 +11,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## Input Two functional specs from a `.plain` file (or across `base` modules). The user provides them directly or points to them by location in a file. diff --git a/forge/skills/analyze-func-specs/SKILL.md b/forge/skills/analyze-func-specs/SKILL.md index 7e7c03f..6260c10 100644 --- a/forge/skills/analyze-func-specs/SKILL.md +++ b/forge/skills/analyze-func-specs/SKILL.md @@ -11,6 +11,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## Input A **set of two or more functional specs** to check against each other. Callers typically supply one of: 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 12b2b22..c1d5e96 100644 --- a/forge/skills/analyze-if-func-spec-too-complex/SKILL.md +++ b/forge/skills/analyze-if-func-spec-too-complex/SKILL.md @@ -12,6 +12,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## Why This Matters The renderer enforces a hard complexity limit: each functional spec must imply a **maximum of 200 changed lines of code**. If a spec exceeds this, the renderer rejects it with "Functional spec too complex!" and the spec must be broken down. Catching this before insertion saves a failed render cycle. diff --git a/forge/skills/break-down-func-spec/SKILL.md b/forge/skills/break-down-func-spec/SKILL.md index 2571792..f179703 100644 --- a/forge/skills/break-down-func-spec/SKILL.md +++ b/forge/skills/break-down-func-spec/SKILL.md @@ -10,6 +10,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## When to Use - `analyze-if-func-spec-too-complex` flagged a spec as TOO COMPLEX. diff --git a/forge/skills/consolidate-concepts/SKILL.md b/forge/skills/consolidate-concepts/SKILL.md index f4e08b7..4248b26 100644 --- a/forge/skills/consolidate-concepts/SKILL.md +++ b/forge/skills/consolidate-concepts/SKILL.md @@ -11,6 +11,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## When to Use - Multiple modules define the same or closely related concepts independently. diff --git a/forge/skills/create-base-module/SKILL.md b/forge/skills/create-base-module/SKILL.md index e3720b0..71e6aa3 100644 --- a/forge/skills/create-base-module/SKILL.md +++ b/forge/skills/create-base-module/SKILL.md @@ -11,6 +11,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## What Base Does `base` attaches the current module to the project's build chain. The base 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 base module, but their outputs are never independent: the current module's code physically contains the base module's. diff --git a/forge/skills/create-import-module/SKILL.md b/forge/skills/create-import-module/SKILL.md index 6f349f2..c59b186 100644 --- a/forge/skills/create-import-module/SKILL.md +++ b/forge/skills/create-import-module/SKILL.md @@ -11,6 +11,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## What an Import Module Is An import module is a `.plain` file that lives in the **`template/`** directory and contains **only** `***definitions***`, `***implementation reqs***`, and/or `***test reqs***`. It must **not** contain `***functional specs***` and must **not** use `base` in its frontmatter. Other modules pull in its content via the `imports` field in their YAML frontmatter, gaining access to its definitions and reqs. diff --git a/forge/skills/debug-specs/SKILL.md b/forge/skills/debug-specs/SKILL.md index c63e6cb..0f1bca0 100644 --- a/forge/skills/debug-specs/SKILL.md +++ b/forge/skills/debug-specs/SKILL.md @@ -11,6 +11,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## When to Use - The user observes a bug in the running application (visual, behavioral, crash, performance). diff --git a/forge/skills/forge-plain/SKILL.md b/forge/skills/forge-plain/SKILL.md index a5f2c33..e6f67e2 100644 --- a/forge/skills/forge-plain/SKILL.md +++ b/forge/skills/forge-plain/SKILL.md @@ -14,6 +14,8 @@ description: >- 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. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## Role 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. diff --git a/forge/skills/init-config-file/SKILL.md b/forge/skills/init-config-file/SKILL.md index 15c7158..07620c9 100644 --- a/forge/skills/init-config-file/SKILL.md +++ b/forge/skills/init-config-file/SKILL.md @@ -73,7 +73,7 @@ Before emitting anything, state the planned split to the user (e.g. "I'll emit ` ### Step 1 — Inventory -1. List every `.plain` file in the repo and identify the top modules (modules not listed under `base` by any other module) — same procedure as `plain-healthcheck` Step 1. +1. List every `.plain` file in the repo and identify the top modules (modules not listed under `base` by any other module) — same procedure as `plain-healthcheck` Step 1. A frontmatter that still uses `requires:` or `import:` is the old syntax: stop and point the user to `migrate-plain-syntax`. 2. For each top module, determine which part it belongs to (single-stack → one part; multi-part → one part per top module). 3. List every script under `test_scripts/` and group them by part (e.g. `*_python.sh` belongs to the backend part, `*_js.sh` belongs to the frontend part). 4. Identify the template directory (typically `template/`) and any custom resource directories (typically `resources/`). diff --git a/forge/skills/load-plain-reference/SKILL.md b/forge/skills/load-plain-reference/SKILL.md index a14e3d8..ed8d152 100644 --- a/forge/skills/load-plain-reference/SKILL.md +++ b/forge/skills/load-plain-reference/SKILL.md @@ -52,6 +52,7 @@ All paths are relative to this `SKILL.md`. | Linked files or `resources/` | `../../rules/linked-resources.md` | | Any `.plain` text or example | `../../rules/bullet-continuation.md`, `../../rules/concise-specs.md` | | Drafting or trimming spec wording, or reviewing a spec for verbosity | `../../rules/concise-specs-examples.md` | +| A file that may be in the old syntax (`- ` functional specs, nested `***acceptance tests***`, `requires:` / `import:`) | `../../rules/module-structure.md` (*Old syntax*), then the `migrate-plain-syntax` skill | For a whole-file review or a task spanning several sections, load the union of the applicable rule files. Do not load every rule by default. diff --git a/forge/skills/refactor-module/SKILL.md b/forge/skills/refactor-module/SKILL.md index b181f8e..af52ad0 100644 --- a/forge/skills/refactor-module/SKILL.md +++ b/forge/skills/refactor-module/SKILL.md @@ -11,6 +11,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## When to Use - A module has many functional specs spanning multiple distinct domains or feature areas. diff --git a/forge/skills/resolve-section-ownership/SKILL.md b/forge/skills/resolve-section-ownership/SKILL.md index afe2170..423ebcc 100644 --- a/forge/skills/resolve-section-ownership/SKILL.md +++ b/forge/skills/resolve-section-ownership/SKILL.md @@ -8,6 +8,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + This is the ownership counterpart to `break-down-func-spec`. That skill splits one functional spec into several because it is **too large**; this one moves facts between sections because they are in the **wrong place**. The two are unrelated: a correctly placed spec can be too large, and a one-line bullet can be misplaced. ## When to Use diff --git a/forge/skills/resolve-spec-conflict/SKILL.md b/forge/skills/resolve-spec-conflict/SKILL.md index aa64d49..904e273 100644 --- a/forge/skills/resolve-spec-conflict/SKILL.md +++ b/forge/skills/resolve-spec-conflict/SKILL.md @@ -11,6 +11,8 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + ## When This Applies A conflict exists (or is suspected) when: From 44070417955ed6d28b76cf782b91d8cb6d3effb4 Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Thu, 15 Oct 2026 10:48:02 +0200 Subject: [PATCH 11/25] Update init-plain-project and plain-healthcheck for test plain files init-plain-project creates tests/ when conformance testing is enabled and names its scaffold template stack.plain, so imports: [stack] does not read like the base: directive. plain-healthcheck inventories test plain files, checks that every functionality has a conformance scenario when conformance tests are enabled, reports the old syntax with a pointer to migrate-plain-syntax, and probes plain-parser with a current-syntax module: when the installed parser rejects it, step 3a is suspended with a clear message until the parser release that reads the new syntax is installed. Refs ENG-190 --- forge/skills/init-plain-project/SKILL.md | 39 ++++++++++-------- .../references/checklist.md | 9 ++-- forge/skills/plain-healthcheck/SKILL.md | 41 ++++++++++++++----- 3 files changed, 57 insertions(+), 32 deletions(-) diff --git a/forge/skills/init-plain-project/SKILL.md b/forge/skills/init-plain-project/SKILL.md index ad088f4..cea7cb9 100644 --- a/forge/skills/init-plain-project/SKILL.md +++ b/forge/skills/init-plain-project/SKILL.md @@ -3,7 +3,7 @@ name: init-plain-project description: >- Lightweight initializer for a new ***plain project. Asks about the base technology and project kind, asks whether conformance testing is enabled, - then scaffolds a template import module (with base implementation reqs and + then scaffolds a template import module (with the stack's implementation reqs and test reqs), a stub top-level module, the unit testing script, and (optionally) the conformance and prepare-environment scripts plus a config.yaml. Does NOT run `plain-healthcheck`. Use when the user wants a @@ -17,21 +17,22 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule ## Scope -This skill is intentionally **minimal**. It produces a runnable project skeleton — not a complete spec. **No `***definitions***` sections are written by this skill — not in the template module, not in the top module.** No functional specs, no concepts, no acceptance tests, no validation. The user will fill those in afterwards with `add-feature`, `add-concept`, `add-functional-spec`, etc. +This skill is intentionally **minimal**. It produces a runnable project skeleton — not a complete spec. **No `***definitions***` sections are written by this skill — not in the template module, not in the top module.** No functional specs, no concepts, no test plain files, no validation. The user will fill those in afterwards with `add-feature`, `add-concept`, `add-functional-spec`, etc. What this skill writes: -- `template/base.plain` — an import module with the project's base `***implementation reqs***` and `***test reqs***`. -- `.plain` — a top-level module that imports `base`. Frontmatter only — no body sections. The user adds `***functional specs***` later. +- `template/stack.plain` — an import module with the project's stack-wide `***implementation reqs***` and `***test reqs***`. +- `.plain` — a top-level module that imports `stack`. Frontmatter only — no body sections. The user adds `***functional specs***` later, each with its test plain file under `tests//`. - `test_scripts/run_unittests_.` — always. - `test_scripts/run_conformance_tests_.` — only if conformance testing is enabled. - `test_scripts/prepare_environment_.` — only if conformance testing is enabled **and** the user opts in. - `config.yaml` at the project root — wired to whichever scripts were generated. +- `tests/` — an empty folder at the project root, only if conformance testing is enabled. `add-functional-spec` later writes each functionality's test plain file to `tests//.plain`. What this skill does **not** do: - Run `plain-healthcheck`. -- Author concepts, functional specs, or acceptance tests. +- Author concepts, functional specs, or test plain files. - Probe the host environment (`check-plain-env` is a separate step the user can run later). ## Workflow @@ -51,28 +52,28 @@ Follow up with a free-form prompt for anything the options didn't cover (e.g. sp Use **AskUserQuestion** for one tight batch covering: - **Unit testing framework** — propose the canonical one for the chosen language (pytest, Jest, Go `testing`, JUnit, ...) and let the user override. -- **Conformance testing** — enabled or not. If the user is unsure, briefly explain it adds a `run_conformance_tests` script and per-spec acceptance tests later. +- **Conformance testing** — enabled or not. If the user is unsure, briefly explain it adds a `run_conformance_tests` script and, for every functionality added later, a test plain file with conformance scenarios. - **Prepare-environment script** — only ask this if conformance testing was enabled in the previous question. Generate one or skip. Recommend "yes" when dependencies need installing (most stacks); recommend "skip" when there is genuinely nothing to set up. If conformance testing is disabled, skip this question entirely and do not generate a prepare-environment script. -### 3. Author `template/base.plain` +### 3. Author `template/stack.plain` Create the import module with `create-import-module`. It must contain: -- `***implementation reqs***` — the base stack as requirements **and everything about `:UnitTests:`**: +- `***implementation reqs***` — the stack as requirements **and everything about `:UnitTests:`**: - Programming language and version. - Primary framework (if any). - Dependency / package manager. - Project kind as a constraint (e.g. ":Implementation: should be a REST API service."). - `:UnitTests:` framework (pytest / Jest / JUnit / Go's `testing` / …) and the command used to run them — phrased in terms of `:UnitTests:` so the partition is explicit. - Anything else the user added in the free-form catch-all. -- `***test reqs***` — the base **conformance**-testing rules (only added if conformance testing is enabled): +- `***test reqs***` — the **conformance**-testing rules (only added if conformance testing is enabled): - ":ConformanceTests: must be implemented and executed - do not skip tests." - The `:ConformanceTests:` framework and the command used to run them. - **Do NOT** put unit-test framework / command here — that lives in `***implementation reqs***` above. -Do **not** add a `***definitions***` section to `template/base.plain`. This skill does not author any concepts. Use only the predefined concepts (`:Implementation:`, `:ConformanceTests:`) in the reqs — no project-specific concepts like `:AppName:` or `:App:`. Do not declare `required_concepts` either. +Do **not** add a `***definitions***` section to `template/stack.plain`. This skill does not author any concepts. Use only the predefined concepts (`:Implementation:`, `:ConformanceTests:`) in the reqs — no project-specific concepts like `:AppName:` or `:App:`. Do not declare `required_concepts` either. -Do **not** add `***functional specs***` to `template/base.plain` — it is an import module. +Do **not** add `***functional specs***` to `template/stack.plain` — it is an import module. ### 4. Author the top module `.plain` @@ -81,7 +82,7 @@ Create it at the repo root with frontmatter: ```yaml --- imports: - - base + - stack description: --- ``` @@ -90,7 +91,11 @@ Body: **none**. The file ends after the closing `---` of the frontmatter. Do **n The file must still be created on disk — an empty body is fine, but the file itself is required. -### 5. Generate the testing scripts +### 5. Create the `tests/` folder + +Only if conformance testing is enabled: create an empty `tests/` folder at the project root. Test plain files land in `tests//` as functionalities are added. If conformance testing is disabled, create nothing. + +### 6. Generate the testing scripts Call the corresponding implementation skills in order. Each skill knows how to write the right Bash or PowerShell variant for the host OS. @@ -98,7 +103,7 @@ Call the corresponding implementation skills in order. Each skill knows how to w - `implement-conformance-testing-script` — only if conformance testing was enabled. If a prepare-environment script will also exist, choose the **activate-only** variant; otherwise the **install-inline** variant. - `implement-prepare-environment-script` — only if conformance testing was enabled **and** the user opted in. -### 6. Write `config.yaml` +### 7. Write `config.yaml` Create `config.yaml` at the project root with **only the keys for scripts that were actually generated**. Valid keys: @@ -113,12 +118,12 @@ Always include `template_dir: template` because the project has a template impor Do **not** invoke `init-config-file` or `plain-healthcheck` here — keeping this skill light is the whole point. The user can run them later if they want a strict canonicalization or validation. -### 7. Recap +### 8. Recap Tell the user what was created and what's next: -- Files written: `template/base.plain`, `.plain`, the test scripts, `config.yaml`. -- 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`. +- Files written: `template/stack.plain`, `.plain`, the test scripts, `config.yaml`, and the `tests/` folder when conformance testing is enabled. +- Suggested next steps: add concepts with `add-concept`, add functional specs with `add-functional-spec` or `add-functional-specs` (each new functionality gets its test plain file under `tests//` when conformance testing is enabled), or jump straight into `add-feature`. - Mention that the project has not been validated — they can run `plain-healthcheck` when they want validation. ## Validation checklist diff --git a/forge/skills/init-plain-project/references/checklist.md b/forge/skills/init-plain-project/references/checklist.md index 7674b8b..ccd108d 100644 --- a/forge/skills/init-plain-project/references/checklist.md +++ b/forge/skills/init-plain-project/references/checklist.md @@ -13,16 +13,17 @@ Use this to verify the scaffold was produced correctly — never as a substitute ## 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 (`imports: [base]`, `description`) and **no body sections** — the file exists on disk. +- [ ] Created `template/stack.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/stack.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/stack.plain`; only predefined concepts (`:Implementation:`, `:ConformanceTests:`) used; no `required_concepts` declared. +- [ ] Created `.plain` at the repo root with frontmatter only (`imports: [stack]`, `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. +- [ ] Created an empty `tests/` folder at the project root only when conformance testing is enabled. - [ ] 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`, or `check-plain-env` (out of scope for this skill). diff --git a/forge/skills/plain-healthcheck/SKILL.md b/forge/skills/plain-healthcheck/SKILL.md index ba1db60..dbc4a07 100644 --- a/forge/skills/plain-healthcheck/SKILL.md +++ b/forge/skills/plain-healthcheck/SKILL.md @@ -3,8 +3,8 @@ name: plain-healthcheck description: >- Verification gate for a ***plain project. Verifies that every `config.yaml` exists and points at scripts that actually live in `test_scripts/`, parses - every top module with `plain-parser check`, and checks every module for what - the parser can't judge (section ownership, bullets, linked resources). Run this whenever anything in the project is finalized — + every top module with `plain-parser check`, and checks every module and test plain file for + what the parser can't judge (section ownership, bullets, scenarios, linked resources). Run this whenever anything in the project is finalized — including (but not limited to) the end of `forge-plain`, the end of `add-feature`, after `debug-specs`, after any single-skill edit that finalizes a concept, functional spec, requirement, template, or config — @@ -40,9 +40,10 @@ The skill is a **detect → fix → re-run** loop. It does not stop at the first 2. Identify **top modules** — every module that is not listed under `base` 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/`. -5. Pair each top module with the `config.yaml` that governs it. The pairing rule is: the config file in the same directory as the top module wins; failing that, the repo-root `config.yaml`. A multi-part project must have one config per part — record any top module that has no governing config as a failure. +5. List every test plain file: each `.plain` file under `tests/` (and under any other folder the user names) whose frontmatter has `module:` and `func_id:`. Record the pair it binds to. +6. Pair each top module with the `config.yaml` that governs it. The pairing rule is: the config file in the same directory as the top module wins; failing that, the repo-root `config.yaml`. A multi-part project must have one config per part — record any top module that has no governing config as a failure. -Print a one-line inventory summary so the rest of the run is easy to follow, e.g. `Top modules: backend/api.plain (config: backend/config.yaml), frontend/web.plain (config: frontend/config.yaml). Scripts in test_scripts/: 4.` +Print a one-line inventory summary so the rest of the run is easy to follow, e.g. `Top modules: backend/api.plain (config: backend/config.yaml), frontend/web.plain (config: frontend/config.yaml). Scripts in test_scripts/: 4. Test plain files: 12.` ### Step 2 — Validate every `config.yaml` @@ -77,24 +78,40 @@ Step 3 has two halves: a mechanical parse by `plain-parser`, then the checks tha [`plain-parser`](https://pypi.org/project/plain-parser/) is the ***plain parser. It parses a module together with its whole `imports` / `base` chain, so running it on every top module covers every module in the project. 1. **Check it is installed.** Run `plain-parser --help`. If the command is not found, look for it where `plain-forge install` puts it before giving up: `~/.local/bin/plain-parser` (`%USERPROFILE%\.local\bin\plain-parser.exe` on Windows), or the directory printed by `uv tool dir --bin`. If it is there, run it by that full path for the rest of this step and tell the user to add the directory to `PATH`. If it is nowhere, stop and report `FAIL` with the install command (`pipx install plain-parser` or `uv tool install plain-parser`; it needs Python 3.11 or newer). Do **not** fall back to checking these rules by reading the files — without the parser there is no syntax gate. -2. **Run it from the top module's own directory**, once per top module: +2. **Check that it parses the current syntax.** Write this probe module as `probe.plain` into a fresh temporary directory and run `plain-parser check probe.plain` there: + + ```plain + ***implementation reqs*** + + - :Implementation: is written in Python 3.12. + + ***functional specs*** + + # probe + :Implementation: starts. + ``` + + If it prints `OK`, continue. If it reports an error, the installed plain-parser predates the current syntax (`# func-id` headings, test plain files, `base:` / `imports:`). Then record one failure against the project — `plain-parser does not parse the current ***plain syntax; install the release that does and run plain-healthcheck again` — skip steps 3 and 4 of 3a, still run 3b, and report `FAIL`. Never rewrite the specs into an older syntax to satisfy the old parser. +3. **Run it from the top module's own directory**, once per top module: ```bash cd && plain-parser check .plain ``` Add `--config-name ` when the governing config is not named `config.yaml`. `plain-parser` reads `template-dir` from the config next to the `.plain` file or in the working directory, and exits `2` when it finds one in both — running from the module's own directory avoids that in multi-part projects. -3. **Read the result.** Exit `0` prints `: OK`. Exit `1` prints `Error: …` on stderr — record that message verbatim as a failure against the top module. Exit `2` is a usage or config error (ambiguous or malformed config) — record it against the config. +4. **Read the result.** Exit `0` prints `: OK`. Exit `1` prints `Error: …` on stderr — record that message verbatim as a failure against the top module. Exit `2` is a usage or config error (ambiguous or malformed config) — record it against the config. -`plain-parser` checks, among other things: frontmatter, that every `imports` / `base` entry exists and the graph has no cycles, that `base` modules have one fixed order, that imported modules have no functional specs, section headings (allowed, not duplicated, `***definitions***` first, `***acceptance tests***` nested under a functional spec), concepts (defined, exported and required correctly), that a module with functional specs has implementation reqs, and that every linked resource and template resolves to an existing, relative, non-`.plain` file. It does not always report every error in one run, so after fixing what it reported, run it again until it prints `OK`. +`plain-parser` checks, among other things: frontmatter, that every `imports` / `base` entry exists and the graph has no cycles, that `base` modules have one fixed order, that imported modules have no functional specs, section headings (allowed, not duplicated, `***definitions***` first), `# func-id` headings in `***functional specs***` with unique func-ids, test plain files (frontmatter `module:` and `func_id:` naming an existing functionality, both sections present, unique scenario-ids), concepts (defined, exported and required correctly), that a module with functional specs has implementation reqs, and that every linked resource and template resolves to an existing, relative, non-`.plain` file. It does not always report every error in one run, so after fixing what it reported, run it again until it prints `OK`. #### 3b. Check what the parser can't judge Check every `.plain` module against the rules loaded by `load-plain-reference`: 1. **Section ownership.** No fact sits in a section that doesn't own it — `:UnitTests:` facts only in `***implementation reqs***`, `:ConformanceTests:` facts only in `***test reqs***`, technology only in `***implementation reqs***`. -2. **Bullets.** Every line in every section is a valid `- ` list item or a correctly indented sub-bullet — no bare continuation lines. +2. **Bullets and headings.** Every line in `***definitions***`, `***implementation reqs***`, and `***test reqs***` is a valid `- ` list item or a correctly indented sub-bullet — no bare continuation lines. Every functionality in `***functional specs***` is a `# ` heading followed by its body. 3. **Linked resources.** Every linked resource is a single **text** file (not a binary), and no URL or folder path appears in the surrounding spec text unless it is a runtime value the generated software itself uses. +4. **Test plain files.** Every test plain file binds to an existing functionality (`module:` names a module, `func_id:` one of its `# func-id` headings) and has both sections in order. Scenario-ids are unique per functionality across every file bound to it. When the module's `config.yaml` has a `conformance-tests-script`, every functionality has at least one conformance scenario; a functionality with none is a failure that names `add-test-scenarios`. +5. **Old syntax.** A top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in a frontmatter is a failure that names `migrate-plain-syntax`. Do not fix it inline. Functional-spec complexity is checked when a spec is authored (`add-functional-spec(s)` → `analyze-if-func-spec-too-complex`). Re-run `analyze-if-func-spec-too-complex` here only for a spec that was hand-edited since it was authored. @@ -105,7 +122,7 @@ Treat 3a and 3b together as a hard gate: the healthcheck **only passes** when `p Iterate until it passes: 1. Identify the offending `.plain` file, the line, and the kind of issue: missing or duplicated concept, cyclic definition, broken `imports`/`base`, misplaced fact, bare continuation line, missing resource or template, etc. -2. Fix only the `.plain` files (or the relevant `config.yaml` / template) using the appropriate edit skill — `add-concept`, `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `resolve-section-ownership`, `resolve-spec-conflict`, `break-down-func-spec`, `consolidate-concepts`, or an inline edit. **Never** modify generated code under `plain_modules//code/` or `plain_modules//tests/`. +2. Fix only the `.plain` files (or the relevant `config.yaml` / template) using the appropriate edit skill — `add-concept`, `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `add-test-scenarios`, `resolve-section-ownership`, `resolve-spec-conflict`, `break-down-func-spec`, `consolidate-concepts`, or an inline edit — or `migrate-plain-syntax` for a file in the old syntax. **Never** modify generated code under `plain_modules//code/` or `plain_modules//tests/`. 3. If you are uncertain about ***plain syntax for the failing construct, re-load `load-plain-reference` before fixing. 4. Re-run the failed checks — for a `plain-parser` failure, re-run `plain-parser check`. Repeat until they pass. @@ -115,7 +132,7 @@ If the failure is something the healthcheck cannot reasonably fix on its own (e. Emit one of: -- **`PASS`** — followed by a short summary of what was checked: `N config.yaml(s) validated`, `M module(s) checked (plain-parser OK for every top module)`, `K scripts referenced`. The caller (`forge-plain`, `add-feature`, `debug-specs`) can then continue to its hand-off step. +- **`PASS`** — followed by a short summary of what was checked: `N config.yaml(s) validated`, `M module(s) checked (plain-parser OK for every top module)`, `T test plain file(s) bound`, `K scripts referenced`. The caller (`forge-plain`, `add-feature`, `debug-specs`) can then continue to its hand-off step. - **`FAIL`** — followed by a numbered list of every unresolved problem. Each entry must include: the file (config / `.plain` / script) it applies to, the concrete issue, and what the user needs to decide if the healthcheck couldn't resolve it. The verdict goes on the first line so callers can pattern-match without parsing the whole report. @@ -136,6 +153,8 @@ The verdict goes on the first line so callers can pattern-match without parsing - [ ] No `config.yaml` mixes stacks (e.g. Python + JS scripts in the same file) - [ ] No `config.yaml` declares `prepare-environment-script` without also declaring `conformance-tests-script` - [ ] Every `test_scripts/*` file is referenced by some `config.yaml` (or surfaced as a warning) +- [ ] The parser probe passed, or the report says 3a is suspended and names the plain-parser release that is needed - [ ] `plain-parser check` printed `OK` for every top module, run from that module's directory -- [ ] Every module passes every check in Step 3b (section ownership, bullets, linked resources) +- [ ] Every module passes every check in Step 3b (section ownership, bullets and headings, linked resources, test plain files, old syntax) +- [ ] Every test plain file binds to an existing functionality, and every functionality has at least one conformance scenario when conformance tests are enabled - [ ] Verdict (`PASS` / `FAIL`) is on the first line, with a numbered list of remaining problems if it failed From 5491930daa8332874840b2bb2ffffa697d05c694 Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Thu, 15 Oct 2026 10:48:02 +0200 Subject: [PATCH 12/25] Route the new rules from load-plain-reference and add a glossary load-plain-reference routes functional specs to semantic-ids.md, test plain files and scenarios to test-plain-files.md, and every .plain task to the new glossary.md that fixes the terms: core spec, functionality, func-id, test plain file, scenario, scenario-id, conformance and acceptance scenario. The project-model and rendering references show the tests/ folder and the one-test-per-scenario rule. Refs ENG-190 --- forge/rules/glossary.md | 21 +++++++++++++++++++ forge/skills/load-plain-reference/SKILL.md | 7 ++++--- .../references/project-model.md | 3 ++- .../references/rendering-and-testing.md | 12 ++++++----- 4 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 forge/rules/glossary.md diff --git a/forge/rules/glossary.md b/forge/rules/glossary.md new file mode 100644 index 0000000..c44625d --- /dev/null +++ b/forge/rules/glossary.md @@ -0,0 +1,21 @@ +--- +description: Terms used in .plain files, in plain-forge, and in plain-parser; use them as written +--- + +# Glossary + +These are the names used across the rules, the skills, and the parser. Use them as written; do not coin synonyms. + +- **Core spec** — a `.plain` file with functional specs, a root module. Test definitions do not live here. +- **Import module** — a `.plain` file in the template directory that other modules pull in with `imports:`; definitions and reqs only (see `import-modules.md`). +- **Base module** — a root module that another root module lists under `base:` to build on its generated code and functional specs (see `base-modules.md`). +- **Functionality** — one functional spec in a module, identified by its func-id. +- **Func-id** — the slug in the `# ` heading of a functionality. Lowercase letters, digits, and hyphens; unique in the module; written as `:` when the module is needed. It needs no meaning (see `semantic-ids.md`). +- **Test plain file** — a `.plain` file that holds the scenarios of one functionality. Its frontmatter keys `module:` and `func_id:` bind it to that functionality; it has a `***conformance tests***` and an `***acceptance tests***` section. By convention it lives at `tests//.plain`. Several may bind to one functionality; their scenarios are merged (see `test-plain-files.md`). +- **Scenario** — one test case in a test plain file: a `# ` heading followed by free text that says what is done and what result is expected. plain-forge writes `given:` / `when:` / `then:`. The renderer never writes a scenario. +- **Scenario-id** — the slug in a scenario heading. Same syntax as a func-id; unique across the scenarios of one functionality after merging. The generated test carries it. +- **Conformance scenario** — a scenario in `***conformance tests***`. The renderer implements every conformance scenario and nothing else. A functionality is done only when all of its conformance tests pass. When conformance tests are enabled, every functionality has at least one. +- **Acceptance scenario** — a scenario in `***acceptance tests***`: an end-to-end check that may span more than one functionality, rendered after the conformance tests of its functionality. +- **Conformance test** — generated test code that implements one conformance scenario. It runs the implementation as a black box and is labelled with the scenario-id. +- **Acceptance test** — generated test code that implements one acceptance scenario, labelled with the scenario-id. +- **Old syntax** — functional specs as `- ` bullets, nested `***acceptance tests***`, and the `requires:` / `import:` directives. Not supported; `migrate-plain-syntax` converts a project. diff --git a/forge/skills/load-plain-reference/SKILL.md b/forge/skills/load-plain-reference/SKILL.md index ed8d152..224963b 100644 --- a/forge/skills/load-plain-reference/SKILL.md +++ b/forge/skills/load-plain-reference/SKILL.md @@ -2,7 +2,7 @@ name: load-plain-reference description: >- Loads the applicable ***plain authoring rules and operational references for the current task. - Covers section ownership, concepts, modules, resources, integrations, rendering, and testing + Covers section ownership, concepts, semantic IDs, test plain files, modules, resources, integrations, rendering, and testing without loading unrelated guidance. Use when authoring, editing, reviewing, or debugging .plain files, or before invoking another skill that reads or writes .plain content. --- @@ -42,7 +42,8 @@ All paths are relative to this `SKILL.md`. | New module, file skeleton, section placement, or comments | `../../rules/module-structure.md` | | Moving facts between sections (see `resolve-section-ownership`) | `../../rules/module-structure.md`, plus the rule file of each section involved | | Definitions or concept usage | `../../rules/definitions.md` | -| Functional specs or acceptance tests | `../../rules/func-specs.md` | +| Functional specs | `../../rules/func-specs.md`, `../../rules/semantic-ids.md` | +| Test plain files or scenarios | `../../rules/test-plain-files.md`, `../../rules/semantic-ids.md` | | Implementation requirements or unit tests | `../../rules/impl-reqs.md` | | Test requirements or conformance tests | `../../rules/test-reqs.md` | | `imports` and import modules | `../../rules/import-modules.md` | @@ -50,7 +51,7 @@ All paths are relative to this `SKILL.md`. | `required_concepts` | `../../rules/required-concepts.md` | | `exported_concepts` | `../../rules/exported-concepts.md` | | Linked files or `resources/` | `../../rules/linked-resources.md` | -| Any `.plain` text or example | `../../rules/bullet-continuation.md`, `../../rules/concise-specs.md` | +| Any `.plain` text or example | `../../rules/glossary.md`, `../../rules/bullet-continuation.md`, `../../rules/concise-specs.md` | | Drafting or trimming spec wording, or reviewing a spec for verbosity | `../../rules/concise-specs-examples.md` | | A file that may be in the old syntax (`- ` functional specs, nested `***acceptance tests***`, `requires:` / `import:`) | `../../rules/module-structure.md` (*Old syntax*), then the `migrate-plain-syntax` skill | diff --git a/forge/skills/load-plain-reference/references/project-model.md b/forge/skills/load-plain-reference/references/project-model.md index 2491739..cfce816 100644 --- a/forge/skills/load-plain-reference/references/project-model.md +++ b/forge/skills/load-plain-reference/references/project-model.md @@ -14,10 +14,11 @@ those specifications. ```text *.plain # Root specification modules template/*.plain # Reusable import modules +tests//.plain # Test plain files, one per functionality resources/ # Linked text artifacts such as schemas and fixtures plain_modules/ # Generated output, one directory per module /code/ # implementation and unit tests - /tests/ # conformance tests, one folder per functional spec + /tests/ # conformance tests, one folder per functionality, one test per scenario test_scripts/ # Unit, environment-preparation, and conformance runners config.yaml # renderer configuration ``` diff --git a/forge/skills/load-plain-reference/references/rendering-and-testing.md b/forge/skills/load-plain-reference/references/rendering-and-testing.md index 8f8ec70..3a88485 100644 --- a/forge/skills/load-plain-reference/references/rendering-and-testing.md +++ b/forge/skills/load-plain-reference/references/rendering-and-testing.md @@ -6,8 +6,10 @@ Functional specs render from top to bottom. When rendering one functional spec, the previous specs but not future specs. A base module's functional specs also count as previous specs according to the module rules. -Each functional spec receives its own conformance-test folder. After a new spec renders, the -renderer runs conformance tests for previous specs to detect regressions. A new failure can mean: +The renderer reads the test plain files under `tests/` and generates exactly the scenarios they hold, +one labelled test per scenario-id; it never adds a test of its own. Each functionality receives its own +conformance-test folder, named by its func-id. After a new spec renders, the renderer runs conformance +tests for previous specs to detect regressions. A new failure can mean: 1. The generated implementation does not satisfy the specification. 2. The generated conformance test does not accurately test the specification. @@ -24,7 +26,7 @@ and debugged, but never edited directly. Apply fixes as follows: - Behavior or implementation/unit-test guidance: edit definitions, functional specs, or implementation requirements as appropriate. - Conformance-test guidance: edit test requirements. -- Acceptance-test guidance: edit the acceptance tests nested under their owning functional spec. +- Scenario guidance: edit the scenarios in the functionality's test plain file with `add-test-scenarios`. Re-render after correcting the source specification. @@ -37,7 +39,7 @@ Test scripts are renderer entry points as well as developer utilities: - `prepare_environment_` optionally prepares a reusable system-temporary environment for conformance testing once per render. - `run_conformance_tests_` receives the generated build folder and one functionality's - conformance-test folder (`plain_modules//tests/`). It may run repeatedly during a render. + conformance-test folder (`plain_modules//tests/`). It may run repeatedly during a render. Invoke the corresponding `implement-*-script` skill when creating or changing a test script. Those skills own exact staging, cleanup, dependency-installation, shell, and exit-code contracts. @@ -50,7 +52,7 @@ Run project scripts from the repository root, using the filenames configured for ./test_scripts/run_unittests.sh plain_modules//code ./test_scripts/prepare_environment.sh plain_modules//code ./test_scripts/run_conformance_tests.sh \ - plain_modules//code plain_modules//tests/ + plain_modules//code plain_modules//tests/ ``` The renderer resolves script arguments to absolute paths. Test scripts must treat input directories From 45a477ec52bc7c7acc4653fd25891931ea617d1e Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Thu, 15 Oct 2026 10:49:48 +0200 Subject: [PATCH 13/25] Describe test plain files and the renamed skills in the repo docs Refs ENG-190 --- AGENTS.md | 10 +++++----- CLAUDE.md | 10 +++++----- README.md | 16 +++++++++------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d1aa2a0..43ef896 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,26 +55,26 @@ This is the heart of the product. Everything under `forge/` is **instructional t operational references for project layout or rendering/testing, but those references must not restate `.plain` authoring rules. Integration rules load only for integration work. Claude skips rereading rules already supplied natively; other agents read only rules not already in context. -- **Example hygiene is load-bearing.** The rules teach `.plain` syntax, and a `.plain` example written the wrong way *leaks* — agents imitate it. Bare-continuation examples may appear **only** inside blocks explicitly labeled BAD / WRONG / `Before:` / `Too complex:`. Every "good"/"after"/"acceptable"/canonical/`## Format` example must make each line a valid `- ` list item. (`forge/rules/bullet-continuation.md` is the canonical rule.) +- **Example hygiene is load-bearing.** The rules teach `.plain` syntax, and a `.plain` example written the wrong way *leaks* — agents imitate it. Bare-continuation examples may appear **only** inside blocks explicitly labeled BAD / WRONG / `Before:` / `Too complex:`. Every "good"/"after"/"acceptable"/canonical/`## Format` example must make each line a valid `- ` list item, except in `***functional specs***`, where each functionality is a `# ` heading, a prose statement line, and `- ` clarifications. (`forge/rules/bullet-continuation.md` and `forge/rules/func-specs.md` are the canonical rules.) - **Skills are narrow and chained.** Each `forge/skills//SKILL.md` has YAML frontmatter (`name` = dir name, `description` = a `>-` block ending in a "Use when…" trigger) and a prose body. Most spec-touching skills invoke `load-plain-reference` first so it can select the relevant rules. Large or operational detail belongs in a skill's `references/` directory and loads only when needed. Only the three test-script generators (`implement-{unit-testing,conformance-testing,prepare-environment}-script`) carry an `assets/` dir of reference `.sh`/`.ps1` scripts. ### The `.plain` model the content describes -A `.plain` file is a module: YAML frontmatter (`description`, `import:`, `requires:`, `exported_concepts:`, `required_concepts:`) plus up to five triple-asterisk sections. **Where a fact lives is strict and enforced** — the renderer reads each concept only from its owning section, so a misplaced fact is silently ignored: +A `.plain` file is a module: YAML frontmatter (`description`, `imports:`, `base:`, `exported_concepts:`, `required_concepts:`) plus up to four triple-asterisk sections. Each functional spec is a `# ` heading followed by its body (`forge/rules/semantic-ids.md`). The test scenarios of a functionality live in a second file kind, the **test plain file** at `tests//.plain` — frontmatter `module:` + `func_id:`, sections `***conformance tests***` and `***acceptance tests***` (`forge/rules/test-plain-files.md`); the renderer generates exactly those scenarios and never invents a test. **Where a fact lives is strict and enforced** — the renderer reads each concept only from its owning section, so a misplaced fact is silently ignored: | Section | Owns | |---|---| | `***definitions***` | concepts as `:CamelCaseToken:`, define-before-use, globally unique, no cycles | | `***implementation reqs***` | HOW it's built (tech/architecture) **and everything about `:UnitTests:`** | -| `***functional specs***` | WHAT it does — observable, language-agnostic; each spec ≤200 changed LOC; rendered incrementally top-to-bottom | +| `***functional specs***` | WHAT it does — observable, language-agnostic; one `# func-id` heading per functionality; each spec ≤200 changed LOC; rendered incrementally top-to-bottom | | `***test reqs***` | **everything about `:ConformanceTests:`** (framework, run command, mocking/network policy) | -| `***acceptance tests***` | nested under a functional spec (not top-level); full end-to-end workflows | +| test plain file (`***conformance tests***`, `***acceptance tests***`) | the scenarios of one functionality; at least one conformance scenario when conformance tests are enabled; acceptance scenarios are end-to-end workflows | Other constraints the rules enforce (see `forge/rules/`): functional specs are mandatorily routed through `add-functional-spec(s)` (never hand-authored), checked for the 200-LOC limit (`analyze-if-func-spec-too-complex` → `break-down-func-spec`) and for conflicts (`analyze-func-specs` → `resolve-spec-conflict`); external artifacts (JSON Schema, OpenAPI, payloads) are **linked** as single local text files under `resources/`, never transcribed and never folders/URLs/binaries; generated output under `plain_modules//code/` (implementation + unit tests) and `plain_modules//tests/` (conformance tests) is read-only — fixes go back into the spec and re-render. ### The skill lifecycle (orchestration) -`forge-plain` is the top-level orchestrator: a short Phase 0 intent interview followed by four gated phases — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (config and script checks, `plain-parser check` on every top module, plus rule checks the parser can't do) then hand off the render target. Phases 1–3 are **one-question-at-a-time, write-to-disk-immediately**. `add-feature` is the same authoring loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold. The installer ships `forge/rules/*.md` beside the skills; native rule consumers load them directly, while other agents reach them through `load-plain-reference`. +`forge-plain` is the top-level orchestrator: a short Phase 0 intent interview followed by four gated phases — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, a test plain file with conformance scenarios per functionality via `add-test-scenarios`, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (config and script checks, `plain-parser check` on every top module, plus rule checks the parser can't do) then hand off the render target. Phases 1–3 are **one-question-at-a-time, write-to-disk-immediately**. `add-feature` is the same authoring loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold; `migrate-plain-syntax` converts a project from the old syntax (`- ` functional specs, nested acceptance tests, `requires:` / `import:`), which no other skill edits. The installer ships `forge/rules/*.md` beside the skills; native rule consumers load them directly, while other agents reach them through `load-plain-reference`. ## Memory / persistent context diff --git a/CLAUDE.md b/CLAUDE.md index d1aa2a0..43ef896 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,26 +55,26 @@ This is the heart of the product. Everything under `forge/` is **instructional t operational references for project layout or rendering/testing, but those references must not restate `.plain` authoring rules. Integration rules load only for integration work. Claude skips rereading rules already supplied natively; other agents read only rules not already in context. -- **Example hygiene is load-bearing.** The rules teach `.plain` syntax, and a `.plain` example written the wrong way *leaks* — agents imitate it. Bare-continuation examples may appear **only** inside blocks explicitly labeled BAD / WRONG / `Before:` / `Too complex:`. Every "good"/"after"/"acceptable"/canonical/`## Format` example must make each line a valid `- ` list item. (`forge/rules/bullet-continuation.md` is the canonical rule.) +- **Example hygiene is load-bearing.** The rules teach `.plain` syntax, and a `.plain` example written the wrong way *leaks* — agents imitate it. Bare-continuation examples may appear **only** inside blocks explicitly labeled BAD / WRONG / `Before:` / `Too complex:`. Every "good"/"after"/"acceptable"/canonical/`## Format` example must make each line a valid `- ` list item, except in `***functional specs***`, where each functionality is a `# ` heading, a prose statement line, and `- ` clarifications. (`forge/rules/bullet-continuation.md` and `forge/rules/func-specs.md` are the canonical rules.) - **Skills are narrow and chained.** Each `forge/skills//SKILL.md` has YAML frontmatter (`name` = dir name, `description` = a `>-` block ending in a "Use when…" trigger) and a prose body. Most spec-touching skills invoke `load-plain-reference` first so it can select the relevant rules. Large or operational detail belongs in a skill's `references/` directory and loads only when needed. Only the three test-script generators (`implement-{unit-testing,conformance-testing,prepare-environment}-script`) carry an `assets/` dir of reference `.sh`/`.ps1` scripts. ### The `.plain` model the content describes -A `.plain` file is a module: YAML frontmatter (`description`, `import:`, `requires:`, `exported_concepts:`, `required_concepts:`) plus up to five triple-asterisk sections. **Where a fact lives is strict and enforced** — the renderer reads each concept only from its owning section, so a misplaced fact is silently ignored: +A `.plain` file is a module: YAML frontmatter (`description`, `imports:`, `base:`, `exported_concepts:`, `required_concepts:`) plus up to four triple-asterisk sections. Each functional spec is a `# ` heading followed by its body (`forge/rules/semantic-ids.md`). The test scenarios of a functionality live in a second file kind, the **test plain file** at `tests//.plain` — frontmatter `module:` + `func_id:`, sections `***conformance tests***` and `***acceptance tests***` (`forge/rules/test-plain-files.md`); the renderer generates exactly those scenarios and never invents a test. **Where a fact lives is strict and enforced** — the renderer reads each concept only from its owning section, so a misplaced fact is silently ignored: | Section | Owns | |---|---| | `***definitions***` | concepts as `:CamelCaseToken:`, define-before-use, globally unique, no cycles | | `***implementation reqs***` | HOW it's built (tech/architecture) **and everything about `:UnitTests:`** | -| `***functional specs***` | WHAT it does — observable, language-agnostic; each spec ≤200 changed LOC; rendered incrementally top-to-bottom | +| `***functional specs***` | WHAT it does — observable, language-agnostic; one `# func-id` heading per functionality; each spec ≤200 changed LOC; rendered incrementally top-to-bottom | | `***test reqs***` | **everything about `:ConformanceTests:`** (framework, run command, mocking/network policy) | -| `***acceptance tests***` | nested under a functional spec (not top-level); full end-to-end workflows | +| test plain file (`***conformance tests***`, `***acceptance tests***`) | the scenarios of one functionality; at least one conformance scenario when conformance tests are enabled; acceptance scenarios are end-to-end workflows | Other constraints the rules enforce (see `forge/rules/`): functional specs are mandatorily routed through `add-functional-spec(s)` (never hand-authored), checked for the 200-LOC limit (`analyze-if-func-spec-too-complex` → `break-down-func-spec`) and for conflicts (`analyze-func-specs` → `resolve-spec-conflict`); external artifacts (JSON Schema, OpenAPI, payloads) are **linked** as single local text files under `resources/`, never transcribed and never folders/URLs/binaries; generated output under `plain_modules//code/` (implementation + unit tests) and `plain_modules//tests/` (conformance tests) is read-only — fixes go back into the spec and re-render. ### The skill lifecycle (orchestration) -`forge-plain` is the top-level orchestrator: a short Phase 0 intent interview followed by four gated phases — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (config and script checks, `plain-parser check` on every top module, plus rule checks the parser can't do) then hand off the render target. Phases 1–3 are **one-question-at-a-time, write-to-disk-immediately**. `add-feature` is the same authoring loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold. The installer ships `forge/rules/*.md` beside the skills; native rule consumers load them directly, while other agents reach them through `load-plain-reference`. +`forge-plain` is the top-level orchestrator: a short Phase 0 intent interview followed by four gated phases — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, a test plain file with conformance scenarios per functionality via `add-test-scenarios`, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (config and script checks, `plain-parser check` on every top module, plus rule checks the parser can't do) then hand off the render target. Phases 1–3 are **one-question-at-a-time, write-to-disk-immediately**. `add-feature` is the same authoring loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold; `migrate-plain-syntax` converts a project from the old syntax (`- ` functional specs, nested acceptance tests, `requires:` / `import:`), which no other skill edits. The installer ships `forge/rules/*.md` beside the skills; native rule consumers load them directly, while other agents reach them through `load-plain-reference`. ## Memory / persistent context diff --git a/README.md b/README.md index 2fabb41..80f1826 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,13 @@ Pick whichever entry point matches how much upfront design you want: ### 2. Grow an existing project - **`add-feature`** — takes a feature request in plain English and runs the same one-question-at-a-time loop that `forge-plain` uses, scoped to a single feature against an existing `.plain` file. -- **Per-section authoring skills** — `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `add-test-requirement`, `add-concept`, `add-acceptance-test`, `add-resource`, `add-template`. Each enforces the relevant ***plain syntax rules (concept uniqueness, complexity limits, bullet syntax, linked-resource constraints, …) so hand-authoring doesn't drift from the language. -- **Module-management skills** — `create-import-module`, `create-requires-module`, `refactor-module`, `consolidate-concepts` for restructuring as the project grows. +- **Per-section authoring skills** — `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `add-test-requirement`, `add-concept`, `add-test-scenarios`, `add-resource`, `add-template`. Each enforces the relevant ***plain syntax rules (concept uniqueness, complexity limits, bullet syntax, linked-resource constraints, …) so hand-authoring doesn't drift from the language. `add-test-scenarios` writes the test plain file of a functionality — `tests//.plain` with the conformance and acceptance scenarios the renderer implements, and nothing else. +- **Module-management skills** — `create-import-module`, `create-base-module`, `refactor-module`, `consolidate-concepts` for restructuring as the project grows. ### 3. Validate and maintain - **`plain-healthcheck`** — the verification gate. Inventories every `.plain` module, validates every `config.yaml`, parses every top module with [`plain-parser`](https://github.com/plainlang/plain-parser), and checks every module against the ***plain rules. Returns `PASS` / `FAIL` with a numbered punch-list when something is broken. +- **`migrate-plain-syntax`** — converts a project from the old syntax (`- ` functional specs, nested acceptance tests, `requires:` / `import:`) to `# func-id` headings, test plain files under `tests/`, and `base:` / `imports:`. Run it when a skill or `plain-parser` reports the old syntax. - **`init-config-file`** — assembles the canonical `config.yaml` per part of a project from the decisions made during interviewing. - **`check-plain-env`** — probes the host machine for everything the project needs (language toolchains, external services, system binaries, drivers) and emits a `PASS` / `WARN` / `FAIL` report with OS-specific install commands. - **`analyze-func-specs`** / **`analyze-if-func-spec-too-complex`** / **`break-down-func-spec`** / **`resolve-spec-conflict`** — the spec-quality toolchain that runs behind `add-feature` and `forge-plain` but can also be invoked directly when reviewing or refactoring. @@ -263,7 +264,7 @@ On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them int | Skill | Description | |-------|-------------| | `forge-plain` | End-to-end QA interview that produces complete `.plain` spec files for a new project | -| `init-plain-project` | Lightweight project initializer — scaffolds `template/base.plain` (base impl + test reqs), a stub top-level module, the testing scripts, and `config.yaml`. No functional specs, no concepts, no validation. Pair with `add-feature` to grow the project feature-by-feature. | +| `init-plain-project` | Lightweight project initializer — scaffolds `template/stack.plain` (stack impl + test reqs), a stub top-level module, the testing scripts, and `config.yaml`. No functional specs, no concepts, no validation. Pair with `add-feature` to grow the project feature-by-feature. | | `add-feature` | Interview the user about a single feature, then write all the specs for it | | `render-spec` | [pyro](https://github.com/plainlang/pyro), the open-source renderer: renders a `.plain` module and the modules it requires or imports into working, tested code, from inside your agent. See [Render with pyro](#render-with-pyro). | @@ -276,7 +277,7 @@ On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them int | `add-implementation-requirement` | Add a non-functional requirement to `***implementation reqs***` | | `add-test-requirement` | Add a testing requirement to `***test reqs***` | | `add-concept` | Define a new concept in `***definitions***` | -| `add-acceptance-test` | Add verification criteria under a functional spec | +| `add-test-scenarios` | Write or update the test plain file of a functionality (`tests//.plain`): conformance scenarios in given/when/then, and acceptance scenarios for end-to-end workflows | | `add-resource` | Link an external file (schema, API spec) to a spec | | `add-template` | Create or include a reusable Liquid template | @@ -285,8 +286,8 @@ On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them int | Skill | Description | |-------|-------------| | `create-import-module` | Create a shared template module (definitions + reqs, no functional specs) | -| `create-requires-module` | Create a module that depends on a previously built module | -| `refactor-module` | Split a large module into smaller modules connected via a requires chain | +| `create-base-module` | Create a module that lists a previously built module under `base` | +| `refactor-module` | Split a large module into smaller modules connected via a base chain | | `consolidate-concepts` | Gather scattered concept definitions into a single shared import module | ### Analysis and Quality @@ -294,8 +295,9 @@ On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them int | Skill | Description | |-------|-------------| | `init-config-file` | Build / finalize the project's `config.yaml` file(s) from the decisions made in Phase 3. Knows the full set of valid keys, refuses to write secrets or per-invocation options, and produces one config per part of the project. Run at the end of `forge-plain` (just before `plain-healthcheck`) and any time the testing surface changes. | -| `plain-healthcheck` | Verification gate: validates every `config.yaml`, confirms each `*-script` field points at a real file in `test_scripts/`, runs `plain-parser check` on every top module, and checks every module against the rules. Run whenever anything in the project is finalized — at the end of `forge-plain`, at the end of `add-feature`, after `debug-specs`, and after any single-skill edit that touches the renderable surface. | +| `plain-healthcheck` | Verification gate: validates every `config.yaml`, confirms each `*-script` field points at a real file in `test_scripts/`, runs `plain-parser check` on every top module, and checks every module and test plain file against the rules. Run whenever anything in the project is finalized — at the end of `forge-plain`, at the end of `add-feature`, after `debug-specs`, and after any single-skill edit that touches the renderable surface. | | `check-plain-env` | Read the project's `.plain` files, `test_scripts/`, `config.yaml`(s), and `resources/`, then probe the host for every requirement **the package manager can't install**: language toolchains (`python` + `pip`, `node` + `npm`, JDK + `mvn`, Go, Rust, .NET, etc.), external services (Postgres, Redis, Docker, ...), system binaries that language packages wrap (`ffmpeg`, `tesseract`, `pdftoppm`, browser binaries, ...), hardware / drivers / accelerators (NVIDIA driver → CUDA toolkit → cuDNN → framework-sees-GPU chain), credential env vars, and `plain-parser`. Does **not** probe individual language packages — `pip install -r requirements.txt` (and equivalents) handle those when the test scripts run. Emits a `PASS` / `WARN` / `FAIL` report with OS-specific install commands for any gaps. Read-only — never installs anything. Run on first-time setup, before rendering on a new machine, after adding a new tech to a project, or any time `command not found` shows up in test output. | +| `migrate-plain-syntax` | Convert a project from the old syntax: `# func-id` headings, test plain files under `tests/`, `base:` / `imports:` | | `analyze-if-func-spec-too-complex` | Check if a spec exceeds the 200-line complexity limit | | `analyze-func-specs` | Check a batch of specs (2+) against each other in one call and return every conflicting pair | | `analyze-2-func-specs` | Legacy: check exactly two specs for conflicts (prefer `analyze-func-specs`) | From ba2705b5b929c66b14c87fa3ee6d0c6e5c5cf40d Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Fri, 16 Oct 2026 11:23:56 +0200 Subject: [PATCH 14/25] Indent clarification bullets under a functionality by two spaces The bullets under the statement line of a functionality are indented by two spaces, as the nested sub-bullets were before; a flush-left - in functional specs is now always the old syntax, and the migration keeps the old indentation as it is. Every example and the rule and skill wording follow. Refs ENG-190 --- AGENTS.md | 2 +- CLAUDE.md | 2 +- forge/rules/bullet-continuation.md | 9 ++-- forge/rules/concise-specs-examples.md | 46 +++++++++---------- .../concise-specs-integration-examples.md | 18 ++++---- forge/rules/func-specs.md | 10 ++-- forge/rules/linked-resources.md | 2 +- forge/rules/module-structure.md | 2 +- forge/skills/add-concept/SKILL.md | 2 +- forge/skills/add-feature/SKILL.md | 2 +- forge/skills/add-functional-spec/SKILL.md | 12 ++--- forge/skills/add-functional-specs/SKILL.md | 10 ++-- .../add-implementation-requirement/SKILL.md | 2 +- forge/skills/add-resource/SKILL.md | 2 +- forge/skills/add-template/SKILL.md | 2 +- forge/skills/add-test-requirement/SKILL.md | 2 +- forge/skills/analyze-2-func-specs/SKILL.md | 2 +- forge/skills/analyze-func-specs/SKILL.md | 2 +- .../analyze-if-func-spec-too-complex/SKILL.md | 8 ++-- forge/skills/break-down-func-spec/SKILL.md | 10 ++-- forge/skills/consolidate-concepts/SKILL.md | 2 +- forge/skills/create-base-module/SKILL.md | 2 +- forge/skills/create-import-module/SKILL.md | 2 +- forge/skills/debug-specs/SKILL.md | 4 +- forge/skills/forge-plain/SKILL.md | 2 +- forge/skills/migrate-plain-syntax/SKILL.md | 14 +++--- forge/skills/plain-healthcheck/SKILL.md | 4 +- forge/skills/refactor-module/SKILL.md | 2 +- .../skills/resolve-section-ownership/SKILL.md | 2 +- forge/skills/resolve-spec-conflict/SKILL.md | 2 +- 30 files changed, 92 insertions(+), 91 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 43ef896..8f7b430 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,7 +55,7 @@ This is the heart of the product. Everything under `forge/` is **instructional t operational references for project layout or rendering/testing, but those references must not restate `.plain` authoring rules. Integration rules load only for integration work. Claude skips rereading rules already supplied natively; other agents read only rules not already in context. -- **Example hygiene is load-bearing.** The rules teach `.plain` syntax, and a `.plain` example written the wrong way *leaks* — agents imitate it. Bare-continuation examples may appear **only** inside blocks explicitly labeled BAD / WRONG / `Before:` / `Too complex:`. Every "good"/"after"/"acceptable"/canonical/`## Format` example must make each line a valid `- ` list item, except in `***functional specs***`, where each functionality is a `# ` heading, a prose statement line, and `- ` clarifications. (`forge/rules/bullet-continuation.md` and `forge/rules/func-specs.md` are the canonical rules.) +- **Example hygiene is load-bearing.** The rules teach `.plain` syntax, and a `.plain` example written the wrong way *leaks* — agents imitate it. Bare-continuation examples may appear **only** inside blocks explicitly labeled BAD / WRONG / `Before:` / `Too complex:`. Every "good"/"after"/"acceptable"/canonical/`## Format` example must make each line a valid `- ` list item, except in `***functional specs***`, where each functionality is a `# ` heading, a prose statement line, and `- ` clarifications indented by two spaces. (`forge/rules/bullet-continuation.md` and `forge/rules/func-specs.md` are the canonical rules.) - **Skills are narrow and chained.** Each `forge/skills//SKILL.md` has YAML frontmatter (`name` = dir name, `description` = a `>-` block ending in a "Use when…" trigger) and a prose body. Most spec-touching skills invoke `load-plain-reference` first so it can select the relevant rules. Large or operational detail belongs in a skill's `references/` directory and loads only when needed. Only the three test-script generators (`implement-{unit-testing,conformance-testing,prepare-environment}-script`) carry an `assets/` dir of reference `.sh`/`.ps1` scripts. ### The `.plain` model the content describes diff --git a/CLAUDE.md b/CLAUDE.md index 43ef896..8f7b430 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,7 +55,7 @@ This is the heart of the product. Everything under `forge/` is **instructional t operational references for project layout or rendering/testing, but those references must not restate `.plain` authoring rules. Integration rules load only for integration work. Claude skips rereading rules already supplied natively; other agents read only rules not already in context. -- **Example hygiene is load-bearing.** The rules teach `.plain` syntax, and a `.plain` example written the wrong way *leaks* — agents imitate it. Bare-continuation examples may appear **only** inside blocks explicitly labeled BAD / WRONG / `Before:` / `Too complex:`. Every "good"/"after"/"acceptable"/canonical/`## Format` example must make each line a valid `- ` list item, except in `***functional specs***`, where each functionality is a `# ` heading, a prose statement line, and `- ` clarifications. (`forge/rules/bullet-continuation.md` and `forge/rules/func-specs.md` are the canonical rules.) +- **Example hygiene is load-bearing.** The rules teach `.plain` syntax, and a `.plain` example written the wrong way *leaks* — agents imitate it. Bare-continuation examples may appear **only** inside blocks explicitly labeled BAD / WRONG / `Before:` / `Too complex:`. Every "good"/"after"/"acceptable"/canonical/`## Format` example must make each line a valid `- ` list item, except in `***functional specs***`, where each functionality is a `# ` heading, a prose statement line, and `- ` clarifications indented by two spaces. (`forge/rules/bullet-continuation.md` and `forge/rules/func-specs.md` are the canonical rules.) - **Skills are narrow and chained.** Each `forge/skills//SKILL.md` has YAML frontmatter (`name` = dir name, `description` = a `>-` block ending in a "Use when…" trigger) and a prose body. Most spec-touching skills invoke `load-plain-reference` first so it can select the relevant rules. Large or operational detail belongs in a skill's `references/` directory and loads only when needed. Only the three test-script generators (`implement-{unit-testing,conformance-testing,prepare-environment}-script`) carry an `assets/` dir of reference `.sh`/`.ps1` scripts. ### The `.plain` model the content describes diff --git a/forge/rules/bullet-continuation.md b/forge/rules/bullet-continuation.md index ae9047c..b1e8231 100644 --- a/forge/rules/bullet-continuation.md +++ b/forge/rules/bullet-continuation.md @@ -29,8 +29,9 @@ clarification of the statement above it. There is no third, typographic reason t and `***test reqs***`: every statement there 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 - `***functional specs***` has a different top level: each functionality is a `# ` heading, - its first body line is prose, and only the clarifications under it are `- ` bullets (see - `func-specs.md`). A scenario in a test plain file has the same shape (see `test-plain-files.md`) + its first body line is prose, and the clarifications under it are `- ` bullets indented by two + spaces (see `func-specs.md`); a flush-left `- ` there is the old syntax. A scenario in a test plain + file has the same shape (see `test-plain-files.md`) BAD — line broken for width (bare continuation): @@ -58,8 +59,8 @@ BEST — separate clarifications expressed as nested bullets: # handle-stripe-webhook :GatewayWebhook: hands off :StripeRequest: to :StripeIntegration:.handle(). -- The method returns a list of :EventEnvelope: dicts. -- The dicts conform to the gateway's :EventEnvelope: contract. + - The method returns a list of :EventEnvelope: dicts. + - The dicts conform to the gateway's :EventEnvelope: contract. ``` ## Presenting `.plain` examples diff --git a/forge/rules/concise-specs-examples.md b/forge/rules/concise-specs-examples.md index bc24813..ee3a6d4 100644 --- a/forge/rules/concise-specs-examples.md +++ b/forge/rules/concise-specs-examples.md @@ -36,9 +36,9 @@ GOOD: # create-task A :User: can create a :Task: via `POST /tasks`. -- The request body is a :Task: without `id`. -- The response is `201` with the created :Task:. -- A request without a name returns `400`. + - The request body is a :Task: without `id`. + - The response is `201` with the created :Task:. + - A request without a name returns `400`. ``` Three bullets became one, and the rewrite names the method, the path, the request shape, and both status codes — none of which the padded version pinned down. @@ -94,10 +94,10 @@ BAD: # list-orders :OrderApi: returns a list of :Order: items over HTTP. -- The list contains :Order: items. -- If there are no :Order: items, the list is empty. -- Each :Order: has an `id`, which is unique. -- The caller receives the response after the request is sent. + - The list contains :Order: items. + - If there are no :Order: items, the list is empty. + - Each :Order: has an `id`, which is unique. + - The caller receives the response after the request is sent. ``` GOOD: @@ -107,7 +107,7 @@ GOOD: # list-orders :OrderApi: returns the :Order: items of the authenticated :User: via `GET /orders`, newest first. -- The response is `200` with a JSON array, empty when the :User: has no :Order: items. + - The response is `200` with a JSON array, empty when the :User: has no :Order: items. ``` Every cut line was already implied: a list of :Order: items contains :Order: items, an identifier identifies, and a response follows a request. What survives is what a reader could *not* have guessed — the ownership filter, the sort order, and the response shape. @@ -254,9 +254,9 @@ BAD: # archive-project A :User: can archive a :Project:. -- Archiving is an action the user performs on a project. -- The archive action is available to users who have access to the project. -- Once archived, the project is in the archived state. + - Archiving is an action the user performs on a project. + - The archive action is available to users who have access to the project. + - Once archived, the project is in the archived state. ``` GOOD: @@ -266,8 +266,8 @@ GOOD: # archive-project A :User: can archive a :Project: they own. -- An archived :Project: is excluded from :ProjectList:. -- Archiving a :Project: that is already archived returns `409`. + - An archived :Project: is excluded from :ProjectList:. + - Archiving a :Project: that is already archived returns `409`. ``` A sub-bullet earns its line by removing an ambiguity. If it only says the parent again in more words, it is filler. @@ -281,8 +281,8 @@ BAD: # expire-session :SessionStore: expires a :Session: 30 minutes after its last use. -- Sessions do not live forever; they time out. -- The timeout is 30 minutes. + - Sessions do not live forever; they time out. + - The timeout is 30 minutes. ``` GOOD: @@ -292,7 +292,7 @@ GOOD: # expire-session :SessionStore: expires a :Session: 30 minutes after its last use. -- A request carrying an expired :Session: returns `401`. + - A request carrying an expired :Session: returns `401`. ``` ## Synonym drift @@ -321,8 +321,8 @@ GOOD: # upload-document A :User: can upload a :Document:. -- :DocumentStore: stores the :Document: and assigns it a :DocumentId:. -- A :User: can retrieve a :Document: by its :DocumentId:. + - :DocumentStore: stores the :Document: and assigns it a :DocumentId:. + - A :User: can retrieve a :Document: by its :DocumentId:. ``` ## Meta-commentary and scope disclaimers @@ -352,8 +352,8 @@ GOOD: # export-csv :ExportService: writes the :Order: items of a :BillingPeriod: to a :CsvFile:. -- The header row is `id,customer,total,currency`. -- An empty :BillingPeriod: produces a :CsvFile: with only the header row. + - The header row is `id,customer,total,currency`. + - An empty :BillingPeriod: produces a :CsvFile: with only the header row. ``` A spec describes the software, not itself. What the spec does *not* change is everything it does not mention, and what later specs will add belongs to those specs. @@ -382,7 +382,7 @@ GOOD: # apply-discounts :PricingEngine: applies the :DiscountTier: matrix in [resources/discount-tiers.json](resources/discount-tiers.json) to an :Order: total. -- An :Order: below the lowest tier threshold gets no discount. + - An :Order: below the lowest tier threshold gets no discount. ``` A section holds `- ` list items. Matrices, schemas, and payloads are external artifacts and are linked from `resources/` — see `linked-resources.md`. @@ -427,8 +427,8 @@ GOOD — each fact in the section that owns it: # resize-image :ImageResizer: produces a :Thumbnail: from an :Image:. -- An :Image: already within the :Thumbnail: bounds is returned unchanged. -- An :Image: that is not a supported format raises :UnsupportedImageError:. + - An :Image: already within the :Thumbnail: bounds is returned unchanged. + - An :Image: that is not a supported format raises :UnsupportedImageError:. ``` Each section now answers one question, and the definition, the mechanism, and the behavior are each one line to check instead of clauses buried in a paragraph about resizing. Use the split whenever a bullet answers more than one of *what is it*, *what does it do*, *how is it built*, and *how is it tested* — see `module-structure.md` for section ownership and canonical section order. diff --git a/forge/rules/concise-specs-integration-examples.md b/forge/rules/concise-specs-integration-examples.md index cda9e2f..ed694ce 100644 --- a/forge/rules/concise-specs-integration-examples.md +++ b/forge/rules/concise-specs-integration-examples.md @@ -157,21 +157,21 @@ BAD — four specs, one operation: # fetch-requires-api-key A call to `fetch` fails before any request to :ProviderApi: is sent when `PROVIDER_API_KEY` is unset or empty. -- The failure names `PROVIDER_API_KEY`. + - The failure names `PROVIDER_API_KEY`. # fetch-reads-catalogue A call to `fetch` reads the field catalogue from :ProviderApi: once, before requesting any :Record:. -- The catalogue yields the name of every field the account defines. -- A call to `fetch` whose catalogue read fails requests no :Record: items. + - The catalogue yields the name of every field the account defines. + - A call to `fetch` whose catalogue read fails requests no :Record: items. # fetch-requests-records A request for :Record: items from :ProviderApi: names every field in the catalogue and asks for at most 100 records. -- Archived :Record: items are not requested. + - Archived :Record: items are not requested. # fetch-pages-records A call to `fetch` requests pages until :ProviderApi: returns a response carrying no next-page cursor. -- Each page after the first is requested with the cursor from the previous response. -- The :Record: items of every page are carried forward together. + - Each page after the first is requested with the cursor from the previous response. + - The :Record: items of every page are carried forward together. ``` GOOD — one spec, with the result stated first: @@ -189,9 +189,9 @@ GOOD — one spec, with the result stated first: # fetch-records :RecordSource: `fetch()` returns every non-archived :Record: with every field :ProviderApi: defines, paging under [resources/pagination.yaml](resources/pagination.yaml). -- :RecordSource: reads the field catalogue once per `fetch()`. -- `fetch()` fails before any request when `PROVIDER_API_KEY` is unset or empty, naming it. -- A failed catalogue read fails `fetch()` before any :Record: is requested. + - :RecordSource: reads the field catalogue once per `fetch()`. + - `fetch()` fails before any request when `PROVIDER_API_KEY` is unset or empty, naming it. + - A failed catalogue read fails `fetch()` before any :Record: is requested. ``` Merging is not in tension with the complexity limit. Split a spec when it implies more than 200 changed lines of code (`analyze-if-func-spec-too-complex`, then `break-down-func-spec`); merge when several specs implement one operation that fits inside that budget. Fragment count is not a measure of size. diff --git a/forge/rules/func-specs.md b/forge/rules/func-specs.md index d833c8e..4c2986f 100644 --- a/forge/rules/func-specs.md +++ b/forge/rules/func-specs.md @@ -9,7 +9,7 @@ When writing or editing a `***functional specs***` section in a `.plain` file, a ## Shape - Each functional spec, called a functionality, is a `# ` heading followed by its body; the func-id follows `semantic-ids.md` - The first body line is the statement of the behavior, written as prose without a leading `- ` -- Clarifications follow the statement as `- ` bullets, one fact per bullet (see `bullet-continuation.md`) +- Clarifications follow the statement as `- ` bullets indented by two spaces, one fact per bullet; a deeper level adds two more spaces (see `bullet-continuation.md`) - No body line starts with `# ` (that starts the next functionality) or with `***` - One blank line separates functionalities - The test scenarios of a functionality live in its test plain file, never in this section (see `test-plain-files.md`) @@ -94,7 +94,7 @@ A :User: can add a :Task:. Only valid :Task: items are added. ## Disambiguation - Each functional spec must be unambiguous — the renderer should have only one reasonable interpretation -- If the statement line is not enough to fully disambiguate the behavior, add **`- ` bullets** under it to add detail +- If the statement line is not enough to fully disambiguate the behavior, add **indented `- ` bullets** under it to add detail - The bullets clarify the statement — they do not introduce separate functionality - Even with the bullets, the spec must still imply ≤ 200 lines of code @@ -134,7 +134,7 @@ A :User: can add a :Task:. Only valid :Task: items are added. # send-message 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:. + - 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/linked-resources.md b/forge/rules/linked-resources.md index 6d57e80..db1c706 100644 --- a/forge/rules/linked-resources.md +++ b/forge/rules/linked-resources.md @@ -40,7 +40,7 @@ A linked resource **must not** be any of the following: # add-task A :User: can add a :Task: by POSTing :TaskCreateRequest: to the `POST /tasks` endpoint of :TasksAPI:. -- The endpoint responds per :TasksAPI:. + - The endpoint responds per :TasksAPI:. ``` ## Each linked resource is referenced from exactly one place diff --git a/forge/rules/module-structure.md b/forge/rules/module-structure.md index c76894d..1f05931 100644 --- a/forge/rules/module-structure.md +++ b/forge/rules/module-structure.md @@ -112,6 +112,6 @@ The :TaskList: is displayed. ``` ## Old syntax -- A core spec is in the old syntax when any of these holds: a top-level `- ` bullet in `***functional specs***`, a `***acceptance tests***` section anywhere in the file, or a `requires:` or `import:` key in the frontmatter +- A core spec is in the old syntax when any of these holds: a flush-left `- ` bullet in `***functional specs***`, a `***acceptance tests***` section anywhere in the file, or a `requires:` or `import:` key in the frontmatter - The old syntax is not supported: the renderer rejects it, and no skill edits a file written in it - When a skill meets the old syntax, it stops, tells the user which file and which marker it found, and points to the `migrate-plain-syntax` skill, which converts the whole project in one pass diff --git a/forge/skills/add-concept/SKILL.md b/forge/skills/add-concept/SKILL.md index 6a29b71..0c5796d 100644 --- a/forge/skills/add-concept/SKILL.md +++ b/forge/skills/add-concept/SKILL.md @@ -10,7 +10,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Workflow diff --git a/forge/skills/add-feature/SKILL.md b/forge/skills/add-feature/SKILL.md index 9e2394f..6c142e9 100644 --- a/forge/skills/add-feature/SKILL.md +++ b/forge/skills/add-feature/SKILL.md @@ -15,7 +15,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. `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 begins with a short intent phase, then runs the authoring loop scoped to one feature. diff --git a/forge/skills/add-functional-spec/SKILL.md b/forge/skills/add-functional-spec/SKILL.md index ea6d065..ad2aa48 100644 --- a/forge/skills/add-functional-spec/SKILL.md +++ b/forge/skills/add-functional-spec/SKILL.md @@ -15,13 +15,13 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Workflow 1. **Identify the target `.plain` file.** If ambiguous, ask the user. 2. **Read the entire file** to understand existing definitions, implementation reqs, and all current functional specs (including those in `base` modules). -3. **Pick the func-id and draft the functional spec.** Choose a func-id per `semantic-ids.md` and confirm no `# ` heading with that id exists in the module. Draft the statement line and its `- ` clarifications following the rules below, then run the review pass in `concise-specs.md` over the draft — trim it to the shortest wording that keeps every fact before any other check. +3. **Pick the func-id and draft the functional spec.** Choose a func-id per `semantic-ids.md` and confirm no `# ` heading with that id exists in the module. Draft the statement line and its indented `- ` clarifications following the rules below, then run the review pass in `concise-specs.md` over the draft — trim it to the shortest wording that keeps every fact before any other check. 4. **Analyze complexity** — use the `analyze-if-func-spec-too-complex` skill to verify the drafted spec implies ≤ 200 LOC. If too complex, use the `break-down-func-spec` skill to split it, then repeat from step 3 for each resulting spec. 5. **Check for conflicts** with every existing functional spec — this is critical. Run `analyze-func-specs` **once** with the new spec plus all existing specs (in the file and in any `base` chain) as a single batch. The batched analyzer returns every conflicting pair in one call — do **not** invoke a pair-by-pair analyzer. For each conflicting pair it reports, run `resolve-spec-conflict` on that pair; re-run `analyze-func-specs` over the touched set after each resolution until the verdict is `COMPATIBLE`. 6. **Append the spec** to the end of the `***functional specs***` section as a `# ` heading followed by its body, one blank line after the previous functionality (specs are chronological; new ones go last). @@ -51,16 +51,16 @@ The new spec must not contradict any existing functional spec. Conflicting specs ### Disambiguation -Each functional spec must be unambiguous — the renderer should have only one reasonable interpretation. If the statement line is not enough to fully disambiguate the behavior, add **`- ` bullets** under it. The bullets clarify the statement; they do not introduce separate functionality. Even with the bullets, the spec must still imply ≤ 200 LOC. +Each functional spec must be unambiguous — the renderer should have only one reasonable interpretation. If the statement line is not enough to fully disambiguate the behavior, add **indented `- ` bullets** under it. The bullets clarify the statement; they do not introduce separate functionality. Even with the bullets, the spec must still imply ≤ 200 LOC. ```plain ***functional specs*** # send-message 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:. + - 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/skills/add-functional-specs/SKILL.md b/forge/skills/add-functional-specs/SKILL.md index 152a683..3dc4ab6 100644 --- a/forge/skills/add-functional-specs/SKILL.md +++ b/forge/skills/add-functional-specs/SKILL.md @@ -14,7 +14,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## When to Use @@ -74,16 +74,16 @@ That coverage is achieved by running `analyze-func-specs` **once per spec being ### Disambiguation (per spec) -Each functional spec must be unambiguous — the renderer should have only one reasonable interpretation. If the statement line is not enough to fully disambiguate the behavior, add **`- ` bullets** under it. The bullets clarify the statement; they do not introduce separate functionality. Even with the bullets, the spec must still imply ≤ 200 LOC. +Each functional spec must be unambiguous — the renderer should have only one reasonable interpretation. If the statement line is not enough to fully disambiguate the behavior, add **indented `- ` bullets** under it. The bullets clarify the statement; they do not introduce separate functionality. Even with the bullets, the spec must still imply ≤ 200 LOC. ```plain ***functional specs*** # send-message 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:. + - 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/skills/add-implementation-requirement/SKILL.md b/forge/skills/add-implementation-requirement/SKILL.md index 17b7242..e0f1271 100644 --- a/forge/skills/add-implementation-requirement/SKILL.md +++ b/forge/skills/add-implementation-requirement/SKILL.md @@ -12,7 +12,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Workflow diff --git a/forge/skills/add-resource/SKILL.md b/forge/skills/add-resource/SKILL.md index 30eb85d..338b6f0 100644 --- a/forge/skills/add-resource/SKILL.md +++ b/forge/skills/add-resource/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. Linked resources are external files referenced from within a `.plain` spec using markdown link syntax. The file contents are passed to the renderer alongside the spec, providing additional context for code generation. diff --git a/forge/skills/add-template/SKILL.md b/forge/skills/add-template/SKILL.md index 5d83a30..9f56c8c 100644 --- a/forge/skills/add-template/SKILL.md +++ b/forge/skills/add-template/SKILL.md @@ -10,7 +10,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. Templates use Liquid syntax (`{% include %}`) to reuse the same spec content across multiple `.plain` files. Each inclusion can pass different parameters, producing tailored output from a single source. This is distinct from `imports` and `base` — templates are about textual reuse with parameterization, not module dependencies. diff --git a/forge/skills/add-test-requirement/SKILL.md b/forge/skills/add-test-requirement/SKILL.md index 6f32df7..361d649 100644 --- a/forge/skills/add-test-requirement/SKILL.md +++ b/forge/skills/add-test-requirement/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Workflow diff --git a/forge/skills/analyze-2-func-specs/SKILL.md b/forge/skills/analyze-2-func-specs/SKILL.md index 2be3530..ea4e4ad 100644 --- a/forge/skills/analyze-2-func-specs/SKILL.md +++ b/forge/skills/analyze-2-func-specs/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Input diff --git a/forge/skills/analyze-func-specs/SKILL.md b/forge/skills/analyze-func-specs/SKILL.md index 6260c10..5fafead 100644 --- a/forge/skills/analyze-func-specs/SKILL.md +++ b/forge/skills/analyze-func-specs/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Input 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 c1d5e96..1f63e23 100644 --- a/forge/skills/analyze-if-func-spec-too-complex/SKILL.md +++ b/forge/skills/analyze-if-func-spec-too-complex/SKILL.md @@ -12,7 +12,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Why This Matters @@ -107,7 +107,7 @@ An express :Order: is queued for immediate dispatch without validation. # split-bulk-order A bulk :Order: is split into sub-orders of 100 items each. -- Each sub-order is processed individually. + - Each sub-order is processed individually. ``` ### 4. Cross-Cutting Concerns @@ -186,8 +186,8 @@ Acceptable (component in its own spec, then referenced): # pdf-renderer-tables :PdfRenderer: lays out multi-page tables from structured content. -- Table headers repeat at the top of each page. -- Each page shows its page number. + - Table headers repeat at the top of each page. + - Each page shows its page number. # export-report-pdf A :Report: is exported to PDF using :PdfRenderer:, embedding charts rendered from :Report: data. diff --git a/forge/skills/break-down-func-spec/SKILL.md b/forge/skills/break-down-func-spec/SKILL.md index f179703..8a27cd9 100644 --- a/forge/skills/break-down-func-spec/SKILL.md +++ b/forge/skills/break-down-func-spec/SKILL.md @@ -10,7 +10,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## When to Use @@ -147,8 +147,8 @@ A standard :MealPlan: is generated using round-robin :Recipe: assignment. # generate-restrictive-plan A restrictive :MealPlan: is generated. -- Excluded :Ingredient: items are filtered out first. -- Round-robin :Recipe: assignment is then applied. + - Excluded :Ingredient: items are filtered out first. + - Round-robin :Recipe: assignment is then applied. # assign-custom-plan A :User: can manually assign :Recipe: items to :Slot: items for a custom :MealPlan:. @@ -200,8 +200,8 @@ A :Report: is exported to PDF, laying out multi-page tables with repeating heade # pdf-renderer-tables :PdfRenderer: lays out multi-page tables from structured content. -- Table headers repeat at the top of each page. -- Each page shows its page number. + - Table headers repeat at the top of each page. + - Each page shows its page number. # export-report-pdf A :Report: is exported to PDF using :PdfRenderer:, embedding charts rendered from :Report: data. diff --git a/forge/skills/consolidate-concepts/SKILL.md b/forge/skills/consolidate-concepts/SKILL.md index 4248b26..7f9b156 100644 --- a/forge/skills/consolidate-concepts/SKILL.md +++ b/forge/skills/consolidate-concepts/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## When to Use diff --git a/forge/skills/create-base-module/SKILL.md b/forge/skills/create-base-module/SKILL.md index 71e6aa3..ae72a13 100644 --- a/forge/skills/create-base-module/SKILL.md +++ b/forge/skills/create-base-module/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## What Base Does diff --git a/forge/skills/create-import-module/SKILL.md b/forge/skills/create-import-module/SKILL.md index c59b186..67dc8c6 100644 --- a/forge/skills/create-import-module/SKILL.md +++ b/forge/skills/create-import-module/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## What an Import Module Is diff --git a/forge/skills/debug-specs/SKILL.md b/forge/skills/debug-specs/SKILL.md index 0f1bca0..a5f94c6 100644 --- a/forge/skills/debug-specs/SKILL.md +++ b/forge/skills/debug-specs/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## When to Use @@ -100,7 +100,7 @@ Apply the fix based on the diagnosed root cause. Use the appropriate skill: | Root Cause | Skill to Use | |------------|-------------| -| Ambiguous spec | Edit the spec inline — add `- ` bullets under the statement line or reword for clarity | +| Ambiguous spec | Edit the spec inline — add indented `- ` bullets under the statement line or reword for clarity | | Missing spec | `add-functional-spec` | | Conflicting specs | `resolve-spec-conflict` | | Incorrect spec | Edit the spec inline — rewrite the incorrect part | diff --git a/forge/skills/forge-plain/SKILL.md b/forge/skills/forge-plain/SKILL.md index e6f67e2..2cb46c5 100644 --- a/forge/skills/forge-plain/SKILL.md +++ b/forge/skills/forge-plain/SKILL.md @@ -14,7 +14,7 @@ description: >- 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. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Role diff --git a/forge/skills/migrate-plain-syntax/SKILL.md b/forge/skills/migrate-plain-syntax/SKILL.md index 94b6294..7d7cd1b 100644 --- a/forge/skills/migrate-plain-syntax/SKILL.md +++ b/forge/skills/migrate-plain-syntax/SKILL.md @@ -18,7 +18,7 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule | Old syntax | Current syntax | |---|---| -| A functional spec is a top-level `- ` bullet with nested ` - ` sub-bullets | A functional spec is a `# ` heading, a statement line, and `- ` clarification bullets | +| A functional spec is a flush-left `- ` bullet with nested ` - ` sub-bullets | A functional spec is a `# ` heading, a statement line, and indented `- ` clarification bullets | | `***acceptance tests***` nested under a functional spec | `***acceptance tests***` section of the functionality's test plain file `tests//.plain` | | The renderer invents conformance tests | `***conformance tests***` section of the test plain file holds at least one scenario per functionality | | `requires:` in the frontmatter | `base:` | @@ -37,7 +37,7 @@ There is no backward compatibility: a project in the old syntax does not render, ### 1. Inventory and confirm 1. List every `.plain` file in the project. Follow the `requires` / `base` and `import` / `imports` chains from each root module so no module is missed. -2. For each file, record which old-syntax markers it holds: top-level `- ` bullets in `***functional specs***`, nested `***acceptance tests***` sections, `requires:`, `import:`. +2. For each file, record which old-syntax markers it holds: flush-left `- ` bullets in `***functional specs***`, nested `***acceptance tests***` sections, `requires:`, `import:`. 3. Read every `config.yaml` to learn whether conformance tests are enabled for each module (a `conformance-tests-script` entry). 4. Show the user the inventory (files, markers, conformance on or off per module) and get explicit confirmation before writing anything. @@ -49,11 +49,11 @@ In every frontmatter, rename the key `requires:` to `base:` and the key `import: For each root module, walk the `***functional specs***` section top to bottom: -1. For every top-level `- ` bullet, propose a func-id per `semantic-ids.md`: a short slug that names the behavior (`add-task`, `export-csv`), unique in the module. Show the module's full list of proposed ids to the user in one `AskUserQuestion` and apply their edits. +1. For every flush-left `- ` bullet, propose a func-id per `semantic-ids.md`: a short slug that names the behavior (`add-task`, `export-csv`), unique in the module. Show the module's full list of proposed ids to the user in one `AskUserQuestion` and apply their edits. 2. Rewrite each functionality in place: - The `# ` heading on its own line. - The bullet text, without the leading `- `, as the statement line. A bare continuation line of the bullet is de-indented and kept as part of the statement. - - Each nested ` - ` sub-bullet de-indented by two spaces, so it becomes a `- ` clarification; deeper nesting is de-indented the same way. + - Each nested ` - ` sub-bullet kept as it is: it is already a clarification indented by two spaces, and deeper nesting keeps its indentation. - One blank line between functionalities. 3. Keep comment lines (`>`) and Liquid tags where they are. 4. Cut each nested `***acceptance tests***` block out of the core spec and keep its bullets for step 4, together with the func-id they belonged to. @@ -77,12 +77,12 @@ When conformance tests are not enabled for a module, skip this step and say so; ### 6. Verify -1. No `***functional specs***` section holds a top-level `- ` bullet; every functionality starts with a `# ` heading. +1. No `***functional specs***` section holds a flush-left `- ` bullet; every functionality starts with a `# ` heading. 2. No core spec holds an `***acceptance tests***` section. 3. No frontmatter holds `requires:` or `import:`. 4. Func-ids are unique per module; scenario-ids are unique per functionality. 5. Every test plain file binds to an existing functionality; when conformance tests are enabled, every functionality has at least one conformance scenario. -6. The number of functionalities per module equals the number of top-level bullets before the migration, and every old acceptance test appears once as an acceptance scenario. +6. The number of functionalities per module equals the number of flush-left bullets before the migration, and every old acceptance test appears once as an acceptance scenario. 7. Run `plain-healthcheck`. Until the plain-parser release that parses the current syntax is installed, its parser step reports that it is suspended; the other checks still run. ### 7. Report @@ -133,7 +133,7 @@ imports: # add-task A :User: can add a :Task:. Only valid :Task: items are added. -- The name must be at least 3 characters long. + - The name must be at least 3 characters long. ``` After, `tests//add-task.plain`, with the conformance scenario drafted by `add-test-scenarios`: diff --git a/forge/skills/plain-healthcheck/SKILL.md b/forge/skills/plain-healthcheck/SKILL.md index dbc4a07..b6c4e7c 100644 --- a/forge/skills/plain-healthcheck/SKILL.md +++ b/forge/skills/plain-healthcheck/SKILL.md @@ -108,10 +108,10 @@ Step 3 has two halves: a mechanical parse by `plain-parser`, then the checks tha Check every `.plain` module against the rules loaded by `load-plain-reference`: 1. **Section ownership.** No fact sits in a section that doesn't own it — `:UnitTests:` facts only in `***implementation reqs***`, `:ConformanceTests:` facts only in `***test reqs***`, technology only in `***implementation reqs***`. -2. **Bullets and headings.** Every line in `***definitions***`, `***implementation reqs***`, and `***test reqs***` is a valid `- ` list item or a correctly indented sub-bullet — no bare continuation lines. Every functionality in `***functional specs***` is a `# ` heading followed by its body. +2. **Bullets and headings.** Every line in `***definitions***`, `***implementation reqs***`, and `***test reqs***` is a valid `- ` list item or a correctly indented sub-bullet — no bare continuation lines. Every functionality in `***functional specs***` is a `# ` heading followed by its body, with its `- ` clarifications indented by two spaces. 3. **Linked resources.** Every linked resource is a single **text** file (not a binary), and no URL or folder path appears in the surrounding spec text unless it is a runtime value the generated software itself uses. 4. **Test plain files.** Every test plain file binds to an existing functionality (`module:` names a module, `func_id:` one of its `# func-id` headings) and has both sections in order. Scenario-ids are unique per functionality across every file bound to it. When the module's `config.yaml` has a `conformance-tests-script`, every functionality has at least one conformance scenario; a functionality with none is a failure that names `add-test-scenarios`. -5. **Old syntax.** A top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in a frontmatter is a failure that names `migrate-plain-syntax`. Do not fix it inline. +5. **Old syntax.** A flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in a frontmatter is a failure that names `migrate-plain-syntax`. Do not fix it inline. Functional-spec complexity is checked when a spec is authored (`add-functional-spec(s)` → `analyze-if-func-spec-too-complex`). Re-run `analyze-if-func-spec-too-complex` here only for a spec that was hand-edited since it was authored. diff --git a/forge/skills/refactor-module/SKILL.md b/forge/skills/refactor-module/SKILL.md index af52ad0..340b6a6 100644 --- a/forge/skills/refactor-module/SKILL.md +++ b/forge/skills/refactor-module/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## When to Use diff --git a/forge/skills/resolve-section-ownership/SKILL.md b/forge/skills/resolve-section-ownership/SKILL.md index 423ebcc..c7e5eaf 100644 --- a/forge/skills/resolve-section-ownership/SKILL.md +++ b/forge/skills/resolve-section-ownership/SKILL.md @@ -8,7 +8,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. This is the ownership counterpart to `break-down-func-spec`. That skill splits one functional spec into several because it is **too large**; this one moves facts between sections because they are in the **wrong place**. The two are unrelated: a correctly placed spec can be too large, and a one-line bullet can be misplaced. diff --git a/forge/skills/resolve-spec-conflict/SKILL.md b/forge/skills/resolve-spec-conflict/SKILL.md index 904e273..fcb6695 100644 --- a/forge/skills/resolve-spec-conflict/SKILL.md +++ b/forge/skills/resolve-spec-conflict/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a top-level `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## When This Applies From 3c9ae8ca83800e310b8fd2fa4fe2f24d00fc6495 Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Fri, 16 Oct 2026 11:24:53 +0200 Subject: [PATCH 15/25] Write test scenarios in plain English by default plain-forge writes a scenario as one sentence that names the action and the expected result, with indented bullets when the sentence is not enough. given/when/then lists stay allowed when the user asks for them. Refs ENG-190 --- README.md | 2 +- forge/rules/concise-specs-examples.md | 10 ++----- forge/rules/glossary.md | 2 +- forge/rules/test-plain-files.md | 34 +++++++--------------- forge/skills/add-test-scenarios/SKILL.md | 31 +++++++------------- forge/skills/migrate-plain-syntax/SKILL.md | 10 ++----- 6 files changed, 29 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 80f1826..e88c0b2 100644 --- a/README.md +++ b/README.md @@ -277,7 +277,7 @@ On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them int | `add-implementation-requirement` | Add a non-functional requirement to `***implementation reqs***` | | `add-test-requirement` | Add a testing requirement to `***test reqs***` | | `add-concept` | Define a new concept in `***definitions***` | -| `add-test-scenarios` | Write or update the test plain file of a functionality (`tests//.plain`): conformance scenarios in given/when/then, and acceptance scenarios for end-to-end workflows | +| `add-test-scenarios` | Write or update the test plain file of a functionality (`tests//.plain`): conformance scenarios in plain English, and acceptance scenarios for end-to-end workflows | | `add-resource` | Link an external file (schema, API spec) to a spec | | `add-template` | Create or include a reusable Liquid template | diff --git a/forge/rules/concise-specs-examples.md b/forge/rules/concise-specs-examples.md index ee3a6d4..553b83e 100644 --- a/forge/rules/concise-specs-examples.md +++ b/forge/rules/concise-specs-examples.md @@ -454,13 +454,9 @@ GOOD: ***acceptance tests*** # create-project -given: - - a :User: is signed in with no :Project: items -when: - - the :User: creates a :Project: named `Apollo` -then: - - :ProjectList: contains exactly one :Project:, named `Apollo` +A signed-in :User: with no :Project: items creates a :Project: named `Apollo` and finds it in :ProjectList:. + - :ProjectList: contains exactly one :Project:, named `Apollo`. ``` -One narrated paragraph becomes an ordered set of checkable steps, and "everything worked as +One narrated paragraph becomes one checkable sentence, and "everything worked as expected" becomes an assertion. diff --git a/forge/rules/glossary.md b/forge/rules/glossary.md index c44625d..f388fce 100644 --- a/forge/rules/glossary.md +++ b/forge/rules/glossary.md @@ -12,7 +12,7 @@ These are the names used across the rules, the skills, and the parser. Use them - **Functionality** — one functional spec in a module, identified by its func-id. - **Func-id** — the slug in the `# ` heading of a functionality. Lowercase letters, digits, and hyphens; unique in the module; written as `:` when the module is needed. It needs no meaning (see `semantic-ids.md`). - **Test plain file** — a `.plain` file that holds the scenarios of one functionality. Its frontmatter keys `module:` and `func_id:` bind it to that functionality; it has a `***conformance tests***` and an `***acceptance tests***` section. By convention it lives at `tests//.plain`. Several may bind to one functionality; their scenarios are merged (see `test-plain-files.md`). -- **Scenario** — one test case in a test plain file: a `# ` heading followed by free text that says what is done and what result is expected. plain-forge writes `given:` / `when:` / `then:`. The renderer never writes a scenario. +- **Scenario** — one test case in a test plain file: a `# ` heading followed by free text that says what is done and what result is expected. plain-forge writes plain English by default; `given:` / `when:` / `then:` is allowed. The renderer never writes a scenario. - **Scenario-id** — the slug in a scenario heading. Same syntax as a func-id; unique across the scenarios of one functionality after merging. The generated test carries it. - **Conformance scenario** — a scenario in `***conformance tests***`. The renderer implements every conformance scenario and nothing else. A functionality is done only when all of its conformance tests pass. When conformance tests are enabled, every functionality has at least one. - **Acceptance scenario** — a scenario in `***acceptance tests***`: an end-to-end check that may span more than one functionality, rendered after the conformance tests of its functionality. diff --git a/forge/rules/test-plain-files.md b/forge/rules/test-plain-files.md index f8f0466..bc3f82a 100644 --- a/forge/rules/test-plain-files.md +++ b/forge/rules/test-plain-files.md @@ -33,11 +33,10 @@ A test plain file holds the test scenarios of one functionality. The renderer ge - Scenario text follows `concise-specs.md`: no narration, no rationale, no step a reader would assume ## The shape plain-forge writes -- plain-forge writes every scenario as `given:` / `when:` / `then:` lists; hand-written scenarios may use any shape -- `given:` the state before the action (running app, existing data, configuration), one `- ` item per fact -- `when:` the one action under test -- `then:` the observable results, one `- ` item per result -- Omit `given:` when there is no setup; never omit `when:` or `then:` +- plain-forge writes every scenario in plain English, with the same shape as a functionality: one statement line, then `- ` bullets indented by two spaces when the line is not enough; hand-written scenarios may use any shape +- The statement line is one sentence that names the action and the expected result (`Adding a :Task: named "Go" to an empty :TaskList: is rejected.`) +- The bullets list, in order, the state before the action, the action, and each observable result that the statement line does not already carry, one fact per bullet +- `given:` / `when:` / `then:` lists or Gherkin steps are allowed when the user asks for them; keep one shape per project - Values that matter to the check are concrete (`named "Buy milk"`, `exits with status 0`), never descriptive (`a valid name`) ## Conformance scenarios @@ -75,29 +74,16 @@ func_id: add-task ***conformance tests*** # add-valid-task -given: - - :App: is running with an empty :TaskList: -when: - - :User: adds a :Task: named "Buy milk" -then: - - :TaskList: shows one :Task: named "Buy milk" +Adding a :Task: named "Buy milk" to an empty :TaskList: shows one :Task: named "Buy milk". # reject-short-name -given: - - :App: is running with an empty :TaskList: -when: - - :User: adds a :Task: named "Go" -then: - - :App: shows the error "Name must be at least 3 characters" - - :TaskList: stays empty +Adding a :Task: named "Go" to an empty :TaskList: is rejected. + - :App: shows the error "Name must be at least 3 characters". + - :TaskList: stays empty. ***acceptance tests*** # add-then-complete -given: - - :App: is running with an empty :TaskList: -when: - - :User: adds a :Task: named "Buy milk" and marks it completed -then: - - :TaskList: shows one :Task: named "Buy milk", marked completed +A :User: adds a :Task: named "Buy milk" to an empty :TaskList: and marks it completed. + - :TaskList: shows one :Task: named "Buy milk", marked completed. ``` diff --git a/forge/skills/add-test-scenarios/SKILL.md b/forge/skills/add-test-scenarios/SKILL.md index fddcff6..4d68660 100644 --- a/forge/skills/add-test-scenarios/SKILL.md +++ b/forge/skills/add-test-scenarios/SKILL.md @@ -3,7 +3,7 @@ name: add-test-scenarios description: >- Write or update the test plain file of one functionality: create tests//.plain, add the conformance scenarios that cover the - functional spec in given/when/then form, and add acceptance scenarios for + functional spec in plain English, and add acceptance scenarios for end-to-end workflows. Use right after a functional spec is added or changed, when a functionality has no conformance scenario while conformance tests are enabled, or when the user asks for a test scenario. This skill replaces the @@ -17,7 +17,7 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule ## What this skill writes -One test plain file per functionality, at `tests//.plain`, with the frontmatter keys `module:` and `func_id:` and the two sections `***conformance tests***` and `***acceptance tests***`. Every scenario is a `# ` heading followed by `given:` / `when:` / `then:` lists. The renderer generates one test per scenario and nothing else, so the scenarios in this file are the only tests the functionality gets. +One test plain file per functionality, at `tests//.plain`, with the frontmatter keys `module:` and `func_id:` and the two sections `***conformance tests***` and `***acceptance tests***`. Every scenario is a `# ` heading, one plain-English statement line, and `- ` bullets indented by two spaces when the line is not enough. The renderer generates one test per scenario and nothing else, so the scenarios in this file are the only tests the functionality gets. ## When to Use @@ -36,10 +36,10 @@ Conformance tests are enabled for a module when the `config.yaml` that governs i 2. **Read the context.** Read the functional spec (heading and body), the `***definitions***` the module sees (its own, its `imports`, and the `exported_concepts` of its `base` modules), the `***test reqs***`, and the functional specs that come before this one, because a scenario may rely on behavior they established. 3. **Locate or create the test plain file.** Look for `tests//.plain`. Also scan the frontmatter of every other `.plain` file under `tests/` for the same `(module, func_id)` pair; read those files too, so scenario-ids stay unique across the merged set. Create the file with the frontmatter and both sections if it does not exist. 4. **List the outcomes the spec states.** Walk the statement line and every clarification bullet. Write down the main path and each boundary, validation, and error case the spec names. Do not add an outcome the spec does not state: a scenario may not extend the spec. If a needed outcome is missing from the spec, ask the user one question and route the answer into the functional spec through `add-functional-spec` (or an inline edit of the clarification bullets) before writing the scenario. -5. **Draft one conformance scenario per outcome.** Give each a scenario-id per `semantic-ids.md` (short slug, unique for this functionality). Write the body as: - - `given:` the state before the action, one `- ` item per fact; omit when there is no setup. - - `when:` the one action under test. - - `then:` every observable result, one `- ` item per result, with concrete values. +5. **Draft one conformance scenario per outcome.** Give each a scenario-id per `semantic-ids.md` (short slug, unique for this functionality). Write the body in plain English: + - One statement line: a sentence that names the action and the expected result, with concrete values. + - When that line is not enough, `- ` bullets indented by two spaces, in order: the state before the action, the one action under test, and each observable result, one fact per bullet. + - `given:` / `when:` / `then:` lists are allowed when the user asks for them; otherwise plain English, and one shape per project. Exercise only this functionality, through the interface the spec names, as a black box. 6. **Draft acceptance scenarios only when they add a workflow.** An acceptance scenario spans this functionality and functionalities rendered before it. Write one only when the user asks for it or when the functionality completes a user-visible workflow that no single conformance scenario shows. Otherwise leave `***acceptance tests***` empty. 7. **Run the review pass** from `concise-specs.md` over every scenario: no narration, no rationale, no step a reader would assume, concrete values, plain words. @@ -75,21 +75,12 @@ func_id: add-task ***conformance tests*** # add-valid-task -given: - - :App: is running with an empty :TaskList: -when: - - :User: adds a :Task: named "Buy milk" -then: - - :TaskList: shows one :Task: named "Buy milk" +Adding a :Task: named "Buy milk" to an empty :TaskList: shows one :Task: named "Buy milk". # reject-short-name -given: - - :App: is running with an empty :TaskList: -when: - - :User: adds a :Task: named "Go" -then: - - :App: shows the error "Name must be at least 3 characters" - - :TaskList: stays empty +Adding a :Task: named "Go" to an empty :TaskList: is rejected. + - :App: shows the error "Name must be at least 3 characters". + - :TaskList: stays empty. ***acceptance tests*** ``` @@ -100,7 +91,7 @@ then: - [ ] `***conformance tests***` comes first, `***acceptance tests***` second; both sections exist - [ ] At least one conformance scenario when conformance tests are enabled for the module - [ ] Every outcome the functional spec states has one conformance scenario; no scenario states an outcome the spec does not -- [ ] Every scenario is a `# ` heading with `given:` / `when:` / `then:` lists and concrete values +- [ ] Every scenario is a `# ` heading, a plain-English statement line, indented bullets when needed, and concrete values - [ ] Scenario-ids follow `semantic-ids.md` and are unique across every test plain file bound to this functionality - [ ] Scenarios are black box and language-agnostic - [ ] Trimmed per `concise-specs.md` diff --git a/forge/skills/migrate-plain-syntax/SKILL.md b/forge/skills/migrate-plain-syntax/SKILL.md index 7d7cd1b..79722f3 100644 --- a/forge/skills/migrate-plain-syntax/SKILL.md +++ b/forge/skills/migrate-plain-syntax/SKILL.md @@ -63,7 +63,7 @@ For each root module, walk the `***functional specs***` section top to bottom: For every functionality that had nested acceptance tests, create `tests//.plain` with the frontmatter keys `module:` (the core spec's file name without `.plain`) and `func_id:`, an empty `***conformance tests***` section, and an `***acceptance tests***` section holding one scenario per old acceptance test: - A `# ` heading per test, with a slug derived from the test text, unique in the file. -- The old test text as the scenario body, verbatim. When the text already separates setup, action, and result, write it as `given:` / `when:` / `then:` lists; when a faithful split is not obvious, keep the prose. Free text is valid scenario text. +- The old test text as the scenario body, verbatim: the bullet text becomes the statement line and its nested sub-bullets keep their indentation. Free text is valid scenario text. Show each created file to the user as a snippet and apply their edits. @@ -147,12 +147,8 @@ func_id: add-task ***conformance tests*** # reject-short-name -given: - - :App: is running with an empty :TaskList: -when: - - :User: adds a :Task: named "Go" -then: - - :TaskList: stays empty +Adding a :Task: named "Go" to an empty :TaskList: is rejected. + - :TaskList: stays empty. ***acceptance tests*** From 289b2b60ae9fcf4279f4869bfb0fd8d1ae0102ed Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Mon, 19 Oct 2026 08:53:44 +0200 Subject: [PATCH 16/25] Define conformance as expected use and acceptance as unexpected use A conformance scenario describes the expected use of a functionality: its main path with valid parameters, the expected caller and the data present. An acceptance scenario describes use that is not expected: invalid parameters, wrong or missing authentication, missing data, an unavailable dependency. The kind of use decides the section, not the number of functionalities a scenario touches; a scenario across functionalities is bound to the last one it exercises. Refs ENG-190 --- forge/rules/concise-specs-examples.md | 4 ++-- forge/rules/glossary.md | 4 ++-- forge/rules/test-plain-files.md | 31 ++++++++++++++++----------- forge/rules/test-reqs.md | 4 ++-- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/forge/rules/concise-specs-examples.md b/forge/rules/concise-specs-examples.md index 553b83e..9309627 100644 --- a/forge/rules/concise-specs-examples.md +++ b/forge/rules/concise-specs-examples.md @@ -442,7 +442,7 @@ The most common single case of this is a functional spec that states a mechanism BAD: ```plain -***acceptance tests*** +***conformance tests*** # create-project The tester should first open the application and log in with a valid account, then navigate to the projects area where they will be able to see the list of projects, and then they should create a new project by filling in the form, after which they should verify that everything worked as expected and the project shows up correctly. @@ -451,7 +451,7 @@ The tester should first open the application and log in with a valid account, th GOOD: ```plain -***acceptance tests*** +***conformance tests*** # create-project A signed-in :User: with no :Project: items creates a :Project: named `Apollo` and finds it in :ProjectList:. diff --git a/forge/rules/glossary.md b/forge/rules/glossary.md index f388fce..ada1c9e 100644 --- a/forge/rules/glossary.md +++ b/forge/rules/glossary.md @@ -14,8 +14,8 @@ These are the names used across the rules, the skills, and the parser. Use them - **Test plain file** — a `.plain` file that holds the scenarios of one functionality. Its frontmatter keys `module:` and `func_id:` bind it to that functionality; it has a `***conformance tests***` and an `***acceptance tests***` section. By convention it lives at `tests//.plain`. Several may bind to one functionality; their scenarios are merged (see `test-plain-files.md`). - **Scenario** — one test case in a test plain file: a `# ` heading followed by free text that says what is done and what result is expected. plain-forge writes plain English by default; `given:` / `when:` / `then:` is allowed. The renderer never writes a scenario. - **Scenario-id** — the slug in a scenario heading. Same syntax as a func-id; unique across the scenarios of one functionality after merging. The generated test carries it. -- **Conformance scenario** — a scenario in `***conformance tests***`. The renderer implements every conformance scenario and nothing else. A functionality is done only when all of its conformance tests pass. When conformance tests are enabled, every functionality has at least one. -- **Acceptance scenario** — a scenario in `***acceptance tests***`: an end-to-end check that may span more than one functionality, rendered after the conformance tests of its functionality. +- **Conformance scenario** — a scenario in `***conformance tests***`: the expected use of the functionality, its main path, with valid parameters, the expected caller, and the data present. The renderer implements every scenario of the test plain file and nothing else. A functionality is done only when all of its conformance tests pass. When conformance tests are enabled, every functionality has at least one. +- **Acceptance scenario** — a scenario in `***acceptance tests***`: use of the same functionality that is not expected, such as invalid parameters, wrong or missing authentication, missing data, or an unavailable dependency. It is rendered after the conformance tests of its functionality. A scenario that spans several functionalities is bound to the last one it exercises and sorted by the same rule. - **Conformance test** — generated test code that implements one conformance scenario. It runs the implementation as a black box and is labelled with the scenario-id. - **Acceptance test** — generated test code that implements one acceptance scenario, labelled with the scenario-id. - **Old syntax** — functional specs as `- ` bullets, nested `***acceptance tests***`, and the `requires:` / `import:` directives. Not supported; `migrate-plain-syntax` converts a project. diff --git a/forge/rules/test-plain-files.md b/forge/rules/test-plain-files.md index bc3f82a..d56f7b8 100644 --- a/forge/rules/test-plain-files.md +++ b/forge/rules/test-plain-files.md @@ -40,17 +40,23 @@ A test plain file holds the test scenarios of one functionality. The renderer ge - Values that matter to the check are concrete (`named "Buy milk"`, `exits with status 0`), never descriptive (`a valid name`) ## Conformance scenarios -- `***conformance tests***` holds the scenarios that gate the functionality: it is done only when every conformance scenario passes +- `***conformance tests***` holds the scenarios of the expected use of the functionality: its main path, with valid parameters, the expected caller, and the data present. For a REST endpoint: what it returns when the caller is authenticated, the data exists, and the parameters are valid +- These scenarios gate the functionality: it is done only when every conformance scenario passes - When conformance tests are enabled for the module (its `config.yaml` has a `conformance-tests-script`), every functionality has at least one conformance scenario; an empty section is an error -- Each conformance scenario exercises the functionality of this file alone, through the interface the functional spec names; it does not test other functionalities -- Cover the main path first, then each boundary and error case the functional spec states; stop when every stated behavior has one scenario +- Cover the main path first, then each variant of expected use the functional spec states (another valid input shape, an optional parameter); stop when every expected behavior has one scenario - Do not write the same scenario twice in different words; one scenario per distinct outcome ## Acceptance scenarios -- `***acceptance tests***` holds end-to-end checks that may span several functionalities; the section may be empty -- An acceptance scenario starts from the functionality of this file and may use the behavior of functionalities rendered before it -- It is rendered after the conformance tests of its functionality pass -- Write one only for a workflow that no single conformance scenario shows; do not restate a conformance scenario here +- `***acceptance tests***` holds the scenarios of use that is not expected: invalid parameters, wrong or missing authentication, missing data, an unavailable dependency. For the same REST endpoint: what it returns when authentication is wrong or the parameters are invalid +- Write one for every error and boundary outcome the functional spec states; the section may be empty when the spec states none +- An acceptance scenario is rendered after the conformance tests of its functionality pass +- A scenario that needs a dependency to fail (a backend that does not respond, a database that is unavailable) is written only when `***test reqs***` let the test cause that failure, for example with a local mock of that dependency; otherwise ask the user before writing it +- The kind of use decides the section, not the number of functionalities the scenario touches (see *Scenarios across functionalities*) + +## Scenarios across functionalities +- A scenario may use functionalities rendered before the one it is bound to, for its setup or as steps of a longer path +- Such a scenario is bound to the last functionality it exercises, because the earlier ones must exist when it runs +- It goes under `***conformance tests***` when every step is expected use, and under `***acceptance tests***` when any step is not; plain-forge writes one only when the user asks for it ## Keeping test plain files in step with functional specs - Every change to a functional spec is followed by a review of its test plain file: scenarios that no longer follow from the spec are removed or rewritten, and new behavior gets a scenario @@ -76,14 +82,13 @@ func_id: add-task # add-valid-task Adding a :Task: named "Buy milk" to an empty :TaskList: shows one :Task: named "Buy milk". +# add-second-task +Adding a :Task: named "Call mom" to a :TaskList: that holds "Buy milk" shows two :Task: items, "Buy milk" first. + +***acceptance tests*** + # reject-short-name Adding a :Task: named "Go" to an empty :TaskList: is rejected. - :App: shows the error "Name must be at least 3 characters". - :TaskList: stays empty. - -***acceptance tests*** - -# add-then-complete -A :User: adds a :Task: named "Buy milk" to an empty :TaskList: and marks it completed. - - :TaskList: shows one :Task: named "Buy milk", marked completed. ``` diff --git a/forge/rules/test-reqs.md b/forge/rules/test-reqs.md index 570563b..e5509eb 100644 --- a/forge/rules/test-reqs.md +++ b/forge/rules/test-reqs.md @@ -31,8 +31,8 @@ When writing or editing a `***test reqs***` section in a `.plain` file, always f | Test type | Where to specify | Generated from | Purpose | |-----------|-----------------|----------------|---------| | Unit tests | `***implementation reqs***` | the implementation, by the renderer | Test individual functionalities in isolation | -| Conformance tests | how: `***test reqs***`; what: `***conformance tests***` of the functionality's test plain file | the conformance scenarios, one test per scenario-id | Verify one functionality against its conformance scenarios | -| Acceptance tests | how: `***test reqs***`; what: `***acceptance tests***` of the functionality's test plain file | the acceptance scenarios, one test per scenario-id | Verify an end-to-end workflow that may span functionalities | +| Conformance tests | how: `***test reqs***`; what: `***conformance tests***` of the functionality's test plain file | the conformance scenarios, one test per scenario-id | Verify the expected use of one functionality, its main path | +| Acceptance tests | how: `***test reqs***`; what: `***acceptance tests***` of the functionality's test plain file | the acceptance scenarios, one test per scenario-id | Verify one functionality under use that is not expected: invalid parameters, wrong or missing authentication, missing data | ## No duplication - Do not duplicate guidance already present in the file or its imports From 84a2bc44da90210246bb04a58456d02667d704d5 Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Mon, 19 Oct 2026 08:53:44 +0200 Subject: [PATCH 17/25] Sort scenarios into expected and unexpected use in the skills add-test-scenarios sorts every outcome of the spec into the section its kind of use decides and checks that a dependency-failure scenario is one the test reqs let the test cause. migrate-plain-syntax sorts old nested acceptance tests by content with the user's confirmation. plain-healthcheck reports a scenario in the wrong section. Refs ENG-190 --- forge/skills/add-test-requirement/SKILL.md | 4 ++-- forge/skills/add-test-scenarios/SKILL.md | 26 ++++++++++++---------- forge/skills/migrate-plain-syntax/SKILL.md | 24 ++++++++++---------- forge/skills/plain-healthcheck/SKILL.md | 2 +- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/forge/skills/add-test-requirement/SKILL.md b/forge/skills/add-test-requirement/SKILL.md index 361d649..4a3b18f 100644 --- a/forge/skills/add-test-requirement/SKILL.md +++ b/forge/skills/add-test-requirement/SKILL.md @@ -44,8 +44,8 @@ Test reqs specify how **conformance tests** should be written and run. Common co | Test Type | Where to Specify | Purpose | |-----------|-----------------|---------| | Unit tests | `***implementation reqs***` | Test individual functionalities in isolation | -| Conformance tests | how: `***test reqs***`; what: `***conformance tests***` of the test plain file | Verify one functionality against its conformance scenarios | -| Acceptance tests | how: `***test reqs***`; what: `***acceptance tests***` of the test plain file | Verify an end-to-end workflow that may span functionalities | +| Conformance tests | how: `***test reqs***`; what: `***conformance tests***` of the test plain file | Verify the expected use of one functionality, its main path | +| Acceptance tests | how: `***test reqs***`; what: `***acceptance tests***` of the test plain file | Verify one functionality under use that is not expected: invalid parameters, wrong or missing authentication, missing data | ## Format diff --git a/forge/skills/add-test-scenarios/SKILL.md b/forge/skills/add-test-scenarios/SKILL.md index 4d68660..c467290 100644 --- a/forge/skills/add-test-scenarios/SKILL.md +++ b/forge/skills/add-test-scenarios/SKILL.md @@ -2,9 +2,10 @@ name: add-test-scenarios description: >- Write or update the test plain file of one functionality: create - tests//.plain, add the conformance scenarios that cover the - functional spec in plain English, and add acceptance scenarios for - end-to-end workflows. Use right after a functional spec is added or changed, + tests//.plain, add the conformance scenarios for its + expected use and the acceptance scenarios for its unexpected use (invalid + parameters, wrong or missing authentication, missing data), in plain + English. Use right after a functional spec is added or changed, when a functionality has no conformance scenario while conformance tests are enabled, or when the user asks for a test scenario. This skill replaces the removed add-acceptance-test skill; nested ***acceptance tests*** under a @@ -17,7 +18,7 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule ## What this skill writes -One test plain file per functionality, at `tests//.plain`, with the frontmatter keys `module:` and `func_id:` and the two sections `***conformance tests***` and `***acceptance tests***`. Every scenario is a `# ` heading, one plain-English statement line, and `- ` bullets indented by two spaces when the line is not enough. The renderer generates one test per scenario and nothing else, so the scenarios in this file are the only tests the functionality gets. +One test plain file per functionality, at `tests//.plain`, with the frontmatter keys `module:` and `func_id:` and the two sections `***conformance tests***` and `***acceptance tests***`. Every scenario is a `# ` heading, one plain-English statement line, and `- ` bullets indented by two spaces when the line is not enough. Expected use goes under `***conformance tests***`, use that is not expected under `***acceptance tests***`. The renderer generates one test per scenario and nothing else, so the scenarios in this file are the only tests the functionality gets. ## When to Use @@ -35,13 +36,13 @@ Conformance tests are enabled for a module when the `config.yaml` that governs i 1. **Identify the functionality.** Take the module name and func-id from the caller or the user. If the user points at a spec by its text, find its `# ` heading. If the core spec has no `# ` headings (old syntax), stop and point the user to `migrate-plain-syntax`. 2. **Read the context.** Read the functional spec (heading and body), the `***definitions***` the module sees (its own, its `imports`, and the `exported_concepts` of its `base` modules), the `***test reqs***`, and the functional specs that come before this one, because a scenario may rely on behavior they established. 3. **Locate or create the test plain file.** Look for `tests//.plain`. Also scan the frontmatter of every other `.plain` file under `tests/` for the same `(module, func_id)` pair; read those files too, so scenario-ids stay unique across the merged set. Create the file with the frontmatter and both sections if it does not exist. -4. **List the outcomes the spec states.** Walk the statement line and every clarification bullet. Write down the main path and each boundary, validation, and error case the spec names. Do not add an outcome the spec does not state: a scenario may not extend the spec. If a needed outcome is missing from the spec, ask the user one question and route the answer into the functional spec through `add-functional-spec` (or an inline edit of the clarification bullets) before writing the scenario. -5. **Draft one conformance scenario per outcome.** Give each a scenario-id per `semantic-ids.md` (short slug, unique for this functionality). Write the body in plain English: +4. **List and sort the outcomes the spec states.** Walk the statement line and every clarification bullet. Write down the main path and each boundary, validation, and error case the spec names. Sort each outcome: expected use (the main path and its valid variants) goes to `***conformance tests***`; use that is not expected (invalid parameters, wrong or missing authentication, missing data, an unavailable dependency) goes to `***acceptance tests***`. Do not add an outcome the spec does not state: a scenario may not extend the spec. If a needed outcome is missing from the spec, ask the user one question and route the answer into the functional spec through `add-functional-spec` (or an inline edit of the clarification bullets) before writing the scenario. +5. **Draft one scenario per outcome, in its section.** Give each a scenario-id per `semantic-ids.md` (short slug, unique for this functionality). Write the body in plain English: - One statement line: a sentence that names the action and the expected result, with concrete values. - When that line is not enough, `- ` bullets indented by two spaces, in order: the state before the action, the one action under test, and each observable result, one fact per bullet. - `given:` / `when:` / `then:` lists are allowed when the user asks for them; otherwise plain English, and one shape per project. - Exercise only this functionality, through the interface the spec names, as a black box. -6. **Draft acceptance scenarios only when they add a workflow.** An acceptance scenario spans this functionality and functionalities rendered before it. Write one only when the user asks for it or when the functionality completes a user-visible workflow that no single conformance scenario shows. Otherwise leave `***acceptance tests***` empty. + Test the functionality through the interface the spec names, as a black box. A scenario may use earlier functionalities for its setup; a scenario that spans several functionalities is bound to the last one it exercises and is written only when the user asks for it. +6. **Check what the test can cause.** A scenario that needs a dependency to fail (a backend that does not respond, a database that is unavailable) is written only when `***test reqs***` let the test cause that failure, for example with a local mock of that dependency. Otherwise ask the user one question: add such a test req, or leave the scenario out. 7. **Run the review pass** from `concise-specs.md` over every scenario: no narration, no rationale, no step a reader would assume, concrete values, plain words. 8. **Write the file and read it again.** Confirm the frontmatter keys, the section order, at least one conformance scenario when conformance tests are enabled, and that no body line starts with `# ` or `***`. 9. **Review with the user** when running inside `forge-plain` or `add-feature`: show the scenarios as the snippet for that loop iteration and apply the response before moving on. @@ -57,7 +58,7 @@ Conformance tests are enabled for a module when the `config.yaml` that governs i ## Rules - A scenario is a direct consequence of the functional spec: it does not contradict, narrow, or extend it. -- A conformance scenario tests this functionality alone; an acceptance scenario may use earlier functionalities. +- The kind of use decides the section, not the number of functionalities a scenario touches: expected use under `***conformance tests***`, everything else under `***acceptance tests***`. - Scenarios are language-agnostic and black box: no function, class, or file of the implementation is named. - Values that matter to the check are concrete (`named "Buy milk"`, `returns 404`); descriptive values (`a valid name`) are not checkable. - One scenario per distinct outcome; the same outcome is never written twice in different words. @@ -77,12 +78,12 @@ func_id: add-task # add-valid-task Adding a :Task: named "Buy milk" to an empty :TaskList: shows one :Task: named "Buy milk". +***acceptance tests*** + # reject-short-name Adding a :Task: named "Go" to an empty :TaskList: is rejected. - :App: shows the error "Name must be at least 3 characters". - :TaskList: stays empty. - -***acceptance tests*** ``` ## Validation Checklist @@ -90,7 +91,8 @@ Adding a :Task: named "Go" to an empty :TaskList: is rejected. - [ ] The file is at `tests//.plain` with `module:` and `func_id:` naming an existing functionality - [ ] `***conformance tests***` comes first, `***acceptance tests***` second; both sections exist - [ ] At least one conformance scenario when conformance tests are enabled for the module -- [ ] Every outcome the functional spec states has one conformance scenario; no scenario states an outcome the spec does not +- [ ] Every outcome the functional spec states has one scenario, in the section its kind of use decides; no scenario states an outcome the spec does not +- [ ] No unexpected-use scenario under `***conformance tests***`, no expected-use scenario under `***acceptance tests***` - [ ] Every scenario is a `# ` heading, a plain-English statement line, indented bullets when needed, and concrete values - [ ] Scenario-ids follow `semantic-ids.md` and are unique across every test plain file bound to this functionality - [ ] Scenarios are black box and language-agnostic diff --git a/forge/skills/migrate-plain-syntax/SKILL.md b/forge/skills/migrate-plain-syntax/SKILL.md index 79722f3..fb619eb 100644 --- a/forge/skills/migrate-plain-syntax/SKILL.md +++ b/forge/skills/migrate-plain-syntax/SKILL.md @@ -60,16 +60,16 @@ For each root module, walk the `***functional specs***` section top to bottom: ### 4. Move the acceptance tests into test plain files -For every functionality that had nested acceptance tests, create `tests//.plain` with the frontmatter keys `module:` (the core spec's file name without `.plain`) and `func_id:`, an empty `***conformance tests***` section, and an `***acceptance tests***` section holding one scenario per old acceptance test: +For every functionality that had nested acceptance tests, create `tests//.plain` with the frontmatter keys `module:` (the core spec's file name without `.plain`) and `func_id:` and both sections, holding one scenario per old acceptance test, sorted by its content: a test of the expected use (valid input, the main path) goes under `***conformance tests***`; a test of use that is not expected (invalid input, wrong or missing authentication, missing data) goes under `***acceptance tests***`: - A `# ` heading per test, with a slug derived from the test text, unique in the file. - The old test text as the scenario body, verbatim: the bullet text becomes the statement line and its nested sub-bullets keep their indentation. Free text is valid scenario text. -Show each created file to the user as a snippet and apply their edits. +Show each created file to the user as a snippet, with the split, and apply their edits before moving on. ### 5. Draft conformance scenarios -When conformance tests are enabled for a module, every functionality needs at least one conformance scenario. Walk the module's functionalities in order and invoke `add-test-scenarios` for each one; it creates the test plain file when step 4 did not, drafts the scenarios from the functional spec, and reviews them with the user one functionality at a time. +When conformance tests are enabled for a module, every functionality needs at least one conformance scenario. Walk the module's functionalities in order and invoke `add-test-scenarios` for each one; it creates the test plain file when step 4 did not, drafts the scenarios the spec still lacks in both sections, and reviews them with the user one functionality at a time. When `plain_modules//tests/` holds conformance tests from an earlier render, read the folder of the functionality first: each generated test is a candidate scenario the user can keep, edit, or drop, which is faster than drafting from the spec alone. @@ -82,7 +82,7 @@ When conformance tests are not enabled for a module, skip this step and say so; 3. No frontmatter holds `requires:` or `import:`. 4. Func-ids are unique per module; scenario-ids are unique per functionality. 5. Every test plain file binds to an existing functionality; when conformance tests are enabled, every functionality has at least one conformance scenario. -6. The number of functionalities per module equals the number of flush-left bullets before the migration, and every old acceptance test appears once as an acceptance scenario. +6. The number of functionalities per module equals the number of flush-left bullets before the migration, and every old acceptance test appears once as a scenario, in the section its content decides. 7. Run `plain-healthcheck`. Until the plain-parser release that parses the current syntax is installed, its parser step reports that it is suspended; the other checks still run. ### 7. Report @@ -95,7 +95,7 @@ Per module: functionalities migrated, directives renamed, acceptance tests moved - Spec text verbatim; the shape changes, the words do not. - The order of functionalities is unchanged; func-ids carry no order. - Func-ids are proposed by this skill and approved by the user before they are written. -- Old acceptance tests become acceptance scenarios, never conformance scenarios; conformance scenarios are drafted from the spec with `add-test-scenarios`. +- Old acceptance tests are sorted by content: expected use becomes a conformance scenario, the rest become acceptance scenarios, and the user confirms the split; `add-test-scenarios` then drafts the scenarios the spec still lacks. - Nothing under `plain_modules/` is edited. ## Format @@ -117,6 +117,7 @@ import: ***acceptance tests*** - Adding a :Task: named "Buy milk" to an empty :TaskList: shows one :Task:. + - Adding a :Task: named "Go" is rejected and :TaskList: stays empty. ``` After, the core spec: @@ -136,7 +137,7 @@ A :User: can add a :Task:. Only valid :Task: items are added. - The name must be at least 3 characters long. ``` -After, `tests//add-task.plain`, with the conformance scenario drafted by `add-test-scenarios`: +After, `tests//add-task.plain`, with the two old acceptance tests sorted by content: ```plain --- @@ -146,14 +147,13 @@ func_id: add-task ***conformance tests*** -# reject-short-name -Adding a :Task: named "Go" to an empty :TaskList: is rejected. - - :TaskList: stays empty. +# add-to-empty-list +Adding a :Task: named "Buy milk" to an empty :TaskList: shows one :Task:. ***acceptance tests*** -# add-to-empty-list -Adding a :Task: named "Buy milk" to an empty :TaskList: shows one :Task:. +# reject-short-name +Adding a :Task: named "Go" is rejected and :TaskList: stays empty. ``` ## Validation Checklist @@ -161,7 +161,7 @@ Adding a :Task: named "Buy milk" to an empty :TaskList: shows one :Task:. - [ ] Every `.plain` file of the project was inventoried and the user confirmed the pass - [ ] `requires:` and `import:` renamed to `base:` and `imports:` in every frontmatter - [ ] Every functionality has a `# ` heading approved by the user; text verbatim; order unchanged -- [ ] No `***acceptance tests***` section remains in a core spec; every old acceptance test is an acceptance scenario in a test plain file +- [ ] No `***acceptance tests***` section remains in a core spec; every old acceptance test is a scenario in a test plain file, in the section its content decides, confirmed by the user - [ ] Every functionality has at least one conformance scenario when conformance tests are enabled for its module - [ ] Func-ids unique per module; scenario-ids unique per functionality - [ ] `plain-healthcheck` run; its report shown to the user diff --git a/forge/skills/plain-healthcheck/SKILL.md b/forge/skills/plain-healthcheck/SKILL.md index b6c4e7c..f8e66a7 100644 --- a/forge/skills/plain-healthcheck/SKILL.md +++ b/forge/skills/plain-healthcheck/SKILL.md @@ -110,7 +110,7 @@ Check every `.plain` module against the rules loaded by `load-plain-reference`: 1. **Section ownership.** No fact sits in a section that doesn't own it — `:UnitTests:` facts only in `***implementation reqs***`, `:ConformanceTests:` facts only in `***test reqs***`, technology only in `***implementation reqs***`. 2. **Bullets and headings.** Every line in `***definitions***`, `***implementation reqs***`, and `***test reqs***` is a valid `- ` list item or a correctly indented sub-bullet — no bare continuation lines. Every functionality in `***functional specs***` is a `# ` heading followed by its body, with its `- ` clarifications indented by two spaces. 3. **Linked resources.** Every linked resource is a single **text** file (not a binary), and no URL or folder path appears in the surrounding spec text unless it is a runtime value the generated software itself uses. -4. **Test plain files.** Every test plain file binds to an existing functionality (`module:` names a module, `func_id:` one of its `# func-id` headings) and has both sections in order. Scenario-ids are unique per functionality across every file bound to it. When the module's `config.yaml` has a `conformance-tests-script`, every functionality has at least one conformance scenario; a functionality with none is a failure that names `add-test-scenarios`. +4. **Test plain files.** Every test plain file binds to an existing functionality (`module:` names a module, `func_id:` one of its `# func-id` headings) and has both sections in order. Scenario-ids are unique per functionality across every file bound to it. When the module's `config.yaml` has a `conformance-tests-script`, every functionality has at least one conformance scenario; a functionality with none is a failure that names `add-test-scenarios`. A scenario under `***conformance tests***` that describes use that is not expected (invalid parameters, wrong or missing authentication, missing data), or one under `***acceptance tests***` that describes the expected use, is a failure that names `add-test-scenarios`. 5. **Old syntax.** A flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in a frontmatter is a failure that names `migrate-plain-syntax`. Do not fix it inline. Functional-spec complexity is checked when a spec is authored (`add-functional-spec(s)` → `analyze-if-func-spec-too-complex`). Re-run `analyze-if-func-spec-too-complex` here only for a spec that was hand-edited since it was authored. From 6a42084874fada0df1c537c06b78604ad585109f Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Mon, 19 Oct 2026 08:53:44 +0200 Subject: [PATCH 18/25] Describe the new meaning of the two scenario sections in the repo docs Refs ENG-190 --- AGENTS.md | 2 +- CLAUDE.md | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8f7b430..8b8d569 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -68,7 +68,7 @@ A `.plain` file is a module: YAML frontmatter (`description`, `imports:`, `base: | `***implementation reqs***` | HOW it's built (tech/architecture) **and everything about `:UnitTests:`** | | `***functional specs***` | WHAT it does — observable, language-agnostic; one `# func-id` heading per functionality; each spec ≤200 changed LOC; rendered incrementally top-to-bottom | | `***test reqs***` | **everything about `:ConformanceTests:`** (framework, run command, mocking/network policy) | -| test plain file (`***conformance tests***`, `***acceptance tests***`) | the scenarios of one functionality; at least one conformance scenario when conformance tests are enabled; acceptance scenarios are end-to-end workflows | +| test plain file (`***conformance tests***`, `***acceptance tests***`) | the scenarios of one functionality; conformance scenarios describe the expected use, acceptance scenarios the use that is not expected (invalid parameters, wrong or missing authentication, missing data); at least one conformance scenario when conformance tests are enabled | Other constraints the rules enforce (see `forge/rules/`): functional specs are mandatorily routed through `add-functional-spec(s)` (never hand-authored), checked for the 200-LOC limit (`analyze-if-func-spec-too-complex` → `break-down-func-spec`) and for conflicts (`analyze-func-specs` → `resolve-spec-conflict`); external artifacts (JSON Schema, OpenAPI, payloads) are **linked** as single local text files under `resources/`, never transcribed and never folders/URLs/binaries; generated output under `plain_modules//code/` (implementation + unit tests) and `plain_modules//tests/` (conformance tests) is read-only — fixes go back into the spec and re-render. diff --git a/CLAUDE.md b/CLAUDE.md index 8f7b430..8b8d569 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -68,7 +68,7 @@ A `.plain` file is a module: YAML frontmatter (`description`, `imports:`, `base: | `***implementation reqs***` | HOW it's built (tech/architecture) **and everything about `:UnitTests:`** | | `***functional specs***` | WHAT it does — observable, language-agnostic; one `# func-id` heading per functionality; each spec ≤200 changed LOC; rendered incrementally top-to-bottom | | `***test reqs***` | **everything about `:ConformanceTests:`** (framework, run command, mocking/network policy) | -| test plain file (`***conformance tests***`, `***acceptance tests***`) | the scenarios of one functionality; at least one conformance scenario when conformance tests are enabled; acceptance scenarios are end-to-end workflows | +| test plain file (`***conformance tests***`, `***acceptance tests***`) | the scenarios of one functionality; conformance scenarios describe the expected use, acceptance scenarios the use that is not expected (invalid parameters, wrong or missing authentication, missing data); at least one conformance scenario when conformance tests are enabled | Other constraints the rules enforce (see `forge/rules/`): functional specs are mandatorily routed through `add-functional-spec(s)` (never hand-authored), checked for the 200-LOC limit (`analyze-if-func-spec-too-complex` → `break-down-func-spec`) and for conflicts (`analyze-func-specs` → `resolve-spec-conflict`); external artifacts (JSON Schema, OpenAPI, payloads) are **linked** as single local text files under `resources/`, never transcribed and never folders/URLs/binaries; generated output under `plain_modules//code/` (implementation + unit tests) and `plain_modules//tests/` (conformance tests) is read-only — fixes go back into the spec and re-render. diff --git a/README.md b/README.md index e88c0b2..b340165 100644 --- a/README.md +++ b/README.md @@ -277,7 +277,7 @@ On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them int | `add-implementation-requirement` | Add a non-functional requirement to `***implementation reqs***` | | `add-test-requirement` | Add a testing requirement to `***test reqs***` | | `add-concept` | Define a new concept in `***definitions***` | -| `add-test-scenarios` | Write or update the test plain file of a functionality (`tests//.plain`): conformance scenarios in plain English, and acceptance scenarios for end-to-end workflows | +| `add-test-scenarios` | Write or update the test plain file of a functionality (`tests//.plain`): conformance scenarios for its expected use and acceptance scenarios for its unexpected use, in plain English | | `add-resource` | Link an external file (schema, API spec) to a spec | | `add-template` | Create or include a reusable Liquid template | From 1cf5a9080675a7f91e647abdef5b4859907c56e2 Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Mon, 19 Oct 2026 13:04:57 +0200 Subject: [PATCH 19/25] Take the base/imports rename out of the branch The frontmatter directives stay requires: and import:, the rule file stays requires-modules.md, and the skill stays create-requires-module. The old syntax is only the flush-left functional specs and the nested acceptance tests; migrate-plain-syntax no longer touches directives. init-plain-project scaffolds template/base.plain again. Refs ENG-190 --- AGENTS.md | 4 +- CLAUDE.md | 4 +- README.md | 12 +- forge/rules/base-modules.md | 81 ------------ forge/rules/concise-specs.md | 6 +- forge/rules/definitions.md | 8 +- forge/rules/exported-concepts.md | 12 +- forge/rules/func-specs.md | 6 +- forge/rules/glossary.md | 6 +- forge/rules/impl-reqs.md | 2 +- forge/rules/import-modules.md | 24 ++-- forge/rules/integration-embedded.md | 4 +- forge/rules/integrations.md | 2 +- forge/rules/linked-resources.md | 4 +- forge/rules/module-structure.md | 18 +-- forge/rules/requires-modules.md | 81 ++++++++++++ forge/rules/test-plain-files.md | 2 +- forge/skills/add-concept/SKILL.md | 6 +- forge/skills/add-feature/SKILL.md | 4 +- .../add-feature/references/checklist.md | 2 +- forge/skills/add-functional-spec/SKILL.md | 8 +- forge/skills/add-functional-specs/SKILL.md | 8 +- .../add-implementation-requirement/SKILL.md | 4 +- forge/skills/add-resource/SKILL.md | 4 +- forge/skills/add-template/SKILL.md | 4 +- forge/skills/add-test-requirement/SKILL.md | 2 +- forge/skills/add-test-scenarios/SKILL.md | 2 +- forge/skills/analyze-2-func-specs/SKILL.md | 4 +- forge/skills/analyze-func-specs/SKILL.md | 4 +- .../analyze-if-func-spec-too-complex/SKILL.md | 6 +- forge/skills/break-down-func-spec/SKILL.md | 8 +- forge/skills/check-plain-env/SKILL.md | 2 +- forge/skills/consolidate-concepts/SKILL.md | 38 +++--- forge/skills/create-base-module/SKILL.md | 115 ------------------ forge/skills/create-import-module/SKILL.md | 22 ++-- forge/skills/create-requires-module/SKILL.md | 115 ++++++++++++++++++ forge/skills/debug-specs/SKILL.md | 4 +- forge/skills/forge-plain/SKILL.md | 2 +- .../forge-plain/references/phase-1-product.md | 2 +- .../references/phase-4-validate-handoff.md | 6 +- forge/skills/init-config-file/SKILL.md | 2 +- forge/skills/init-plain-project/SKILL.md | 22 ++-- .../references/checklist.md | 8 +- forge/skills/load-plain-reference/SKILL.md | 6 +- .../references/project-model.md | 2 +- .../references/rendering-and-testing.md | 2 +- forge/skills/migrate-plain-syntax/SKILL.md | 51 ++++---- forge/skills/plain-healthcheck/SKILL.md | 16 +-- forge/skills/refactor-module/SKILL.md | 36 +++--- .../skills/resolve-section-ownership/SKILL.md | 4 +- forge/skills/resolve-spec-conflict/SKILL.md | 2 +- 51 files changed, 395 insertions(+), 404 deletions(-) delete mode 100644 forge/rules/base-modules.md create mode 100644 forge/rules/requires-modules.md delete mode 100644 forge/skills/create-base-module/SKILL.md create mode 100644 forge/skills/create-requires-module/SKILL.md diff --git a/AGENTS.md b/AGENTS.md index 8b8d569..d43d31f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,7 +60,7 @@ This is the heart of the product. Everything under `forge/` is **instructional t ### The `.plain` model the content describes -A `.plain` file is a module: YAML frontmatter (`description`, `imports:`, `base:`, `exported_concepts:`, `required_concepts:`) plus up to four triple-asterisk sections. Each functional spec is a `# ` heading followed by its body (`forge/rules/semantic-ids.md`). The test scenarios of a functionality live in a second file kind, the **test plain file** at `tests//.plain` — frontmatter `module:` + `func_id:`, sections `***conformance tests***` and `***acceptance tests***` (`forge/rules/test-plain-files.md`); the renderer generates exactly those scenarios and never invents a test. **Where a fact lives is strict and enforced** — the renderer reads each concept only from its owning section, so a misplaced fact is silently ignored: +A `.plain` file is a module: YAML frontmatter (`description`, `import:`, `requires:`, `exported_concepts:`, `required_concepts:`) plus up to four triple-asterisk sections. Each functional spec is a `# ` heading followed by its body (`forge/rules/semantic-ids.md`). The test scenarios of a functionality live in a second file kind, the **test plain file** at `tests//.plain` — frontmatter `module:` + `func_id:`, sections `***conformance tests***` and `***acceptance tests***` (`forge/rules/test-plain-files.md`); the renderer generates exactly those scenarios and never invents a test. **Where a fact lives is strict and enforced** — the renderer reads each concept only from its owning section, so a misplaced fact is silently ignored: | Section | Owns | |---|---| @@ -74,7 +74,7 @@ Other constraints the rules enforce (see `forge/rules/`): functional specs are m ### The skill lifecycle (orchestration) -`forge-plain` is the top-level orchestrator: a short Phase 0 intent interview followed by four gated phases — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, a test plain file with conformance scenarios per functionality via `add-test-scenarios`, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (config and script checks, `plain-parser check` on every top module, plus rule checks the parser can't do) then hand off the render target. Phases 1–3 are **one-question-at-a-time, write-to-disk-immediately**. `add-feature` is the same authoring loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold; `migrate-plain-syntax` converts a project from the old syntax (`- ` functional specs, nested acceptance tests, `requires:` / `import:`), which no other skill edits. The installer ships `forge/rules/*.md` beside the skills; native rule consumers load them directly, while other agents reach them through `load-plain-reference`. +`forge-plain` is the top-level orchestrator: a short Phase 0 intent interview followed by four gated phases — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, a test plain file with conformance scenarios per functionality via `add-test-scenarios`, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (config and script checks, `plain-parser check` on every top module, plus rule checks the parser can't do) then hand off the render target. Phases 1–3 are **one-question-at-a-time, write-to-disk-immediately**. `add-feature` is the same authoring loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold; `migrate-plain-syntax` converts a project from the old syntax (`- ` functional specs, nested acceptance tests), which no other skill edits. The installer ships `forge/rules/*.md` beside the skills; native rule consumers load them directly, while other agents reach them through `load-plain-reference`. ## Memory / persistent context diff --git a/CLAUDE.md b/CLAUDE.md index 8b8d569..d43d31f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,7 +60,7 @@ This is the heart of the product. Everything under `forge/` is **instructional t ### The `.plain` model the content describes -A `.plain` file is a module: YAML frontmatter (`description`, `imports:`, `base:`, `exported_concepts:`, `required_concepts:`) plus up to four triple-asterisk sections. Each functional spec is a `# ` heading followed by its body (`forge/rules/semantic-ids.md`). The test scenarios of a functionality live in a second file kind, the **test plain file** at `tests//.plain` — frontmatter `module:` + `func_id:`, sections `***conformance tests***` and `***acceptance tests***` (`forge/rules/test-plain-files.md`); the renderer generates exactly those scenarios and never invents a test. **Where a fact lives is strict and enforced** — the renderer reads each concept only from its owning section, so a misplaced fact is silently ignored: +A `.plain` file is a module: YAML frontmatter (`description`, `import:`, `requires:`, `exported_concepts:`, `required_concepts:`) plus up to four triple-asterisk sections. Each functional spec is a `# ` heading followed by its body (`forge/rules/semantic-ids.md`). The test scenarios of a functionality live in a second file kind, the **test plain file** at `tests//.plain` — frontmatter `module:` + `func_id:`, sections `***conformance tests***` and `***acceptance tests***` (`forge/rules/test-plain-files.md`); the renderer generates exactly those scenarios and never invents a test. **Where a fact lives is strict and enforced** — the renderer reads each concept only from its owning section, so a misplaced fact is silently ignored: | Section | Owns | |---|---| @@ -74,7 +74,7 @@ Other constraints the rules enforce (see `forge/rules/`): functional specs are m ### The skill lifecycle (orchestration) -`forge-plain` is the top-level orchestrator: a short Phase 0 intent interview followed by four gated phases — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, a test plain file with conformance scenarios per functionality via `add-test-scenarios`, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (config and script checks, `plain-parser check` on every top module, plus rule checks the parser can't do) then hand off the render target. Phases 1–3 are **one-question-at-a-time, write-to-disk-immediately**. `add-feature` is the same authoring loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold; `migrate-plain-syntax` converts a project from the old syntax (`- ` functional specs, nested acceptance tests, `requires:` / `import:`), which no other skill edits. The installer ships `forge/rules/*.md` beside the skills; native rule consumers load them directly, while other agents reach them through `load-plain-reference`. +`forge-plain` is the top-level orchestrator: a short Phase 0 intent interview followed by four gated phases — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, a test plain file with conformance scenarios per functionality via `add-test-scenarios`, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (config and script checks, `plain-parser check` on every top module, plus rule checks the parser can't do) then hand off the render target. Phases 1–3 are **one-question-at-a-time, write-to-disk-immediately**. `add-feature` is the same authoring loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold; `migrate-plain-syntax` converts a project from the old syntax (`- ` functional specs, nested acceptance tests), which no other skill edits. The installer ships `forge/rules/*.md` beside the skills; native rule consumers load them directly, while other agents reach them through `load-plain-reference`. ## Memory / persistent context diff --git a/README.md b/README.md index b340165..34f2701 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,12 @@ Pick whichever entry point matches how much upfront design you want: - **`add-feature`** — takes a feature request in plain English and runs the same one-question-at-a-time loop that `forge-plain` uses, scoped to a single feature against an existing `.plain` file. - **Per-section authoring skills** — `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `add-test-requirement`, `add-concept`, `add-test-scenarios`, `add-resource`, `add-template`. Each enforces the relevant ***plain syntax rules (concept uniqueness, complexity limits, bullet syntax, linked-resource constraints, …) so hand-authoring doesn't drift from the language. `add-test-scenarios` writes the test plain file of a functionality — `tests//.plain` with the conformance and acceptance scenarios the renderer implements, and nothing else. -- **Module-management skills** — `create-import-module`, `create-base-module`, `refactor-module`, `consolidate-concepts` for restructuring as the project grows. +- **Module-management skills** — `create-import-module`, `create-requires-module`, `refactor-module`, `consolidate-concepts` for restructuring as the project grows. ### 3. Validate and maintain - **`plain-healthcheck`** — the verification gate. Inventories every `.plain` module, validates every `config.yaml`, parses every top module with [`plain-parser`](https://github.com/plainlang/plain-parser), and checks every module against the ***plain rules. Returns `PASS` / `FAIL` with a numbered punch-list when something is broken. -- **`migrate-plain-syntax`** — converts a project from the old syntax (`- ` functional specs, nested acceptance tests, `requires:` / `import:`) to `# func-id` headings, test plain files under `tests/`, and `base:` / `imports:`. Run it when a skill or `plain-parser` reports the old syntax. +- **`migrate-plain-syntax`** — converts a project from the old syntax (`- ` functional specs, nested acceptance tests) to `# func-id` headings and test plain files under `tests/`. Run it when a skill or `plain-parser` reports the old syntax. - **`init-config-file`** — assembles the canonical `config.yaml` per part of a project from the decisions made during interviewing. - **`check-plain-env`** — probes the host machine for everything the project needs (language toolchains, external services, system binaries, drivers) and emits a `PASS` / `WARN` / `FAIL` report with OS-specific install commands. - **`analyze-func-specs`** / **`analyze-if-func-spec-too-complex`** / **`break-down-func-spec`** / **`resolve-spec-conflict`** — the spec-quality toolchain that runs behind `add-feature` and `forge-plain` but can also be invoked directly when reviewing or refactoring. @@ -264,7 +264,7 @@ On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them int | Skill | Description | |-------|-------------| | `forge-plain` | End-to-end QA interview that produces complete `.plain` spec files for a new project | -| `init-plain-project` | Lightweight project initializer — scaffolds `template/stack.plain` (stack impl + test reqs), a stub top-level module, the testing scripts, and `config.yaml`. No functional specs, no concepts, no validation. Pair with `add-feature` to grow the project feature-by-feature. | +| `init-plain-project` | Lightweight project initializer — scaffolds `template/base.plain` (base impl + test reqs), a stub top-level module, the testing scripts, and `config.yaml`. No functional specs, no concepts, no validation. Pair with `add-feature` to grow the project feature-by-feature. | | `add-feature` | Interview the user about a single feature, then write all the specs for it | | `render-spec` | [pyro](https://github.com/plainlang/pyro), the open-source renderer: renders a `.plain` module and the modules it requires or imports into working, tested code, from inside your agent. See [Render with pyro](#render-with-pyro). | @@ -286,8 +286,8 @@ On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them int | Skill | Description | |-------|-------------| | `create-import-module` | Create a shared template module (definitions + reqs, no functional specs) | -| `create-base-module` | Create a module that lists a previously built module under `base` | -| `refactor-module` | Split a large module into smaller modules connected via a base chain | +| `create-requires-module` | Create a module that depends on a previously built module | +| `refactor-module` | Split a large module into smaller modules connected via a requires chain | | `consolidate-concepts` | Gather scattered concept definitions into a single shared import module | ### Analysis and Quality @@ -297,7 +297,7 @@ On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them int | `init-config-file` | Build / finalize the project's `config.yaml` file(s) from the decisions made in Phase 3. Knows the full set of valid keys, refuses to write secrets or per-invocation options, and produces one config per part of the project. Run at the end of `forge-plain` (just before `plain-healthcheck`) and any time the testing surface changes. | | `plain-healthcheck` | Verification gate: validates every `config.yaml`, confirms each `*-script` field points at a real file in `test_scripts/`, runs `plain-parser check` on every top module, and checks every module and test plain file against the rules. Run whenever anything in the project is finalized — at the end of `forge-plain`, at the end of `add-feature`, after `debug-specs`, and after any single-skill edit that touches the renderable surface. | | `check-plain-env` | Read the project's `.plain` files, `test_scripts/`, `config.yaml`(s), and `resources/`, then probe the host for every requirement **the package manager can't install**: language toolchains (`python` + `pip`, `node` + `npm`, JDK + `mvn`, Go, Rust, .NET, etc.), external services (Postgres, Redis, Docker, ...), system binaries that language packages wrap (`ffmpeg`, `tesseract`, `pdftoppm`, browser binaries, ...), hardware / drivers / accelerators (NVIDIA driver → CUDA toolkit → cuDNN → framework-sees-GPU chain), credential env vars, and `plain-parser`. Does **not** probe individual language packages — `pip install -r requirements.txt` (and equivalents) handle those when the test scripts run. Emits a `PASS` / `WARN` / `FAIL` report with OS-specific install commands for any gaps. Read-only — never installs anything. Run on first-time setup, before rendering on a new machine, after adding a new tech to a project, or any time `command not found` shows up in test output. | -| `migrate-plain-syntax` | Convert a project from the old syntax: `# func-id` headings, test plain files under `tests/`, `base:` / `imports:` | +| `migrate-plain-syntax` | Convert a project from the old syntax to `# func-id` headings and test plain files under `tests/` | | `analyze-if-func-spec-too-complex` | Check if a spec exceeds the 200-line complexity limit | | `analyze-func-specs` | Check a batch of specs (2+) against each other in one call and return every conflicting pair | | `analyze-2-func-specs` | Legacy: check exactly two specs for conflicts (prefer `analyze-func-specs`) | diff --git a/forge/rules/base-modules.md b/forge/rules/base-modules.md deleted file mode 100644 index 2b656e7..0000000 --- a/forge/rules/base-modules.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -description: Rules for creating and using base modules in .plain files ---- - -# Rules for base modules - -When creating or editing a `.plain` file that uses `base`, or one that is listed in another `.plain` file's `base` directive, always follow these rules: - -## What base does - -- `base` attaches the current module to the project's **build chain**: the base module is built first, and its generated code is copied as the starting point for the current module -- The copied code is the base module's **accumulated** output — it already contains the code of all of that module's own ancestors -- The `***functional specs***` of the base module **and of all its ancestors** become visible as **previous functional specs** — this visibility **is transitive** down the whole chain -- Of a base 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 under `base` -- The test plain files of a base module stay with that module; the current module never lists scenarios for a base module's functionality (see `test-plain-files.md`) - -## Chain topology - -- All modules connected by `base` 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 list two modules from divergent branches — there is no way to merge two divergent codebases into one starting point -- When `base` 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 list the same parent, creating independent branches (and therefore independent top modules) - -## Tech stack must match (hard rule) - -- Because the base 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 `base` when they target the **same language, framework, and runtime** -- A runtime / network dependency between systems is **not** a reason to use `base` -- Example of the mistake: a React frontend that talks to a Python/FastAPI backend over HTTP must **not** list `base: [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 `imports` module — never through `base` - -## Building upon but not necessarily extension - -- The current module does not need to functionally extend the base module — it may address a completely different concern -- Even so, the two are never independent artifacts: the current module's output physically contains the base module's code, because both are stages of one accumulating codebase -- Use `base` to place a module at the right point in that lineage, whether or not it builds on the base module's functionality - -## Conflict prevention - -- 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 base module's `exported_concepts` are available, and only for modules that explicitly list it under `base` -- Each exported concept is available with its full definition text -- Other concepts from base modules are internal and invisible -- To reference a concept exported by an ancestor deeper in the chain, list that ancestor under `base` as well -- If you need shared definitions across branches or stacks, use `imports` for that — not `base` - -## File locations - -- Modules that use `base` live at the **repository root** — they are functional modules with specs -- `base` entries name other root-level modules (e.g., `auth`, `messaging`) -- Never list an import module under `base` — import modules are for `imports` only - -## Format - -```plain ---- -base: - - auth -imports: - - airplain -description: Module built on top of auth, importing shared definitions ---- -``` - -A module can list multiple base modules **only when they lie on one ancestor path**. Here `messaging` itself has `auth` as its base, so `auth` is an ancestor of `messaging`; the module attaches after `messaging` and lists `auth` only to see its exports: - -```plain ---- -base: - - auth - - messaging -imports: - - airplain ---- -``` diff --git a/forge/rules/concise-specs.md b/forge/rules/concise-specs.md index c689478..be6aa79 100644 --- a/forge/rules/concise-specs.md +++ b/forge/rules/concise-specs.md @@ -23,12 +23,12 @@ Do not write: - Universal software defaults: that invalid input is rejected, that errors are reported, that data is saved when saving is the point of the operation - Rationale, motivation, benefits, or background — the spec states what is, not why it was chosen - Standard behavior of a technology already named in `***implementation reqs***` (an HTTP framework parses HTTP; a database driver opens connections) -- Restatements of a nearby bullet in different words, or of anything already stated in an `imports` or `base` module +- Restatements of a nearby bullet in different words, or of anything already stated in an `import`ed or `require`d module - Anything about work the spec is not about ("no changes to the existing UI") - Meta-commentary about the spec itself ("this spec is intentionally small", "as described above") Worked pairs for every item above — padding, rationale, defaults, technology behavior, -meta-commentary, and duplication of an `imports` module — are in `concise-specs-examples.md`. +meta-commentary, and duplication of an `import` — are in `concise-specs-examples.md`. ## Use plain words @@ -74,7 +74,7 @@ Read the drafted lines once more and apply, in order: 1. **The deletion test** — cut every line whose removal would change nothing about what gets built 2. **The word test** — replace every ornate word with a plain one, and delete every uncheckable adjective, adverb, and hedge -3. **The duplication test** — cut anything already stated in this file, an `imports` module, or a `base` chain +3. **The duplication test** — cut anything already stated in this file, an `import`, or a `requires` chain 4. **The read-aloud test** — a line you cannot read aloud in one breath is either two facts or one padded one If a cut would remove an interface detail, a boundary case, or a disambiguation, keep the fact and shorten the wording instead — see *Do not over-cut* in `concise-specs-examples.md` for what a spec cut past its content looks like. diff --git a/forge/rules/definitions.md b/forge/rules/definitions.md index b4521bb..0eeac49 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 concepts coming from `imports` and `base` 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 @@ -37,7 +37,7 @@ When writing or editing a `***definitions***` section in a `.plain` file, always ## Define before use - A concept must be defined before it is referenced in any section (definitions, implementation reqs, functional specs, test reqs) -- Sources of definitions: the module's own `***definitions***`, an `imports` module's definitions, or a `base` module's `exported_concepts` +- Sources of definitions: the module's own `***definitions***`, an `import`ed module's definitions, or a `require`d module's `exported_concepts` ## No circular references - Concept references must not form cycles — if A references B, then B must not reference A (directly or indirectly) @@ -64,8 +64,8 @@ Bad — circular: ``` ## Exported concepts are not transitive -- If module A exports a concept and module B lists A under `base`, a module C with `base: [B]` does **not** gain access to A's exports -- To use an ancestor's exports, list that ancestor under `base` as well; concepts shared more broadly 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 77b4997..eb68371 100644 --- a/forge/rules/exported-concepts.md +++ b/forge/rules/exported-concepts.md @@ -7,13 +7,13 @@ description: Rules for using exported_concepts in .plain files When adding or editing `exported_concepts` in a `.plain` file's frontmatter, always follow these rules: ## What exported_concepts does -- `exported_concepts` declares which concepts from this module are visible to modules that list it under `base` +- `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 `base` receive exported concepts — `imports` gives access to all definitions, not just exports +- 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 list under `base` +- Use `exported_concepts` on any module that other modules will `require` - List only the concepts that downstream modules actually need to reference - Keep the exported surface small — expose only what is necessary @@ -22,14 +22,14 @@ 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 lists A under `base`, a module C with `base: [B]` does **not** gain access to `:Foo:` -- If module C also needs `:Foo:`, it should list A under `base` as well (`base: [A, B]`) or get `:Foo:` 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 ```plain --- -imports: +import: - airplain exported_concepts: [":User:", ":JwtToken:"] description: Exports User and JwtToken for downstream modules diff --git a/forge/rules/func-specs.md b/forge/rules/func-specs.md index 4c2986f..ac00889 100644 --- a/forge/rules/func-specs.md +++ b/forge/rules/func-specs.md @@ -72,13 +72,13 @@ A :User: can add a :Task:. Only valid :Task: items are added. - The first spec must still imply a testable outcome; setup alone is not a functional spec - Prefer one minimal entry point or public operation with a deterministic result that can be invoked and verified - Defer persistence, integrations, multiple interfaces, branching workflows, and cross-cutting behavior until later specs can build on the generated baseline -- This extra bootstrap cost applies when no previous implementation baseline exists; a module that continues from a `base` module may already inherit the necessary scaffold +- This extra bootstrap cost applies when no previous implementation baseline exists; a module that continues from a `requires` module may already inherit the necessary scaffold ## Chronological ordering - 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 `base` modules are treated as previous requirements — transitively, across the whole ancestor chain +- 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 @@ -113,7 +113,7 @@ A :User: can add a :Task:. Only valid :Task: items are added. ## Encapsulation - Functionality must be self-contained in the spec text -- `base` modules only pass on functional specs and `exported_concepts` — never implementation reqs, so 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 ## Test scenarios live in test plain files diff --git a/forge/rules/glossary.md b/forge/rules/glossary.md index ada1c9e..38b70c2 100644 --- a/forge/rules/glossary.md +++ b/forge/rules/glossary.md @@ -7,8 +7,8 @@ description: Terms used in .plain files, in plain-forge, and in plain-parser; us These are the names used across the rules, the skills, and the parser. Use them as written; do not coin synonyms. - **Core spec** — a `.plain` file with functional specs, a root module. Test definitions do not live here. -- **Import module** — a `.plain` file in the template directory that other modules pull in with `imports:`; definitions and reqs only (see `import-modules.md`). -- **Base module** — a root module that another root module lists under `base:` to build on its generated code and functional specs (see `base-modules.md`). +- **Import module** — a `.plain` file in the template directory that other modules pull in with `import:`; definitions and reqs only (see `import-modules.md`). +- **Requires module** — a root module that another root module names under `requires:` to build on its generated code and functional specs (see `requires-modules.md`). - **Functionality** — one functional spec in a module, identified by its func-id. - **Func-id** — the slug in the `# ` heading of a functionality. Lowercase letters, digits, and hyphens; unique in the module; written as `:` when the module is needed. It needs no meaning (see `semantic-ids.md`). - **Test plain file** — a `.plain` file that holds the scenarios of one functionality. Its frontmatter keys `module:` and `func_id:` bind it to that functionality; it has a `***conformance tests***` and an `***acceptance tests***` section. By convention it lives at `tests//.plain`. Several may bind to one functionality; their scenarios are merged (see `test-plain-files.md`). @@ -18,4 +18,4 @@ These are the names used across the rules, the skills, and the parser. Use them - **Acceptance scenario** — a scenario in `***acceptance tests***`: use of the same functionality that is not expected, such as invalid parameters, wrong or missing authentication, missing data, or an unavailable dependency. It is rendered after the conformance tests of its functionality. A scenario that spans several functionalities is bound to the last one it exercises and sorted by the same rule. - **Conformance test** — generated test code that implements one conformance scenario. It runs the implementation as a black box and is labelled with the scenario-id. - **Acceptance test** — generated test code that implements one acceptance scenario, labelled with the scenario-id. -- **Old syntax** — functional specs as `- ` bullets, nested `***acceptance tests***`, and the `requires:` / `import:` directives. Not supported; `migrate-plain-syntax` converts a project. +- **Old syntax** — functional specs as `- ` bullets and nested `***acceptance tests***`. Not supported; `migrate-plain-syntax` converts a project. diff --git a/forge/rules/impl-reqs.md b/forge/rules/impl-reqs.md index 8d3fe00..e0c422c 100644 --- a/forge/rules/impl-reqs.md +++ b/forge/rules/impl-reqs.md @@ -41,7 +41,7 @@ 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 -- `base` modules only pass on functional specs and `exported_concepts` — never 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 ## Conciseness diff --git a/forge/rules/import-modules.md b/forge/rules/import-modules.md index d0934ce..b4ab4ee 100644 --- a/forge/rules/import-modules.md +++ b/forge/rules/import-modules.md @@ -4,16 +4,16 @@ description: Rules for creating and using import modules in .plain files # Rules for import modules -When creating or editing a `.plain` file that uses `imports` or is intended to be imported by other modules, always follow these rules: +When creating or editing a `.plain` file that uses `import` or is intended to be imported by other modules, always follow these rules: ## What an import module is - An import module contains **only** `***definitions***`, `***implementation reqs***`, and/or `***test reqs***` - It must **not** contain `***functional specs***` -- It must **not** use `base` in its frontmatter -- It may list other import modules under `imports` for layered reuse +- It must **not** use `requires` in its frontmatter +- It may optionally `import` other import modules for layered reuse -## What imports does -- `imports` pulls in `***definitions***`, `***implementation reqs***`, and `***test reqs***` from the target module +## What import does +- `import` pulls in `***definitions***`, `***implementation reqs***`, and `***test reqs***` from the target module - It does **not** pull in `***functional specs***` - Import paths omit the `.plain` extension @@ -25,7 +25,7 @@ When creating or editing a `.plain` file that uses `imports` or is intended to b ```plain --- -imports: +import: - airplain description: Module that imports shared definitions and reqs --- @@ -35,14 +35,14 @@ A module can import multiple modules: ```plain --- -imports: +import: - airplain - shared_utils --- ``` -## Combining imports with base -- A module can use both `imports` and `base` together -- `imports` brings in shared definitions and reqs -- `base` attaches the module to the build chain — accumulated generated code and previous functional specs -- An import module itself must **not** use `base` +## Combining import with requires +- A module can use both `import` and `requires` together +- `import` brings in shared definitions and reqs +- `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/integration-embedded.md b/forge/rules/integration-embedded.md index daefafc..0a2d876 100644 --- a/forge/rules/integration-embedded.md +++ b/forge/rules/integration-embedded.md @@ -47,7 +47,7 @@ This keeps the integration self-contained in one directory the user can `cd` int Run host discovery **before** the first Phase 1 question. Treat the results as ground truth for everything that follows. -1. **Locate the host's `.plain` setup (if any).** Look for an existing `.plain` file or directory, a `config.yaml` declaring `plain_source_dir` / `plain_modules_dir` / `resources_dir` / `test_scripts_dir`, a `plain_modules/`, a `resources/`, and a `test_scripts/`. If the host already has a `.plain` setup, **adopt it verbatim**: the new integration module lands inside the existing `plain_source_dir` and lists the relevant base module under `base` (use `create-base-module`). Do not create a parallel layout. +1. **Locate the host's `.plain` setup (if any).** Look for an existing `.plain` file or directory, a `config.yaml` declaring `plain_source_dir` / `plain_modules_dir` / `resources_dir` / `test_scripts_dir`, a `plain_modules/`, a `resources/`, and a `test_scripts/`. If the host already has a `.plain` setup, **adopt it verbatim**: the new integration module lands inside the existing `plain_source_dir` and `requires` the relevant base module (use `create-requires-module`). Do not create a parallel layout. 2. **Read every existing integration in the host.** For each one, extract: intent and scope, host base class / interface / protocol the integration subclasses or implements, package path and naming convention, configuration pattern (env vars, settings module, secret manager), error / exception hierarchy, logging / metrics / tracing conventions, testing pattern (live vs recorded vs mocked, fixture location, conformance-test layout). The new integration must follow the same patterns unless the user explicitly opts out. 3. **Capture findings in the `host-codebase` concept.** Cite the existing integration by file path so the reasoning is auditable. The concept holds, as facts: - Host codebase root (absolute or project-relative path) @@ -159,7 +159,7 @@ Before declaring an embedded integration done, in addition to the shared checkli ## Anti-patterns specific to embedded integrations -- **Choosing a different language, framework, or dependency manager than the host.** The host stack is inherited; cross-stack `base` chains are forbidden by [`base-modules.md`](base-modules.md) +- **Choosing a different language, framework, or dependency manager than the host.** The host stack is inherited; cross-stack `requires` chains are forbidden by [`requires-modules.md`](requires-modules.md) - **Redefining a host class under `plain_modules/`.** Reference the host symbol by FQN; let the renderer import it - **Inlining a host base class body into the contract spec.** Reference the host file as a linked resource **at its original host-relative path** — do not inline its contents and do not copy it into `resources/` - **Copying host source files into `resources/host/` (or anywhere under `resources/`).** That creates a second source of truth that silently drifts from the host. Link the host file in place; the integration `.plain` module already lives inside the host codebase, so the path resolves naturally diff --git a/forge/rules/integrations.md b/forge/rules/integrations.md index b3c3c3c..c9d9179 100644 --- a/forge/rules/integrations.md +++ b/forge/rules/integrations.md @@ -131,7 +131,7 @@ A production-ready integration spec captures every corner case the API can throw - **Inlining a host base class body into the contract spec.** Add the host file as a linked resource under `resources/host/` and reference it by FQN - **Embedding credentials, tokens, or signing keys in a `.plain` file or in a summary** — credentials are referenced by env-var name only - **Authoring against unverified credentials.** Validate first; if the user has no credentials yet, flag it in the module's frontmatter description and re-validate once credentials arrive -- **Listing a separate-stack module under `base`** (a Python backend with a React frontend as its base, or vice versa) — see [`base-modules.md`](base-modules.md). Use a shared API schema in `resources/` instead +- **`requires`-ing a separate-stack module** (a Python backend `requires`-ing a React frontend, or vice versa) — see [`requires-modules.md`](requires-modules.md). Use a shared API schema in `resources/` instead - **Authoring Phase 1 specs from the docs first and "reconciling" with the live API later.** Probe the API as you reach each topic; the live response is the source of truth from the moment it's captured - **Writing any integration spec from memory of the provider's API instead of web-searching for its documentation and `fetch`-ing every relevant page first.** No matter how well-known the API (Stripe, GitHub, Slack, Salesforce, AWS, OpenAI, …), the canonical pages must be located with web search and then retrieved with `fetch` at spec-authoring time — see *Live API must be cross-checked against the documentation*. Authoring from memory bakes in whatever version of the API was current during training, which is always older than the version the integration will actually call - **Guessing a documentation URL from memory and `fetch`-ing it without searching first.** A remembered URL may 404, redirect to a stale version, or miss the page that actually documents the topic. Search to find the authoritative, current links, then fetch the full set diff --git a/forge/rules/linked-resources.md b/forge/rules/linked-resources.md index db1c706..2f4eaad 100644 --- a/forge/rules/linked-resources.md +++ b/forge/rules/linked-resources.md @@ -13,7 +13,7 @@ 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 `base` or `imports` frontmatter directives. Do not link it as a markdown resource; the renderer will flag this as a syntax error. +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 @@ -65,7 +65,7 @@ A :User: can edit a :Task: using :TaskModalSpec:. ## File location and path resolution - Paths are resolved against the directory that contains the `.plain` file passed to the renderer, not the directory where the renderer is run -- This includes links inside imported and base modules — they resolve against that same directory, not against the directory the imported module itself lives in +- This includes links inside imported and required modules — they resolve against that same directory, not against the directory the imported module itself lives in - A path that does not resolve there is then looked up in the `template-dir` directory (if configured), and last in the renderer's built-in template library - Because the `.plain` file's own directory is the anchor, a render gives the same result from any 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 diff --git a/forge/rules/module-structure.md b/forge/rules/module-structure.md index 1f05931..9441436 100644 --- a/forge/rules/module-structure.md +++ b/forge/rules/module-structure.md @@ -18,9 +18,9 @@ When creating a new `.plain` file or reviewing the structure of a whole file, al | Field | Purpose | Rule file | |---|---|---| | `description` | One-line summary of the module; recommended on every module | — | -| `imports` | Pull definitions, implementation reqs, and test reqs from import modules | `import-modules.md` | -| `base` | Attach the module to the build chain of another root module | `base-modules.md` | -| `exported_concepts` | Declare which of this module's concepts are visible to modules that list it under `base` | `exported-concepts.md` | +| `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 @@ -33,9 +33,9 @@ When creating a new `.plain` file or reviewing the structure of a whole file, al ## 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 `imports` module (implementation reqs are not inherited through `base`). A frontmatter-only file is a valid scaffold, but it cannot be rendered until a functional spec is added -- A **base module** is a root module that another root module lists under `base` (see `base-modules.md`) -- An **import module** provides shared content that other modules pull in via `imports`: it contains only `***definitions***`, `***implementation reqs***`, and/or `***test reqs***` — never `***functional specs***`, never `base` (see `import-modules.md`) +- 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`) - A **test plain file** holds the scenarios of one functionality of a root module; its frontmatter `module:` and `func_id:` bind it to that functionality, it lives under `tests/`, and it has no section of a core spec (see `test-plain-files.md`) ## Section ownership (where a fact lives) @@ -62,7 +62,7 @@ When creating a new `.plain` file or reviewing the structure of a whole file, al ## 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 `imports`, which includes another module semantically (see `import-modules.md`) +- 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 %} @@ -83,7 +83,7 @@ A :Widget: can have its tags listed. ```plain --- description: Command-line task manager. -imports: +import: - airplain --- @@ -112,6 +112,6 @@ The :TaskList: is displayed. ``` ## Old syntax -- A core spec is in the old syntax when any of these holds: a flush-left `- ` bullet in `***functional specs***`, a `***acceptance tests***` section anywhere in the file, or a `requires:` or `import:` key in the frontmatter +- A core spec is in the old syntax when any of these holds: a flush-left `- ` bullet in `***functional specs***` or a `***acceptance tests***` section anywhere in the file - The old syntax is not supported: the renderer rejects it, and no skill edits a file written in it - When a skill meets the old syntax, it stops, tells the user which file and which marker it found, and points to the `migrate-plain-syntax` skill, which converts the whole project in one pass diff --git a/forge/rules/requires-modules.md b/forge/rules/requires-modules.md new file mode 100644 index 0000000..5612d1d --- /dev/null +++ b/forge/rules/requires-modules.md @@ -0,0 +1,81 @@ +--- +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`, or one that is referenced in another `.plain` file's `requires` directive, always follow these rules: + +## What requires does + +- `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` +- The test plain files of a required module stay with that module; the current module never lists scenarios for a required module's functionality (see `test-plain-files.md`) + +## 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 module — never through `requires` + +## 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 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`) +- Never `require` an import module — import modules are for `import` only + +## Format + +```plain +--- +requires: + - auth +import: + - airplain +description: Module built on top of auth, importing shared definitions +--- +``` + +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 +--- +requires: + - auth + - messaging +import: + - airplain +--- +``` diff --git a/forge/rules/test-plain-files.md b/forge/rules/test-plain-files.md index d56f7b8..2c25ae1 100644 --- a/forge/rules/test-plain-files.md +++ b/forge/rules/test-plain-files.md @@ -25,7 +25,7 @@ A test plain file holds the test scenarios of one functionality. The renderer ge ## Scenarios - A scenario is a `# ` heading (see `semantic-ids.md`) followed by free text that says what is done and what result is expected - A scenario body line may be prose, a `- ` bullet, or any other text, as long as it does not start with `# ` (that starts a new scenario) or with `***` -- A scenario may reference every concept visible to its module: its own definitions, its `imports`, and the `exported_concepts` of its `base` modules. It defines no concepts +- A scenario may reference every concept visible to its module: its own definitions, its `import` modules, and the `exported_concepts` of its `requires` modules. It defines no concepts - Scenarios are language-agnostic: they describe behavior through the observable interface (CLI, HTTP, files, UI) and never name a function, class, or file of the implementation - A scenario tests the functionality as a black box; the generated test never imports the implementation's internals - A scenario is a direct consequence of the functional spec: it does not contradict, narrow, or extend the behavior the spec states diff --git a/forge/skills/add-concept/SKILL.md b/forge/skills/add-concept/SKILL.md index 0c5796d..1d9f08a 100644 --- a/forge/skills/add-concept/SKILL.md +++ b/forge/skills/add-concept/SKILL.md @@ -10,7 +10,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Workflow @@ -18,7 +18,7 @@ Before reading or editing any `.plain` file, check it for the old syntax (see *O 2. **Read the file** to understand existing definitions, imports, and concepts. 3. **Validate the concept name** against the syntax rules below. 4. **Check for uniqueness** — the concept name must not already exist in the file or its imports. -5. **Check referenced concepts** — any `:ConceptName:` used in the definition must already be defined above it (in this file or via `imports`/`base`). Concept references must not form cycles (e.g., A references B and B references A). +5. **Check referenced concepts** — any `:ConceptName:` used in the definition must already be defined above it (in this file or via `import`/`requires`). Concept references must not form cycles (e.g., A references B and B references A). 6. **Check for circular references** — if the new concept references `:B:`, then `:B:` must not reference the new concept (directly or indirectly). Example of a circular definition to avoid: ```plain - :Order: is placed by :Customer: and contains :OrderItem: entries. @@ -38,7 +38,7 @@ Before reading or editing any `.plain` file, check it for the old syntax (see *O - CamelCase, starting with an uppercase letter - Valid characters: letters, digits, `+`, `-`, `.`, `_` - Must be globally unique across the spec and all its imports -- Exported concepts from `base` modules are **not transitive** — to reference an ancestor's export, list that ancestor under `base`; if a concept is shared across branches, 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/add-feature/SKILL.md b/forge/skills/add-feature/SKILL.md index 6c142e9..19761d4 100644 --- a/forge/skills/add-feature/SKILL.md +++ b/forge/skills/add-feature/SKILL.md @@ -15,7 +15,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. `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 begins with a short intent phase, then runs the authoring loop scoped to one feature. @@ -51,7 +51,7 @@ Carry the confirmed brief into every later phase. Before advancing, check the Ph 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 confirmed feature-intent brief.** Identify which existing `.plain` file(s) the feature most likely belongs to. -2. **Read the target `.plain` file(s)**, following their `imports` and `base` chains, and the test plain files under `tests/`, so the existing definitions, implementation reqs, functional specs, test reqs, and scenarios are in context. This is what lets Phase 2 recognize impact when it surfaces. +2. **Read the target `.plain` file(s)**, following their `import` and `requires` chains, and the test plain files under `tests/`, so the existing definitions, implementation reqs, functional specs, test reqs, and scenarios 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 the target file and a single concrete starter question are clear. Do **not** ask framing, scope, or multi-part design questions here. diff --git a/forge/skills/add-feature/references/checklist.md b/forge/skills/add-feature/references/checklist.md index 1c4b93c..a827104 100644 --- a/forge/skills/add-feature/references/checklist.md +++ b/forge/skills/add-feature/references/checklist.md @@ -16,7 +16,7 @@ Use this to verify the workflow was followed — never as a substitute for it. R - [ ] Carried the confirmed feature-intent brief into scope and identified the likely target `.plain` file(s) without restarting intent discovery. -- [ ] Read the target `.plain` file(s) and followed their `imports`/`base` chain before authoring. +- [ ] 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) diff --git a/forge/skills/add-functional-spec/SKILL.md b/forge/skills/add-functional-spec/SKILL.md index ad2aa48..f64d7e7 100644 --- a/forge/skills/add-functional-spec/SKILL.md +++ b/forge/skills/add-functional-spec/SKILL.md @@ -15,15 +15,15 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Workflow 1. **Identify the target `.plain` file.** If ambiguous, ask the user. -2. **Read the entire file** to understand existing definitions, implementation reqs, and all current functional specs (including those in `base` modules). +2. **Read the entire file** to understand existing definitions, implementation reqs, and all current functional specs (including those in `requires` modules). 3. **Pick the func-id and draft the functional spec.** Choose a func-id per `semantic-ids.md` and confirm no `# ` heading with that id exists in the module. Draft the statement line and its indented `- ` clarifications following the rules below, then run the review pass in `concise-specs.md` over the draft — trim it to the shortest wording that keeps every fact before any other check. 4. **Analyze complexity** — use the `analyze-if-func-spec-too-complex` skill to verify the drafted spec implies ≤ 200 LOC. If too complex, use the `break-down-func-spec` skill to split it, then repeat from step 3 for each resulting spec. -5. **Check for conflicts** with every existing functional spec — this is critical. Run `analyze-func-specs` **once** with the new spec plus all existing specs (in the file and in any `base` chain) as a single batch. The batched analyzer returns every conflicting pair in one call — do **not** invoke a pair-by-pair analyzer. For each conflicting pair it reports, run `resolve-spec-conflict` on that pair; re-run `analyze-func-specs` over the touched set after each resolution until the verdict is `COMPATIBLE`. +5. **Check for conflicts** with every existing functional spec — this is critical. Run `analyze-func-specs` **once** with the new spec plus all existing specs (in the file and in any `requires` chain) as a single batch. The batched analyzer returns every conflicting pair in one call — do **not** invoke a pair-by-pair analyzer. For each conflicting pair it reports, run `resolve-spec-conflict` on that pair; re-run `analyze-func-specs` over the touched set after each resolution until the verdict is `COMPATIBLE`. 6. **Append the spec** to the end of the `***functional specs***` section as a `# ` heading followed by its body, one blank line after the previous functionality (specs are chronological; new ones go last). 7. **Read the file again** to confirm correct placement and syntax. 8. **Write the test plain file.** When conformance tests are enabled for the module, invoke `add-test-scenarios` for the new functionality (see *Test Scenarios* below). @@ -70,7 +70,7 @@ Specs must be detailed enough that a developer can use the built software withou ### Encapsulation -Functionality must be self-contained in the spec text. `base` modules only pass on functional specs, so do not rely on implementation reqs to convey behavior — they won't be visible in downstream modules. +Functionality must be self-contained in the spec text. `requires` modules only import functional specs, so do not rely on implementation reqs to convey behavior — they won't be visible in downstream modules. ## Test Scenarios diff --git a/forge/skills/add-functional-specs/SKILL.md b/forge/skills/add-functional-specs/SKILL.md index 3dc4ab6..8e95e54 100644 --- a/forge/skills/add-functional-specs/SKILL.md +++ b/forge/skills/add-functional-specs/SKILL.md @@ -14,7 +14,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## When to Use @@ -29,13 +29,13 @@ If you only need to add a single spec, use the `add-functional-spec` skill inste This skill is `add-functional-spec` applied **one spec at a time, in order**, with no shortcuts. The batch never escapes per-spec rigor. 1. **Identify the target `.plain` file.** If ambiguous, ask the user. -2. **Read the entire file** to understand existing definitions, implementation reqs, and all current functional specs (including those reachable via `imports` / `base`). +2. **Read the entire file** to understand existing definitions, implementation reqs, and all current functional specs (including those reachable via `import` / `requires`). 3. **Draft all the specs you intend to add**, in the chronological order they should appear, each with its own func-id per `semantic-ids.md`, unique in the module and within the batch. Keep the drafts in a working list — nothing is inserted yet. Run the review pass in `concise-specs.md` over every draft before continuing; trim each to the shortest wording that keeps every fact. 4. **For each drafted spec, in order:** 1. **Analyze complexity.** Run `analyze-if-func-spec-too-complex` on the spec. If the verdict is `TOO COMPLEX`, run `break-down-func-spec`, replace the entry in the working list with the resulting smaller specs (preserving order), and restart this step on the first of the replacements. 2. **Check for conflicts.** Run `analyze-func-specs` **once** with this single input set: - the new spec, plus - - every existing functional spec in the file and its `base` chain, plus + - every existing functional spec in the file and its `requires` chain, plus - every spec from this batch that has already been inserted. The batched analyzer returns every conflicting pair in one call — do **not** loop over pairs invoking a per-pair analyzer. For each conflicting pair it reports, run `resolve-spec-conflict`. Apply the resolution — which may revise the new spec, an existing spec, or both — then re-run `analyze-func-specs` once over the touched set until the verdict is `COMPATIBLE`. @@ -93,7 +93,7 @@ Specs must be detailed enough that a developer can use the built software withou ### Encapsulation -Functionality must be self-contained in the spec text. `base` modules only pass on functional specs, so do not rely on implementation reqs to convey behavior — they won't be visible in downstream modules. +Functionality must be self-contained in the spec text. `requires` modules only import functional specs, so do not rely on implementation reqs to convey behavior — they won't be visible in downstream modules. ## Test Scenarios diff --git a/forge/skills/add-implementation-requirement/SKILL.md b/forge/skills/add-implementation-requirement/SKILL.md index e0f1271..264336e 100644 --- a/forge/skills/add-implementation-requirement/SKILL.md +++ b/forge/skills/add-implementation-requirement/SKILL.md @@ -12,7 +12,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Workflow @@ -77,7 +77,7 @@ Reference defined `:Concepts:` where they add clarity. Implementation reqs in no ## Encapsulation Warning -`base` modules only receive functional specs from their dependencies — not implementation reqs. If downstream modules need certain behavior to be visible, that behavior must be expressed in functional specs, not here. +`requires` modules only receive functional specs from their dependencies — not implementation reqs. If downstream modules need certain behavior to be visible, that behavior must be expressed in functional specs, not here. ## Validation Checklist diff --git a/forge/skills/add-resource/SKILL.md b/forge/skills/add-resource/SKILL.md index 338b6f0..794edc5 100644 --- a/forge/skills/add-resource/SKILL.md +++ b/forge/skills/add-resource/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. Linked resources are external files referenced from within a `.plain` spec using markdown link syntax. The file contents are passed to the renderer alongside the spec, providing additional context for code generation. @@ -44,7 +44,7 @@ An API conforming to the [API specification](resources/api_spec.yaml) is exposed ## Path Rules - Paths are resolved **against the directory that contains the `.plain` file passed to the renderer**, not the directory where the renderer is run. -- This includes links inside imported and base modules — they resolve against that same directory, not against the directory the imported module itself lives in. +- This includes links inside imported and required modules — they resolve against that same directory, not against the directory the imported module itself lives in. - A path that does not resolve there is then looked up in the `template-dir` directory (if configured), and last in the renderer's built-in template library. - 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. diff --git a/forge/skills/add-template/SKILL.md b/forge/skills/add-template/SKILL.md index 9f56c8c..aa96d5e 100644 --- a/forge/skills/add-template/SKILL.md +++ b/forge/skills/add-template/SKILL.md @@ -10,9 +10,9 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. -Templates use Liquid syntax (`{% include %}`) to reuse the same spec content across multiple `.plain` files. Each inclusion can pass different parameters, producing tailored output from a single source. This is distinct from `imports` and `base` — templates are about textual reuse with parameterization, not module dependencies. +Templates use Liquid syntax (`{% include %}`) to reuse the same spec content across multiple `.plain` files. Each inclusion can pass different parameters, producing tailored output from a single source. This is distinct from `import` and `requires` — templates are about textual reuse with parameterization, not module dependencies. ## When to Use Templates diff --git a/forge/skills/add-test-requirement/SKILL.md b/forge/skills/add-test-requirement/SKILL.md index 4a3b18f..099a55a 100644 --- a/forge/skills/add-test-requirement/SKILL.md +++ b/forge/skills/add-test-requirement/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Workflow diff --git a/forge/skills/add-test-scenarios/SKILL.md b/forge/skills/add-test-scenarios/SKILL.md index c467290..46637ca 100644 --- a/forge/skills/add-test-scenarios/SKILL.md +++ b/forge/skills/add-test-scenarios/SKILL.md @@ -34,7 +34,7 @@ Conformance tests are enabled for a module when the `config.yaml` that governs i ## Workflow 1. **Identify the functionality.** Take the module name and func-id from the caller or the user. If the user points at a spec by its text, find its `# ` heading. If the core spec has no `# ` headings (old syntax), stop and point the user to `migrate-plain-syntax`. -2. **Read the context.** Read the functional spec (heading and body), the `***definitions***` the module sees (its own, its `imports`, and the `exported_concepts` of its `base` modules), the `***test reqs***`, and the functional specs that come before this one, because a scenario may rely on behavior they established. +2. **Read the context.** Read the functional spec (heading and body), the `***definitions***` the module sees (its own, its `import` modules, and the `exported_concepts` of its `requires` modules), the `***test reqs***`, and the functional specs that come before this one, because a scenario may rely on behavior they established. 3. **Locate or create the test plain file.** Look for `tests//.plain`. Also scan the frontmatter of every other `.plain` file under `tests/` for the same `(module, func_id)` pair; read those files too, so scenario-ids stay unique across the merged set. Create the file with the frontmatter and both sections if it does not exist. 4. **List and sort the outcomes the spec states.** Walk the statement line and every clarification bullet. Write down the main path and each boundary, validation, and error case the spec names. Sort each outcome: expected use (the main path and its valid variants) goes to `***conformance tests***`; use that is not expected (invalid parameters, wrong or missing authentication, missing data, an unavailable dependency) goes to `***acceptance tests***`. Do not add an outcome the spec does not state: a scenario may not extend the spec. If a needed outcome is missing from the spec, ask the user one question and route the answer into the functional spec through `add-functional-spec` (or an inline edit of the clarification bullets) before writing the scenario. 5. **Draft one scenario per outcome, in its section.** Give each a scenario-id per `semantic-ids.md` (short slug, unique for this functionality). Write the body in plain English: diff --git a/forge/skills/analyze-2-func-specs/SKILL.md b/forge/skills/analyze-2-func-specs/SKILL.md index ea4e4ad..e55a9e3 100644 --- a/forge/skills/analyze-2-func-specs/SKILL.md +++ b/forge/skills/analyze-2-func-specs/SKILL.md @@ -11,11 +11,11 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Input -Two functional specs from a `.plain` file (or across `base` modules). The user provides them directly or points to them by location in a file. +Two functional specs from a `.plain` file (or across `requires` modules). The user provides them directly or points to them by location in a file. ## Workflow diff --git a/forge/skills/analyze-func-specs/SKILL.md b/forge/skills/analyze-func-specs/SKILL.md index 5fafead..8cbea71 100644 --- a/forge/skills/analyze-func-specs/SKILL.md +++ b/forge/skills/analyze-func-specs/SKILL.md @@ -11,14 +11,14 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Input A **set of two or more functional specs** to check against each other. Callers typically supply one of: - A whole `***functional specs***` section to self-check. -- A new spec (or batch of new specs) **plus** the existing specs from the same file and any `base` modules. +- A new spec (or batch of new specs) **plus** the existing specs from the same file and any `requires` modules. - An explicit list of spec snippets the user wants compared. Each spec must be identifiable. Use its func-id (`:` when the batch spans modules). A draft that has no func-id yet gets a **stable identifier** in order of appearance: `S1`, `S2`, `S3`, …. Use those identifiers in the output. If the caller provided its own labels or quoted snippets, use those instead and keep them verbatim. 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 1f63e23..5a680dc 100644 --- a/forge/skills/analyze-if-func-spec-too-complex/SKILL.md +++ b/forge/skills/analyze-if-func-spec-too-complex/SKILL.md @@ -12,7 +12,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Why This Matters @@ -24,7 +24,7 @@ A drafted functional spec (not yet inserted into the file), plus the full contex ## Workflow -1. **Read the full `.plain` file** (and any `base`/`imports` modules) to understand the current codebase context — what already exists and what the new spec builds on. +1. **Read the full `.plain` file** (and any `requires`/`import` modules) to understand the current codebase context — what already exists and what the new spec builds on. 2. **Read the drafted functional spec** carefully. 3. **Run the complexity analysis** using the checklist below. 4. **Output the verdict and nothing else** — either `ACCEPTABLE` or `TOO COMPLEX`. No reasoning, no LOC estimate, no breakdown. @@ -36,7 +36,7 @@ Work through each indicator. A single "yes" does not automatically mean the spec ### 0. First-Spec Bootstrap Cost Is this the first functional spec in a new root module with no implementation baseline supplied by -`base`? If so, include the implicit cost of creating the project structure, package metadata, +`requires`? If so, include the implicit cost of creating the project structure, package metadata, framework configuration, entry point, initial source files, and unit-test infrastructure. The first spec must be substantially simpler than a later spec. It should expose one minimal, diff --git a/forge/skills/break-down-func-spec/SKILL.md b/forge/skills/break-down-func-spec/SKILL.md index 8a27cd9..8cb6497 100644 --- a/forge/skills/break-down-func-spec/SKILL.md +++ b/forge/skills/break-down-func-spec/SKILL.md @@ -10,7 +10,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## When to Use @@ -24,7 +24,7 @@ The functional spec to break down, plus the full `.plain` file it belongs to. ## Workflow -1. **Read the full `.plain` file** — definitions, implementation reqs, and all functional specs (including `base` modules) to understand context. +1. **Read the full `.plain` file** — definitions, implementation reqs, and all functional specs (including `requires` modules) to understand context. 2. **Identify the spec to break down** — the one flagged as too complex or pointed to by the user. 3. **Analyze why it is too complex** — use the indicators from `analyze-if-func-spec-too-complex`: - Multiple distinct behaviors bundled together? @@ -41,7 +41,7 @@ The functional spec to break down, plus the full `.plain` file it belongs to. 5. **Draft the replacement specs** — write the smaller specs in chronological order, each with its own func-id per `semantic-ids.md`; the replacement that keeps the original's main behavior may keep the original's func-id. The first replacement spec typically sets up the foundation, and subsequent ones layer behavior on top. 6. **Verify functional completeness** — this is critical. The replacement specs taken together must cover **100% of the functionality** described in the original spec. Walk through every behavior, condition, and detail in the original and confirm it appears in exactly one of the replacement specs. Nothing may be lost, weakened, or left implicit. If any functionality from the original is missing, add it to the appropriate replacement spec or create an additional one. 7. **Verify each replacement spec** — run `analyze-if-func-spec-too-complex` on each to confirm it fits within the limit. -8. **Check for conflicts** — run `analyze-func-specs` **once** with the full set of replacement specs plus every existing spec in the file and its `base` chain. The batched analyzer reports every conflicting pair (replacement × existing and replacement × replacement) in one call — do not loop over pairs. Resolve each reported pair with `resolve-spec-conflict`, then re-run `analyze-func-specs` over the touched set until the verdict is `COMPATIBLE`. +8. **Check for conflicts** — run `analyze-func-specs` **once** with the full set of replacement specs plus every existing spec in the file and its `requires` chain. The batched analyzer reports every conflicting pair (replacement × existing and replacement × replacement) in one call — do not loop over pairs. Resolve each reported pair with `resolve-spec-conflict`, then re-run `analyze-func-specs` over the touched set until the verdict is `COMPATIBLE`. 9. **Replace in the file** — remove the original functionality (heading and body) and insert the replacement functionalities, each as a `# ` heading with its body, in its position, preserving chronological order. 10. **Bring the test plain files in step** — invoke `add-test-scenarios` for each replacement functionality. It moves every scenario of the original's test plain file to the replacement it tests, writes scenarios for outcomes that have none, and deletes the original's file when its func-id no longer exists. 11. **Read the file again** to confirm correct placement and syntax. @@ -207,7 +207,7 @@ A :Report: is exported to PDF, laying out multi-page tables with repeating heade 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-base-module` / `refactor-module` instead of a sibling spec. +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 diff --git a/forge/skills/check-plain-env/SKILL.md b/forge/skills/check-plain-env/SKILL.md index 88a4511..b9cb8f7 100644 --- a/forge/skills/check-plain-env/SKILL.md +++ b/forge/skills/check-plain-env/SKILL.md @@ -43,7 +43,7 @@ The skill is a **derive → probe → report** pass. It does not stop at the fir ### Step 1 — Inventory the project 1. **Detect the host OS** first. Run `uname -s 2>/dev/null || echo "$OS"`. Use the result to (a) pick the right script extensions to inspect (`.sh` vs `.ps1`) and (b) pick the right install commands later (Homebrew, apt, dnf, pacman, choco, scoop, winget). -2. **List every `.plain` file** in the repo root and in any subdirectory that contains them. Note which are top modules (not listed under `base` by any other module) and which are import modules (under `template/`). +2. **List every `.plain` file** in the repo root and in any subdirectory that contains them. Note which are top modules (not `requires`-ed by any other module) and which are import modules (under `template/`). 3. **List every `config.yaml`** in the repo and read each one. Note the `*-script` keys it sets and the values they point at. 4. **List every script under `test_scripts/`** (both `.sh` and `.ps1` — projects can ship both). 5. **List every file under `resources/`** if the directory exists. Schemas and protocol files there often imply running services (e.g. a Postgres `.sql` schema implies a Postgres server). diff --git a/forge/skills/consolidate-concepts/SKILL.md b/forge/skills/consolidate-concepts/SKILL.md index 7f9b156..05fd13d 100644 --- a/forge/skills/consolidate-concepts/SKILL.md +++ b/forge/skills/consolidate-concepts/SKILL.md @@ -11,18 +11,18 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## When to Use - Multiple modules define the same or closely related concepts independently. -- A `base` chain uses long `exported_concepts` lists to share definitions that would be simpler as an import. +- A `requires` chain uses long `exported_concepts` lists to share definitions that would be simpler as an import. - After a `refactor-module` split, shared concepts ended up duplicated or awkwardly exported. - The user wants a single source of truth for domain concepts used across modules. ## Guiding Principle -This is a structural refactoring. The **behavior** of every affected module must remain **identical**. No functional spec, implementation req, or test req is changed. Only concept definitions move — from scattered locations into one shared import module — and the modules' `imports` lists are updated to reference it. +This is a structural refactoring. The **behavior** of every affected module must remain **identical**. No functional spec, implementation req, or test req is changed. Only concept definitions move — from scattered locations into one shared import module — and the modules' `import` lists are updated to reference it. ## Input @@ -30,7 +30,7 @@ The set of modules to consolidate from, plus optionally the name for the new (or ## Phase 1 — Analyze Concepts Across Modules -1. **Read all source modules.** For each `.plain` file involved, read the full file including frontmatter, definitions, implementation reqs, test reqs, and functional specs. Also read their `imports` and `base` chains. +1. **Read all source modules.** For each `.plain` file involved, read the full file including frontmatter, definitions, implementation reqs, test reqs, and functional specs. Also read their `import` and `requires` chains. 2. **Build a concept inventory.** For each concept defined across all source modules, record: - Where it is defined (which file, which line) - Which modules reference it (in definitions, functional specs, implementation reqs, or test reqs) @@ -72,11 +72,11 @@ For each affected module, determine the frontmatter changes: | Change | When | |--------|------| -| Add the new import to `imports` list | Module does not already import the target | +| Add the new import to `import` list | Module does not already import the target | | Remove concepts from `exported_concepts` | Concepts that were exported only to share definitions — now handled by the import | | Keep concepts in `exported_concepts` | Concepts that downstream modules still need via export (because those downstream modules do not import the new template) | -**Important:** `imports` provides definitions to the importing module. `exported_concepts` on a base module provides concepts to the modules that list it under `base`. If a downstream module will now list the shared template under `imports`, the concept can be removed from `exported_concepts`. If a downstream module does **not** import the template, the concept must remain exported. +**Important:** `import` provides definitions to the importing module. `exported_concepts` on a `requires` module provides concepts to downstream modules in the build chain. If a downstream module will now `import` the shared template directly, the concept can be removed from `exported_concepts`. If a downstream module does **not** import the template, the concept must remain exported. ### 2e. Present the plan @@ -84,7 +84,7 @@ Summarize for the user: - Target import module (new or existing) and its path - List of concepts being moved, with source module for each - List of concepts staying local (and why) -- Frontmatter changes per module (`imports` additions, `exported_concepts` removals) +- Frontmatter changes per module (`import` additions, `exported_concepts` removals) - Confirmation that no functional specs, implementation reqs, or test reqs are changing Get explicit user confirmation before proceeding. @@ -94,7 +94,7 @@ Get explicit user confirmation before proceeding. ### 3a. Create or update the import module If creating a new import module: -1. Create `template/.plain` with YAML frontmatter. If it needs concepts from another import, add that to its own `imports` list. +1. Create `template/.plain` with YAML frontmatter. If it needs concepts from another import, add that to its own `import` list. 2. Add a `***definitions***` section with all consolidated concepts in dependency order. 3. Do **not** add `***functional specs***` — import modules must not contain them. @@ -108,14 +108,14 @@ If updating an existing import module: For each module that had concepts moved out: 1. **Remove the concept definitions** from the module's `***definitions***` section. Remove the full definition including all sub-bullets. -2. **Add the import** to the module's YAML frontmatter `imports` list (if not already present). +2. **Add the import** to the module's YAML frontmatter `import` list (if not already present). 3. **Update `exported_concepts`** — remove concepts that no longer need to be exported (see 2d). 4. **Do not touch** `***functional specs***`, `***implementation reqs***`, or `***test reqs***` — concept references (`:ConceptName:`) in these sections remain unchanged since the concept is now provided by the import. ### 3c. Update downstream modules (if applicable) If concepts were removed from a module's `exported_concepts` and downstream modules relied on them: -1. Add the new import to the downstream module's `imports` list so it gets the concepts directly. +1. Add the new import to the downstream module's `import` list so it gets the concepts directly. 2. Verify the downstream module's specs can still resolve all concept references. ## Phase 4 — Verify @@ -123,7 +123,7 @@ If concepts were removed from a module's `exported_concepts` and downstream modu ### 4a. Concept availability check For every affected module (source modules, downstream modules, and the import module itself): -- [ ] Every `:Concept:` referenced in functional specs is resolvable (defined locally, via import, or via exported concepts from a base module) +- [ ] Every `:Concept:` referenced in functional specs is resolvable (defined locally, via import, or via exported concepts from requires) - [ ] Every `:Concept:` referenced in definitions is defined before the concept that references it - [ ] Every `:Concept:` referenced in implementation reqs and test reqs is resolvable @@ -131,7 +131,7 @@ For every affected module (source modules, downstream modules, and the import mo - [ ] No concept is defined in both the import module and a source module - [ ] No concept is defined in both the import module and another import that a source module uses -- [ ] Concept names are globally unique across each module's full resolution scope (local + imports + base exports) +- [ ] Concept names are globally unique across each module's full resolution scope (local + imports + requires exports) ### 4c. Definition integrity check @@ -150,11 +150,11 @@ For every affected module (source modules, downstream modules, and the import mo For the import module: - [ ] File is in the `template/` directory - [ ] Does not contain `***functional specs***` -- [ ] Does not use `base` +- [ ] Does not use `requires` - [ ] YAML frontmatter is correctly formatted For each source module: -- [ ] YAML frontmatter `imports` list includes the target import module +- [ ] YAML frontmatter `import` list includes the target import module - [ ] `exported_concepts` is correct (no over-export, no missing exports) - [ ] Module still has at least one functional spec and one implementation req @@ -168,16 +168,16 @@ Read each modified `.plain` file in full and present the changes to the user for If a concept is only referenced within a single module, consolidating it into a shared import adds unnecessary coupling. Keep it local. ### Forgetting to update downstream modules -When a concept is removed from `exported_concepts`, any module that lists the source module under `base` and references that concept will break — unless it now imports the shared template. Always trace the full dependency chain. +When a concept is removed from `exported_concepts`, any module that `requires` the source module and references that concept will break — unless it now imports the shared template. Always trace the full dependency chain. ### Breaking concept ordering Concepts in the import module must be topologically ordered. If `:Meal:` references `:MealType:` in its definition, then `:MealType:` must appear first. The source modules may have had them in the right order locally, but merging concepts from different modules requires re-establishing a valid order. ### Creating circular imports -An import module cannot list under `imports` a module that directly or indirectly imports it back. If the shared concepts reference concepts from an existing template, the new import module should itself import that template — not the other way around. +An import module cannot `import` a module that directly or indirectly imports it back. If the shared concepts reference concepts from an existing template, the new import module should itself import that template — not the other way around. ### Removing concepts from exported_concepts prematurely -Only remove a concept from `exported_concepts` if **every** downstream module that needs it will now get it via `imports`. If even one downstream module does not import the shared template, the export must stay. +Only remove a concept from `exported_concepts` if **every** downstream module that needs it will now get it via `import`. If even one downstream module does not import the shared template, the export must stay. ## Validation Checklist @@ -185,11 +185,11 @@ Only remove a concept from `exported_concepts` if **every** downstream module th - [ ] All moved concepts are defined in the import module with identical text - [ ] Concepts are in valid dependency order in the import module - [ ] No concept is defined in both the import module and a source module -- [ ] Every affected module's `imports` list includes the target import module +- [ ] Every affected module's `import` list includes the target import module - [ ] `exported_concepts` updated correctly — no missing exports, no unnecessary exports - [ ] All concept references in all affected modules resolve correctly - [ ] No functional specs, implementation reqs, test reqs, or test plain files were modified -- [ ] Import module is in `template/` and has no functional specs or base +- [ ] Import module is in `template/` and has no functional specs or requires - [ ] No circular imports introduced - [ ] Downstream modules updated if needed - [ ] User approved the final result diff --git a/forge/skills/create-base-module/SKILL.md b/forge/skills/create-base-module/SKILL.md deleted file mode 100644 index ae72a13..0000000 --- a/forge/skills/create-base-module/SKILL.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -name: create-base-module -description: >- - Create a ***plain module that lists another module under base to build on - it in the build chain. Use when the user wants to create a new .plain file - that builds on top of a previously built module, inheriting its functional - specs and generated code as a starting point. ---- - -# Create Base Module - -Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. - -## What Base Does - -`base` attaches the current module to the project's build chain. The base 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 base module, but their outputs are never independent: the current module's code physically contains the base module's. - -When this module is rendered: -- The base module's generated code (`plain_modules//code`) is copied as the starting point. -- The `***functional specs***` of the base module **and all its ancestors** become visible as **previous functional specs**. -- Of the base 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 under `base` too. -- The base module's test plain files stay with the base module. The new module gets its own test plain files under `tests//`, one per functionality it adds (see `test-plain-files.md`). - -All modules connected by `base` form a single tree with a common root. **Diamond joins are prohibited**: multiple `base` 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 `base` for: -- Building on top of an existing module's functionality -- 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 `imports` instead — see the `create-import-module` skill. - -## Workflow - -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 `base` field. Modules with functional specs live at the root, not in `template/`. -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. Add every functional spec through `add-functional-spec` or `add-functional-specs`, which also create its test plain file. -6. **Check for conflicts** between your new functional specs and any spec in the ancestor chain. - -## Format - -The `base` field is a list of module names in the YAML frontmatter: - -```plain ---- -base: - - core_module -imports: - - shared_template -description: Extended module that builds on core_module ---- - -***definitions*** - -- :NewFeature: is a feature added by this module. - -***functional specs*** - -# new-feature -:NewFeature: is available. -``` - -A module can use both `base` and `imports` together. `base` names other root-level modules; `imports` resolves from the default `template/` directory (no prefix needed). - -Multiple `base` entries are legal only when they lie on one ancestor path (e.g., `base: [auth, messaging]` where `messaging` itself has `auth` as its base). The deepest entry (`messaging`) is the attachment point; `auth` is listed only to make its exports visible. - -## Exported Concepts - -The base module controls what concepts are visible via `exported_concepts`: - -```plain -> In the base module's frontmatter: ---- -exported_concepts: [":StorageClient:", ":BackupResult:"] ---- -``` - -Only `:StorageClient:` and `:BackupResult:` would be available to modules that list this one under `base`, each with its full definition text. All other concepts from the base module are internal. - -## Chronological Ordering with Base - -Functional specs from the whole `base` 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 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 base module — `base` places it at the right point in the accumulating build chain either way. - -## Imports vs Base - -| Aspect | `imports` | `base` | -|--------|-----------|--------| -| 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, 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/`) -- [ ] Base module exists and is renderable -- [ ] All `base` entries lie on one ancestor path — no diamond joins -- [ ] The deepest `base` 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 -- [ ] Every functional spec has its test plain file under `tests//` -- [ ] Both `base` and `imports` are used correctly (not mixed up) -- [ ] YAML frontmatter is correctly formatted between `---` markers diff --git a/forge/skills/create-import-module/SKILL.md b/forge/skills/create-import-module/SKILL.md index 67dc8c6..8b30ec4 100644 --- a/forge/skills/create-import-module/SKILL.md +++ b/forge/skills/create-import-module/SKILL.md @@ -11,11 +11,11 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## What an Import Module Is -An import module is a `.plain` file that lives in the **`template/`** directory and contains **only** `***definitions***`, `***implementation reqs***`, and/or `***test reqs***`. It must **not** contain `***functional specs***` and must **not** use `base` in its frontmatter. Other modules pull in its content via the `imports` field in their YAML frontmatter, gaining access to its definitions and reqs. +An import module is a `.plain` file that lives in the **`template/`** directory and contains **only** `***definitions***`, `***implementation reqs***`, and/or `***test reqs***`. It must **not** contain `***functional specs***` and must **not** use `requires` in its frontmatter. Other modules pull in its content via the `import` field in their YAML frontmatter, gaining access to its definitions and reqs. Use import modules for: - Sharing concept definitions across multiple modules @@ -23,23 +23,23 @@ Use import modules for: - Providing common test requirements - Creating reusable foundational structure (templates) -If the module needs functional specs (i.e., it describes what software should do), it is not an import module — see the `create-base-module` skill. +If the module needs functional specs (i.e., it describes what software should do), it is not an import module — see the `create-requires-module` skill. ## Workflow 1. **Determine what shared content this module should provide** — which definitions, implementation reqs, or test reqs will other modules need? -2. **Create the `.plain` file in the `template/` directory** with YAML frontmatter. Use `required_concepts` to declare concepts that importing modules must define. It may list other templates under `imports` for layered reuse, but must **not** use `base`. +2. **Create the `.plain` file in the `template/` directory** with YAML frontmatter. Use `required_concepts` to declare concepts that importing modules must define. It may optionally `import` other templates for layered reuse, but must **not** use `requires`. 3. **Add the shared content** — definitions, implementation reqs, and/or test reqs. 4. **Do not add `***functional specs***`** — import modules must not contain functional specs. 5. **Verify concept availability** — ensure all `:Concepts:` referenced are either defined in this module, provided by its own imports, declared as `required_concepts`, or are predefined concepts. ## Format -The `imports` field is a list of module paths in the YAML frontmatter: +The `import` field is a list of module paths in the YAML frontmatter: ```plain --- -imports: +import: - base_template required_concepts: [":AppName:"] description: Shared API definitions and reqs @@ -70,7 +70,7 @@ If an imported template declares `required_concepts`, the importing module **mus ```plain --- -imports: +import: - auth_template --- @@ -84,10 +84,10 @@ imports: - :AuthApiSpec: is the OpenAPI specification for the external service's auth endpoint. ``` -## Imports vs Base +## Import vs Requires -| Aspect | `imports` | `base` | -|--------|-----------|--------| +| Aspect | `import` | `requires` | +|--------|----------|------------| | Pulls in definitions | Yes | No (only `exported_concepts`) | | Pulls in implementation reqs | Yes | No | | Pulls in test reqs | Yes | No | @@ -99,7 +99,7 @@ imports: - [ ] Module file is in the `template/` directory - [ ] Module does **not** contain `***functional specs***` -- [ ] Module does **not** use `base` in its frontmatter +- [ ] Module does **not** use `requires` in its frontmatter - [ ] Contains at least one of: definitions, implementation reqs, or test reqs - [ ] All `required_concepts` from any imports this module itself uses are satisfied - [ ] `required_concepts` declared for any concepts referenced but not defined here diff --git a/forge/skills/create-requires-module/SKILL.md b/forge/skills/create-requires-module/SKILL.md new file mode 100644 index 0000000..2224f88 --- /dev/null +++ b/forge/skills/create-requires-module/SKILL.md @@ -0,0 +1,115 @@ +--- +name: create-requires-module +description: >- + Create a ***plain module that uses requires to depend on another module in + the build chain. Use when the user wants to create a new .plain file that + builds on top of a previously built module, inheriting its functional specs + and generated code as a starting point. +--- + +# Create Requires Module + +Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. + +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. + +## What Requires Does + +`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//code`) is copied as the starting point. +- 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. +- The required module's test plain files stay with the required module. The new module gets its own test plain files under `tests//`, one per functionality it adds (see `test-plain-files.md`). + +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: +- Building on top of an existing module's functionality +- 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 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 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. Add every functional spec through `add-functional-spec` or `add-functional-specs`, which also create its test plain file. +6. **Check for conflicts** between your new functional specs and any spec in the ancestor chain. + +## Format + +The `requires` field is a list of module paths in the YAML frontmatter: + +```plain +--- +requires: + - base_module +import: + - shared_template +description: Extended module that builds on base_module +--- + +***definitions*** + +- :NewFeature: is a feature added by this module. + +***functional specs*** + +# new-feature +: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). + +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`: + +```plain +> In the required module's frontmatter: +--- +exported_concepts: [":StorageClient:", ":BackupResult:"] +--- +``` + +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 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 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 — `requires` places it at the right point in the accumulating build chain either way. + +## Import vs Requires + +| Aspect | `import` | `requires` | +|--------|----------|------------| +| 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, 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 +- [ ] 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 +- [ ] Every functional spec has its test plain file under `tests//` +- [ ] Both `requires` and `import` are used correctly (not mixed up) +- [ ] YAML frontmatter is correctly formatted between `---` markers diff --git a/forge/skills/debug-specs/SKILL.md b/forge/skills/debug-specs/SKILL.md index a5f94c6..9c42845 100644 --- a/forge/skills/debug-specs/SKILL.md +++ b/forge/skills/debug-specs/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## When to Use @@ -32,7 +32,7 @@ Generated code in `plain_modules//code/` and `plain_modules//tes ## Phase 1 — Understand the Context -1. **Read the `.plain` file(s)** for the module — frontmatter, definitions, implementation reqs, test reqs, and all functional specs. Also read `imports` and `base` chains, and the test plain files under `tests//`. +1. **Read the `.plain` file(s)** for the module — frontmatter, definitions, implementation reqs, test reqs, and all functional specs. Also read `import` and `requires` chains, and the test plain files under `tests//`. 2. **Read the user's observation carefully.** Rephrase it back to confirm understanding. Identify: - What is the expected behavior? - What is the actual behavior? diff --git a/forge/skills/forge-plain/SKILL.md b/forge/skills/forge-plain/SKILL.md index 2cb46c5..58d44b8 100644 --- a/forge/skills/forge-plain/SKILL.md +++ b/forge/skills/forge-plain/SKILL.md @@ -14,7 +14,7 @@ description: >- 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. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## Role diff --git a/forge/skills/forge-plain/references/phase-1-product.md b/forge/skills/forge-plain/references/phase-1-product.md index 49e67d0..502899a 100644 --- a/forge/skills/forge-plain/references/phase-1-product.md +++ b/forge/skills/forge-plain/references/phase-1-product.md @@ -8,7 +8,7 @@ for each topic below, in order. 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 (`imports`, `base`, `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. +- **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), headed by a func-id per `semantic-ids.md`. 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.** - **`***implementation reqs***`** — only a requirement that can be stated without naming a technology: an error-handling or retry policy, an idempotency or ordering guarantee, a data-format or precision rule. Use the `add-implementation-requirement` skill, on the module whose behavior it constrains. The moment stating it takes a language, framework, library, storage engine, or deployment target, it is a Phase 2 answer — note it and steer back. diff --git a/forge/skills/forge-plain/references/phase-4-validate-handoff.md b/forge/skills/forge-plain/references/phase-4-validate-handoff.md index 2dc1b4a..1c881bb 100644 --- a/forge/skills/forge-plain/references/phase-4-validate-handoff.md +++ b/forge/skills/forge-plain/references/phase-4-validate-handoff.md @@ -4,7 +4,7 @@ Two halves. First **the agent** validates every spec with `plain-healthcheck` so ## 4a. Identify the render target -Find the **last module in the dependency chain** — the module that is not listed under `base` by any other module. If there is only one module, use it. Call this module ``. +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`. @@ -27,10 +27,10 @@ Run the `plain-healthcheck` skill — the single source of truth for "is this pr - 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 `plain-parser check` on every top module, which parses it with its whole `imports` / `base` chain (frontmatter, module graph, sections, concepts, linked resources, templates), and +- runs `plain-parser check` on every top module, which parses it with its whole `import` / `requires` chain (frontmatter, module graph, sections, concepts, linked resources, templates), and - checks every module for what the parser can't judge (section ownership, bullets, linked-resource contents). -The skill runs the full detect → fix → re-run loop itself (syntax errors, undefined concepts, broken `imports` / `base` chains, cyclic definitions, missing templates, misplaced facts, config drift, missing scripts) and returns only once everything passes or a gap genuinely needs the user. Then: +The skill runs the full detect → fix → re-run loop itself (syntax errors, undefined concepts, broken `import` / `requires` chains, cyclic definitions, missing templates, misplaced facts, 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. diff --git a/forge/skills/init-config-file/SKILL.md b/forge/skills/init-config-file/SKILL.md index 07620c9..d66a38c 100644 --- a/forge/skills/init-config-file/SKILL.md +++ b/forge/skills/init-config-file/SKILL.md @@ -73,7 +73,7 @@ Before emitting anything, state the planned split to the user (e.g. "I'll emit ` ### Step 1 — Inventory -1. List every `.plain` file in the repo and identify the top modules (modules not listed under `base` by any other module) — same procedure as `plain-healthcheck` Step 1. A frontmatter that still uses `requires:` or `import:` is the old syntax: stop and point the user to `migrate-plain-syntax`. +1. List every `.plain` file in the repo and identify the top modules (modules not `requires`-ed by any other module) — same procedure as `plain-healthcheck` Step 1. 2. For each top module, determine which part it belongs to (single-stack → one part; multi-part → one part per top module). 3. List every script under `test_scripts/` and group them by part (e.g. `*_python.sh` belongs to the backend part, `*_js.sh` belongs to the frontend part). 4. Identify the template directory (typically `template/`) and any custom resource directories (typically `resources/`). diff --git a/forge/skills/init-plain-project/SKILL.md b/forge/skills/init-plain-project/SKILL.md index cea7cb9..2ba1bcc 100644 --- a/forge/skills/init-plain-project/SKILL.md +++ b/forge/skills/init-plain-project/SKILL.md @@ -3,7 +3,7 @@ name: init-plain-project description: >- Lightweight initializer for a new ***plain project. Asks about the base technology and project kind, asks whether conformance testing is enabled, - then scaffolds a template import module (with the stack's implementation reqs and + then scaffolds a template import module (with base implementation reqs and test reqs), a stub top-level module, the unit testing script, and (optionally) the conformance and prepare-environment scripts plus a config.yaml. Does NOT run `plain-healthcheck`. Use when the user wants a @@ -21,8 +21,8 @@ This skill is intentionally **minimal**. It produces a runnable project skeleton What this skill writes: -- `template/stack.plain` — an import module with the project's stack-wide `***implementation reqs***` and `***test reqs***`. -- `.plain` — a top-level module that imports `stack`. Frontmatter only — no body sections. The user adds `***functional specs***` later, each with its test plain file under `tests//`. +- `template/base.plain` — an import module with the project's base `***implementation reqs***` and `***test reqs***`. +- `.plain` — a top-level module that imports `base`. Frontmatter only — no body sections. The user adds `***functional specs***` later, each with its test plain file under `tests//`. - `test_scripts/run_unittests_.` — always. - `test_scripts/run_conformance_tests_.` — only if conformance testing is enabled. - `test_scripts/prepare_environment_.` — only if conformance testing is enabled **and** the user opts in. @@ -55,25 +55,25 @@ Use **AskUserQuestion** for one tight batch covering: - **Conformance testing** — enabled or not. If the user is unsure, briefly explain it adds a `run_conformance_tests` script and, for every functionality added later, a test plain file with conformance scenarios. - **Prepare-environment script** — only ask this if conformance testing was enabled in the previous question. Generate one or skip. Recommend "yes" when dependencies need installing (most stacks); recommend "skip" when there is genuinely nothing to set up. If conformance testing is disabled, skip this question entirely and do not generate a prepare-environment script. -### 3. Author `template/stack.plain` +### 3. Author `template/base.plain` Create the import module with `create-import-module`. It must contain: -- `***implementation reqs***` — the stack as requirements **and everything about `:UnitTests:`**: +- `***implementation reqs***` — the base stack as requirements **and everything about `:UnitTests:`**: - Programming language and version. - Primary framework (if any). - Dependency / package manager. - Project kind as a constraint (e.g. ":Implementation: should be a REST API service."). - `:UnitTests:` framework (pytest / Jest / JUnit / Go's `testing` / …) and the command used to run them — phrased in terms of `:UnitTests:` so the partition is explicit. - Anything else the user added in the free-form catch-all. -- `***test reqs***` — the **conformance**-testing rules (only added if conformance testing is enabled): +- `***test reqs***` — the base **conformance**-testing rules (only added if conformance testing is enabled): - ":ConformanceTests: must be implemented and executed - do not skip tests." - The `:ConformanceTests:` framework and the command used to run them. - **Do NOT** put unit-test framework / command here — that lives in `***implementation reqs***` above. -Do **not** add a `***definitions***` section to `template/stack.plain`. This skill does not author any concepts. Use only the predefined concepts (`:Implementation:`, `:ConformanceTests:`) in the reqs — no project-specific concepts like `:AppName:` or `:App:`. Do not declare `required_concepts` either. +Do **not** add a `***definitions***` section to `template/base.plain`. This skill does not author any concepts. Use only the predefined concepts (`:Implementation:`, `:ConformanceTests:`) in the reqs — no project-specific concepts like `:AppName:` or `:App:`. Do not declare `required_concepts` either. -Do **not** add `***functional specs***` to `template/stack.plain` — it is an import module. +Do **not** add `***functional specs***` to `template/base.plain` — it is an import module. ### 4. Author the top module `.plain` @@ -81,8 +81,8 @@ Create it at the repo root with frontmatter: ```yaml --- -imports: - - stack +import: + - base description: --- ``` @@ -122,7 +122,7 @@ Do **not** invoke `init-config-file` or `plain-healthcheck` here — keeping thi Tell the user what was created and what's next: -- Files written: `template/stack.plain`, `.plain`, the test scripts, `config.yaml`, and the `tests/` folder when conformance testing is enabled. +- Files written: `template/base.plain`, `.plain`, the test scripts, `config.yaml`, and the `tests/` folder when conformance testing is enabled. - Suggested next steps: add concepts with `add-concept`, add functional specs with `add-functional-spec` or `add-functional-specs` (each new functionality gets its test plain file under `tests//` when conformance testing is enabled), or jump straight into `add-feature`. - Mention that the project has not been validated — they can run `plain-healthcheck` when they want validation. diff --git a/forge/skills/init-plain-project/references/checklist.md b/forge/skills/init-plain-project/references/checklist.md index ccd108d..5239687 100644 --- a/forge/skills/init-plain-project/references/checklist.md +++ b/forge/skills/init-plain-project/references/checklist.md @@ -13,10 +13,10 @@ Use this to verify the scaffold was produced correctly — never as a substitute ## Author the modules -- [ ] Created `template/stack.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/stack.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/stack.plain`; only predefined concepts (`:Implementation:`, `:ConformanceTests:`) used; no `required_concepts` declared. -- [ ] Created `.plain` at the repo root with frontmatter only (`imports: [stack]`, `description`) and **no body sections** — the file exists on disk. +- [ ] 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 diff --git a/forge/skills/load-plain-reference/SKILL.md b/forge/skills/load-plain-reference/SKILL.md index 224963b..a2a6aed 100644 --- a/forge/skills/load-plain-reference/SKILL.md +++ b/forge/skills/load-plain-reference/SKILL.md @@ -46,14 +46,14 @@ All paths are relative to this `SKILL.md`. | Test plain files or scenarios | `../../rules/test-plain-files.md`, `../../rules/semantic-ids.md` | | Implementation requirements or unit tests | `../../rules/impl-reqs.md` | | Test requirements or conformance tests | `../../rules/test-reqs.md` | -| `imports` and import modules | `../../rules/import-modules.md` | -| `base` and base modules | `../../rules/base-modules.md` | +| `import` modules | `../../rules/import-modules.md` | +| `requires` modules | `../../rules/requires-modules.md` | | `required_concepts` | `../../rules/required-concepts.md` | | `exported_concepts` | `../../rules/exported-concepts.md` | | Linked files or `resources/` | `../../rules/linked-resources.md` | | Any `.plain` text or example | `../../rules/glossary.md`, `../../rules/bullet-continuation.md`, `../../rules/concise-specs.md` | | Drafting or trimming spec wording, or reviewing a spec for verbosity | `../../rules/concise-specs-examples.md` | -| A file that may be in the old syntax (`- ` functional specs, nested `***acceptance tests***`, `requires:` / `import:`) | `../../rules/module-structure.md` (*Old syntax*), then the `migrate-plain-syntax` skill | +| A file that may be in the old syntax (`- ` functional specs, nested `***acceptance tests***`) | `../../rules/module-structure.md` (*Old syntax*), then the `migrate-plain-syntax` skill | For a whole-file review or a task spanning several sections, load the union of the applicable rule files. Do not load every rule by default. diff --git a/forge/skills/load-plain-reference/references/project-model.md b/forge/skills/load-plain-reference/references/project-model.md index cfce816..b374979 100644 --- a/forge/skills/load-plain-reference/references/project-model.md +++ b/forge/skills/load-plain-reference/references/project-model.md @@ -28,7 +28,7 @@ what these directories may contain and how a spec refers to them. ## Template inclusion -In addition to frontmatter `imports`, ***plain supports parameterized template inclusion: +In addition to frontmatter `import`, ***plain supports parameterized template inclusion: ```plain {% include "python-console-app-template.plain", main_executable_file_name: "my_app.py" %} diff --git a/forge/skills/load-plain-reference/references/rendering-and-testing.md b/forge/skills/load-plain-reference/references/rendering-and-testing.md index 3a88485..8ad5cc4 100644 --- a/forge/skills/load-plain-reference/references/rendering-and-testing.md +++ b/forge/skills/load-plain-reference/references/rendering-and-testing.md @@ -3,7 +3,7 @@ ## Incremental rendering Functional specs render from top to bottom. When rendering one functional spec, the renderer knows -the previous specs but not future specs. A base module's functional specs also count as previous +the previous specs but not future specs. A required module's functional specs also count as previous specs according to the module rules. The renderer reads the test plain files under `tests/` and generates exactly the scenarios they hold, diff --git a/forge/skills/migrate-plain-syntax/SKILL.md b/forge/skills/migrate-plain-syntax/SKILL.md index fb619eb..f16980b 100644 --- a/forge/skills/migrate-plain-syntax/SKILL.md +++ b/forge/skills/migrate-plain-syntax/SKILL.md @@ -4,15 +4,14 @@ description: >- Convert a ***plain project from the old syntax to the current one: give every functional spec a # func-id heading, move nested ***acceptance tests*** into test plain files under tests//, draft conformance scenarios - with the user for every functionality, and rename the frontmatter directives - requires and import to base and imports. Use when a skill or plain-parser + with the user for every functionality. Use when a skill or plain-parser reports the old syntax, or when the user asks to migrate or upgrade old .plain files. --- # Migrate Plain Syntax -Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. This skill needs `module-structure.md`, `func-specs.md`, `semantic-ids.md`, `test-plain-files.md`, `base-modules.md`, and `import-modules.md`. +Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. This skill needs `module-structure.md`, `func-specs.md`, `semantic-ids.md`, `test-plain-files.md`, `requires-modules.md`, and `import-modules.md`. ## What changes @@ -21,14 +20,12 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule | A functional spec is a flush-left `- ` bullet with nested ` - ` sub-bullets | A functional spec is a `# ` heading, a statement line, and indented `- ` clarification bullets | | `***acceptance tests***` nested under a functional spec | `***acceptance tests***` section of the functionality's test plain file `tests//.plain` | | The renderer invents conformance tests | `***conformance tests***` section of the test plain file holds at least one scenario per functionality | -| `requires:` in the frontmatter | `base:` | -| `import:` in the frontmatter | `imports:` | There is no backward compatibility: a project in the old syntax does not render, and no other skill edits it. The migration converts the whole project in one pass. ## Scope -- Every `.plain` file of the project: the root modules, the import modules in the template directory, and every module reachable through `base` and `imports`. A half-migrated project fails the parser. +- Every `.plain` file of the project: the root modules, the import modules in the template directory, and every module reachable through `requires` and `import`. A half-migrated project fails the parser. - Spec text is copied verbatim; only its shape changes. Rewording, trimming, and splitting are separate work for `add-feature`, `break-down-func-spec`, or `resolve-section-ownership` afterwards. - Generated output under `plain_modules/` is never edited. Nothing is rendered. @@ -36,16 +33,12 @@ There is no backward compatibility: a project in the old syntax does not render, ### 1. Inventory and confirm -1. List every `.plain` file in the project. Follow the `requires` / `base` and `import` / `imports` chains from each root module so no module is missed. -2. For each file, record which old-syntax markers it holds: flush-left `- ` bullets in `***functional specs***`, nested `***acceptance tests***` sections, `requires:`, `import:`. +1. List every `.plain` file in the project. Follow the `requires` and `import` chains from each root module so no module is missed. +2. For each file, record which old-syntax markers it holds: flush-left `- ` bullets in `***functional specs***`, nested `***acceptance tests***` sections. 3. Read every `config.yaml` to learn whether conformance tests are enabled for each module (a `conformance-tests-script` entry). 4. Show the user the inventory (files, markers, conformance on or off per module) and get explicit confirmation before writing anything. -### 2. Rename the directives - -In every frontmatter, rename the key `requires:` to `base:` and the key `import:` to `imports:`. Values stay unchanged. `{% include %}` lines and `exported_concepts` / `required_concepts` stay unchanged. - -### 3. Give every functionality a func-id +### 2. Give every functionality a func-id For each root module, walk the `***functional specs***` section top to bottom: @@ -56,9 +49,9 @@ For each root module, walk the `***functional specs***` section top to bottom: - Each nested ` - ` sub-bullet kept as it is: it is already a clarification indented by two spaces, and deeper nesting keeps its indentation. - One blank line between functionalities. 3. Keep comment lines (`>`) and Liquid tags where they are. -4. Cut each nested `***acceptance tests***` block out of the core spec and keep its bullets for step 4, together with the func-id they belonged to. +4. Cut each nested `***acceptance tests***` block out of the core spec and keep its bullets for step 3, together with the func-id they belonged to. -### 4. Move the acceptance tests into test plain files +### 3. Move the acceptance tests into test plain files For every functionality that had nested acceptance tests, create `tests//.plain` with the frontmatter keys `module:` (the core spec's file name without `.plain`) and `func_id:` and both sections, holding one scenario per old acceptance test, sorted by its content: a test of the expected use (valid input, the main path) goes under `***conformance tests***`; a test of use that is not expected (invalid input, wrong or missing authentication, missing data) goes under `***acceptance tests***`: @@ -67,27 +60,26 @@ For every functionality that had nested acceptance tests, create `tests/ Show each created file to the user as a snippet, with the split, and apply their edits before moving on. -### 5. Draft conformance scenarios +### 4. Draft conformance scenarios -When conformance tests are enabled for a module, every functionality needs at least one conformance scenario. Walk the module's functionalities in order and invoke `add-test-scenarios` for each one; it creates the test plain file when step 4 did not, drafts the scenarios the spec still lacks in both sections, and reviews them with the user one functionality at a time. +When conformance tests are enabled for a module, every functionality needs at least one conformance scenario. Walk the module's functionalities in order and invoke `add-test-scenarios` for each one; it creates the test plain file when step 3 did not, drafts the scenarios the spec still lacks in both sections, and reviews them with the user one functionality at a time. When `plain_modules//tests/` holds conformance tests from an earlier render, read the folder of the functionality first: each generated test is a candidate scenario the user can keep, edit, or drop, which is faster than drafting from the spec alone. -When conformance tests are not enabled for a module, skip this step and say so; the test plain files created in step 4 keep an empty `***conformance tests***` section. +When conformance tests are not enabled for a module, skip this step and say so; the test plain files created in step 3 keep an empty `***conformance tests***` section. -### 6. Verify +### 5. Verify 1. No `***functional specs***` section holds a flush-left `- ` bullet; every functionality starts with a `# ` heading. 2. No core spec holds an `***acceptance tests***` section. -3. No frontmatter holds `requires:` or `import:`. -4. Func-ids are unique per module; scenario-ids are unique per functionality. -5. Every test plain file binds to an existing functionality; when conformance tests are enabled, every functionality has at least one conformance scenario. -6. The number of functionalities per module equals the number of flush-left bullets before the migration, and every old acceptance test appears once as a scenario, in the section its content decides. -7. Run `plain-healthcheck`. Until the plain-parser release that parses the current syntax is installed, its parser step reports that it is suspended; the other checks still run. +3. Func-ids are unique per module; scenario-ids are unique per functionality. +4. Every test plain file binds to an existing functionality; when conformance tests are enabled, every functionality has at least one conformance scenario. +5. The number of functionalities per module equals the number of flush-left bullets before the migration, and every old acceptance test appears once as a scenario, in the section its content decides. +6. Run `plain-healthcheck`. Until the plain-parser release that parses the current syntax is installed, its parser step reports that it is suspended; the other checks still run. -### 7. Report +### 6. Report -Per module: functionalities migrated, directives renamed, acceptance tests moved, test plain files created, conformance scenarios drafted, and anything the user still has to decide. Remind the user that the project renders only with a renderer that reads test plain files. +Per module: functionalities migrated, acceptance tests moved, test plain files created, conformance scenarios drafted, and anything the user still has to decide. Remind the user that the project renders only with a renderer that reads test plain files. ## Rules @@ -124,10 +116,10 @@ After, the core spec: ```plain --- -base: +requires: - core -imports: - - stack +import: + - base --- ***functional specs*** @@ -159,7 +151,6 @@ Adding a :Task: named "Go" is rejected and :TaskList: stays empty. ## Validation Checklist - [ ] Every `.plain` file of the project was inventoried and the user confirmed the pass -- [ ] `requires:` and `import:` renamed to `base:` and `imports:` in every frontmatter - [ ] Every functionality has a `# ` heading approved by the user; text verbatim; order unchanged - [ ] No `***acceptance tests***` section remains in a core spec; every old acceptance test is a scenario in a test plain file, in the section its content decides, confirmed by the user - [ ] Every functionality has at least one conformance scenario when conformance tests are enabled for its module diff --git a/forge/skills/plain-healthcheck/SKILL.md b/forge/skills/plain-healthcheck/SKILL.md index f8e66a7..53229cd 100644 --- a/forge/skills/plain-healthcheck/SKILL.md +++ b/forge/skills/plain-healthcheck/SKILL.md @@ -22,7 +22,7 @@ Run this skill **whenever anything in the ***plain project is finalized** and th - **End of `forge-plain`** (Phase 4) — before handing off the render target. - **End of `add-feature`** (Phase 3 final review) — before declaring the feature done. - **End of `debug-specs`** — after applying a fix, before telling the user to re-render. -- **After finalizing any single edit** that changes the renderable surface — e.g. after `add-concept`, `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `add-test-requirement`, `add-test-scenarios`, `add-template`, `add-resource`, `resolve-spec-conflict`, `break-down-func-spec`, `consolidate-concepts`, `refactor-module`, `create-import-module`, `create-base-module`, or any of the `implement-*-testing-script` skills. +- **After finalizing any single edit** that changes the renderable surface — e.g. after `add-concept`, `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `add-test-requirement`, `add-test-scenarios`, `add-template`, `add-resource`, `resolve-spec-conflict`, `break-down-func-spec`, `consolidate-concepts`, `refactor-module`, `create-import-module`, `create-requires-module`, or any of the `implement-*-testing-script` skills. - **After hand-editing** a `.plain` file, a `config.yaml`, or anything under `test_scripts/`. - **On demand** — whenever the user asks whether the project is in a renderable state. @@ -36,8 +36,8 @@ 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 (`base`, `imports`); Step 3a validates it. -2. Identify **top modules** — every module that is not listed under `base` 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. +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`); Step 3a validates it. +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/`. 5. List every test plain file: each `.plain` file under `tests/` (and under any other folder the user names) whose frontmatter has `module:` and `func_id:`. Record the pair it binds to. @@ -75,7 +75,7 @@ Step 3 has two halves: a mechanical parse by `plain-parser`, then the checks tha #### 3a. Parse every top module with `plain-parser` -[`plain-parser`](https://pypi.org/project/plain-parser/) is the ***plain parser. It parses a module together with its whole `imports` / `base` chain, so running it on every top module covers every module in the project. +[`plain-parser`](https://pypi.org/project/plain-parser/) is the ***plain parser. It parses a module together with its whole `import` / `requires` chain, so running it on every top module covers every module in the project. 1. **Check it is installed.** Run `plain-parser --help`. If the command is not found, look for it where `plain-forge install` puts it before giving up: `~/.local/bin/plain-parser` (`%USERPROFILE%\.local\bin\plain-parser.exe` on Windows), or the directory printed by `uv tool dir --bin`. If it is there, run it by that full path for the rest of this step and tell the user to add the directory to `PATH`. If it is nowhere, stop and report `FAIL` with the install command (`pipx install plain-parser` or `uv tool install plain-parser`; it needs Python 3.11 or newer). Do **not** fall back to checking these rules by reading the files — without the parser there is no syntax gate. 2. **Check that it parses the current syntax.** Write this probe module as `probe.plain` into a fresh temporary directory and run `plain-parser check probe.plain` there: @@ -91,7 +91,7 @@ Step 3 has two halves: a mechanical parse by `plain-parser`, then the checks tha :Implementation: starts. ``` - If it prints `OK`, continue. If it reports an error, the installed plain-parser predates the current syntax (`# func-id` headings, test plain files, `base:` / `imports:`). Then record one failure against the project — `plain-parser does not parse the current ***plain syntax; install the release that does and run plain-healthcheck again` — skip steps 3 and 4 of 3a, still run 3b, and report `FAIL`. Never rewrite the specs into an older syntax to satisfy the old parser. + If it prints `OK`, continue. If it reports an error, the installed plain-parser predates the current syntax (`# func-id` headings, test plain files). Then record one failure against the project — `plain-parser does not parse the current ***plain syntax; install the release that does and run plain-healthcheck again` — skip steps 3 and 4 of 3a, still run 3b, and report `FAIL`. Never rewrite the specs into an older syntax to satisfy the old parser. 3. **Run it from the top module's own directory**, once per top module: ```bash @@ -101,7 +101,7 @@ Step 3 has two halves: a mechanical parse by `plain-parser`, then the checks tha Add `--config-name ` when the governing config is not named `config.yaml`. `plain-parser` reads `template-dir` from the config next to the `.plain` file or in the working directory, and exits `2` when it finds one in both — running from the module's own directory avoids that in multi-part projects. 4. **Read the result.** Exit `0` prints `: OK`. Exit `1` prints `Error: …` on stderr — record that message verbatim as a failure against the top module. Exit `2` is a usage or config error (ambiguous or malformed config) — record it against the config. -`plain-parser` checks, among other things: frontmatter, that every `imports` / `base` entry exists and the graph has no cycles, that `base` modules have one fixed order, that imported modules have no functional specs, section headings (allowed, not duplicated, `***definitions***` first), `# func-id` headings in `***functional specs***` with unique func-ids, test plain files (frontmatter `module:` and `func_id:` naming an existing functionality, both sections present, unique scenario-ids), concepts (defined, exported and required correctly), that a module with functional specs has implementation reqs, and that every linked resource and template resolves to an existing, relative, non-`.plain` file. It does not always report every error in one run, so after fixing what it reported, run it again until it prints `OK`. +`plain-parser` checks, among other things: frontmatter, that every `import` / `requires` entry exists and the graph has no cycles, that `requires` modules have one fixed order, that imported modules have no functional specs, section headings (allowed, not duplicated, `***definitions***` first), `# func-id` headings in `***functional specs***` with unique func-ids, test plain files (frontmatter `module:` and `func_id:` naming an existing functionality, both sections present, unique scenario-ids), concepts (defined, exported and required correctly), that a module with functional specs has implementation reqs, and that every linked resource and template resolves to an existing, relative, non-`.plain` file. It does not always report every error in one run, so after fixing what it reported, run it again until it prints `OK`. #### 3b. Check what the parser can't judge @@ -111,7 +111,7 @@ Check every `.plain` module against the rules loaded by `load-plain-reference`: 2. **Bullets and headings.** Every line in `***definitions***`, `***implementation reqs***`, and `***test reqs***` is a valid `- ` list item or a correctly indented sub-bullet — no bare continuation lines. Every functionality in `***functional specs***` is a `# ` heading followed by its body, with its `- ` clarifications indented by two spaces. 3. **Linked resources.** Every linked resource is a single **text** file (not a binary), and no URL or folder path appears in the surrounding spec text unless it is a runtime value the generated software itself uses. 4. **Test plain files.** Every test plain file binds to an existing functionality (`module:` names a module, `func_id:` one of its `# func-id` headings) and has both sections in order. Scenario-ids are unique per functionality across every file bound to it. When the module's `config.yaml` has a `conformance-tests-script`, every functionality has at least one conformance scenario; a functionality with none is a failure that names `add-test-scenarios`. A scenario under `***conformance tests***` that describes use that is not expected (invalid parameters, wrong or missing authentication, missing data), or one under `***acceptance tests***` that describes the expected use, is a failure that names `add-test-scenarios`. -5. **Old syntax.** A flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in a frontmatter is a failure that names `migrate-plain-syntax`. Do not fix it inline. +5. **Old syntax.** A flush-left `- ` bullet in `***functional specs***`, or an `***acceptance tests***` section in a core spec is a failure that names `migrate-plain-syntax`. Do not fix it inline. Functional-spec complexity is checked when a spec is authored (`add-functional-spec(s)` → `analyze-if-func-spec-too-complex`). Re-run `analyze-if-func-spec-too-complex` here only for a spec that was hand-edited since it was authored. @@ -121,7 +121,7 @@ Treat 3a and 3b together as a hard gate: the healthcheck **only passes** when `p Iterate until it passes: -1. Identify the offending `.plain` file, the line, and the kind of issue: missing or duplicated concept, cyclic definition, broken `imports`/`base`, misplaced fact, bare continuation line, missing resource or template, etc. +1. Identify the offending `.plain` file, the line, and the kind of issue: missing or duplicated concept, cyclic definition, broken `import`/`requires`, misplaced fact, bare continuation line, missing resource or template, etc. 2. Fix only the `.plain` files (or the relevant `config.yaml` / template) using the appropriate edit skill — `add-concept`, `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `add-test-scenarios`, `resolve-section-ownership`, `resolve-spec-conflict`, `break-down-func-spec`, `consolidate-concepts`, or an inline edit — or `migrate-plain-syntax` for a file in the old syntax. **Never** modify generated code under `plain_modules//code/` or `plain_modules//tests/`. 3. If you are uncertain about ***plain syntax for the failing construct, re-load `load-plain-reference` before fixing. 4. Re-run the failed checks — for a `plain-parser` failure, re-run `plain-parser check`. Repeat until they pass. diff --git a/forge/skills/refactor-module/SKILL.md b/forge/skills/refactor-module/SKILL.md index 340b6a6..859f97d 100644 --- a/forge/skills/refactor-module/SKILL.md +++ b/forge/skills/refactor-module/SKILL.md @@ -2,7 +2,7 @@ name: refactor-module description: >- Split a large ***plain module into smaller modules grouped by logical domain. - The resulting modules are connected via a base chain so that functionality + The resulting modules are connected via a requires chain so that functionality is 100% preserved. Use when a module has grown too large and its functional specs span multiple distinct concerns that would be clearer as separate modules. --- @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## When to Use @@ -30,7 +30,7 @@ The `.plain` file to refactor, plus guidance from the user on the desired groupi ## Phase 1 — Analyze the Current Module -1. **Read the entire `.plain` file** — frontmatter, definitions, implementation reqs, test reqs, and all functional specs. Also read any `imports` and `base` chains to understand the full context. +1. **Read the entire `.plain` file** — frontmatter, definitions, implementation reqs, test reqs, and all functional specs. Also read any `import` and `requires` chains to understand the full context. 2. **Inventory the functional specs.** List each functionality by its func-id and note which `:Concepts:` it references. Build a dependency map: which specs depend on earlier specs (by referencing behavior or state they introduced)? 3. **Inventory the definitions.** For each concept, note which functional specs reference it. Identify concepts that are used across multiple logical groups vs. concepts used only within one group. 4. **Identify logical groups.** Look for natural seams — clusters of functional specs that share concepts, represent a cohesive domain, or describe a single feature area. Common groupings: @@ -47,17 +47,17 @@ Present the proposed split to the user and get explicit confirmation before maki ### 2a. Define the module chain -The modules form a `base` chain that preserves the original chronological ordering of functional specs. The first module in the chain contains the earliest specs; each subsequent module lists the previous one under `base` and adds the next logical group. +The modules form a `requires` chain that preserves the original chronological ordering of functional specs. The first module in the chain contains the earliest specs; each subsequent module `requires` the previous one and adds the next logical group. ``` original-module ↓ splits into -module-group-a (no base — this is the chain root) -module-group-b (base: module-group-a) -module-group-c (base: module-group-b) +module-group-a (no requires — this is the chain root) +module-group-b (requires: module-group-a) +module-group-c (requires: module-group-b) ``` -If the original module already had `base` or `imports` dependencies, the first new module inherits them. +If the original module already had `requires` or `import` dependencies, the first new module inherits them. ### 2b. Assign functional specs to modules @@ -89,7 +89,7 @@ If many concepts are shared across all modules, consider creating a new import m Summarize for the user: - Number of resulting modules and their names - Which functional specs go in each module (listed by func-id) -- The `base` chain order +- The `requires` chain order - Any new import module being created - Which concepts are exported from each module - Confirmation that all original specs are accounted for (none lost) @@ -107,7 +107,7 @@ If shared definitions or reqs are being moved to a new import template, create i ### 3b. Create the chain root Create the first module in the chain: -1. Set the YAML frontmatter — inherit the original module's `imports` references. Add `exported_concepts` for any concepts that downstream modules need. +1. Set the YAML frontmatter — inherit the original module's `import` references. Add `exported_concepts` for any concepts that downstream modules need. 2. Add the `***definitions***` section with concepts used by this module's specs and concepts that need to be exported. 3. Add `***implementation reqs***` and `***test reqs***` specific to this module (if not fully covered by the import template). 4. Add the `***functional specs***` assigned to this module, preserving their original order and exact wording. @@ -116,9 +116,9 @@ Create the first module in the chain: ### 3c. Create subsequent modules For each subsequent module in the chain: -1. Set `base` to the previous module in the chain. Set `imports` if it uses shared templates. +1. Set `requires` to point to the previous module in the chain. Set `import` if it uses shared templates. 2. Add `exported_concepts` for any concepts that later modules in the chain need. -3. Add `***definitions***` for concepts local to this module. Do **not** redefine concepts already available via `base` (exported) or `imports`. +3. Add `***definitions***` for concepts local to this module. Do **not** redefine concepts already available via `requires` (exported) or `import`. 4. Add `***implementation reqs***` and `***test reqs***` specific to this module. 5. Add the `***functional specs***` assigned to this module, preserving their original order and exact wording. 6. Move the test plain files of the moved functionalities the same way. @@ -127,7 +127,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 other modules listed the original module under `base`, update them to list 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 under `base` as well. +- 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 @@ -142,12 +142,12 @@ Checklist: ### 4b. Chronological ordering check -Verify that the chronological order of all functional specs — when read across the base chain from root to leaf — matches the original ordering exactly. A spec must not appear before a spec it depends on. +Verify that the chronological order of all functional specs — when read across the requires chain from root to leaf — matches the original ordering exactly. A spec must not appear before a spec it depends on. ### 4c. Concept availability check For each module, verify: -- [ ] Every `:Concept:` referenced in its specs is either defined locally, available via `imports`, or available via `exported_concepts` from a module listed under `base` +- [ ] 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 @@ -156,7 +156,7 @@ For each module, verify: For each new module: - [ ] Has at least one functional spec and one implementation req - [ ] YAML frontmatter is correctly formatted -- [ ] `base` and `imports` paths are correct +- [ ] `requires` and `import` paths are correct - [ ] Module file is at the repository root (not in `template/`) - [ ] Specs are language-agnostic - [ ] All external interfaces remain explicit @@ -188,12 +188,12 @@ A test plain file binds to its functionality by `module:` and `func_id:`. When a - [ ] All original functional specs are present in exactly one new module — none lost - [ ] Spec text is identical to the original — no rewording - [ ] Test plain files moved with their functionalities and rebound to the new module -- [ ] Chronological order preserved across the base chain +- [ ] Chronological order preserved across the requires chain - [ ] Every referenced `:Concept:` is available in each module (local, import, or exported) - [ ] `exported_concepts` are correctly set on each module - [ ] No concept name collisions - [ ] Each module has at least one functional spec and one implementation req -- [ ] `base` chain is correctly ordered (root → leaf) +- [ ] `requires` chain is correctly ordered (root → leaf) - [ ] Original module removed or updated - [ ] 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 diff --git a/forge/skills/resolve-section-ownership/SKILL.md b/forge/skills/resolve-section-ownership/SKILL.md index c7e5eaf..ccb8890 100644 --- a/forge/skills/resolve-section-ownership/SKILL.md +++ b/forge/skills/resolve-section-ownership/SKILL.md @@ -8,7 +8,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. This is the ownership counterpart to `break-down-func-spec`. That skill splits one functional spec into several because it is **too large**; this one moves facts between sections because they are in the **wrong place**. The two are unrelated: a correctly placed spec can be too large, and a one-line bullet can be misplaced. @@ -32,7 +32,7 @@ The bullet, section, or `.plain` file to resolve. ## Workflow -1. **Read the full `.plain` file** — every section, plus its `imports` and `base` chains, so an existing home for a fact is visible before a new one is written. +1. **Read the full `.plain` file** — every section, plus its `import` and `requires` chains, so an existing home for a fact is visible before a new one is written. 2. **Identify the target** — one bullet, one section, or the whole file. 3. **Decompose each bullet into atomic facts.** A clause joined by *and*, *which*, *so that*, or a comma often carries a second fact. Keep each fact in the author's own words for now; rewording comes at step 6. 4. **Route each fact** by the *Section ownership* table in `module-structure.md`. Where a fact resists the table, apply the question it encodes: **would a caller of the finished software observe this?** Yes → `***functional specs***`. No, it is how the result is reached → `***implementation reqs***`. It says what a term means → `***definitions***`. It shapes `:ConformanceTests:` → `***test reqs***`. diff --git a/forge/skills/resolve-spec-conflict/SKILL.md b/forge/skills/resolve-spec-conflict/SKILL.md index fcb6695..b3ed90c 100644 --- a/forge/skills/resolve-spec-conflict/SKILL.md +++ b/forge/skills/resolve-spec-conflict/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***`, an `***acceptance tests***` section in a core spec, or `requires:` / `import:` in the frontmatter. If any is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. ## When This Applies From 0a18b91cc748eb7c357eb2495c4520487490d768 Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Tue, 20 Oct 2026 14:03:02 +0200 Subject: [PATCH 20/25] Narrow the old-syntax check to bullet-shaped functional specs The signs of the old syntax are a functional specs section whose specs are - bullets with no # headings, and an acceptance tests section in a core spec. A - that is not indented under a heading is a formatting issue, fixed by indenting it. The signs are described once, in module-structure.md; each skill keeps a one-sentence pointer. Refs ENG-190 --- forge/rules/bullet-continuation.md | 4 ++-- forge/rules/glossary.md | 2 +- forge/rules/module-structure.md | 3 ++- forge/skills/add-concept/SKILL.md | 2 +- forge/skills/add-feature/SKILL.md | 2 +- forge/skills/add-functional-spec/SKILL.md | 2 +- forge/skills/add-functional-specs/SKILL.md | 2 +- forge/skills/add-implementation-requirement/SKILL.md | 2 +- forge/skills/add-resource/SKILL.md | 2 +- forge/skills/add-template/SKILL.md | 2 +- forge/skills/add-test-requirement/SKILL.md | 2 +- forge/skills/analyze-2-func-specs/SKILL.md | 2 +- forge/skills/analyze-func-specs/SKILL.md | 2 +- forge/skills/analyze-if-func-spec-too-complex/SKILL.md | 2 +- forge/skills/break-down-func-spec/SKILL.md | 2 +- forge/skills/consolidate-concepts/SKILL.md | 2 +- forge/skills/create-import-module/SKILL.md | 2 +- forge/skills/create-requires-module/SKILL.md | 2 +- forge/skills/debug-specs/SKILL.md | 2 +- forge/skills/forge-plain/SKILL.md | 2 +- forge/skills/migrate-plain-syntax/SKILL.md | 10 +++++----- forge/skills/plain-healthcheck/SKILL.md | 4 ++-- forge/skills/refactor-module/SKILL.md | 2 +- forge/skills/resolve-section-ownership/SKILL.md | 2 +- forge/skills/resolve-spec-conflict/SKILL.md | 2 +- 25 files changed, 32 insertions(+), 31 deletions(-) diff --git a/forge/rules/bullet-continuation.md b/forge/rules/bullet-continuation.md index b1e8231..f173486 100644 --- a/forge/rules/bullet-continuation.md +++ b/forge/rules/bullet-continuation.md @@ -30,8 +30,8 @@ clarification of the statement above it. There is no third, typographic reason t a syntax error. A bare continuation, by contrast, is accepted and folded into its parent item verbatim - `***functional specs***` has a different top level: each functionality is a `# ` heading, its first body line is prose, and the clarifications under it are `- ` bullets indented by two - spaces (see `func-specs.md`); a flush-left `- ` there is the old syntax. A scenario in a test plain - file has the same shape (see `test-plain-files.md`) + spaces (see `func-specs.md`). A scenario in a test plain file has the same shape (see + `test-plain-files.md`) BAD — line broken for width (bare continuation): diff --git a/forge/rules/glossary.md b/forge/rules/glossary.md index 38b70c2..7696c3f 100644 --- a/forge/rules/glossary.md +++ b/forge/rules/glossary.md @@ -18,4 +18,4 @@ These are the names used across the rules, the skills, and the parser. Use them - **Acceptance scenario** — a scenario in `***acceptance tests***`: use of the same functionality that is not expected, such as invalid parameters, wrong or missing authentication, missing data, or an unavailable dependency. It is rendered after the conformance tests of its functionality. A scenario that spans several functionalities is bound to the last one it exercises and sorted by the same rule. - **Conformance test** — generated test code that implements one conformance scenario. It runs the implementation as a black box and is labelled with the scenario-id. - **Acceptance test** — generated test code that implements one acceptance scenario, labelled with the scenario-id. -- **Old syntax** — functional specs as `- ` bullets and nested `***acceptance tests***`. Not supported; `migrate-plain-syntax` converts a project. +- **Old syntax** — functional specs written as `- ` bullets with no `# ` headings, and `***acceptance tests***` nested in a core spec. Not supported; `migrate-plain-syntax` converts a project. diff --git a/forge/rules/module-structure.md b/forge/rules/module-structure.md index 9441436..36f1d11 100644 --- a/forge/rules/module-structure.md +++ b/forge/rules/module-structure.md @@ -112,6 +112,7 @@ The :TaskList: is displayed. ``` ## Old syntax -- A core spec is in the old syntax when any of these holds: a flush-left `- ` bullet in `***functional specs***` or a `***acceptance tests***` section anywhere in the file +- A core spec is in the old syntax when either of these holds: its `***functional specs***` section has a `- ` bullet before its first `# ` heading or has no `# ` heading at all (the specs are written as bullets), or the file holds an `***acceptance tests***` section anywhere +- A `- ` under a `# ` heading that is not indented is a formatting issue, not the old syntax: indent it by two spaces - The old syntax is not supported: the renderer rejects it, and no skill edits a file written in it - When a skill meets the old syntax, it stops, tells the user which file and which marker it found, and points to the `migrate-plain-syntax` skill, which converts the whole project in one pass diff --git a/forge/skills/add-concept/SKILL.md b/forge/skills/add-concept/SKILL.md index 1d9f08a..78a631d 100644 --- a/forge/skills/add-concept/SKILL.md +++ b/forge/skills/add-concept/SKILL.md @@ -10,7 +10,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Workflow diff --git a/forge/skills/add-feature/SKILL.md b/forge/skills/add-feature/SKILL.md index 19761d4..68bada9 100644 --- a/forge/skills/add-feature/SKILL.md +++ b/forge/skills/add-feature/SKILL.md @@ -15,7 +15,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. `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 begins with a short intent phase, then runs the authoring loop scoped to one feature. diff --git a/forge/skills/add-functional-spec/SKILL.md b/forge/skills/add-functional-spec/SKILL.md index f64d7e7..6e21f85 100644 --- a/forge/skills/add-functional-spec/SKILL.md +++ b/forge/skills/add-functional-spec/SKILL.md @@ -15,7 +15,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Workflow diff --git a/forge/skills/add-functional-specs/SKILL.md b/forge/skills/add-functional-specs/SKILL.md index 8e95e54..c1562c0 100644 --- a/forge/skills/add-functional-specs/SKILL.md +++ b/forge/skills/add-functional-specs/SKILL.md @@ -14,7 +14,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## When to Use diff --git a/forge/skills/add-implementation-requirement/SKILL.md b/forge/skills/add-implementation-requirement/SKILL.md index 264336e..42a5615 100644 --- a/forge/skills/add-implementation-requirement/SKILL.md +++ b/forge/skills/add-implementation-requirement/SKILL.md @@ -12,7 +12,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Workflow diff --git a/forge/skills/add-resource/SKILL.md b/forge/skills/add-resource/SKILL.md index 794edc5..743906f 100644 --- a/forge/skills/add-resource/SKILL.md +++ b/forge/skills/add-resource/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. Linked resources are external files referenced from within a `.plain` spec using markdown link syntax. The file contents are passed to the renderer alongside the spec, providing additional context for code generation. diff --git a/forge/skills/add-template/SKILL.md b/forge/skills/add-template/SKILL.md index aa96d5e..15850b9 100644 --- a/forge/skills/add-template/SKILL.md +++ b/forge/skills/add-template/SKILL.md @@ -10,7 +10,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. Templates use Liquid syntax (`{% include %}`) to reuse the same spec content across multiple `.plain` files. Each inclusion can pass different parameters, producing tailored output from a single source. This is distinct from `import` and `requires` — templates are about textual reuse with parameterization, not module dependencies. diff --git a/forge/skills/add-test-requirement/SKILL.md b/forge/skills/add-test-requirement/SKILL.md index 099a55a..b4c223f 100644 --- a/forge/skills/add-test-requirement/SKILL.md +++ b/forge/skills/add-test-requirement/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Workflow diff --git a/forge/skills/analyze-2-func-specs/SKILL.md b/forge/skills/analyze-2-func-specs/SKILL.md index e55a9e3..ad1d42f 100644 --- a/forge/skills/analyze-2-func-specs/SKILL.md +++ b/forge/skills/analyze-2-func-specs/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Input diff --git a/forge/skills/analyze-func-specs/SKILL.md b/forge/skills/analyze-func-specs/SKILL.md index 8cbea71..89c060c 100644 --- a/forge/skills/analyze-func-specs/SKILL.md +++ b/forge/skills/analyze-func-specs/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Input 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 5a680dc..11fcd1b 100644 --- a/forge/skills/analyze-if-func-spec-too-complex/SKILL.md +++ b/forge/skills/analyze-if-func-spec-too-complex/SKILL.md @@ -12,7 +12,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Why This Matters diff --git a/forge/skills/break-down-func-spec/SKILL.md b/forge/skills/break-down-func-spec/SKILL.md index 8cb6497..34d84e1 100644 --- a/forge/skills/break-down-func-spec/SKILL.md +++ b/forge/skills/break-down-func-spec/SKILL.md @@ -10,7 +10,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## When to Use diff --git a/forge/skills/consolidate-concepts/SKILL.md b/forge/skills/consolidate-concepts/SKILL.md index 05fd13d..95ac398 100644 --- a/forge/skills/consolidate-concepts/SKILL.md +++ b/forge/skills/consolidate-concepts/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## When to Use diff --git a/forge/skills/create-import-module/SKILL.md b/forge/skills/create-import-module/SKILL.md index 8b30ec4..2778a82 100644 --- a/forge/skills/create-import-module/SKILL.md +++ b/forge/skills/create-import-module/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## What an Import Module Is diff --git a/forge/skills/create-requires-module/SKILL.md b/forge/skills/create-requires-module/SKILL.md index 2224f88..16deb70 100644 --- a/forge/skills/create-requires-module/SKILL.md +++ b/forge/skills/create-requires-module/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## What Requires Does diff --git a/forge/skills/debug-specs/SKILL.md b/forge/skills/debug-specs/SKILL.md index 9c42845..e282cb5 100644 --- a/forge/skills/debug-specs/SKILL.md +++ b/forge/skills/debug-specs/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## When to Use diff --git a/forge/skills/forge-plain/SKILL.md b/forge/skills/forge-plain/SKILL.md index 58d44b8..4201653 100644 --- a/forge/skills/forge-plain/SKILL.md +++ b/forge/skills/forge-plain/SKILL.md @@ -14,7 +14,7 @@ description: >- 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. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Role diff --git a/forge/skills/migrate-plain-syntax/SKILL.md b/forge/skills/migrate-plain-syntax/SKILL.md index f16980b..923199b 100644 --- a/forge/skills/migrate-plain-syntax/SKILL.md +++ b/forge/skills/migrate-plain-syntax/SKILL.md @@ -17,7 +17,7 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule | Old syntax | Current syntax | |---|---| -| A functional spec is a flush-left `- ` bullet with nested ` - ` sub-bullets | A functional spec is a `# ` heading, a statement line, and indented `- ` clarification bullets | +| A functional spec is a top-level `- ` bullet with nested ` - ` sub-bullets | A functional spec is a `# ` heading, a statement line, and indented `- ` clarification bullets | | `***acceptance tests***` nested under a functional spec | `***acceptance tests***` section of the functionality's test plain file `tests//.plain` | | The renderer invents conformance tests | `***conformance tests***` section of the test plain file holds at least one scenario per functionality | @@ -34,7 +34,7 @@ There is no backward compatibility: a project in the old syntax does not render, ### 1. Inventory and confirm 1. List every `.plain` file in the project. Follow the `requires` and `import` chains from each root module so no module is missed. -2. For each file, record which old-syntax markers it holds: flush-left `- ` bullets in `***functional specs***`, nested `***acceptance tests***` sections. +2. For each file, record which signs of the old syntax it holds: a `***functional specs***` section whose specs are `- ` bullets with no `# ` headings, nested `***acceptance tests***` sections. 3. Read every `config.yaml` to learn whether conformance tests are enabled for each module (a `conformance-tests-script` entry). 4. Show the user the inventory (files, markers, conformance on or off per module) and get explicit confirmation before writing anything. @@ -42,7 +42,7 @@ There is no backward compatibility: a project in the old syntax does not render, For each root module, walk the `***functional specs***` section top to bottom: -1. For every flush-left `- ` bullet, propose a func-id per `semantic-ids.md`: a short slug that names the behavior (`add-task`, `export-csv`), unique in the module. Show the module's full list of proposed ids to the user in one `AskUserQuestion` and apply their edits. +1. For every top-level `- ` bullet (a functional spec written as a bullet), propose a func-id per `semantic-ids.md`: a short slug that names the behavior (`add-task`, `export-csv`), unique in the module. Show the module's full list of proposed ids to the user in one `AskUserQuestion` and apply their edits. 2. Rewrite each functionality in place: - The `# ` heading on its own line. - The bullet text, without the leading `- `, as the statement line. A bare continuation line of the bullet is de-indented and kept as part of the statement. @@ -70,11 +70,11 @@ When conformance tests are not enabled for a module, skip this step and say so; ### 5. Verify -1. No `***functional specs***` section holds a flush-left `- ` bullet; every functionality starts with a `# ` heading. +1. No `***functional specs***` section holds a `- ` bullet before its first `# ` heading; every functionality starts with a `# ` heading. 2. No core spec holds an `***acceptance tests***` section. 3. Func-ids are unique per module; scenario-ids are unique per functionality. 4. Every test plain file binds to an existing functionality; when conformance tests are enabled, every functionality has at least one conformance scenario. -5. The number of functionalities per module equals the number of flush-left bullets before the migration, and every old acceptance test appears once as a scenario, in the section its content decides. +5. The number of functionalities per module equals the number of top-level bullets before the migration, and every old acceptance test appears once as a scenario, in the section its content decides. 6. Run `plain-healthcheck`. Until the plain-parser release that parses the current syntax is installed, its parser step reports that it is suspended; the other checks still run. ### 6. Report diff --git a/forge/skills/plain-healthcheck/SKILL.md b/forge/skills/plain-healthcheck/SKILL.md index 53229cd..44d697b 100644 --- a/forge/skills/plain-healthcheck/SKILL.md +++ b/forge/skills/plain-healthcheck/SKILL.md @@ -108,10 +108,10 @@ Step 3 has two halves: a mechanical parse by `plain-parser`, then the checks tha Check every `.plain` module against the rules loaded by `load-plain-reference`: 1. **Section ownership.** No fact sits in a section that doesn't own it — `:UnitTests:` facts only in `***implementation reqs***`, `:ConformanceTests:` facts only in `***test reqs***`, technology only in `***implementation reqs***`. -2. **Bullets and headings.** Every line in `***definitions***`, `***implementation reqs***`, and `***test reqs***` is a valid `- ` list item or a correctly indented sub-bullet — no bare continuation lines. Every functionality in `***functional specs***` is a `# ` heading followed by its body, with its `- ` clarifications indented by two spaces. +2. **Bullets and headings.** Every line in `***definitions***`, `***implementation reqs***`, and `***test reqs***` is a valid `- ` list item or a correctly indented sub-bullet — no bare continuation lines. Every functionality in `***functional specs***` is a `# ` heading followed by its body, with its `- ` clarifications indented by two spaces; a `- ` under a heading that is not indented is fixed by indenting it. 3. **Linked resources.** Every linked resource is a single **text** file (not a binary), and no URL or folder path appears in the surrounding spec text unless it is a runtime value the generated software itself uses. 4. **Test plain files.** Every test plain file binds to an existing functionality (`module:` names a module, `func_id:` one of its `# func-id` headings) and has both sections in order. Scenario-ids are unique per functionality across every file bound to it. When the module's `config.yaml` has a `conformance-tests-script`, every functionality has at least one conformance scenario; a functionality with none is a failure that names `add-test-scenarios`. A scenario under `***conformance tests***` that describes use that is not expected (invalid parameters, wrong or missing authentication, missing data), or one under `***acceptance tests***` that describes the expected use, is a failure that names `add-test-scenarios`. -5. **Old syntax.** A flush-left `- ` bullet in `***functional specs***`, or an `***acceptance tests***` section in a core spec is a failure that names `migrate-plain-syntax`. Do not fix it inline. +5. **Old syntax.** A `***functional specs***` section that has a `- ` bullet before its first `# ` heading or no `# ` heading at all, or an `***acceptance tests***` section in a core spec, is a failure that names `migrate-plain-syntax`. Do not fix it inline. Functional-spec complexity is checked when a spec is authored (`add-functional-spec(s)` → `analyze-if-func-spec-too-complex`). Re-run `analyze-if-func-spec-too-complex` here only for a spec that was hand-edited since it was authored. diff --git a/forge/skills/refactor-module/SKILL.md b/forge/skills/refactor-module/SKILL.md index 859f97d..a260f6f 100644 --- a/forge/skills/refactor-module/SKILL.md +++ b/forge/skills/refactor-module/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## When to Use diff --git a/forge/skills/resolve-section-ownership/SKILL.md b/forge/skills/resolve-section-ownership/SKILL.md index ccb8890..fe2a144 100644 --- a/forge/skills/resolve-section-ownership/SKILL.md +++ b/forge/skills/resolve-section-ownership/SKILL.md @@ -8,7 +8,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. This is the ownership counterpart to `break-down-func-spec`. That skill splits one functional spec into several because it is **too large**; this one moves facts between sections because they are in the **wrong place**. The two are unrelated: a correctly placed spec can be too large, and a one-line bullet can be misplaced. diff --git a/forge/skills/resolve-spec-conflict/SKILL.md b/forge/skills/resolve-spec-conflict/SKILL.md index b3ed90c..aacd8c0 100644 --- a/forge/skills/resolve-spec-conflict/SKILL.md +++ b/forge/skills/resolve-spec-conflict/SKILL.md @@ -11,7 +11,7 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -Before reading or editing any `.plain` file, check it for the old syntax (see *Old syntax* in `module-structure.md`): a flush-left `- ` bullet in `***functional specs***` or an `***acceptance tests***` section in a core spec. If either is present, stop, name the file and the marker, and point the user to `migrate-plain-syntax`; do not edit a file in the old syntax. +If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## When This Applies From 9773ac55f7538d05f6a41b8ed8745c3b53c6d4fc Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Tue, 20 Oct 2026 14:03:29 +0200 Subject: [PATCH 21/25] Say sign, not marker, in the old-syntax rule --- forge/rules/module-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge/rules/module-structure.md b/forge/rules/module-structure.md index 36f1d11..1f9e2bb 100644 --- a/forge/rules/module-structure.md +++ b/forge/rules/module-structure.md @@ -115,4 +115,4 @@ The :TaskList: is displayed. - A core spec is in the old syntax when either of these holds: its `***functional specs***` section has a `- ` bullet before its first `# ` heading or has no `# ` heading at all (the specs are written as bullets), or the file holds an `***acceptance tests***` section anywhere - A `- ` under a `# ` heading that is not indented is a formatting issue, not the old syntax: indent it by two spaces - The old syntax is not supported: the renderer rejects it, and no skill edits a file written in it -- When a skill meets the old syntax, it stops, tells the user which file and which marker it found, and points to the `migrate-plain-syntax` skill, which converts the whole project in one pass +- When a skill meets the old syntax, it stops, tells the user which file and which sign it found, and points to the `migrate-plain-syntax` skill, which converts the whole project in one pass From b1ca4eb0ba31218e687ca56d21393ced26b27a7b Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Tue, 20 Oct 2026 14:03:38 +0200 Subject: [PATCH 22/25] Say signs of the old syntax in the migration inventory --- forge/skills/migrate-plain-syntax/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge/skills/migrate-plain-syntax/SKILL.md b/forge/skills/migrate-plain-syntax/SKILL.md index 923199b..765f3f7 100644 --- a/forge/skills/migrate-plain-syntax/SKILL.md +++ b/forge/skills/migrate-plain-syntax/SKILL.md @@ -36,7 +36,7 @@ There is no backward compatibility: a project in the old syntax does not render, 1. List every `.plain` file in the project. Follow the `requires` and `import` chains from each root module so no module is missed. 2. For each file, record which signs of the old syntax it holds: a `***functional specs***` section whose specs are `- ` bullets with no `# ` headings, nested `***acceptance tests***` sections. 3. Read every `config.yaml` to learn whether conformance tests are enabled for each module (a `conformance-tests-script` entry). -4. Show the user the inventory (files, markers, conformance on or off per module) and get explicit confirmation before writing anything. +4. Show the user the inventory (files, signs of the old syntax, conformance on or off per module) and get explicit confirmation before writing anything. ### 2. Give every functionality a func-id From f8773e948842114df6c896b1086c27f88d3a537b Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Wed, 21 Oct 2026 09:50:52 +0200 Subject: [PATCH 23/25] Drop the glossary and most old-syntax pointers The terms live in semantic-ids.md and test-plain-files.md; the test plain file example lives in add-test-scenarios. The nested acceptance tests section is named first among the signs of the old syntax, and the pointer to migrate-plain-syntax stays only in the skills that start from an existing project: add-feature, debug-specs and refactor-module. Refs ENG-190 --- AGENTS.md | 2 +- CLAUDE.md | 2 +- README.md | 2 +- forge/rules/glossary.md | 21 --------------- forge/rules/module-structure.md | 3 +-- forge/rules/test-plain-files.md | 26 +------------------ forge/skills/add-concept/SKILL.md | 1 - forge/skills/add-functional-spec/SKILL.md | 1 - forge/skills/add-functional-specs/SKILL.md | 1 - .../add-implementation-requirement/SKILL.md | 1 - forge/skills/add-resource/SKILL.md | 1 - forge/skills/add-template/SKILL.md | 1 - forge/skills/add-test-requirement/SKILL.md | 1 - forge/skills/analyze-2-func-specs/SKILL.md | 1 - forge/skills/analyze-func-specs/SKILL.md | 1 - .../analyze-if-func-spec-too-complex/SKILL.md | 1 - forge/skills/break-down-func-spec/SKILL.md | 1 - forge/skills/consolidate-concepts/SKILL.md | 1 - forge/skills/create-import-module/SKILL.md | 1 - forge/skills/create-requires-module/SKILL.md | 1 - forge/skills/forge-plain/SKILL.md | 1 - forge/skills/load-plain-reference/SKILL.md | 6 ++--- forge/skills/migrate-plain-syntax/SKILL.md | 4 +-- forge/skills/plain-healthcheck/SKILL.md | 2 +- .../skills/resolve-section-ownership/SKILL.md | 1 - forge/skills/resolve-spec-conflict/SKILL.md | 1 - 26 files changed, 11 insertions(+), 74 deletions(-) delete mode 100644 forge/rules/glossary.md diff --git a/AGENTS.md b/AGENTS.md index d43d31f..70991a1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -74,7 +74,7 @@ Other constraints the rules enforce (see `forge/rules/`): functional specs are m ### The skill lifecycle (orchestration) -`forge-plain` is the top-level orchestrator: a short Phase 0 intent interview followed by four gated phases — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, a test plain file with conformance scenarios per functionality via `add-test-scenarios`, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (config and script checks, `plain-parser check` on every top module, plus rule checks the parser can't do) then hand off the render target. Phases 1–3 are **one-question-at-a-time, write-to-disk-immediately**. `add-feature` is the same authoring loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold; `migrate-plain-syntax` converts a project from the old syntax (`- ` functional specs, nested acceptance tests), which no other skill edits. The installer ships `forge/rules/*.md` beside the skills; native rule consumers load them directly, while other agents reach them through `load-plain-reference`. +`forge-plain` is the top-level orchestrator: a short Phase 0 intent interview followed by four gated phases — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, a test plain file with conformance scenarios per functionality via `add-test-scenarios`, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (config and script checks, `plain-parser check` on every top module, plus rule checks the parser can't do) then hand off the render target. Phases 1–3 are **one-question-at-a-time, write-to-disk-immediately**. `add-feature` is the same authoring loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold; `migrate-plain-syntax` converts a project from the old syntax (nested acceptance tests, `- ` functional specs), which no other skill edits. The installer ships `forge/rules/*.md` beside the skills; native rule consumers load them directly, while other agents reach them through `load-plain-reference`. ## Memory / persistent context diff --git a/CLAUDE.md b/CLAUDE.md index d43d31f..70991a1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,7 +74,7 @@ Other constraints the rules enforce (see `forge/rules/`): functional specs are m ### The skill lifecycle (orchestration) -`forge-plain` is the top-level orchestrator: a short Phase 0 intent interview followed by four gated phases — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, a test plain file with conformance scenarios per functionality via `add-test-scenarios`, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (config and script checks, `plain-parser check` on every top module, plus rule checks the parser can't do) then hand off the render target. Phases 1–3 are **one-question-at-a-time, write-to-disk-immediately**. `add-feature` is the same authoring loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold; `migrate-plain-syntax` converts a project from the old syntax (`- ` functional specs, nested acceptance tests), which no other skill edits. The installer ships `forge/rules/*.md` beside the skills; native rule consumers load them directly, while other agents reach them through `load-plain-reference`. +`forge-plain` is the top-level orchestrator: a short Phase 0 intent interview followed by four gated phases — (1) definitions + functional specs, (2) implementation reqs / tech stack, (3) testing (unit→impl reqs, conformance→test reqs, a test plain file with conformance scenarios per functionality via `add-test-scenarios`, generate `test_scripts/`, build `config.yaml`, probe host via `check-plain-env`), (4) validate via `plain-healthcheck` (config and script checks, `plain-parser check` on every top module, plus rule checks the parser can't do) then hand off the render target. Phases 1–3 are **one-question-at-a-time, write-to-disk-immediately**. `add-feature` is the same authoring loop scoped to one feature on an existing project; `init-plain-project` is a no-interview scaffold; `migrate-plain-syntax` converts a project from the old syntax (nested acceptance tests, `- ` functional specs), which no other skill edits. The installer ships `forge/rules/*.md` beside the skills; native rule consumers load them directly, while other agents reach them through `load-plain-reference`. ## Memory / persistent context diff --git a/README.md b/README.md index 34f2701..be3b356 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Pick whichever entry point matches how much upfront design you want: ### 3. Validate and maintain - **`plain-healthcheck`** — the verification gate. Inventories every `.plain` module, validates every `config.yaml`, parses every top module with [`plain-parser`](https://github.com/plainlang/plain-parser), and checks every module against the ***plain rules. Returns `PASS` / `FAIL` with a numbered punch-list when something is broken. -- **`migrate-plain-syntax`** — converts a project from the old syntax (`- ` functional specs, nested acceptance tests) to `# func-id` headings and test plain files under `tests/`. Run it when a skill or `plain-parser` reports the old syntax. +- **`migrate-plain-syntax`** — converts a project from the old syntax (nested acceptance tests, `- ` functional specs) to `# func-id` headings and test plain files under `tests/`. Run it when a skill or `plain-parser` reports the old syntax. - **`init-config-file`** — assembles the canonical `config.yaml` per part of a project from the decisions made during interviewing. - **`check-plain-env`** — probes the host machine for everything the project needs (language toolchains, external services, system binaries, drivers) and emits a `PASS` / `WARN` / `FAIL` report with OS-specific install commands. - **`analyze-func-specs`** / **`analyze-if-func-spec-too-complex`** / **`break-down-func-spec`** / **`resolve-spec-conflict`** — the spec-quality toolchain that runs behind `add-feature` and `forge-plain` but can also be invoked directly when reviewing or refactoring. diff --git a/forge/rules/glossary.md b/forge/rules/glossary.md deleted file mode 100644 index 7696c3f..0000000 --- a/forge/rules/glossary.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -description: Terms used in .plain files, in plain-forge, and in plain-parser; use them as written ---- - -# Glossary - -These are the names used across the rules, the skills, and the parser. Use them as written; do not coin synonyms. - -- **Core spec** — a `.plain` file with functional specs, a root module. Test definitions do not live here. -- **Import module** — a `.plain` file in the template directory that other modules pull in with `import:`; definitions and reqs only (see `import-modules.md`). -- **Requires module** — a root module that another root module names under `requires:` to build on its generated code and functional specs (see `requires-modules.md`). -- **Functionality** — one functional spec in a module, identified by its func-id. -- **Func-id** — the slug in the `# ` heading of a functionality. Lowercase letters, digits, and hyphens; unique in the module; written as `:` when the module is needed. It needs no meaning (see `semantic-ids.md`). -- **Test plain file** — a `.plain` file that holds the scenarios of one functionality. Its frontmatter keys `module:` and `func_id:` bind it to that functionality; it has a `***conformance tests***` and an `***acceptance tests***` section. By convention it lives at `tests//.plain`. Several may bind to one functionality; their scenarios are merged (see `test-plain-files.md`). -- **Scenario** — one test case in a test plain file: a `# ` heading followed by free text that says what is done and what result is expected. plain-forge writes plain English by default; `given:` / `when:` / `then:` is allowed. The renderer never writes a scenario. -- **Scenario-id** — the slug in a scenario heading. Same syntax as a func-id; unique across the scenarios of one functionality after merging. The generated test carries it. -- **Conformance scenario** — a scenario in `***conformance tests***`: the expected use of the functionality, its main path, with valid parameters, the expected caller, and the data present. The renderer implements every scenario of the test plain file and nothing else. A functionality is done only when all of its conformance tests pass. When conformance tests are enabled, every functionality has at least one. -- **Acceptance scenario** — a scenario in `***acceptance tests***`: use of the same functionality that is not expected, such as invalid parameters, wrong or missing authentication, missing data, or an unavailable dependency. It is rendered after the conformance tests of its functionality. A scenario that spans several functionalities is bound to the last one it exercises and sorted by the same rule. -- **Conformance test** — generated test code that implements one conformance scenario. It runs the implementation as a black box and is labelled with the scenario-id. -- **Acceptance test** — generated test code that implements one acceptance scenario, labelled with the scenario-id. -- **Old syntax** — functional specs written as `- ` bullets with no `# ` headings, and `***acceptance tests***` nested in a core spec. Not supported; `migrate-plain-syntax` converts a project. diff --git a/forge/rules/module-structure.md b/forge/rules/module-structure.md index 1f9e2bb..c71a7e1 100644 --- a/forge/rules/module-structure.md +++ b/forge/rules/module-structure.md @@ -112,7 +112,6 @@ The :TaskList: is displayed. ``` ## Old syntax -- A core spec is in the old syntax when either of these holds: its `***functional specs***` section has a `- ` bullet before its first `# ` heading or has no `# ` heading at all (the specs are written as bullets), or the file holds an `***acceptance tests***` section anywhere -- A `- ` under a `# ` heading that is not indented is a formatting issue, not the old syntax: indent it by two spaces +- A core spec is in the old syntax when it holds an `***acceptance tests***` section (nested under a functional spec), or when its `***functional specs***` are written as `- ` bullets with no `# ` headings - The old syntax is not supported: the renderer rejects it, and no skill edits a file written in it - When a skill meets the old syntax, it stops, tells the user which file and which sign it found, and points to the `migrate-plain-syntax` skill, which converts the whole project in one pass diff --git a/forge/rules/test-plain-files.md b/forge/rules/test-plain-files.md index 2c25ae1..9dc21c4 100644 --- a/forge/rules/test-plain-files.md +++ b/forge/rules/test-plain-files.md @@ -4,7 +4,7 @@ description: Syntax and authoring rules for test plain files, which hold the con # Rules for test plain files -A test plain file holds the test scenarios of one functionality. The renderer generates conformance and acceptance tests from these scenarios and from nothing else: it never invents a scenario. Scenarios are written by people or by plain-forge at authoring time and are reviewed like any other spec line. Always follow these rules when creating or editing one. +A test plain file holds the test scenarios of one functionality. Conformance scenarios test the core functionality, its expected use; acceptance scenarios catch the edge cases, the use that is not expected. The renderer generates conformance and acceptance tests from these scenarios and from nothing else: it never invents a scenario. Scenarios are written by people or by plain-forge at authoring time and are reviewed like any other spec line. Always follow these rules when creating or editing one. ## File skeleton - A test plain file is a `.plain` file with YAML frontmatter and two sections, in this order: `***conformance tests***`, then `***acceptance tests***` @@ -68,27 +68,3 @@ A test plain file holds the test scenarios of one functionality. The renderer ge ## Relation to `***test reqs***` - `***test reqs***` of the module say how `:ConformanceTests:` are built and run (framework, command, network policy); the scenarios say what they check - A fact about the framework or the run command never goes into a scenario, and a scenario never goes into `***test reqs***` - -## Format - -```plain ---- -module: task-manager -func_id: add-task ---- - -***conformance tests*** - -# add-valid-task -Adding a :Task: named "Buy milk" to an empty :TaskList: shows one :Task: named "Buy milk". - -# add-second-task -Adding a :Task: named "Call mom" to a :TaskList: that holds "Buy milk" shows two :Task: items, "Buy milk" first. - -***acceptance tests*** - -# reject-short-name -Adding a :Task: named "Go" to an empty :TaskList: is rejected. - - :App: shows the error "Name must be at least 3 characters". - - :TaskList: stays empty. -``` diff --git a/forge/skills/add-concept/SKILL.md b/forge/skills/add-concept/SKILL.md index 78a631d..31314cc 100644 --- a/forge/skills/add-concept/SKILL.md +++ b/forge/skills/add-concept/SKILL.md @@ -10,7 +10,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Workflow diff --git a/forge/skills/add-functional-spec/SKILL.md b/forge/skills/add-functional-spec/SKILL.md index 6e21f85..1271522 100644 --- a/forge/skills/add-functional-spec/SKILL.md +++ b/forge/skills/add-functional-spec/SKILL.md @@ -15,7 +15,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Workflow diff --git a/forge/skills/add-functional-specs/SKILL.md b/forge/skills/add-functional-specs/SKILL.md index c1562c0..ceafe96 100644 --- a/forge/skills/add-functional-specs/SKILL.md +++ b/forge/skills/add-functional-specs/SKILL.md @@ -14,7 +14,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## When to Use diff --git a/forge/skills/add-implementation-requirement/SKILL.md b/forge/skills/add-implementation-requirement/SKILL.md index 42a5615..aa986e8 100644 --- a/forge/skills/add-implementation-requirement/SKILL.md +++ b/forge/skills/add-implementation-requirement/SKILL.md @@ -12,7 +12,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Workflow diff --git a/forge/skills/add-resource/SKILL.md b/forge/skills/add-resource/SKILL.md index 743906f..899a227 100644 --- a/forge/skills/add-resource/SKILL.md +++ b/forge/skills/add-resource/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. Linked resources are external files referenced from within a `.plain` spec using markdown link syntax. The file contents are passed to the renderer alongside the spec, providing additional context for code generation. diff --git a/forge/skills/add-template/SKILL.md b/forge/skills/add-template/SKILL.md index 15850b9..00fc50b 100644 --- a/forge/skills/add-template/SKILL.md +++ b/forge/skills/add-template/SKILL.md @@ -10,7 +10,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. Templates use Liquid syntax (`{% include %}`) to reuse the same spec content across multiple `.plain` files. Each inclusion can pass different parameters, producing tailored output from a single source. This is distinct from `import` and `requires` — templates are about textual reuse with parameterization, not module dependencies. diff --git a/forge/skills/add-test-requirement/SKILL.md b/forge/skills/add-test-requirement/SKILL.md index b4c223f..56b565f 100644 --- a/forge/skills/add-test-requirement/SKILL.md +++ b/forge/skills/add-test-requirement/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Workflow diff --git a/forge/skills/analyze-2-func-specs/SKILL.md b/forge/skills/analyze-2-func-specs/SKILL.md index ad1d42f..e010f14 100644 --- a/forge/skills/analyze-2-func-specs/SKILL.md +++ b/forge/skills/analyze-2-func-specs/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Input diff --git a/forge/skills/analyze-func-specs/SKILL.md b/forge/skills/analyze-func-specs/SKILL.md index 89c060c..46e2816 100644 --- a/forge/skills/analyze-func-specs/SKILL.md +++ b/forge/skills/analyze-func-specs/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Input 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 11fcd1b..6dcdebb 100644 --- a/forge/skills/analyze-if-func-spec-too-complex/SKILL.md +++ b/forge/skills/analyze-if-func-spec-too-complex/SKILL.md @@ -12,7 +12,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Why This Matters diff --git a/forge/skills/break-down-func-spec/SKILL.md b/forge/skills/break-down-func-spec/SKILL.md index 34d84e1..2036446 100644 --- a/forge/skills/break-down-func-spec/SKILL.md +++ b/forge/skills/break-down-func-spec/SKILL.md @@ -10,7 +10,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## When to Use diff --git a/forge/skills/consolidate-concepts/SKILL.md b/forge/skills/consolidate-concepts/SKILL.md index 95ac398..b294374 100644 --- a/forge/skills/consolidate-concepts/SKILL.md +++ b/forge/skills/consolidate-concepts/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## When to Use diff --git a/forge/skills/create-import-module/SKILL.md b/forge/skills/create-import-module/SKILL.md index 2778a82..069fc83 100644 --- a/forge/skills/create-import-module/SKILL.md +++ b/forge/skills/create-import-module/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## What an Import Module Is diff --git a/forge/skills/create-requires-module/SKILL.md b/forge/skills/create-requires-module/SKILL.md index 16deb70..a86e12d 100644 --- a/forge/skills/create-requires-module/SKILL.md +++ b/forge/skills/create-requires-module/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## What Requires Does diff --git a/forge/skills/forge-plain/SKILL.md b/forge/skills/forge-plain/SKILL.md index 4201653..ac95c7b 100644 --- a/forge/skills/forge-plain/SKILL.md +++ b/forge/skills/forge-plain/SKILL.md @@ -14,7 +14,6 @@ description: >- 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. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## Role diff --git a/forge/skills/load-plain-reference/SKILL.md b/forge/skills/load-plain-reference/SKILL.md index a2a6aed..beda63c 100644 --- a/forge/skills/load-plain-reference/SKILL.md +++ b/forge/skills/load-plain-reference/SKILL.md @@ -2,7 +2,7 @@ name: load-plain-reference description: >- Loads the applicable ***plain authoring rules and operational references for the current task. - Covers section ownership, concepts, semantic IDs, test plain files, modules, resources, integrations, rendering, and testing + Covers section ownership, concepts, func-ids, test plain files, modules, resources, integrations, rendering, and testing without loading unrelated guidance. Use when authoring, editing, reviewing, or debugging .plain files, or before invoking another skill that reads or writes .plain content. --- @@ -51,9 +51,9 @@ All paths are relative to this `SKILL.md`. | `required_concepts` | `../../rules/required-concepts.md` | | `exported_concepts` | `../../rules/exported-concepts.md` | | Linked files or `resources/` | `../../rules/linked-resources.md` | -| Any `.plain` text or example | `../../rules/glossary.md`, `../../rules/bullet-continuation.md`, `../../rules/concise-specs.md` | +| Any `.plain` text or example | `../../rules/bullet-continuation.md`, `../../rules/concise-specs.md` | | Drafting or trimming spec wording, or reviewing a spec for verbosity | `../../rules/concise-specs-examples.md` | -| A file that may be in the old syntax (`- ` functional specs, nested `***acceptance tests***`) | `../../rules/module-structure.md` (*Old syntax*), then the `migrate-plain-syntax` skill | +| A file that may be in the old syntax (nested `***acceptance tests***`, `- ` functional specs) | `../../rules/module-structure.md` (*Old syntax*), then the `migrate-plain-syntax` skill | For a whole-file review or a task spanning several sections, load the union of the applicable rule files. Do not load every rule by default. diff --git a/forge/skills/migrate-plain-syntax/SKILL.md b/forge/skills/migrate-plain-syntax/SKILL.md index 765f3f7..09f2f61 100644 --- a/forge/skills/migrate-plain-syntax/SKILL.md +++ b/forge/skills/migrate-plain-syntax/SKILL.md @@ -17,8 +17,8 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule | Old syntax | Current syntax | |---|---| -| A functional spec is a top-level `- ` bullet with nested ` - ` sub-bullets | A functional spec is a `# ` heading, a statement line, and indented `- ` clarification bullets | | `***acceptance tests***` nested under a functional spec | `***acceptance tests***` section of the functionality's test plain file `tests//.plain` | +| A functional spec is a top-level `- ` bullet with nested ` - ` sub-bullets | A functional spec is a `# ` heading, a statement line, and indented `- ` clarification bullets | | The renderer invents conformance tests | `***conformance tests***` section of the test plain file holds at least one scenario per functionality | There is no backward compatibility: a project in the old syntax does not render, and no other skill edits it. The migration converts the whole project in one pass. @@ -34,7 +34,7 @@ There is no backward compatibility: a project in the old syntax does not render, ### 1. Inventory and confirm 1. List every `.plain` file in the project. Follow the `requires` and `import` chains from each root module so no module is missed. -2. For each file, record which signs of the old syntax it holds: a `***functional specs***` section whose specs are `- ` bullets with no `# ` headings, nested `***acceptance tests***` sections. +2. For each file, record which signs of the old syntax it holds: nested `***acceptance tests***` sections, a `***functional specs***` section whose specs are `- ` bullets with no `# ` headings. 3. Read every `config.yaml` to learn whether conformance tests are enabled for each module (a `conformance-tests-script` entry). 4. Show the user the inventory (files, signs of the old syntax, conformance on or off per module) and get explicit confirmation before writing anything. diff --git a/forge/skills/plain-healthcheck/SKILL.md b/forge/skills/plain-healthcheck/SKILL.md index 44d697b..63df5fa 100644 --- a/forge/skills/plain-healthcheck/SKILL.md +++ b/forge/skills/plain-healthcheck/SKILL.md @@ -111,7 +111,7 @@ Check every `.plain` module against the rules loaded by `load-plain-reference`: 2. **Bullets and headings.** Every line in `***definitions***`, `***implementation reqs***`, and `***test reqs***` is a valid `- ` list item or a correctly indented sub-bullet — no bare continuation lines. Every functionality in `***functional specs***` is a `# ` heading followed by its body, with its `- ` clarifications indented by two spaces; a `- ` under a heading that is not indented is fixed by indenting it. 3. **Linked resources.** Every linked resource is a single **text** file (not a binary), and no URL or folder path appears in the surrounding spec text unless it is a runtime value the generated software itself uses. 4. **Test plain files.** Every test plain file binds to an existing functionality (`module:` names a module, `func_id:` one of its `# func-id` headings) and has both sections in order. Scenario-ids are unique per functionality across every file bound to it. When the module's `config.yaml` has a `conformance-tests-script`, every functionality has at least one conformance scenario; a functionality with none is a failure that names `add-test-scenarios`. A scenario under `***conformance tests***` that describes use that is not expected (invalid parameters, wrong or missing authentication, missing data), or one under `***acceptance tests***` that describes the expected use, is a failure that names `add-test-scenarios`. -5. **Old syntax.** A `***functional specs***` section that has a `- ` bullet before its first `# ` heading or no `# ` heading at all, or an `***acceptance tests***` section in a core spec, is a failure that names `migrate-plain-syntax`. Do not fix it inline. +5. **Old syntax.** An `***acceptance tests***` section in a core spec, or a `***functional specs***` section written as `- ` bullets with no `# ` headings, is a failure that names `migrate-plain-syntax`. Do not fix it inline. Functional-spec complexity is checked when a spec is authored (`add-functional-spec(s)` → `analyze-if-func-spec-too-complex`). Re-run `analyze-if-func-spec-too-complex` here only for a spec that was hand-edited since it was authored. diff --git a/forge/skills/resolve-section-ownership/SKILL.md b/forge/skills/resolve-section-ownership/SKILL.md index fe2a144..31a9a84 100644 --- a/forge/skills/resolve-section-ownership/SKILL.md +++ b/forge/skills/resolve-section-ownership/SKILL.md @@ -8,7 +8,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. This is the ownership counterpart to `break-down-func-spec`. That skill splits one functional spec into several because it is **too large**; this one moves facts between sections because they are in the **wrong place**. The two are unrelated: a correctly placed spec can be too large, and a one-line bullet can be misplaced. diff --git a/forge/skills/resolve-spec-conflict/SKILL.md b/forge/skills/resolve-spec-conflict/SKILL.md index aacd8c0..86276e3 100644 --- a/forge/skills/resolve-spec-conflict/SKILL.md +++ b/forge/skills/resolve-spec-conflict/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. -If a target `.plain` file is in the old syntax (see *Old syntax* in `module-structure.md`), stop, name the file, and point the user to `migrate-plain-syntax`; do not edit it. ## When This Applies From e3d49ab26b8072a45ca30cb193bd16b870bf1b35 Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Wed, 21 Oct 2026 09:51:05 +0200 Subject: [PATCH 24/25] Collapse the blank lines left by the removed pointers --- forge/skills/add-concept/SKILL.md | 1 - forge/skills/add-functional-spec/SKILL.md | 1 - forge/skills/add-functional-specs/SKILL.md | 1 - forge/skills/add-implementation-requirement/SKILL.md | 1 - forge/skills/add-resource/SKILL.md | 1 - forge/skills/add-template/SKILL.md | 1 - forge/skills/add-test-requirement/SKILL.md | 1 - forge/skills/analyze-2-func-specs/SKILL.md | 1 - forge/skills/analyze-func-specs/SKILL.md | 1 - forge/skills/analyze-if-func-spec-too-complex/SKILL.md | 1 - forge/skills/break-down-func-spec/SKILL.md | 1 - forge/skills/consolidate-concepts/SKILL.md | 1 - forge/skills/create-import-module/SKILL.md | 1 - forge/skills/create-requires-module/SKILL.md | 1 - forge/skills/forge-plain/SKILL.md | 1 - forge/skills/resolve-section-ownership/SKILL.md | 1 - forge/skills/resolve-spec-conflict/SKILL.md | 1 - 17 files changed, 17 deletions(-) diff --git a/forge/skills/add-concept/SKILL.md b/forge/skills/add-concept/SKILL.md index 31314cc..fceacf9 100644 --- a/forge/skills/add-concept/SKILL.md +++ b/forge/skills/add-concept/SKILL.md @@ -10,7 +10,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - ## Workflow 1. **Identify the target `.plain` file.** If ambiguous, ask the user. diff --git a/forge/skills/add-functional-spec/SKILL.md b/forge/skills/add-functional-spec/SKILL.md index 1271522..4a0b905 100644 --- a/forge/skills/add-functional-spec/SKILL.md +++ b/forge/skills/add-functional-spec/SKILL.md @@ -15,7 +15,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - ## Workflow 1. **Identify the target `.plain` file.** If ambiguous, ask the user. diff --git a/forge/skills/add-functional-specs/SKILL.md b/forge/skills/add-functional-specs/SKILL.md index ceafe96..0274163 100644 --- a/forge/skills/add-functional-specs/SKILL.md +++ b/forge/skills/add-functional-specs/SKILL.md @@ -14,7 +14,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - ## When to Use - The user asks for several functional specs to be added in one go (e.g. "add these three specs", "spec out this feature in a few bullets"). diff --git a/forge/skills/add-implementation-requirement/SKILL.md b/forge/skills/add-implementation-requirement/SKILL.md index aa986e8..60a65fd 100644 --- a/forge/skills/add-implementation-requirement/SKILL.md +++ b/forge/skills/add-implementation-requirement/SKILL.md @@ -12,7 +12,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - ## Workflow 1. **Identify the target `.plain` file.** If ambiguous, ask the user. diff --git a/forge/skills/add-resource/SKILL.md b/forge/skills/add-resource/SKILL.md index 899a227..983ffda 100644 --- a/forge/skills/add-resource/SKILL.md +++ b/forge/skills/add-resource/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - Linked resources are external files referenced from within a `.plain` spec using markdown link syntax. The file contents are passed to the renderer alongside the spec, providing additional context for code generation. ## Workflow diff --git a/forge/skills/add-template/SKILL.md b/forge/skills/add-template/SKILL.md index 00fc50b..058c46f 100644 --- a/forge/skills/add-template/SKILL.md +++ b/forge/skills/add-template/SKILL.md @@ -10,7 +10,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - Templates use Liquid syntax (`{% include %}`) to reuse the same spec content across multiple `.plain` files. Each inclusion can pass different parameters, producing tailored output from a single source. This is distinct from `import` and `requires` — templates are about textual reuse with parameterization, not module dependencies. ## When to Use Templates diff --git a/forge/skills/add-test-requirement/SKILL.md b/forge/skills/add-test-requirement/SKILL.md index 56b565f..3af681d 100644 --- a/forge/skills/add-test-requirement/SKILL.md +++ b/forge/skills/add-test-requirement/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - ## Workflow 1. **Identify the target `.plain` file.** If ambiguous, ask the user. diff --git a/forge/skills/analyze-2-func-specs/SKILL.md b/forge/skills/analyze-2-func-specs/SKILL.md index e010f14..0ca61e0 100644 --- a/forge/skills/analyze-2-func-specs/SKILL.md +++ b/forge/skills/analyze-2-func-specs/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - ## Input Two functional specs from a `.plain` file (or across `requires` modules). The user provides them directly or points to them by location in a file. diff --git a/forge/skills/analyze-func-specs/SKILL.md b/forge/skills/analyze-func-specs/SKILL.md index 46e2816..76bf2fd 100644 --- a/forge/skills/analyze-func-specs/SKILL.md +++ b/forge/skills/analyze-func-specs/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - ## Input A **set of two or more functional specs** to check against each other. Callers typically supply one of: 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 6dcdebb..672c6a7 100644 --- a/forge/skills/analyze-if-func-spec-too-complex/SKILL.md +++ b/forge/skills/analyze-if-func-spec-too-complex/SKILL.md @@ -12,7 +12,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - ## Why This Matters The renderer enforces a hard complexity limit: each functional spec must imply a **maximum of 200 changed lines of code**. If a spec exceeds this, the renderer rejects it with "Functional spec too complex!" and the spec must be broken down. Catching this before insertion saves a failed render cycle. diff --git a/forge/skills/break-down-func-spec/SKILL.md b/forge/skills/break-down-func-spec/SKILL.md index 2036446..b34a0d9 100644 --- a/forge/skills/break-down-func-spec/SKILL.md +++ b/forge/skills/break-down-func-spec/SKILL.md @@ -10,7 +10,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - ## When to Use - `analyze-if-func-spec-too-complex` flagged a spec as TOO COMPLEX. diff --git a/forge/skills/consolidate-concepts/SKILL.md b/forge/skills/consolidate-concepts/SKILL.md index b294374..0054cb0 100644 --- a/forge/skills/consolidate-concepts/SKILL.md +++ b/forge/skills/consolidate-concepts/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - ## When to Use - Multiple modules define the same or closely related concepts independently. diff --git a/forge/skills/create-import-module/SKILL.md b/forge/skills/create-import-module/SKILL.md index 069fc83..390d15b 100644 --- a/forge/skills/create-import-module/SKILL.md +++ b/forge/skills/create-import-module/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - ## What an Import Module Is An import module is a `.plain` file that lives in the **`template/`** directory and contains **only** `***definitions***`, `***implementation reqs***`, and/or `***test reqs***`. It must **not** contain `***functional specs***` and must **not** use `requires` in its frontmatter. Other modules pull in its content via the `import` field in their YAML frontmatter, gaining access to its definitions and reqs. diff --git a/forge/skills/create-requires-module/SKILL.md b/forge/skills/create-requires-module/SKILL.md index a86e12d..73f9dc1 100644 --- a/forge/skills/create-requires-module/SKILL.md +++ b/forge/skills/create-requires-module/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - ## What Requires Does `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. diff --git a/forge/skills/forge-plain/SKILL.md b/forge/skills/forge-plain/SKILL.md index ac95c7b..a5f2c33 100644 --- a/forge/skills/forge-plain/SKILL.md +++ b/forge/skills/forge-plain/SKILL.md @@ -14,7 +14,6 @@ description: >- 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. - ## Role 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. diff --git a/forge/skills/resolve-section-ownership/SKILL.md b/forge/skills/resolve-section-ownership/SKILL.md index 31a9a84..d6b0209 100644 --- a/forge/skills/resolve-section-ownership/SKILL.md +++ b/forge/skills/resolve-section-ownership/SKILL.md @@ -8,7 +8,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - This is the ownership counterpart to `break-down-func-spec`. That skill splits one functional spec into several because it is **too large**; this one moves facts between sections because they are in the **wrong place**. The two are unrelated: a correctly placed spec can be too large, and a one-line bullet can be misplaced. ## When to Use diff --git a/forge/skills/resolve-spec-conflict/SKILL.md b/forge/skills/resolve-spec-conflict/SKILL.md index 86276e3..aa64d49 100644 --- a/forge/skills/resolve-spec-conflict/SKILL.md +++ b/forge/skills/resolve-spec-conflict/SKILL.md @@ -11,7 +11,6 @@ description: >- Always use the skill `load-plain-reference` to retrieve the ***plain syntax rules — but only if you haven't done so yet. - ## When This Applies A conflict exists (or is suspected) when: From a7c30a56a1df1d2c0c134fefe9200fbf86e076f1 Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Wed, 21 Oct 2026 11:03:51 +0200 Subject: [PATCH 25/25] Shorten the add-test-scenarios description --- forge/skills/add-test-scenarios/SKILL.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/forge/skills/add-test-scenarios/SKILL.md b/forge/skills/add-test-scenarios/SKILL.md index 46637ca..455f057 100644 --- a/forge/skills/add-test-scenarios/SKILL.md +++ b/forge/skills/add-test-scenarios/SKILL.md @@ -1,15 +1,11 @@ --- name: add-test-scenarios description: >- - Write or update the test plain file of one functionality: create - tests//.plain, add the conformance scenarios for its - expected use and the acceptance scenarios for its unexpected use (invalid - parameters, wrong or missing authentication, missing data), in plain - English. Use right after a functional spec is added or changed, - when a functionality has no conformance scenario while conformance tests are - enabled, or when the user asks for a test scenario. This skill replaces the - removed add-acceptance-test skill; nested ***acceptance tests*** under a - functional spec are no longer written. + Write or update the test plain file of one functionality + (tests//.plain): conformance scenarios for its expected use + and acceptance scenarios for its unexpected use, in plain English. Use after + a functional spec is added or changed, when a functionality has no + conformance scenario, or when the user asks for a test scenario. --- # Add Test Scenarios