plain-forge 1.0.20 → 1.0.21-next.55f13d7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +1 -0
  2. package/bin/cli.mjs +1 -1
  3. package/forge/rules/bullet-continuation.md +16 -6
  4. package/forge/rules/concise-specs-examples.md +431 -0
  5. package/forge/rules/concise-specs-integration-examples.md +272 -0
  6. package/forge/rules/concise-specs.md +80 -0
  7. package/forge/rules/definitions.md +7 -4
  8. package/forge/rules/exported-concepts.md +3 -2
  9. package/forge/rules/func-specs.md +10 -3
  10. package/forge/rules/impl-reqs.md +8 -4
  11. package/forge/rules/import-modules.md +4 -6
  12. package/forge/rules/integration-embedded.md +0 -1
  13. package/forge/rules/integration-standalone.md +1 -1
  14. package/forge/rules/integrations.md +6 -7
  15. package/forge/rules/linked-resources.md +9 -8
  16. package/forge/rules/module-structure.md +105 -0
  17. package/forge/rules/required-concepts.md +5 -5
  18. package/forge/rules/requires-modules.md +37 -21
  19. package/forge/rules/test-reqs.md +11 -7
  20. package/forge/skills/add-acceptance-test/SKILL.md +1 -0
  21. package/forge/skills/add-concept/SKILL.md +2 -1
  22. package/forge/skills/add-feature/SKILL.md +2 -2
  23. package/forge/skills/add-functional-spec/SKILL.md +2 -1
  24. package/forge/skills/add-functional-specs/SKILL.md +2 -1
  25. package/forge/skills/add-implementation-requirement/SKILL.md +2 -1
  26. package/forge/skills/add-test-requirement/SKILL.md +1 -0
  27. package/forge/skills/break-down-func-spec/SKILL.md +1 -0
  28. package/forge/skills/check-forge-version/SKILL.md +154 -0
  29. package/forge/skills/check-forge-version/scripts/check-forge-version.mjs +368 -0
  30. package/forge/skills/create-import-module/SKILL.md +3 -3
  31. package/forge/skills/create-requires-module/SKILL.md +24 -19
  32. package/forge/skills/forge-plain/SKILL.md +1 -1
  33. package/forge/skills/forge-plain/references/checklist.md +2 -1
  34. package/forge/skills/forge-plain/references/phase-1-product.md +3 -2
  35. package/forge/skills/forge-plain/references/phase-2-tech.md +1 -1
  36. package/forge/skills/load-plain-reference/SKILL.md +20 -2
  37. package/forge/skills/load-plain-reference/references/project-model.md +0 -11
  38. package/forge/skills/plain-healthcheck/SKILL.md +1 -1
  39. package/forge/skills/refactor-module/SKILL.md +3 -3
  40. package/forge/skills/resolve-section-ownership/SKILL.md +65 -0
  41. package/forge/skills/resolve-spec-conflict/SKILL.md +1 -0
  42. package/package.json +5 -5
