plain-forge 1.0.22-next.e8a40fd → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +17 -26
  2. package/bin/cli.mjs +7 -4
  3. package/bin/python-tools.mjs +139 -0
  4. package/forge/rules/integration-embedded-testing.md +1 -1
  5. package/forge/rules/linked-resources.md +5 -2
  6. package/forge/skills/add-feature/SKILL.md +2 -1
  7. package/forge/skills/add-feature/references/checklist.md +1 -1
  8. package/forge/skills/add-resource/SKILL.md +5 -3
  9. package/forge/skills/check-forge-version/SKILL.md +1 -1
  10. package/forge/skills/check-forge-version/scripts/check-forge-version.mjs +1 -1
  11. package/forge/skills/check-plain-env/SKILL.md +7 -8
  12. package/forge/skills/forge-plain/SKILL.md +3 -4
  13. package/forge/skills/forge-plain/references/checklist.md +2 -2
  14. package/forge/skills/forge-plain/references/phase-3-testing.md +1 -1
  15. package/forge/skills/forge-plain/references/phase-4-validate-handoff.md +13 -15
  16. package/forge/skills/implement-conformance-testing-script/SKILL.md +3 -3
  17. package/forge/skills/implement-prepare-environment-script/SKILL.md +3 -3
  18. package/forge/skills/implement-unit-testing-script/SKILL.md +3 -3
  19. package/forge/skills/init-config-file/SKILL.md +22 -134
  20. package/forge/skills/init-plain-project/SKILL.md +5 -5
  21. package/forge/skills/init-plain-project/references/checklist.md +2 -2
  22. package/forge/skills/load-plain-reference/SKILL.md +3 -4
  23. package/forge/skills/load-plain-reference/references/project-model.md +1 -1
  24. package/forge/skills/plain-healthcheck/SKILL.md +42 -33
  25. package/package.json +2 -2
  26. package/forge/skills/load-codeplain-reference/SKILL.md +0 -188
  27. package/forge/skills/run-codeplain/SKILL.md +0 -483
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # plain-forge
6
6
 
