plain-forge 1.0.13 → 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.
@@ -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
@@ -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
@@ -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 mode — TUI vs `--headless`
84
+ ## Launch always in `--headless` mode
85
85
 
86
- Pick a launch mode with the user before starting.
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. **This skill never reads the script source code.** It reads the output those scripts produce while the renderer is iterating. Three places to look, in priority order:
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 `enabled` by default per the help), codeplain captures each script's stdout/stderr and writes it into `codeplain.log` between its own wrapper lines:
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>/src/`.
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"). The `agent_cli_commander_exit_handling_issue_v2.json` example in this repo — "exit codes were 1 instead of the expected 0 or 2" — is exactly this shape when the spec didn't pin the exit code.
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` or directly to an inline spec edit / `add-implementation-requirement`. 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.
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. Two parallel fixes are needed:
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
- 1. **Spec side** — the spec was probably specific enough; the renderer chose the path of least resistance. Either tighten the acceptance test so the loophole is closed (`add-functional-spec` / inline edit), or add an implementation req that names the requirement explicitly (`add-implementation-requirement`).
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: this is **not** a spec problem. Stop the renderer and hand off to the matching `implement-*-testing-script` skill, or have the user repair the script directly. The spec stays as-is.
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 F instead.
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 — Renderer stalled with no log activity
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 G — Unit-test retry loop
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plain-forge",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "description": "Conversational spec-writing tool for ***plain specification language",
5
5
  "type": "module",
6
6
  "engines": {