@@ -0,0 +1,272 @@
1
+ ---
2
+ description: Worked BAD to GOOD examples of concise integration specs, where over-precision and obvious edge cases dominate
3
+ ---
4
+
5
+ # Worked examples of concise integration specs
6
+
7
+ `concise-specs.md` is the rule and `concise-specs-examples.md` is the general example bank. This file covers integration specs only, because they fail differently.
8
+
9
+ An integration is technically demanding, the provider's contract is large, and the author has the provider's documentation open while writing. The result is a spec that is *too precise*: status codes, field lists, retry arithmetic, and every edge case the API can produce, written out as prose bullets. It reads as rigorous and is unreviewable.
10
+
11
+ Conciseness here is almost never about covering less. `integrations.md` makes edge-case coverage a hard floor — auth, environments, versioning, pagination, rate limits, the error model, retries, idempotency, webhooks, data mapping, compliance, observability. Every one of those must be covered. The lever is **where** the coverage lives:
12
+
13
+ - **Shapes and enumerations go to `resources/`** — schemas, error maps, retry policy, rate-limit headers. A spec bullet links to the file; it never restates its contents.
14
+ - **The spec keeps behavior** — what the integration does with the shape, and what a caller observes.
15
+ - **Mechanism goes to `***implementation reqs***`** — retry logic, exception hierarchies, token caching, connection handling. Integration precision is mostly HOW, and `impl-reqs.md` owns HOW. `***functional specs***` keeps only what a caller can observe.
16
+
17
+ That last lever is easy to miss, so several examples below move a bullet between sections rather than shortening it. A fact in the wrong section is worse than a verbose one: the renderer reads each concept only from its owning section, so a misplaced fact is silently ignored.
18
+
19
+ Applied consistently, a spec gets shorter as coverage goes *up*, because each new corner case adds a line to a resource file rather than a paragraph to the spec.
20
+
21
+ The BAD blocks intentionally demonstrate the mistakes they name, including invalid syntax. Copy only from GOOD blocks.
22
+
23
+ ## Status codes and error categories enumerated inline
24
+
25
+ BAD:
26
+
27
+ ```plain
28
+ ***definitions***
29
+
30
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
31
+
32
+ ***functional specs***
33
+
34
+ - :ProviderClient: retries the request when :ProviderApi: returns 408, 425, 429, 500, 502, 503, or 504, and also on connection errors, DNS failures, socket timeouts, TLS handshake failures, and connection resets.
35
+
36
+ - :ProviderClient: does not retry on 400, 401, 403, 404, 405, 409, 410, 415, or 422, and raises immediately.
37
+
38
+ - On 429, :ProviderClient: waits and tries again, unless the retry budget is exhausted.
39
+ ```
40
+
41
+ GOOD:
42
+
43
+ ```plain
44
+ ***definitions***
45
+
46
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
47
+
48
+ ***implementation reqs***
49
+
50
+ - :ProviderClient: classifies every failure of :ProviderApi: through [resources/error-map.yaml](resources/error-map.yaml) and retries the transient ones under [resources/retry-policy.yaml](resources/retry-policy.yaml).
51
+ ```
52
+
53
+ The status-code lists move into `error-map.yaml`, which the renderer already reads to generate the exception hierarchy and the transient predicate. Coverage is unchanged and the classification now has one source of truth instead of two that can disagree — see *Transient vs. permanent exception classification* in `integrations.md`.
54
+
55
+ Note the section too. Classifying and retrying is mechanism, so it is an implementation req; what belongs in `***functional specs***` is only the part a caller observes — which exception each error category surfaces as.
56
+
57
+ ## Restating a schema the resource already carries
58
+
59
+ BAD:
60
+
61
+ ```plain
62
+ ***definitions***
63
+
64
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
65
+
66
+ - :Customer: is a customer record returned by :ProviderApi:. It has an `id` (string), an `email` (string), a `created_at` (ISO 8601 timestamp), a `status` (one of `active`, `pending`, `churned`), an optional `phone` (string or null), and a `metadata` object of string keys to string values.
67
+ ```
68
+
69
+ GOOD:
70
+
71
+ ```plain
72
+ ***definitions***
73
+
74
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
75
+
76
+ - :Customer: is a :ProviderApi: record, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml) under `components.schemas.Customer`.
77
+ ```
78
+
79
+ Field lists rot the moment the provider adds a field, and the renderer generates types from the schema, not from the sentence. Keep in the definition only what the schema cannot say — a stability or uniqueness guarantee, a unit, a lifecycle rule.
80
+
81
+ ## Specifying the provider instead of the integration
82
+
83
+ BAD:
84
+
85
+ ```plain
86
+ ***definitions***
87
+
88
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
89
+
90
+ - :Customer: is a :ProviderApi: record, at `components.schemas.Customer`.
91
+
92
+ ***functional specs***
93
+
94
+ - :ProviderApi: returns `200` with a JSON body when a request succeeds.
95
+
96
+ - :ProviderApi: returns `404` when the requested :Customer: does not exist.
97
+
98
+ - :ProviderApi: rate-limits clients and includes the remaining quota in the response headers.
99
+
100
+ - :ProviderApi: requires the `Authorization` header on every request.
101
+ ```
102
+
103
+ GOOD:
104
+
105
+ ```plain
106
+ ***definitions***
107
+
108
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
109
+
110
+ - :ProviderClient: is the component every call to :ProviderApi: goes through.
111
+
112
+ - :Customer: is a :ProviderApi: record, at `components.schemas.Customer`.
113
+
114
+ - :CustomerId: identifies a :Customer: and is stable across updates.
115
+
116
+ - :CustomerNotFoundError: is raised when :ProviderApi: has no :Customer: for a :CustomerId:.
117
+
118
+ ***implementation reqs***
119
+
120
+ - :ProviderClient: authenticates to :ProviderApi: with the credential in the environment variable `PROVIDER_API_KEY`.
121
+
122
+ - :ProviderClient: raises :CustomerNotFoundError: when :ProviderApi: has no :Customer: for the requested :CustomerId:.
123
+
124
+ ***functional specs***
125
+
126
+ - :ProviderClient: fetches a :Customer: from :ProviderApi: by :CustomerId:.
127
+ ```
128
+
129
+ Every BAD bullet is true, and none of them is deleted — they are moved. The success shape, the `404`, and the `Authorization` requirement are what an OpenAPI file exists to state; the rate-limit model has its own conventional files. Provider behavior with no machine-readable home goes in `resources/<provider>-notes.md`. `integrations.md` carries the full artifact table.
130
+
131
+ What stays in the sections is only what gets built. A bullet stating provider behavior renders into nothing — the renderer cannot build the provider, and no conformance test can assert a sentence that makes no claim about the integration. State instead what the integration does when the provider behaves that way.
132
+
133
+ Note where the links hang: both artifacts attach to the :ProviderApi: concept, and everything else references the token. `linked-resources.md` requires each resource to be linked from exactly one place, so a second spec needing the contract cannot introduce a second, divergent reference to it.
134
+
135
+ ## One operation fragmented into several specs
136
+
137
+ Integration work invites this: the author walks through the request lifecycle — credential check, discovery call, request shape, paging — and each step becomes its own spec.
138
+
139
+ BAD — four specs, one operation:
140
+
141
+ ```plain
142
+ ***definitions***
143
+
144
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
145
+
146
+ - :Record: is an account record of :ProviderApi:.
147
+
148
+ ***functional specs***
149
+
150
+ - A call to `fetch` fails before any request to :ProviderApi: is sent when `PROVIDER_API_KEY` is unset or empty.
151
+ - The failure names `PROVIDER_API_KEY`.
152
+
153
+ - A call to `fetch` reads the field catalogue from :ProviderApi: once, before requesting any :Record:.
154
+ - The catalogue yields the name of every field the account defines.
155
+ - A call to `fetch` whose catalogue read fails requests no :Record: items.
156
+
157
+ - A request for :Record: items from :ProviderApi: names every field in the catalogue and asks for at most 100 records.
158
+ - Archived :Record: items are not requested.
159
+
160
+ - A call to `fetch` requests pages until :ProviderApi: returns a response carrying no next-page cursor.
161
+ - Each page after the first is requested with the cursor from the previous response.
162
+ - The :Record: items of every page are carried forward together.
163
+ ```
164
+
165
+ GOOD — one spec, with the result stated first:
166
+
167
+ ```plain
168
+ ***definitions***
169
+
170
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
171
+
172
+ - :Record: is an account record of :ProviderApi:.
173
+
174
+ - :RecordSource: is the component through which the integration reads :Record: items from :ProviderApi:.
175
+
176
+ ***functional specs***
177
+
178
+ - :RecordSource: `fetch()` returns every non-archived :Record: with every field :ProviderApi: defines, paging under [resources/pagination.yaml](resources/pagination.yaml).
179
+ - :RecordSource: reads the field catalogue once per `fetch()`.
180
+ - `fetch()` fails before any request when `PROVIDER_API_KEY` is unset or empty, naming it.
181
+ - A failed catalogue read fails `fetch()` before any :Record: is requested.
182
+ ```
183
+
184
+ Merging is not in tension with the complexity limit. Split a spec when it implies more than 200 changed lines of code (`analyze-if-func-spec-too-complex`, then `break-down-func-spec`); merge when several specs implement one operation that fits inside that budget. Fragment count is not a measure of size.
185
+
186
+ ## The auth flow narrated step by step
187
+
188
+ BAD:
189
+
190
+ ```plain
191
+ ***functional specs***
192
+
193
+ - :ProviderAuth: first reads the client id and the client secret, then builds a form-encoded body containing `grant_type=client_credentials`, the client id, and the client secret, then posts that body to the token endpoint, then parses the JSON response, then extracts the `access_token` and the `expires_in` fields, then stores the token in memory together with the moment it expires, and then attaches it to subsequent requests as a bearer token.
194
+
195
+ - Before each request, :ProviderAuth: compares the current time to the stored expiry and, if the token has expired or is about to expire, repeats the whole procedure described above to obtain a new token.
196
+ ```
197
+
198
+ GOOD:
199
+
200
+ ```plain
201
+ ***definitions***
202
+
203
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
204
+
205
+ - :ProviderClient: is the component every call to :ProviderApi: goes through.
206
+
207
+ - :AccessToken: is the bearer credential :ProviderApi: accepts.
208
+
209
+ - :AuthTokenManager: manages the lifecycle of all :AccessToken:
210
+
211
+ ***implementation reqs***
212
+
213
+ - :ProviderAuth: obtains an :AccessToken: by the grant in [resources/provider.openapi.yaml](resources/provider.openapi.yaml) under `components.securitySchemes.oauth2`.
214
+
215
+ - :ProviderAuth: reads `PROVIDER_CLIENT_ID` and `PROVIDER_CLIENT_SECRET` from the environment.
216
+
217
+ - :AuthTokenManager: refreshes an :AccessToken: 60 seconds before it expires, and once on a `401` from :ProviderApi:.
218
+
219
+ - Concurrent requests finding the :AccessToken: expired cause one refresh.
220
+
221
+ - :ProviderClient: uses :AuthTokenManager: to cache and refresh :AccessToken:.
222
+ ```
223
+
224
+ The grant type, endpoint, body encoding, and response fields are in the security scheme. What
225
+ survives is the policy the scheme does not describe: the env-var names, the refresh margin, the
226
+ retry-once-on-401 rule, and the concurrency behavior.
227
+
228
+ Almost all of it is implementation reqs — acquisition, caching, and refresh are machinery a caller never sees. Not all of it, though, and the rewrite has to name a component to say the rest. :ProviderAuth: performs the grant and has no observable surface; :AuthTokenManager: is what consumes it and hands tokens to :ProviderClient:, so it is the concept a functional spec can take as its subject. Without that consumer the section is left with a subject-less bullet, which `func-specs.md` rules out — and that one spec is the only line here a live conformance test can assert.
229
+
230
+ ## Per-endpoint test reqs
231
+
232
+ BAD:
233
+
234
+ ```plain
235
+ ***definitions***
236
+
237
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
238
+
239
+ ***test reqs***
240
+
241
+ - :ConformanceTests: for the customer list endpoint run against the live :ProviderApi: sandbox.
242
+
243
+ - :ConformanceTests: for the customer retrieval endpoint run against the live :ProviderApi: sandbox.
244
+
245
+ - :ConformanceTests: for the invoice list endpoint run against the live :ProviderApi: sandbox.
246
+
247
+ - :ConformanceTests: for the webhook endpoint run against the live :ProviderApi: sandbox.
248
+
249
+ - :ConformanceTests: for the customer list endpoint use the credentials from the environment.
250
+
251
+ - :ConformanceTests: for the invoice list endpoint use the credentials from the environment.
252
+ ```
253
+
254
+ GOOD:
255
+
256
+ ```plain
257
+ ***definitions***
258
+
259
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
260
+
261
+ ***test reqs***
262
+
263
+ - :ConformanceTests: run against the live :ProviderApi: sandbox — no recorded fixtures and no mocking of provider calls.
264
+ - Credentials come from `PROVIDER_CLIENT_ID` and `PROVIDER_CLIENT_SECRET` in the environment.
265
+ - The `429` and forced-`5xx` paths mock that endpoint alone; every other path is live.
266
+
267
+ - :ConformanceTests: are executed via the run script [test_scripts/run_conformance_tests_python.sh](test_scripts/run_conformance_tests_python.sh).
268
+ ```
269
+
270
+ A policy that holds for every endpoint is stated once. Enumerate an endpoint only where it departs
271
+ from the policy — here, the two paths that cannot be exercised live safely, which
272
+ `integrations.md` requires to be documented explicitly.
@@ -0,0 +1,80 @@
1
+ ---
2
+ description: Conciseness and plain-language rules that keep .plain specs reviewable by a human
3
+ ---
4
+
5
+ # Rules for keeping `.plain` specs concise
6
+
7
+ These rules apply to every section, to concept explanations, and to acceptance tests. `concise-specs-examples.md` holds a worked BAD to GOOD pair for each failure mode named here, and `concise-specs-integration-examples.md` covers the failure modes specific to integration specs.
8
+
9
+ ## Why this matters
10
+
11
+ - A spec is the source of truth, so a human has to be able to review it
12
+ - A long, dense, or ornate spec is skipped rather than reviewed — an unreviewed spec is an unverified spec, however correct it happens to be
13
+ - Length is not a proxy for rigor: a spec earns trust by being checkable, not by being thorough-looking
14
+ - Conciseness is about removing what carries no information, never about removing required detail — the deterministic-interface, disambiguation, and testability rules always win (see `func-specs.md`)
15
+
16
+ ## Do not write the obvious
17
+
18
+ Delete any line a competent reader would already assume. Ask of every line: *if this were missing, would anything be built differently?* If not, cut it.
19
+
20
+ Do not write:
21
+
22
+ - Restatements of a concept's own name (`:UserEmail: is the email address of a :User:.` adds nothing beyond what `definitions.md` already requires the concept to carry)
23
+ - Universal software defaults: that invalid input is rejected, that errors are reported, that data is saved when saving is the point of the operation
24
+ - Rationale, motivation, benefits, or background — the spec states what is, not why it was chosen
25
+ - Standard behavior of a technology already named in `***implementation reqs***` (an HTTP framework parses HTTP; a database driver opens connections)
26
+ - Restatements of a nearby bullet in different words, or of anything already stated in an `import`ed or `require`d module
27
+ - Anything about work the spec is not about ("no changes to the existing UI")
28
+ - Meta-commentary about the spec itself ("this spec is intentionally small", "as described above")
29
+
30
+ Worked pairs for every item above — padding, rationale, defaults, technology behavior,
31
+ meta-commentary, and duplication of an `import` — are in `concise-specs-examples.md`.
32
+
33
+ ## Use plain words
34
+
35
+ - Prefer the shortest word that is still exact; a technical term needs to be exact, an elaborate one is not
36
+ - Write short sentences: one clause plus, at most, one qualifier
37
+ - Use the same word for the same thing every time — synonym variety reads as two different things
38
+ - Do not use adverbs and adjectives that no test can check: `robust`, `seamless`, `efficient`, `scalable`, `comprehensive`, `modern`, `properly`, `gracefully`, `as appropriate`, `where applicable`, `best practices`. Either state the checkable fact (`a response is returned within 200 ms`) or delete the word
39
+ - Do not hedge: `should ideally`, `may optionally`, `typically`, `generally`, `if possible`. A spec states one behavior; a hedge tells the renderer to guess
40
+
41
+ | Do not write | Write |
42
+ |---|---|
43
+ | utilize, leverage | use |
44
+ | facilitate, enable the ability to | let, or name the action |
45
+ | in order to | to |
46
+ | prior to / subsequent to | before / after |
47
+ | in the event that | if |
48
+ | is responsible for handling | handles |
49
+ | performs the validation of | validates |
50
+ | a plurality of | several, or a number |
51
+ | terminate, initiate | stop, start |
52
+ | functionality for X | X |
53
+
54
+ ## Keep the shape small
55
+
56
+ - One statement per bullet, one bullet per fact (see `bullet-continuation.md`)
57
+ - Nest a sub-bullet only to remove a real ambiguity; a sub-bullet that merely elaborates is filler
58
+ - Consecutive specs that share a subject are usually one spec: if a top-level bullet only tells part of the story of one operation, merge the set, lead with the operation's result, and demote the steps and failure modes to sub-bullets (see *One operation fragmented into several specs* in `concise-specs-integration-examples.md`)
59
+ - Tables, prose paragraphs, and code blocks do not belong in a section; external artifacts are linked from `resources/` (see `linked-resources.md`)
60
+ - A bullet that answers more than one of *what is it*, *what does it do*, *how is it built*, and *how is it tested* is not one fact — split it across the sections that own those facts, which reads better and is the only way the renderer sees each part (see `module-structure.md`, and *Split one crowded bullet across its owning sections* in `concise-specs-examples.md`)
61
+
62
+ ## Terse is not a defect
63
+
64
+ A finished spec usually reads as abrupt: clipped bullets, no connective tissue, no lead-ins. That is the intended shape, not something to smooth out afterwards.
65
+
66
+ - Add a word only to make a line correct, unambiguous, or checkable — never to make it read better
67
+ - Do not merge short bullets into one sentence for rhythm, and do not open a section or a bullet with a lead-in
68
+ - The bar is the other rules — correct, intent intact, reviewable, testable. A spec that clears them is finished however clipped it sounds
69
+ - Filler is not neutral: the renderer reads every line as a requirement, so a line written for flow can invent one
70
+
71
+ ## Review pass before writing to disk
72
+
73
+ Read the drafted lines once more and apply, in order:
74
+
75
+ 1. **The deletion test** — cut every line whose removal would change nothing about what gets built
76
+ 2. **The word test** — replace every ornate word with a plain one, and delete every uncheckable adjective, adverb, and hedge
77
+ 3. **The duplication test** — cut anything already stated in this file, an `import`, or a `requires` chain
78
+ 4. **The read-aloud test** — a line you cannot read aloud in one breath is either two facts or one padded one
79
+
80
+ If a cut would remove an interface detail, a boundary case, or a disambiguation, keep the fact and shorten the wording instead — see *Do not over-cut* in `concise-specs-examples.md` for what a spec cut past its content looks like.
@@ -13,7 +13,7 @@ When writing or editing a `***definitions***` section in a `.plain` file, always
13
13
 
