plain-forge 1.0.20 → 1.0.21-next.55f13d7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +1 -0
  2. package/bin/cli.mjs +1 -1
  3. package/forge/rules/bullet-continuation.md +16 -6
  4. package/forge/rules/concise-specs-examples.md +431 -0
  5. package/forge/rules/concise-specs-integration-examples.md +272 -0
  6. package/forge/rules/concise-specs.md +80 -0
  7. package/forge/rules/definitions.md +7 -4
  8. package/forge/rules/exported-concepts.md +3 -2
  9. package/forge/rules/func-specs.md +10 -3
  10. package/forge/rules/impl-reqs.md +8 -4
  11. package/forge/rules/import-modules.md +4 -6
  12. package/forge/rules/integration-embedded.md +0 -1
  13. package/forge/rules/integration-standalone.md +1 -1
  14. package/forge/rules/integrations.md +6 -7
  15. package/forge/rules/linked-resources.md +9 -8
  16. package/forge/rules/module-structure.md +105 -0
  17. package/forge/rules/required-concepts.md +5 -5
  18. package/forge/rules/requires-modules.md +37 -21
  19. package/forge/rules/test-reqs.md +11 -7
  20. package/forge/skills/add-acceptance-test/SKILL.md +1 -0
  21. package/forge/skills/add-concept/SKILL.md +2 -1
  22. package/forge/skills/add-feature/SKILL.md +2 -2
  23. package/forge/skills/add-functional-spec/SKILL.md +2 -1
  24. package/forge/skills/add-functional-specs/SKILL.md +2 -1
  25. package/forge/skills/add-implementation-requirement/SKILL.md +2 -1
  26. package/forge/skills/add-test-requirement/SKILL.md +1 -0
  27. package/forge/skills/break-down-func-spec/SKILL.md +1 -0
  28. package/forge/skills/check-forge-version/SKILL.md +154 -0
  29. package/forge/skills/check-forge-version/scripts/check-forge-version.mjs +368 -0
  30. package/forge/skills/create-import-module/SKILL.md +3 -3
  31. package/forge/skills/create-requires-module/SKILL.md +24 -19
  32. package/forge/skills/forge-plain/SKILL.md +1 -1
  33. package/forge/skills/forge-plain/references/checklist.md +2 -1
  34. package/forge/skills/forge-plain/references/phase-1-product.md +3 -2
  35. package/forge/skills/forge-plain/references/phase-2-tech.md +1 -1
  36. package/forge/skills/load-plain-reference/SKILL.md +20 -2
  37. package/forge/skills/load-plain-reference/references/project-model.md +0 -11
  38. package/forge/skills/plain-healthcheck/SKILL.md +1 -1
  39. package/forge/skills/refactor-module/SKILL.md +3 -3
  40. package/forge/skills/resolve-section-ownership/SKILL.md +65 -0
  41. package/forge/skills/resolve-spec-conflict/SKILL.md +1 -0
  42. package/package.json +5 -5
