plain-forge 1.0.1
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/LICENSE +21 -0
- package/README.md +247 -0
- package/bin/cli.mjs +143 -0
- package/forge/docs/.gitkeep +0 -0
- package/forge/rules/definitions.md +57 -0
- package/forge/rules/exported-concepts.md +39 -0
- package/forge/rules/func-specs.md +72 -0
- package/forge/rules/impl-reqs.md +50 -0
- package/forge/rules/import-modules.md +51 -0
- package/forge/rules/required-concepts.md +45 -0
- package/forge/rules/requires-modules.md +59 -0
- package/forge/rules/test-reqs.md +47 -0
- package/forge/skills/add-acceptance-test/SKILL.md +98 -0
- package/forge/skills/add-concept/SKILL.md +67 -0
- package/forge/skills/add-feature/SKILL.md +136 -0
- package/forge/skills/add-functional-spec/SKILL.md +81 -0
- package/forge/skills/add-functional-specs/SKILL.md +115 -0
- package/forge/skills/add-implementation-requirement/SKILL.md +73 -0
- package/forge/skills/add-resource/SKILL.md +108 -0
- package/forge/skills/add-template/SKILL.md +65 -0
- package/forge/skills/add-test-requirement/SKILL.md +68 -0
- package/forge/skills/analyze-2-func-specs/SKILL.md +102 -0
- package/forge/skills/analyze-func-specs/SKILL.md +124 -0
- package/forge/skills/analyze-if-func-spec-too-complex/SKILL.md +152 -0
- package/forge/skills/break-down-func-spec/SKILL.md +156 -0
- package/forge/skills/check-plain-env/SKILL.md +288 -0
- package/forge/skills/consolidate-concepts/SKILL.md +193 -0
- package/forge/skills/create-import-module/SKILL.md +98 -0
- package/forge/skills/create-requires-module/SKILL.md +104 -0
- package/forge/skills/debug-specs/SKILL.md +189 -0
- package/forge/skills/forge-integration/SKILL.md +443 -0
- package/forge/skills/forge-plain/SKILL.md +333 -0
- package/forge/skills/implement-conformance-testing-script/SKILL.md +247 -0
- package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_cypress.ps1 +324 -0
- package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_golang.ps1 +100 -0
- package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +102 -0
- package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.ps1 +92 -0
- package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +100 -0
- package/forge/skills/implement-prepare-environment-script/SKILL.md +242 -0
- package/forge/skills/implement-prepare-environment-script/assets/prepare_environment_java.sh +42 -0
- package/forge/skills/implement-prepare-environment-script/assets/prepare_environment_python.sh +81 -0
- package/forge/skills/implement-unit-testing-script/SKILL.md +133 -0
- package/forge/skills/implement-unit-testing-script/assets/run_unittests_flutter.ps1 +82 -0
- package/forge/skills/implement-unit-testing-script/assets/run_unittests_golang.ps1 +68 -0
- package/forge/skills/implement-unit-testing-script/assets/run_unittests_java.sh +45 -0
- package/forge/skills/implement-unit-testing-script/assets/run_unittests_python.ps1 +76 -0
- package/forge/skills/implement-unit-testing-script/assets/run_unittests_python.sh +90 -0
- package/forge/skills/implement-unit-testing-script/assets/run_unittests_react.ps1 +83 -0
- package/forge/skills/init-config-file/SKILL.md +261 -0
- package/forge/skills/init-plain-project/SKILL.md +124 -0
- package/forge/skills/load-plain-reference/SKILL.md +646 -0
- package/forge/skills/plain-healthcheck/SKILL.md +132 -0
- package/forge/skills/refactor-module/SKILL.md +197 -0
- package/forge/skills/resolve-spec-conflict/SKILL.md +88 -0
- package/forge/skills/run-codeplain/SKILL.md +540 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Codeplain Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/plain-forge.png" alt="plain-forge" width="600" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# plain-forge
|
|
6
|
+
|
|
7
|
+
A conversational spec-writing tool that runs in any AI coding agent (Claude Code, Codex, OpenCode, and more) and is built on the [***plain](https://plainlang.org) specification language. Describe what you want to build in plain English, and plain-forge guides you through a structured interview to produce complete `.plain` spec files — which then generate production-ready code via the [Codeplain](https://codeplain.ai) renderer.
|
|
8
|
+
|
|
9
|
+
## How It Works
|
|
10
|
+
|
|
11
|
+
The main entry point is `forge-plain`. It turns a conversation into ***plain specs through four phases:
|
|
12
|
+
|
|
13
|
+
1. **What are we building?** — Walk through the product: description, users, scope, core entities, key features, user flows, business rules, and (if applicable) UI behavior. Produces the `***definitions***` and `***functional specs***` for each module.
|
|
14
|
+
2. **What technologies should it use?** — Pick the stack and architecture: language, frameworks, data storage, external services, project structure, and any other stack-wide constraints. Produces the `***implementation reqs***`.
|
|
15
|
+
3. **How should testing be done?** — Decide the testing strategy: framework, test types in scope, conformance/acceptance tests, environment-preparation scripts, layout, and execution. Produces the `***test reqs***`, any `***acceptance tests***`, the runnable scripts under `test_scripts/`, and the `config.yaml`(s) wiring them in. plain-forge then probes your machine to confirm everything those scripts need is actually installed.
|
|
16
|
+
4. **Validate and hand off** — plain-forge identifies the final module in the dependency chain and runs `codeplain <module>.plain --dry-run` itself to catch any static errors (syntax, undefined concepts, broken `import`/`requires` chains, complexity violations, conflicts). It fixes the `.plain` files until the dry-run passes, then hands you the exact `codeplain <module>.plain` command (plus any test scripts) so the real render starts from a clean spec.
|
|
17
|
+
|
|
18
|
+
Each phase is **incremental**, not a single long questionnaire. plain-forge walks one topic at a time, runs an **ask → author → review** loop on every topic — structured questions, immediate edits to the `.plain` files (and `test_scripts/` / `config.yaml` in Phase 3), then snippet-by-snippet confirmation — and only moves on once every flagged snippet is explicitly approved.
|
|
19
|
+
|
|
20
|
+
## Getting Started
|
|
21
|
+
|
|
22
|
+
plain-forge ships as a set of skills that plug into your AI coding tool of choice. Install it once, then invoke `forge-plain` (or `add-feature` to add a feature to an existing ***plain project) from any project.
|
|
23
|
+
|
|
24
|
+
### Install with the `skills` CLI (any runtime)
|
|
25
|
+
|
|
26
|
+
The fastest way to add plain-forge is the `skills` CLI. The `--all` flag installs **every** plain-forge skill at once:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx skills add Codeplain-ai/plain-forge --skill '*'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
#### Install into a specific runtime
|
|
33
|
+
|
|
34
|
+
If you only use one runtime, pass `--agent` to target just that one (you can repeat the flag to pick several):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Just Claude Code
|
|
38
|
+
npx skills add Codeplain-ai/plain-forge --skill '*' --agent claude-code
|
|
39
|
+
|
|
40
|
+
# Just Codex
|
|
41
|
+
npx skills add Codeplain-ai/plain-forge --skill '*' --agent codex
|
|
42
|
+
|
|
43
|
+
# Just OpenCode
|
|
44
|
+
npx skills add Codeplain-ai/plain-forge --skill '*' --agent opencode
|
|
45
|
+
|
|
46
|
+
# Any combination, non-interactive
|
|
47
|
+
npx skills add Codeplain-ai/plain-forge --skill '*' --agent opencode --agent codex --agent claude-code
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If you'd rather use the native install flow for a specific runtime, the per-tool instructions below still work.
|
|
51
|
+
|
|
52
|
+
### Install in Claude Code
|
|
53
|
+
|
|
54
|
+
Requires the [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and configured. Inside any Claude Code session, run the following **three commands**, one after the other (copy and paste each one separately):
|
|
55
|
+
|
|
56
|
+
**1.** Register this repository as a plugin marketplace:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
/plugin marketplace add Codeplain-ai/plain-forge
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**2.** Install the `plain-forge` plugin from it:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
/plugin install plain-forge@plain-forge
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**3.** Reload plugins so Claude Code picks up the newly installed skills:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
/reload-plugins
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Without the reload, the plain-forge skills won't be visible in the current session even though the install succeeded. Once all three commands have run, all plain-forge skills become available.
|
|
75
|
+
|
|
76
|
+
### Install in Codex
|
|
77
|
+
|
|
78
|
+
Requires the [OpenAI Codex CLI](https://developers.openai.com/codex/cli/reference) installed and signed in. Installation is **two steps**, but only the first one is a shell command:
|
|
79
|
+
|
|
80
|
+
**1.** From your shell, register this repository as a Codex marketplace:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
codex plugin marketplace add Codeplain-ai/plain-forge
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**2.** Inside Codex, open the plugin directory, pick the `plain-forge` marketplace, and install the plugin from there. (The Codex CLI does not currently expose a `codex plugin install` equivalent — installation has to be triggered from the in-app plugin directory.)
|
|
87
|
+
|
|
88
|
+
Once the plugin is installed, all plain-forge skills become available in your Codex sessions.
|
|
89
|
+
|
|
90
|
+
## Usage
|
|
91
|
+
|
|
92
|
+
### Prerequisites
|
|
93
|
+
|
|
94
|
+
1. Open your project folder and start a session in your favorite AI coding agent (Claude Code, OpenCode, Codex, …).
|
|
95
|
+
2. Make sure the plain-forge skills are available in that session.
|
|
96
|
+
|
|
97
|
+
### Starting a new project
|
|
98
|
+
|
|
99
|
+
1. Invoke `forge-plain` to launch the structured QA workflow.
|
|
100
|
+
2. Answer the questions. plain-forge writes the `.plain` files for you as you go through the four phases.
|
|
101
|
+
3. Render the specs into code (see [Rendering specs](#rendering-specs) below).
|
|
102
|
+
|
|
103
|
+
### Starting a new project — incremental workflow
|
|
104
|
+
|
|
105
|
+
If you'd rather skip the full upfront interview and build the specs feature-by-feature, use this lighter loop:
|
|
106
|
+
|
|
107
|
+
1. Invoke `init-plain-project`. It asks just for the base technology, the project kind, and whether conformance testing is enabled, then scaffolds the project skeleton: `template/base.plain` with the base `***implementation reqs***` and `***test reqs***`, a stub top-level `<project>.plain` (frontmatter only — no functional specs, no concepts), the unit-test script, an optional conformance-test script, an optional prepare-environment script, and a `config.yaml` wired to whichever scripts were generated. No `codeplain --dry-run` is run.
|
|
108
|
+
2. From there, either:
|
|
109
|
+
- **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.
|
|
110
|
+
- **Invoke `add-feature` manually** whenever you want to drive the loop yourself.
|
|
111
|
+
3. Repeat step 2 for each feature you want to add. The specs grow incrementally and `plain-healthcheck` is run as the final automated step of every `add-feature` pass.
|
|
112
|
+
4. Render the specs into code (see [Rendering specs](#rendering-specs) below).
|
|
113
|
+
|
|
114
|
+
### Adding a feature to an existing project
|
|
115
|
+
|
|
116
|
+
1. Invoke `add-feature`.
|
|
117
|
+
2. Describe the feature in plain English. plain-forge runs the same **ask → author → review** loop scoped to that feature and updates the relevant `.plain` file(s).
|
|
118
|
+
3. Re-render to regenerate the code (see [Rendering specs](#rendering-specs)).
|
|
119
|
+
|
|
120
|
+
### Rendering specs
|
|
121
|
+
|
|
122
|
+
Once your `.plain` files are ready (and `plain-healthcheck` is green), render the specs into code with the [Codeplain](https://codeplain.ai) renderer:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
codeplain <module>.plain
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
plain-forge prints the exact command (with the right final module name) at the end of Phase 4.
|
|
129
|
+
|
|
130
|
+
#### Supervised render (experimental)
|
|
131
|
+
|
|
132
|
+
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`.
|
|
133
|
+
|
|
134
|
+
This is an **experimental** feature — the default and most reliable way to render is still the manual `codeplain <module>.plain` invocation above.
|
|
135
|
+
|
|
136
|
+
### Debugging specs
|
|
137
|
+
|
|
138
|
+
Hit a bug in the rendered app, a failing test, or behavior that doesn't match what you specified?
|
|
139
|
+
|
|
140
|
+
1. Invoke `debug-specs`. plain-forge reads the generated code in `plain_modules/` (and the failing tests, if any), traces the issue back to the responsible `.plain` spec, and diagnoses the root cause — **ambiguous spec**, **missing spec**, **conflicting specs**, **incorrect spec**, or a **missing implementation req**.
|
|
141
|
+
2. plain-forge applies the fix in the `.plain` file(s) only and summarizes what changed.
|
|
142
|
+
3. Re-render to regenerate the code (see [Rendering specs](#rendering-specs)).
|
|
143
|
+
|
|
144
|
+
> **Important:** Never edit generated code under `plain_modules/` or `conformance_tests/` directly — your changes will be overwritten on the next render. Always fix the spec and re-render.
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
## Repository Structure
|
|
148
|
+
|
|
149
|
+
plain-forge keeps a single canonical source of truth under `forge/` and uses tiny per-runtime adapters to regenerate the directory layout each AI tool expects. The generated outputs are committed so existing install commands keep working — no build step is needed for end users.
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
forge/ # canonical, runtime-neutral content
|
|
153
|
+
skills/ # all skills used during spec writing
|
|
154
|
+
rules/ # workspace rules for spec validation
|
|
155
|
+
docs/ # shared docs (PLAIN_REFERENCE.md, etc.)
|
|
156
|
+
|
|
157
|
+
runtimes/ # per-runtime adapters
|
|
158
|
+
claude/
|
|
159
|
+
build.ts # generates .claude/ + .claude-plugin/ from forge/
|
|
160
|
+
templates/ # Claude-specific files: settings.json, hook script, plugin manifests
|
|
161
|
+
codex/
|
|
162
|
+
build.ts # generates .codex-plugin/ and .agents/plugins/ (manifest points at forge/skills/)
|
|
163
|
+
templates/ # Codex-specific files: plugin.json, marketplace catalog
|
|
164
|
+
opencode/
|
|
165
|
+
build.ts # generates .opencode/ from forge/
|
|
166
|
+
templates/ # OpenCode-specific files: package.json, .gitignore
|
|
167
|
+
|
|
168
|
+
bin/
|
|
169
|
+
forge-build.ts # orchestrator: runs every runtimes/*/build.ts
|
|
170
|
+
lib.ts # shared symlink/copy helpers
|
|
171
|
+
|
|
172
|
+
# Generated outputs (committed, do not edit by hand):
|
|
173
|
+
.claude/ # Claude Code plugin layout
|
|
174
|
+
.claude-plugin/ # Claude Code plugin manifests
|
|
175
|
+
.codex-plugin/ # Codex plugin manifest (its "skills" field points at forge/skills/)
|
|
176
|
+
.agents/plugins/ # Codex marketplace catalog
|
|
177
|
+
.opencode/ # OpenCode plugin layout
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Contributing
|
|
181
|
+
|
|
182
|
+
After editing anything under `forge/` or `runtimes/*/templates/`, regenerate the runtime outputs:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
npm install # required after every fresh clone (node_modules/ is gitignored)
|
|
186
|
+
npm run build # regenerate runtime outputs for Claude, Codex, OpenCode
|
|
187
|
+
npm run clean # remove generated outputs and rebuild from scratch
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
If `npm run build` errors with `sh: tsx: command not found`, it means `node_modules/` is missing — run `npm install` first.
|
|
191
|
+
|
|
192
|
+
The build is idempotent — re-running it produces no `git diff`.
|
|
193
|
+
|
|
194
|
+
## Available Skills
|
|
195
|
+
|
|
196
|
+
### Core Workflow
|
|
197
|
+
|
|
198
|
+
| Skill | Description |
|
|
199
|
+
|-------|-------------|
|
|
200
|
+
| `forge-plain` | End-to-end QA interview that produces complete `.plain` spec files for a new project |
|
|
201
|
+
| `init-plain-project` | Lightweight project initializer — scaffolds `template/base.plain` (base impl + test reqs), a stub top-level module, the testing scripts, and `config.yaml`. No functional specs, no concepts, no dry-run. Pair with `add-feature` to grow the project feature-by-feature. |
|
|
202
|
+
| `add-feature` | Interview the user about a single feature, then write all the specs for it |
|
|
203
|
+
| `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. |
|
|
204
|
+
|
|
205
|
+
### Spec Authoring
|
|
206
|
+
|
|
207
|
+
| Skill | Description |
|
|
208
|
+
|-------|-------------|
|
|
209
|
+
| `add-functional-spec` | Add a single feature spec to `***functional specs***` |
|
|
210
|
+
| `add-functional-specs` | Add multiple feature specs to `***functional specs***` in one pass (same per-spec checks as `add-functional-spec`) |
|
|
211
|
+
| `add-implementation-requirement` | Add a non-functional requirement to `***implementation reqs***` |
|
|
212
|
+
| `add-test-requirement` | Add a testing requirement to `***test reqs***` |
|
|
213
|
+
| `add-concept` | Define a new concept in `***definitions***` |
|
|
214
|
+
| `add-acceptance-test` | Add verification criteria under a functional spec |
|
|
215
|
+
| `add-resource` | Link an external file (schema, API spec) to a spec |
|
|
216
|
+
| `add-template` | Create or include a reusable Liquid template |
|
|
217
|
+
|
|
218
|
+
### Module Management
|
|
219
|
+
|
|
220
|
+
| Skill | Description |
|
|
221
|
+
|-------|-------------|
|
|
222
|
+
| `create-import-module` | Create a shared template module (definitions + reqs, no functional specs) |
|
|
223
|
+
| `create-requires-module` | Create a module that depends on a previously built module |
|
|
224
|
+
| `refactor-module` | Split a large module into smaller modules connected via a requires chain |
|
|
225
|
+
| `consolidate-concepts` | Gather scattered concept definitions into a single shared import module |
|
|
226
|
+
|
|
227
|
+
### Analysis and Quality
|
|
228
|
+
|
|
229
|
+
| Skill | Description |
|
|
230
|
+
|-------|-------------|
|
|
231
|
+
| `init-config-file` | Build / finalize the project's `config.yaml` file(s) from the decisions made in Phase 3. Knows the full set of valid keys derived from the `codeplain` CLI, refuses to write secrets or per-invocation flags, and produces one config per part of the project. Run at the end of `forge-plain` (just before `plain-healthcheck`) and any time the testing surface changes. |
|
|
232
|
+
| `plain-healthcheck` | Verification gate: validates every `config.yaml`, confirms each `*-script` field points at a real file in `test_scripts/`, and dry-runs every top module. Run whenever anything in the project is finalized — at the end of `forge-plain`, at the end of `add-feature`, after `debug-specs`, and after any single-skill edit that touches the renderable surface. |
|
|
233
|
+
| `check-plain-env` | Read the project's `.plain` files, `test_scripts/`, `config.yaml`(s), and `resources/`, then probe the host for every requirement **the package manager can't install**: language toolchains (`python` + `pip`, `node` + `npm`, JDK + `mvn`, Go, Rust, .NET, etc.), external services (Postgres, Redis, Docker, ...), system binaries that language packages wrap (`ffmpeg`, `tesseract`, `pdftoppm`, browser binaries, ...), hardware / drivers / accelerators (NVIDIA driver → CUDA toolkit → cuDNN → framework-sees-GPU chain), `codeplain` itself, and credential env vars. Does **not** probe individual language packages — `pip install -r requirements.txt` (and equivalents) handle those when the test scripts run. Emits a `PASS` / `WARN` / `FAIL` report with OS-specific install commands for any gaps. Read-only — never installs anything. Run on first-time setup, before rendering on a new machine, after adding a new tech to a project, or any time `command not found` shows up in test output. |
|
|
234
|
+
| `analyze-if-func-spec-too-complex` | Check if a spec exceeds the 200-line complexity limit |
|
|
235
|
+
| `analyze-func-specs` | Check a batch of specs (2+) against each other in one call and return every conflicting pair |
|
|
236
|
+
| `analyze-2-func-specs` | Legacy: check exactly two specs for conflicts (prefer `analyze-func-specs`) |
|
|
237
|
+
| `break-down-func-spec` | Split an overly complex spec into smaller specs (each ≤ 200 LOC) |
|
|
238
|
+
| `resolve-spec-conflict` | Resolve a conflict between two functional specs |
|
|
239
|
+
|
|
240
|
+
### Debugging and Testing
|
|
241
|
+
|
|
242
|
+
| Skill | Description |
|
|
243
|
+
|-------|-------------|
|
|
244
|
+
| `debug-specs` | Investigate a bug by tracing generated code back to specs and fixing only the `.plain` files |
|
|
245
|
+
| `implement-unit-testing-script` | Generate a per-language unit-test runner (`run_unittests_<lang>.sh` / `.ps1`) |
|
|
246
|
+
| `implement-conformance-testing-script` | Generate a per-language conformance-test runner; picks the install-inline or activate-only variant based on whether `prepare_environment_<lang>` exists |
|
|
247
|
+
| `implement-prepare-environment-script` | Generate a per-language one-time setup script (`prepare_environment_<lang>.sh` / `.ps1`) that stages the build and pre-warms dependencies so conformance tests start cold; reconciles any existing conformance script to remove its now-redundant install step |
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import readline from "node:readline/promises";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const pkgRoot = path.resolve(path.dirname(__filename), "..");
|
|
10
|
+
const forgeDir = path.join(pkgRoot, "forge");
|
|
11
|
+
|
|
12
|
+
const AGENTS = {
|
|
13
|
+
claude: ".claude",
|
|
14
|
+
codex: ".codex",
|
|
15
|
+
forgecode: ".forgecode",
|
|
16
|
+
universal: ".agents",
|
|
17
|
+
};
|
|
18
|
+
const SCOPES = ["project", "global"];
|
|
19
|
+
|
|
20
|
+
function usage() {
|
|
21
|
+
console.log(`Usage: plain-forge install [options]
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--agent <claude|codex|forgecode|universal> Target agent layout
|
|
25
|
+
--scope <project|global> Install into cwd or $HOME
|
|
26
|
+
--skill <name> Install only the named skill (repeatable; default: all)
|
|
27
|
+
-h, --help Show this help
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
plain-forge install --agent claude --scope project
|
|
31
|
+
plain-forge install --agent universal --scope global
|
|
32
|
+
plain-forge install --agent claude --skill add-functional-spec --skill add-concept
|
|
33
|
+
|
|
34
|
+
Missing flags are prompted interactively.`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseArgs(argv) {
|
|
38
|
+
const out = { _: [], skills: [] };
|
|
39
|
+
for (let i = 0; i < argv.length; i++) {
|
|
40
|
+
const a = argv[i];
|
|
41
|
+
if (a === "--agent") out.agent = argv[++i];
|
|
42
|
+
else if (a === "--scope") out.scope = argv[++i];
|
|
43
|
+
else if (a === "--skill") out.skills.push(argv[++i]);
|
|
44
|
+
else if (a === "-h" || a === "--help") out.help = true;
|
|
45
|
+
else out._.push(a);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function promptChoice(question, choices) {
|
|
51
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
52
|
+
try {
|
|
53
|
+
while (true) {
|
|
54
|
+
const ans = (await rl.question(`${question} [${choices.join("/")}]: `)).trim().toLowerCase();
|
|
55
|
+
if (choices.includes(ans)) return ans;
|
|
56
|
+
console.log(` please answer one of: ${choices.join(", ")}`);
|
|
57
|
+
}
|
|
58
|
+
} finally {
|
|
59
|
+
rl.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function copyTree(srcDir, destDir, filterNames) {
|
|
64
|
+
if (!fs.existsSync(srcDir)) return 0;
|
|
65
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
66
|
+
let count = 0;
|
|
67
|
+
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
68
|
+
if (filterNames && !filterNames.has(entry.name)) continue;
|
|
69
|
+
const src = path.join(srcDir, entry.name);
|
|
70
|
+
const dest = path.join(destDir, entry.name);
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
fs.cpSync(src, dest, { recursive: true, force: true, dereference: true });
|
|
73
|
+
} else {
|
|
74
|
+
fs.copyFileSync(src, dest);
|
|
75
|
+
}
|
|
76
|
+
count++;
|
|
77
|
+
}
|
|
78
|
+
return count;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function cmdInstall(args) {
|
|
82
|
+
let agent = args.agent;
|
|
83
|
+
if (!agent) agent = await promptChoice("Which agent?", Object.keys(AGENTS));
|
|
84
|
+
if (!Object.hasOwn(AGENTS, agent)) {
|
|
85
|
+
console.error(`unknown agent "${agent}". valid: ${Object.keys(AGENTS).join(", ")}`);
|
|
86
|
+
process.exit(2);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let scope = args.scope;
|
|
90
|
+
if (!scope) scope = await promptChoice("Scope?", SCOPES);
|
|
91
|
+
if (!SCOPES.includes(scope)) {
|
|
92
|
+
console.error(`unknown scope "${scope}". valid: ${SCOPES.join(", ")}`);
|
|
93
|
+
process.exit(2);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const root = scope === "global" ? os.homedir() : process.cwd();
|
|
97
|
+
const baseDir = path.join(root, AGENTS[agent]);
|
|
98
|
+
|
|
99
|
+
const explicit = args.skills.filter((s) => s !== "*");
|
|
100
|
+
const skillFilter = explicit.length ? new Set(explicit) : null;
|
|
101
|
+
|
|
102
|
+
const skillsSrc = path.join(forgeDir, "skills");
|
|
103
|
+
if (skillFilter) {
|
|
104
|
+
for (const name of skillFilter) {
|
|
105
|
+
if (!fs.existsSync(path.join(skillsSrc, name))) {
|
|
106
|
+
console.error(`skill "${name}" not found under ${skillsSrc}`);
|
|
107
|
+
process.exit(2);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const skillsCount = copyTree(skillsSrc, path.join(baseDir, "skills"), skillFilter);
|
|
113
|
+
const rulesCount = copyTree(path.join(forgeDir, "rules"), path.join(baseDir, "rules"), null);
|
|
114
|
+
const docsCount = copyTree(path.join(forgeDir, "docs"), path.join(baseDir, "docs"), null);
|
|
115
|
+
|
|
116
|
+
console.log(`installed into ${baseDir}`);
|
|
117
|
+
console.log(` skills: ${skillsCount}`);
|
|
118
|
+
console.log(` rules: ${rulesCount}`);
|
|
119
|
+
console.log(` docs: ${docsCount}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function main() {
|
|
123
|
+
const args = parseArgs(process.argv.slice(2));
|
|
124
|
+
if (args.help || args._.length === 0) {
|
|
125
|
+
usage();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const cmd = args._[0];
|
|
129
|
+
switch (cmd) {
|
|
130
|
+
case "install":
|
|
131
|
+
await cmdInstall(args);
|
|
132
|
+
break;
|
|
133
|
+
default:
|
|
134
|
+
console.error(`unknown command "${cmd}"`);
|
|
135
|
+
usage();
|
|
136
|
+
process.exit(2);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
main().catch((err) => {
|
|
141
|
+
console.error(err instanceof Error ? err.stack ?? err.message : err);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Rules for writing ***definitions*** sections in .plain files
|
|
3
|
+
globs: "**/*.plain"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rules for writing `***definitions***`
|
|
7
|
+
|
|
8
|
+
When writing or editing a `***definitions***` section in a `.plain` file, always follow these rules:
|
|
9
|
+
|
|
10
|
+
## Concept syntax
|
|
11
|
+
- Wrap concept names in colons: `:ConceptName:`
|
|
12
|
+
- Use CamelCase starting with an uppercase letter
|
|
13
|
+
- Valid characters: letters, digits, `+`, `-`, `.`, `_`
|
|
14
|
+
|
|
15
|
+
## Uniqueness
|
|
16
|
+
- Concept names must be globally unique across the spec and all its imports
|
|
17
|
+
- Check for collisions with imported templates, `import` and `requires` modules before adding
|
|
18
|
+
|
|
19
|
+
## Define before use
|
|
20
|
+
- A concept must be defined before it is referenced in any section (definitions, implementation reqs, functional specs, test reqs)
|
|
21
|
+
- Sources of definitions: the module's own `***definitions***`, an `import`ed module's definitions, or a `require`d module's `exported_concepts`
|
|
22
|
+
|
|
23
|
+
## No circular references
|
|
24
|
+
- Concept references must not form cycles — if A references B, then B must not reference A (directly or indirectly)
|
|
25
|
+
- Insert each concept after any concepts it references
|
|
26
|
+
|
|
27
|
+
Bad — circular:
|
|
28
|
+
|
|
29
|
+
```plain
|
|
30
|
+
- :Customer: is a user who has placed at least one :Order:.
|
|
31
|
+
- :Order: is placed by :Customer: and contains :OrderItem: entries.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`:Order:` references `:Customer:`, and `:Customer:` references `:Order:`. Fix by removing the back-reference:
|
|
35
|
+
|
|
36
|
+
```plain
|
|
37
|
+
- :Customer: is a user of the system.
|
|
38
|
+
- :Order: is placed by :Customer: and contains :OrderItem: entries.
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Exported concepts are not transitive
|
|
42
|
+
- If module A exports a concept and module B `requires` A, module C `requires` B does **not** gain access to A's exports
|
|
43
|
+
- Shared concepts belong in a common import module
|
|
44
|
+
|
|
45
|
+
## Description quality
|
|
46
|
+
- Descriptions must be clear, concise, and language-agnostic
|
|
47
|
+
- Nest attributes and constraints as sub-bullets
|
|
48
|
+
- Do not use programming language constructs (generics, annotations, framework types) in definitions
|
|
49
|
+
|
|
50
|
+
## Format
|
|
51
|
+
|
|
52
|
+
```plain
|
|
53
|
+
***definitions***
|
|
54
|
+
- :ConceptName: is a description of what it represents.
|
|
55
|
+
- Attribute one (required)
|
|
56
|
+
- Attribute two (optional)
|
|
57
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Rules for using exported_concepts in .plain files
|
|
3
|
+
globs: "**/*.plain"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rules for `exported_concepts`
|
|
7
|
+
|
|
8
|
+
When adding or editing `exported_concepts` in a `.plain` file's frontmatter, always follow these rules:
|
|
9
|
+
|
|
10
|
+
## What exported_concepts does
|
|
11
|
+
- `exported_concepts` declares which concepts from this module are visible to modules that `require` it
|
|
12
|
+
- Concepts not listed in `exported_concepts` are internal to the module and invisible to downstream modules
|
|
13
|
+
- Only modules that use `requires` receive exported concepts — `import` gives access to all definitions, not just exports
|
|
14
|
+
|
|
15
|
+
## When to use it
|
|
16
|
+
- Use `exported_concepts` on any module that other modules will `require`
|
|
17
|
+
- List only the concepts that downstream modules actually need to reference
|
|
18
|
+
- Keep the exported surface small — expose only what is necessary
|
|
19
|
+
|
|
20
|
+
## Concepts must be defined
|
|
21
|
+
- Every concept listed in `exported_concepts` must be defined in the module's own `***definitions***` section
|
|
22
|
+
- Do not export concepts that are not defined in the module
|
|
23
|
+
|
|
24
|
+
## Exports are not transitive
|
|
25
|
+
- If module A exports `:Foo:` and module B `requires` A, module C `requires` B does **not** gain access to `:Foo:`
|
|
26
|
+
- If module C also needs `:Foo:`, it must either `require` A directly or get it through a common import module
|
|
27
|
+
|
|
28
|
+
## Format
|
|
29
|
+
|
|
30
|
+
```plain
|
|
31
|
+
---
|
|
32
|
+
import:
|
|
33
|
+
- airplain
|
|
34
|
+
exported_concepts: [":User:", ":JwtToken:"]
|
|
35
|
+
description: Exports User and JwtToken for downstream modules
|
|
36
|
+
---
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
List concepts as a YAML array with each concept in `:ConceptName:` notation.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Rules for writing ***functional specs*** and ***acceptance tests*** in .plain files
|
|
3
|
+
globs: "**/*.plain"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rules for writing `***functional specs***`
|
|
7
|
+
|
|
8
|
+
When writing or editing a `***functional specs***` section in a `.plain` file, always follow these rules:
|
|
9
|
+
|
|
10
|
+
## Complexity limit
|
|
11
|
+
- Each functional spec must imply a **maximum of 200 changed lines of code**
|
|
12
|
+
- If a spec is too large, use `break-down-func-spec` to split it into multiple smaller, independent specs
|
|
13
|
+
- Use `analyze-if-func-spec-too-complex` to verify before inserting
|
|
14
|
+
- Use `analyze-func-specs` to check a spec (or a batch of specs) against all relevant existing specs in a single batched call; use `resolve-spec-conflict` for each conflicting pair it reports
|
|
15
|
+
|
|
16
|
+
## Chronological ordering
|
|
17
|
+
- Specs are rendered incrementally, top to bottom
|
|
18
|
+
- The renderer has **no knowledge of future specs** — only previously rendered specs are in context
|
|
19
|
+
- A new spec can reference behavior from earlier specs but cannot assume anything about specs that come after it
|
|
20
|
+
- Functional specs from `requires` modules are treated as previous requirements
|
|
21
|
+
|
|
22
|
+
## No conflicts
|
|
23
|
+
- The new spec must not contradict any existing functional spec
|
|
24
|
+
- Before adding, review all existing specs and verify compatibility
|
|
25
|
+
- If ambiguity exists, add explicit detail to eliminate any conflicting interpretation
|
|
26
|
+
|
|
27
|
+
## Language agnosticism
|
|
28
|
+
- Write in terms of behavior, concepts, and domain logic
|
|
29
|
+
- Language-specific guidance belongs in `***implementation reqs***`
|
|
30
|
+
|
|
31
|
+
## Disambiguation
|
|
32
|
+
- Each functional spec must be unambiguous — the renderer should have only one reasonable interpretation
|
|
33
|
+
- If a single line is not enough to fully disambiguate the behavior, use **nested sub-bullets** to add detail
|
|
34
|
+
- Nested lines clarify the parent spec — they do not introduce separate functionality
|
|
35
|
+
- Even with nested detail, the spec must still imply ≤ 200 lines of code
|
|
36
|
+
|
|
37
|
+
## Deterministic interface
|
|
38
|
+
- Specs must be detailed enough that a developer can use the built software without reading the generated code
|
|
39
|
+
- All external interfaces must be explicit: REST endpoint paths and HTTP methods, CLI command names and arguments, file formats, message schemas, etc.
|
|
40
|
+
- Never leave interface details up to the renderer's discretion
|
|
41
|
+
|
|
42
|
+
## Encapsulation
|
|
43
|
+
- Functionality must be self-contained in the spec text
|
|
44
|
+
- `requires` modules only receive functional specs — do not rely on implementation reqs to convey behavior
|
|
45
|
+
- Behavior that downstream modules need must be expressed in functional specs, not elsewhere
|
|
46
|
+
|
|
47
|
+
## Acceptance tests
|
|
48
|
+
- Nest `***acceptance tests***` under a functional spec when verification criteria are needed
|
|
49
|
+
- Each acceptance test must be a **full workflow test** — a specific scenario that exercises the functional spec end-to-end, not a unit-level check of a single field or condition
|
|
50
|
+
- Do not restate the obvious behavior from the functional spec — simple, direct verifications are already auto-generated as conformance tests. Acceptance tests must go beyond that: multi-step workflows, interactions between concepts, edge-case scenarios, or end-to-end sequences that prove the feature works in a realistic context
|
|
51
|
+
- Each acceptance test must be a direct logical consequence of the parent spec — it illustrates, not extends
|
|
52
|
+
- Acceptance tests must describe concrete, verifiable outcomes — not vague qualities
|
|
53
|
+
- Acceptance tests must not contradict, narrow, or extend beyond the parent spec
|
|
54
|
+
|
|
55
|
+
## Format
|
|
56
|
+
|
|
57
|
+
```plain
|
|
58
|
+
***functional specs***
|
|
59
|
+
|
|
60
|
+
- Implement the entry point for :App:.
|
|
61
|
+
|
|
62
|
+
- :User: should be able to add :Task:. Only valid :Task: items can be added.
|
|
63
|
+
|
|
64
|
+
- :User: should be able to send a :Message: to a :Conversation:.
|
|
65
|
+
- A :Message: must have non-empty content.
|
|
66
|
+
- The :Message: is appended to the end of the :Conversation:.
|
|
67
|
+
- All :Participant: members of the :Conversation: can see the new :Message:.
|
|
68
|
+
|
|
69
|
+
***acceptance tests***
|
|
70
|
+
- Sending a :Message: to a :Conversation: with three participants should
|
|
71
|
+
make the message visible to all three.
|
|
72
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Rules for writing ***implementation reqs*** sections in .plain files
|
|
3
|
+
globs: "**/*.plain"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rules for writing `***implementation reqs***`
|
|
7
|
+
|
|
8
|
+
When writing or editing an `***implementation reqs***` section in a `.plain` file, always follow these rules:
|
|
9
|
+
|
|
10
|
+
## HOW, not WHAT
|
|
11
|
+
- Implementation reqs describe **how** the software should be built, not **what** it should do
|
|
12
|
+
- Observable behavior (endpoints, business rules, user-facing features) belongs in `***functional specs***`
|
|
13
|
+
- Internal structure, technology choices, and coding guidance belong here
|
|
14
|
+
|
|
15
|
+
## What belongs here
|
|
16
|
+
- Technology choices: language, framework, runtime version
|
|
17
|
+
- Architectural constraints: patterns, layering, dependency rules
|
|
18
|
+
- Coding standards: naming conventions, style guidelines
|
|
19
|
+
- Data formats: serialization, encoding, transformation rules
|
|
20
|
+
- Error handling: strategies, retry logic, exception hierarchies
|
|
21
|
+
- Algorithm descriptions: specific approaches when behavior alone is insufficient
|
|
22
|
+
- Performance guidance: memory constraints, streaming requirements, batching strategies
|
|
23
|
+
- Language-specific constructs: generics, annotations, framework-specific types and idioms
|
|
24
|
+
|
|
25
|
+
## What does NOT belong here
|
|
26
|
+
- Behavior and features → `***functional specs***`
|
|
27
|
+
- Concept definitions → `***definitions***`
|
|
28
|
+
- Conformance test instructions → `***test reqs***`
|
|
29
|
+
|
|
30
|
+
## Encapsulation warning
|
|
31
|
+
- `requires` modules only receive functional specs from their dependencies — not implementation reqs
|
|
32
|
+
- If downstream modules need certain behavior to be visible, express it in functional specs, not here
|
|
33
|
+
|
|
34
|
+
## No duplication
|
|
35
|
+
- Do not duplicate guidance already present in the file or its imports
|
|
36
|
+
- Check imported templates before adding a new req
|
|
37
|
+
|
|
38
|
+
## Concept references
|
|
39
|
+
- Reference defined `:Concepts:` where they add clarity
|
|
40
|
+
- All referenced concepts must already be defined in `***definitions***`
|
|
41
|
+
- Implementation reqs in non-leaf sections apply to all subsections
|
|
42
|
+
|
|
43
|
+
## Format
|
|
44
|
+
|
|
45
|
+
```plain
|
|
46
|
+
***implementation reqs***
|
|
47
|
+
- :Implementation: should be in Python 3.12.
|
|
48
|
+
- :Implementation: should use pip for dependency management.
|
|
49
|
+
- When writing CSV files, :Implementation: should use streaming writes to avoid holding large datasets in memory.
|
|
50
|
+
```
|