14
14
  ## Uniqueness
15
15
  - Concept names must be globally unique across the spec and all its imports
16
- - Check for collisions with imported templates, `import` and `requires` modules before adding
16
+ - Check for collisions with concepts coming from `import` and `requires` modules before adding
17
17
 
18
18
  ## Predefined concepts (do not redefine)
19
19
  - ***plain ships several predefined concepts that are available in every module without being defined
@@ -31,6 +31,10 @@ When writing or editing a `***definitions***` section in a `.plain` file, always
31
31
  | `:ConformanceTests:` | Auto-generated tests verifying conformance to the spec |
32
32
  | `:AcceptanceTest:` / `:AcceptanceTests:` | Tests validating specific aspects of the implementation |
33
33
 
34
+ ## Conciseness
35
+ - A definition is one plain sentence plus, where needed, its attributes — see `concise-specs.md`
36
+ - Never restate the concept name as its own definition, and never explain why the concept exists
37
+
34
38
  ## Define before use
35
39
  - A concept must be defined before it is referenced in any section (definitions, implementation reqs, functional specs, test reqs)
36
40
  - Sources of definitions: the module's own `***definitions***`, an `import`ed module's definitions, or a `require`d module's `exported_concepts`
@@ -60,8 +64,8 @@ Bad — circular:
60
64
  ```
61
65
 
62
66
  ## Exported concepts are not transitive
63
- - If module A exports a concept and module B `requires` A, module C `requires` B does **not** gain access to A's exports
64
- - Shared concepts belong in a common import module
67
+ - If module A exports a concept and module B `requires` A, a module C with `requires: [B]` does **not** gain access to A's exports
68
+ - To use an ancestor's exports, list that ancestor in `requires` as well; concepts shared more broadly belong in a common import module
65
69
 
66
70
  ## Description quality
67
71
  - Descriptions must be clear, concise, and language-agnostic
@@ -89,7 +93,6 @@ Example of defining technical components alongside their behavior:
89
93
 
90
94
  - :CsvFileReader: reads CSV rows in a batched manner.
91
95
 
92
-
93
96
  - :CsvJoiner: joins csvs and uses :CsvFileWriter: to create the merged CSV.
94
97
  ```
