plain-forge 1.0.12 → 1.0.14
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 +32 -23
- package/bin/cli.mjs +105 -3
- package/forge/rules/definitions.md +17 -0
- package/forge/rules/impl-reqs.md +2 -0
- package/forge/rules/linked-resources.md +0 -1
- package/forge/rules/test-reqs.md +3 -0
- package/forge/skills/run-codeplain/SKILL.md +19 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,6 +95,27 @@ Each deprecated file is confirmed individually before it's deleted — you'll se
|
|
|
95
95
|
|
|
96
96
|
Installs that predate the manifest (anyone who installed before this feature existed) have no manifest to read. `update` still finds them by their skill footprint: if the `forge-plain`, `add-feature`, `debug-specs`, and `load-plain-reference` skills are all present in an agent directory, it's treated as a plain-forge install. Such installs are refreshed without pruning (overwrite-only), and gain a manifest going forward so later updates can prune.
|
|
97
97
|
|
|
98
|
+
#### Removing an install
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx plain-forge uninstall
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`uninstall` reads the install manifest (`<agent-dir>/.plain-forge/manifest.json`) and deletes **exactly** the files plain-forge wrote, then the manifest itself, then any directory left empty (including the agent directory). Your own skills and third-party content are never in the manifest, so they are never touched.
|
|
105
|
+
|
|
106
|
+
By default it removes **every** agent layout in the **project** scope (the current folder). Narrow it with flags:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npx plain-forge uninstall --agent claude --scope global
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
| Flag | Default | Values |
|
|
113
|
+
|------|---------|--------|
|
|
114
|
+
| `--agent` | `*` (all agents) | `claude`, `codex`, `forgecode`, `universal`, or `*` |
|
|
115
|
+
| `--scope` | `project` | `project` (cwd) or `global` (home directory) |
|
|
116
|
+
|
|
117
|
+
If an install has **no manifest** (e.g. one that predates manifests), `uninstall` cannot tell which files are plain-forge's, so it refuses to delete anything: it prints an error, lists the directories to clean up by hand, and exits non-zero. Refresh such an install with `update` first (which writes a manifest going forward), then `uninstall`.
|
|
118
|
+
|
|
98
119
|
## Usage
|
|
99
120
|
|
|
100
121
|
### Prerequisites
|
|
@@ -154,36 +175,24 @@ Hit a bug in the rendered app, a failing test, or behavior that doesn't match wh
|
|
|
154
175
|
|
|
155
176
|
## Repository Structure
|
|
156
177
|
|
|
157
|
-
plain-forge keeps a single canonical source of truth under `forge
|
|
178
|
+
plain-forge keeps a single canonical source of truth under `forge/`. The `plain-forge install` CLI copies that content straight into whichever agent directory you choose — there is no build step and no generated, committed output to keep in sync.
|
|
158
179
|
|
|
159
180
|
```
|
|
160
|
-
forge/ # canonical,
|
|
181
|
+
forge/ # canonical content, copied verbatim on install
|
|
161
182
|
skills/ # all skills used during spec writing
|
|
162
|
-
rules/ #
|
|
163
|
-
|
|
164
|
-
runtimes/ # per-runtime adapters
|
|
165
|
-
claude/
|
|
166
|
-
build.ts # generates .claude/ + .claude-plugin/ from forge/
|
|
167
|
-
templates/ # Claude-specific files: settings.json, hook script, plugin manifests
|
|
168
|
-
codex/
|
|
169
|
-
build.ts # generates .codex-plugin/ and .agents/plugins/ (manifest points at forge/skills/)
|
|
170
|
-
templates/ # Codex-specific files: plugin.json, marketplace catalog
|
|
171
|
-
opencode/
|
|
172
|
-
build.ts # generates .opencode/ from forge/
|
|
173
|
-
templates/ # OpenCode-specific files: package.json, .gitignore
|
|
183
|
+
rules/ # spec-writing rules (installed as workspace instructions)
|
|
174
184
|
|
|
175
185
|
bin/
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
#
|
|
180
|
-
|
|
181
|
-
.
|
|
182
|
-
.codex-plugin/ # Codex plugin manifest (its "skills" field points at forge/skills/)
|
|
183
|
-
.agents/plugins/ # Codex marketplace catalog
|
|
184
|
-
.opencode/ # OpenCode plugin layout
|
|
186
|
+
cli.mjs # the `plain-forge` CLI — `install` and `update` commands
|
|
187
|
+
|
|
188
|
+
test/
|
|
189
|
+
cli.test.mjs # tests for the install / update CLI
|
|
190
|
+
|
|
191
|
+
package.json # ships only `bin/cli.mjs` and `forge/` to npm
|
|
185
192
|
```
|
|
186
193
|
|
|
194
|
+
On `install`, the CLI reads `forge/skills` and `forge/rules` and writes them into the chosen agent directory (`.claude/`, `.codex/`, `.forgecode/`, or `.agents/`), recording every file it wrote in `<agent-dir>/.plain-forge/manifest.json` so `update` can later refresh and prune precisely.
|
|
195
|
+
|
|
187
196
|
## Available Skills
|
|
188
197
|
|
|
189
198
|
### Core Workflow
|
package/bin/cli.mjs
CHANGED
|
@@ -78,8 +78,9 @@ function usage() {
|
|
|
78
78
|
console.log(`Usage: plain-forge <command> [options]
|
|
79
79
|
|
|
80
80
|
Commands:
|
|
81
|
-
install
|
|
82
|
-
update
|
|
81
|
+
install Install plain-forge into an agent directory
|
|
82
|
+
update Refresh every existing plain-forge install in cwd and $HOME
|
|
83
|
+
uninstall Remove a plain-forge install using its manifest
|
|
83
84
|
|
|
84
85
|
Install options:
|
|
85
86
|
--agent <claude|codex|forgecode|universal> Target agent layout
|
|
@@ -90,16 +91,25 @@ Update options:
|
|
|
90
91
|
-y, --yes Remove deprecated files without
|
|
91
92
|
confirming each one
|
|
92
93
|
|
|
94
|
+
Uninstall options:
|
|
95
|
+
--agent <claude|codex|forgecode|universal|*> Which install to remove
|
|
96
|
+
(default: * — every agent)
|
|
97
|
+
--scope <project|global> Where to look (default: project)
|
|
98
|
+
|
|
93
99
|
Examples:
|
|
94
100
|
plain-forge install --agent claude --scope project
|
|
95
101
|
plain-forge install --agent universal --scope global
|
|
96
102
|
plain-forge update
|
|
97
103
|
plain-forge update --yes
|
|
104
|
+
plain-forge uninstall
|
|
105
|
+
plain-forge uninstall --agent claude --scope global
|
|
98
106
|
|
|
99
107
|
"install" fails if plain-forge is already installed at the target — use
|
|
100
108
|
"update" to refresh it. Missing install flags are prompted interactively.
|
|
101
109
|
"update" auto-detects installs and prunes only files plain-forge wrote
|
|
102
|
-
(confirming each removal), leaving your own and third-party skills untouched
|
|
110
|
+
(confirming each removal), leaving your own and third-party skills untouched.
|
|
111
|
+
"uninstall" deletes exactly the files recorded in the install manifest, then
|
|
112
|
+
the manifest itself; an install with no manifest is reported and left in place.`);
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
function parseArgs(argv) {
|
|
@@ -488,6 +498,95 @@ async function cmdUpdate(args) {
|
|
|
488
498
|
}
|
|
489
499
|
}
|
|
490
500
|
|
|
501
|
+
async function cmdUninstall(args) {
|
|
502
|
+
printBanner();
|
|
503
|
+
|
|
504
|
+
const scope = args.scope ?? "project";
|
|
505
|
+
if (!SCOPES.includes(scope)) {
|
|
506
|
+
console.error(`unknown scope "${scope}". valid: ${SCOPES.join(", ")}`);
|
|
507
|
+
process.exit(2);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Default agent is "*" — every agent layout. A named agent narrows to one.
|
|
511
|
+
const agentArg = args.agent ?? "*";
|
|
512
|
+
let agents;
|
|
513
|
+
if (agentArg === "*" || agentArg === "all") {
|
|
514
|
+
agents = Object.keys(AGENTS);
|
|
515
|
+
} else if (Object.hasOwn(AGENTS, agentArg)) {
|
|
516
|
+
agents = [agentArg];
|
|
517
|
+
} else {
|
|
518
|
+
console.error(
|
|
519
|
+
`unknown agent "${agentArg}". valid: ${Object.keys(AGENTS).join(", ")}, or "*" for all`,
|
|
520
|
+
);
|
|
521
|
+
process.exit(2);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const root = scope === "global" ? os.homedir() : process.cwd();
|
|
525
|
+
|
|
526
|
+
let found = 0;
|
|
527
|
+
let removed = 0;
|
|
528
|
+
let hadError = false;
|
|
529
|
+
|
|
530
|
+
for (const agent of agents) {
|
|
531
|
+
const baseDir = path.join(root, AGENTS[agent]);
|
|
532
|
+
if (!fs.existsSync(baseDir)) continue;
|
|
533
|
+
|
|
534
|
+
const manifest = readManifest(baseDir);
|
|
535
|
+
const legacy = !manifest && hasForgeSignature(baseDir);
|
|
536
|
+
if (!manifest && !legacy) continue; // not a plain-forge install
|
|
537
|
+
found++;
|
|
538
|
+
|
|
539
|
+
// No manifest → we have no record of which files are ours, so deleting
|
|
540
|
+
// would risk touching the user's own content. Refuse and point at the
|
|
541
|
+
// directories to clean by hand.
|
|
542
|
+
if (!manifest) {
|
|
543
|
+
hadError = true;
|
|
544
|
+
console.error(`cannot uninstall ${agent} (${scope}) — ${baseDir}`);
|
|
545
|
+
console.error(
|
|
546
|
+
` the install manifest (${MANIFEST_REL}) is missing, so automatic deletion is not supported.`,
|
|
547
|
+
);
|
|
548
|
+
console.error(` please remove plain-forge's files manually from:`);
|
|
549
|
+
for (const dir of CONTENT_DIRS) {
|
|
550
|
+
const p = path.join(baseDir, dir);
|
|
551
|
+
if (fs.existsSync(p)) console.error(` ${p}`);
|
|
552
|
+
}
|
|
553
|
+
console.error("");
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
let deleted = 0;
|
|
558
|
+
let failed = 0;
|
|
559
|
+
for (const rel of manifest.files) {
|
|
560
|
+
if (deleteForgeFile(baseDir, rel)) deleted++;
|
|
561
|
+
else failed++;
|
|
562
|
+
}
|
|
563
|
+
// Finally remove the manifest itself, then prune the now-empty .plain-forge
|
|
564
|
+
// directory and the agent directory if nothing else remains in it.
|
|
565
|
+
fs.rmSync(manifestPathFor(baseDir), { force: true });
|
|
566
|
+
removeEmptyDirsUpward(
|
|
567
|
+
path.join(baseDir, path.dirname(MANIFEST_REL)),
|
|
568
|
+
baseDir,
|
|
569
|
+
);
|
|
570
|
+
removeEmptyDirsUpward(baseDir, root);
|
|
571
|
+
|
|
572
|
+
console.log(`uninstalled ${agent} (${scope}) from ${baseDir}`);
|
|
573
|
+
console.log(
|
|
574
|
+
` removed ${deleted} file(s)${failed ? `, ${failed} could not be removed` : ""} + manifest`,
|
|
575
|
+
);
|
|
576
|
+
console.log();
|
|
577
|
+
removed++;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (found === 0) {
|
|
581
|
+
console.log(`no plain-forge installation found in ${root} (scope: ${scope}).`);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (removed > 0) {
|
|
585
|
+
console.log(`uninstalled ${removed} installation(s).`);
|
|
586
|
+
}
|
|
587
|
+
if (hadError) process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
|
|
491
590
|
function printNextSteps(agent) {
|
|
492
591
|
const bold = (s) => `\x1b[1;97m${s}\x1b[0m`;
|
|
493
592
|
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
@@ -548,6 +647,9 @@ async function main() {
|
|
|
548
647
|
case "update":
|
|
549
648
|
await cmdUpdate(args);
|
|
550
649
|
break;
|
|
650
|
+
case "uninstall":
|
|
651
|
+
await cmdUninstall(args);
|
|
652
|
+
break;
|
|
551
653
|
default:
|
|
552
654
|
console.error(`unknown command "${cmd}"`);
|
|
553
655
|
usage();
|
|
@@ -63,6 +63,23 @@ Bad — circular:
|
|
|
63
63
|
- Nest attributes and constraints as sub-bullets
|
|
64
64
|
- Do not use programming language constructs (generics, annotations, framework types) in definitions
|
|
65
65
|
|
|
66
|
+
## Concept granularity and technical components
|
|
67
|
+
- Definitions are not restricted to high-level domain objects. When an implementation warrants abstract, technical, or infrastructural components to clarify responsibilities, it is desired and encouraged to define them.
|
|
68
|
+
- Group related technical concepts using the `of` keyword or by stating their relationship explicitly.
|
|
69
|
+
|
|
70
|
+
Example of defining technical components alongside their behavior:
|
|
71
|
+
|
|
72
|
+
```plain
|
|
73
|
+
- :CsvToJsonConverter: bidirectionally converts csv and json strings.
|
|
74
|
+
- :CsvToJson: of :CsvToJsonConverter: converts a list of csv strings (e.g., "value1,value2,value3") to a list of json strings (e.g., "{\"key1\":\"value1\"}").
|
|
75
|
+
- :JsonToCsv: of :CsvToJsonConverter: converts a list of json strings (e.g., "{\"key1\":\"value1\"}") to a list of csv strings (e.g., "value1,value2").
|
|
76
|
+
|
|
77
|
+
- :StorageService: is an abstraction for a remote blob storage backend.
|
|
78
|
+
- :CsvFileWriter: writes CSV rows to local files, automatically rotates to new files when size limits are reached, and flushes completed files to :StorageService:.
|
|
79
|
+
- :CsvFileReader: reads CSV rows in a batched manner.
|
|
80
|
+
- :CsvJoiner: joins csvs and uses :CsvFileWriter: to create the merged CSV.
|
|
81
|
+
```
|
|
82
|
+
|
|
66
83
|
## Format
|
|
67
84
|
|
|
68
85
|
```plain
|
package/forge/rules/impl-reqs.md
CHANGED
|
@@ -39,6 +39,7 @@ When writing or editing an `***implementation reqs***` section in a `.plain` fil
|
|
|
39
39
|
- Unit-test guidance (`:UnitTests:` framework, structure, mocking) goes **here**, not in `***test reqs***`
|
|
40
40
|
- `***test reqs***` is exclusively for `:ConformanceTests:` — framework, execution command, mocking policy, environment setup
|
|
41
41
|
- Putting unit-test guidance in `***test reqs***` is one of the most common authoring mistakes; the rendered code will silently miss those requirements because the unit-test generator only reads `***implementation reqs***`
|
|
42
|
+
- Unit testing run scripts should be linked here as a linked resource
|
|
42
43
|
|
|
43
44
|
## Encapsulation warning
|
|
44
45
|
- `requires` modules only receive functional specs from their dependencies — not implementation reqs
|
|
@@ -60,4 +61,5 @@ When writing or editing an `***implementation reqs***` section in a `.plain` fil
|
|
|
60
61
|
- :Implementation: should be in Python 3.12.
|
|
61
62
|
- :Implementation: should use pip for dependency management.
|
|
62
63
|
- When writing CSV files, :Implementation: should use streaming writes to avoid holding large datasets in memory.
|
|
64
|
+
- :UnitTests: are executed via the run script [test_scripts/run_unittests_python.sh](test_scripts/run_unittests_python.sh).
|
|
63
65
|
```
|
|
@@ -62,5 +62,4 @@ If the markdown-link target ends with `/`, contains `://`, resolves to a directo
|
|
|
62
62
|
|
|
63
63
|
## File location and path resolution
|
|
64
64
|
- Paths are resolved relative to the `.plain` file's directory
|
|
65
|
-
- Only files in the same folder (and subfolders) can be linked
|
|
66
65
|
- The conventional location is `resources/` under the `.plain` file's directory
|
package/forge/rules/test-reqs.md
CHANGED
|
@@ -18,6 +18,7 @@ When writing or editing a `***test reqs***` section in a `.plain` file, always f
|
|
|
18
18
|
- `:ConformanceTests:` live outside the generated codebase (typically in a separate project under `conformance_tests/<module>/`), so requirements that shape them belong in test reqs by definition
|
|
19
19
|
- The conformance-test generator reads **only** `***test reqs***` — anything about `:ConformanceTests:` placed elsewhere (e.g. `***implementation reqs***`) is silently ignored
|
|
20
20
|
- Author each `:ConformanceTests:` requirement via `add-test-requirement` and phrase it in terms of `:ConformanceTests:` so the partition stays visible at a glance
|
|
21
|
+
- Conformance testing run scripts should be linked here as a linked resource
|
|
21
22
|
|
|
22
23
|
## What belongs here
|
|
23
24
|
- Test framework: which framework to use (e.g., pytest, Unittest, xUnit)
|
|
@@ -50,4 +51,6 @@ When writing or editing a `***test reqs***` section in a `.plain` file, always f
|
|
|
50
51
|
- :ConformanceTests: will be run using "pytest" command.
|
|
51
52
|
- :ConformanceTests: must be implemented and executed - do not skip tests.
|
|
52
53
|
- :ConformanceTests: should mock all external HTTP calls.
|
|
54
|
+
- :ConformanceTests: are preapred via the preapre script [test_scripts/prepare_environment_python.sh](test_scripts/prepare_environment_python.sh).
|
|
55
|
+
- :ConformanceTests: are executed via the run script [test_scripts/run_conformance_tests_python.sh](test_scripts/run_conformance_tests_python.sh).
|
|
53
56
|
```
|
|
@@ -81,13 +81,9 @@ Before launching anything:
|
|
|
81
81
|
|
|
82
82
|
Keep this synthesis short (5–10 bullets, project-specific). It is the single most important reference for the classifier in [Spec-deviation classification](#spec-deviation-classification). The healthcheck only checks that the scripts *exist* and are *referenced correctly*; this step is the only place that reads what they actually *do*.
|
|
83
83
|
|
|
84
|
-
## Launch
|
|
84
|
+
## Launch always in `--headless` mode
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
### Mode A — `--headless`, agent-launched (default; preferred for supervised runs)
|
|
89
|
-
|
|
90
|
-
The agent launches the renderer in the background and supervises via the log file + filesystem. The terminal tool **cannot** drive an interactive TUI, so TUI mode is incompatible with agent-launched rendering. Use `--headless`:
|
|
86
|
+
The agent must always launch the renderer in the background and supervise it via the log file and filesystem. The terminal tool **cannot** drive an interactive TUI, so TUI mode is incompatible with agent-launched rendering. Always use `--headless`:
|
|
91
87
|
|
|
92
88
|
```bash
|
|
93
89
|
nohup codeplain <module>.plain --headless <other-flags> > /dev/null 2>&1 &
|
|
@@ -96,12 +92,6 @@ echo $! # capture the PID
|
|
|
96
92
|
|
|
97
93
|
Record the PID. Confirm `codeplain.log` starts growing within a few seconds; if it doesn't, the launch failed — check the user's shell / API key and re-try.
|
|
98
94
|
|
|
99
|
-
### Mode B — user runs the TUI in another terminal; agent attaches
|
|
100
|
-
|
|
101
|
-
The user has, or wants, the TUI running in a separate terminal of their own. The agent does not launch anything — it only monitors. Confirm the PID with the user (`pgrep -fl codeplain` is the easy way) and confirm the log path. Everything else in the workflow is identical.
|
|
102
|
-
|
|
103
|
-
Never try to "screenshot" or "read" the TUI directly — you can't see it. Treat the TUI as opaque; rely on log + filesystem signals.
|
|
104
|
-
|
|
105
95
|
## Log monitoring contract
|
|
106
96
|
|
|
107
97
|
The monitor loop in Phase 1 leans almost entirely on `codeplain.log`. Before describing the loop, here is exactly how to read this file efficiently and what to look for. Treat this section as the reference the loop calls into.
|
|
@@ -189,11 +179,11 @@ Use these as cheap, idempotent ways to interrogate the log. Each is safe to run
|
|
|
189
179
|
|
|
190
180
|
## Reading test-script output
|
|
191
181
|
|
|
192
|
-
The renderer drives three external shell scripts during a run — `unittests-script`, `prepare-environment-script`, and `conformance-tests-script` (whichever are declared in the project's `config.yaml`). Each script wraps a real test framework (vitest, jest, pytest, tsc, etc.) and writes verbose output.
|
|
182
|
+
The renderer drives three external shell scripts during a run — `unittests-script`, `prepare-environment-script`, and `conformance-tests-script` (whichever are declared in the project's `config.yaml`). Each script wraps a real test framework (vitest, jest, pytest, tsc, etc.) and writes verbose output. It reads the output those scripts produce while the renderer is iterating. Three places to look, in priority order:
|
|
193
183
|
|
|
194
184
|
### 1. Inline in `codeplain.log` (the primary source)
|
|
195
185
|
|
|
196
|
-
With verbose logging on (the default — `--verbose` is `
|
|
186
|
+
With verbose logging on (the default — `--verbose` is `disabled`), codeplain captures each script's stdout/stderr and writes it into `codeplain.log` between its own wrapper lines:
|
|
197
187
|
|
|
198
188
|
```
|
|
199
189
|
INFO:codeplain:Running unit tests script .../unit_testing_typescript.sh. (attempt: 1)
|
|
@@ -233,10 +223,6 @@ This is the renderer telling you, in its own words, what it tried and what is st
|
|
|
233
223
|
|
|
234
224
|
Refresh these files on each pass while a retry loop is active — the renderer rewrites them between attempts.
|
|
235
225
|
|
|
236
|
-
### 3. The TUI (last resort, agent can't see it)
|
|
237
|
-
|
|
238
|
-
If the user is running in Mode B (TUI visible to them, not to you), the script output is also painted on the TUI live. The agent cannot read it directly. When you need it and it isn't in `codeplain.log`, **ask the user to paste the latest test-output panel of the TUI**, or to relaunch in `--headless` so the log captures everything.
|
|
239
|
-
|
|
240
226
|
## Spec-deviation classification
|
|
241
227
|
|
|
242
228
|
This is the most important active judgment the skill makes. Once a functionality enters a retry loop (unit-test or conformance), the renderer iterates — changing source code, and sometimes changing its own conformance tests — trying to satisfy the test-script output. **Not every iteration is honest.** Each iteration must be classified before deciding whether to let the renderer continue, stop and fix the spec, or stop and fix the test script.
|
|
@@ -247,7 +233,7 @@ The inputs to the classifier are:
|
|
|
247
233
|
2. The **test-script output** for the most recent attempt, from `codeplain.log` per the section above.
|
|
248
234
|
3. The renderer's own **`.memory/conformance_test_memory/<id>.json`** for this functionality (when present).
|
|
249
235
|
4. The newest **conformance test file** the renderer just edited under `conformance_tests/<module>/<fname>/`.
|
|
250
|
-
5. The newest **implementation file(s)** the renderer just edited under `plain_modules/<module
|
|
236
|
+
5. The newest **implementation file(s)** the renderer just edited under `plain_modules/<module>/`.
|
|
251
237
|
|
|
252
238
|
With these in hand, place the current iteration into one of four buckets:
|
|
253
239
|
|
|
@@ -267,11 +253,11 @@ Action: let the renderer keep going, up to the threshold (5 conformance attempts
|
|
|
267
253
|
Signals — any one of these is enough:
|
|
268
254
|
|
|
269
255
|
- The **same test** fails every attempt with the **same assertion**, and the assertion checks something the functional spec **does not explicitly say** (e.g. exact exit code, exact error wording, specific status field).
|
|
270
|
-
- `current_issue_summary` in memory describes a question the spec does not answer (e.g. "unclear whether `--help` should exit 0 or 2").
|
|
256
|
+
- `current_issue_summary` in memory describes a question the spec does not answer (e.g. "unclear whether `--help` should exit 0 or 2").
|
|
271
257
|
- The renderer's `fix_attempt_summary` keeps oscillating between two opposite interpretations of the same requirement (regex variant A this attempt, variant B next attempt, A again the attempt after).
|
|
272
258
|
- The conformance test file is being rewritten between attempts in a way that **changes which behavior is being asserted**, not just how it is asserted.
|
|
273
259
|
|
|
274
|
-
Action: stop the renderer (SIGINT). Hand off to `debug-specs
|
|
260
|
+
Action: stop the renderer (SIGINT). Hand off to `debug-specs`. The spec must be tightened so there is only one valid behavior to converge on. Then resume with `--render-from <N>` for the offending functionality.
|
|
275
261
|
|
|
276
262
|
### Bucket 3 — Renderer is drifting away from the spec (stop immediately)
|
|
277
263
|
|
|
@@ -282,10 +268,12 @@ The dangerous case. Signals:
|
|
|
282
268
|
- The renderer's `key_learnings` reads like rationalization rather than diagnosis (e.g. "adjusted assertion to match observed behavior", "removed strict check that was causing failures").
|
|
283
269
|
- Failure count is going **up** between attempts.
|
|
284
270
|
|
|
285
|
-
Action: stop the renderer **immediately**, don't wait for the threshold.
|
|
271
|
+
Action: stop the renderer **immediately**, don't wait for the threshold. The fix must be applied to the `.plain` file (the spec was probably not specific enough and the renderer chose the path of least resistance):
|
|
272
|
+
- If it happened during the **conformance testing phase**, the `***test reqs***` need to be tightened.
|
|
273
|
+
- If it happened during the rendering of a **functional requirement**, the functional specs, definitions, or `***impl reqs***` need to change.
|
|
274
|
+
- If it happened during **acceptance testing**, the acceptance test definitions need to be tightened.
|
|
286
275
|
|
|
287
|
-
|
|
288
|
-
2. **Generated-code side** — do nothing; the broken code under `plain_modules/` will be overwritten on resume. **Never** hand-edit it.
|
|
276
|
+
Do nothing on the generated-code side; the broken code under `plain_modules/` will be overwritten on resume. **Never** hand-edit it.
|
|
289
277
|
|
|
290
278
|
Then resume with `--render-from <N>` (or `--force-render` if the drift contaminated earlier functionalities). Re-running `plain-healthcheck` first is mandatory.
|
|
291
279
|
|
|
@@ -298,7 +286,10 @@ Signals:
|
|
|
298
286
|
- The script is failing **before** running any framework command at all (`die: prepared environment missing`, missing argument, etc.).
|
|
299
287
|
- The failure mode is identical across functionalities, not specific to the spec being rendered.
|
|
300
288
|
|
|
301
|
-
Action:
|
|
289
|
+
Action: This is **not** a spec problem and the spec should stay as is. Stop the renderer and investigate why the script fails:
|
|
290
|
+
- If it's a bug in the script (e.g., pathing issue, syntax error, missing environment variable load), attempt to fix the script directly, or hand off to the matching `implement-*-testing-script` skill.
|
|
291
|
+
- If it's a missing host dependency (e.g., Python or Node is not installed, or a required global package is missing), inform the user and ask them to install it.
|
|
292
|
+
- **Before resuming the renderer**, test the script by running it manually exactly as the renderer would. If the same infrastructure/script issue persists, the fix didn't work. If the script now runs (even if it reports legitimate test failures), the fix was successful.
|
|
302
293
|
|
|
303
294
|
### Classifier discipline
|
|
304
295
|
|
|
@@ -371,7 +362,7 @@ Do **not** treat hitting the threshold as the diagnosis. From `ATTEMPT_COUNTER >
|
|
|
371
362
|
- **Bucket 1** — keep going until the threshold; report the per-attempt failure count.
|
|
372
363
|
- **Bucket 2** — stop now (regardless of how close to the threshold you are); hand off to `debug-specs` / `add-implementation-requirement` / inline spec edit. The spec is under-specified.
|
|
373
364
|
- **Bucket 3** — stop **immediately**, do not wait for the threshold; the renderer is drifting. Tighten the spec or acceptance test before resuming.
|
|
374
|
-
- **Bucket 4** — the test script is the problem; route to Pathology
|
|
365
|
+
- **Bucket 4** — the test script is the problem; route to Pathology E instead.
|
|
375
366
|
|
|
376
367
|
### Pathology B — `Functional spec too complex!`
|
|
377
368
|
|
|
@@ -385,19 +376,11 @@ The renderer can't resolve a reference. Stop is automatic. Hand off to `add-conc
|
|
|
385
376
|
|
|
386
377
|
Renderer surfaces a conflict, or you can see two specs in the same module that contradict each other in the generated code. Hand off to `resolve-spec-conflict`.
|
|
387
378
|
|
|
388
|
-
### Pathology E —
|
|
389
|
-
|
|
390
|
-
No new bytes appended to `codeplain.log` for **>2 minutes** *and* the process is still alive. Detect it via `stat -f %m codeplain.log` (mtime) and `wc -c` (size unchanged across passes). Usually the API is slow; occasionally the renderer is wedged. Action:
|
|
391
|
-
|
|
392
|
-
1. Confirm the process is still alive (`ps -p <pid>`).
|
|
393
|
-
2. Confirm the API is reachable (`curl -sSI https://api.codeplain.ai` or whatever `--api` points at).
|
|
394
|
-
3. Wait one more cadence cycle. If the log is still frozen, ask the user whether to stop.
|
|
395
|
-
|
|
396
|
-
### Pathology F — Test scripts themselves are broken
|
|
379
|
+
### Pathology E — Test scripts themselves are broken
|
|
397
380
|
|
|
398
381
|
Symptom: log shows the unit-test or conformance-test **script** itself failing (non-zero exit, syntax error, missing dependency) on every attempt, rather than the renderer's code failing the tests. The fix is **not** a spec change — it is a `test_scripts/` change. Stop and hand off to the matching `implement-*-testing-script` skill, or have the user repair the script directly.
|
|
399
382
|
|
|
400
|
-
### Pathology
|
|
383
|
+
### Pathology F — Unit-test retry loop
|
|
401
384
|
|
|
402
385
|
Less common than the conformance loop, but the same shape: `Running unit tests script ... (attempt: <N>)` with `<N>` climbing for the same functionality. Threshold: **3 attempts**. From `ATTEMPT_COUNTER >= 2` onwards, run the same [Spec-deviation classification](#spec-deviation-classification) classifier and act on the bucket it returns. The most common root causes for unit-test loops are an implementation req that doesn't match the unit-test scaffolding (Bucket 2), or test reqs / framework usage that the renderer is satisfying by weakening assertions (Bucket 3). Hand-off is typically `add-implementation-requirement` or `debug-specs`.
|
|
403
386
|
|