diff --git a/AGENTS.md b/AGENTS.md index 833e56d..bf94e97 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,8 +16,8 @@ So a change here is almost always one of: (a) editing the installer CLI, or (b) ## Commands ```bash -npm test # full suite: node --test "test/**/*.test.mjs" -node --test --test-name-pattern="" "test/**/*.test.mjs" # run a single test by name +npm test # full suite: node --test test/*.test.mjs +node --test --test-name-pattern="" test/*.test.mjs # run a single test by name node --test test/cli.test.mjs # run a single test file ``` @@ -69,7 +69,7 @@ A `.plain` file is a module: YAML frontmatter (`description`, `import:`, `requir | `***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 | -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 code under `plain_modules/`/`conformance_tests/` is read-only — fixes go back into the spec and re-render. +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) @@ -77,4 +77,4 @@ Other constraints the rules enforce (see `forge/rules/`): functional specs are m ## Memory / persistent context -- Generated artifacts and project scratch (`plain_modules/`, `conformance_tests/`, `test_scripts/`, `*.yaml`, `codeplain.log`, env files) are gitignored. +- Generated artifacts and project scratch (`plain_modules/`, `test_scripts/`, `*.yaml`, `codeplain.log`, env files) are gitignored. The renderer writes everything it generates under `plain_modules//`: `code/` for implementation and unit tests, `tests/` for conformance tests (one folder per functional spec). diff --git a/CLAUDE.md b/CLAUDE.md index 501fe0b..bf94e97 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -69,7 +69,7 @@ A `.plain` file is a module: YAML frontmatter (`description`, `import:`, `requir | `***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 | -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 code under `plain_modules/`/`conformance_tests/` is read-only — fixes go back into the spec and re-render. +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) @@ -77,4 +77,4 @@ Other constraints the rules enforce (see `forge/rules/`): functional specs are m ## Memory / persistent context -- Generated artifacts and project scratch (`plain_modules/`, `conformance_tests/`, `test_scripts/`, `*.yaml`, `codeplain.log`, env files) are gitignored. +- Generated artifacts and project scratch (`plain_modules/`, `test_scripts/`, `*.yaml`, `codeplain.log`, env files) are gitignored. The renderer writes everything it generates under `plain_modules//`: `code/` for implementation and unit tests, `tests/` for conformance tests (one folder per functional spec). diff --git a/README.md b/README.md index cafb148..94d8a1b 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,7 @@ Hit a bug in the rendered app, a failing test, or behavior that doesn't match wh 2. plain-forge applies the fix in the `.plain` file(s) only and summarizes what changed. 3. Re-render to regenerate the code (see [Rendering specs](#rendering-specs)). -> **Important:** Never edit generated code under `plain_modules/` or `conformance_tests/` directly — your changes will be overwritten on the next render. Always fix the spec and re-render. +> **Important:** Never edit generated output under `plain_modules/` directly — neither the code in `plain_modules//code/` nor the conformance tests in `plain_modules//tests/`. Your changes will be overwritten on the next render. Always fix the spec and re-render. ## Repository Structure diff --git a/forge/rules/impl-reqs.md b/forge/rules/impl-reqs.md index 770c674..8ba9732 100644 --- a/forge/rules/impl-reqs.md +++ b/forge/rules/impl-reqs.md @@ -13,7 +13,7 @@ When writing or editing an `***implementation reqs***` section in a `.plain` fil ## `:UnitTests:` lives here (hard rule) - **Everything** about `:UnitTests:` goes in `***implementation reqs***` — paths, approach, packages, framework, conventions, fixtures, mocking policy, file layout, naming, lint / static-analysis gates -- `:UnitTests:` are part of the generated codebase (they sit inside `plain_modules//` alongside the implementation), so requirements that shape them are implementation reqs by definition +- `:UnitTests:` are part of the generated codebase (they sit inside `plain_modules//code/` alongside the implementation), so requirements that shape them are implementation reqs by definition - The unit-test generator reads **only** `***implementation reqs***` — anything about `:UnitTests:` placed elsewhere (e.g. `***test reqs***`) is silently ignored - Author each `:UnitTests:` requirement via `add-implementation-requirement` and phrase it in terms of `:UnitTests:` so the partition stays visible at a glance diff --git a/forge/rules/integration-embedded-testing.md b/forge/rules/integration-embedded-testing.md index 3934b7d..2cce46e 100644 --- a/forge/rules/integration-embedded-testing.md +++ b/forge/rules/integration-embedded-testing.md @@ -75,7 +75,7 @@ mvn test -Dtest='<:UnitTests: package>.**.*Test' checkstyle:check These reqs feed `run_conformance_tests_`. At minimum, declare: -1. **`:ConformanceTests:` source location** — where the conformance suite lives in the project (typically a sibling folder, e.g. `conformance_tests//`); the renderer passes the resolved path as `$2` +1. **`:ConformanceTests:` source location** — the generated suite lives under `plain_modules//tests/`, one folder per functional spec; the renderer passes the resolved path of the folder under test as `$2` 2. **`:ConformanceTests:` framework and execution command** — `mvn test --no-transfer-progress`, `pytest`, `npm test`, `go test ./...`, etc., with any flags / profiles the project requires 3. **Fully qualified `:ConformanceTests:` package** (or path / pattern) used to scope discovery, if the runner needs one 4. **`:ConformanceTests:` network and secrets policy** — by default the suite runs against the **live provider** (see [`integrations.md`](integrations.md) → *`:ConformanceTests:` always run against the live integration*). Declare the env-var names the script reads (e.g. `_API_KEY`), whether the script loads a `.env` file before running, and any specific endpoints that are mocked because they can't be exercised live safely (429, forced 5xx) @@ -85,7 +85,7 @@ These reqs feed `run_conformance_tests_`. At minimum, declare: Author the conformance facts as one or more entries, phrased in terms of `:ConformanceTests:`: ```plain -- :ConformanceTests: of :Implementation: live in `conformance_tests/foo/` and are implemented with JUnit 5 + Maven. +- :ConformanceTests: of :Implementation: live in `plain_modules/foo/tests/` and are implemented with JUnit 5 + Maven. - The fully qualified package used for :ConformanceTests: discovery is `com.example.integrations.foo.conformance`. - :ConformanceTests: are run via `mvn test --no-transfer-progress`; the host's Surefire plugin must be installed. - :ConformanceTests: run against the live :ProviderName: sandbox — no mocking of provider calls. @@ -139,7 +139,7 @@ All three scripts are invoked by the `codeplain` renderer with positional argume ## 1. `prepare_environment_` — copy into the host, then compile -Receives one positional argument: the renderer's build output folder (e.g. `plain_modules//`). +Receives one positional argument: the renderer's build output folder (e.g. `plain_modules//code/`). Purpose: stage the generated code **into the host codebase at the module's package path**, then run the host's install / build so that downstream test projects (specifically the conformance suite) can depend on it from the local dependency cache. diff --git a/forge/rules/integration-embedded.md b/forge/rules/integration-embedded.md index 8e976ed..747a1f2 100644 --- a/forge/rules/integration-embedded.md +++ b/forge/rules/integration-embedded.md @@ -30,6 +30,9 @@ The embedded integration's `.plain` specs (and the rest of the ***plain project │ ├── resources/ │ ├── test_scripts/ │ ├── plain_modules/ # generated; gitignored +│ │ └── / +│ │ ├── code/ # implementation + unit tests +│ │ └── tests/ # conformance tests, per functional spec │ └── config.yaml └── src/ # host source tree (Java, Python, etc.) ``` @@ -117,7 +120,7 @@ The renderer reads the directives from the spec and the shapes from the linked s ## Test-script wiring — copy into the host, run tests there -Embedded integrations are tested **inside the host codebase itself**. The prepare and unit-test scripts copy the renderer's output (`$1`, i.e. `plain_modules//`) into the host's source tree at the module's package path, then compile / test the host project in place. Only the conformance script uses a scratch folder in the system temp directory (`/tmp/_conformance/`), because the conformance suite is a separate project that consumes the host build as a dependency. +Embedded integrations are tested **inside the host codebase itself**. The prepare and unit-test scripts copy the renderer's output (`$1`, i.e. `plain_modules//code/`) into the host's source tree at the module's package path, then compile / test the host project in place. Only the conformance script uses a scratch folder in the system temp directory (`/tmp/_conformance/`), because the conformance suite is a separate project that consumes the host build as a dependency. This matters because the integration's generated code references host symbols by their full import path (e.g. `from host_project.integrations.base import IntegrationContract`). Those imports only resolve cleanly when the test process is rooted in the host's package layout — anything else creates path edge cases that bite later in conformance failures. diff --git a/forge/rules/requires-modules.md b/forge/rules/requires-modules.md index 7b9ebf3..3790b7f 100644 --- a/forge/rules/requires-modules.md +++ b/forge/rules/requires-modules.md @@ -8,7 +8,7 @@ When creating or editing a `.plain` file that uses `requires`, always follow the ## What requires does - `requires` establishes a **build ordering** — the required module is built before the current one -- The required module's generated code (`plain_modules/`) is copied as the starting point +- The required module's generated code (`plain_modules//code`) is copied as the starting point - The required module's `***functional specs***` become visible as **previous functional specs** — this property **is transitive** - Only `exported_concepts` from the required module are available — not its full definitions — and this property **is not transitive** diff --git a/forge/rules/test-reqs.md b/forge/rules/test-reqs.md index 1a080fc..8e2bf7f 100644 --- a/forge/rules/test-reqs.md +++ b/forge/rules/test-reqs.md @@ -14,7 +14,7 @@ When writing or editing a `***test reqs***` section in a `.plain` file, always f ## `:ConformanceTests:` lives here (hard rule) - **Everything** about `:ConformanceTests:` goes in `***test reqs***` — paths, approach, packages, framework, execution command, mocking / network policy, fixtures, pass criteria, environment prerequisites -- `:ConformanceTests:` live outside the generated codebase (typically in a separate project under `conformance_tests//`), so requirements that shape them belong in test reqs by definition +- `:ConformanceTests:` live outside the generated codebase, in their own tree under `plain_modules//tests/` (one folder per functional spec), so requirements that shape them belong in test reqs by definition - The conformance-test generator reads **only** `***test reqs***` — anything about `:ConformanceTests:` placed elsewhere (e.g. `***implementation reqs***`) is silently ignored - Author each `:ConformanceTests:` requirement via `add-test-requirement` and phrase it in terms of `:ConformanceTests:` so the partition stays visible at a glance - Conformance testing run scripts should be linked here as a linked resource diff --git a/forge/skills/create-requires-module/SKILL.md b/forge/skills/create-requires-module/SKILL.md index 2fdda6e..2a2becb 100644 --- a/forge/skills/create-requires-module/SKILL.md +++ b/forge/skills/create-requires-module/SKILL.md @@ -16,7 +16,7 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule `requires` establishes a build ordering between modules. The required module is built **before** the current one. This does not necessarily mean the current module extends or depends on the required module's code — it may be completely independent. The `requires` relationship simply ensures the build order is correct. When this module is rendered: -- The required module's generated code (`plain_modules/`) is copied as the starting point. +- The required module's generated code (`plain_modules//code`) is copied as the starting point. - The required module's `***functional specs***` become visible as **previous functional specs**. - Only `exported_concepts` from the required module are available (not its full definitions). diff --git a/forge/skills/debug-specs/SKILL.md b/forge/skills/debug-specs/SKILL.md index eecc65d..117c4dc 100644 --- a/forge/skills/debug-specs/SKILL.md +++ b/forge/skills/debug-specs/SKILL.md @@ -20,11 +20,11 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule ## Guiding Principle -Generated code in `plain_modules/` and `conformance_tests/` is **read-only** — it exists solely as evidence for diagnosis. All fixes are applied exclusively to the `.plain` spec files. The workflow is: observe → read generated code → trace to spec → fix the spec. +Generated code in `plain_modules//code/` and `plain_modules//tests/` is **read-only** — it exists solely as evidence for diagnosis. All fixes are applied exclusively to the `.plain` spec files. The workflow is: observe → read generated code → trace to spec → fix the spec. ## Input -1. **The module name** — identifies the `plain_modules//` directory and the corresponding `.plain` file(s). +1. **The module name** — identifies the `plain_modules//` directory (`code/` and `tests/`) and the corresponding `.plain` file(s). 2. **The user's observation** — what is wrong? This can be a bug description, a screenshot, a test failure, an error message, or a general "this doesn't work right." 3. **Optional: a specific functional spec** — if the user suspects a particular spec, start there. Otherwise, investigate broadly. @@ -38,7 +38,7 @@ Generated code in `plain_modules/` and `conformance_tests/` is **read-only** — ## Phase 2 — Investigate the Generated Code -Read files in `plain_modules//` to understand what the renderer produced. **Do not modify any generated files.** +Read files in `plain_modules//code/` to understand what the renderer produced. **Do not modify any generated files.** ### 2a. Narrow the search @@ -60,7 +60,7 @@ 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 in `conformance_tests//` or `plain_modules//test/`. +1. Read the failing test — conformance tests in `plain_modules//tests//`, 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). @@ -164,7 +164,7 @@ revise the new spec ## Common Pitfalls ### Fixing the code instead of the spec -Never modify files in `plain_modules/` or `conformance_tests/`. Even if the fix is obvious in the code, the change must be made in the `.plain` file so it persists across re-renders. +Never modify files in `plain_modules//code/` or `plain_modules//tests/`. Even if the fix is obvious in the code, the change must be made in the `.plain` file so it persists across re-renders. ### Treating symptoms instead of root causes If the user says "the button is in the wrong place," don't just add positioning detail. Investigate why the renderer placed it there — the root cause might be a missing layout spec, an ambiguous screen description, or a conflict with another spec. diff --git a/forge/skills/forge-plain/SKILL.md b/forge/skills/forge-plain/SKILL.md index 1861c98..5ffcb9f 100644 --- a/forge/skills/forge-plain/SKILL.md +++ b/forge/skills/forge-plain/SKILL.md @@ -124,4 +124,4 @@ Once the initial specs exist, the user will return with new features. Use the `a - Applicable language rules and operational references: `load-plain-reference`. - Spec-editing skills live in `.claude/skills/`. - Templates go in `template/`, but import paths omit the `template/` prefix. Resources go in `resources/`. -- Generated code lands in `plain_modules/` (read-only, never edit). Test scripts live in `test_scripts/`. +- Generated code lands in `plain_modules//code/` and generated conformance tests in `plain_modules//tests/` (both read-only, never edit). Test scripts live in `test_scripts/`. diff --git a/forge/skills/implement-conformance-testing-script/SKILL.md b/forge/skills/implement-conformance-testing-script/SKILL.md index fa944ac..e5424a4 100644 --- a/forge/skills/implement-conformance-testing-script/SKILL.md +++ b/forge/skills/implement-conformance-testing-script/SKILL.md @@ -43,7 +43,7 @@ Everything else — toolchain check, build staging, dependency isolation, exit c ### Why this split exists -The conformance runner is invoked **once per functional spec** by the renderer. Each functional spec in a module has its own `conformance_tests///` folder, and after the renderer finishes generating code for a new spec, it runs the conformance tests of **every previous spec** in the same module to detect regressions. For a module with N functional specs, this script is called **on the order of N times per render** — not once per render. +The conformance runner is invoked **once per functional spec** by the renderer. Each functional spec in a module has its own `plain_modules//tests//` folder, and after the renderer finishes generating code for a new spec, it runs the conformance tests of **every previous spec** in the same module to detect regressions. For a module with N functional specs, this script is called **on the order of N times per render** — not once per render. That per-spec invocation pattern is what makes the install step expensive. A naive runner that does `pip install` / `npm ci` / `mvn install -DskipTests` / `cargo build` on every invocation pays the install cost N times per render. For anything beyond a toy project, that cost dominates wall-clock time. @@ -123,7 +123,7 @@ A conformance script has **two** read-only inputs: the source build folder (`$1` Why each input is read-only: -- **`$1` (build folder)** is shared with the renderer (`plain_modules/...` by default) and downstream tooling. Writing into it corrupts the renderer's view of "what was generated" and breaks subsequent renders. The whole point of staging into the system temp directory is so the source folder stays a clean, reproducible artifact of the render. +- **`$1` (build folder)** is shared with the renderer (`plain_modules//code` by default) and downstream tooling. Writing into it corrupts the renderer's view of "what was generated" and breaks subsequent renders. The whole point of staging into the system temp directory is so the source folder stays a clean, reproducible artifact of the render. - **`$2` (conformance tests folder)** is the user's authored test source — typically checked into version control. Writing into it pollutes the working tree, churns git status, and (with frameworks that auto-discover) can make subsequent runs pick up generated files as if they were tests. If you find yourself about to issue any command whose `cwd` is `$1` or `$2`, or whose target path starts with `$1/` or `$2/`, **stop**. Either move the operation into `/_`, or you're doing something the script must not do. diff --git a/forge/skills/implement-prepare-environment-script/SKILL.md b/forge/skills/implement-prepare-environment-script/SKILL.md index 8f43e34..36c765a 100644 --- a/forge/skills/implement-prepare-environment-script/SKILL.md +++ b/forge/skills/implement-prepare-environment-script/SKILL.md @@ -27,7 +27,7 @@ When this script exists for a project, the corresponding conformance script's ow ### Why this script exists at all (the structural reason) -The conformance test runner is invoked **once per functional spec** by the renderer — not once per render. Each functional spec in a module has its own `conformance_tests///` folder, and after the renderer finishes generating code for a new spec, it runs the conformance tests of **every previous spec** in the same module to detect regressions. For a module with N functional specs, the conformance script is invoked roughly N times on every render. +The conformance test runner is invoked **once per functional spec** by the renderer — not once per render. Each functional spec in a module has its own `plain_modules//tests//` folder, and after the renderer finishes generating code for a new spec, it runs the conformance tests of **every previous spec** in the same module to detect regressions. For a module with N functional specs, the conformance script is invoked roughly N times on every render. Without a prepare script, every one of those N invocations does the full dependency install (Python venv + `pip install`, full Maven dependency tree, `npm ci`, `cargo build`, ...) from scratch. That cost — paid N times per render — dominates the wall-clock time of rendering a non-trivial project. @@ -103,7 +103,7 @@ The source build folder passed in as `$1` is **input only**. Prepare reads from - pre-build into it (every compile output — `target/`, `build/`, `dist/`, native binaries, generated sources — lives inside `/_`), - create logs, caches, or temp files inside it. -The build folder is shared with the renderer (`plain_modules/...` by default) and with the conformance script, which staging-checks the working folder via `if [ ! -d "/tmp/_$(basename "$1")" ]` and expects `$1` itself to look the same as it did right after rendering. Writing into `$1` corrupts the renderer's view of "what was generated", churns git status if the project commits `$1`, and (if the conformance script ever does an `rm -rf $1` during its own setup) silently destroys work prepare did. +The build folder is shared with the renderer (`plain_modules//code` by default) and with the conformance script, which staging-checks the working folder via `if [ ! -d "/tmp/_$(basename "$1")" ]` and expects `$1` itself to look the same as it did right after rendering. Writing into `$1` corrupts the renderer's view of "what was generated", churns git status if the project commits `$1`, and (if the conformance script ever does an `rm -rf $1` during its own setup) silently destroys work prepare did. The whole point of staging into `/_` is so the source build folder stays a clean, reproducible artifact of the render and no build debris lands inside the user's project. Every dependency, every compiled class, every binary, every cache must land inside the working folder — because that is exactly what the conformance script's activate-only variant attaches to. diff --git a/forge/skills/implement-unit-testing-script/SKILL.md b/forge/skills/implement-unit-testing-script/SKILL.md index 912f377..7b3f57c 100644 --- a/forge/skills/implement-unit-testing-script/SKILL.md +++ b/forge/skills/implement-unit-testing-script/SKILL.md @@ -47,7 +47,7 @@ The source build folder passed in as `$1` is **input only**. The script must nev - run the test command from inside it (every test command runs from inside `/_` after the `cd` in step 5), - create logs, caches, build outputs, or temp files inside it. -The build folder is shared with the renderer (`plain_modules/...` by default) and downstream tooling. Writing into it corrupts the renderer's view of "what was generated" and breaks subsequent renders. Every write must go into `/_` — the whole point of staging into the system temp directory is so the source build folder stays a clean, reproducible artifact of the render and no build debris is left inside the user's project. +The build folder is shared with the renderer (`plain_modules//code` by default) and downstream tooling. Writing into it corrupts the renderer's view of "what was generated" and breaks subsequent renders. Every write must go into `/_` — the whole point of staging into the system temp directory is so the source build folder stays a clean, reproducible artifact of the render and no build debris is left inside the user's project. If you find yourself about to issue any command whose `cwd` is the source folder, or whose target path starts with `$1/`, **stop**. Either move the operation into `/_`, or you're doing something the script must not do. diff --git a/forge/skills/init-config-file/SKILL.md b/forge/skills/init-config-file/SKILL.md index 629effa..d1d3e13 100644 --- a/forge/skills/init-config-file/SKILL.md +++ b/forge/skills/init-config-file/SKILL.md @@ -59,8 +59,7 @@ These keys reflect choices made in Phase 3 of `forge-plain` and are the bread an | `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. | | `logging-config-path` | path (string) | `logging_config.yaml` | Points at a **separate** YAML file consumed by Python's `logging.config.dictConfig`. This is the only knob that lets the user actually change log **levels** for the renderer and its dependencies. See [Logging configuration](#logging-configuration) below. Include the key explicitly whenever the project ships a non-default logging config; leave it out only when the user is happy with the renderer's defaults (`INFO` root, `WARNING` for `git`, `ERROR` for `transitions`). | -| `conformance-tests-folder` | string | `conformance_tests` | Include only when the user picked a non-default folder name. | -| `build-folder` | string | `plain_modules` | Include only when the user picked a non-default folder name. Must differ from `build-dest`. | +| `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 copy destination explicitly so every project's `config.yaml` has the same, predictable target folder for the post-render copy. Even though `dist` matches the renderer's default, we still write it out so the choice is visible in the file and protected against future default changes. Must differ from `build-folder`. | | `base-folder` | string | — | Include when the user wants build output rooted somewhere other than the project root. | @@ -72,7 +71,7 @@ These are useful but the defaults are almost always fine. Only include them when |---|---|---|---| | `copy-build` | bool | `true` | The renderer copies the rendered code to `build-dest` after a successful render. Set to `false` only when the user doesn't want this. | | `copy-conformance-tests` | bool | `false` | Requires `conformance-tests-script` to also be set. | -| `conformance-tests-dest` | string | `dist_conformance_tests` | Target folder for the conformance-test copy. Must differ from `conformance-tests-folder`. | +| `conformance-tests-dest` | string | `dist_conformance_tests` | Target folder for the post-render copy of `plain_modules//tests`. Must differ from `build-folder`. | | `log-to-file` | bool | `true` | Disable only when the user explicitly does not want a log file. Controls whether logs are mirrored to disk — it does **not** set the log level (that's `logging-config-path`'s job). | | `log-file-name` | string | `codeplain.log` | If `log-to-file` is `false`, this key must be left out. A relative value set here resolves against the config file's directory; when the key is left out entirely, the default resolves against the `.plain` file's directory. | | `render-machine-graph` | bool | `false` | Include only when the user wants the state-machine graph rendered. | diff --git a/forge/skills/load-codeplain-reference/SKILL.md b/forge/skills/load-codeplain-reference/SKILL.md index 20e3af1..7ce66ab 100644 --- a/forge/skills/load-codeplain-reference/SKILL.md +++ b/forge/skills/load-codeplain-reference/SKILL.md @@ -13,7 +13,7 @@ description: >- # CODEPLAIN_CLI_REFERENCE.md -`codeplain` is the CLI that renders `***plain` specification files into production-ready code. It reads a `.plain` module (and everything it `import`s / `requires`), calls the codeplain API, and writes generated code under `plain_modules/` and conformance tests under `conformance_tests/`. The `.plain` specs are the source of truth; the generated code is a read-only artifact. +`codeplain` is the CLI that renders `***plain` specification files into production-ready code. It reads a `.plain` module (and everything it `import`s / `requires`), calls the codeplain API, and writes generated code under `plain_modules//code/` and conformance tests under `plain_modules//tests/`. The `.plain` specs are the source of truth; the generated code is a read-only artifact. This reference covers the CLI surface only. For the `***plain` language itself use `load-plain-reference`; to supervise a live render use `run-codeplain`; to assemble or validate `config.yaml` use `init-config-file` / `plain-healthcheck`. @@ -75,7 +75,7 @@ A path's meaning depends on **where it was written**: ### Folders - **`--base-folder BASE_FOLDER`** — base folder for the build files. -- **`--build-folder BUILD_FOLDER`** — folder for build files (generated code lands under here, per module: `plain_modules//`). +- **`--build-folder BUILD_FOLDER`** — folder for build files (generated code lands under here, per module: `plain_modules//code/` and `plain_modules//tests/`). - **`--template-dir TEMPLATE_DIR`** — path to a custom template directory. Templates are searched in this order: 1) the directory containing the `.plain` file, 2) this custom template directory, 3) the built-in `standard_template_library` directory. In `config.yaml` the equivalent key is `template_dir` — set it whenever the project has import modules or templates. ### Test-script wiring @@ -126,8 +126,8 @@ A path's meaning depends on **where it was written**: Generated artifacts (gitignored, read-only — never edit): -- `plain_modules//` — the generated project for each `.plain` spec (implementation + unit tests). -- `conformance_tests///` — generated conformance tests, one subfolder per functionality. +- `plain_modules//code/` — the generated project for each `.plain` spec (implementation + unit tests). +- `plain_modules//tests//` — generated conformance tests, one subfolder per functionality. - `codeplain.log` — the per-run log (overwritten each run). When a run ends, `codeplain` writes a result banner (to the log, and to the terminal outside headless mode): diff --git a/forge/skills/load-plain-reference/references/project-model.md b/forge/skills/load-plain-reference/references/project-model.md index 47221e0..87cf426 100644 --- a/forge/skills/load-plain-reference/references/project-model.md +++ b/forge/skills/load-plain-reference/references/project-model.md @@ -15,8 +15,9 @@ those specifications. *.plain # Root specification modules template/*.plain # Reusable import modules resources/ # Linked text artifacts such as schemas and fixtures -plain_modules/ # Generated implementation and unit tests -conformance_tests/ # Generated conformance tests, grouped by module and functionality +plain_modules/ # Generated output, one directory per module + /code/ # implementation and unit tests + /tests/ # conformance tests, one folder per functional spec test_scripts/ # Unit, environment-preparation, and conformance runners config.yaml # codeplain CLI 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 fc33daa..595f656 100644 --- a/forge/skills/load-plain-reference/references/rendering-and-testing.md +++ b/forge/skills/load-plain-reference/references/rendering-and-testing.md @@ -18,7 +18,7 @@ and debugging skills rather than editing generated output. ## Generated artifacts are read-only -Everything under `plain_modules/` and `conformance_tests/` is generated. It may be read, executed, +Everything under `plain_modules/` is generated — both `/code/` and `/tests/`. It may be read, executed, and debugged, but never edited directly. Apply fixes as follows: - Behavior or implementation/unit-test guidance: edit definitions, functional specs, or @@ -37,7 +37,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. 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. @@ -47,10 +47,10 @@ skills own exact staging, cleanup, dependency-installation, shell, and exit-code Run project scripts from the repository root, using the filenames configured for that project: ```bash -./test_scripts/run_unittests.sh plain_modules/ -./test_scripts/prepare_environment.sh plain_modules/ +./test_scripts/run_unittests.sh plain_modules//code +./test_scripts/prepare_environment.sh plain_modules//code ./test_scripts/run_conformance_tests.sh \ - plain_modules/ conformance_tests// + plain_modules//code plain_modules//tests/ ``` The renderer resolves script arguments to absolute paths. Test scripts must treat input directories diff --git a/forge/skills/plain-healthcheck/SKILL.md b/forge/skills/plain-healthcheck/SKILL.md index 8cb5338..cc8fb77 100644 --- a/forge/skills/plain-healthcheck/SKILL.md +++ b/forge/skills/plain-healthcheck/SKILL.md @@ -90,7 +90,7 @@ Treat the dry-run as a hard gate: the healthcheck **only passes** when every top Iterate until it passes: 1. Read the error output. If the first run was not verbose, immediately re-run with `--verbose`. Identify the offending `.plain` file, the line (if reported), and the kind of issue: missing concept, syntax error, cyclic definition, complexity violation (`Functional spec too complex!`), conflicting reqs, missing template, broken `import`/`requires`, missing config field, 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-spec-conflict`, `break-down-func-spec`, `consolidate-concepts`, or an inline edit. **Never** modify generated code under `plain_modules/` or `conformance_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`, `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 same `codeplain .plain --dry-run …` command with the same flags. Repeat until it exits successfully. diff --git a/forge/skills/run-codeplain/SKILL.md b/forge/skills/run-codeplain/SKILL.md index 28dd5c3..face6c6 100644 --- a/forge/skills/run-codeplain/SKILL.md +++ b/forge/skills/run-codeplain/SKILL.md @@ -21,7 +21,7 @@ A **supervisor** around a live `codeplain` render. The renderer itself is the co 1. Launches `codeplain .plain` (or attaches to an in-flight run). 2. Watches three signal sources in a tight loop, in priority order: - **`codeplain.log`** — the primary signal. Everything the renderer is doing, deciding, retrying, or failing at gets written here. This is the file you stare at. - - the generated **code outputs** under `plain_modules//` and `conformance_tests//` — a secondary signal, used to corroborate or contradict what the log says. + - the generated **code outputs** under `plain_modules//` and `plain_modules//tests/` — a secondary signal, used to corroborate or contradict what the log says. - the **process / TUI** itself (alive? exited? exit code?) — a tertiary signal, used only to know whether to keep monitoring. 3. Surfaces what is happening to the user in plain English, in near-real-time. 4. On pathology, stops the renderer (with user approval), hands off to the right edit skill, and resumes from the last completed functionality. @@ -76,7 +76,7 @@ Before launching anything: - **What "pass" means**, exit-code-wise. Some scripts combine multiple checks (e.g. `unit_testing_typescript.sh` here runs `tsc --noEmit` *and* `vitest`, and its final exit code is the worst of the two — so a type error alone fails the unit-test gate). - **What "fail" means**, including any failures that the script deliberately downgrades to warnings (e.g. `prepare_environment_typescript.sh` here treats a failed `npm run build` as a non-fatal warning). Failures the script swallows are failures the renderer will **never see**, so a spec defect that depends on them will silently slip through. - **Where the scripts materialize their working environment** (current-convention scripts stage into the system temp directory, e.g. `/tmp/typescript_/`; older scripts used project-local folders like `.tmp/typescript_/`, `build/`, `target/`). This is where the renderer's iteration actually runs; if you need to look at what is breaking, this is the directory — not `plain_modules//` directly. - - **How conformance tests are discovered and staged** (glob, naming convention, alias setup, etc.). Some scripts stage tests into the source tree (this repo flattens `conformance_tests///` into `src//`); some run them in place; some require a specific file extension. This tells you exactly which conformance test file is in play for a given functionality. + - **How conformance tests are discovered and staged** (glob, naming convention, alias setup, etc.). Some scripts stage tests into the source tree (this repo flattens `plain_modules//tests//` into `src//`); some run them in place; some require a specific file extension. This tells you exactly which conformance test file is in play for a given functionality. - **Toolchain prerequisites** the script asserts (Node version, Python version, specific binaries). If any are missing on the user's machine, stop now and tell them — the renderer will burn credits on phantom failures otherwise. Keep this synthesis short (5–10 bullets, project-specific). It is the single most important reference for the classifier in [Spec-deviation classification](#spec-deviation-classification). The healthcheck only checks that the scripts *exist* and are *referenced correctly*; this step is the only place that reads what they actually *do*. @@ -132,7 +132,7 @@ All patterns below are taken from the actual codeplain runtime. Treat them as th | `Implementing conformance tests...` / `Implementing test requirements:` | Conformance phase starting for the current functionality. | Routine. | | `Running testing environment preparation script .* for build folder` | `prepare-environment-script` is running. | Routine. | | `\[#79FC96\]All Testing Environment Preparation scripts have passed successfully.\[/#79FC96\]` | Env prep green. | Routine. | -| `Running conformance tests script .* for conformance_tests// \(functionality in module \)` | Conformance tests starting for functionality ``. | Note `` — that is the on-disk folder where the latest conformance test lives. | +| `Running conformance tests script .* for plain_modules//tests/ \(functionality in module \)` | Conformance tests starting for functionality ``. | Note `` — that is the on-disk folder where the latest conformance test lives. | | `Running conformance tests attempt .` | Conformance retry loop. **Watch `` carefully.** | Increment `ATTEMPT_COUNTER = `. At `>=5`, trigger Pathology A. | | `Fixing conformance test for functionality in module .` | Renderer is patching its own conformance test between attempts. | On its own, routine. Combined with climbing ``, smoking gun for Pathology A. | | `Functional spec too complex!` | Single spec implies >200 LOC. Renderer aborts. | Pathology B. | @@ -199,7 +199,7 @@ What to extract from the framework output — stay framework-agnostic, look for - **Individual test names that failed**: lines beginning with `×`, `FAIL`, `✗`, `× should ...`, `FAIL src/.../foo.test.ts > `. The *same* test failing across attempts is the smoking gun for an under-specified spec. - **Assertion bodies**: `expected X, got Y`. The values reveal exactly what the test expects vs what the implementation produced — the cleanest input to the classifier below. - **Compile / type errors** (when the script wraps a type-checker): `error TS`, `cannot find module`, `is not assignable to`. A unit-test gate that keeps re-failing on the same type error means the implementation req or the type-level spec is wrong, not the code. -- **Stack traces**: which file under the prepared working folder threw, and at which line. Map the file back to `plain_modules//src/...` to find the code the spec is supposed to be governing. +- **Stack traces**: which file under the prepared working folder threw, and at which line. Map the file back to `plain_modules//code/...` to find the code the spec is supposed to be governing. - **"No test files found" / "No tests collected"**: the script ran but discovered nothing. Almost always a path/glob mismatch in the staged working folder, not a spec problem. Treat as Pathology F. Always set the logs to be verbose in `config.yaml`. @@ -232,8 +232,8 @@ The inputs to the classifier are: 1. The **spec** that governs the failing functionality — the functional spec and its acceptance test(s) in the `.plain` file. 2. The **test-script output** for the most recent attempt, from `codeplain.log` per the section above. 3. The renderer's own **`.memory/conformance_test_memory/.json`** for this functionality (when present). -4. The newest **conformance test file** the renderer just edited under `conformance_tests///`. -5. The newest **implementation file(s)** the renderer just edited under `plain_modules//`. +4. The newest **conformance test file** the renderer just edited under `plain_modules//tests//`. +5. The newest **implementation file(s)** the renderer just edited under `plain_modules//code/`. With these in hand, place the current iteration into one of four buckets: @@ -320,15 +320,15 @@ If the process is gone but you haven't yet seen a `✓` or `✗` banner in the l This is a secondary signal — it confirms what the log already said. Don't go on a fishing trip here. -For the current module, the renderer drops generated code into `plain_modules//` and conformance tests into `conformance_tests///`. Each pass, inspect what is new: +For the current module, the renderer drops generated code into `plain_modules//` and conformance tests into `plain_modules//tests//`. Each pass, inspect what is new: -- New files under `plain_modules//src/` confirm the functionality is being implemented. -- New folders under `conformance_tests//` confirm the functionality has reached the conformance phase. +- New files under `plain_modules//code/` confirm the functionality is being implemented. +- New folders under `plain_modules//tests/` confirm the functionality has reached the conformance phase. - Files that were rewritten between passes are the renderer's "fix" attempts — diffing them across iterations is what reveals whether the renderer is converging or thrashing. Read **only the newest** file(s) (the ones that changed since last pass), and read them to understand *what the renderer chose to do*, not just *that* it did something. This is the first line of defense against "the spec is ambiguous and the renderer guessed wrong" — you can often spot the wrong guess in the generated code long before tests fail. -Generated code under `plain_modules/` and `conformance_tests/` is **read-only**. Never edit it. Edits go in the `.plain` files. +Generated code under `plain_modules//code/` and `plain_modules//tests/` is **read-only**. Never edit it. Edits go in the `.plain` files. ### 1d. Cadence @@ -384,7 +384,7 @@ Symptom: log shows the unit-test or conformance-test **script** itself failing ( ``` ImportError: Start directory is not importable -/Users/x/project//Users/x/project/conformance_tests/module/spec +/Users/x/project//Users/x/project/plain_modules/module/tests/spec ``` The render then gets stuck "fixing" tests that can never pass. The fix is never a spec edit: stop the render and regenerate the affected scripts via the matching `implement-*-testing-script` skill (current convention: system-temp working folder from `basename "$1"`, `$2` resolved to absolute before any `cd`). @@ -434,7 +434,7 @@ If the fix required regenerating a module's earlier output (e.g. a definition ch When the renderer exits successfully: 1. Read the success banner from the log: render id, generated code folder, functionalities count, used credits, render time. Report it to the user verbatim — these are the numbers they actually care about. -2. List the top-level files/folders that appeared (or changed) under `plain_modules//` and `conformance_tests//` so the user knows where to look. +2. List the top-level files/folders that appeared (or changed) under `plain_modules//` and `plain_modules//tests/` so the user knows where to look. 3. Remind the user of the **side-channel commands** they may want to run themselves, based on the config: - `./test_scripts/` for unit tests, - `./test_scripts/` to set up the test env, @@ -446,7 +446,7 @@ When the renderer exits with a failure that wasn't intercepted by Phase 2 (e.g. ## Anti-patterns -- **Editing generated code to "fix" a render.** Never. Generated files under `plain_modules/` and `conformance_tests/` exist as evidence only; they are overwritten every render. All fixes go in `.plain` files. +- **Editing generated code to "fix" a render.** Never. Generated files under `plain_modules//code/` and `plain_modules//tests/` exist as evidence only; they are overwritten every render. All fixes go in `.plain` files. - **Reading the test scripts' source code to understand what's failing.** Don't. The scripts run real frameworks (vitest, pytest, etc.) and the *output* of those frameworks — captured in `codeplain.log` and in `.memory/conformance_test_memory/*.json` — is what tells you what is failing and why. Reading the script source tells you nothing the log doesn't already say. - **Treating every conformance loop as under-specification.** Run the classifier. The renderer sometimes drifts (Bucket 3) by silently weakening its own tests; that is the opposite problem and the fix is different. - **Re-reading the whole log on every pass.** It grows. Track the byte offset and read only what was appended since the last pass. Full reads are reserved for "I just saw an error and need backstory." @@ -480,4 +480,4 @@ When the renderer exits with a failure that wasn't intercepted by Phase 2 (e.g. - [ ] Spec fixes were delegated to the right edit skill, not done inside this skill - [ ] Resumes use `--render-from ` (or `--render-range`) — `--force-render` only with user approval - [ ] On success, final render id / credits / time were reported and side-channel test commands were surfaced -- [ ] No generated code under `plain_modules/` or `conformance_tests/` was modified +- [ ] No generated code under `plain_modules//code/` or `plain_modules//tests/` was modified