95
98
 
@@ -10,6 +10,7 @@ When adding or editing `exported_concepts` in a `.plain` file's frontmatter, alw
10
10
  - `exported_concepts` declares which concepts from this module are visible to modules that `require` it
11
11
  - Concepts not listed in `exported_concepts` are internal to the module and invisible to downstream modules
12
12
  - Only modules that use `requires` receive exported concepts — `import` gives access to all definitions, not just exports
13
+ - An exported concept is visible with its full definition text
13
14
 
14
15
  ## When to use it
15
16
  - Use `exported_concepts` on any module that other modules will `require`
@@ -21,8 +22,8 @@ When adding or editing `exported_concepts` in a `.plain` file's frontmatter, alw
21
22
  - Do not export concepts that are not defined in the module
22
23
 
23
24
  ## Exports are not transitive
24
- - If module A exports `:Foo:` and module B `requires` A, module C `requires` B does **not** gain access to `:Foo:`
25
- - If module C also needs `:Foo:`, it must either `require` A directly or get it through a common import module
25
+ - If module A exports `:Foo:` and module B `requires` A, a module C with `requires: [B]` does **not** gain access to `:Foo:`
26
+ - If module C also needs `:Foo:`, it should list A in its `requires` as well (`requires: [A, B]`) or get `:Foo:` through a common import module
26
27
 
