@turri-dev/cli 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/.pi/themes/turri-dark.json +80 -0
  2. package/.pi/themes/turri-light.json +82 -0
  3. package/LICENSE +21 -0
  4. package/README.md +272 -2
  5. package/assets/jellyfish/dark-00.png +0 -0
  6. package/assets/jellyfish/dark-01.png +0 -0
  7. package/assets/jellyfish/dark-02.png +0 -0
  8. package/assets/jellyfish/dark-03.png +0 -0
  9. package/assets/jellyfish/dark-04.png +0 -0
  10. package/assets/jellyfish/dark-05.png +0 -0
  11. package/assets/jellyfish/dark-06.png +0 -0
  12. package/assets/jellyfish/dark-07.png +0 -0
  13. package/assets/jellyfish/dark-08.png +0 -0
  14. package/assets/jellyfish/dark-09.png +0 -0
  15. package/assets/jellyfish/dark-10.png +0 -0
  16. package/assets/jellyfish/dark-11.png +0 -0
  17. package/assets/jellyfish/dark-12.png +0 -0
  18. package/assets/jellyfish/dark-13.png +0 -0
  19. package/assets/jellyfish/dark-14.png +0 -0
  20. package/assets/jellyfish/fallback.txt +252 -0
  21. package/assets/jellyfish/light-00.png +0 -0
  22. package/assets/jellyfish/light-01.png +0 -0
  23. package/assets/jellyfish/light-02.png +0 -0
  24. package/assets/jellyfish/light-03.png +0 -0
  25. package/assets/jellyfish/light-04.png +0 -0
  26. package/assets/jellyfish/light-05.png +0 -0
  27. package/assets/jellyfish/light-06.png +0 -0
  28. package/assets/jellyfish/light-07.png +0 -0
  29. package/assets/jellyfish/light-08.png +0 -0
  30. package/assets/jellyfish/light-09.png +0 -0
  31. package/assets/jellyfish/light-10.png +0 -0
  32. package/assets/jellyfish/light-11.png +0 -0
  33. package/assets/jellyfish/light-12.png +0 -0
  34. package/assets/jellyfish/light-13.png +0 -0
  35. package/assets/jellyfish/light-14.png +0 -0
  36. package/bin/turri.mjs +29 -0
  37. package/dist/ask.js +149 -0
  38. package/dist/brand.js +50 -0
  39. package/dist/btw.js +174 -0
  40. package/dist/chat-models.js +37 -0
  41. package/dist/chat-skills.js +18 -0
  42. package/dist/cli.js +178 -0
  43. package/dist/config-command.js +197 -0
  44. package/dist/config-edit.js +367 -0
  45. package/dist/config.js +489 -0
  46. package/dist/conform-cli.js +29 -0
  47. package/dist/cultivate-cli.js +110 -0
  48. package/dist/drift-cli.js +25 -0
  49. package/dist/environment.js +70 -0
  50. package/dist/extension.js +2372 -0
  51. package/dist/genome.js +301 -0
  52. package/dist/graph.js +246 -0
  53. package/dist/identity.js +35 -0
  54. package/dist/init-config.js +511 -0
  55. package/dist/judge.js +62 -0
  56. package/dist/lineage-cli.js +13 -0
  57. package/dist/loop-cli.js +132 -0
  58. package/dist/loop-tools.js +225 -0
  59. package/dist/mature-tools.js +193 -0
  60. package/dist/model-check.js +117 -0
  61. package/dist/module-report.js +9 -0
  62. package/dist/pi-launch.js +31 -0
  63. package/dist/pi.js +813 -0
  64. package/dist/plain/check.js +76 -0
  65. package/dist/plain/concepts.js +213 -0
  66. package/dist/plain/errors.js +43 -0
  67. package/dist/plain/files.js +57 -0
  68. package/dist/plain/highlight.js +195 -0
  69. package/dist/plain/index.js +15 -0
  70. package/dist/plain/liquid.js +129 -0
  71. package/dist/plain/markdown.js +208 -0
  72. package/dist/plain/parser.js +752 -0
  73. package/dist/plain/plain.tmLanguage.json +124 -0
  74. package/dist/plain/spec.js +291 -0
  75. package/dist/plain/stdlib.js +44 -0
  76. package/dist/question-dialog.js +125 -0
  77. package/dist/questioning.js +109 -0
  78. package/dist/quiet.js +125 -0
  79. package/dist/regenerate/activity.js +187 -0
  80. package/dist/regenerate/agent-env.js +190 -0
  81. package/dist/regenerate/agent-shell.js +114 -0
  82. package/dist/regenerate/arbiter.js +133 -0
  83. package/dist/regenerate/audit.js +203 -0
  84. package/dist/regenerate/cases.js +83 -0
  85. package/dist/regenerate/change-view.js +102 -0
  86. package/dist/regenerate/choose-conformance.js +117 -0
  87. package/dist/regenerate/chunk-plan.js +625 -0
  88. package/dist/regenerate/codebase.js +75 -0
  89. package/dist/regenerate/command-check.js +312 -0
  90. package/dist/regenerate/compaction.js +20 -0
  91. package/dist/regenerate/confine.js +310 -0
  92. package/dist/regenerate/conform.js +133 -0
  93. package/dist/regenerate/conformance-agents.js +955 -0
  94. package/dist/regenerate/conformance.js +936 -0
  95. package/dist/regenerate/conversation-writes.js +100 -0
  96. package/dist/regenerate/credentials.js +88 -0
  97. package/dist/regenerate/debug-log.js +228 -0
  98. package/dist/regenerate/debugger.js +90 -0
  99. package/dist/regenerate/delivery.js +19 -0
  100. package/dist/regenerate/diagnose-job.js +40 -0
  101. package/dist/regenerate/diagnose.js +81 -0
  102. package/dist/regenerate/diff-view.js +240 -0
  103. package/dist/regenerate/drift.js +194 -0
  104. package/dist/regenerate/engine.js +118 -0
  105. package/dist/regenerate/environment-error.js +220 -0
  106. package/dist/regenerate/job-log.js +102 -0
  107. package/dist/regenerate/job-panel.js +336 -0
  108. package/dist/regenerate/jobs-tree.js +300 -0
  109. package/dist/regenerate/jobs.js +427 -0
  110. package/dist/regenerate/lanes.js +32 -0
  111. package/dist/regenerate/lineage.js +384 -0
  112. package/dist/regenerate/loop.js +1435 -0
  113. package/dist/regenerate/masking.js +95 -0
  114. package/dist/regenerate/notes.js +397 -0
  115. package/dist/regenerate/os-sandbox.js +243 -0
  116. package/dist/regenerate/paths.js +165 -0
  117. package/dist/regenerate/plain-palette.js +83 -0
  118. package/dist/regenerate/prompt.js +704 -0
  119. package/dist/regenerate/records.js +294 -0
  120. package/dist/regenerate/regenerate.js +598 -0
  121. package/dist/regenerate/regression.js +181 -0
  122. package/dist/regenerate/release.js +262 -0
  123. package/dist/regenerate/requires.js +241 -0
  124. package/dist/regenerate/resources.js +164 -0
  125. package/dist/regenerate/self-check.js +111 -0
  126. package/dist/regenerate/services.js +155 -0
  127. package/dist/regenerate/shell.js +319 -0
  128. package/dist/regenerate/side-panel.js +278 -0
  129. package/dist/regenerate/spec-map-view.js +574 -0
  130. package/dist/regenerate/spec-map.js +561 -0
  131. package/dist/regenerate/spec-steps.js +527 -0
  132. package/dist/regenerate/state.js +10 -0
  133. package/dist/regenerate/steer.js +91 -0
  134. package/dist/regenerate/steering.js +119 -0
  135. package/dist/regenerate/stop-report.js +161 -0
  136. package/dist/regenerate/submit-work.js +71 -0
  137. package/dist/regenerate/test-kinds.js +124 -0
  138. package/dist/regenerate/test-scripts.js +695 -0
  139. package/dist/regenerate/testing.js +406 -0
  140. package/dist/regenerate/transcript-view.js +1 -0
  141. package/dist/regenerate/unit-gate.js +20 -0
  142. package/dist/regenerate/unit-review.js +347 -0
  143. package/dist/regenerate/web.js +256 -0
  144. package/dist/regenerate/workspace.js +99 -0
  145. package/dist/regenerate-cli.js +68 -0
  146. package/dist/release-agent.js +114 -0
  147. package/dist/release-cli.js +131 -0
  148. package/dist/release-tools.js +148 -0
  149. package/dist/review/live.js +165 -0
  150. package/dist/rules.js +47 -0
  151. package/dist/self-invocation.js +110 -0
  152. package/dist/setup.js +245 -0
  153. package/dist/skill-progress.js +78 -0
  154. package/dist/skill.js +149 -0
  155. package/dist/skills.js +74 -0
  156. package/dist/spec-diff-cli.js +37 -0
  157. package/dist/spec-tools.js +155 -0
  158. package/dist/stack.js +158 -0
  159. package/dist/stacks.js +97 -0
  160. package/dist/stdlib.js +100 -0
  161. package/dist/terminal-images.js +46 -0
  162. package/dist/tools.js +242 -0
  163. package/dist/validate-cli.js +52 -0
  164. package/docs/README.md +58 -0
  165. package/docs/authoring-plain.md +245 -0
  166. package/docs/config.md +353 -0
  167. package/docs/deploy.md +109 -0
  168. package/docs/development.md +75 -0
  169. package/docs/headless.md +148 -0
  170. package/docs/images/how-turri-works.svg +50 -0
  171. package/docs/images/turri-banner.png +0 -0
  172. package/docs/installation.md +118 -0
  173. package/docs/parser.md +108 -0
  174. package/docs/regenerating.md +1116 -0
  175. package/docs/sandbox.md +233 -0
  176. package/docs/skills-and-gates.md +254 -0
  177. package/docs/theming.md +76 -0
  178. package/docs/using.md +454 -0
  179. package/genome/rules/bullet-continuation.md +69 -0
  180. package/genome/rules/concise-specs-examples.md +431 -0
  181. package/genome/rules/concise-specs-integration-examples.md +270 -0
  182. package/genome/rules/concise-specs.md +80 -0
  183. package/genome/rules/definitions.md +107 -0
  184. package/genome/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +163 -0
  185. package/genome/rules/examples/integration-embedded-testing/run_unittests_java.sh +51 -0
  186. package/genome/rules/exported-concepts.md +39 -0
  187. package/genome/rules/func-specs.md +129 -0
  188. package/genome/rules/impl-reqs.md +68 -0
  189. package/genome/rules/import-modules.md +48 -0
  190. package/genome/rules/integration-embedded-testing.md +276 -0
  191. package/genome/rules/integration-embedded.md +171 -0
  192. package/genome/rules/integration-standalone.md +135 -0
  193. package/genome/rules/integrations.md +137 -0
  194. package/genome/rules/linked-resources.md +69 -0
  195. package/genome/rules/module-structure.md +105 -0
  196. package/genome/rules/project-model.md +88 -0
  197. package/genome/rules/required-concepts.md +44 -0
  198. package/genome/rules/requires-modules.md +80 -0
  199. package/genome/rules/stdlib.md +73 -0
  200. package/genome/rules/test-reqs.md +57 -0
  201. package/genome/skills/add-acceptance-test/SKILL.md +100 -0
  202. package/genome/skills/add-concept/SKILL.md +71 -0
  203. package/genome/skills/add-functional-spec/SKILL.md +84 -0
  204. package/genome/skills/add-functional-specs/SKILL.md +118 -0
  205. package/genome/skills/add-implementation-requirement/SKILL.md +86 -0
  206. package/genome/skills/add-resource/SKILL.md +105 -0
  207. package/genome/skills/add-template/SKILL.md +64 -0
  208. package/genome/skills/add-test-requirement/SKILL.md +73 -0
  209. package/genome/skills/analyze-2-func-specs/SKILL.md +101 -0
  210. package/genome/skills/analyze-func-specs/SKILL.md +123 -0
  211. package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +211 -0
  212. package/genome/skills/break-down-func-spec/SKILL.md +205 -0
  213. package/genome/skills/brood/SKILL.md +229 -0
  214. package/genome/skills/brood/references/intent.md +35 -0
  215. package/genome/skills/brood/references/product.md +101 -0
  216. package/genome/skills/brood/references/stack.md +61 -0
  217. package/genome/skills/brood/references/testing.md +56 -0
  218. package/genome/skills/bud/SKILL.md +148 -0
  219. package/genome/skills/bud/references/checklist.md +44 -0
  220. package/genome/skills/bud/references/intent.md +30 -0
  221. package/genome/skills/check-plain-env/SKILL.md +285 -0
  222. package/genome/skills/consolidate-concepts/SKILL.md +192 -0
  223. package/genome/skills/create-import-module/SKILL.md +104 -0
  224. package/genome/skills/create-requires-module/SKILL.md +109 -0
  225. package/genome/skills/debug-specs/SKILL.md +188 -0
  226. package/genome/skills/implement-conformance-testing-script/SKILL.md +238 -0
  227. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_cypress.ps1 +337 -0
  228. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_golang.ps1 +111 -0
  229. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +136 -0
  230. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.ps1 +103 -0
  231. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +150 -0
  232. package/genome/skills/implement-unit-testing-script/SKILL.md +160 -0
  233. package/genome/skills/implement-unit-testing-script/assets/run_unittests_flutter.ps1 +87 -0
  234. package/genome/skills/implement-unit-testing-script/assets/run_unittests_golang.ps1 +73 -0
  235. package/genome/skills/implement-unit-testing-script/assets/run_unittests_java.sh +72 -0
  236. package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.ps1 +81 -0
  237. package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.sh +98 -0
  238. package/genome/skills/implement-unit-testing-script/assets/run_unittests_react.ps1 +87 -0
  239. package/genome/skills/init-plain-project/SKILL.md +117 -0
  240. package/genome/skills/init-plain-project/references/checklist.md +30 -0
  241. package/genome/skills/mature/SKILL.md +126 -0
  242. package/genome/skills/refactor-module/SKILL.md +196 -0
  243. package/genome/skills/resolve-section-ownership/SKILL.md +64 -0
  244. package/genome/skills/resolve-spec-conflict/SKILL.md +96 -0
  245. package/genome/skills/spawn/SKILL.md +163 -0
  246. package/package.json +68 -4
  247. package/stdlib/backend/common.plain +25 -0
  248. package/stdlib/backend/conformance/common.plain +12 -0
  249. package/stdlib/backend/conformance/go-gin.plain +9 -0
  250. package/stdlib/backend/conformance/go-nethttp.plain +9 -0
  251. package/stdlib/backend/conformance/java-springboot.plain +9 -0
  252. package/stdlib/backend/conformance/python-django.plain +9 -0
  253. package/stdlib/backend/conformance/python-fastapi.plain +9 -0
  254. package/stdlib/backend/conformance/python-flask.plain +9 -0
  255. package/stdlib/backend/conformance/rust-axum.plain +9 -0
  256. package/stdlib/backend/conformance/typescript-express.plain +9 -0
  257. package/stdlib/backend/conformance/typescript-fastify.plain +9 -0
  258. package/stdlib/backend/conformance/typescript-nestjs.plain +9 -0
  259. package/stdlib/backend/go-gin.plain +18 -0
  260. package/stdlib/backend/go-nethttp.plain +18 -0
  261. package/stdlib/backend/java-springboot.plain +19 -0
  262. package/stdlib/backend/python-django.plain +19 -0
  263. package/stdlib/backend/python-fastapi.plain +23 -0
  264. package/stdlib/backend/python-flask.plain +23 -0
  265. package/stdlib/backend/rust-axum.plain +17 -0
  266. package/stdlib/backend/typescript-express.plain +19 -0
  267. package/stdlib/backend/typescript-fastify.plain +19 -0
  268. package/stdlib/backend/typescript-nestjs.plain +19 -0
  269. package/stdlib/cli/common.plain +17 -0
  270. package/stdlib/cli/conformance/common.plain +11 -0
  271. package/stdlib/cli/conformance/go-cobra.plain +9 -0
  272. package/stdlib/cli/conformance/go-flag.plain +9 -0
  273. package/stdlib/cli/conformance/java-picocli.plain +9 -0
  274. package/stdlib/cli/conformance/python-argparse.plain +9 -0
  275. package/stdlib/cli/conformance/python-click.plain +9 -0
  276. package/stdlib/cli/conformance/python-typer.plain +9 -0
  277. package/stdlib/cli/conformance/rust-clap.plain +9 -0
  278. package/stdlib/cli/conformance/typescript-commander.plain +9 -0
  279. package/stdlib/cli/go-cobra.plain +14 -0
  280. package/stdlib/cli/go-flag.plain +14 -0
  281. package/stdlib/cli/java-picocli.plain +15 -0
  282. package/stdlib/cli/python-argparse.plain +18 -0
  283. package/stdlib/cli/python-click.plain +16 -0
  284. package/stdlib/cli/python-typer.plain +16 -0
  285. package/stdlib/cli/rust-clap.plain +14 -0
  286. package/stdlib/cli/typescript-commander.plain +17 -0
  287. package/stdlib/frontend/angular.plain +17 -0
  288. package/stdlib/frontend/common.plain +17 -0
  289. package/stdlib/frontend/conformance/angular.plain +5 -0
  290. package/stdlib/frontend/conformance/common.plain +11 -0
  291. package/stdlib/frontend/conformance/react-vite.plain +5 -0
  292. package/stdlib/frontend/conformance/sveltekit-spa.plain +5 -0
  293. package/stdlib/frontend/conformance/vue-vite.plain +5 -0
  294. package/stdlib/frontend/react-vite.plain +16 -0
  295. package/stdlib/frontend/sveltekit-spa.plain +16 -0
  296. package/stdlib/frontend/vue-vite.plain +16 -0
  297. package/stdlib/lang/go.plain +13 -0
  298. package/stdlib/lang/java.plain +12 -0
  299. package/stdlib/lang/python.plain +18 -0
  300. package/stdlib/lang/rust.plain +12 -0
  301. package/stdlib/lang/typescript.plain +13 -0
  302. package/stdlib/library/common.plain +15 -0
  303. package/stdlib/library/conformance/common.plain +9 -0
  304. package/stdlib/library/conformance/go.plain +9 -0
  305. package/stdlib/library/conformance/java-gradle.plain +9 -0
  306. package/stdlib/library/conformance/java-maven.plain +9 -0
  307. package/stdlib/library/conformance/python.plain +9 -0
  308. package/stdlib/library/conformance/rust.plain +9 -0
  309. package/stdlib/library/conformance/typescript.plain +9 -0
  310. package/stdlib/library/go.plain +12 -0
  311. package/stdlib/library/java-gradle.plain +13 -0
  312. package/stdlib/library/java-maven.plain +13 -0
  313. package/stdlib/library/python.plain +13 -0
  314. package/stdlib/library/rust.plain +12 -0
  315. package/stdlib/library/typescript.plain +15 -0