7
- A toolkit for working with [∗∗∗plain](https://plainlang.org) projects from inside your AI coding agent of choice — Claude Code, Codex, ForgeCode, OpenCode, and any other agent that reads from a standard skills directory. plain-forge ships skills, rules, and docs that turn a conversation into complete `.plain` spec files, then keeps maintaining them across the lifetime of the project. The specs are rendered into production-ready code by the [codeplain](https://codeplain.ai) renderer.
7
+ A toolkit for working with [∗∗∗plain](https://plainlang.org) projects from inside your AI coding agent of choice — Claude Code, Codex, ForgeCode, OpenCode, and any other agent that reads from a standard skills directory. plain-forge ships skills, rules, and docs that turn a conversation into complete `.plain` spec files, then keeps maintaining them across the lifetime of the project. The specs are then rendered into production-ready code by a ***plain renderer.
8
8
 
9
9
  ## What plain-forge does
10
10
 
@@ -14,7 +14,7 @@ plain-forge is organized around four kinds of work, each with its own entry-poin
14
14
 
15
15
  Pick whichever entry point matches how much upfront design you want:
16
16
 
17
- - **`forge-plain`** — full end-to-end interview. One question at a time, immediate writes to disk, covers product → tech stack → testing → validation in four phases. Produces a complete `.plain` project with `config.yaml`, test scripts, and a successful `codeplain --dry-run` before handing off.
17
+ - **`forge-plain`** — full end-to-end interview. One question at a time, immediate writes to disk, covers product → tech stack → testing → validation in four phases. Produces a complete `.plain` project with `config.yaml`, test scripts, and a passing `plain-healthcheck` before handing off.
18
18
  - **`init-plain-project`** — lightweight scaffold. Asks only about the base technology, project kind, and whether conformance testing is on; emits a template module, a stub top-level module, the testing scripts, and a `config.yaml`. No interview, no specs — pair it with `add-feature` to grow the project feature by feature.
19
19
 
20
20
  ### 2. Grow an existing project
@@ -25,15 +25,14 @@ Pick whichever entry point matches how much upfront design you want:
25
25
 
26
26
  ### 3. Validate and maintain
27
27
 
28
- - **`plain-healthcheck`** — the verification gate. Inventories every `.plain` module, validates every `config.yaml`, and dry-runs every top module with the right config. Returns `PASS` / `FAIL` with a numbered punch-list when something is broken.
28
+ - **`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.
29
29
  - **`init-config-file`** — assembles the canonical `config.yaml` per part of a project from the decisions made during interviewing.
30
- - **`check-plain-env`** — probes the host machine for everything the project needs (language toolchains, external services, system binaries, drivers, `codeplain` itself) and emits a `PASS` / `WARN` / `FAIL` report with OS-specific install commands.
30
+ - **`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.
31
31
  - **`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.
32
32
 
33
- ### 4. Debug and render
33
+ ### 4. Debug and test
34
34
 
35
35
  - **`debug-specs`** — when the rendered app misbehaves, this traces the generated code back to the spec that caused it and fixes the spec (never the generated code).
36
- - **`run-codeplain`** — experimental supervised render. Launches `codeplain` for you, tails the log, watches generated code appear under `plain_modules/`, and detects pathologies (stuck conformance loops, complexity errors, missing concepts, render failures). On approval it stops the renderer, hands off to the right spec-edit skill, and resumes.
37
36
  - **`render-spec`** — [pyro](https://github.com/plainlang/pyro), the open-source ***plain renderer, bundled with plain-forge. Renders a `.plain` module (and the modules it requires or imports) into working, tested code directly inside your agent. See [Render with pyro](#render-with-pyro).
38
37
  - **`implement-unit-testing-script`** / **`implement-conformance-testing-script`** / **`implement-prepare-environment-script`** — generate the per-language testing scripts that the renderer and you both invoke. New languages can be added by these skills without touching any other part of the project.
39
38
 
@@ -57,6 +56,8 @@ This prompts you to pick an agent and a scope using an arrow-key menu. You can a
57
56
  npx plain-forge install --agent claude --scope project
58
57
  ```
59
58
 
59
+ `install` also installs [`plain-parser`](https://pypi.org/project/plain-parser/), the Python CLI that `plain-healthcheck` uses to validate `.plain` files. It installs it with [uv](https://docs.astral.sh/uv/), installing uv first if it is missing, on Linux, macOS and Windows alike. If plain-parser is already on your `PATH`, nothing is installed. Pass `--skip-plain-parser` to leave both alone.
60
+
60
61
  **Agent options:**
61
62
 
62
63
  | `--agent` | Installs into (project · global) | Use when |
@@ -148,6 +149,7 @@ If an install has **no manifest** (e.g. one that predates manifests), `uninstall
148
149
 
149
150
  1. Open your project folder and start a session in your favorite AI coding agent (Claude Code, OpenCode, Codex, …).
150
151
  2. Make sure the plain-forge skills are available in that session.
152
+ 3. Make sure [`plain-parser`](https://pypi.org/project/plain-parser/) is installed. `plain-healthcheck` uses it to validate `.plain` files. `npx plain-forge install` installs it for you; otherwise run `uv tool install plain-parser` or `pipx install plain-parser` (Python 3.11 or newer).
151
153
 
152
154
  ### Starting a new project
153
155
 
@@ -159,7 +161,7 @@ If an install has **no manifest** (e.g. one that predates manifests), `uninstall
159
161
 
160
162
  If you'd rather skip the full upfront interview and build the specs feature-by-feature, use this lighter loop:
161
163
 
162
- 1. Invoke `init-plain-project`. It asks just for the base technology, the project kind, and whether conformance testing is enabled, then scaffolds the project skeleton: `template/base.plain` with the base `***implementation reqs***` and `***test reqs***`, a stub top-level `<project>.plain` (frontmatter only — no functional specs, no concepts), the unit-test script, an optional conformance-test script, an optional prepare-environment script, and a `config.yaml` wired to whichever scripts were generated. No `codeplain --dry-run` is run.
164
+ 1. Invoke `init-plain-project`. It asks just for the base technology, the project kind, and whether conformance testing is enabled, then scaffolds the project skeleton: `template/base.plain` with the base `***implementation reqs***` and `***test reqs***`, a stub top-level `<project>.plain` (frontmatter only — no functional specs, no concepts), the unit-test script, an optional conformance-test script, an optional prepare-environment script, and a `config.yaml` wired to whichever scripts were generated. The project is not validated.
163
165
  2. From there, either:
164
166
  - **Converse with the agent.** Just describe the next feature in plain English; the agent will invoke `add-feature` for you and run its one-question-at-a-time loop until the feature is on disk.
165
167
  - **Invoke `add-feature` manually** whenever you want to drive the loop yourself.
@@ -174,19 +176,7 @@ If you'd rather skip the full upfront interview and build the specs feature-by-f
174
176
 
175
177
  ### Rendering specs
176
178
 
177
- Once your `.plain` files are ready (and `plain-healthcheck` is green), render the specs into code with the [Codeplain](https://codeplain.ai) renderer:
178
-
179
- ```bash
180
- codeplain <module>.plain
181
- ```
182
-
183
- plain-forge prints the exact command (with the right final module name) at the end of Phase 4.
184
-
185
- #### Supervised render (experimental)
186
-
187
- If you'd rather have plain-forge babysit the run from your AI coding agent, invoke `run-codeplain`. It launches the renderer for you, tails `codeplain.log`, watches generated code appear under `plain_modules/`, and surfaces what's happening in plain English. If it detects a pathology (stuck conformance loop, complexity error, missing concept, render failure), it asks for approval to stop the renderer, hands off to the right spec-edit skill (`debug-specs`, `resolve-spec-conflict`, `break-down-func-spec`, …), and resumes the render from the last completed functionality via `--render-from`.
188
-
189
- This is an **experimental** feature — the default and most reliable way to render is still the manual `codeplain <module>.plain` invocation above.
179
+ Once your `.plain` files are ready (and `plain-healthcheck` is green), render the specs into code with a ***plain renderer, passing it the final module in the dependency chain. plain-forge names that module at the end of Phase 4.
190
180
 
191
181
  #### Render with pyro
192
182
 
@@ -227,6 +217,7 @@ forge/ # canonical content, copied verbatim on install
227
217
 
228
218
  bin/
229
219
  cli.mjs # the `plain-forge` CLI — `install` and `update` commands
220
+ python-tools.mjs # installs uv + plain-parser during `install` (until plain-parser ships on npm)
230
221
 
231
222
  vendor/
232
223
  pyro/ # git submodule: plainlang/pyro, pinned to the release plain-forge ships
@@ -237,8 +228,9 @@ scripts/
237
228
 
238
229
  test/
239
230
  cli.test.mjs # tests for the install / update CLI
231
+ python-tools.test.mjs # tests for the uv + plain-parser install step
240
232
 
241
- package.json # ships only `bin/cli.mjs` and `forge/` to npm
233
+ package.json # ships only `bin/cli.mjs`, `bin/python-tools.mjs` and `forge/` to npm
242
234
  ```
243
235
 
244
236
  ### Bundled pyro skill
@@ -270,10 +262,9 @@ On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them int
270
262
  | Skill | Description |
271
263
  |-------|-------------|
272
264
  | `forge-plain` | End-to-end QA interview that produces complete `.plain` spec files for a new project |
273
- | `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 dry-run. Pair with `add-feature` to grow the project feature-by-feature. |
265
+ | `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. |
274
266
  | `add-feature` | Interview the user about a single feature, then write all the specs for it |
275
267
  | `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
- | `run-codeplain` | **Experimental.** Launch a `codeplain` render and supervise it end-to-end — tails `codeplain.log`, watches generated code appear, detects pathologies (stuck conformance loops, complexity errors, missing concepts, render failures), and on approval stops the renderer, hands off to the right spec-edit skill, and resumes with `--render-from`. The default render path is still the manual `codeplain <module>.plain` command. |
277
268
 
278
269
  ### Spec Authoring
279
270
 
@@ -301,9 +292,9 @@ On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them int
301
292
 
302
293
  | Skill | Description |
303
294
  |-------|-------------|
304
- | `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 derived from the `codeplain` CLI, refuses to write secrets or per-invocation flags, 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. |
305
- | `plain-healthcheck` | Verification gate: validates every `config.yaml`, confirms each `*-script` field points at a real file in `test_scripts/`, and dry-runs every top module. 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. |
306
- | `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), `codeplain` itself, and credential env vars. 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. |
295
+ | `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. |
296
+ | `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. |
297
+ | `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. |
307
298
  | `analyze-if-func-spec-too-complex` | Check if a spec exceeds the 200-line complexity limit |
308
299
  | `analyze-func-specs` | Check a batch of specs (2+) against each other in one call and return every conflicting pair |
309
300
  | `analyze-2-func-specs` | Legacy: check exactly two specs for conflicts (prefer `analyze-func-specs`) |
package/bin/cli.mjs CHANGED
@@ -5,6 +5,8 @@ import path from "node:path";
5
5
  import readline from "node:readline";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
+ import { ensurePlainParser } from "./python-tools.mjs";
9
+
8
10
  const __filename = fileURLToPath(import.meta.url);
9
11
  const pkgRoot = path.resolve(path.dirname(__filename), "..");
10
12
  const forgeDir = path.join(pkgRoot, "forge");
@@ -117,6 +119,7 @@ Install options:
117
119
  --agent <claude|codex|copilot|forgecode|opencode|universal>
118
120
  Target agent layout
119
121
  --scope <project|global> Install into cwd or $HOME
122
+ --skip-plain-parser Don't install plain-parser (and uv)
120
123
  -h, --help Show this help
121
124
 
122
125
  Update options:
@@ -139,6 +142,8 @@ Examples:
139
142
 
140
143
  "install" fails if plain-forge is already installed at the target — use
141
144
  "update" to refresh it. Missing install flags are prompted interactively.
145
+ "install" also installs plain-parser, which the healthcheck uses to validate
146
+ .plain files, with uv (installing uv first if it is missing).
142
147
  "update" auto-detects installs and prunes only files plain-forge wrote
143
148
  (confirming each removal), leaving your own and third-party skills untouched.
144
149
  "uninstall" deletes exactly the files recorded in the install manifest, then
@@ -152,6 +157,7 @@ function parseArgs(argv) {
152
157
  if (a === "--agent") out.agent = argv[++i];
153
158
  else if (a === "--scope") out.scope = argv[++i];
154
159
  else if (a === "-y" || a === "--yes") out.yes = true;
160
+ else if (a === "--skip-plain-parser") out.skipPlainParser = true;
155
161
  else if (a === "-h" || a === "--help") out.help = true;
156
162
  else out._.push(a);
157
163
  }
@@ -794,6 +800,7 @@ async function cmdInstall(args) {
794
800
  console.log(` rules: ${counts.rules}`);
795
801
  console.log(` docs: ${counts.docs}`);
796
802
  wireRules(agent, scope);
803
+ if (!args.skipPlainParser) ensurePlainParser();
797
804
  console.log();
798
805
  printNextSteps(agent);
799
806
  }
@@ -988,7 +995,6 @@ function printNextSteps(agent) {
988
995
  const bold = (s) => `\x1b[1m${s}\x1b[0m`;
989
996
  const dim = (s) => `\x1b[2m${s}\x1b[0m`;
990
997
  const plain = (s) => color(palette.plain, s);
991
- const codeplain = (s) => color(palette.brand, s);
992
998
  const link = (s) => color(palette.link, s, "4;");
993
999
 
994
1000
  console.log(`\x1b[1mnext steps:\x1b[0m`);
@@ -1006,9 +1012,6 @@ function printNextSteps(agent) {
1006
1012
  ` ${bold("add-feature")} — add a feature to an existing ${plain("***plain")} project`,
1007
1013
  );
1008
1014
  console.log();
1009
- console.log(
1010
- `prerequisite: install the ${codeplain("codeplain")} CLI to render your specs into code — ${link("https://www.codeplain.ai/")}`,
1011
- );
1012
1015
  console.log(
1013
1016
  `usage guide: ${link("https://github.com/plainlang/plain-forge#usage")}`,
1014
1017
  );
@@ -0,0 +1,139 @@
1
+ // Installs plain-parser, the Python CLI `plain-healthcheck` uses to validate
2
+ // `.plain` files, together with uv, the tool that installs it.
3
+ //
4
+ // Temporary: once plain-parser ships as an npm package it becomes a regular
5
+ // dependency in package.json and this file goes away.
6
+ //
7
+ // Nothing here throws. A failed step prints a warning with the manual
8
+ // commands, and the plain-forge install carries on.
9
+ import { spawnSync } from "node:child_process";
10
+ import fs from "node:fs";
11
+ import os from "node:os";
12
+ import path from "node:path";
13
+
14
+ const PLAIN_PARSER = "plain-parser";
15
+
16
+ const UV_INSTALLERS = {
17
+ win32: {
18
+ cmd: "powershell.exe",
19
+ args: [
20
+ "-NoProfile",
21
+ "-ExecutionPolicy",
22
+ "ByPass",
23
+ "-Command",
24
+ "irm https://astral.sh/uv/install.ps1 | iex",
25
+ ],
26
+ manual:
27
+ 'powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"',
28
+ },
29
+ // Linux and macOS. Falls back to wget where curl is missing (minimal images).
30
+ posix: {
31
+ cmd: "sh",
32
+ args: [
33
+ "-c",
34
+ "if command -v curl >/dev/null 2>&1; then curl -LsSf https://astral.sh/uv/install.sh | sh; " +
35
+ "elif command -v wget >/dev/null 2>&1; then wget -qO- https://astral.sh/uv/install.sh | sh; " +
36
+ 'else echo "the uv installer needs curl or wget, and neither is installed" >&2; exit 1; fi',
37
+ ],
38
+ manual: "curl -LsSf https://astral.sh/uv/install.sh | sh",
39
+ },
40
+ };
41
+
42
+ function uvInstaller(platform) {
43
+ return platform === "win32" ? UV_INSTALLERS.win32 : UV_INSTALLERS.posix;
44
+ }
45
+
46
+ // Run a command. `inherit` streams its output to the user; otherwise stdout is
47
+ // captured. `error` is set (ENOENT) when the command is not on PATH.
48
+ function runCommand(cmd, args, { inherit = false } = {}) {
49
+ const r = spawnSync(cmd, args, {
50
+ encoding: "utf8",
51
+ stdio: inherit ? "inherit" : ["ignore", "pipe", "pipe"],
52
+ });
53
+ return { status: r.status, stdout: r.stdout ?? "", error: r.error };
54
+ }
55
+
56
+ function isOnPath(cmd, run) {
57
+ const r = run(cmd, ["--help"]);
58
+ return !r.error && r.status === 0;
59
+ }
60
+
61
+ // Where the uv installer puts uv. A fresh install is not on this process's
62
+ // PATH, so look for it here before giving up.
63
+ function uvCandidates(platform, env, home) {
64
+ const exe = platform === "win32" ? "uv.exe" : "uv";
65
+ const dirs = [
66
+ env.UV_INSTALL_DIR,
67
+ env.XDG_BIN_HOME,
68
+ path.join(home, ".local", "bin"),
69
+ path.join(home, ".cargo", "bin"),
70
+ ].filter(Boolean);
71
+ return dirs.map((d) => path.join(d, exe));
72
+ }
73
+
74
+ function findUv({ platform, env, home, run, exists }) {
75
+ if (isOnPath("uv", run)) return "uv";
76
+ return uvCandidates(platform, env, home).find(exists) ?? null;
77
+ }
78
+
79
+ function warnOffPath(log, binDir) {
80
+ log(` plain-parser: installed${binDir ? ` into ${binDir}` : ""}, which is not on your PATH yet.`);
81
+ log(` run "uv tool update-shell" and open a new terminal.`);
82
+ return { status: "not-on-path" };
83
+ }
84
+
85
+ function warnManual(log, reason, platform) {
86
+ log(` plain-parser: not installed — ${reason}`);
87
+ log(` plain-forge's healthcheck needs it. Install it yourself with:`);
88
+ log(` ${uvInstaller(platform).manual}`);
89
+ log(` uv tool install ${PLAIN_PARSER}`);
90
+ }
91
+
92
+ // Make sure `plain-parser` is installed, installing uv first if needed.
93
+ // Returns { status } — "present", "installed", "not-on-path", or "failed".
94
+ function ensurePlainParser({
95
+ platform = process.platform,
96
+ env = process.env,
97
+ home = os.homedir(),
98
+ run = runCommand,
99
+ exists = fs.existsSync,
100
+ log = console.log,
101
+ } = {}) {
102
+ if (isOnPath(PLAIN_PARSER, run)) {
103
+ log(` plain-parser: already installed`);
104
+ return { status: "present" };
105
+ }
106
+
107
+ let uv = findUv({ platform, env, home, run, exists });
108
+ if (!uv) {
109
+ log(` uv: not found — installing it (https://docs.astral.sh/uv/)`);
110
+ const { cmd, args } = uvInstaller(platform);
111
+ const r = run(cmd, args, { inherit: true });
112
+ uv = !r.error && r.status === 0 ? findUv({ platform, env, home, run, exists }) : null;
113
+ if (!uv) {
114
+ warnManual(log, "installing uv failed", platform);
115
+ return { status: "failed" };
116
+ }
117
+ }
118
+
119
+ // Installed by an earlier run, but its bin dir is not on PATH.
120
+ const binDir = run(uv, ["tool", "dir", "--bin"]).stdout.trim();
121
+ const exe = platform === "win32" ? `${PLAIN_PARSER}.exe` : PLAIN_PARSER;
122
+ if (binDir && exists(path.join(binDir, exe))) return warnOffPath(log, binDir);
123
+
124
+ log(` plain-parser: not found — installing it with uv`);
125
+ // uv downloads a suitable Python (3.11+) itself when the host has none.
126
+ const r = run(uv, ["tool", "install", PLAIN_PARSER], { inherit: true });
127
+ if (r.error || r.status !== 0) {
128
+ warnManual(log, "`uv tool install plain-parser` failed", platform);
129
+ return { status: "failed" };
130
+ }
131
+
132
+ if (isOnPath(PLAIN_PARSER, run)) {
133
+ log(` plain-parser: installed`);
134
+ return { status: "installed" };
135
+ }
136
+ return warnOffPath(log, binDir);
137
+ }
138
+
139
+ export { ensurePlainParser, uvInstaller, uvCandidates };
@@ -120,7 +120,7 @@ output=$(mvn test --no-transfer-progress 2>&1)
120
120
 
121
121
  ## Common contract (all three scripts)
122
122
 
123
- All three scripts are invoked by the `codeplain` renderer with positional arguments. They MUST:
123
+ All three scripts are invoked by the renderer with positional arguments. They MUST:
124
124
 
125
125
  1. **Be POSIX-bash (`.sh`) on macOS / Linux, PowerShell (`.ps1`) on Windows.** Executable, idempotent — re-running with the same inputs produces the same result
126
126
  2. **Validate every positional argument up front.** Exit with a clear `Usage:` line on bad args. Conventional exit codes (consistent with the shared testing-script rules):
@@ -61,6 +61,9 @@ A linked resource **must not** be any of the following:
61
61
  ```
62
62
 
63
63
  ## File location and path resolution
64
- - Paths are resolved relative to the directory where `codeplain` is run (the current working directory)
64
+ - Paths are resolved against the directory that contains the `.plain` file passed to the renderer, not the directory where the renderer is run
65
+ - This includes links inside imported and required modules — they resolve against that same directory, not against the directory the imported module itself lives in
66
+ - 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
67
+ - Because the `.plain` file's own directory is the anchor, a render gives the same result from any working directory
65
68
  - 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
66
- - The conventional location is a `resources/` directory; keep the resource inside the project so the path stays stable from wherever `codeplain` is run
69
+ - The conventional location is a `resources/` directory next to the `.plain` file
@@ -114,7 +114,8 @@ Most checks already happened in the loop; this is a slim consistency pass whose
114
114
  - Acceptance tests (if any) are consistent with their parent specs.
115
115
  3. Present the final diff for the modified file(s) and get the user's approval.
116
116
  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.
117
- 5. **Run `plain-healthcheck`** — the last thing this skill does. It validates every `config.yaml` and dry-runs every top module, 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 and remind them to re-render with `codeplain <module>.plain`.
117
+ 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 `<module>.plain`.
118
+ 6. **Default renderer: pyro.** Ask the user whether to re-render `<module>` now with pyro's `render-spec` skill (`/render-spec <module>.plain`, or `/pyro:render-spec <module>.plain` when pyro is installed as a Claude Code plugin), and invoke it only if they agree. If no `render-spec` skill is available in this session, tell the user pyro is not installed and point them to https://github.com/plainlang/pyro#installation. The user may render with any other ***plain renderer instead.
118
119
 
119
120
  ## Next feature
120
121
 
@@ -35,4 +35,4 @@ Use this to verify the workflow was followed — never as a substitute for it. R
35
35
  - [ ] Read the modified `.plain` file(s) in full.
36
36
  - [ ] 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.
37
37
  - [ ] Presented the final diff and got the user's approval (change requests dropped back into the one-question loop, not a restart).
38
- - [ ] 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 with `codeplain <module>.plain`.
38
+ - [ ] 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 `<module>.plain`.
@@ -40,7 +40,9 @@ Use standard markdown link syntax inside any spec section:
40
40
 
41
41
  ## Path Rules
42
42
 
43
- - Paths are resolved **relative to the directory where `codeplain` is run** (the current working directory).
43
+ - Paths are resolved **against the directory that contains the `.plain` file passed to the renderer**, not the directory where the renderer is run.
44
+ - This includes links inside imported and required modules — they resolve against that same directory, not against the directory the imported module itself lives in.
45
+ - 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.
44
46
  - 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.
45
47
  - **No external URLs** — only local file references.
46
48
  - **No folder paths** — the target must be a file, not a directory.
@@ -69,7 +71,7 @@ The **only** exceptions are URLs and paths that are *values the produced softwar
69
71
 
70
72
  ### What a linked resource CAN be
71
73
 
72
- - A single file inside the project, reachable by a path relative to where `codeplain` is run — parent-directory traversal (`../`) is allowed (`resources/` is the conventional home).
74
+ - A single file inside the project, reachable by a path relative to the `.plain` file — parent-directory traversal (`../`) is allowed (`resources/` is the conventional home).
73
75
  - A **text-based** file the renderer can read end-to-end: JSON, YAML, XML, HTML, Markdown, plain text, CSV, TSV, source code in any language (`.py`, `.js`, `.ts`, `.go`, `.java`, `.rb`, `.rs`, `.kt`, `.swift`, `.c`, `.cpp`, `.cs`, …), shell scripts, SQL, JSON Schema, OpenAPI, AsyncAPI, Protobuf `.proto`, GraphQL SDL, `.jolt`, `.env.example`, `.toml`, `.ini`, `.proto`, `Dockerfile`, etc.
74
76
 
75
77
  ## Common Resource Types
@@ -101,6 +103,6 @@ The **only** exceptions are URLs and paths that are *values the produced softwar
101
103
  - [ ] **Target is a text-based file** (no binary extensions: `.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.tiff`, `.webp`, `.ico`, `.pdf`, `.docx`, `.xlsx`, `.pptx`, `.zip`, `.tar`, `.gz`, `.mp3`, `.mp4`, `.wav`, `.exe`, `.so`, `.dylib`, `.class`, `.wasm`, …)
102
104
  - [ ] **No URLs or folder paths anywhere in the surrounding `.plain` content** (not as link targets, not in body prose), with the sole exception of URLs / paths that are runtime values the generated software itself uses
103
105
  - [ ] Path is relative to the `.plain` file, not absolute
104
- - [ ] Path resolves to a real text file on disk from where `codeplain` is run (`../` traversal is allowed)
106
+ - [ ] Path resolves to a real text file on disk relative to the `.plain` file (`../` traversal is allowed)
105
107
  - [ ] Markdown link syntax is correct: `[display text](relative/path)`
106
108
  - [ ] Resource content is relevant and adds value beyond what the spec text says
@@ -104,7 +104,7 @@ Relay the verdict to the user, then act on it:
104
104
  ```
105
105
 
106
106
  State plainly that **continuing on an outdated plain-forge is not recommended** — its skills and
107
- rules have been superseded, specs authored against them may not match what the current `codeplain`
107
+ rules have been superseded, specs authored against them may not match what the current
108
108
  renderer expects, and fixes released since the installed version are not in effect. On `WARN`,
109
109
  where the version could not be confirmed, say the install should be treated as outdated until
110
110
  proven otherwise.
@@ -265,7 +265,7 @@ function report(installs, latest, statuses) {
265
265
  `${n} ${n === 1 ? "install is" : "installs are"} behind v${latest.version}. ` +
266
266
  "Continuing on an outdated plain-forge is not recommended: its skills and rules have " +
267
267
  "been superseded, so specs authored against them may not match what the current " +
268
- "codeplain renderer expects, and fixes released since this version are not in effect.",
268
+ "renderer expects, and fixes released since this version are not in effect.",
269
269
  ),
270
270
  );
271
271
  }
@@ -18,7 +18,7 @@ This skill answers one question: **does the host machine have everything the ***
18
18
 
19
19
  ## When to run
20
20
 
21
- - **First time you open a ***plain project on a new machine** — before the first `codeplain <module>.plain` or `./test_scripts/run_unittests*.sh` invocation. This is the most common case.
21
+ - **First time you open a ***plain project on a new machine** — before the first render or `./test_scripts/run_unittests*.sh` invocation. This is the most common case.
22
22
  - **At the start of `forge-plain` Phase 3 environment verification** — `forge-plain` historically did this inline; delegate to this skill instead so the same check runs the same way everywhere.
23
23
  - **After `add-feature`** — when the new feature brought in a new dependency (a new framework, a new service, a new package manager) the project didn't need before.
24
24
  - **Before a real render** — alongside `plain-healthcheck`. `plain-healthcheck` answers "are the specs renderable?"; this skill answers "can this machine render them?". Both should pass.
@@ -33,7 +33,7 @@ This skill is **read-only and observational**. It does not edit `.plain` files,
33
33
  - It does **not** install anything. It only suggests install commands and lets the user run them.
34
34
  - It does **not** generate scripts, modify `config.yaml`, or change project files. Use `implement-*-testing-script`, `init-config-file`, etc. for that.
35
35
  - It does **not** start services (databases, brokers, Docker daemons). It only checks whether the right binaries and (optionally) running endpoints are reachable.
36
- - It does **not** validate the specs themselves (syntax, dry-run, complexity). Use `plain-healthcheck` for that — the two skills are complementary.
36
+ - It does **not** validate the specs themselves (syntax, concepts, module graph). Use `plain-healthcheck` for that — the two skills are complementary. It only checks that `plain-parser`, which `plain-healthcheck` runs, is installed.
37
37
  - It does **not** print secrets. Check whether an env var is *set* (e.g. `printenv FOO >/dev/null && echo set || echo missing`), never echo its value.
38
38
 
39
39
  ## Workflow
@@ -147,10 +147,9 @@ Worked example — the GPU case:
147
147
 
148
148
  Add these to every requirement list regardless of project:
149
149
 
150
- - `codeplain` CLI on `PATH` — `codeplain --version`.
151
- - `CODEPLAIN_API_KEY` env var **set** — `printenv CODEPLAIN_API_KEY >/dev/null && echo set || echo missing` (never echo the value).
152
150
  - A shell matching the testing scripts' extension (Bash for `.sh`, PowerShell for `.ps1`).
153
151
  - `git` (the renderer uses it; almost every plain project tracks itself in git).
152
+ - `plain-parser` and **Python 3.11 or newer** — `plain-healthcheck` validates every module with `plain-parser check`. Probe `python3 --version` and `plain-parser --help`; install with `pipx install plain-parser` or `uv tool install plain-parser`.
154
153
 
155
154
  ### Step 3 — Probe the host
156
155
 
@@ -163,7 +162,7 @@ For every requirement the project produced in Step 2, run a check using the `ter
163
162
  Probe in the same category order Step 2 produced them, so the report reads top-down:
164
163
 
165
164
  1. **Category 1 — Language toolchains.** Run the toolchain + package-manager probes listed in Step 2's Category 1 for the languages this project actually uses. Do not probe individual language packages.
166
- 2. **Category 5 — Codeplain and `git`.** Always required, independent of the project. `codeplain --version`, `printenv CODEPLAIN_API_KEY >/dev/null`, `git --version`. Verify the shell flavor matches the scripts' extension; a `.sh`-only project on native Windows is a `FAIL` (suggest WSL).
165
+ 2. **Category 5 — Shell, `git`, and `plain-parser`.** Always required, independent of the project. `git --version`, `python3 --version` (3.11 or newer), `plain-parser --help`. Verify the shell flavor matches the scripts' extension; a `.sh`-only project on native Windows is a `FAIL` (suggest WSL).
167
166
  3. **Category 2 — External services.** For each service identified at runtime, probe the CLI's presence first, then — if the CLI exists — check whether the service itself is reachable. Service binary present but daemon down → `WARN`, not `FAIL`.
168
167
  4. **Category 3 — System binaries that language packages wrap.** For each one identified in Step 2, check the system binary is on `PATH` (`which <bin>` / `<bin> --version`). The wrapper package itself isn't probed.
169
168
  5. **Category 4 — Hardware, drivers, accelerators.** Walk the layered probe from Step 2 in order (driver → device visibility → SDK → acceleration libs → framework-sees-it → version match), reporting each layer's result separately. Never collapse a multi-layer failure into a single "GPU not available" message — each layer has a different fix.
@@ -186,7 +185,7 @@ For every **FAIL** and **WARN** item, the report must include four columns:
186
185
  | **Status** | "missing", "outdated (found X, need Y)", "binary present but service not running", or "env var not set". |
187
186
  | **How to install** | OS-specific command(s). See [Install suggestions](#install-suggestions) below. |
188
187
 
189
- Group the report into the same six probe groups used in Step 3 (toolchains, codeplain/git, services, system binaries, hardware, credentials), so the user can fix one whole layer at a time.
188
+ Group the report into the same six probe groups used in Step 3 (toolchains, shell/git/plain-parser, services, system binaries, hardware, credentials), so the user can fix one whole layer at a time.
190
189
 
191
190
  End the report with a one-liner reminder: `Re-run check-plain-env after installing missing items to confirm.`
192
191
 
@@ -213,7 +212,6 @@ The tables below are a **starter set** of well-known install commands for the th
213
212
  | Docker | Install Docker Desktop from https://docker.com/products/docker-desktop |
214
213
  | `pg_config` (psycopg) | comes with `brew install postgresql@16` |
215
214
  | Xcode CLT | `xcode-select --install` |
216
- | `codeplain` | follow the install instructions at https://codeplain.ai |
217
215
 
218
216
  ### Linux (Debian/Ubuntu)
219
217
 
@@ -263,7 +261,7 @@ When the host OS detection in Step 1 was inconclusive (e.g. an unrecognized Linu
263
261
  - **Don't pre-bake a catalog.** Derive the requirement list at runtime from the project in front of you. A hard-coded "things to always check" table becomes wrong the moment any project deviates from the assumed shape — and every project does.
264
262
  - **Don't probe testing framework binaries as if they were independent.** `pytest`, `jest`, `vitest`, `phpunit`, `junit-console`, etc. are installed by the package manager via `requirements.txt` / `package.json` / `pom.xml`. The toolchain check (Category 1) is enough — the framework binary itself is out of scope.
265
263
  - **Don't probe in silence.** Use the `terminal` tool and capture the actual command output (version strings, exit codes). Telling the user "looks like Python is installed" without running `python3 --version` is guessing.
266
- - **Don't print secret values.** Check whether `CODEPLAIN_API_KEY`, `DATABASE_URL`, etc. are set, not what they contain. Use `printenv VAR >/dev/null` not `printenv VAR`.
264
+ - **Don't print secret values.** Check whether `DATABASE_URL`, API keys, etc. are set, not what they contain. Use `printenv VAR >/dev/null` not `printenv VAR`.
267
265
  - **Don't install anything on the user's behalf.** Even when the install command is obvious. The user needs to opt in.
268
266
  - **Don't stop at the first FAIL.** Run the full sweep so the report is complete in one pass.
269
267
  - **Don't suggest `curl | sh` installs when a package manager works.** Reserve the upstream installer fallback for cases where no package-manager option exists (e.g. `rustup`, sometimes Go).
@@ -280,6 +278,7 @@ When the host OS detection in Step 1 was inconclusive (e.g. an unrecognized Linu
280
278
  - [ ] Every hardware / accelerator signal ("GPU", "fine-tune", "local inference", "CUDA", "MPS", etc.) triggered the layered Category 4 probe.
281
279
  - [ ] Every requirement probed via the `terminal` tool with an actual version / availability command (no "looks installed" guesses).
282
280
  - [ ] GPU / accelerator chains probed layer-by-layer (driver → device visibility → toolkit → acceleration libs → framework-sees-it → version match), with each layer reported separately.
281
+ - [ ] `plain-parser` and Python 3.11 or newer probed (always required).
283
282
  - [ ] Each result classified as PASS / WARN / FAIL.
284
283
  - [ ] Report has the verdict on the first line.
285
284
  - [ ] Every FAIL and WARN row has all four columns: What / Why / Status / How to install.
@@ -4,7 +4,7 @@ description: >-
4
4
  End-to-end `***plain` spec authoring workflow: a short intent interview followed by a gated,
5
5
  one-question-at-a-time interview (product, tech stack, testing) that writes complete .plain
6
6
  specification files to disk incrementally, reviews each addition, and
7
- validates the specs with a dry-run before handoff. Use when the user starts a
7
+ validates the specs with plain-healthcheck before handoff. Use when the user starts a
8
8
  new project or wants to build something new from scratch. Not for adding a
9
9
  feature to an existing project (use add-feature) or for editing generated code
10
10
  (the .plain specs are the source of truth).
@@ -85,7 +85,7 @@ When entering a phase, read its reference file and walk its topics **in order**
85
85
  | 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 |
86
86
  | 2 — What tech should it use? | `references/phase-2-tech.md` | the new `***implementation reqs***` are on disk and approved |
87
87
  | 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 |
88
- | 4 — Validate and hand off | `references/phase-4-validate-handoff.md` | the agent ran `codeplain <module>.plain --dry-run` successfully against the render target, and the user has the render command plus every side-channel command |
88
+ | 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 |
89
89
 
90
90
  Between phases, summarize and get explicit overall confirmation before continuing — the intent brief
91
91
  after Phase 0; the full feature list and module/concept layout after Phase 1; the tech stack and
@@ -112,8 +112,7 @@ Once the initial specs exist, the user will return with new features. Use the `a
112
112
  - **A phase gate is not met** (a review agent returned unmet boxes) → do not advance; fix each gap through the interview → author → review loop, then spawn a fresh review agent and repeat until `APPROVED`.
113
113
  - **A review agent can't be spawned or returns nothing usable** → spawn a fresh one; do not self-certify the gate. Never `SendMessage` a running reviewer — capture its returned result. If spawning genuinely fails in this environment, walk the phase's checklist block inline as a fallback and tell the user the review was self-run.
114
114
  - **`check-plain-env` returns `FAIL`** (Phase 3) → walk each gap with the user; install, swap to an alternative, or explicitly acknowledge it before Phase 4. Re-invoke after any install.
115
- - **`plain-healthcheck` returns `FAIL`** (Phase 4) → do not present the render command; work through its numbered list with the right edit skill and re-run until it passes.
116
- - **Environment failure** (`codeplain` not on PATH, `CODEPLAIN_API_KEY` unset) → tell the user exactly what is missing and how to fix it; never pretend the check passed.
115
+ - **`plain-healthcheck` returns `FAIL`** (Phase 4) → do not hand off the render target; work through its numbered list with the right edit skill and re-run until it passes.
117
116
 
118
117
  ## Self-check checklist
119
118
 
@@ -63,5 +63,5 @@ A box is met only when its source confirms it. Return `APPROVED` only when every
63
63
 
64
64
  - [ ] **[disk]** Identified the render target — the last module in the dependency chain (or the single module).
65
65
  - [ ] **[both]** Ran `init-config-file` to build the final `config.yaml`(s); resolved any precondition gap with the user before validating.
66
- - [ ] **[record]** Ran `plain-healthcheck`; worked its numbered list to `PASS` — never presented the render command on a `FAIL`.
67
- - [ ] **[record]** Presented the render command only after the dry-run passed, plus every side-channel script actually generated in Phase 3.
66
+ - [ ] **[record]** Ran `plain-healthcheck`; worked its numbered list to `PASS` — never handed off the render target on a `FAIL`.
67
+ - [ ] **[record]** Handed off the render target only after `plain-healthcheck` passed, plus every side-channel script actually generated in Phase 3.
@@ -55,7 +55,7 @@ When all topics are complete, recap the full testing strategy: which `config.yam
55
55
 
56
56
  Delegate environment verification to the `check-plain-env` skill — do **not** probe the machine inline. It is the single source of truth for "can this machine render and test this project?" and derives the requirement list at runtime from the project's `.plain` files, `test_scripts/`, `config.yaml`(s), and `resources/`.
57
57
 
58
- `check-plain-env` detects the host OS, builds the requirement list (language toolchains + their package managers, external services, system binaries that language packages wrap, hardware / drivers / accelerators, `codeplain` itself, credentials — the layers a package manager **cannot** install), probes each with a real version / availability command, and emits a `PASS` / `WARN` / `FAIL` report with OS-specific install commands for any gaps. It never probes individual language packages (`torch`, `numpy`, `FastAPI`, `react`, JARs, gems) — those are installed by the project's own `prepare_environment` / unit-test scripts the moment they run. It is read-only and installs nothing itself.
58
+ `check-plain-env` detects the host OS, builds the requirement list (language toolchains + their package managers, external services, system binaries that language packages wrap, hardware / drivers / accelerators, credentials — the layers a package manager **cannot** install), probes each with a real version / availability command, and emits a `PASS` / `WARN` / `FAIL` report with OS-specific install commands for any gaps. It never probes individual language packages (`torch`, `numpy`, `FastAPI`, `react`, JARs, gems) — those are installed by the project's own `prepare_environment` / unit-test scripts the moment they run. It is read-only and installs nothing itself.
59
59
 
60
60
  Act on the return value:
61
61
 
@@ -1,6 +1,6 @@
1
1
  # Phase 4 — Validate and hand off
2
2
 
3
- Two halves. First **the agent** validates every spec end-to-end with a `codeplain` dry-run so the user never wastes a real render — or any debugging time — on a fixable static error. Only after that passes does the agent **hand off** the render command (plus any side-channel commands) to the user.
3
+ Two halves. First **the agent** validates every spec with `plain-healthcheck` so the user never wastes a real render — or any debugging time — on a fixable static error. Only after that passes does the agent **hand off** the render target (plus any side-channel commands) to the user.
4
4
 
5
5
  ## 4a. Identify the render target
6
6
 
@@ -14,36 +14,34 @@ Find the **last module in the dependency chain** — the module that is not `req
14
14
  Finalize the project's `config.yaml` file(s) before validation. Phase 3 may have written provisional entries as scripts were generated; **this** is where they are consolidated into the canonical form the renderer expects. Invoke the `init-config-file` skill. It:
15
15
 
16
16
  - enumerates every part of the project (one `config.yaml` per part — single-stack → root config; multi-part → one config per part),
17
- - assembles only the **valid** config keys derived from the `codeplain` CLI parser,
18
- - emits a clean YAML file per part (script paths first, then template/build folders, then copy/log settings),
17
+ - assembles only the **valid** config keys,
18
+ - emits a clean YAML file per part (script paths first, then template/build folders),
19
19
  - verifies every `*-script` value resolves to a real file on disk,
20
- - refuses to write secrets (`api-key`) or per-invocation flags (`dry-run`, `full-plain`, `render-range`, `render-from`, `replay-with`) into the config.
20
+ - refuses to write secrets or per-invocation options into the config.
21
21
 
22
22
  If `init-config-file` stops because a precondition isn't met (e.g. a `prepare-environment-script` exists but no conformance script does), resolve the gap with the user before continuing — do **not** hand a known-broken config to `plain-healthcheck`.
23
23
 
24
24
  ## 4c. Validate the project with `plain-healthcheck`
25
25
 
26
- Run the `plain-healthcheck` skill — the single source of truth for "is this project ready to render?". Do **not** run the dry-run inline. It:
26
+ Run the `plain-healthcheck` skill — the single source of truth for "is this project ready to render?". Do **not** run its checks inline. It:
27
27
 
28
28
  - inventories every `.plain` module and identifies every top module,
29
29
  - validates every `config.yaml` (existence, parseability, script paths actually pointing at files in `test_scripts/`, no mixed stacks), and
30
- - runs `codeplain <top>.plain --dry-run` for **every** top module with the correct `--config-name` for multi-part projects.
30
+ - 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
31
+ - checks every module for what the parser can't judge (section ownership, bullets, linked-resource contents).
31
32
 
32
- The skill runs the full detect → fix → re-run loop itself (syntax errors, undefined concepts, broken `import` / `requires` chains, cyclic definitions, missing templates, complexity violations, conflicting reqs, config drift, missing scripts) and returns only once everything passes or a gap genuinely needs the user. Then:
33
+ 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:
33
34
 
34
35
  - **`PASS`** → move on to step 4d.
35
36
  - **`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.
36
- - **Environment failure** (e.g. `codeplain` not on PATH, `CODEPLAIN_API_KEY` not set) → `plain-healthcheck` surfaces this as a clearly-marked environment failure. Tell the user exactly what's missing and how to fix it before continuing. Do not pretend the healthcheck passed.
37
37
 
38
- ## 4d. Present the render command
38
+ ## 4d. Hand off the render target
39
39
 
40
- Only after the dry-run passes, tell the user their specs are ready and present the render command:
40
+ Only after `plain-healthcheck` passes, tell the user their specs are ready and that `<module>` is the module to render:
41
41
 
42
- ```
43
- codeplain <module>.plain
44
- ```
42
+ - Chain `base.plain → features.plain → integrations.plain` → render `integrations.plain`.
43
+ - Single module `my_app.plain` → render `my_app.plain`.
45
44
 
46
- - Chain `base.plain → features.plain → integrations.plain` → `codeplain integrations.plain`.
47
- - Single module `my_app.plain` → `codeplain my_app.plain`.
45
+ **Default renderer: pyro.** Ask the user whether to render `<module>` now with pyro's `render-spec` skill (`/render-spec <module>.plain`, or `/pyro:render-spec <module>.plain` when pyro is installed as a Claude Code plugin), and invoke it only if they agree. If no `render-spec` skill is available in this session, tell the user pyro is not installed and point them to https://github.com/plainlang/pyro#installation. The user may render with any other ***plain renderer instead.
48
46
 
49
47
  Also remind the user of any **side-channel commands** they may want to run themselves per the Phase 3 testing strategy — for example `./test_scripts/run_unittests.sh <module>`, `./test_scripts/prepare_environment.sh <module>`, or `./test_scripts/run_conformance_tests.sh <module> <conformance_tests_folder>`. Mention only the scripts that were actually generated in Phase 3.