27
28
  ## Format
28
29
 
@@ -37,8 +37,15 @@ GOOD — statements of fact with concrete subjects:
37
37
  - A :User: can add a :Task:. Only valid :Task: items are added.
38
38
  ```
39
39
 
40
+ ## Conciseness
41
+ - A functional spec that a human will not read is not reviewed, so every spec must be as short as its content allows — see `concise-specs.md`
42
+ - Cut anything obvious, any rationale, and every uncheckable adjective; keep every interface detail, boundary case, and disambiguation
43
+ - Shorten the wording, never the facts: the rules below always win over brevity
44
+
40
45
  ## Complexity limit
41
- - Each functional spec must imply a **maximum of 200 changed lines of code**
46
+ - The complexity limit is about breaking software into small, independently understandable behavior slices that people and AI can reason about, implement, and test reliably
47
+ - The current mechanism for enforcing the complexity limit is that each functional spec must imply a **maximum of 200 changed lines of code**.
48
+ - Smaller slices produce focused test failures, reduce hidden coupling and regression surface, and make review and incremental rendering more predictable
42
49
  - If a spec is too large, use `break-down-func-spec` to split it into multiple smaller, independent specs
43
50
  - Use `analyze-if-func-spec-too-complex` to verify before inserting
44
51
  - 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
@@ -57,7 +64,7 @@ GOOD — statements of fact with concrete subjects:
57
64
  - Specs are rendered incrementally, top to bottom
58
65
  - The renderer has **no knowledge of future specs** — only previously rendered specs are in context
59
66
  - A new spec can reference behavior from earlier specs but cannot assume anything about specs that come after it
60
- - Functional specs from `requires` modules are treated as previous requirements
67
+ - Functional specs from `requires` modules are treated as previous requirements — transitively, across the whole ancestor chain
61
68
 
62
69
  ## No conflicts
63
70
  - The new spec must not contradict any existing functional spec
@@ -92,7 +99,7 @@ GOOD — statements of fact with concrete subjects:
92
99
 
93
100
  ## Encapsulation
94
101
  - Functionality must be self-contained in the spec text
95
- - `requires` modules only receive functional specs — do not rely on implementation reqs to convey behavior
102
+ - `requires` modules only pass on functional specs and `exported_concepts` — never implementation reqs, so do not rely on implementation reqs to convey behavior
96
103
  - Behavior that downstream modules need must be expressed in functional specs, not elsewhere
97
104
 
98
105
  ## Acceptance tests
@@ -41,17 +41,21 @@ When writing or editing an `***implementation reqs***` section in a `.plain` fil
41
41
  - Unit testing run scripts should be linked here as a linked resource
42
42
 
43
43
  ## Encapsulation warning
44
- - `requires` modules only receive functional specs from their dependencies — not implementation reqs
44
+ - `requires` modules only pass on functional specs and `exported_concepts` — never implementation reqs
45
45
  - If downstream modules need certain behavior to be visible, express it in functional specs, not here
46
46
 
47
+ ## Conciseness
48
+ - State each req in the fewest plain words that stay exact — see `concise-specs.md`
49
+ - Do not restate the standard behavior of a technology this section already names
50
+
47
51
  ## No duplication
48
52
  - Do not duplicate guidance already present in the file or its imports
49
- - Check imported templates before adding a new req
53
+ - Check imported modules before adding a new req
50
54
 
51
55
  ## Concept references
52
56
  - Reference defined `:Concepts:` where they add clarity
53
57
  - All referenced concepts must already be defined in `***definitions***`
54
- - Implementation reqs in non-leaf sections apply to all subsections
58
+ - Make the component a req constrains its subject; `:Implementation:` is the whole codebase, so use it only for a fact that holds everywhere — language, dependency manager, stack-wide convention
55
59
 
56
60
  ## Format
57
61
 
@@ -59,6 +63,6 @@ When writing or editing an `***implementation reqs***` section in a `.plain` fil
59
63
  ***implementation reqs***
60
64
  - :Implementation: should be in Python 3.12.
61
65
  - :Implementation: should use pip for dependency management.
62
- - When writing CSV files, :Implementation: should use streaming writes to avoid holding large datasets in memory.
66
+ - :ReportExporter: writes a :CsvFile: with streaming writes, never holding the full dataset in memory.
63
67
  - :UnitTests: are executed via the run script [test_scripts/run_unittests_python.sh](test_scripts/run_unittests_python.sh).
64
68
  ```