@@ -0,0 +1,135 @@
1
+ ---
2
+ description: Rules for authoring ***plain specs for REST API integrations deployed standalone
3
+ ---
4
+
5
+ # Rules for **standalone** integration specs
6
+
7
+ When an integration `.plain` module is **standalone** — meaning the generated code in `plain_modules/` is the deployable artifact itself (a service, daemon, CLI, scheduled job, container, or library published to a registry) — these rules apply on top of the shared rules in [`integrations.md`](integrations.md). If anything below contradicts a guess made from memory, the rules here win.
8
+
9
+ Standalone means: the integration is a complete application in its own right, picks its own tech stack, owns its own lifecycle, and exposes its own contract surface to external consumers.
10
+
11
+ ## The integration owns its tech stack
12
+
13
+ Unlike embedded integrations, a standalone integration has no host to inherit from. Every stack decision is **explicit**, captured during `brood`'s stack phase, and recorded in `***implementation reqs***`:
14
+
15
+ - **Implementation language** and its exact version
16
+ - **HTTP client library** (e.g. `httpx`, `requests`, `node-fetch`, `axios`, `go-resty`, `reqwest`) — pinned, with the auth/retry/timeout features the integration uses called out
17
+ - **Application framework** (Flask, FastAPI, Express, Fiber, Gin, Spring Boot, …) when the integration exposes HTTP; **CLI framework** (Click, argparse, `cobra`, `clap`) when it exposes a CLI
18
+ - **Data storage** (when needed): idempotency-key persistence, webhook deduplication store, OAuth token cache, response cache — pick the storage primitive (SQLite, Postgres, Redis, on-disk file, in-memory with LRU bounds) and pin it
19
+ - **Architecture and layering** — which patterns the codebase follows (clean architecture, hexagonal, simple flat package, …)
20
+ - **Packaging artifact** — published Python wheel / sdist, npm package, Go binary, container image, OS package, …; one or more, named explicitly
21
+
22
+ Standalone integrations may freely import any third-party library that isn't constrained by an existing host. Choose the smallest set that solves the problem.
23
+
24
+ ## The contract schema is the public artifact
25
+
26
+ In an embedded integration, the schema feeds the renderer's code generation; the host codebase consumes the *generated class*. In a standalone integration, the schema is **shipped** to external consumers and the generated implementation is internal to `plain_modules/`.
27
+
28
+ - **Pick the publication surface and write it into the spec.** Examples (one functional spec per surface):
29
+ - HTTP service: "served at `/openapi.json` (and a Swagger UI at `/docs`)"
30
+ - npm-published library: "published as `<package>` to npm; `package.json` exports the schema as `./schema.json`"
31
+ - PyPI-published library: "published as `<package>` to PyPI; bundle ships `schema.json` as a package data file"
32
+ - CLI: "emitted as `--help` and as `<cli> schema` (JSON-on-stdout)"
33
+ - Queue worker: "published as a JSON Schema in the team's schema registry under `<topic>.v<version>.json`"
34
+ - Scheduled job: "published as JSON Schema in `resources/contract/job-config.schema.json` and linked from the runbook"
35
+ - **Schema versioning is part of the contract.** Pin the version in the schema's `$id` / OpenAPI `info.version` and follow semver. Backwards-incompatible changes ship under a new file path, never as mutations to the published version
36
+ - **The renderer ships the schema verbatim** — the spec must not restate fields from the schema, and the generated implementation must consume the same file as the published artifact (so the two cannot drift)
37
+
38
+ ## All lifecycle stages must be explicit
39
+
40
+ A standalone integration has no host lifecycle to delegate to. Every stage that an embedded integration could skip is mandatory here, each one captured as its own functional spec:
41
+
42
+ - **Initialization** — one-time setup at startup. Read configuration, validate credentials proactively (eager) or defer to first call (lazy) — pick one and write the spec accordingly. Open connection pools, register webhook handlers, initialize observability
43
+ - **Credential refresh** — proactive (background timer / pre-expiry refresh) vs. reactive (refresh-on-401). Use a refresh lock to prevent thundering herd. Specify the lock primitive (in-process mutex, distributed lock in Redis, …) when the integration runs as more than one replica
44
+ - **Graceful shutdown** — drain in-flight requests with a deadline; what happens to retries still queued (abandoned, persisted, returned to caller as a specific error); what signals trigger shutdown (`SIGTERM`, container-stop hook, …)
45
+ - **Health checks** — define what "healthy" means for **this** integration: last successful provider call within a window, valid credentials, provider's own `/health` returning 2xx, dependent storage reachable. Spec the response shape (status code, body) for the health endpoint when one is exposed
46
+ - **Background loops** — cron jobs, polling workers, queue consumers: each gets its own functional spec covering trigger, interval, leader-election (if applicable), failure handling, and observability
47
+
48
+ ## Entry-point types — pick deliberately, enumerate fully
49
+
50
+ A standalone integration exposes one or more **entry points** to its consumers. Mixing entry-point types (e.g. HTTP + queue worker + CLI) is allowed, but every entry point must be enumerated up front and gets at least one functional spec.
51
+
52
+ | Entry-point type | Public surface | Contract format |
53
+ |------------------|----------------|-----------------|
54
+ | HTTP service | One or more routes (path + method) | OpenAPI 3.1 (`resources/contract/<integration>.openapi.yaml`) |
55
+ | CLI | Command + subcommands + flags | JSON Schema for config + `--help` text seeded from the schema |
56
+ | Queue worker | Topic / partition + message envelope | JSON Schema per message type (`resources/contract/<topic>.schema.json`) |
57
+ | Scheduled job | Job name + cron expression + config schema | JSON Schema for config |
58
+ | Library | Public package surface (functions, classes) | JSON Schema for I/O + published-package manifest |
59
+ | Webhook receiver | Provider-driven inbound HTTP | JSON Schema per event type under `resources/webhooks/` (shared with embedded — see [`integrations.md`](integrations.md)) |
60
+
61
+ Each entry point gets its own functional spec describing what calling it does end-to-end (which earlier functional specs it composes — auth, the endpoint call, the retry policy, the error handling).
62
+
63
+ ## Side effects are first-class — spell them out
64
+
65
+ A standalone integration usually mutates state outside the provider call itself. Each side effect is captured as a functional spec describing:
66
+
67
+ - **What** gets mutated — local DB writes, file writes, cache updates, emitted domain events, metrics, log lines
68
+ - **When** the mutation happens — before or after the provider call
69
+ - **Is the mutation transactional with the provider call** — atomic, eventually consistent, fire-and-forget
70
+ - **What happens on partial failure** — provider call succeeded but the side effect failed (and vice versa); rollback / compensation / retry strategy
71
+
72
+ ## Concurrency and backpressure
73
+
74
+ Standalone integrations face concurrent load that an embedded library doesn't. The spec must pin:
75
+
76
+ - **Sync vs. async** in the host-language sense (`async def`, `Promise`, goroutines, threads)
77
+ - **Expected peak throughput** (RPS) the consumer will issue
78
+ - **Connection pool size**
79
+ - **Request queue depth**
80
+ - **Backpressure behavior** when the provider's rate limit and the consumer's demand collide — drop, queue, block with timeout, or 429 the consumer
81
+ - **Concurrency limits on mutating operations** — when the provider has its own per-resource serialization constraints
82
+
83
+ Capture all of this as a concurrency concept and one functional spec for "apply backpressure when local demand exceeds rate-limit budget".
84
+
85
+ ## Configuration surface
86
+
87
+ - Every config knob the consumer can set lives in `resources/config.schema.json` (JSON Schema): env var names, config file keys, secrets, feature flags, regional selectors, timeouts, retry counts, base URL overrides
88
+ - The configuration concept enumerates every key by name (type, default, required vs optional, validation, where it is read — startup vs per-call)
89
+ - Secrets are referenced by env var name only; never written into the schema as default values
90
+ - One functional spec covers "load and validate configuration on startup" — fail-fast on missing required values
91
+
92
+ ## Versioning of the integration itself
93
+
94
+ Separate from the provider's API version (which lives in the provider OpenAPI file), the **integration's own contract** is versioned independently:
95
+
96
+ - Semver of the published library / OpenAPI `info.version` / schema `$id` URL / CLI `--version`
97
+ - Backwards-compatibility policy stated explicitly (e.g. "minor and patch are wire-compatible; breaking changes only on major")
98
+ - New major versions ship at a new schema path; never mutate a published version in place
99
+
100
+ Capture as a contract-version concept; pin the version in every published schema.
101
+
102
+ ## Testing — live conformance, secrets from env, webhooks
103
+
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
+
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
+ - **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
+ - **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
+ - **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
110
+ - **Webhook tests** (if webhooks are in scope) must cover signature verification end-to-end — including invalid signatures and replay attempts. Signing keys are env vars, like every other secret
111
+ - **Rate-limit (429) tests.** The 429 path must **not** exhaust the live API's quota — use a local mock for that specific endpoint, document the exception in `***test reqs***`. Every other path remains live
112
+ - **Idempotency tests.** Run the same mutating call twice (with the same idempotency key) against the live sandbox and assert the same response
113
+
114
+ ## Standalone-specific completion checklist
115
+
116
+ Before declaring a standalone integration done, in addition to the shared checklist in [`integrations.md`](integrations.md):
117
+
118
+ - [ ] Tech stack decisions are recorded in `***implementation reqs***` — language and version, HTTP client library and version, framework (HTTP / CLI / worker as applicable), data storage choice(s), architecture/layering
119
+ - [ ] Publication surface(s) for the contract schema are enumerated in functional specs (one per surface)
120
+ - [ ] Schema versioning strategy is captured — `$id` / `info.version` / package version pinned, backwards-compatibility policy stated
121
+ - [ ] Every entry point is enumerated and has at least one functional spec (HTTP routes, CLI commands, queue topics, scheduled jobs, library APIs)
122
+ - [ ] Every lifecycle stage has a functional spec — initialization, credential refresh, graceful shutdown, health check
123
+ - [ ] Side effects are each captured as a functional spec with ordering, transactionality, and partial-failure handling
124
+ - [ ] Concurrency model is captured (sync vs async, pool size, queue depth, backpressure strategy)
125
+ - [ ] Configuration surface lives in `resources/config.schema.json` and is linked from the configuration concept
126
+ - [ ] Testing strategy is recorded in `***test reqs***` — live vs recorded, sandbox credential source, webhook signature coverage, rate-limit-test isolation
127
+
128
+ ## Anti-patterns specific to standalone integrations
129
+
130
+ - **Skipping any lifecycle stage** because it "feels small". Standalone integrations have no host to fall back on; an implicit shutdown is an undefined shutdown
131
+ - **Inlining fields from `resources/contract/<entry-point>.schema.json` into a functional spec.** The schema is the source of truth and is **published** — duplicating fields in spec text guarantees drift
132
+ - **Mutating a published schema's `$id` / version path** instead of shipping a new file. Consumers may already be pinning the old version
133
+ - **Treating configuration as "whatever env vars seem useful at runtime".** Every key lives in `resources/config.schema.json` with type, default, and validation; the loader fails fast on missing required values
134
+ - **Picking a tech stack incrementally while writing functional specs.** Decide it once in `brood`'s stack phase and transcribe into `***implementation reqs***`; don't accrete a stack across several specs
135
+ - **Hitting the live provider in rate-limit (429) or destructive-error (500) tests.** Use a local mock; live calls are for the happy path and for sandbox-safe error paths
@@ -0,0 +1,137 @@
1
+ ---
2
+ description: Rules for authoring ***plain specs for REST API integrations
3
+ ---
4
+
5
+ # Rules for writing integration specs in `***plain`
6
+
7
+ When writing or editing a `.plain` file that describes an **integration** against a REST API (synchronous JSON/HTTP request-response plus webhook callbacks), always follow these rules. Non-REST integrations (gRPC, GraphQL, SOAP, message brokers, raw TCP, file drops) are out of scope of these rules.
8
+
9
+ ## Scope of "integration" specs
10
+ - An integration `.plain` module describes how the project talks to a **third-party or internal REST API**
11
+ - The integration may be **embedded** (lives as a library/module inside an existing host codebase) — see [`integration-embedded.md`](integration-embedded.md) for the additional rules that apply
12
+ - Or **standalone** (a service, daemon, CLI, scheduled job, or container) — see [`integration-standalone.md`](integration-standalone.md) for the additional rules that apply
13
+ - The contract surface, edge-case coverage, live-API cross-check, and `resources/` layout described below apply to **both** shapes — the shape-specific rule files add to them, never replace them
14
+
15
+ ## Contract artifacts live in `resources/` (hard rule)
16
+
17
+ **Every structural contract the integration deals with — endpoint definitions, request/response schemas, error envelopes, webhook payloads, pagination envelopes, rate-limit headers, the integration's own I/O contract — lives in `resources/` as a linked resource.** Concepts and functional specs **reference** these files; they never restate fields, types, status codes, or header names inline.
18
+
19
+ Pick the right format per artifact:
20
+
21
+ | Artifact | Format | Conventional path |
22
+ |----------|--------|-------------------|
23
+ | Provider's REST surface | OpenAPI 3.1 (YAML or JSON) | `resources/<provider>.openapi.yaml` |
24
+ | Webhook payload (per event type) | JSON Schema Draft 2020-12 | `resources/webhooks/<event>.schema.json` |
25
+ | Rich webhook contracts (multi-channel) | AsyncAPI 2.6+ | `resources/webhooks.asyncapi.yaml` |
26
+ | Integration's own I/O contract | JSON Schema or OpenAPI | `resources/contract/<entry-point>.schema.json` |
27
+ | Configuration surface | JSON Schema | `resources/config.schema.json` |
28
+ | Error code → category mapping | YAML enum | `resources/error-map.yaml` |
29
+ | Rate-limit header inventory | YAML enum | `resources/rate-limit-headers.yaml` |
30
+ | Retry policy parameters | YAML enum | `resources/retry-policy.yaml` |
31
+ | Provider behavior with no machine-readable home | Markdown | `resources/<provider>-notes.md` |
32
+
33
+ Rules that flow from this:
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.
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.
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.
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.
39
+ - **The renderer generates language-native types from the resources.** For embedded integrations the renderer reads JSON Schema / OpenAPI components and emits Pydantic / TypeScript / Go types in `plain_modules/`. The spec declares **which** schema to generate from, **where** the generated type should land, and **what host base class** it must subclass — but never restates the schema's fields.
40
+
41
+ A single `.plain` module can (and typically will) reference many resources. That is the intended pattern; do not try to collapse them into one mega-file.
42
+
43
+ ## Live API must be cross-checked against the documentation
44
+
45
+ Documentation lies — it goes stale, omits undocumented fields, describes a different API version, papers over breaking changes. Every integration spec must be grounded in what the API really returns, not what the docs claim it returns.
46
+
47
+ - **Discover the documentation links with web search first, then `fetch` all of them.** Documentation moves, gets reorganized, and is versioned — so never assume a doc URL from memory. Begin every topic by issuing **web searches with human-readable queries** to find the canonical pages, then `fetch` **every** relevant link the search surfaces — the endpoint reference, the auth guide, the webhook catalog, the error reference, the pagination/rate-limit docs, the changelog. The search step finds *which* URLs are authoritative and current; the `fetch` step retrieves their actual content. Do not stop at the first hit — fetch the full set so a topic is grounded in all of its sources, not a single page.
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.
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:
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.
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
+ - **The fetched documentation is then cross-checked against the live API** — see the rest of this section.
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
+ - **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
+ - **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.
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.
57
+ - **Credentials are never written to `.plain` files or summaries.** Reference them by env-var name only.
58
+
59
+ ## `:ConformanceTests:` always run against the live integration (hard rule)
60
+
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.
62
+
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
64
+ - The only exceptions are paths that **cannot** be exercised live safely:
65
+ - **Rate-limit (429) tests** — must not exhaust the live quota; use a local mock for that specific endpoint
66
+ - **Deliberately destructive failure modes** (forced 5xx) the provider doesn't let you trigger — same; mock the specific endpoint
67
+ Document each exception explicitly in `***test reqs***`; everything else is live by default
68
+
69
+ ### Secrets come from the environment
70
+
71
+ Live conformance needs credentials. The integration spec must pin every credential as **an env var name, never a literal value**, and the conformance script must read those env vars at runtime:
72
+
73
+ - **Author the env-var names in `***test reqs***`** (using `:ConformanceTests:`) and again in the auth concept. Examples: `STRIPE_API_KEY`, `GITHUB_TOKEN`, `SALESFORCE_CLIENT_ID` + `SALESFORCE_CLIENT_SECRET`. Use names that match what the provider's own docs use, so a user copying values from the provider console doesn't need to translate
74
+ - **The user supplies the values out-of-band**, either:
75
+ - in a `.env` file at the project root (`.env` is gitignored; the project ships `.env.example` with the names but no values), or
76
+ - exported in the shell that invokes the test scripts (CI uses the same names from its secret store)
77
+ - **`run_conformance_tests_<lang>` reads the env vars at runtime.** If a required var is missing, the script must fail fast with `Error: <NAME> is required for :ConformanceTests:` and exit `69` (per the testing-script exit-code conventions). Never default to a placeholder, never use a value baked into the script
78
+ - **The script may optionally load a `.env` file** before running the suite — typical pattern is to look for `.env` in the project root and `source` / `dotenv -e` it if present, but never fail when it's absent (since CI provides the vars directly via the shell). If a `.env` loader is used, the script must verify each required var is set **after** loading, not before
79
+ - **The conformance suite reads the same env-var names** the integration's runtime reads — so a credential that works at runtime is the same credential that exercises the suite
80
+ - **Credentials never appear in `.plain` files, commits, summaries, logs, or fixtures.** The cross-check (see *Live API must be cross-checked*) already requires redacting credentials from saved fixtures; the same rule applies to conformance logs
81
+
82
+ The `.plain` spec should make this discoverable. A minimal `***test reqs***` block for an integration looks like:
83
+
84
+ ```plain
85
+ - :ConformanceTests: run against the live :ProviderName: sandbox — no mocking of provider calls.
86
+ - Credentials are read from the environment, never from a file checked into the repo.
87
+ - The conformance script reads `<PROVIDER>_API_KEY` (and any additional secrets) from the shell or from a `.env` file at the project root.
88
+ - The script must verify every required env var is set after the optional `.env` load and fail fast with a clear error if any is missing.
89
+ - The 429 (rate-limit) and forced-5xx paths use a local mock for that specific endpoint; every other path is live.
90
+ ```
91
+
92
+ ## Embedded vs standalone — pick the shape early
93
+
94
+ Every integration is either **embedded** (lives as a library/module inside an existing host codebase) or **standalone** (a service, daemon, CLI, scheduled job, or container). The choice is captured as a concept (`integration-shape: embedded | standalone`) so later specs can reference it.
95
+
96
+ The contract artifact itself is **identical** across both shapes: a JSON Schema (or OpenAPI) file under `resources/contract/`. What changes is **what the renderer emits from it** and **what extra context the spec carries**:
97
+
98
+ - **Embedded** → the renderer generates a host-language class from the schema and wires it into the host's import path. The host codebase dictates the tech stack — see [`integration-embedded.md`](integration-embedded.md) for the full ruleset
99
+ - **Standalone** → the renderer treats the schema as the public artifact: it generates an internal implementation in `plain_modules/` and ships the schema verbatim for external consumers. The integration owns its stack — see [`integration-standalone.md`](integration-standalone.md) for the full ruleset
100
+
101
+ ## Edge-case coverage is a hard floor, not a stretch goal
102
+
103
+ A production-ready integration spec captures every corner case the API can throw at the integration. Each of the following must be in the specs (or explicitly recorded as "not applicable" / "not in scope" with the user's acknowledgement) before the integration is considered complete:
104
+
105
+ - **Provider, purpose, canonical documentation URL(s)** as concepts
106
+ - **Endpoints in scope** in `resources/<provider>.openapi.yaml`, one `paths.<path>.<method>` entry per endpoint; endpoint concepts link to the OpenAPI entry
107
+ - **Auth scheme**, credential source pinned by env-var name, refresh policy, scopes — with `components.securitySchemes` in the OpenAPI matching
108
+ - **Environments** (sandbox / staging / production) and the switch mechanism — reflected in `servers` of the OpenAPI file
109
+ - **API version pinning** strategy (URL path, header, `Accept`, query string) and the deprecation policy
110
+ - **Request serialization** (content type, date format, numeric precision, custom headers) in `components.schemas` / `components.parameters`
111
+ - **Pagination model** as `components.schemas.PageEnvelope` plus `resources/pagination.yaml` (style, defaults, safety cap)
112
+ - **Rate-limit model** as `resources/rate-limit-headers.yaml` + `resources/rate-limits.yaml` + `components.schemas.RateLimitError`
113
+ - **Error model** as `components.schemas.ErrorEnvelope` + `resources/error-map.yaml`, with one functional spec per error category
114
+ - **Retry policy** in `resources/retry-policy.yaml`
115
+ - **Transient vs. permanent exception classification** — every error the integration can raise is classified as either *transient* (retryable per the retry policy above) or *permanent* (surfaced to the caller immediately). The classification lives in `resources/error-map.yaml` (per-status-code) and in the integration's own exception hierarchy concept, and the two must agree. Concretely:
116
+ - **Transient by default**: network-level errors (`ConnectionError` / equivalent, DNS failure, socket timeout, TLS handshake failure, connection reset), HTTP 408, 425, 429, 500, 502, 503, 504, and any provider-specific application-level error codes the docs / live API mark as retryable (e.g. Stripe's `lock_timeout`, AWS `ThrottlingException`)
117
+ - **Permanent by default**: every other 4xx (400, 401, 403, 404, 405, 409, 410, 415, 422, …), schema-validation failures on otherwise-2xx responses, signature-verification failures on webhooks, and any provider error code the docs / live API mark as non-retryable
118
+ - **Integration exception hierarchy**: the integration exposes a base exception class (e.g. `<Provider>Error`) with two direct subclasses, `<Provider>TransientError` (raised after the retry policy gives up on a transient condition) and `<Provider>PermanentError` (raised immediately on a permanent condition). Every concrete exception inherits from exactly one of those two — callers branch on the base class, never on individual subclasses
119
+ - **The classification is data-driven**, not code-driven — `resources/error-map.yaml` is the source of truth, the renderer reads it to generate the exception hierarchy and the `is_transient(error) -> bool` predicate. Spec text never enumerates status codes inline
120
+ - **Discrepancies found during the live-API cross-check override the docs** — if the docs say a code is transient but the live API never recovers from it (or vice versa), the live behavior wins and the divergence is recorded in `error-map.yaml` plus a note in the error-model concept ("docs claim X retryable, live API returns Y permanently; we follow the live API")
121
+ - **Idempotency strategy** in `resources/idempotency.yaml` + idempotency header in `components.parameters`
122
+ - **Webhook contracts** as `resources/webhooks/<event>.schema.json` per event type + `resources/webhook-signing.yaml`
123
+ - **Data mapping** (entity schemas + transformations / exclusions) as entity schemas + `resources/data-mapping.yaml`
124
+ - **Compliance / data-sensitivity** constraints (PII, PHI, payment data, data residency, log redaction, audit logs)
125
+ - **Observability** (log fields, provider request IDs, metrics, tracing propagation)
126
+
127
+ ## Anti-patterns (do not do these)
128
+
129
+ - **Restating an OpenAPI / JSON Schema field list in a concept or functional spec.** The schema lives in `resources/`; the spec links to it
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
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
132
+ - **Embedding credentials, tokens, or signing keys in a `.plain` file or in a summary** — credentials are referenced by env-var name only
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
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
135
+ - **Authoring the integration's functional 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
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
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
@@ -0,0 +1,69 @@
1
+ ---
2
+ description: Rules for linking external resources from .plain specs
3
+ ---
4
+
5
+ # Rules for linked resources in `.plain` files
6
+
7
+ Specifications can reference external files using markdown link syntax. The renderer reads the linked file's bytes verbatim and feeds them to the model alongside the spec. That mechanism only works for a specific shape of target — violating any of the rules below is one of the most common and disruptive mistakes in `.plain` authoring.
8
+
9
+ ## Hard constraint: a linked resource is always a single file on disk - text, or a PNG or JPEG image
10
+
11
+ A linked resource **must not** be any of the following:
12
+
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
+ 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
+ 3. **A binary file other than a PNG or JPEG image.** GIF, WEBP, SVG, 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 PDF spec becomes a Markdown extract or the underlying JSON Schema / OpenAPI, an architecture diagram becomes a Mermaid block).
16
+ - **PNG and JPEG images (`.png`, `.jpg`, `.jpeg`) are the exception**: a UI mockup, a screenshot of the look to match, a diagram. The coding agents are shown the image itself, so the coding model must be one that reads images - the render stops, saying so, when it is not. Each image is at most 5 MB.
17
+ - An image says what something looks like, not what it does: the behaviour still belongs in the spec's text. Link a mockup from a concept (`:CheckoutPage:` looks as [checkout mockup](resources/checkout.png) shows) and keep the rules - what each button does, which fields are required - in the functional specs.
18
+ - Where the model may not read images, or a detail must be exact (a colour code, a spacing, a label), say it in text as well; the image is the reference, not the only source.
19
+ 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.
20
+
21
+ ## Never use a URL or folder path as a *reference* in prose
22
+ - 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
23
+ - 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
24
+ - A URL or path in prose is fine when the *text itself* is the complete fact:
25
+ - **Runtime values** the produced software uses — the base URL an integration calls, a database connection path, a CLI argument default, an output directory layout
26
+ - **Provenance notes** for a snapshotted resource — recording the canonical URL a file saved under `resources/` was fetched from
27
+ - 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
28
+
29
+ ## Structured protocol artifacts must be linked, never transcribed
30
+ - 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)
31
+ - The spec line should describe the *role* of the artifact ("the request body conforms to ...", "the public API surface is defined in ...") rather than its contents
32
+ - Reasons: one source of truth (no drift between prose and schema); the renderer and the generated code can both consume the file directly; schema changes show up cleanly as diffs
33
+
34
+ ```plain
35
+ ***definitions***
36
+
37
+ - :TaskCreateRequest: is the JSON payload for creating a task.
38
+ - It is defined by [resources/task_create_request.schema.json](resources/task_create_request.schema.json).
39
+ - :TasksAPI: is the public HTTP surface for tasks.
40
+ - It is defined by [resources/tasks_openapi.yaml](resources/tasks_openapi.yaml).
41
+
42
+ ***functional specs***
43
+
44
+ - A :User: can add a :Task: by POSTing :TaskCreateRequest: to the `POST /tasks` endpoint of :TasksAPI:.
45
+ - The endpoint responds per :TasksAPI:.
46
+ ```
47
+
48
+ ## Each linked resource is referenced from exactly one place
49
+ - Linking the same file from two functional specs (or from a functional spec **and** an implementation requirement) creates two independent sources of truth — any later edit silently diverges
50
+ - If a resource needs to inform multiple parts of the project, **don't repeat the link** — attach the resource to a **concept** in `***definitions***` and reference the concept token (`:ConceptName:`) elsewhere
51
+ - If you're about to paste the same `[name](path)` link a second time, **stop** — create the concept first
52
+
53
+ ```plain
54
+ ***definitions***
55
+
56
+ - :TaskModalSpec: is the user-interface contract for the task modal.
57
+ - It is fully described in [task_modal_specification.yaml](task_modal_specification.yaml).
58
+
59
+ ***functional specs***
60
+
61
+ - A :User: can add a :Task: using :TaskModalSpec:.
62
+
63
+ - A :User: can edit a :Task: using :TaskModalSpec:.
64
+ ```
65
+
66
+ ## File location and path resolution
67
+ - Paths are resolved relative to the directory where `turri` is run (the current working directory)
68
+ - Relative paths may traverse into parent directories — `[resource](../../resources/resource.md)` is valid — as long as they resolve to a real text file on disk
69
+ - The conventional location is a `resources/` directory; keep the resource inside the project so the path stays stable from wherever `turri` is run
@@ -0,0 +1,105 @@
1
+ ---
2
+ description: File-level structure of .plain modules — frontmatter, sections, ordering, and section ownership
3
+ ---
4
+
5
+ # Rules for `.plain` module structure
6
+
7
+ When creating a new `.plain` file or reviewing the structure of a whole file, always follow these rules. Section *content* rules live in the per-section rule files linked below — this file covers only file-level structure.
8
+
9
+ ## File skeleton
10
+ - A `.plain` file is one **module**: optional YAML frontmatter between `---` markers, followed by sections marked with `***section name***` headers
11
+ - The frontmatter, when present, must be the first thing in the file
12
+ - Every content line inside a section must be a `- ` list item — see `bullet-continuation.md`
13
+ - Every line must be as short and plain as its content allows — see `concise-specs.md`
14
+
15
+ ## Frontmatter fields
16
+
17
+ | Field | Purpose | Rule file |
18
+ |---|---|---|
19
+ | `description` | One-line summary of the module; recommended on every module | — |
20
+ | `import` | Pull definitions, implementation reqs, and test reqs from import modules | `import-modules.md` |
21
+ | `requires` | Attach the module to the build chain of another root module | `requires-modules.md` |
22
+ | `exported_concepts` | Declare which of this module's concepts are visible to modules that `require` it | `exported-concepts.md` |
23
+ | `required_concepts` | Declare concepts an import module expects the importing module to define | `required-concepts.md` |
24
+
25
+ ## Sections
26
+ - There are exactly four top-level section markers: `***definitions***`, `***implementation reqs***`, `***test reqs***`, and `***functional specs***`
27
+ - Each section appears **at most once** per file; all are optional (which ones are allowed depends on the module kind below)
28
+ - `***acceptance tests***` is **never a top-level section** — it appears only nested under a single functional spec (see `func-specs.md`)
29
+ - Write sections in canonical order: `***definitions***` → `***implementation reqs***` → `***test reqs***` → `***functional specs***`
30
+ - Definitions come first because every concept must be defined before it is referenced
31
+ - Functional specs come last because they are rendered incrementally and their nested acceptance tests close the file
32
+
33
+ ## Module kinds
34
+ - A **root module** lives at the repository root and carries behavior: it contains `***functional specs***`
35
+ - To be renderable, a root module needs at least one functional spec plus the implementation reqs to build it — its own or from an `import`ed module (implementation reqs are not inherited through `requires`). A frontmatter-only file is a valid scaffold, but it cannot be rendered until a functional spec is added
36
+ - A **requires module** is a root module that is used by another root module via `requires` directive (see `requires-modules.md`)
37
+ - An **import module** provides shared content that other modules pull in via `import`: it contains only `***definitions***`, `***implementation reqs***`, and/or `***test reqs***` — never `***functional specs***`, never `requires` (see `import-modules.md`)
38
+
39
+ ## Section ownership (where a fact lives)
40
+ - The renderer reads each kind of fact **only from its owning section** — a fact placed in the wrong section is silently ignored, not flagged
41
+ - Before writing any requirement, place it by content:
42
+ - To move facts that are already misplaced, or to split a bullet carrying several kinds of fact, use the `resolve-section-ownership` skill
43
+
44
+ | Content | Owning section | Rule file |
45
+ |---|---|---|
46
+ | Concepts (`:CamelCaseToken:`) | `***definitions***` | `definitions.md` |
47
+ | HOW the software is built — tech stack, architecture, coding standards — and **everything about `:UnitTests:`** | `***implementation reqs***` | `impl-reqs.md` |
48
+ | **Everything about `:ConformanceTests:`** — framework, run command, mocking and network policy | `***test reqs***` | `test-reqs.md` |
49
+ | WHAT the software does — observable, language-agnostic behavior | `***functional specs***` | `func-specs.md` |
50
+ | End-to-end workflow verification of one functional spec | nested `***acceptance tests***` | `func-specs.md` |
51
+
52
+ ## Comments
53
+ - Lines beginning with `>` are ignored when rendering
54
+
55
+ ```plain
56
+ > This is a comment in ***plain.
57
+ ```
58
+
59
+ - Comments explain the specification to human authors — never use a comment to carry a requirement the renderer must implement
60
+
61
+ ## Liquid templates
62
+ - `.plain` files are processed with [Liquid](https://shopify.github.io/liquid/) before parsing — the full Liquid syntax (variables, filters, conditionals, loops) is available in any section
63
+ - Expansion is **textual** and happens before the specification is interpreted — unlike `import`, which includes another module semantically (see `import-modules.md`)
64
+
65
+ ```plain
66
+ {% assign max_tags = 3 %}
67
+
68
+ ***functional specs***
69
+
70
+ - A :Widget: can be created with up to {{ max_tags }} tags.
71
+ {% if max_tags > 1 %}
72
+ - A :Widget: can have its tags listed.
73
+ {% endif %}
74
+ ```
75
+
76
+ ## Format
77
+
78
+ ```plain
79
+ ---
80
+ description: Command-line task manager.
81
+ import:
82
+ - airplain
83
+ ---
84
+
85
+ ***definitions***
86
+
87
+ - :Task: is an activity tracked by the application.
88
+ - Name (required)
89
+
90
+ - :TaskList: is the collection of all :Task: items. Initially empty.
91
+
92
+ ***implementation reqs***
93
+
94
+ - :Implementation: keeps :TaskList: in memory.
95
+
96
+ ***test reqs***
97
+
98
+ - :ConformanceTests: must not access the network.
99
+
100
+ ***functional specs***
101
+
102
+ - A :Task: can be added to the :TaskList:.
103
+
104
+ - The :TaskList: is displayed.
105
+ ```
@@ -0,0 +1,88 @@
1
+ ---
2
+ description: The ***plain project model - the specs as source of truth, the repository layout, generated output, template parameters, and what an earlier test failing after a render means
3
+ ---
4
+
5
+ # The ***plain project model
6
+
7
+ This rule covers the project as a whole. Section content is owned by the per-section rule files;
8
+ file structure by `module-structure.md`.
9
+
10
+ ## Source of truth
11
+
12
+ - The `.plain` files are the source of truth. They state what the software does, how it is built,
13
+ and how it is tested; the code is generated from them
14
+ - Every change to the software is a change to a `.plain` file, which the next render builds. A fix
15
+ made anywhere else is lost the next time the code is regenerated
16
+
17
+ ## Repository layout
18
+
19
+ ```text
20
+ *.plain # root modules: what the software does
21
+ template/*.plain # import modules: shared definitions and reqs
22
+ resources/ # files specs link to: schemas, API specs, fixtures
23
+ test_scripts/ # unit and conformance runners, found by turri (not linked)
24
+ config.turri.yaml # the project's build decisions: models, conformance tests, the scripts in use
25
+ plain_modules/ # generated - one folder per module
26
+ <module>/code/ # implementation and unit tests
27
+ <module>/tests/ # the whole conformance suite of the module
28
+ ```
29
+
30
+ Follow `module-structure.md`, `import-modules.md` and `linked-resources.md` for what these
31
+ directories may contain and how a spec refers to them.
32
+
33
+ ## The config records the build decisions
34
+
35
+ `config.turri.yaml`, at the project root beside the specs, is where the project's decisions about how it
36
+ is built live: the models, whether conformance tests are written (`conformance-tests:`), and the scripts
37
+ it uses (`unittests-script:`, `conformance-tests-script:`,
38
+ `prepare-environment-script:`). turri can find a script by its name, but the config is what a person
39
+ reads to see how the project is set up, and what they change.
40
+
41
+ - Settle a decision, write it into the config in the same step - never leave it to be inferred from
42
+ what is on disk. A testing script you write or keep is named by its key, by path
43
+ from the root (`unittests-script: test_scripts/run_unittests_python.sh`); conformance tests
44
+ turned on or off are `conformance-tests: true` or `false`
45
+ - Change only the keys you settled. Where the file has a key's example commented out, uncomment that
46
+ line and set it; keep every other line and comment as it is
47
+ - A project whose parts are on different stacks keeps each stack's scripts in its own
48
+ `config.turri.<stack>.yaml` beside it; what the stacks share stays in `config.turri.yaml`
49
+ - A project with only a `config.yaml` has it copied to `config.turri.yaml` first, and each
50
+ `config.<name>.yaml` to `config.turri.<name>.yaml`; `config.yaml` may be another tool's, and is never
51
+ written
52
+ - Say in your summary which lines you wrote
53
+
54
+ ## Generated output is read-only
55
+
56
+ Everything under `plain_modules/` is generated - `code/` and `tests/` alike. Read it, run it, debug
57
+ it; never edit it. Fix the spec instead, in the section that owns the fact:
58
+
59
+ - behaviour, implementation or unit-test guidance → definitions, functional specs, or implementation
60
+ reqs
61
+ - conformance-test guidance → test reqs
62
+ - acceptance-test guidance → the acceptance tests nested under their functional spec
63
+
64
+ Then render again.
65
+
66
+ ## Template inclusion with parameters
67
+
68
+ Besides frontmatter `import`, a template can be included with parameters:
69
+
70
+ ```plain
71
+ {% include "python-console-app-template.plain", main_executable_file_name: "my_app.py" %}
72
+ ```
73
+
74
+ Parameters are key-value pairs; the template reads them as `{{ variable_name }}`.
75
+
76
+ ## When an earlier test fails after a render
77
+
78
+ Functional specs render in order, each knowing the ones before it and none after it - a required
79
+ module's specs count as before. After each render every conformance test so far runs again. A test
80
+ that passed before and fails now means one of:
81
+
82
+ 1. the new code does not satisfy its spec;
83
+ 2. the test does not test its spec accurately;
84
+ 3. the new spec and an earlier one conflict.
85
+
86
+ The harness triages the first two and repairs the code. The third is a spec problem: resolve it in
87
+ the `.plain` files with `resolve-spec-conflict`, and trace a misbehaving build back to its spec with
88
+ `debug-specs` - never by editing generated output.
@@ -0,0 +1,44 @@
1
+ ---
2
+ description: Rules for using required_concepts in .plain files
3
+ ---
4
+
5
+ # Rules for `required_concepts`
6
+
7
+ When adding or editing `required_concepts` in a `.plain` file's frontmatter, always follow these rules:
8
+
9
+ ## What required_concepts does
10
+ - `required_concepts` declares concepts that any module importing this file **must** define
11
+ - It creates a contract: the import module references these concepts but does not define them — the importing module is responsible for providing definitions
12
+ - This is used exclusively on import modules
13
+
14
+ ## When to use it
15
+ - Use `required_concepts` when an import module references concepts that vary per project or per importing module
16
+ - The import module can reference these concepts in its definitions, implementation reqs, or test reqs — but their actual definitions come from whoever imports the file
17
+
18
+ ## Importing module must satisfy the contract
19
+ - Every concept listed in `required_concepts` must be defined somewhere visible to the importing module — in its own `***definitions***` section or in another module it imports
20
+ - If the importing module does not define a required concept, the spec is invalid
21
+ - Check `required_concepts` of all imported modules before finalizing a module
22
+
23
+ ## Do not define required concepts locally
24
+ - The import module that declares `required_concepts` must **not** define those concepts itself
25
+ - The whole point is that the importing module provides the definition
26
+
27
+ ## Format
28
+
29
+ ```plain
30
+ ---
31
+ required_concepts: [":AppName:", ":AppConfig:"]
32
+ description: Imported module that requires AppName and AppConfig to be defined by the importer
33
+ ---
34
+
35
+ ***definitions***
36
+ - :MainFile: is the entry point for :AppName:.
37
+
38
+ ***implementation reqs***
39
+ - :MainFile: should load :AppConfig: on startup.
40
+ ```
41
+
42
+ In this example, `:AppName:` and `:AppConfig:` are referenced but not defined — the module that imports this import module must define them.
43
+
44
+ List concepts as a YAML array with each concept in `:ConceptName:` notation.