plain-forge
Advanced tools
@@ -31,3 +31,3 @@ --- | ||
| - General technical terms that are not language-specific are fine: null values, JSON types, HTTP status codes, REST endpoints, etc. | ||
| - **Naming concrete components is encouraged.** Functional specs can and should refer to `:CsvToJsonConverter:` and its methods `:CsvToJson:` / `:JsonToCsv:` and pin down their inputs, outputs, and error behavior — those names are part of the public contract and survive a language switch. What they must **not** do is bake in how the contract is realized (`@staticmethod`, `class Foo extends Bar`, `List<T>`, etc.) | ||
| - **Naming concrete components is encouraged.** Functional specs can and should refer to concrete domain components, services, or entities (e.g., `:PaymentProcessor:`, `:UserRepository:`, `:DataConverter:`) and their operations (e.g., `:ChargeCard:`, `:FindById:`), pinning down their inputs, outputs, and error behavior. Those names are part of the public contract and survive a language switch. What they must **not** do is bake in how the contract is realized (`@staticmethod`, `class Foo extends Bar`, `List<T>`, `async def`, etc.) | ||
| - **Litmus test:** if the project switched from Python to Java (or vice versa), would the functional spec read correctly with only `***implementation reqs***` updated? If yes, the spec is language-agnostic. If the spec itself would need rewording, the construct belongs in implementation reqs. | ||
@@ -34,0 +34,0 @@ |
@@ -12,3 +12,3 @@ --- | ||
| > **For test-script authoring**, also follow [`integration-embedded-testing.md`](integration-embedded-testing.md). It defines the per-script contract (`prepare_environment_<lang>`, `run_unittests_<lang>`, `run_conformance_tests_<lang>`) — staging into the host vs `.tmp/`, arg validation, exit codes, output parsing, the three `***implementation reqs***` entries the spec must declare so the scripts can be generated, and a Java / Maven reference implementation. This file (`integration-embedded.md`) only summarizes the test-script wiring; the testing rule is the source of truth. | ||
| > **For test-script authoring**, also follow [`integration-embedded-testing.md`](integration-embedded-testing.md). It defines the per-script contract (`prepare_environment_<lang>`, `run_unittests_<lang>`, `run_conformance_tests_<lang>`) — staging into the host vs `.tmp/`, arg validation, exit codes, output parsing, the three `***implementation reqs***` entries the spec must declare so the scripts can be generated. This file (`integration-embedded.md`) only summarizes the test-script wiring; the testing rule is the source of truth. | ||
@@ -18,3 +18,3 @@ ## The host codebase dictates the tech stack (hard rule) | ||
| - Language, framework, dependency manager, packaging layout, coding standards, error model, logging library, and architecture are **inherited** from the host — they are **never chosen** by the integration spec | ||
| - Do not re-ask the user about any of these in any phase — they are facts to be discovered from the host's manifest files (`pyproject.toml`, `package.json`, `go.mod`, `Cargo.toml`, `pom.xml`, …) and source tree | ||
| - Do not re-ask the user about any of these in any phase — they are facts to be discovered from the host's manifest files (`pyproject.toml`, `package.json`, `go.mod`, `Cargo.toml`, `pom.xml`, …) and other integrations. | ||
| - 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 | ||
@@ -21,0 +21,0 @@ - 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 |
@@ -47,8 +47,8 @@ --- | ||
| - **Always `fetch` the provider's documentation — even if you already "know" the API.** Training-data memory of any third-party REST API is, by definition, stale: endpoints get renamed, fields get added or deprecated, auth flows change, error envelopes shift, and rate-limit headers are renamed between releases. The only acceptable source of truth for what the API looks like *today* is the provider's own live documentation, retrieved with `fetch` at spec-authoring time. This applies without exception — there is no API well-known enough to skip this step, and a spec authored from memory is a spec authored against the wrong contract. Concretely: | ||
| - **Always `fetch` the provider's documentation — even if you already "know" the API.** The only acceptable source of truth for what the API looks like *today* is the provider's own live documentation, retrieved with `fetch` at spec-authoring time. This applies without exception — there is no API well-known enough to skip this step, and a spec authored from memory is a spec authored against the wrong contract. Concretely: | ||
| - Before authoring **any** endpoint, auth, error, pagination, or webhook concept, `fetch` the relevant documentation page(s) and quote concrete details (status codes, field names, header names, error formats) directly from the fetched content into the resources under `resources/`. Never paraphrase from memory. | ||
| - Save the fetched documentation snapshot under `resources/docs/<provider>/<page>.md` (or `.html` if structure matters) so the spec has a stable doc artifact the renderer and reviewers can consult, independent of the live URL changing or going behind auth. | ||
| - If a documentation page is unreachable (paywall, login wall, JS-only render that `fetch` can't see), say so explicitly and ask the user for the canonical content rather than filling the gap from memory. | ||
| - **The fetched documentation is then cross-checked against the live API** — see the rest of this section. Memory is not part of the loop at any point. | ||
| - **Validate credentials against the live API** before authoring downstream specs. A 2xx on a low-risk read-only endpoint (`/v1/me`, `/account`, `/whoami`, `/health`) is the gate. On 401/403, stop and resolve before continuing. | ||
| - **The fetched documentation is then cross-checked against the live API** — see the rest of this section. | ||
| - **Validate credentials against the live API** before authoring downstream specs. A 2xx on a low-risk read-only endpoint (`/v1/me`, `/account`, `/whoami`, `/health`) is the gate. On 401/403, stop and resolve before continuing. The credentials should be validated against the live API before any downstream specs are authored. | ||
| - **Issue the minimum cross-check coverage** with `fetch`: one discovery / schema endpoint if available, one list endpoint per primary entity in scope, one single-object retrieval per primary entity, one empty/boundary response, one 404, one 400/422, and one deliberate 401. | ||
@@ -116,2 +116,8 @@ - **Save every probe response under `resources/fixtures/`** with credentials redacted. The fixtures become the seed for `resources/<provider>.openapi.yaml` and feed conformance tests later. | ||
| - **Retry policy** in `resources/retry-policy.yaml` | ||
| - **Transient vs. permanent exception classification** — every error the integration can raise is classified as either *transient* (retryable per the retry policy above) or *permanent* (surfaced to the caller immediately). The classification lives in `resources/error-map.yaml` (per-status-code) and in the integration's own exception hierarchy concept, and the two must agree. Concretely: | ||
| - **Transient by default**: network-level errors (`ConnectionError` / equivalent, DNS failure, socket timeout, TLS handshake failure, connection reset), HTTP 408, 425, 429, 500, 502, 503, 504, and any provider-specific application-level error codes the docs / live API mark as retryable (e.g. Stripe's `lock_timeout`, AWS `ThrottlingException`) | ||
| - **Permanent by default**: every other 4xx (400, 401, 403, 404, 405, 409, 410, 415, 422, …), schema-validation failures on otherwise-2xx responses, signature-verification failures on webhooks, and any provider error code the docs / live API mark as non-retryable | ||
| - **Integration exception hierarchy**: the integration exposes a base exception class (e.g. `<Provider>Error`) with two direct subclasses, `<Provider>TransientError` (raised after the retry policy gives up on a transient condition) and `<Provider>PermanentError` (raised immediately on a permanent condition). Every concrete exception inherits from exactly one of those two — callers branch on the base class, never on individual subclasses | ||
| - **The classification is data-driven**, not code-driven — `resources/error-map.yaml` is the source of truth, the renderer reads it to generate the exception hierarchy and the `is_transient(error) -> bool` predicate. Spec text never enumerates status codes inline | ||
| - **Discrepancies found during the live-API cross-check override the docs** — if the docs say a code is transient but the live API never recovers from it (or vice versa), the live behavior wins and the divergence is recorded in `error-map.yaml` plus a note in the error-model concept ("docs claim X retryable, live API returns Y permanently; we follow the live API") | ||
| - **Idempotency strategy** in `resources/idempotency.yaml` + idempotency header in `components.parameters` | ||
@@ -118,0 +124,0 @@ - **Webhook contracts** as `resources/webhooks/<event>.schema.json` per event type + `resources/webhook-signing.yaml` |
@@ -409,5 +409,5 @@ --- | ||
| Naming concrete *components* — classes, methods, functions, fields — is encouraged and not in conflict with this rule. A functional spec should freely refer to `:CsvToJsonConverter:` as a component with methods `:CsvToJson:` and `:JsonToCsv:`, describe their inputs, outputs, and error behavior, and treat those names as part of the public contract. What it must *not* do is bake in how that contract is realized in a particular language: no `@staticmethod` decorators, no `class Foo extends Bar` phrasing, no `List<T>` or `Optional<T>` syntax, no "POJO with static methods" framing. | ||
| Naming concrete *components* — classes, methods, functions, fields — is encouraged and not in conflict with this rule. A functional spec should freely refer to concrete domain components, services, or entities (e.g., `:PaymentProcessor:`, `:UserRepository:`, `:DataConverter:`) and their operations (e.g., `:ChargeCard:`, `:FindById:`), pinning down their inputs, outputs, and error behaviors, and treat those names as part of the public contract. What it must *not* do is bake in how that contract is realized in a particular language: no `@staticmethod` decorators, no `class Foo extends Bar` phrasing, no `List<T>` or `Optional<T>` syntax, no "POJO with static methods" framing. | ||
| The litmus test: if you switched the project from Python to Java (or vice versa), would the functional spec still read correctly with only `***implementation reqs***` updated? If yes, the spec is language-agnostic. If the functional spec itself would need rewording because it referenced a language-specific construct, the construct belongs in implementation reqs instead. The component name (`:CsvToJsonConverter:`) is the same across languages; the syntax used to express "static method on a class" is not. | ||
| The litmus test: if you switched the project from Python to Java (or vice versa), would the functional spec still read correctly with only `***implementation reqs***` updated? If yes, the spec is language-agnostic. If the functional spec itself would need rewording because it referenced a language-specific construct, the construct belongs in implementation reqs instead. The component name (`:DataConverter:`) is the same across languages; the syntax used to express "static method on a class" is not. | ||
| - **Keep sentences short and clear — but never at the cost of ambiguity.** Spec lines should be easy to read and understand at a glance. Prefer short, direct sentences and plain words over long sentences and jargon — if a 10-cent word and a 50-cent word say the same thing, use the 10-cent one. This applies to every spec section, not only functional specs: `***definitions***`, `***implementation reqs***`, `***test reqs***`, and `***acceptance tests***` should all be as concise as they can be while staying unambiguous. The hard constraint is in the second half of that rule: **wordy-but-precise always beats terse-but-ambiguous.** If trimming a clause, a qualifier, or a sub-bullet would leave the spec open to more than one reasonable interpretation, leave it in. When a sentence starts to grow because the behavior is genuinely complex, split it into two short sentences (or into a parent line + sub-bullets) rather than dropping detail. Concision is in service of clarity, never the other way around. | ||
@@ -424,8 +424,8 @@ - **Specs must be deterministic enough to both *run* and *use* the software without reading the generated code.** A developer should be able to figure out, from the specs alone, two distinct things: | ||
| ```plain | ||
| - Implement :CsvToJsonConverter: as a stateless utility component exposing two operations. | ||
| - :CsvToJson: takes a CSV row and a list of header strings, returns a JSON object keyed by header name. Infer and convert value types. Empty CSV values must be converted to null. Null values must be preserved — keys with null values must appear in the result, not be omitted. Handle CSV escaping (quoted values, commas within quotes). Raise an error if the number of columns does not match the headers. | ||
| - :JsonToCsv: takes a JSON object and a list of header strings defining column order, returns a CSV row without a header row. Handle CSV escaping for values containing commas or quotes. Output an empty string for null or missing fields. Extra keys in the JSON object that are not present in the headers list should be silently ignored. | ||
| - Implement :DataConverter: as a stateless utility component exposing two operations. | ||
| - :FormatData: takes a raw data string and a format type, returns a formatted string. Infer and convert value types. Empty inputs must be converted to null. Null values must be preserved — keys with null values must appear in the result, not be omitted. Handle escaping logic. Raise an error if the format type is not supported. | ||
| - :ParseData: takes a formatted string and returns a structured object. Output an empty structure for null or missing fields. Unrecognized extra keys should be silently ignored. | ||
| ``` | ||
| This rule is complementary to the earlier "specs should be language-agnostic" guideline, not in conflict with it. Component names (`:CsvToJsonConverter:`, `:CsvToJson:`, `:JsonToCsv:`) and behavioral contracts belong in functional specs because they survive a language switch unchanged. Language-specific realizations of those contracts — "POJO class with static methods", "Python module with module-level functions", `@staticmethod`, `class Foo`, exception types like `IllegalArgumentException` vs `ValueError`, choice of test framework (`pytest` vs `JUnit`), mocking library, fixture style, assertion syntax — belong in `***implementation reqs***` and `***test reqs***`, because those are exactly what changes when the target language changes. Use `***implementation reqs***` for *how the production code is realized* (language, frameworks, libraries, syntax, error types) and `***test reqs***` for *how the tests are realized* (test framework, test runner, mocking and fixture conventions, parametrization style, naming conventions, file layout). The goal is that swapping languages requires editing only `***implementation reqs***` and `***test reqs***`; the functional spec for `:CsvToJsonConverter:` should read identically whether the project is in Python, Java, or anything else. | ||
| This rule is complementary to the earlier "specs should be language-agnostic" guideline, not in conflict with it. Component names (`:DataConverter:`, `:FormatData:`, `:ParseData:`) and behavioral contracts belong in functional specs because they survive a language switch unchanged. Language-specific realizations of those contracts — "POJO class with static methods", "Python module with module-level functions", `@staticmethod`, `class Foo`, exception types like `IllegalArgumentException` vs `ValueError`, choice of test framework (`pytest` vs `JUnit`), mocking library, fixture style, assertion syntax — belong in `***implementation reqs***` and `***test reqs***`, because those are exactly what changes when the target language changes. Use `***implementation reqs***` for *how the production code is realized* (language, frameworks, libraries, syntax, error types) and `***test reqs***` for *how the tests are realized* (test framework, test runner, mocking and fixture conventions, parametrization style, naming conventions, file layout). The goal is that swapping languages requires editing only `***implementation reqs***` and `***test reqs***`; the functional spec for `:DataConverter:` should read identically whether the project is in Python, Java, or anything else. | ||
@@ -432,0 +432,0 @@ ## Line Length Rule |
@@ -215,53 +215,4 @@ --- | ||
| Always set the logs to be verbose in `config.yaml` and make sure the following logging_config.yaml exists and is set: | ||
| Always set the logs to be verbose in `config.yaml`. | ||
| ```yaml | ||
| version: 1 | ||
| disable_existing_loggers: false | ||
| root: | ||
| level: DEBUG | ||
| loggers: | ||
| codeplain: | ||
| level: DEBUG | ||
| propagate: true | ||
| git: | ||
| level: DEBUG | ||
| propagate: true | ||
| transitions: | ||
| level: DEBUG | ||
| propagate: true | ||
| transitions.extensions.diagrams: | ||
| level: DEBUG | ||
| propagate: true | ||
| anthropic: | ||
| level: DEBUG | ||
| propagate: true | ||
| openai: | ||
| level: DEBUG | ||
| propagate: true | ||
| google: | ||
| level: DEBUG | ||
| propagate: true | ||
| google_genai: | ||
| level: DEBUG | ||
| propagate: true | ||
| httpx: | ||
| level: DEBUG | ||
| propagate: true | ||
| httpcore: | ||
| level: DEBUG | ||
| propagate: true | ||
| urllib3: | ||
| level: DEBUG | ||
| propagate: true | ||
| requests: | ||
| level: DEBUG | ||
| propagate: true | ||
| asyncio: | ||
| level: DEBUG | ||
| propagate: true | ||
| ``` | ||
| ### 2. Renderer memory under `plain_modules/<module>/.memory/` | ||
@@ -268,0 +219,0 @@ |
+1
-1
| { | ||
| "name": "plain-forge", | ||
| "version": "1.0.8", | ||
| "version": "1.0.9", | ||
| "description": "Conversational spec-writing tool for ***plain specification language", | ||
@@ -5,0 +5,0 @@ "type": "module", |
604185
0.17%