@@ -10,18 +10,16 @@ When creating or editing a `.plain` file that uses `import` or is intended to be
10
10
  - An import module contains **only** `***definitions***`, `***implementation reqs***`, and/or `***test reqs***`
11
11
  - It must **not** contain `***functional specs***`
12
12
  - It must **not** use `requires` in its frontmatter
13
- - It must live in the **`template/`** directory
14
- - It may optionally `import` other modules or templates for layered reuse
13
+ - It may optionally `import` other import modules for layered reuse
15
14
 
16
15
  ## What import does
17
16
  - `import` pulls in `***definitions***`, `***implementation reqs***`, and `***test reqs***` from the target module
18
17
  - It does **not** pull in `***functional specs***`
19
- - The default import directory is `template/` — the `template/` prefix is not needed in import paths
20
18
  - Import paths omit the `.plain` extension
21
19
 
22
20
  ## Concept visibility
23
- - All concepts defined in the imported module become available in the importing module
24
- - Check for concept name collisions between imports and local definitions before adding
21
+ - All concepts defined in the imported module become available in the importing module — including concepts the imported module itself imports (layered imports are transitive)
22
+ - Check for concept name collisions between imports and local definitions before adding — a collision is rejected as a syntax error ("Concepts were defined multiple times")
25
23
 
26
24
  ## Format
27
25
 
@@ -46,5 +44,5 @@ import:
46
44
  ## Combining import with requires
47
45
  - A module can use both `import` and `requires` together
48
46
  - `import` brings in shared definitions and reqs
49
- - `requires` brings in the build dependency chain and functional specs
47
+ - `requires` attaches the module to the build chain — accumulated generated code and previous functional specs
50
48
  - An import module itself must **not** use `requires`
@@ -82,7 +82,6 @@ This rule applies to **host source code only**. Other artifacts still live under
82
82
 
83
83
  - **Contract schemas authored by the integration** — `resources/contract/<entry-point>.schema.json`
84
84
  - **Configuration schema** — `resources/config.schema.json`
85
- - **Captured probe responses** (from the live-API cross-check) — `resources/fixtures/<endpoint>.<case>.json`
86
85
  - **Static lookup tables** the integration owns — `resources/error-map.yaml`, `resources/retry-policy.yaml`, etc.
87
86
 
