plain-forge 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -8
- package/forge/rules/examples/integration-embedded-testing/prepare_environment_java.sh +51 -0
- package/forge/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +99 -0
- package/forge/rules/examples/integration-embedded-testing/run_unittests_java.sh +51 -0
- package/forge/rules/integration-embedded-testing.md +203 -0
- package/forge/rules/integration-embedded.md +64 -47
- package/forge/skills/add-acceptance-test/SKILL.md +24 -0
- package/forge/skills/add-concept/SKILL.md +23 -0
- package/forge/skills/add-functional-spec/SKILL.md +23 -0
- package/forge/skills/add-functional-specs/SKILL.md +24 -0
- package/forge/skills/add-implementation-requirement/SKILL.md +24 -0
- package/forge/skills/add-test-requirement/SKILL.md +22 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,18 +4,39 @@
|
|
|
4
4
|
|
|
5
5
|
# plain-forge
|
|
6
6
|
|
|
7
|
-
A
|
|
7
|
+
A toolkit for working with [***plain](https://plainlang.org) projects from inside your AI coding agent of choice — Claude Code, Codex, ForgeCode, OpenCode, and any other agent that reads from a standard skills directory. plain-forge ships skills, rules, and docs that turn a conversation into complete `.plain` spec files, then keeps maintaining them across the lifetime of the project. The specs are rendered into production-ready code by the [codeplain](https://codeplain.ai) renderer.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## What plain-forge does
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
plain-forge is organized around four kinds of work, each with its own entry-point skill (and a long tail of supporting skills behind it).
|
|
12
12
|
|
|
13
|
-
1.
|
|
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.
|
|
13
|
+
### 1. Bootstrap a new project
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
Pick whichever entry point matches how much upfront design you want:
|
|
16
|
+
|
|
17
|
+
- **`forge-plain`** — full end-to-end interview. One question at a time, immediate writes to disk, covers product → tech stack → testing → validation in four phases. Produces a complete `.plain` project with `config.yaml`, test scripts, and a successful `codeplain --dry-run` before handing off.
|
|
18
|
+
- **`init-plain-project`** — lightweight scaffold. Asks only about the base technology, project kind, and whether conformance testing is on; emits a template module, a stub top-level module, the testing scripts, and a `config.yaml`. No interview, no specs — pair it with `add-feature` to grow the project feature by feature.
|
|
19
|
+
|
|
20
|
+
### 2. Grow an existing project
|
|
21
|
+
|
|
22
|
+
- **`add-feature`** — takes a feature request in plain English and runs the same one-question-at-a-time loop that `forge-plain` uses, scoped to a single feature against an existing `.plain` file.
|
|
23
|
+
- **Per-section authoring skills** — `add-functional-spec`, `add-functional-specs`, `add-implementation-requirement`, `add-test-requirement`, `add-concept`, `add-acceptance-test`, `add-resource`, `add-template`. Each enforces the relevant ***plain syntax rules (concept uniqueness, complexity limits, line-length, linked-resource constraints, …) so hand-authoring doesn't drift from the language.
|
|
24
|
+
- **Module-management skills** — `create-import-module`, `create-requires-module`, `refactor-module`, `consolidate-concepts` for restructuring as the project grows.
|
|
25
|
+
|
|
26
|
+
### 3. Validate and maintain
|
|
27
|
+
|
|
28
|
+
- **`plain-healthcheck`** — the verification gate. Inventories every `.plain` module, validates every `config.yaml`, and dry-runs every top module with the right config. Returns `PASS` / `FAIL` with a numbered punch-list when something is broken.
|
|
29
|
+
- **`init-config-file`** — assembles the canonical `config.yaml` per part of a project from the decisions made during interviewing.
|
|
30
|
+
- **`check-plain-env`** — probes the host machine for everything the project needs (language toolchains, external services, system binaries, drivers, `codeplain` itself) and emits a `PASS` / `WARN` / `FAIL` report with OS-specific install commands.
|
|
31
|
+
- **`analyze-func-specs`** / **`analyze-if-func-spec-too-complex`** / **`break-down-func-spec`** / **`resolve-spec-conflict`** — the spec-quality toolchain that runs behind `add-feature` and `forge-plain` but can also be invoked directly when reviewing or refactoring.
|
|
32
|
+
|
|
33
|
+
### 4. Debug and render
|
|
34
|
+
|
|
35
|
+
- **`debug-specs`** — when the rendered app misbehaves, this traces the generated code back to the spec that caused it and fixes the spec (never the generated code).
|
|
36
|
+
- **`run-codeplain`** — experimental supervised render. Launches `codeplain` for you, tails the log, watches generated code appear under `plain_modules/`, and detects pathologies (stuck conformance loops, complexity errors, missing concepts, render failures). On approval it stops the renderer, hands off to the right spec-edit skill, and resumes.
|
|
37
|
+
- **`implement-unit-testing-script`** / **`implement-conformance-testing-script`** / **`implement-prepare-environment-script`** — generate the per-language testing scripts that the renderer and you both invoke. New languages can be added by these skills without touching any other part of the project.
|
|
38
|
+
|
|
39
|
+
Each skill operates on the same one-question-at-a-time, write-immediately, refine-through-follow-ups loop. Specs land on disk after every answer; later answers fix earlier writes in place. There is no batched interview, no "I'll gather context first," and no hand-authored functional specs — every new spec goes through the authoring skills so the complexity and conflict checks actually run.
|
|
19
40
|
|
|
20
41
|
## Getting Started
|
|
21
42
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Export Java 21
|
|
4
|
+
#export JAVA_HOME="/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home"
|
|
5
|
+
#export PATH="$JAVA_HOME/bin:$PATH"
|
|
6
|
+
echo "JAVA_HOME: $JAVA_HOME"
|
|
7
|
+
|
|
8
|
+
# Check if build folder name is provided
|
|
9
|
+
if [ -z "$1" ]; then
|
|
10
|
+
printf "Error: No build folder name provided.\n"
|
|
11
|
+
printf "Usage: $0 <build_folder_name>\n"
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
JAVA_BUILD_SUBFOLDER=../../../
|
|
16
|
+
|
|
17
|
+
if [ "${VERBOSE:-}" -eq 1 ] 2>/dev/null; then
|
|
18
|
+
printf "Copying generated code to main project folder: $JAVA_BUILD_SUBFOLDER\n"
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Check if the main project folder exists
|
|
22
|
+
if [ ! -d "$JAVA_BUILD_SUBFOLDER" ]; then
|
|
23
|
+
echo "Error: Main project folder '$JAVA_BUILD_SUBFOLDER' does not exist."
|
|
24
|
+
exit 2
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# clean existing code from the main project folder
|
|
28
|
+
rm -rf $JAVA_BUILD_SUBFOLDER/<placeholder for integration code>/*
|
|
29
|
+
rm -rf $JAVA_BUILD_SUBFOLDER/<placeholder for integration code tests>/*
|
|
30
|
+
|
|
31
|
+
# copy generated code to the main project folder
|
|
32
|
+
# FIX: added the mkdir lines otherwise it complains that the cp destintation directory does not exist
|
|
33
|
+
mkdir -p $JAVA_BUILD_SUBFOLDER/<placeholder for integration code>
|
|
34
|
+
mkdir -p $JAVA_BUILD_SUBFOLDER/<placeholder for integration code tests>
|
|
35
|
+
cp -R $1/<placeholder for integration code>/* $JAVA_BUILD_SUBFOLDER/<<placeholder for integration code>
|
|
36
|
+
cp -R $1/<placeholder for integration code tests>/* $JAVA_BUILD_SUBFOLDER/<placeholder for integration code tests>
|
|
37
|
+
|
|
38
|
+
# Move to the subfolder
|
|
39
|
+
echo "Moving to: $JAVA_BUILD_SUBFOLDER"
|
|
40
|
+
cd "$JAVA_BUILD_SUBFOLDER" 2>/dev/null
|
|
41
|
+
|
|
42
|
+
if [ $? -ne 0 ]; then
|
|
43
|
+
printf "Error: Java build folder '$JAVA_BUILD_SUBFOLDER' does not exist.\n"
|
|
44
|
+
exit 2
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Remove target directory to avoid conflicts
|
|
48
|
+
rm -rf ./target || echo "Warning: some files may be locked"
|
|
49
|
+
|
|
50
|
+
echo "Runinng maven install in the build folder..."
|
|
51
|
+
mvn clean install -Dspring-boot.repackage.skip=true -DskipTests
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Export Java 21
|
|
4
|
+
#export JAVA_HOME="/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home"
|
|
5
|
+
#export PATH="$JAVA_HOME/bin:$PATH"
|
|
6
|
+
echo "JAVA_HOME: $JAVA_HOME"
|
|
7
|
+
|
|
8
|
+
# Check if build folder name is provided
|
|
9
|
+
if [ -z "$1" ]; then
|
|
10
|
+
printf "Error: No build folder name provided.\n"
|
|
11
|
+
printf "Usage: $0 <build_folder_name> <conformance_tests_folder>\n"
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
# Check if conformance tests folder name is provided
|
|
16
|
+
if [ -z "$2" ]; then
|
|
17
|
+
printf "Error: No conformance tests folder name provided.\n"
|
|
18
|
+
printf "Usage: $0 <build_folder_name> <conformance_tests_folder>\n"
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
current_dir=$(pwd)
|
|
23
|
+
|
|
24
|
+
CONFORMANCE_TESTS_FOLDER=".tmp/java_conformance"
|
|
25
|
+
|
|
26
|
+
cd "$current_dir" 2>/dev/null
|
|
27
|
+
|
|
28
|
+
printf "Preparing Java conformance tests subfolder: $CONFORMANCE_TESTS_FOLDER\n"
|
|
29
|
+
|
|
30
|
+
# Check if the go build subfolder exists
|
|
31
|
+
if [ -d "$CONFORMANCE_TESTS_FOLDER" ]; then
|
|
32
|
+
# Find and delete all files and folders
|
|
33
|
+
find "$CONFORMANCE_TESTS_FOLDER" -mindepth 1 -exec rm -rf {} +
|
|
34
|
+
|
|
35
|
+
if [ "${VERBOSE:-}" -eq 1 ] 2>/dev/null; then
|
|
36
|
+
printf "Cleanup completed.\n"
|
|
37
|
+
fi
|
|
38
|
+
else
|
|
39
|
+
# print the current directory
|
|
40
|
+
printf "Current directory: $(pwd)\n"
|
|
41
|
+
|
|
42
|
+
printf "Subfolder does not exist. Creating it...\n"
|
|
43
|
+
mkdir -p $CONFORMANCE_TESTS_FOLDER
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
printf "Copying all files and folders from $2 to $CONFORMANCE_TESTS_FOLDER...\n"
|
|
47
|
+
cp -R $2/* $CONFORMANCE_TESTS_FOLDER
|
|
48
|
+
|
|
49
|
+
# Move to the subfolder
|
|
50
|
+
printf "Moving to $CONFORMANCE_TESTS_FOLDER...\n"
|
|
51
|
+
cd "$CONFORMANCE_TESTS_FOLDER" 2>/dev/null
|
|
52
|
+
|
|
53
|
+
if [ $? -ne 0 ]; then
|
|
54
|
+
printf "Error: Java conformance tests folder '$CONFORMANCE_TESTS_FOLDER' does not exist.\n"
|
|
55
|
+
exit 2
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
echo "Running maven install in the conformance tests folder..."
|
|
59
|
+
mvn clean install -DskipTests
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if [ $? -ne 0 ]; then
|
|
63
|
+
exit 2
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
echo "Running maven test in the conformance tests folder..."
|
|
67
|
+
|
|
68
|
+
output=$(mvn test --no-transfer-progress 2>&1)
|
|
69
|
+
exit_code=$?
|
|
70
|
+
|
|
71
|
+
echo "Finished running maven test in the conformance tests folder..."
|
|
72
|
+
|
|
73
|
+
# Check if no tests were run
|
|
74
|
+
if echo "$output" | grep -q 'Tests run: [1-9][0-9]*, Failures: 0, Errors: 0, Skipped: 0'; then
|
|
75
|
+
echo "All tests passed"
|
|
76
|
+
echo "$output"
|
|
77
|
+
exit $exit_code
|
|
78
|
+
else
|
|
79
|
+
# Check if no tests were run
|
|
80
|
+
if echo "$output" | grep -q 'Tests run: 0, Failures: 0, Errors: 0, Skipped: 0'; then
|
|
81
|
+
echo "Tests run: 0"
|
|
82
|
+
echo "Error: No tests were executed (Tests run: 0)"
|
|
83
|
+
echo "You are seeing this cause the following appeared in the output: Tests run: 0, Failures: 0, Errors: 0, Skipped: 0"
|
|
84
|
+
else
|
|
85
|
+
echo "Some tests failed, had errors, or were skipped. This is not allowed. All tests must pass."
|
|
86
|
+
fi
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# If there was an error, print the output and exit with the error code
|
|
90
|
+
if [ $exit_code -ne 0 ]; then
|
|
91
|
+
echo "Error: Maven test failed in the conformance tests folder with exit code $exit_code..."
|
|
92
|
+
echo "$output"
|
|
93
|
+
exit $exit_code
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
echo "Finished running conformance tests..."
|
|
97
|
+
echo "$output"
|
|
98
|
+
# Echo the original exit code of the unittest command
|
|
99
|
+
exit $exit_code
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Export Java 21
|
|
4
|
+
#export JAVA_HOME="/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home"
|
|
5
|
+
#export PATH="$JAVA_HOME/bin:$PATH"
|
|
6
|
+
echo "JAVA_HOME: $JAVA_HOME"
|
|
7
|
+
|
|
8
|
+
# Check if subfolder name is provided
|
|
9
|
+
if [ -z "$1" ]; then
|
|
10
|
+
echo "Error: No subfolder name provided."
|
|
11
|
+
echo "Usage: $0 <subfolder_name>"
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
# FIX: Changed this line from "../../" to "../../.." to go up three levels to the main project folder
|
|
15
|
+
MAIN_PROJECT_FOLDER="$(cd "$(dirname "$0")/../../.." && pwd)"
|
|
16
|
+
|
|
17
|
+
if [ "${VERBOSE:-}" -eq 1 ] 2>/dev/null; then
|
|
18
|
+
printf "Copying generated code to main project folder: $MAIN_PROJECT_FOLDER\n"
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Check if the main project folder exists
|
|
22
|
+
if [ ! -d "$MAIN_PROJECT_FOLDER" ]; then
|
|
23
|
+
echo "Error: Main project folder '$MAIN_PROJECT_FOLDER' does not exist."
|
|
24
|
+
exit 2
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# clean existing code from the main project folder
|
|
28
|
+
rm -rf $MAIN_PROJECT_FOLDER/<placeholder for integration code>/*
|
|
29
|
+
rm -rf $MAIN_PROJECT_FOLDER/<placeholder for integration code tests>/*
|
|
30
|
+
|
|
31
|
+
# copy generated code to the main project folder
|
|
32
|
+
# FIX: added the mkdir lines otherwise it complains that the cp destintation directory does not exist
|
|
33
|
+
mkdir -p $MAIN_PROJECT_FOLDER/<placeholder for integration code>
|
|
34
|
+
mkdir -p $MAIN_PROJECT_FOLDER/<placeholder for integration code tests>
|
|
35
|
+
cp -R $1/<placeholder for integration code>/* $MAIN_PROJECT_FOLDER/<placeholder for integration code>
|
|
36
|
+
cp -R $1/<placeholder for integration code tests>/* $MAIN_PROJECT_FOLDER/<placeholder for integration code tests>
|
|
37
|
+
|
|
38
|
+
# Move to the subfolder
|
|
39
|
+
# FIX: added this line to print the current directory to help the LLM
|
|
40
|
+
echo "Moving to: $MAIN_PROJECT_FOLDER"
|
|
41
|
+
cd "$MAIN_PROJECT_FOLDER" 2>/dev/null
|
|
42
|
+
|
|
43
|
+
if [ $? -ne 0 ]; then
|
|
44
|
+
printf "Error: Java build folder '$MAIN_PROJECT_FOLDER' does not exist.\n"
|
|
45
|
+
exit 2
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Execute all Java unittests in the subfolder
|
|
50
|
+
echo "Running Java unittests in $1..."
|
|
51
|
+
mvn test -Dtest='<placeholder for integration code tests package>.**.*Test' checkstyle:check
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Rules for authoring the three test scripts that an embedded ***plain integration ships
|
|
3
|
+
globs: "**/*.plain"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rules for embedded-integration test scripts
|
|
7
|
+
|
|
8
|
+
When an embedded integration ships its three `test_scripts/` (prepare-environment, unit, conformance) — whether you author them by hand or via the `implement-*-testing-script` skills — these rules apply on top of the shared testing-script rules in [PLAIN_REFERENCE.md](../docs/PLAIN_REFERENCE.md) (exit-code conventions, the activate-only vs install-inline conformance distinction, `VERBOSE=1`, etc.).
|
|
9
|
+
|
|
10
|
+
## Staging model (read this first)
|
|
11
|
+
|
|
12
|
+
The three scripts do **not** all stage into the same place. Embedded integrations need a runnable host project, so the prepare and unit-test scripts operate **inside the host codebase itself**; only the conformance script uses a `.tmp/` scratch folder because the conformance suite lives in a separate project.
|
|
13
|
+
|
|
14
|
+
| Script | Where it operates | What it does to the host source tree |
|
|
15
|
+
|--------|-------------------|---------------------------------------|
|
|
16
|
+
| `prepare_environment_<lang>` | Inside the host codebase | Copies `$1` into the module's package path under the host's source tree, then compiles / installs the host project |
|
|
17
|
+
| `run_unittests_<lang>` | Inside the host codebase | Same copy as above (self-contained), then runs the module's unit tests + lint inside the host |
|
|
18
|
+
| `run_conformance_tests_<lang>` | `.tmp/<lang>_conformance/` | Copies `$2` (the conformance-tests folder) into the scratch directory and runs the conformance suite there, which depends on the host build that `prepare_environment` already installed |
|
|
19
|
+
|
|
20
|
+
This deliberately writes into the host's `src/main/...` and `src/test/...` (or the language equivalent). Two consequences flow from that:
|
|
21
|
+
|
|
22
|
+
- **The host codebase root must come from a configured env var** (`HOST_CODEBASE_ROOT` is the conventional name; declare it in the integration's configuration concept). Scripts never hardcode it
|
|
23
|
+
- **The destructive-op scoping rule below becomes critical.** `rm -rf` must target only the module's own package path under the host's source tree, never the host's `src/main/`, `target/`, `node_modules/`, or `build/` at the project root
|
|
24
|
+
|
|
25
|
+
This rule covers the **mechanics each script must obey** regardless of language: argument handling, exit codes, idempotency, path resolution, output parsing, and the "what NOT to put here" guard rails. The language-specific install / test commands come from the skills; the contract below is invariant.
|
|
26
|
+
|
|
27
|
+
## What the `.plain` spec must declare in `***implementation reqs***`
|
|
28
|
+
|
|
29
|
+
The three scripts are generated from facts in the integration's spec. For an embedded integration, those facts cannot be inferred — they have to be **declared explicitly** in `***implementation reqs***` so the renderer (and the `implement-*-testing-script` skills) can fill them into the three placeholders that every script body needs:
|
|
30
|
+
|
|
31
|
+
1. **Integration source path inside the host** — where `$1/<source>/*` gets copied to. Example: `src/main/java/com/example/integrations/foo`
|
|
32
|
+
2. **Integration test source path inside the host** — where `$1/<tests>/*` gets copied to, and where `:UnitTests:` are discovered. Example: `src/test/java/com/example/integrations/foo`
|
|
33
|
+
3. **Test package for the test-filter argument** — the fully qualified package the test runner uses to scope discovery. Example: `com.example.integrations.foo`
|
|
34
|
+
|
|
35
|
+
Author one `***implementation reqs***` entry per fact (use the `add-implementation-requirement` skill). Phrase each one in terms of `:UnitTests:` (the predefined concept) and the integration's other concepts, not as raw paths floating in the spec — that keeps the reqs reviewable and the renderer can resolve them when emitting scripts:
|
|
36
|
+
|
|
37
|
+
```plain
|
|
38
|
+
- :UnitTests: of :Implementation: live in `src/test/java/com/example/integrations/foo` inside the host codebase.
|
|
39
|
+
- The corresponding integration source code lives in `src/main/java/com/example/integrations/foo`.
|
|
40
|
+
- The fully qualified test package used for test discovery is `com.example.integrations.foo`.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
These three facts feed directly into the script bodies:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# clean existing code from the host
|
|
47
|
+
rm -rf $MAIN_PROJECT_FOLDER/<integration source path>/*
|
|
48
|
+
rm -rf $MAIN_PROJECT_FOLDER/<integration test source path>/*
|
|
49
|
+
|
|
50
|
+
# create destinations and copy generated code into the host
|
|
51
|
+
mkdir -p $MAIN_PROJECT_FOLDER/<integration source path>
|
|
52
|
+
mkdir -p $MAIN_PROJECT_FOLDER/<integration test source path>
|
|
53
|
+
cp -R $1/<integration source path>/* $MAIN_PROJECT_FOLDER/<integration source path>
|
|
54
|
+
cp -R $1/<integration test source path>/* $MAIN_PROJECT_FOLDER/<integration test source path>
|
|
55
|
+
|
|
56
|
+
# run the unit tests scoped to the integration's package
|
|
57
|
+
mvn test -Dtest='<test package>.**.*Test' checkstyle:check
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Rules that flow from this:
|
|
61
|
+
|
|
62
|
+
- **The three paths must agree.** The source path, the test-source path, and the test package describe the same module from three angles. A mismatch (e.g. test path `src/test/java/com/example/foo` but test package `com.example.bar`) silently produces a green build with stale or zero tests
|
|
63
|
+
- **Paths are host-relative**, not absolute. `MAIN_PROJECT_FOLDER` comes from `HOST_CODEBASE_ROOT` (per the configuration concept); the paths above join onto it
|
|
64
|
+
- **The renderer's output folder `$1` mirrors the same layout.** `$1/<integration source path>/*` exists because the renderer emits the generated code into the same package directories it'll be copied into — the `cp -R` is a straight overlay, not a path translation
|
|
65
|
+
- **Each fact lives in one place.** If two `***implementation reqs***` entries declare slightly different test paths, the renderer can't tell which to use. Author the three reqs as a tight group (the example above is one bullet with two sub-bullets) so a future reviewer sees them together
|
|
66
|
+
- **For non-Java languages**, the three facts have language-specific equivalents — Python: `src/foo/`, `tests/foo/`, `tests.foo`; Node: `src/foo/`, `test/foo/`, `test/foo/**/*.test.ts`. The `implement-*-testing-script` skills know the mapping per language, but the spec still has to declare the host-relative paths so the skill knows where to copy
|
|
67
|
+
|
|
68
|
+
## Common contract (all three scripts)
|
|
69
|
+
|
|
70
|
+
All three scripts are invoked by the `codeplain` renderer with positional arguments. They MUST:
|
|
71
|
+
|
|
72
|
+
1. **Be POSIX-bash (`.sh`) on macOS / Linux, PowerShell (`.ps1`) on Windows.** Executable, idempotent — re-running with the same inputs produces the same result
|
|
73
|
+
2. **Validate every positional argument up front.** Exit with a clear `Usage:` line on bad args. Conventional exit codes (consistent with the shared testing-script rules):
|
|
74
|
+
- `1` — bad arguments / usage error (and "no tests discovered" for the conformance runner)
|
|
75
|
+
- `2` — missing or inaccessible input folder
|
|
76
|
+
- `69` — unrecoverable environment failure (missing toolchain, cannot enter working folder, install failed)
|
|
77
|
+
- Any other non-zero code — propagated verbatim from the underlying build / test tool
|
|
78
|
+
3. **Echo the toolchain home at the top** — the first thing a developer checks when CI breaks. For Java: `JAVA_HOME`. For Python: the active interpreter (`python --version` / `which python`). For Node: `node --version`. For Go: `go env GOROOT`. Pick the variable whose value most often explains "why does it work locally but not on the CI runner?"
|
|
79
|
+
4. **Respect `VERBOSE=1`.** Gate chatty diagnostic prints behind `if [ "${VERBOSE:-}" = "1" ]` (and the PowerShell equivalent). Errors and key step markers print unconditionally
|
|
80
|
+
5. **Resolve paths relative to the script, not `$PWD`.** Use `"$(cd "$(dirname "$0")/<relative-path-to-anchor>" && pwd)"`. Hard-coded `../../` chains break the moment the renderer's `cwd` changes
|
|
81
|
+
6. **Create destination directories before copying.** `mkdir -p` before any `cp -R` / `rsync` / `robocopy` — most copy commands do not create intermediate directories and fail silently in some shells
|
|
82
|
+
7. **Scope destructive operations narrowly.** Any `rm -rf` (or `Remove-Item -Recurse -Force`) targets only the module's own package path inside the working folder — never the host's `src/`, `target/`, `node_modules/`, `build/`, or `dist/` at the project root. Only `prepare_environment` owns the build-output directory's lifecycle for its `.tmp/` working folder
|
|
83
|
+
8. **Print where you are before you `cd`.** `echo "Moving to: $DIR"` saves an hour of debugging when paths are wrong. PowerShell: `Write-Host "Moving to: $DIR"`
|
|
84
|
+
|
|
85
|
+
## 1. `prepare_environment_<lang>` — copy into the host, then compile
|
|
86
|
+
|
|
87
|
+
Receives one positional argument: the renderer's build output folder (e.g. `plain_modules/<module>/`).
|
|
88
|
+
|
|
89
|
+
Purpose: stage the generated code **into the host codebase at the module's package path**, then run the host's install / build so that downstream test projects (specifically the conformance suite) can depend on it from the local dependency cache.
|
|
90
|
+
|
|
91
|
+
Required steps:
|
|
92
|
+
|
|
93
|
+
1. Validate `$1` (the renderer's output folder); fail with usage if missing
|
|
94
|
+
2. Resolve the host codebase root from the env var declared in the `host-codebase` concept (`HOST_CODEBASE_ROOT` is the conventional name; surface it in `--help` / usage)
|
|
95
|
+
3. `rm -rf` the module's package directories under the host's source tree — both main / source AND test directories. Both, because stale tests in the target package cause confusing failures
|
|
96
|
+
4. `mkdir -p` the destination directories
|
|
97
|
+
5. Copy `$1/src/...` (or the language-equivalent layout) into the host's source tree at the module's package path
|
|
98
|
+
6. `cd` into the host codebase root, verify with an explicit exit-code check (`if [ $? -ne 0 ]` / `if (! $?) { ... }`)
|
|
99
|
+
7. Clean the host's build-output directory before compiling — stale class / object files from a previous build cause confusing link-time mismatches when the contract has changed. Maven: `rm -rf ./target`. Cargo: `cargo clean -p <pkg>`. Go: `go clean -cache` (usually unnecessary, but mention if applicable). For npm projects with incremental builds disabled, wipe the relevant `dist/` / `build/` folder
|
|
100
|
+
8. Run the host's standard install / build command — but **skip the fat-artifact and skip tests** when the build system supports it:
|
|
101
|
+
- Maven: `mvn clean install -DskipTests` (plus any host-specific repackage-skip flag)
|
|
102
|
+
- npm / pnpm / yarn: `npm ci` (no test script; dependency install only)
|
|
103
|
+
- Python: `pip install -e .` (then any extra integration dependencies)
|
|
104
|
+
- Go: `go mod tidy && go build ./...`
|
|
105
|
+
- Cargo: `cargo fetch && cargo build --tests --no-run`
|
|
106
|
+
9. Exit `0` on success; propagate the underlying build tool's exit code on failure
|
|
107
|
+
|
|
108
|
+
**Why this script exists separately.** Conformance tests live in a separate project and resolve the integration's code as a dependency from a local cache (`~/.m2`, `node_modules`, the language equivalent). Without an explicit install step, conformance tests link against whatever was last built — which may not be the renderer's most recent output.
|
|
109
|
+
|
|
110
|
+
## 2. `run_unittests_<lang>` — copy into the host, then run unit tests there
|
|
111
|
+
|
|
112
|
+
Receives one positional argument: the renderer's build output folder.
|
|
113
|
+
|
|
114
|
+
Purpose: stage the generated code **into the host codebase** (the same way `prepare_environment` does) and run the module's unit tests + language-appropriate quality gates against the host project from inside the host root.
|
|
115
|
+
|
|
116
|
+
Required steps:
|
|
117
|
+
|
|
118
|
+
1. Validate `$1`
|
|
119
|
+
2. Resolve the host codebase root from the configured env var
|
|
120
|
+
3. `rm -rf` the module's main + test package directories under the host's source tree
|
|
121
|
+
4. `mkdir -p` and copy `$1/src/...` into place — **same staging as `prepare_environment`**
|
|
122
|
+
5. `cd` into the host codebase root
|
|
123
|
+
6. Run the test command **scoped to the module's package only**:
|
|
124
|
+
- Maven: `mvn test -Dtest='<fully.qualified.package>.**.*Test' <lint-check>:check`
|
|
125
|
+
- pytest: `pytest tests/<module>/` (or whatever path matches the module)
|
|
126
|
+
- Jest: `npm test -- --testPathPattern '<module>/'`
|
|
127
|
+
- Go: `go test ./<module>/...`
|
|
128
|
+
7. Always include the host's lint / static-analysis gate in the same invocation when the build system has one (Checkstyle, ESLint, Pylint / Ruff, `go vet`, `cargo clippy`, …). Running tests without the lint gate produces a false-green when style violations would otherwise block the build
|
|
129
|
+
|
|
130
|
+
Hard rules:
|
|
131
|
+
|
|
132
|
+
- **Yes, this duplicates `prepare_environment`'s copy step.** That's intentional — `run_unittests` must be self-contained per the shared testing-script rules (there is no activate-only variant for unit tests). A user invoking the unit-test script independently must not need `prepare_environment` to have run first
|
|
133
|
+
- **Never use a "clean and test" shortcut** (`mvn clean test`, `npm run rebuild`, …) — the cleanup belongs in `prepare_environment` and would erase whatever it installed
|
|
134
|
+
- **Never run the full test suite** when scoping is possible. A per-module script that runs the entire host's tests wastes minutes on every render iteration
|
|
135
|
+
|
|
136
|
+
## 3. `run_conformance_tests_<lang>` — copy into `.tmp/`, then run the external suite
|
|
137
|
+
|
|
138
|
+
Receives two positional arguments: the renderer's build output folder (`$1`) and the conformance-tests folder (`$2`).
|
|
139
|
+
|
|
140
|
+
Purpose: run the conformance suite in `$2` against the build that `prepare_environment` produced. The conformance suite is a separate project that consumes the integration as a dependency.
|
|
141
|
+
|
|
142
|
+
Required steps:
|
|
143
|
+
|
|
144
|
+
1. Validate `$1` and `$2`. **Only `$2` is actually used by the script body today** — but keep `$1` in the signature; the renderer passes both positionally
|
|
145
|
+
2. Stage `$2/*` into a scratch directory under `.tmp/<lang>_conformance/`. Wipe it first if it exists. Use a deletion form that handles hidden files and odd shells safely:
|
|
146
|
+
- Bash: `find "$DIR" -mindepth 1 -exec rm -rf {} +` (safer than `rm -rf "$DIR"/*`)
|
|
147
|
+
- PowerShell: `Remove-Item -Recurse -Force "$DIR\*"` after confirming the path exists
|
|
148
|
+
3. `cd` into the scratch directory
|
|
149
|
+
4. Compile / set up the conformance project — exit early if this fails:
|
|
150
|
+
- Maven: `mvn clean install -DskipTests`
|
|
151
|
+
- npm: `npm ci`
|
|
152
|
+
- Python: `pip install -r requirements.txt`
|
|
153
|
+
- Go / Cargo: standard build
|
|
154
|
+
5. Run the conformance test command and **capture stdout to a variable**:
|
|
155
|
+
- Bash: `output=$(mvn test --no-transfer-progress 2>&1)` (or the language equivalent)
|
|
156
|
+
- PowerShell: `$output = & <cmd> 2>&1 | Out-String`
|
|
157
|
+
6. **Parse the captured output strictly** (see *Pass criteria* below) — don't trust the test tool's exit code alone
|
|
158
|
+
7. On the failure path: print the captured output unconditionally so the renderer (and the user) can see what failed
|
|
159
|
+
8. On the success path: print the output only if `VERBOSE=1`
|
|
160
|
+
|
|
161
|
+
### Pass criteria (strict)
|
|
162
|
+
|
|
163
|
+
These are the **only** valid pass criteria. Anything outside this list is a failure:
|
|
164
|
+
|
|
165
|
+
- At least one test ran AND zero failures AND zero errors AND zero skipped. Maven example: `Tests run: [1-9][0-9]*, Failures: 0, Errors: 0, Skipped: 0`. Each language's test runner has an equivalent summary line — match it with a regex, not by substring
|
|
166
|
+
- Exit with the test tool's exit code on the success path (almost always `0`)
|
|
167
|
+
|
|
168
|
+
Failure cases — each must be detected explicitly:
|
|
169
|
+
|
|
170
|
+
- `Tests run: 0` (or the language equivalent) → **failure**. The renderer must never be told "green" when nothing ran. Exit `1` per the shared testing-script rules (the "no tests discovered" exit code)
|
|
171
|
+
- Any non-zero `Failures:`, `Errors:`, or `Skipped:` count → **failure**. Dump the captured output and propagate the test tool's exit code
|
|
172
|
+
- The test tool exited non-zero but the summary line shows `Tests run: 0` → still a `1` (no-tests-discovered), not the tool's own exit code
|
|
173
|
+
|
|
174
|
+
**Why parse stdout instead of trusting the test tool's exit code.** Every major test runner returns `0` when zero test classes match a filter. Maven Surefire, pytest with no collected tests, `go test ./pkg/that/has/no/_test.go/files`, Jest with an empty pattern — all green-by-default. The renderer's contract is "tests must run AND must pass" — the exit code alone cannot express that.
|
|
175
|
+
|
|
176
|
+
## Cross-cutting rules
|
|
177
|
+
|
|
178
|
+
- **No secrets in scripts.** Read credentials from env vars the renderer (or the user's shell) already sets. If a required env var is missing, fail fast with a clear `Error: $FOO is required for ...` and exit `69`. Never hardcode an API key, never read from a checked-in file
|
|
179
|
+
- **Mirror the package / module path exactly.** A typo in the package segment (e.g. one wrong directory in `com/example/foo/...` or `host_pkg/foo/bar/...`) makes `rm -rf` silently do nothing and the subsequent copy put files in the wrong place. Both produce a **green** build with stale code. Derive the package path from the `host-codebase` concept; never retype it inline
|
|
180
|
+
- **Don't add cleverness to the script that belongs in the build.** If you're tempted to `grep` the test output for more than the pass/fail summary, change the test or the test-runner config instead. The script is a thin contract — keep it that way
|
|
181
|
+
- **Don't use shortcut profiles that skip quality gates.** Maven's `-Pfast`, Gradle's `-x check`, npm's `--ignore-scripts`, pytest's `--no-cov`, `-DskipTests` outside `prepare_environment` — all defeat the purpose of these scripts. The build's full quality gate (tests + lint + coverage thresholds) must run for unit and conformance
|
|
182
|
+
- **Failure messages are for humans.** `printf "Error: <what failed> at <where>\n"` then exit with the right code. No silent failures, no `|| true` to paper over errors, no `2>/dev/null` on commands whose stderr matters
|
|
183
|
+
- **Use existing scripts as templates.** When you add a new embedded integration, copy from a known-good module, then search-and-replace only the package path and the test-filter argument. Resist the urge to "improve" structure mid-port — pattern-matching across many modules is a feature, not a bug
|
|
184
|
+
|
|
185
|
+
## Reference implementation (Java + Maven)
|
|
186
|
+
|
|
187
|
+
A working three-script set for a Java / Maven embedded integration lives under [`examples/integration-embedded-testing/`](examples/integration-embedded-testing/) next to this file:
|
|
188
|
+
|
|
189
|
+
- [`prepare_environment_java.sh`](examples/integration-embedded-testing/prepare_environment_java.sh) — copies `$1` into the host's source tree, cleans `target/`, runs `mvn clean install -DskipTests`
|
|
190
|
+
- [`run_unittests_java.sh`](examples/integration-embedded-testing/run_unittests_java.sh) — re-stages into the host and runs `mvn test -Dtest='<module-pkg>.**.*Test' checkstyle:check`
|
|
191
|
+
- [`run_conformance_tests_java.sh`](examples/integration-embedded-testing/run_conformance_tests_java.sh) — copies `$2` into `.tmp/java_conformance/`, builds the conformance project, runs the suite, and parses the Surefire summary line per the strict pass criteria above
|
|
192
|
+
|
|
193
|
+
Use them as a template when adding a new embedded integration in Java / Maven — copy, search-and-replace only the package segment and the `-Dtest` filter, then wire into `config.yaml`. For other languages (Python, Node.js, Go, Rust, …), the `implement-*-testing-script` skills generate the equivalent three-script set following the same contract.
|
|
194
|
+
|
|
195
|
+
## Adding a script for a new module
|
|
196
|
+
|
|
197
|
+
1. Copy the three scripts from a working embedded-integration module into the new module's directory
|
|
198
|
+
2. Search-and-replace the package segment everywhere it appears — including the test-filter argument (the most commonly missed spot)
|
|
199
|
+
3. `chmod +x` all three (and verify ACLs on Windows for the `.ps1` siblings)
|
|
200
|
+
4. Wire them into the module's `config.yaml` via the `unittests-script`, `conformance-tests-script`, and `prepare-environment-script` keys. Use `init-config-file` to assemble the canonical form
|
|
201
|
+
5. Smoke-test by running each script manually with the renderer's output folder as `$1` (and the conformance-tests folder as `$2` for the conformance runner). Confirm exit codes match what the renderer expects
|
|
202
|
+
|
|
203
|
+
For non-Java languages, the plain-forge skills [`implement-prepare-environment-script`](../skills/implement-prepare-environment-script/SKILL.md), [`implement-unit-testing-script`](../skills/implement-unit-testing-script/SKILL.md), and [`implement-conformance-testing-script`](../skills/implement-conformance-testing-script/SKILL.md) adapt the contract above to Python, Node.js, Go, Rust, Flutter, and others. Always route script creation through these skills so the cross-cutting rules above are applied uniformly.
|
|
@@ -16,6 +16,29 @@ Embedded means: the host codebase already exists, has its own language / framewo
|
|
|
16
16
|
- If a Phase 3 (`forge-plain`) tech-stack question seems to push back on a host rule, treat the host as ground truth and rewrite the question
|
|
17
17
|
- Implementation reqs added in Phase 3 are **transcribed** from the host stack verbatim — host language and exact version, host framework + version, dependency manager and manifest path, packaging layout, host conventions the contract must follow, and every host-package version the contract pins
|
|
18
18
|
|
|
19
|
+
## Project layout — the integration lives under `plain/` at the host root
|
|
20
|
+
|
|
21
|
+
The embedded integration's `.plain` specs (and the rest of the ***plain project — `template/`, `resources/`, `test_scripts/`, `config.yaml`, generated `plain_modules/`) live in a single top-level `plain/` folder at the host repository root, alongside the host's own source tree:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
<host repo>/
|
|
25
|
+
├── docs/
|
|
26
|
+
├── plain/ # ← the embedded ***plain integration lives here
|
|
27
|
+
│ ├── <module>.plain
|
|
28
|
+
│ ├── template/
|
|
29
|
+
│ ├── resources/
|
|
30
|
+
│ ├── test_scripts/
|
|
31
|
+
│ ├── plain_modules/ # generated; gitignored
|
|
32
|
+
│ └── config.yaml
|
|
33
|
+
└── src/ # host source tree (Java, Python, etc.)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This keeps the integration self-contained in one directory the user can `cd` into to render, while leaving the host's own source layout untouched.
|
|
37
|
+
|
|
38
|
+
- If the host **already** has a `plain/` directory, adopt it verbatim — see step 1 of *Discover before you ask* below
|
|
39
|
+
- If the host does **not** yet have one, create `plain/` at the repo root; do not invent a different name (`specs/`, `plain_specs/`, …)
|
|
40
|
+
- The three test scripts still `cd` into `$HOST_CODEBASE_ROOT` (the host repo root) to compile and run tests against the host project; the `plain/` folder is the integration's **authoring** root, not its **runtime** root
|
|
41
|
+
|
|
19
42
|
## Discover before you ask
|
|
20
43
|
|
|
21
44
|
Run host discovery **before** the first Phase 1 question. Treat the results as ground truth for everything that follows.
|
|
@@ -38,12 +61,27 @@ Run host discovery **before** the first Phase 1 question. Treat the results as g
|
|
|
38
61
|
- The renderer is allowed to redefine **only** symbols the host does not provide and the contract schema does not capture
|
|
39
62
|
- Naming the symbol by FQN is not optional decoration — it tells the renderer where the type comes from, which prevents a duplicate definition under `plain_modules/`
|
|
40
63
|
|
|
41
|
-
##
|
|
64
|
+
## Link host files at their original path — never copy them into `resources/`
|
|
65
|
+
|
|
66
|
+
The integration's `.plain` module lives **inside the host codebase** (per the "adopt the host's `.plain` setup verbatim" step). That means host source files are already reachable as linked resources via their host-relative paths. The integration spec references them **in place**; it never duplicates them under `resources/host/`.
|
|
42
67
|
|
|
43
|
-
- Every host file the integration touches (base classes, configuration modules, registries, exception classes, lifecycle hooks) is
|
|
68
|
+
- Every host file the integration touches (base classes, configuration modules, registries, exception classes, lifecycle hooks) is referenced from the relevant spec using `***linked resource***` syntax with the **path as it exists in the host codebase** — e.g. `[base.py](host_project/integrations/base.py)` if the `.plain` module sits next to `host_project/`
|
|
69
|
+
- **Do NOT copy host files into `resources/host/`.** A copy creates a second source of truth that drifts the moment the host file is edited; the rendered code will then disagree with whatever the host actually ships
|
|
70
|
+
- **Do NOT add host files via the `add-resource` skill's default copy behavior** when that behavior would duplicate the file — point at the existing host path directly
|
|
44
71
|
- **Never inline a host file's contents** into a spec
|
|
45
|
-
- **Never describe a host symbol's shape from memory** — the renderer reads the linked file's bytes and that is the source of truth
|
|
46
|
-
- This obeys the broader [`linked-resources.md`](linked-resources.md) rules
|
|
72
|
+
- **Never describe a host symbol's shape from memory** — the renderer reads the linked file's bytes at its host path and that is the source of truth
|
|
73
|
+
- This still obeys the broader [`linked-resources.md`](linked-resources.md) rules: a directory is not a valid link, a URL is not a valid link, a binary is not a valid link. Only the *location* changes — host files live where the host put them, not under `resources/`
|
|
74
|
+
|
|
75
|
+
### What still belongs under `resources/`
|
|
76
|
+
|
|
77
|
+
This rule applies to **host source code only**. Other artifacts still live under `resources/` exactly like in a non-embedded project:
|
|
78
|
+
|
|
79
|
+
- **Contract schemas authored by the integration** — `resources/contract/<entry-point>.schema.json`
|
|
80
|
+
- **Configuration schema** — `resources/config.schema.json`
|
|
81
|
+
- **Captured probe responses** (from the live-API cross-check) — `resources/fixtures/<endpoint>.<case>.json`
|
|
82
|
+
- **Static lookup tables** the integration owns — `resources/error-map.yaml`, `resources/retry-policy.yaml`, etc.
|
|
83
|
+
|
|
84
|
+
The rule of thumb: if the host wrote it and ships it, link it where the host put it. If the integration is authoring it for the first time, it goes under `resources/`.
|
|
47
85
|
|
|
48
86
|
## The contract spec declares inheritance, not duplication
|
|
49
87
|
|
|
@@ -76,47 +114,25 @@ The renderer reads the directives from the spec and the shapes from the linked s
|
|
|
76
114
|
- If two reqs are in tension (one from the host, one newly authored), the host wins; rewrite or drop the newly authored req
|
|
77
115
|
- Do not author a req that re-declares something the host already enforces — that's a maintenance burden with no benefit
|
|
78
116
|
|
|
79
|
-
## Test-script wiring —
|
|
117
|
+
## Test-script wiring — copy into the host, run tests there
|
|
80
118
|
|
|
81
|
-
Embedded integrations are tested **inside
|
|
119
|
+
Embedded integrations are tested **inside the host codebase itself**. The prepare and unit-test scripts copy the renderer's output (`$1`, i.e. `plain_modules/<module>/`) into the host's source tree at the module's package path, then compile / test the host project in place. Only the conformance script uses a `.tmp/` scratch folder, because the conformance suite is a separate project that consumes the host build as a dependency.
|
|
82
120
|
|
|
83
121
|
This matters because the integration's generated code references host symbols by their full import path (e.g. `from host_project.integrations.base import IntegrationContract`). Those imports only resolve cleanly when the test process is rooted in the host's package layout — anything else creates path edge cases that bite later in conformance failures.
|
|
84
122
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Both scripts stage their own working copy under `.tmp/<lang>_<arg>/` per the shared testing-script rules (input folders are read-only). Inside that working folder:
|
|
88
|
-
|
|
89
|
-
1. **Copy the host codebase into the working folder.** Use a recursive copy (`rsync -a --delete`, `cp -R`, or `robocopy`) so each test run starts from a clean, identical host tree
|
|
90
|
-
2. **Overlay `plain_modules/<module>/` into the host's package tree at the target package path** recorded in the `host-codebase` concept (e.g. `plain_modules/integrations/<provider>/` → `<host_copy>/host_project/integrations/<provider>/`). Use a copy that overwrites — the generated module replaces any same-named files in the host copy
|
|
91
|
-
3. **Install dependencies inside the merged tree.** The host's own manifest (`pyproject.toml` / `package.json` / `go.mod` / …) drives the install. The integration's extra dependencies are layered on top by either (a) the renderer having written them into the host's manifest already, or (b) the script installing them explicitly after the host install
|
|
92
|
-
4. **Run the test command from inside the merged tree** — `cwd` is `<host_copy>`, and the test runner discovers tests using the host's normal layout
|
|
93
|
-
|
|
94
|
-
### Per-script responsibilities
|
|
95
|
-
|
|
96
|
-
- **`prepare_environment_<lang>`** performs the full merge once per render (host copy + plain_modules overlay + dependency install + any build artifacts) into `.tmp/<lang>_<arg>/`. The N subsequent `run_conformance_tests_<lang>` invocations attach to this populated folder (activate-only variant — see the shared testing-script rules)
|
|
97
|
-
- **`run_unittests_<lang>`** performs its **own** merge into its **own** `.tmp/<lang>_<arg>/` working folder — it does not share `prepare`'s folder, and it does not depend on `prepare` having run. The host copy + overlay + install steps are duplicated inside the unit-test script for self-containedness
|
|
98
|
-
- **`run_conformance_tests_<lang>`** does **not** re-merge. It `cd`s into the working folder that `prepare_environment` populated and runs the conformance command against the merged tree
|
|
99
|
-
|
|
100
|
-
### Language-specific merge primitives
|
|
101
|
-
|
|
102
|
-
| Language | Host copy | Overlay | Dependency install | Test invocation (inside merged tree) |
|
|
103
|
-
|----------|-----------|---------|--------------------|--------------------------------------|
|
|
104
|
-
| Python | `rsync -a <host>/ .tmp/python_<arg>/` | `rsync -a plain_modules/<module>/ .tmp/python_<arg>/<host_pkg_path>/` | `cd .tmp/python_<arg> && pip install -e .` (then integration extras) | `cd .tmp/python_<arg> && pytest …` |
|
|
105
|
-
| Node.js | `rsync -a <host>/ .tmp/node_<arg>/` | `rsync -a plain_modules/<module>/ .tmp/node_<arg>/<host_pkg_path>/` | `cd .tmp/node_<arg> && npm ci` (then integration extras) | `cd .tmp/node_<arg> && npm test …` |
|
|
106
|
-
| Go | `rsync -a <host>/ .tmp/go_<arg>/` | `rsync -a plain_modules/<module>/ .tmp/go_<arg>/<host_pkg_path>/` | `cd .tmp/go_<arg> && go mod tidy` | `cd .tmp/go_<arg> && go test ./…` |
|
|
107
|
-
| Java / Kotlin | `rsync -a <host>/ .tmp/java_<arg>/` | `rsync -a plain_modules/<module>/ .tmp/java_<arg>/<host_pkg_path>/` | `cd .tmp/java_<arg> && mvn -q -DskipTests install` | `cd .tmp/java_<arg> && mvn test …` |
|
|
108
|
-
| Rust | `rsync -a <host>/ .tmp/rust_<arg>/` | `rsync -a plain_modules/<module>/ .tmp/rust_<arg>/<host_pkg_path>/` | `cd .tmp/rust_<arg> && cargo fetch` | `cd .tmp/rust_<arg> && cargo test …` |
|
|
123
|
+
See [`integration-embedded-testing.md`](integration-embedded-testing.md) for the full per-script contract (arg validation, exit codes, idempotency, output parsing, pass criteria, cross-cutting rules). The summary that belongs in *this* file:
|
|
109
124
|
|
|
110
|
-
|
|
125
|
+
- **`prepare_environment_<lang>`** copies `$1` into the host's source tree at the module's package path, cleans the host's build-output directory, then runs the host's install / build (e.g. `mvn clean install -DskipTests`). The conformance suite later resolves the integration from the host's local dependency cache
|
|
126
|
+
- **`run_unittests_<lang>`** repeats the same copy into the host (self-contained — must work without `prepare_environment` having run first), then runs the module's unit tests + lint scoped to the module's package
|
|
127
|
+
- **`run_conformance_tests_<lang>`** copies `$2` (the conformance-tests folder) into `.tmp/<lang>_conformance/`, `cd`s in, builds the conformance project, and runs it against the build that `prepare_environment` already installed into the host
|
|
111
128
|
|
|
112
129
|
### Invariants the scripts must enforce
|
|
113
130
|
|
|
114
131
|
- **Host root is a parameter, not a literal.** No script may hardcode an absolute host path. Read the host root from an env var (e.g. `HOST_CODEBASE_ROOT`) with a sensible default matching the user's layout (e.g. `../host_project`). Surface the env var in each script's `--help` / usage banner. Capture this env var in the integration's configuration concept so it has exactly one declared name across specs, scripts, and runtime
|
|
115
|
-
- **Target package path is read from the `host-codebase` concept** — never inferred from a heuristic. The renderer writes that path into the generated module's location too, so the
|
|
116
|
-
- **
|
|
117
|
-
- **Each
|
|
118
|
-
-
|
|
119
|
-
- **`***test reqs***` must document the merge contract** — name the merge primitive (`rsync` / `cp -R` / `robocopy`), the env var the host root is read from, the target package path inside the host where `plain_modules/<module>/` is overlaid, and the language-appropriate install + test commands. The renderer reads this req and emits the right script bodies
|
|
132
|
+
- **Target package path is read from the `host-codebase` concept** — never inferred from a heuristic. The renderer writes that path into the generated module's location too, so the copy destination is unambiguous
|
|
133
|
+
- **Destructive ops are scoped to the module's own package path** under the host's source tree. `rm -rf` never touches the host's `src/main/`, `target/`, `node_modules/`, `build/`, or `dist/` at the project root. Only the module-specific package directories are wiped
|
|
134
|
+
- **Each script is idempotent.** Re-running the same script with the same `$1` yields the same result
|
|
135
|
+
- **`***test reqs***` must document the script contract** — name the env var the host root is read from, the target package path inside the host where `$1` is copied, and the language-appropriate install + test commands. The renderer reads this req and emits the right script bodies
|
|
120
136
|
|
|
121
137
|
## Embedded-specific completion checklist
|
|
122
138
|
|
|
@@ -125,25 +141,26 @@ Before declaring an embedded integration done, in addition to the shared checkli
|
|
|
125
141
|
- [ ] `host-codebase` concept records host root path, host language + version, host dependency manager + manifest, target package path, host base class import path, target generated-class FQN, and the host conventions the contract follows
|
|
126
142
|
- [ ] Contract spec carries renderer directives (target language, target file path, target class name, host base class to subclass, host-package pins) and **links** to the contract schema; no class body is inlined
|
|
127
143
|
- [ ] Every host symbol referenced in any spec uses its fully qualified import path and is tagged "imported from the host codebase; do not redefine"
|
|
128
|
-
- [ ] Every host file the integration touches has been
|
|
144
|
+
- [ ] Every host file the integration touches is linked at its **original host-relative path** — no host file has been copied into `resources/host/` or anywhere else, and no host file contents are inlined in any spec
|
|
129
145
|
- [ ] `forge-plain` Phase 2's tech-stack decisions are transcribed verbatim from the host (no independent stack choices)
|
|
130
146
|
- [ ] Host-package version pins are copied into `***implementation reqs***`
|
|
131
|
-
- [ ] `prepare_environment` copies the host
|
|
132
|
-
- [ ] `run_unittests` runs the same host-
|
|
133
|
-
- [ ] `run_conformance_tests` `
|
|
147
|
+
- [ ] `prepare_environment` copies `$1` into the host's source tree at the module's package path, cleans the host's build-output directory, and runs the host's install / build so the conformance suite can resolve the integration from the local dependency cache
|
|
148
|
+
- [ ] `run_unittests` runs the same copy-into-host sequence (self-contained — does not depend on `prepare_environment` having run) and invokes the host's test runner scoped to the module's package
|
|
149
|
+
- [ ] `run_conformance_tests` copies `$2` into `.tmp/<lang>_conformance/`, `cd`s in, builds the conformance project, and runs it against the host build that `prepare_environment` already installed
|
|
134
150
|
- [ ] Host codebase root is read from a named env var (default value documented in each script's usage) — never hardcoded
|
|
135
|
-
- [ ] Target package path
|
|
136
|
-
- [ ]
|
|
137
|
-
- [ ] A `***test reqs***` entry documents the
|
|
151
|
+
- [ ] Target package path inside the host where `$1` is copied is read from the `host-codebase` concept — never inferred
|
|
152
|
+
- [ ] Every `rm -rf` in the scripts is scoped to the module's own package directory under the host's source tree — never targets the host's `src/main/`, `target/`, `node_modules/`, `build/`, or `dist/` at the project root
|
|
153
|
+
- [ ] A `***test reqs***` entry documents the script contract (env var name, target package path inside the host, install + test commands)
|
|
138
154
|
|
|
139
155
|
## Anti-patterns specific to embedded integrations
|
|
140
156
|
|
|
141
157
|
- **Choosing a different language, framework, or dependency manager than the host.** The host stack is inherited; cross-stack `requires` chains are forbidden by [`requires-modules.md`](requires-modules.md)
|
|
142
158
|
- **Redefining a host class under `plain_modules/`.** Reference the host symbol by FQN; let the renderer import it
|
|
143
|
-
- **Inlining a host base class body into the contract spec.**
|
|
159
|
+
- **Inlining a host base class body into the contract spec.** Reference the host file as a linked resource **at its original host-relative path** — do not inline its contents and do not copy it into `resources/`
|
|
160
|
+
- **Copying host source files into `resources/host/` (or anywhere under `resources/`).** That creates a second source of truth that silently drifts from the host. Link the host file in place; the integration `.plain` module already lives inside the host codebase, so the path resolves naturally
|
|
144
161
|
- **Hardcoding the host codebase path in any spec or script.** Read it from the env var declared in the configuration concept
|
|
145
162
|
- **Asking the user to design the integration's tech stack.** Read it from the host's manifest files
|
|
146
163
|
- **Authoring an integration spec that contradicts an existing integration in the same host** without first surfacing the conflict and getting explicit user confirmation
|
|
147
|
-
- **Wiring tests with `PYTHONPATH` / `NODE_PATH` / Go `replace` directives instead of physically
|
|
148
|
-
- **
|
|
149
|
-
- **
|
|
164
|
+
- **Wiring tests with `PYTHONPATH` / `NODE_PATH` / Go `replace` directives instead of physically copying `$1` into the host's package tree.** The import-stitching approach is forbidden — every prepare / unit test starts by copying the generated module into the host's source tree at the module's package path
|
|
165
|
+
- **Letting an `rm -rf` in a test script reach the host's `src/main/`, `target/`, `node_modules/`, `build/`, or `dist/` at the project root.** Destructive ops are scoped strictly to the module's own package directory. A wrong package path in the script silently does nothing and copies files in the wrong place — both produce a green build with stale code
|
|
166
|
+
- **Running conformance tests against a stale local dependency cache.** `prepare_environment` must run before conformance for the conformance project to resolve the integration from `~/.m2` (or the language equivalent). If conformance gets invoked without a fresh prepare, dependent on which build last hit the cache, the suite tests the wrong code
|
|
@@ -44,6 +44,30 @@ Key formatting rules:
|
|
|
44
44
|
- Each test bullet is indented under the `***acceptance tests***` header.
|
|
45
45
|
- Multiple acceptance tests can be listed under a single functional spec.
|
|
46
46
|
|
|
47
|
+
### Line syntax (hard rule)
|
|
48
|
+
|
|
49
|
+
**Every line inside `***acceptance tests***` must be its own list item starting with `- `.** ***plain has no concept of bare continuation lines — indented prose without a leading `- ` is **invalid syntax** and the renderer will reject it.
|
|
50
|
+
|
|
51
|
+
- Hard limit: 120 characters per line. If a sentence is too long, **split it at a natural clause boundary into nested `- ` bullets** — never wrap onto an unprefixed line.
|
|
52
|
+
- Nested clarifications are also `- ` items, indented under the parent. The indentation alone is not enough; the leading `- ` is required.
|
|
53
|
+
|
|
54
|
+
BAD — bare continuation lines (invalid ***plain syntax, will not render):
|
|
55
|
+
|
|
56
|
+
```plain
|
|
57
|
+
***acceptance tests***
|
|
58
|
+
- Processing 250 :Task: items should result in 3 batches with the
|
|
59
|
+
last batch containing the remaining 50 items.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
GOOD — every line starts with `- `:
|
|
63
|
+
|
|
64
|
+
```plain
|
|
65
|
+
***acceptance tests***
|
|
66
|
+
- Processing 250 :Task: items should result in 3 batches.
|
|
67
|
+
- The first two batches each contain 100 items.
|
|
68
|
+
- The last batch contains the remaining 50 items.
|
|
69
|
+
```
|
|
70
|
+
|
|
47
71
|
## Rules
|
|
48
72
|
|
|
49
73
|
### Conformance with the Functional Spec
|
|
@@ -56,6 +56,29 @@ Attributes and constraints are nested sub-bullets:
|
|
|
56
56
|
- Due Date - completion deadline (optional)
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
## Line syntax (hard rule)
|
|
60
|
+
|
|
61
|
+
**Every line inside `***definitions***` must be its own list item starting with `- `.** ***plain has no concept of bare continuation lines — indented prose without a leading `- ` is **invalid syntax** and the renderer will reject it.
|
|
62
|
+
|
|
63
|
+
- Hard limit: 120 characters per line. If a sentence is too long, **split it at a natural clause boundary into nested `- ` bullets** — never wrap onto an unprefixed line.
|
|
64
|
+
- Nested attributes are also `- ` items, indented under the parent. The indentation alone is not enough; the leading `- ` is required.
|
|
65
|
+
|
|
66
|
+
BAD — bare continuation lines (invalid ***plain syntax, will not render):
|
|
67
|
+
|
|
68
|
+
```plain
|
|
69
|
+
- :Task: describes an activity that needs to be done by :User:.
|
|
70
|
+
- Name is a short description that the user provides when creating
|
|
71
|
+
the task and is shown in the task list.
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
GOOD — every line starts with `- `:
|
|
75
|
+
|
|
76
|
+
```plain
|
|
77
|
+
- :Task: describes an activity that needs to be done by :User:.
|
|
78
|
+
- Name is a short description provided when creating the task.
|
|
79
|
+
- The name is shown in the task list.
|
|
80
|
+
```
|
|
81
|
+
|
|
59
82
|
## Validation Checklist
|
|
60
83
|
|
|
61
84
|
- [ ] Name uses `:CamelCase:` notation
|
|
@@ -57,6 +57,29 @@ Each functional spec must be unambiguous — the renderer should have only one r
|
|
|
57
57
|
- All :Participant: members of the :Conversation: can see the new :Message:.
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
### Line syntax (hard rule)
|
|
61
|
+
|
|
62
|
+
**Every line inside `***functional specs***` must be its own list item starting with `- `.** ***plain has no concept of bare continuation lines — indented prose without a leading `- ` is **invalid syntax** and the renderer will reject it.
|
|
63
|
+
|
|
64
|
+
- Hard limit: 120 characters per line. If a sentence is too long, **split it at a natural clause boundary into nested `- ` bullets** — never wrap onto an unprefixed line.
|
|
65
|
+
- Nested clarifications are also `- ` items, indented under the parent. The indentation alone is not enough; the leading `- ` is required.
|
|
66
|
+
|
|
67
|
+
BAD — bare continuation lines (invalid ***plain syntax, will not render):
|
|
68
|
+
|
|
69
|
+
```plain
|
|
70
|
+
- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(),
|
|
71
|
+
which returns a list of :EventEnvelope: dicts conforming to the gateway's
|
|
72
|
+
contract.
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
GOOD — every line starts with `- `:
|
|
76
|
+
|
|
77
|
+
```plain
|
|
78
|
+
- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle().
|
|
79
|
+
- The method returns a list of :EventEnvelope: dicts.
|
|
80
|
+
- The dicts must conform to the gateway's :EventEnvelope: contract.
|
|
81
|
+
```
|
|
82
|
+
|
|
60
83
|
### Deterministic Interface
|
|
61
84
|
|
|
62
85
|
Specs must be detailed enough that a developer can use the built software without reading the generated code. All external interfaces must be explicit — REST endpoint paths and HTTP methods, CLI command names and arguments, file formats, message schemas, etc. Never leave interface details up to the renderer's discretion.
|
|
@@ -80,6 +80,30 @@ Each functional spec must be unambiguous — the renderer should have only one r
|
|
|
80
80
|
- All :Participant: members of the :Conversation: can see the new :Message:.
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
+
### Line syntax (hard rule, per spec)
|
|
84
|
+
|
|
85
|
+
**Every line inside `***functional specs***` must be its own list item starting with `- `.** ***plain has no concept of bare continuation lines — indented prose without a leading `- ` is **invalid syntax** and the renderer will reject the whole file.
|
|
86
|
+
|
|
87
|
+
- Hard limit: 120 characters per line. If a sentence is too long, **split it at a natural clause boundary into nested `- ` bullets** — never wrap onto an unprefixed line.
|
|
88
|
+
- Nested clarifications are also `- ` items, indented under the parent. The indentation alone is not enough; the leading `- ` is required.
|
|
89
|
+
- This rule applies to **every** spec in the batch — one bad continuation line invalidates the entire insert.
|
|
90
|
+
|
|
91
|
+
BAD — bare continuation lines (invalid ***plain syntax, will not render):
|
|
92
|
+
|
|
93
|
+
```plain
|
|
94
|
+
- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle(),
|
|
95
|
+
which returns a list of :EventEnvelope: dicts conforming to the gateway's
|
|
96
|
+
contract.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
GOOD — every line starts with `- `:
|
|
100
|
+
|
|
101
|
+
```plain
|
|
102
|
+
- :GatewayWebhook: should hand off :StripeRequest: to :StripeIntegration:.handle().
|
|
103
|
+
- The method returns a list of :EventEnvelope: dicts.
|
|
104
|
+
- The dicts must conform to the gateway's :EventEnvelope: contract.
|
|
105
|
+
```
|
|
106
|
+
|
|
83
107
|
### Deterministic Interface
|
|
84
108
|
|
|
85
109
|
Specs must be detailed enough that a developer can use the built software without reading the generated code. All external interfaces must be explicit — REST endpoint paths and HTTP methods, CLI command names and arguments, file formats, message schemas, etc. Never leave interface details up to the renderer's discretion.
|
|
@@ -60,6 +60,30 @@ Implementation reqs are bullet points in the `***implementation reqs***` section
|
|
|
60
60
|
|
|
61
61
|
Reference defined `:Concepts:` where they add clarity. Implementation reqs in non-leaf sections apply to all subsections.
|
|
62
62
|
|
|
63
|
+
## Line syntax (hard rule)
|
|
64
|
+
|
|
65
|
+
**Every line inside a section must be its own list item starting with `- `.** ***plain has no concept of bare continuation lines — indented prose without a leading `- ` is **invalid syntax** and the renderer will reject it.
|
|
66
|
+
|
|
67
|
+
- Hard limit: 120 characters per line. If a sentence is too long, **split it at a natural clause boundary into nested `- ` bullets** — never wrap onto an unprefixed line.
|
|
68
|
+
- Nested detail is also a `- ` item, indented under its parent. The indentation alone is not enough; the leading `- ` is required.
|
|
69
|
+
|
|
70
|
+
BAD — bare continuation lines (invalid ***plain syntax, will not render):
|
|
71
|
+
|
|
72
|
+
```plain
|
|
73
|
+
- :Implementation: tech stack will be finalized in Phase 2.
|
|
74
|
+
- Until then, treat this section as a placeholder so the renderer accepts
|
|
75
|
+
the file. Phase 2 will replace this with language, framework, HTTP
|
|
76
|
+
client, packaging, and architecture decisions.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
GOOD — every line starts with `- `:
|
|
80
|
+
|
|
81
|
+
```plain
|
|
82
|
+
- :Implementation: tech stack will be finalized in Phase 2.
|
|
83
|
+
- Until then, treat this section as a placeholder so the renderer accepts the file.
|
|
84
|
+
- Phase 2 will replace it with language, framework, HTTP client, packaging, and architecture decisions.
|
|
85
|
+
```
|
|
86
|
+
|
|
63
87
|
## Encapsulation Warning
|
|
64
88
|
|
|
65
89
|
`requires` modules only receive functional specs from their dependencies — not implementation reqs. If downstream modules need certain behavior to be visible, that behavior must be expressed in functional specs, not here.
|
|
@@ -59,6 +59,28 @@ Test reqs are bullet points in the `***test reqs***` section:
|
|
|
59
59
|
|
|
60
60
|
Reference predefined concepts like `:ConformanceTests:` and any defined `:Concepts:` where they add clarity.
|
|
61
61
|
|
|
62
|
+
## Line syntax (hard rule)
|
|
63
|
+
|
|
64
|
+
**Every line inside `***test reqs***` must be its own list item starting with `- `.** ***plain has no concept of bare continuation lines — indented prose without a leading `- ` is **invalid syntax** and the renderer will reject it.
|
|
65
|
+
|
|
66
|
+
- Hard limit: 120 characters per line. If a sentence is too long, **split it at a natural clause boundary into nested `- ` bullets** — never wrap onto an unprefixed line.
|
|
67
|
+
- Nested clarifications are also `- ` items, indented under the parent. The indentation alone is not enough; the leading `- ` is required.
|
|
68
|
+
|
|
69
|
+
BAD — bare continuation lines (invalid ***plain syntax, will not render):
|
|
70
|
+
|
|
71
|
+
```plain
|
|
72
|
+
- :ConformanceTests: must mock all external HTTP calls so that the
|
|
73
|
+
test suite remains hermetic and does not depend on network access.
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
GOOD — every line starts with `- `:
|
|
77
|
+
|
|
78
|
+
```plain
|
|
79
|
+
- :ConformanceTests: must mock all external HTTP calls.
|
|
80
|
+
- The test suite must remain hermetic.
|
|
81
|
+
- Tests must not depend on network access.
|
|
82
|
+
```
|
|
83
|
+
|
|
62
84
|
## Validation Checklist
|
|
63
85
|
|
|
64
86
|
- [ ] Describes conformance testing concerns, not unit tests or behavior
|