@@ -0,0 +1,105 @@
1
+ ---
2
+ description: File-level structure of .plain modules — frontmatter, sections, ordering, and section ownership
3
+ ---
4
+
5
+ # Rules for `.plain` module structure
6
+
7
+ When creating a new `.plain` file or reviewing the structure of a whole file, always follow these rules. Section *content* rules live in the per-section rule files linked below — this file covers only file-level structure.
8
+
9
+ ## File skeleton
10
+ - A `.plain` file is one **module**: optional YAML frontmatter between `---` markers, followed by sections marked with `***section name***` headers
11
+ - The frontmatter, when present, must be the first thing in the file
12
+ - Every content line inside a section must be a `- ` list item — see `bullet-continuation.md`
13
+ - Every line must be as short and plain as its content allows — see `concise-specs.md`
14
+
15
+ ## Frontmatter fields
16
+
17
+ | Field | Purpose | Rule file |
18
+ |---|---|---|
19
+ | `description` | One-line summary of the module; recommended on every module | — |
20
+ | `import` | Pull definitions, implementation reqs, and test reqs from import modules | `import-modules.md` |
21
+ | `requires` | Attach the module to the build chain of another root module | `requires-modules.md` |
22
+ | `exported_concepts` | Declare which of this module's concepts are visible to modules that `require` it | `exported-concepts.md` |
23
+ | `required_concepts` | Declare concepts an import module expects the importing module to define | `required-concepts.md` |
24
+
25
+ ## Sections
26
+ - There are exactly four top-level section markers: `***definitions***`, `***implementation reqs***`, `***test reqs***`, and `***functional specs***`
27
+ - Each section appears **at most once** per file; all are optional (which ones are allowed depends on the module kind below)
28
+ - `***acceptance tests***` is **never a top-level section** — it appears only nested under a single functional spec (see `func-specs.md`)
29
+ - Write sections in canonical order: `***definitions***` → `***implementation reqs***` → `***test reqs***` → `***functional specs***`
30
+ - Definitions come first because every concept must be defined before it is referenced
31
+ - Functional specs come last because they are rendered incrementally and their nested acceptance tests close the file
32
+
33
+ ## Module kinds
34
+ - A **root module** lives at the repository root and carries behavior: it contains `***functional specs***`
35
+ - To be renderable, a root module needs at least one functional spec plus the implementation reqs to build it — its own or from an `import`ed module (implementation reqs are not inherited through `requires`). A frontmatter-only file is a valid scaffold, but it cannot be rendered until a functional spec is added
36
+ - A **requires module** is a root module that is used by another root module via `requires` directive (see `requires-modules.md`)
37
+ - An **import module** provides shared content that other modules pull in via `import`: it contains only `***definitions***`, `***implementation reqs***`, and/or `***test reqs***` — never `***functional specs***`, never `requires` (see `import-modules.md`)
38
+
39
+ ## Section ownership (where a fact lives)
40
+ - The renderer reads each kind of fact **only from its owning section** — a fact placed in the wrong section is silently ignored, not flagged
41
+ - Before writing any requirement, place it by content:
42
+ - To move facts that are already misplaced, or to split a bullet carrying several kinds of fact, use the `resolve-section-ownership` skill
43
+
44
+ | Content | Owning section | Rule file |
45
+ |---|---|---|
46
+ | Concepts (`:CamelCaseToken:`) | `***definitions***` | `definitions.md` |
47
+ | HOW the software is built — tech stack, architecture, coding standards — and **everything about `:UnitTests:`** | `***implementation reqs***` | `impl-reqs.md` |
48
+ | **Everything about `:ConformanceTests:`** — framework, run command, mocking and network policy | `***test reqs***` | `test-reqs.md` |
49
+ | WHAT the software does — observable, language-agnostic behavior | `***functional specs***` | `func-specs.md` |
50
+ | End-to-end workflow verification of one functional spec | nested `***acceptance tests***` | `func-specs.md` |
51
+
52
+ ## Comments
53
+ - Lines beginning with `>` are ignored when rendering
54
+
55
+ ```plain
56
+ > This is a comment in ***plain.
57
+ ```
58
+
59
+ - Comments explain the specification to human authors — never use a comment to carry a requirement the renderer must implement
60
+
61
+ ## Liquid templates
62
+ - `.plain` files are processed with [Liquid](https://shopify.github.io/liquid/) before parsing — the full Liquid syntax (variables, filters, conditionals, loops) is available in any section
63
+ - Expansion is **textual** and happens before the specification is interpreted — unlike `import`, which includes another module semantically (see `import-modules.md`)
64
+
65
+ ```plain
66
+ {% assign max_tags = 3 %}
67
+
68
+ ***functional specs***
69
+
70
+ - A :Widget: can be created with up to {{ max_tags }} tags.
71
+ {% if max_tags > 1 %}
72
+ - A :Widget: can have its tags listed.
73
+ {% endif %}
74
+ ```
75
+
76
+ ## Format
77
+
78
+ ```plain
79
+ ---
80
+ description: Command-line task manager.
81
+ import:
82
+ - airplain
83
+ ---
84
+
85
+ ***definitions***
86
+
87
+ - :Task: is an activity tracked by the application.
88
+ - Name (required)
89
+
90
+ - :TaskList: is the collection of all :Task: items. Initially empty.
91
+
92
+ ***implementation reqs***
93
+
94
+ - :Implementation: keeps :TaskList: in memory.
95
+
96
+ ***test reqs***
97
+
98
+ - :ConformanceTests: must not access the network.
99
+
100
+ ***functional specs***
101
+
102
+ - A :Task: can be added to the :TaskList:.
103
+
104
+ - The :TaskList: is displayed.
105
+ ```
@@ -9,14 +9,14 @@ When adding or editing `required_concepts` in a `.plain` file's frontmatter, alw
9
9
  ## What required_concepts does
10
10
  - `required_concepts` declares concepts that any module importing this file **must** define
11
11
  - It creates a contract: the import module references these concepts but does not define them — the importing module is responsible for providing definitions
12
- - This is used exclusively on import modules and templates
12
+ - This is used exclusively on import modules
13
13
 
14
14
  ## When to use it
15
- - Use `required_concepts` when an import module or template (in `template/`) references concepts that vary per project or per importing module
15
+ - Use `required_concepts` when an import module references concepts that vary per project or per importing module
16
16
  - The import module can reference these concepts in its definitions, implementation reqs, or test reqs — but their actual definitions come from whoever imports the file
17
17
 
18
18
  ## Importing module must satisfy the contract
19
- - Every concept listed in `required_concepts` must be defined in the importing module's own `***definitions***` section
19
+ - Every concept listed in `required_concepts` must be defined somewhere visible to the importing module — in its own `***definitions***` section or in another module it imports
20
20
  - If the importing module does not define a required concept, the spec is invalid
21
21
  - Check `required_concepts` of all imported modules before finalizing a module
22
22
 
@@ -29,7 +29,7 @@ When adding or editing `required_concepts` in a `.plain` file's frontmatter, alw
29
29
  ```plain
30
30
  ---
31
31
  required_concepts: [":AppName:", ":AppConfig:"]
32
- description: Template that requires AppName and AppConfig to be defined by the importer
32
+ description: Imported module that requires AppName and AppConfig to be defined by the importer
33
33
  ---
34
34
 
35
35
  ***definitions***
@@ -39,6 +39,6 @@ description: Template that requires AppName and AppConfig to be defined by the i
39
39
  - :MainFile: should load :AppConfig: on startup.
40
40
  ```
41
41
 
42
- In this example, `:AppName:` and `:AppConfig:` are referenced but not defined — the module that imports this template must define them.
42
+ In this example, `:AppName:` and `:AppConfig:` are referenced but not defined — the module that imports this import module must define them.
43
43
 
44
44
  List concepts as a YAML array with each concept in `:ConceptName:` notation.
@@ -4,40 +4,56 @@ description: Rules for creating and using requires modules in .plain files
4
4
 
5
5
  # Rules for requires modules
6
6
 
7
- When creating or editing a `.plain` file that uses `requires`, always follow these rules:
7
+ When creating or editing a `.plain` file that uses `requires`, or one that is referenced in another `.plain` file's `requires` directive, always follow these rules:
8
8
 
9
9
  ## What requires does
10
- - `requires` establishes a **build ordering** — the required module is built before the current one
11
- - The required module's generated code (`plain_modules/<required_module>/code`) is copied as the starting point
12
- - The required module's `***functional specs***` become visible as **previous functional specs** — this property **is transitive**
13
- - Only `exported_concepts` from the required module are available — not its full definitions — and this property **is not transitive**
10
+
11
+ - `requires` attaches the current module to the project's **build chain**: the required module is built first, and its generated code is copied as the starting point for the current module
12
+ - The copied code is the required module's **accumulated** output — it already contains the code of all of that module's own ancestors
13
+ - The `***functional specs***` of the required module **and of all its ancestors** become visible as **previous functional specs** — this visibility **is transitive** down the whole chain
14
+ - Of a required module's definitions, only the concepts listed in its `exported_concepts` are visible; the module's other definitions stay internal. Each exported concept arrives with its full definition text
15
+ - Export visibility **is not transitive**: an ancestor's exports are visible only if that ancestor is itself listed in `requires`
16
+
17
+ ## Chain topology
18
+
19
+ - All modules connected by `requires` form a single **tree** with a common root: code accumulates along each branch, and branches never reconverge
20
+ - **Diamond joins are prohibited.** A module must not require two modules from divergent branches — there is no way to merge two divergent codebases into one starting point
21
+ - When `requires` lists multiple modules, every entry must lie on **one root-to-tip ancestor path**: each listed module must be an ancestor of the deepest one
22
+ - The **deepest entry is the attachment point** — it alone determines the build order and the starting code. The other entries add nothing to ordering; they exist **only** to make those ancestors' `exported_concepts` visible
23
+ - Branching outward is fine: two modules may each require the same parent, creating independent branches (and therefore independent top modules)
14
24
 
15
25
  ## Tech stack must match (hard rule)
26
+
16
27
  - Because the required module's generated code is copied as the starting point and the renderer continues building on top of it with a single toolchain, two modules can only be linked with `requires` when they target the **same language, framework, and runtime**
17
28
  - A runtime / network dependency between systems is **not** a reason to use `requires`
18
29
  - Example of the mistake: a React frontend that talks to a Python/FastAPI backend over HTTP must **not** `requires: [backend]` — the stacks differ
19
- - Model that pair as two independent root modules (each with its own `config.yaml` and test scripts) and express the contract through a shared API schema in `resources/` or shared concepts in an `import`ed template — never through `requires`
30
+ - Model that pair as two independent root modules (each with its own `config.yaml` and test scripts) and express the contract through a shared API schema in `resources/` or shared concepts in an `import`ed module — never through `requires`
20
31
 
21
- ## Build order, not necessarily dependency
22
- - The current module does not need to extend or depend on the required module's code
23
- - The two modules may be completely independent
24
- - `requires` ensures the build order is correct for the project as a whole
32
+ ## Building upon but not necessarily extension
33
+
34
+ - The current module does not need to functionally extend the required module — it may address a completely different concern
35
+ - Even so, the two are never independent artifacts: the current module's output physically contains the required module's code, because both are stages of one accumulating codebase
36
+ - Use `requires` to place a module at the right point in that lineage, whether or not it builds on the required module's functionality
25
37
 
26
38
  ## Conflict prevention
27
- - The current module's functional specs must not conflict with the required module's specs
28
- - The required module's specs are treated as previous requirements — the renderer sees them as context
29
- - Review the required module's functional specs before adding new ones
30
39
 
31
- ## No access to full definitions
32
- - Only concepts listed in the required module's `exported_concepts` are available
33
- - Other concepts from the required module are internal and invisible
34
- - If you need shared definitions, use `import` for that — not `requires`
40
+ - The current module's functional specs must not conflict with the specs of **any module in its ancestor chain** — all of them are previous functional specs
41
+ - The renderer sees the whole chain's specs as prior requirements/context when rendering the current module
42
+ - Review the ancestor chain's functional specs before adding new ones
43
+
44
+ ## Exports are the only visible definitions
45
+
46
+ - Only concepts listed in a required module's `exported_concepts` are available, and only for modules explicitly listed in `requires`
47
+ - Each exported concept is available with its full definition text
48
+ - Other concepts from required modules are internal and invisible
49
+ - To reference a concept exported by an ancestor deeper in the chain, list that ancestor in `requires` as well
50
+ - If you need shared definitions across branches or stacks, use `import` for that — not `requires`
35
51
 
36
52
  ## File locations
53
+
37
54
  - Modules that use `requires` live at the **repository root** — they are functional modules with specs
38
55
  - `requires` paths point to other root-level modules (e.g., `auth`, `messaging`)
39
- - The default import directory is `template/` — the `template/` prefix is not needed in import paths (e.g., `airplain`)
40
- - Never `require` a template — templates are for `import` only
56
+ - Never `require` an import module — import modules are for `import` only
41
57
 
42
58
  ## Format
43
59
 
@@ -47,11 +63,11 @@ requires:
47
63
  - auth
48
64
  import:
49
65
  - airplain
50
- description: Module built after auth, importing shared definitions
66
+ description: Module built on top of auth, importing shared definitions
51
67
  ---
52
68
  ```
53
69
 
54
- A module can require multiple modules:
70
+ A module can list multiple required modules **only when they lie on one ancestor path**. Here `messaging` itself requires `auth`, so `auth` is an ancestor of `messaging`; the module attaches after `messaging` and lists `auth` only to see its exports:
55
71
 
56
72
  ```plain
57
73
  ---
@@ -28,20 +28,24 @@ When writing or editing a `***test reqs***` section in a `.plain` file, always f
28
28
 
29
29
  ## Test type reference
30
30
 
31
- | Test type | Where to specify | Purpose |
32
- |-----------|-----------------|---------|
33
- | Unit tests | `***implementation reqs***` | Test individual functionalities in isolation |
34
- | Conformance tests | `***test reqs***` | Verify implementation conforms to the full spec |
35
- | Acceptance tests | `***acceptance tests***` under a functional spec | Verify a specific functional spec |
31
+ | Test type | Where to specify | Generated | Purpose |
32
+ |-----------|-----------------|-----------|---------|
33
+ | Unit tests | `***implementation reqs***` | Automatically | Test individual functionalities in isolation |
34
+ | Conformance tests | `***test reqs***` | Automatically | Verify each functionality's implementation against its functional spec |
35
+ | Acceptance tests | `***acceptance tests***` under a functional spec | Manually | Verify implementation of a functional spec through a specific end-to-end scenario |
36
36
 
37
37
  ## No duplication
38
38
  - Do not duplicate guidance already present in the file or its imports
39
- - Check imported templates before adding a new test req
39
+ - Check imported modules before adding a new test req
40
40
 
41
41
  ## Concept references
42
42
  - Reference predefined concepts like `:ConformanceTests:` where appropriate
43
43
  - All other referenced `:Concepts:` must be defined in `***definitions***`
44
44
 
45
+ ## Conciseness
46
+ - State each req in the fewest plain words that stay exact — see `concise-specs.md`
47
+ - Do not restate the default behavior of the test framework this section already names
48
+
45
49
  ## Format
46
50
 
47
51
  ```plain
@@ -50,6 +54,6 @@ When writing or editing a `***test reqs***` section in a `.plain` file, always f
50
54
  - :ConformanceTests: will be run using "pytest" command.
51
55
  - :ConformanceTests: must be implemented and executed - do not skip tests.
52
56
  - :ConformanceTests: should mock all external HTTP calls.
53
- - :ConformanceTests: are preapred via the preapre script [test_scripts/prepare_environment_python.sh](test_scripts/prepare_environment_python.sh).
57
+ - :ConformanceTests: are prepared via the prepare script [test_scripts/prepare_environment_python.sh](test_scripts/prepare_environment_python.sh).
54
58
  - :ConformanceTests: are executed via the run script [test_scripts/run_conformance_tests_python.sh](test_scripts/run_conformance_tests_python.sh).
55
59
  ```
@@ -95,6 +95,7 @@ Acceptance tests extend conformance tests and are implemented according to the `
95
95
  - [ ] Nested under the correct functional spec
96
96
  - [ ] Indentation is correct (`***acceptance tests***` indented under the spec)
97
97
  - [ ] Each test describes a concrete, verifiable outcome
98
+ - [ ] Trimmed per `concise-specs.md`: plain words, no obvious steps, no restatement of the parent spec
98
99
  - [ ] Tests are scoped to the parent functional spec only
99
100
  - [ ] Written in language-agnostic terms
100
101
  - [ ] No duplication with existing acceptance tests on the same spec
@@ -36,7 +36,7 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule
36
36
  - CamelCase, starting with an uppercase letter
37
37
  - Valid characters: letters, digits, `+`, `-`, `.`, `_`
38
38
  - Must be globally unique across the spec and all its imports
39
- - Exported concepts from `requires` modules are **not transitive** — if a concept needs to be shared across multiple `requires` modules, define it in a common import module instead
39
+ - Exported concepts from `requires` modules are **not transitive** — to reference an ancestor's export, list that ancestor in `requires`; if a concept is shared across branches, define it in a common import module instead
40
40
 
41
41
  ## Definition Format
42
42
 
@@ -68,4 +68,5 @@ Attributes and constraints are nested sub-bullets:
68
68
  - [ ] All referenced concepts (`:OtherConcept:`) are already defined above
69
69
  - [ ] No circular references between concepts
70
70
  - [ ] Description is clear, concise, and language-agnostic
71
+ - [ ] Trimmed per `concise-specs.md`: one plain sentence, no restatement of the name, no rationale
71
72
  - [ ] Placed inside a `***definitions***` section
@@ -66,7 +66,7 @@ Ask one writable question per `AskUserQuestion` call, targeting exactly one of:
66
66
  - **A concept** — a new concept, or one single attribute of an existing one.
67
67
  - **A single edge case** — one invalid input, empty state, or boundary value.
68
68
  - **A single constraint** — one business rule, permission, ordering, or size limit.
69
- - **Implementation guidance** — only when the functionality needs technology / a library / a pattern not already in the file or its imports.
69
+ - **Implementation guidance** — a policy that shapes how the functionality is built (retry, error handling, data format), or technology / a library / a pattern not already in the file or its imports.
70
70
  - **Verification** — only when the *Conformance gate* below is satisfied: one concrete outcome that proves this functionality works.
71
71
 
72
72
  Never bundle a second question into the prompt; never ask a question whose answer doesn't translate into a writable snippet on its own.
@@ -77,7 +77,7 @@ Route each answer to the right edit skill — never hand-author a `***plain` sec
77
77
 
78
78
  - **New concept** → `add-concept` (into `***definitions***`; define before any reference).
79
79
  - **New functional spec** → `add-functional-spec`. It runs `analyze-if-func-spec-too-complex` and `analyze-func-specs` for you — let it. **Never hand-author functional specs.** If it reports the spec is too complex, ask the user a follow-up question to split it (the next loop iteration) — do not break it down alone.
80
- - **New implementation req** → `add-implementation-requirement`. Only when the answer introduces technology / a library / a data format / an architectural pattern not already present.
80
+ - **New implementation req** → `add-implementation-requirement`. When the answer states HOW the functionality is built — a retry or error-handling policy, a data format, a library, an architectural pattern — and it is not already in the file or its imports.
81
81
  - **New acceptance test** → `add-acceptance-test`, under the relevant functional spec. Only when the *Conformance gate* is satisfied and the answer describes a concrete verification.
82
82
  - **New test req** → `add-test-requirement`. Only when conformance testing is configured and the answer changes how conformance tests are run.
83
83
 
@@ -19,7 +19,7 @@ Always use the skill `load-plain-reference` to retrieve the ***plain syntax rule
19
19
 
20
20
  1. **Identify the target `.plain` file.** If ambiguous, ask the user.
21
21
  2. **Read the entire file** to understand existing definitions, implementation reqs, and all current functional specs (including those in `requires` modules).
22
- 3. **Draft the functional spec** following the rules below.
22
+ 3. **Draft the functional spec** following the rules below, then run the review pass in `concise-specs.md` over the draft — trim it to the shortest wording that keeps every fact before any other check.
23
23
  4. **Analyze complexity** — use the `analyze-if-func-spec-too-complex` skill to verify the drafted spec implies ≤ 200 LOC. If too complex, use the `break-down-func-spec` skill to split it, then repeat from step 3 for each resulting spec.
24
24
  5. **Check for conflicts** with every existing functional spec — this is critical. Run `analyze-func-specs` **once** with the new spec plus all existing specs (in the file and in any `requires` chain) as a single batch. The batched analyzer returns every conflicting pair in one call — do **not** invoke a pair-by-pair analyzer. For each conflicting pair it reports, run `resolve-spec-conflict` on that pair; re-run `analyze-func-specs` over the touched set after each resolution until the verdict is `COMPATIBLE`.
25
25
  6. **Append the spec** to the end of the `***functional specs***` section (specs are chronological; new ones go last).
@@ -80,5 +80,6 @@ If the functional spec needs verification, use the `add-acceptance-test` skill t
80
80
  - [ ] All external interfaces are explicit (endpoint paths, methods, CLI args, formats, etc.)
81
81
  - [ ] All referenced `:Concepts:` are defined in `***definitions***`
82
82
  - [ ] Sentences are short, clear, and unambiguous
83
+ - [ ] Trimmed per `concise-specs.md`: nothing obvious, no rationale, no uncheckable adjectives or hedges, plain words throughout
83
84
  - [ ] No redundancy with existing specs
84
85
  - [ ] Placed in correct chronological position (usually at the end)
@@ -28,7 +28,7 @@ This skill is `add-functional-spec` applied **one spec at a time, in order**, wi
28
28
 
29
29
  1. **Identify the target `.plain` file.** If ambiguous, ask the user.
30
30
  2. **Read the entire file** to understand existing definitions, implementation reqs, and all current functional specs (including those reachable via `import` / `requires`).
31
- 3. **Draft all the specs you intend to add**, in the chronological order they should appear. Keep the drafts in a working list — nothing is inserted yet.
31
+ 3. **Draft all the specs you intend to add**, in the chronological order they should appear. Keep the drafts in a working list — nothing is inserted yet. Run the review pass in `concise-specs.md` over every draft before continuing; trim each to the shortest wording that keeps every fact.
32
32
  4. **For each drafted spec, in order:**
33
33
  1. **Analyze complexity.** Run `analyze-if-func-spec-too-complex` on the spec. If the verdict is `TOO COMPLEX`, run `break-down-func-spec`, replace the entry in the working list with the resulting smaller specs (preserving order), and restart this step on the first of the replacements.
34
34
  2. **Check for conflicts.** Run `analyze-func-specs` **once** with this single input set:
@@ -114,5 +114,6 @@ If any of the newly added functional specs need verification, use the `add-accep
114
114
  - [ ] All external interfaces explicit (endpoint paths, methods, CLI args, formats, etc.)
115
115
  - [ ] All referenced `:Concepts:` are defined in `***definitions***`
116
116
  - [ ] Sentences are short, clear, and unambiguous
117
+ - [ ] Every spec trimmed per `concise-specs.md`: nothing obvious, no rationale, no uncheckable adjectives or hedges, plain words throughout
117
118
  - [ ] No redundancy with existing specs or within the batch
118
119
  - [ ] Specs placed in correct chronological positions
@@ -66,7 +66,7 @@ Implementation reqs are bullet points in the `***implementation reqs***` section
66
66
 
67
67
  - :Implementation: should use pip for dependency management.
68
68
 
69
- - When writing CSV files, :Implementation: should use streaming writes to avoid holding large datasets in memory.
69
+ - :ReportExporter: writes a :CsvFile: with streaming writes, never holding the full dataset in memory.
70
70
 
71
71
  ```
72
72
 
@@ -82,5 +82,6 @@ Reference defined `:Concepts:` where they add clarity. Implementation reqs in no
82
82
  - [ ] Describes HOW to build, not WHAT to build
83
83
  - [ ] All referenced `:Concepts:` are defined in `***definitions***`
84
84
  - [ ] Does not duplicate guidance already present in the file or its imports
85
+ - [ ] Trimmed per `concise-specs.md`: plain words, no rationale, no standard behavior of a technology already named
85
86
  - [ ] Placed inside a `***implementation reqs***` section
86
87
  - [ ] No behavioral requirements that should be in `***functional specs***`
@@ -69,5 +69,6 @@ Reference predefined concepts like `:ConformanceTests:` and any defined `:Concep
69
69
  - [ ] Describes conformance testing concerns, not unit tests or behavior
70
70
  - [ ] All referenced `:Concepts:` are defined (or are predefined like `:ConformanceTests:`)
71
71
  - [ ] Does not duplicate guidance already present in the file or its imports
72
+ - [ ] Trimmed per `concise-specs.md`: plain words, no rationale, no default framework behavior
72
73
  - [ ] Placed inside a `***test reqs***` section
73
74
  - [ ] No behavioral requirements that should be in `***functional specs***`
@@ -201,5 +201,6 @@ If the original spec had acceptance tests, redistribute them to the most appropr
201
201
  - [ ] All `:Concepts:` referenced in replacement specs are defined
202
202
  - [ ] Any extracted technical component is defined as a concept, built by its own earlier spec, and only **referenced** (not re-described) by the original
203
203
  - [ ] Replacement specs are language-agnostic
204
+ - [ ] Replacement specs trimmed per `concise-specs.md` — a split is not a licence to re-add prose
204
205
  - [ ] All external interfaces remain explicit
205
206
  - [ ] Acceptance tests (if any) have been redistributed or rewritten
@@ -0,0 +1,154 @@
1
+ ---
2
+ name: check-forge-version
3
+ description: >-
4
+ Reports whether the plain-forge installation on this machine is current. Enumerates every agent
5
+ directory plain-forge can install into — project scope and global scope, across all supported
6
+ agent layouts — reads the installed version from each `.plain-forge/manifest.json`, resolves the
7
+ latest published version from the npm registry, and emits a PASS / WARN / FAIL verdict naming
8
+ every install that is current, stale, or unmanaged. For a stale install it surfaces the exact
9
+ `npx plain-forge update` command and asks the user to run it. This concerns the plain-forge
10
+ tooling itself, so it is not tied to any ***plain project or authoring workflow. Use when someone
11
+ asks whether plain-forge is up to date, on the first use of a forge skill in a session, after a
12
+ forge skill or rule behaves unexpectedly, or when a rule referenced in conversation appears to be
13
+ missing. Do not use to perform the update, which stays the user's decision. Do not use to check a
14
+ project's host tooling, which is what `check-plain-env` covers.
15
+ ---
16
+
17
+ # Check Forge Version
18
+
19
+ This skill answers one question: **is the plain-forge installed on this machine the latest published
20
+ version?** It reads the installed version from every install's manifest, compares each against the
21
+ `latest` dist-tag on npm, and — when an install is behind — asks the user to run
22
+ `npx plain-forge update`.
23
+
24
+ The subject is **plain-forge itself**, not any project it is used on. This skill reads no `.plain`
25
+ files, needs no ***plain project in the working directory, and is equally valid in an empty
26
+ directory. Do not invoke `load-plain-reference` for it; no authoring rules apply.
27
+
28
+ ## When to run
29
+
30
+ - **On the first use of any forge skill in a session** — the cheapest moment to discover that the
31
+ skills and rules about to be followed are from a superseded release.
32
+ - **On demand** — whenever the user asks whether plain-forge is up to date, which version is
33
+ installed, or where it is installed.
34
+ - **After a forge skill or rule behaves unexpectedly** — a skill that contradicts the documented
35
+ behavior, or a rule file that is missing entirely, is most often a stale install rather than a bug.
36
+ - **When a skill or rule named in conversation does not exist on disk** — a newer release probably
37
+ added it.
38
+ - **After a plain-forge release is announced**, to confirm the update actually landed in every scope.
39
+
40
+ ## What this skill does NOT do
41
+
42
+ - It does **not** run `npx plain-forge update`, or install, or uninstall anything. It reports and
43
+ asks; acting is the user's decision.
44
+ - It does **not** modify any file — not a manifest, not a skill, not a rule. It is read-only apart
45
+ from the network read against the npm registry.
46
+ - It does **not** check project host tooling, runtimes, services, or credentials. That is
47
+ `check-plain-env`.
48
+ - It does **not** validate `.plain` specs. That is `plain-healthcheck`.
49
+ - It does **not** resolve which agent the user *should* install into. It reports what is present.
50
+
51
+ ## Workflow
52
+
53
+ ### Step 1 — Run the check
54
+
55
+ Run the bundled script with the `terminal` tool, passing its path relative to this `SKILL.md`:
56
+
57
+ ```
58
+ node scripts/check-forge-version.mjs
59
+ ```
60
+
61
+ **Run it from the project root** whose project-scope install should be checked, giving the full path
62
+ to the script — for a Claude layout, `node .claude/skills/check-forge-version/scripts/check-forge-version.mjs`.
63
+ The script always reports the install it lives in and the global installs regardless of where it is
64
+ invoked, but it can only find a *project-scope* install in the current working directory. When the
65
+ report is missing an install that is known to exist, re-run it from that project's root before
66
+ concluding anything.
67
+
68
+ Add `--json` for a machine-readable report, or `--offline` to list the installs without querying npm.
69
+
70
+ The script is the single source of truth for this check. It mirrors `bin/cli.mjs` for both the
71
+ install-path model (including the two global paths that break the usual pattern — ForgeCode's
72
+ `~/forge` and OpenCode's `~/.config/opencode`) and the version comparison. Do not re-derive either by
73
+ hand, and do not substitute an ad-hoc `npm view` plus a guess at where the install lives.
74
+
75
+ ### Step 2 — Read the verdict
76
+
77
+ The verdict is the **first line of stdout**, and it is also the exit code:
78
+
79
+ | Exit | Verdict | Meaning |
80
+ |---|---|---|
81
+ | 0 | `PASS` | Every install found is at the latest version. |
82
+ | 1 | `FAIL` | At least one install is behind the latest version. |
83
+ | 2 | `WARN` | Installs found, but at least one version is indeterminate (no manifest, or a prerelease). |
84
+ | 3 | `ERROR` | The latest version could not be resolved from npm. |
85
+ | 4 | `NONE` | No install found in any candidate directory. |
86
+
87
+ Per-install statuses are `CURRENT`, `STALE`, `UNMANAGED` (a pre-manifest install — `update` adopts
88
+ it and writes a manifest going forward), or `INDETERMINATE`.
89
+
90
+ Each install is labelled with its scope: `self` is the install this skill is running from, `project`
91
+ is one found in the current working directory, and `global` is one under the home directory.
92
+
93
+ ### Step 3 — Report and prompt
94
+
95
+ Relay the verdict to the user, then act on it:
96
+
97
+ - **`PASS`** — state the installed version and that it is current. Stop. Do not suggest an update.
98
+ - **`FAIL`** or **`WARN`** — do not report this as a passing aside. Lead with the recommendation,
99
+ name each affected install by agent, scope, and path, give the installed version and the latest
100
+ version, and **ask the user directly to update before continuing**:
101
+
102
+ ```
103
+ npx plain-forge update
104
+ ```
105
+
106
+ State plainly that **continuing on an outdated plain-forge is not recommended** — its skills and
107
+ rules have been superseded, specs authored against them may not match what the current `codeplain`
108
+ renderer expects, and fixes released since the installed version are not in effect. On `WARN`,
109
+ where the version could not be confirmed, say the install should be treated as outdated until
110
+ proven otherwise.
111
+
112
+ Add that a single `update` run covers every install across both scopes, prunes files that no longer
113
+ ship, and never touches the user's own or third-party skills — so the fix is one command and
114
+ carries no risk to their content.
115
+
116
+ Then **stop and wait for the user** — do not run the update. If the user chooses to continue
117
+ without updating, proceed with the task, but say once that the stale rules are a likely source of
118
+ any surprising behaviour that follows.
119
+ - **`NONE`** — report that plain-forge is not installed here and surface `npx plain-forge install`,
120
+ noting that the script's output lists every directory that was checked.
121
+ - **`ERROR`** — report that the installed versions could not be compared against anything, give the
122
+ reason from the script, and offer the two options: re-run when the network is reachable, or check
123
+ https://www.npmjs.com/package/plain-forge directly. Never guess whether the install is current.
124
+
125
+ When the user agrees to update, they run the command themselves. After it completes, re-run Step 1 to
126
+ confirm the result rather than assuming it worked.
127
+
128
+ ## Error Handling
129
+
130
+ - **Registry unreachable** — the script already falls back from the registry endpoint to
131
+ `npm view plain-forge version`, which succeeds on hosts pointed at an internal mirror. If both
132
+ fail it exits 3; treat the version as unknown and surface the reason instead of assuming staleness.
133
+ - **`node` unavailable** — plain-forge requires Node ≥ 18, so this is itself the finding. Report that
134
+ Node is missing and that no plain-forge command, including `update`, can run without it.
135
+ - **Malformed or hand-edited manifest** — the script treats an unparseable manifest as absent and
136
+ falls back to detecting the install by its skill footprint, reporting it as `UNMANAGED`. Recommend
137
+ `npx plain-forge update`, which rewrites a correct manifest.
138
+ - **An install ahead of the registry** — a local development build reports `CURRENT`, not stale. Do
139
+ not prompt for an update in that case.
140
+ - **Prerelease installed** — a version such as `1.0.21-rc.1` is not dotted-numeric and reports
141
+ `INDETERMINATE`. Prereleases ship under npm's `next` tag, so a comparison against `latest` is not
142
+ meaningful; say so rather than prompting for an update that would move the user off the prerelease.
143
+
144
+ ## Validation Checklist
145
+
146
+ - [ ] `scripts/check-forge-version.mjs` was actually executed; no verdict was inferred from memory or
147
+ from a bare `npm view`.
148
+ - [ ] The verdict reported to the user matches the script's first line and exit code.
149
+ - [ ] Every install the script listed was relayed with its agent, scope, and path.
150
+ - [ ] `npx plain-forge update` was **offered**, never executed by this skill.
151
+ - [ ] Nothing on disk was modified.
152
+ - [ ] No `.plain` file was read and `load-plain-reference` was not invoked — this skill is about the
153
+ tooling, not about any project.
154
+ - [ ] On `ERROR` or `NONE`, no claim was made about whether the install is current.