88
87
  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/`.
@@ -103,7 +103,7 @@ Capture as a contract-version concept; pin the version in every published schema
103
103
 
104
104
  `:ConformanceTests:` for a standalone integration **run against the live provider** (see [`integrations.md`](integrations.md) → *`:ConformanceTests:` always run against the live integration*). The testing strategy is captured as `***test reqs***` entries authored via `add-test-requirement`:
105
105
 
106
- - **Conformance is live by default.** No VCR cassettes, no prerecorded responses for the calls under test, no mock servers. A green conformance run that never touched the provider proves nothing. Recorded responses under `resources/fixtures/` exist for unit tests and for grounding the OpenAPI schemas — not for conformance
106
+ - **Conformance is live by default.** No VCR cassettes, no prerecorded responses for the calls under test, no mock servers. A green conformance run that never touched the provider proves nothing. Recorded responses may back unit tests — never conformance
107
107
  - **Secrets come from environment variables.** Pin every credential as an env-var name (e.g. `<PROVIDER>_API_KEY`, `<PROVIDER>_CLIENT_ID` + `<PROVIDER>_CLIENT_SECRET`) in `***test reqs***` and in the auth concept. Use the names the provider's docs use so users don't have to translate
108
108
  - **The user supplies values via `.env` or the shell.** The project ships `.env.example` (gitignored `.env` for real values). CI provides the same env-var names from its secret store. The conformance script may optionally `source` a `.env` from the project root if one exists; it must verify every required var is set after that optional load and fail fast (exit `69`) on missing vars
109
109
  - **Sandbox credentials in CI.** Name where credentials come from (CI secret store, dedicated test tenant), the rotation / leak-response policy, and the env-var names CI must set
@@ -28,10 +28,11 @@ Pick the right format per artifact:
28
28
  | Error code → category mapping | YAML enum | `resources/error-map.yaml` |
29
29
  | Rate-limit header inventory | YAML enum | `resources/rate-limit-headers.yaml` |
30
30
  | Retry policy parameters | YAML enum | `resources/retry-policy.yaml` |
31
- | Captured probe responses | Raw JSON | `resources/fixtures/<endpoint>.<case>.json` |
31
+ | Provider behavior with no machine-readable home | Markdown | `resources/<provider>-notes.md` |
32
32
 
33
33
  Rules that flow from this:
34
34
 
35
+ - **Provider behavior is recorded, not specified.** How the provider itself works — its quota accounting, consistency guarantees, documented quirks, and any place the live API diverges from its docs — is true and worth capturing, but it is not a requirement on the software being built. Whatever has a machine-readable home in the table above goes there; the rest goes in `resources/<provider>-notes.md`. A functional spec never states provider behavior; it states what the integration does in the face of it.
35
36
  - **Concepts carry references, not data.** An endpoint concept names the endpoint and points at its OpenAPI `paths.<path>.<method>` entry; it does not duplicate the request/response shape in concept attributes. Same for webhook concepts, error-model concepts, pagination concepts, etc.
36
37
  - **Functional specs consume linked resources.** A spec describes **behavior** ("call endpoint X, parse the response, classify errors, retry on 5xx") and links to the resource that supplies the **shape**. Field names, types, and validation rules live in the resource file.
37
38
  - **Schemas are versioned by file path.** When the provider releases a new API version, copy the new OpenAPI file to a new path (e.g. `resources/<provider>.v2.openapi.yaml`); never mutate the v1 file in place.
@@ -47,13 +48,11 @@ Documentation lies — it goes stale, omits undocumented fields, describes a dif
47
48
  - If the environment has **no web-search tool** (only URL-based `fetch` is available), say so explicitly, construct the URL from the provider's well-known documentation root and crawl outward by `fetch`-ing that root and following its links, and ask the user for the canonical URL whenever you cannot reach the right page that way. Never substitute memory for the search-then-fetch step.
48
49
  - **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:
49
50
  - Before authoring **any** endpoint, auth, error, pagination, or webhook concept, **web-search to locate the relevant documentation page(s), then `fetch` each one** 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.
50
- - 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.
51
51
  - 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.
52
52
  - **The fetched documentation is then cross-checked against the live API** — see the rest of this section.
53
53
  - **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.
54
54
  - **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.
55
- - **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.
56
- - **Every discrepancy is recorded, not smoothed over.** Each finding goes into the relevant resource (the OpenAPI file, the error envelope schema, `rate-limit-headers.yaml`, …) as the source of truth, with a short note in the corresponding concept saying "docs claim X, live API returns Y; we follow the live API".
55
+ - **Where the docs and the live API disagree, the live API wins** — the resource records what the API actually returns, and nothing records the disagreement itself.
57
56
  - **Only `GET` / `HEAD` / `OPTIONS` on the cross-check.** Mutating calls (`POST`, `PATCH`, `PUT`, `DELETE`) require explicit per-call user confirmation and must target a sandbox account.
58
57
  - **Credentials are never written to `.plain` files or summaries.** Reference them by env-var name only.
59
58
 
@@ -61,7 +60,7 @@ Documentation lies — it goes stale, omits undocumented fields, describes a dif
61
60
 
62
61
  Integrations exist to talk to a real third-party (or internal) API. Their `:ConformanceTests:` therefore **always run against the live integration** — no VCR cassettes, no recorded fixtures, no `nock` / `WireMock` / `MSW` mocks for the calls under test. A "green" conformance run that never touched the provider proves nothing about the integration.
63
62
 
64
- - The integration's `:ConformanceTests:` **must** make real network calls to the provider (typically a sandbox / staging environment, occasionally production for read-only paths). Fixtures under `resources/fixtures/` exist for unit tests and for grounding the schemas in the OpenAPI file — they are **not** a substitute for live conformance
63
+ - The integration's `:ConformanceTests:` **must** make real network calls to the provider (typically a sandbox / staging environment, occasionally production for read-only paths). Recorded responses may back unit tests, but they are **not** a substitute for live conformance
65
64
  - The only exceptions are paths that **cannot** be exercised live safely:
66
65
  - **Rate-limit (429) tests** — must not exhaust the live quota; use a local mock for that specific endpoint
67
66
  - **Deliberately destructive failure modes** (forced 5xx) the provider doesn't let you trigger — same; mock the specific endpoint
@@ -128,11 +127,11 @@ A production-ready integration spec captures every corner case the API can throw
128
127
  ## Anti-patterns (do not do these)
129
128
 
130
129
  - **Restating an OpenAPI / JSON Schema field list in a concept or functional spec.** The schema lives in `resources/`; the spec links to it
131
- - **Pasting a webhook payload, error envelope, or list-endpoint response inline.** Save it as a fixture under `resources/fixtures/` and link the fixture
130
+ - **Pasting a webhook payload, error envelope, or list-endpoint response inline.** Describe its shape in the applicable schema under `resources/` and link that schema
132
131
  - **Inlining a host base class body into the contract spec.** Add the host file as a linked resource under `resources/host/` and reference it by FQN
133
132
  - **Embedding credentials, tokens, or signing keys in a `.plain` file or in a summary** — credentials are referenced by env-var name only
134
133
  - **Authoring against unverified credentials.** Validate first; if the user has no credentials yet, flag it in the module's frontmatter description and re-validate once credentials arrive
135
134
  - **`requires`-ing a separate-stack module** (a Python backend `requires`-ing a React frontend, or vice versa) — see [`requires-modules.md`](requires-modules.md). Use a shared API schema in `resources/` instead
136
135
  - **Authoring Phase 1 specs from the docs first and "reconciling" with the live API later.** Probe the API as you reach each topic; the live response is the source of truth from the moment it's captured
137
- - **Writing any integration spec from memory of the provider's API instead of web-searching for its documentation and `fetch`-ing every relevant page first.** No matter how well-known the API (Stripe, GitHub, Slack, Salesforce, AWS, OpenAI, …), the canonical pages must be located with web search and then retrieved with `fetch` at spec-authoring time and saved under `resources/docs/<provider>/` — see *Live API must be cross-checked against the documentation*. Authoring from memory bakes in whatever version of the API was current during training, which is always older than the version the integration will actually call
136
+ - **Writing any integration spec from memory of the provider's API instead of web-searching for its documentation and `fetch`-ing every relevant page first.** No matter how well-known the API (Stripe, GitHub, Slack, Salesforce, AWS, OpenAI, …), the canonical pages must be located with web search and then retrieved with `fetch` at spec-authoring time — see *Live API must be cross-checked against the documentation*. Authoring from memory bakes in whatever version of the API was current during training, which is always older than the version the integration will actually call
138
137
  - **Guessing a documentation URL from memory and `fetch`-ing it without searching first.** A remembered URL may 404, redirect to a stale version, or miss the page that actually documents the topic. Search to find the authoritative, current links, then fetch the full set
@@ -13,14 +13,15 @@ A linked resource **must not** be any of the following:
13
13
  1. **A folder / directory.** `[integrations](src/integrations/)`, `[host project](../host_project/)` are invalid — the renderer cannot ingest a directory. Pick the single most representative file inside (a `README.md`, an exemplar source file, a manifest) and link **that**.
14
14
  2. **A URL / external location.** `[Stripe docs](https://stripe.com/docs/api)`, any `http://` / `https://` / `ftp://` / `git://` / `s3://` / `gs://` target. Linked resources are local-file only. If a URL's content is essential, fetch it once, save the response to a text file under `resources/`, and link the saved file.
15
15
  3. **A binary file.** PNG, JPG, GIF, PDF, DOCX, XLSX, ZIP, MP3, MP4, compiled binaries (`.exe`, `.so`, `.class`, `.wasm`), and anything else that isn't human-readable text in its raw form. Binary content cannot be meaningfully consumed by the renderer — transcribe it into a text-based form first (a UI screenshot becomes a Markdown description, a PDF spec becomes a Markdown extract or the underlying JSON Schema / OpenAPI, an architecture diagram becomes a Mermaid block).
16
- 4. **Another `.plain` file.** `[Auth Module](auth.plain)` is invalid. To reference another module, use the `require` or `import` frontmatter directives. Do not link it as a markdown resource; the `plain-dry-run` will flag this as a syntax error.
17
-
18
-
19
- ## URLs and folder paths must not appear *anywhere* in `.plain` content
20
- - The constraint is not only about markdown links — URLs and folder paths must not appear **anywhere** in `.plain` content (concept body prose, functional-spec text, implementation reqs, test reqs)
21
- - The renderer cannot follow URLs or open folders; a URL in prose is a *ghost dependency* — it looks meaningful to a human reader but contributes nothing to code generation, and the spec silently drifts from reality
22
- - **The only exception** is for URLs and paths that are *values the produced software itself uses at runtime* — the base URL an integration calls, a database connection path, a CLI argument default. Those are configuration values, not external references
23
- - Litmus test: "Would the renderer benefit from reading the bytes at this URL / folder?" If yes, save it to a file and link the file. If no (it's a runtime value the code carries forward), it can stay as plain text
16
+ 4. **Another `.plain` file.** `[Auth Module](auth.plain)` is invalid. To reference another module, use the `require` or `import` frontmatter directives. Do not link it as a markdown resource; the renderer will flag this as a syntax error.
17
+
18
+ ## Never use a URL or folder path as a *reference* in prose
19
+ - The hard constraint above is enforced only on markdown links. A bare URL or folder path in prose (concept body, functional-spec text, implementation reqs, test reqs) passes validation and is fed to the renderer verbatim as part of the spec text
20
+ - That is exactly why it is dangerous as a *reference*: the renderer cannot fetch the URL or open the folder, so the model sees only the address, not the content behind it. To a human reader the line looks grounded in a source; to the renderer it is a *ghost dependency*, and the spec silently drifts from whatever the URL actually says
21
+ - A URL or path in prose is fine when the *text itself* is the complete fact:
22
+ - **Runtime values** the produced software uses — the base URL an integration calls, a database connection path, a CLI argument default, an output directory layout
23
+ - **Provenance notes** for a snapshotted resource — recording the canonical URL a file saved under `resources/` was fetched from
24
+ - Litmus test: "Would the renderer benefit from reading the bytes at this URL / folder?" If yes, save it to a file and link the file. If no (the address itself is the fact), it can stay as plain text
24
25
 
25
26
  ## Structured protocol artifacts must be linked, never transcribed
26
27
  - JSON Schema, OpenAPI / Swagger, GraphQL SDL, Protobuf `.proto`, Avro / Thrift schemas, XML XSDs, AsyncAPI specs, JSON-RPC method definitions, wire-protocol descriptions, payload examples — anything with a formal machine-readable shape — belongs in a file under `resources/` (or another directory within the project)