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.
- package/README.md +17 -26
- package/bin/cli.mjs +7 -4
- package/bin/python-tools.mjs +139 -0
- package/forge/rules/integration-embedded-testing.md +1 -1
- package/forge/rules/linked-resources.md +5 -2
- package/forge/skills/add-feature/SKILL.md +2 -1
- package/forge/skills/add-feature/references/checklist.md +1 -1
- package/forge/skills/add-resource/SKILL.md +5 -3
- package/forge/skills/check-forge-version/SKILL.md +1 -1
- package/forge/skills/check-forge-version/scripts/check-forge-version.mjs +1 -1
- package/forge/skills/check-plain-env/SKILL.md +7 -8
- package/forge/skills/forge-plain/SKILL.md +3 -4
- package/forge/skills/forge-plain/references/checklist.md +2 -2
- package/forge/skills/forge-plain/references/phase-3-testing.md +1 -1
- package/forge/skills/forge-plain/references/phase-4-validate-handoff.md +13 -15
- package/forge/skills/implement-conformance-testing-script/SKILL.md +3 -3
- package/forge/skills/implement-prepare-environment-script/SKILL.md +3 -3
- package/forge/skills/implement-unit-testing-script/SKILL.md +3 -3
- package/forge/skills/init-config-file/SKILL.md +22 -134
- package/forge/skills/init-plain-project/SKILL.md +5 -5
- package/forge/skills/init-plain-project/references/checklist.md +2 -2
- package/forge/skills/load-plain-reference/SKILL.md +3 -4
- package/forge/skills/load-plain-reference/references/project-model.md +1 -1
- package/forge/skills/plain-healthcheck/SKILL.md +42 -33
- package/package.json +2 -2
- package/forge/skills/load-codeplain-reference/SKILL.md +0 -188
- 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
|
|
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
|
|
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`,
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
305
|
-
| `plain-healthcheck` | Verification gate: validates every `config.yaml`, confirms each `*-script` field points at a real file in `test_scripts/`,
|
|
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),
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 **
|
|
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
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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,
|
|
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 —
|
|
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,
|
|
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 `
|
|
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
|
|
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` |
|
|
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
|
|
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
|
|
67
|
-
- [ ] **[record]**
|
|
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,
|
|
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
|
|
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
|
|
18
|
-
- emits a clean YAML file per part (script paths first, then template/build folders
|
|
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
|
|
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
|
|
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 `
|
|
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,
|
|
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.
|
|
38
|
+
## 4d. Hand off the render target
|
|
39
39
|
|
|
40
|
-
Only after
|
|
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
|
-
|
|
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
|
-
|
|
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.
|