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.
- package/README.md +1 -0
- package/bin/cli.mjs +1 -1
- package/forge/rules/bullet-continuation.md +16 -6
- package/forge/rules/concise-specs-examples.md +431 -0
- package/forge/rules/concise-specs-integration-examples.md +272 -0
- package/forge/rules/concise-specs.md +80 -0
- package/forge/rules/definitions.md +7 -4
- package/forge/rules/exported-concepts.md +3 -2
- package/forge/rules/func-specs.md +10 -3
- package/forge/rules/impl-reqs.md +8 -4
- package/forge/rules/import-modules.md +4 -6
- package/forge/rules/integration-embedded.md +0 -1
- package/forge/rules/integration-standalone.md +1 -1
- package/forge/rules/integrations.md +6 -7
- package/forge/rules/linked-resources.md +9 -8
- package/forge/rules/module-structure.md +105 -0
- package/forge/rules/required-concepts.md +5 -5
- package/forge/rules/requires-modules.md +37 -21
- package/forge/rules/test-reqs.md +11 -7
- package/forge/skills/add-acceptance-test/SKILL.md +1 -0
- package/forge/skills/add-concept/SKILL.md +2 -1
- package/forge/skills/add-feature/SKILL.md +2 -2
- package/forge/skills/add-functional-spec/SKILL.md +2 -1
- package/forge/skills/add-functional-specs/SKILL.md +2 -1
- package/forge/skills/add-implementation-requirement/SKILL.md +2 -1
- package/forge/skills/add-test-requirement/SKILL.md +1 -0
- package/forge/skills/break-down-func-spec/SKILL.md +1 -0
- package/forge/skills/check-forge-version/SKILL.md +154 -0
- package/forge/skills/check-forge-version/scripts/check-forge-version.mjs +368 -0
- package/forge/skills/create-import-module/SKILL.md +3 -3
- package/forge/skills/create-requires-module/SKILL.md +24 -19
- package/forge/skills/forge-plain/SKILL.md +1 -1
- package/forge/skills/forge-plain/references/checklist.md +2 -1
- package/forge/skills/forge-plain/references/phase-1-product.md +3 -2
- package/forge/skills/forge-plain/references/phase-2-tech.md +1 -1
- package/forge/skills/load-plain-reference/SKILL.md +20 -2
- package/forge/skills/load-plain-reference/references/project-model.md +0 -11
- package/forge/skills/plain-healthcheck/SKILL.md +1 -1
- package/forge/skills/refactor-module/SKILL.md +3 -3
- package/forge/skills/resolve-section-ownership/SKILL.md +65 -0
- package/forge/skills/resolve-spec-conflict/SKILL.md +1 -0
- 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
|
|
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
|
|
64
|
-
-
|
|
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
|
|
25
|
-
- If module C also needs `:Foo:`, it
|
|
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
|
-
-
|
|
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
|
|
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
|
package/forge/rules/impl-reqs.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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`
|
|
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
|
|
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
|
-
|
|
|
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
|
-
- **
|
|
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).
|
|
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.**
|
|
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
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
- **
|
|
23
|
-
-
|
|
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)
|