@terreno/mcp 57.3.0 → 57.4.0

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 (250) hide show
  1. package/README.md +7 -1
  2. package/dist/bootstrap.d.ts +0 -18
  3. package/dist/bootstrap.d.ts.map +1 -1
  4. package/dist/bootstrap.js +10 -2428
  5. package/dist/bootstrap.js.map +1 -1
  6. package/dist/docs/guidelines/admin-frontend/guidelines/core.md +5 -1
  7. package/dist/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +23 -10
  8. package/dist/docs/resources/syncdb.md +4 -1
  9. package/dist/docs/upgrades/57.4.0.md +140 -0
  10. package/dist/docs/versioned/next/README.md +3 -0
  11. package/dist/docs/versioned/next/explanation/README.md +4 -1
  12. package/dist/docs/versioned/next/explanation/admin-interface.md +20 -13
  13. package/dist/docs/versioned/next/explanation/charts.md +29 -0
  14. package/dist/docs/versioned/next/explanation/deployment-architecture-gcp.md +32 -0
  15. package/dist/docs/versioned/next/explanation/deployment-baseline.md +1 -1
  16. package/dist/docs/versioned/next/explanation/example-coverage.md +43 -0
  17. package/dist/docs/versioned/next/explanation/local-first-data.md +5 -2
  18. package/dist/docs/versioned/next/explanation/loop-engineering.md +9 -6
  19. package/dist/docs/versioned/next/explanation/model-router-actions.md +19 -2
  20. package/dist/docs/versioned/next/explanation/organizations.md +88 -0
  21. package/dist/docs/versioned/next/explanation/planning-plugins.md +114 -0
  22. package/dist/docs/versioned/next/explanation/positioning.md +3 -3
  23. package/dist/docs/versioned/next/explanation/repository-settings.md +6 -5
  24. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +44 -34
  25. package/dist/docs/versioned/next/explanation/source-rules.md +1 -1
  26. package/dist/docs/versioned/next/explanation/static-analysis.md +76 -16
  27. package/dist/docs/versioned/next/how-to/README.md +10 -0
  28. package/dist/docs/versioned/next/how-to/add-gpt-chat-mascot.md +92 -0
  29. package/dist/docs/versioned/next/how-to/add-organizations.md +204 -0
  30. package/dist/docs/versioned/next/how-to/admin-add-model.md +9 -1
  31. package/dist/docs/versioned/next/how-to/admin-import-prebuilt.md +7 -1
  32. package/dist/docs/versioned/next/how-to/audit-log.md +85 -0
  33. package/dist/docs/versioned/next/how-to/background-jobs.md +296 -0
  34. package/dist/docs/versioned/next/how-to/build-admin-screens.md +42 -13
  35. package/dist/docs/versioned/next/how-to/charts-and-dashboards.md +37 -0
  36. package/dist/docs/versioned/next/how-to/circleci.md +136 -27
  37. package/dist/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +36 -0
  38. package/dist/docs/versioned/next/how-to/configure-better-auth.md +4 -0
  39. package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +154 -0
  40. package/dist/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +2 -0
  41. package/dist/docs/versioned/next/how-to/github-actions-ci.md +4 -3
  42. package/dist/docs/versioned/next/how-to/github-issue-lifecycle.md +12 -8
  43. package/dist/docs/versioned/next/how-to/in-app-notifications.md +147 -0
  44. package/dist/docs/versioned/next/how-to/install-agent-skills.md +24 -12
  45. package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +19 -3
  46. package/dist/docs/versioned/next/how-to/product-announcements.md +155 -0
  47. package/dist/docs/versioned/next/how-to/run-mongodb-migrations.md +127 -0
  48. package/dist/docs/versioned/next/how-to/run-tests-locally.md +102 -0
  49. package/dist/docs/versioned/next/how-to/seed-a-database.md +7 -2
  50. package/dist/docs/versioned/next/implementationPlans/README.md +8 -5
  51. package/dist/docs/versioned/next/implementationPlans/admin-scripts-durable-jobs.md +44 -0
  52. package/dist/docs/versioned/next/implementationPlans/agent-ui-blocks.md +386 -0
  53. package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +11 -8
  54. package/dist/docs/versioned/next/implementationPlans/announcements-surfaces.md +231 -0
  55. package/dist/docs/versioned/next/implementationPlans/announcements.md +289 -0
  56. package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +3 -1
  57. package/dist/docs/versioned/next/implementationPlans/charts-and-dashboards.md +246 -0
  58. package/dist/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +4 -3
  59. package/dist/docs/versioned/next/implementationPlans/create-terreno-app.md +177 -0
  60. package/dist/docs/versioned/next/implementationPlans/datatable-server-side-filtering.md +219 -0
  61. package/dist/docs/versioned/next/implementationPlans/examples-demo-coverage.md +17 -17
  62. package/dist/docs/versioned/next/implementationPlans/framework-audit-log.md +247 -0
  63. package/dist/docs/versioned/next/implementationPlans/gpt-chat-mascot.md +40 -0
  64. package/dist/docs/versioned/next/implementationPlans/job-queues.md +330 -0
  65. package/dist/docs/versioned/next/implementationPlans/knip-cleanup.md +136 -0
  66. package/dist/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +210 -0
  67. package/dist/docs/versioned/next/implementationPlans/mongodb-migrations-tooling.md +254 -0
  68. package/dist/docs/versioned/next/implementationPlans/notification-center.md +185 -0
  69. package/dist/docs/versioned/next/implementationPlans/org-management-ui.md +8 -3
  70. package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
  71. package/dist/docs/versioned/next/reference/README.md +4 -0
  72. package/dist/docs/versioned/next/reference/admin-backend.md +83 -5
  73. package/dist/docs/versioned/next/reference/admin-config.md +7 -0
  74. package/dist/docs/versioned/next/reference/admin-frontend.md +178 -5
  75. package/dist/docs/versioned/next/reference/admin-spa.md +18 -1
  76. package/dist/docs/versioned/next/reference/ai.md +2 -0
  77. package/dist/docs/versioned/next/reference/announcements.md +179 -0
  78. package/dist/docs/versioned/next/reference/api.md +343 -4
  79. package/dist/docs/versioned/next/reference/comms.md +33 -0
  80. package/dist/docs/versioned/next/reference/create-terreno-app.md +44 -0
  81. package/dist/docs/versioned/next/reference/environment-variables.md +29 -0
  82. package/dist/docs/versioned/next/reference/jobs.md +319 -0
  83. package/dist/docs/versioned/next/reference/legacy/rtk.md +5 -1
  84. package/dist/docs/versioned/next/reference/lifecycle-plugin.md +29 -11
  85. package/dist/docs/versioned/next/reference/mcp-server.md +2 -43
  86. package/dist/docs/versioned/next/reference/scan-plugin.md +169 -0
  87. package/dist/docs/versioned/next/reference/syncdb.md +38 -12
  88. package/dist/docs/versioned/next/reference/test.md +57 -6
  89. package/dist/docs/versioned/next/reference/ui-performance.md +2 -1
  90. package/dist/docs/versioned/next/reference/ui.md +257 -3
  91. package/dist/docs/versioned/next/tasks/admin-scripts-durable-jobs.md +10 -0
  92. package/dist/docs/versioned/next/tasks/agent-ui-blocks.md +114 -0
  93. package/dist/docs/versioned/next/tasks/announcements-surfaces.md +101 -0
  94. package/dist/docs/versioned/next/tasks/announcements.md +173 -0
  95. package/dist/docs/versioned/next/tasks/charts-and-dashboards.md +120 -0
  96. package/dist/docs/versioned/next/tasks/create-terreno-app.md +82 -0
  97. package/dist/docs/versioned/next/tasks/datatable-server-side-filtering.md +87 -0
  98. package/dist/docs/versioned/next/tasks/examples-demo-coverage.md +123 -17
  99. package/dist/docs/versioned/next/tasks/framework-audit-log.md +86 -0
  100. package/dist/docs/versioned/next/tasks/gpt-chat-mascot.md +13 -0
  101. package/dist/docs/versioned/next/tasks/job-queues.md +144 -0
  102. package/dist/docs/versioned/next/tasks/knip-cleanup.md +146 -0
  103. package/dist/docs/versioned/next/tasks/migrate-admin-to-syncdb.md +209 -0
  104. package/dist/docs/versioned/next/tasks/mongodb-migrations-tooling.md +107 -0
  105. package/dist/docs/versioned/next/tasks/notification-center.md +102 -0
  106. package/dist/docs/versioned/next/tasks/org-management-ui.md +14 -14
  107. package/dist/docs/versioned/next/tutorials/README.md +1 -1
  108. package/dist/docs/versioned/next/tutorials/getting-started.md +62 -22
  109. package/dist/help/excerpt.d.ts +2 -0
  110. package/dist/help/excerpt.d.ts.map +1 -0
  111. package/dist/help/excerpt.js +8 -0
  112. package/dist/help/excerpt.js.map +1 -0
  113. package/dist/help/updateNotes.d.ts +42 -0
  114. package/dist/help/updateNotes.d.ts.map +1 -0
  115. package/dist/help/updateNotes.js +235 -0
  116. package/dist/help/updateNotes.js.map +1 -0
  117. package/dist/index.js +1 -1
  118. package/dist/index.js.map +1 -1
  119. package/dist/local/tools/databaseQuery.d.ts +1 -0
  120. package/dist/local/tools/databaseQuery.d.ts.map +1 -1
  121. package/dist/local/tools/databaseQuery.js +1 -0
  122. package/dist/local/tools/databaseQuery.js.map +1 -1
  123. package/dist/packageGuidelines.d.ts +2 -2
  124. package/dist/packageGuidelines.d.ts.map +1 -1
  125. package/dist/packageGuidelines.js +2 -8
  126. package/dist/packageGuidelines.js.map +1 -1
  127. package/dist/resources.d.ts +3 -0
  128. package/dist/resources.d.ts.map +1 -1
  129. package/dist/resources.js +3 -0
  130. package/dist/resources.js.map +1 -1
  131. package/dist/search/chunker.d.ts +1 -0
  132. package/dist/search/chunker.d.ts.map +1 -1
  133. package/dist/search/chunker.js +1 -0
  134. package/dist/search/chunker.js.map +1 -1
  135. package/dist/search/docIndex.d.ts +1 -5
  136. package/dist/search/docIndex.d.ts.map +1 -1
  137. package/dist/search/docIndex.js +1 -0
  138. package/dist/search/docIndex.js.map +1 -1
  139. package/dist/search/docVersion.d.ts +2 -1
  140. package/dist/search/docVersion.d.ts.map +1 -1
  141. package/dist/search/docVersion.js +3 -1
  142. package/dist/search/docVersion.js.map +1 -1
  143. package/dist/tools.d.ts +2 -2
  144. package/dist/tools.d.ts.map +1 -1
  145. package/dist/tools.js +113 -2
  146. package/dist/tools.js.map +1 -1
  147. package/package.json +28 -27
  148. package/src/docs/guidelines/admin-frontend/guidelines/core.md +5 -1
  149. package/src/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +23 -10
  150. package/src/docs/resources/syncdb.md +4 -1
  151. package/src/docs/upgrades/57.4.0.md +140 -0
  152. package/src/docs/versioned/next/README.md +3 -0
  153. package/src/docs/versioned/next/explanation/README.md +4 -1
  154. package/src/docs/versioned/next/explanation/admin-interface.md +20 -13
  155. package/src/docs/versioned/next/explanation/charts.md +29 -0
  156. package/src/docs/versioned/next/explanation/deployment-architecture-gcp.md +32 -0
  157. package/src/docs/versioned/next/explanation/deployment-baseline.md +1 -1
  158. package/src/docs/versioned/next/explanation/example-coverage.md +43 -0
  159. package/src/docs/versioned/next/explanation/local-first-data.md +5 -2
  160. package/src/docs/versioned/next/explanation/loop-engineering.md +9 -6
  161. package/src/docs/versioned/next/explanation/model-router-actions.md +19 -2
  162. package/src/docs/versioned/next/explanation/organizations.md +88 -0
  163. package/src/docs/versioned/next/explanation/planning-plugins.md +114 -0
  164. package/src/docs/versioned/next/explanation/positioning.md +3 -3
  165. package/src/docs/versioned/next/explanation/repository-settings.md +6 -5
  166. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +44 -34
  167. package/src/docs/versioned/next/explanation/source-rules.md +1 -1
  168. package/src/docs/versioned/next/explanation/static-analysis.md +76 -16
  169. package/src/docs/versioned/next/how-to/README.md +10 -0
  170. package/src/docs/versioned/next/how-to/add-gpt-chat-mascot.md +92 -0
  171. package/src/docs/versioned/next/how-to/add-organizations.md +204 -0
  172. package/src/docs/versioned/next/how-to/admin-add-model.md +9 -1
  173. package/src/docs/versioned/next/how-to/admin-import-prebuilt.md +7 -1
  174. package/src/docs/versioned/next/how-to/audit-log.md +85 -0
  175. package/src/docs/versioned/next/how-to/background-jobs.md +296 -0
  176. package/src/docs/versioned/next/how-to/build-admin-screens.md +42 -13
  177. package/src/docs/versioned/next/how-to/charts-and-dashboards.md +37 -0
  178. package/src/docs/versioned/next/how-to/circleci.md +136 -27
  179. package/src/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +36 -0
  180. package/src/docs/versioned/next/how-to/configure-better-auth.md +4 -0
  181. package/src/docs/versioned/next/how-to/create-a-terreno-app.md +154 -0
  182. package/src/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +2 -0
  183. package/src/docs/versioned/next/how-to/github-actions-ci.md +4 -3
  184. package/src/docs/versioned/next/how-to/github-issue-lifecycle.md +12 -8
  185. package/src/docs/versioned/next/how-to/in-app-notifications.md +147 -0
  186. package/src/docs/versioned/next/how-to/install-agent-skills.md +24 -12
  187. package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +19 -3
  188. package/src/docs/versioned/next/how-to/product-announcements.md +155 -0
  189. package/src/docs/versioned/next/how-to/run-mongodb-migrations.md +127 -0
  190. package/src/docs/versioned/next/how-to/run-tests-locally.md +102 -0
  191. package/src/docs/versioned/next/how-to/seed-a-database.md +7 -2
  192. package/src/docs/versioned/next/implementationPlans/README.md +8 -5
  193. package/src/docs/versioned/next/implementationPlans/admin-scripts-durable-jobs.md +44 -0
  194. package/src/docs/versioned/next/implementationPlans/agent-ui-blocks.md +386 -0
  195. package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +11 -8
  196. package/src/docs/versioned/next/implementationPlans/announcements-surfaces.md +231 -0
  197. package/src/docs/versioned/next/implementationPlans/announcements.md +289 -0
  198. package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +3 -1
  199. package/src/docs/versioned/next/implementationPlans/charts-and-dashboards.md +246 -0
  200. package/src/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +4 -3
  201. package/src/docs/versioned/next/implementationPlans/create-terreno-app.md +177 -0
  202. package/src/docs/versioned/next/implementationPlans/datatable-server-side-filtering.md +219 -0
  203. package/src/docs/versioned/next/implementationPlans/examples-demo-coverage.md +17 -17
  204. package/src/docs/versioned/next/implementationPlans/framework-audit-log.md +247 -0
  205. package/src/docs/versioned/next/implementationPlans/gpt-chat-mascot.md +40 -0
  206. package/src/docs/versioned/next/implementationPlans/job-queues.md +330 -0
  207. package/src/docs/versioned/next/implementationPlans/knip-cleanup.md +136 -0
  208. package/src/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +210 -0
  209. package/src/docs/versioned/next/implementationPlans/mongodb-migrations-tooling.md +254 -0
  210. package/src/docs/versioned/next/implementationPlans/notification-center.md +185 -0
  211. package/src/docs/versioned/next/implementationPlans/org-management-ui.md +8 -3
  212. package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
  213. package/src/docs/versioned/next/reference/README.md +4 -0
  214. package/src/docs/versioned/next/reference/admin-backend.md +83 -5
  215. package/src/docs/versioned/next/reference/admin-config.md +7 -0
  216. package/src/docs/versioned/next/reference/admin-frontend.md +178 -5
  217. package/src/docs/versioned/next/reference/admin-spa.md +18 -1
  218. package/src/docs/versioned/next/reference/ai.md +2 -0
  219. package/src/docs/versioned/next/reference/announcements.md +179 -0
  220. package/src/docs/versioned/next/reference/api.md +343 -4
  221. package/src/docs/versioned/next/reference/comms.md +33 -0
  222. package/src/docs/versioned/next/reference/create-terreno-app.md +44 -0
  223. package/src/docs/versioned/next/reference/environment-variables.md +29 -0
  224. package/src/docs/versioned/next/reference/jobs.md +319 -0
  225. package/src/docs/versioned/next/reference/legacy/rtk.md +5 -1
  226. package/src/docs/versioned/next/reference/lifecycle-plugin.md +29 -11
  227. package/src/docs/versioned/next/reference/mcp-server.md +2 -43
  228. package/src/docs/versioned/next/reference/scan-plugin.md +169 -0
  229. package/src/docs/versioned/next/reference/syncdb.md +38 -12
  230. package/src/docs/versioned/next/reference/test.md +57 -6
  231. package/src/docs/versioned/next/reference/ui-performance.md +2 -1
  232. package/src/docs/versioned/next/reference/ui.md +257 -3
  233. package/src/docs/versioned/next/tasks/admin-scripts-durable-jobs.md +10 -0
  234. package/src/docs/versioned/next/tasks/agent-ui-blocks.md +114 -0
  235. package/src/docs/versioned/next/tasks/announcements-surfaces.md +101 -0
  236. package/src/docs/versioned/next/tasks/announcements.md +173 -0
  237. package/src/docs/versioned/next/tasks/charts-and-dashboards.md +120 -0
  238. package/src/docs/versioned/next/tasks/create-terreno-app.md +82 -0
  239. package/src/docs/versioned/next/tasks/datatable-server-side-filtering.md +87 -0
  240. package/src/docs/versioned/next/tasks/examples-demo-coverage.md +123 -17
  241. package/src/docs/versioned/next/tasks/framework-audit-log.md +86 -0
  242. package/src/docs/versioned/next/tasks/gpt-chat-mascot.md +13 -0
  243. package/src/docs/versioned/next/tasks/job-queues.md +144 -0
  244. package/src/docs/versioned/next/tasks/knip-cleanup.md +146 -0
  245. package/src/docs/versioned/next/tasks/migrate-admin-to-syncdb.md +209 -0
  246. package/src/docs/versioned/next/tasks/mongodb-migrations-tooling.md +107 -0
  247. package/src/docs/versioned/next/tasks/notification-center.md +102 -0
  248. package/src/docs/versioned/next/tasks/org-management-ui.md +14 -14
  249. package/src/docs/versioned/next/tutorials/README.md +1 -1
  250. package/src/docs/versioned/next/tutorials/getting-started.md +62 -22
@@ -0,0 +1,319 @@
1
+ # @terreno/jobs
2
+
3
+ Durable background jobs plugin for `@terreno/api`. Persists work in MongoDB, runs handlers
4
+ with retries and schedules, and supports pluggable dispatch (`MongoJobRunner`, GCP Cloud
5
+ Tasks, Vercel Queues, custom).
6
+
7
+ Operator guide: [Durable background jobs](../how-to/background-jobs.md).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ bun add @terreno/jobs
13
+ ```
14
+
15
+ Peers: `mongoose ^8.0.0 || ^9.0.0`; optional `@google-cloud/tasks`, `@vercel/queue`.
16
+
17
+ ## Package exports
18
+
19
+ | Import | Contents |
20
+ | --- | --- |
21
+ | `@terreno/jobs` | `JobsApp`, `getJobsService`, `tryGetJobsService`, `unregisterJobsService`, `Job`, `JobSchedule`, types, `MongoJobRunner` |
22
+ | `@terreno/jobs/runners/gcpCloudTasks` | `GcpCloudTasksRunner`, client seams |
23
+ | `@terreno/jobs/runners/vercelQueues` | `VercelQueuesRunner`, `createVercelQueuesConsumer`, limits/helpers |
24
+
25
+ ## JobsApp
26
+
27
+ `JobsApp` implements `TerrenoPlugin` and `JobsRunnerHost`.
28
+
29
+ ### Options (`JobsAppOptions`)
30
+
31
+ | Option | Default | Description |
32
+ | --- | --- | --- |
33
+ | `basePath` | `"/jobs"` | Admin routes and execute route prefix |
34
+ | `runner` | `MongoJobRunner` | Dispatch + optional poll loop |
35
+ | `timezone` | `"UTC"` | Default IANA zone for schedules without `schedule.timezone` |
36
+ | `lockTtlMs` | `900_000` (15 min) | Stale `lockedAt` reclaim threshold |
37
+ | `pollIntervalMs` | `1_000` | Mongo runner idle sleep between polls |
38
+ | `accessControl` | — | Terreno RBAC; jobs admin needs `admin:access` **and** `admin:jobs` |
39
+ | `redactPayload` | — | Admin payload projection hook (see below) |
40
+ | `mountExecuteRoute` | `false` | Force `POST {basePath}/execute` |
41
+ | `executeAuth` | — | Required when execute route is mounted |
42
+
43
+ Execute route mounts when `mountExecuteRoute: true` **or** `runner.requiresExecuteRoute === true`.
44
+
45
+ ### Methods
46
+
47
+ | Method | Description |
48
+ | --- | --- |
49
+ | `define(name, definition)` | Register handler, retry, and optional schedule |
50
+ | `register(app, openApi?)` | Mount admin + execute routes; register `JobsService` |
51
+ | `startWorker()` | Start runner `start()` (fire-and-forget); reconciles schedules first |
52
+ | `stopWorker()` | Abort signal + `runner.stop()` |
53
+ | `executeQueuedJob(jobId, signal?)` | Run by id (queue consumer / tests) |
54
+ | `adminContribution()` | Admin screen + home widget metadata |
55
+
56
+ ## JobDefinition
57
+
58
+ ```typescript
59
+ interface JobDefinition {
60
+ handler: (payload: unknown, ctx: JobHandlerContext) => Promise<void>;
61
+ retry?: {
62
+ backoffMs?: number; // default 1_000
63
+ maxAttempts?: number; // default 5
64
+ maxBackoffMs?: number; // default 900_000
65
+ };
66
+ schedule?: {
67
+ cron: string;
68
+ timezone?: string; // IANA; falls back to JobsApp timezone
69
+ };
70
+ }
71
+ ```
72
+
73
+ `JobHandlerContext`: `{jobId, log: ScopedLogger, signal: AbortSignal}`.
74
+
75
+ ## Enqueue (`EnqueueJobParams`)
76
+
77
+ | Field | Required | Description |
78
+ | --- | --- | --- |
79
+ | `name` | yes | Registered handler name |
80
+ | `payload` | yes | JSON-serializable mixed |
81
+ | `runAt` | no | Earliest claim time (default now, UTC) |
82
+ | `idempotencyKey` | no | Dedupes per `name` |
83
+ | `scheduleId` | no | Set by scheduler for cron children |
84
+
85
+ `getJobsService().enqueue(...)` after `JobsApp.register()`. `tryGetJobsService()` returns
86
+ `undefined` when `JobsApp` is not mounted. `unregisterJobsService()` clears the process
87
+ singleton (tests).
88
+
89
+ ### Admin scripts (`admin/script`)
90
+
91
+ `@terreno/admin-backend` `defineAdminScriptJob` registers handler name `admin/script` with
92
+ `retry.maxAttempts: 1`. Payload: `{scriptName, taskId, wetRun, args, createdByName?}`.
93
+ The Scripts UI still polls `BackgroundTask` by `taskId`. Without `JobsApp`, script HTTP
94
+ runs stay in-process.
95
+
96
+ ## JobRunner
97
+
98
+ ```typescript
99
+ interface JobRunner {
100
+ readonly id: string;
101
+ readonly requiresExecuteRoute?: boolean;
102
+ enqueue(job: JobDocument): Promise<void>;
103
+ start?(options: JobRunnerStartOptions): Promise<void>;
104
+ stop?(): Promise<void>;
105
+ }
106
+ ```
107
+
108
+ Built-in ids: `mongo`, `gcp-cloud-tasks`, `vercel-queues`.
109
+
110
+ ## Job model (`Job`)
111
+
112
+ | Field | Default | Description |
113
+ | --- | --- | --- |
114
+ | `name` | — | Handler name (indexed) |
115
+ | `status` | — | See statuses below |
116
+ | `payload` | — | Handler input |
117
+ | `payloadRedacted` | `false` | Admin omission flag |
118
+ | `runAt` | — | Earliest claim (indexed) |
119
+ | `attemptCount` | `0` | Executions so far |
120
+ | `attempts` | `[]` | `{at, error?, errorClass?}` |
121
+ | `maxAttempts` | `5` | From definition at enqueue |
122
+ | `backoffMs` | `1_000` | Retry base |
123
+ | `maxBackoffMs` | `900_000` | Retry cap |
124
+ | `lastError` | — | Latest failure message; `$unset` when the job completes |
125
+ | `lockedAt` / `lockedBy` | — | Claim lease |
126
+ | `idempotencyKey` | — | Partial unique with `name` |
127
+ | `scheduleId` | — | Parent schedule ref |
128
+ | `retriedFromId` / `retriedById` | — | Admin retry linkage |
129
+ | `created` / `updated` | plugins | Timestamps |
130
+
131
+ Indexes: `(name, idempotencyKey)` partial unique; `(status, runAt)`; `(scheduleId, status)`.
132
+
133
+ ### Statuses
134
+
135
+ | Status | Meaning |
136
+ | --- | --- |
137
+ | `pending` | Waiting for `runAt`; claimable |
138
+ | `running` | Claimed; handler executing |
139
+ | `scheduled` | Reserved in schema / in-flight filter |
140
+ | `completed` | Success |
141
+ | `dead` | Exhausted `maxAttempts` |
142
+ | `failed` | No handler registered at run time |
143
+ | `cancelled` | Admin cancel |
144
+
145
+ ### Retry backoff
146
+
147
+ `computeRetryDelayMs`: `backoffMs * 2^(attemptCount-1)`, capped at `maxBackoffMs`, × equal jitter in `[0.5, 1.0)`.
148
+
149
+ ### Claim / lock
150
+
151
+ `createClaimLock` sets `lockedAt`, `lockedBy` (worker id). Claims match `status ∈ {pending,running,scheduled}`, `runAt <= now`, and expired or missing lock. `executeJobById` returns `noop` for terminal rows, `409` conflict for live lock or not-due.
152
+
153
+ ## JobSchedule model
154
+
155
+ | Field | Default | Description |
156
+ | --- | --- | --- |
157
+ | `name` | — | Unique; matches `define` name |
158
+ | `handlerName` | — | Same as `name` |
159
+ | `cron` | — | Standard cron expression |
160
+ | `timezone` | `UTC` | IANA zone |
161
+ | `enabled` | `true` | Pause via admin API |
162
+ | `nextRunAt` | — | Next tick (UTC instant) |
163
+
164
+ ## HTTP routes
165
+
166
+ Base: `{basePath}` (default `/jobs`). All admin routes require auth + jobs admin permission.
167
+
168
+ | Method | Path | Description |
169
+ | --- | --- | --- |
170
+ | GET | `/jobs` | Paginated list (`page`, `limit`, `name`, `status`, `scheduleId`, `start`, `end`, `q`) |
171
+ | GET | `/jobs/stats` | Counts by status |
172
+ | GET | `/jobs/:id` | Detail |
173
+ | POST | `/jobs/:id/retry` | New linked row from `dead` / `failed` |
174
+ | POST | `/jobs/:id/requeue` | Reset same row from `dead` / `failed` / `cancelled` |
175
+ | POST | `/jobs/:id/cancel` | Cancel `pending` / `running` / `scheduled` |
176
+ | GET | `/jobs/schedules` | All schedules |
177
+ | POST | `/jobs/schedules/:name/pause` | `enabled: false` |
178
+ | POST | `/jobs/schedules/:name/resume` | `enabled: true` |
179
+ | POST | `/jobs/execute` | Internal dispatch (`{jobId}`); `executeAuth` required |
180
+
181
+ List defaults: `limit` 50, max 200. Execute uses 30-minute handler timeout.
182
+
183
+ ### RBAC
184
+
185
+ When `accessControl` is configured, every jobs admin route checks **both**
186
+ `admin:access` and `admin:jobs` (`accessControl.can({permissions: {admin: ["access", "jobs"]}})`).
187
+ Default `terrenoStatements.admin` includes both actions. When `accessControl` is omitted,
188
+ routes fall back to legacy `user.admin === true`.
189
+
190
+ ### Admin payload redaction
191
+
192
+ `JobsAdminRedactPayload`:
193
+
194
+ ```typescript
195
+ type JobsAdminRedactPayload = (input: {
196
+ name: string;
197
+ payload: unknown;
198
+ payloadRedacted: boolean;
199
+ }) => unknown | undefined;
200
+ ```
201
+
202
+ **Default (omit hook):** `payload` is not included in admin list/detail JSON.
203
+
204
+ **Opt-in projection** — return only fields operators need; raw identity passthrough is
205
+ discouraged:
206
+
207
+ ```typescript
208
+ new JobsApp({
209
+ redactPayload: ({name, payload}) => {
210
+ if (name === "billing/capture") {
211
+ return {invoiceId: (payload as {invoiceId?: string}).invoiceId};
212
+ }
213
+ return undefined; // hide unknown job types
214
+ },
215
+ });
216
+ ```
217
+
218
+ Return `undefined` to hide payload for a row. `payloadRedacted: true` on the document
219
+ always hides payload regardless of the hook.
220
+
221
+ ## Admin frontend widgets
222
+
223
+ `@terreno/admin-frontend` public exports for jobs:
224
+
225
+ | Export | Role |
226
+ | --- | --- |
227
+ | `JOBS_ADMIN_WIDGETS`, `JobsDashboardScreenWidget` | Screen widget map (`jobs` → dashboard) |
228
+ | `JOBS_HOME_WIDGETS`, `JobsHomeWidget` | Home widget (`dead` / `running` counts) |
229
+ | `JobsDashboardScreen`, `JobsJobDetail` | Full screens (Expo admin routes) |
230
+ | `JobsStatusBadge` | Status chip |
231
+ | `canCancelJob`, `canRequeueJob`, `canRetryJob`, `formatJobTimestamp`, `jobRowId`, `unwrapJobRow` | Row helpers |
232
+ | `JobRow`, `JobAttemptRow`, `JobScheduleRow`, `JobsStats` | Row/types for tables |
233
+ | `JobsDashboardFilters`, `parseJobsDashboardSearchParams`, `serializeJobsDashboardSearchParams` | URL filter helpers |
234
+
235
+ Widgets are registered in the built-in registry — enable via `JobsApp.adminContribution()` only.
236
+ The admin shell lists Jobs under **Platform** (not Screens). Hosts still contribute
237
+ `name: "jobs"`; do not add a second sidebar link.
238
+ Custom screens should pass the app's generated RTK `api` into `AdminProvider`; jobs screens wire
239
+ admin HTTP internally (not via a public `useJobsDashboardApi` export).
240
+
241
+ ## GCP Cloud Tasks (`@terreno/jobs/runners/gcpCloudTasks`)
242
+
243
+ ### `GcpCloudTasksRunnerConfig`
244
+
245
+ | Field | Required | Description |
246
+ | --- | --- | --- |
247
+ | `project` | yes | GCP project id |
248
+ | `location` | yes | Queue region |
249
+ | `queue` | yes | Queue name |
250
+ | `publicUrl` | yes | Absolute `http(s)` API origin (no trailing slash) |
251
+ | `serviceAccountEmail` | yes | OIDC invoker SA |
252
+ | `oidcAudience` | no | Defaults to execute URL |
253
+ | `basePath` | no | Default `/jobs` |
254
+ | `dispatchDeadlineSeconds` | no | Cloud Tasks HTTP wait before retry. Range 15–1800. Default **1800**. The runner sends protobuf `{seconds}` (what `@google-cloud/tasks` expects); REST encodes `"1800s"` so a 30-minute Cloud Run handler is not retried at the 10-minute HTTP default |
255
+ | `client` | no | Injected Cloud Tasks client. Required in `bun build --compile` images (REST + `google-auth-library` in the example app). Optional peer `createRequire` fallback for Node. |
256
+
257
+ Enqueue: `createTask` POST to `{publicUrl}{basePath}/execute`, body `{jobId}` base64,
258
+ `dispatchDeadline` `{seconds: 1800}` by default (REST JSON `"1800s"`), `scheduleTime` when
259
+ `runAt` is future.
260
+ `requiresExecuteRoute: true`.
261
+ `start()` ticks due Mongo schedules into `enqueue`; it does not claim job rows.
262
+
263
+ **Operator responsibility:** implement `executeAuth` to verify the OIDC token (issuer,
264
+ audience, SA email). `@terreno/jobs` does not ship a GCP verifier.
265
+
266
+ ## Vercel Queues (`@terreno/jobs/runners/vercelQueues`)
267
+
268
+ ### `VercelQueuesRunnerConfig`
269
+
270
+ | Field | Default | Description |
271
+ | --- | --- | --- |
272
+ | `topic` | required | Queue topic name |
273
+ | `region` | — | Optional send region |
274
+ | `retentionSeconds` | `86400` | Min 60, max 604800 |
275
+ | `send` | — | Test seam |
276
+
277
+ Constants: `VERCEL_QUEUE_MAX_DELAY_SECONDS` = 604800 (7d); visibility retry clamp 30–3600s.
278
+
279
+ Enqueue: `send(topic, {jobId}, {delaySeconds?, retentionSeconds?, idempotencyKey})`.
280
+ `runAt` beyond 7 days throws `VercelQueueRunAtBeyondLimitError`.
281
+
282
+ `createVercelQueuesConsumer({host, handleCallback?, signal?, visibilityTimeoutSeconds?})` wraps
283
+ `@vercel/queue` `handleCallback` → `executeJobById`. `requiresExecuteRoute: false`.
284
+
285
+ `vercel.json`: `"experimentalTriggers": [{"type": "queue/v2beta", "topic": "<topic>"}]`.
286
+
287
+ ## Environment variables
288
+
289
+ `@terreno/jobs` uses **explicit constructor options**, not env readers.
290
+
291
+ | Variable | Read by | Notes |
292
+ | --- | --- | --- |
293
+ | `JOBS_START_WORKER` | example-backend only | `true`/`false`; unset → `true`. Disable in API when running `jobs:worker`. |
294
+ | `JOB_TRACE_LOGS` | app convention | Gates verbose worker logs via `createFeatureFlaggedLogger` (not read by jobs package) |
295
+
296
+ Legacy `GCP_TASKS_NOTIFICATIONS_QUEUE` / `GCP_TASK_PROCESSOR_QUEUE` appear in example-backend **test** fixtures only — not used by `@terreno/jobs`. Configure Cloud Tasks via `GcpCloudTasksRunner` constructor args.
297
+
298
+ ## Process-local alternatives
299
+
300
+ | API | Package | Durable | Use |
301
+ | --- | --- | --- | --- |
302
+ | `cronjob()` | `@terreno/api` | No | In-process recurring timer |
303
+ | `wrapScript()` | `@terreno/api` | No | One-shot CLI / script wrapper |
304
+ | `JobsApp` | `@terreno/jobs` | Yes | Retries, DLQ, schedules, admin, cloud dispatch |
305
+
306
+ ## Commands (monorepo)
307
+
308
+ ```bash
309
+ bun run jobs:compile
310
+ bun run jobs:lint
311
+ bun run jobs:test
312
+ bun run jobs:worker # example-backend standalone worker
313
+ ```
314
+
315
+ ## Related
316
+
317
+ - [Durable background jobs how-to](../how-to/background-jobs.md)
318
+ - [API reference — wrapScript / cronjob](api.md#wrapscript)
319
+ - [Environment variables](environment-variables.md#background-jobs)
@@ -2,7 +2,11 @@
2
2
 
3
3
  > **Deprecated for data synchronization.** `@terreno/rtk` remains published through the **56.x** line (and the stable **0.x** line) with a deprecation notice. It will **not** be published in the next major Terreno release. For collection reads/writes, offline sync, and realtime convergence, use [`@terreno/syncdb`](../syncdb.md) and the [migration guide](../../how-to/migrate-rtk-to-syncdb.md).
4
4
  >
5
- > RTK Query is still the correct tool for **non-synced** endpoints: generated OpenAPI hooks (`/auth/me`, admin RPC, AI routes), Better Auth session Redux wiring, feature flags, and sockets.
5
+ > RTK Query remains the correct tool for **non-synced** generated OpenAPI routes
6
+ > such as `/auth/me` and AI APIs, Better Auth session Redux wiring, feature
7
+ > flags, and sockets. Built-in admin RPC uses host-bound fetch; ObjectId/API-only
8
+ > admin CRUD keeps RTK compatibility in Terreno 57, then `useAdminApi` and the
9
+ > required admin `api` prop are removed in Terreno 58.
6
10
 
7
11
  Redux Toolkit Query utilities for frontends using @terreno/api backends. JWT auth, token storage, SDK code generation from OpenAPI, and real-time WebSocket connections.
8
12
 
@@ -1,6 +1,6 @@
1
1
  # Lifecycle plugin reference
2
2
 
3
- Plugin: `terreno-planning` (`2.8.0`)
3
+ Plugin: `terreno-planning` (`2.12.0`)
4
4
 
5
5
  Planning skills are model-invocable: agents may select them from descriptions, not only
6
6
  from slash commands. Grow, Brew, and Taste each implement one bounded transition. Pick continues an inner loop until the
@@ -10,11 +10,11 @@ loops that invoke those stages; they are not stages and must not appear as `stag
10
10
 
11
11
  | Skill | Preconditions | Primary output | PASS next |
12
12
  | --- | --- | --- | --- |
13
- | `terreno-1-grow` | request/spec + repository | approved IP/tasks + criterion/verification map | Pick (enters inner loop) |
13
+ | `terreno-1-grow` | request/spec + repository | approved IP/tasks + criterion/verification map + standalone approval brief | Pick (enters inner loop) |
14
14
  | `terreno-2-pick` | approved task + branch/state | one implemented slice, then Roast, then the next task | Roast, or Brew when the list is done |
15
15
  | `terreno-3-roast` | Pick result + current diff | independent requirement/evidence verdict for the current task | emit Pick if tasks remain, else Brew; never invoke Pick; pass a task-scoped briefing; do not spawn two unconstrained reviewers |
16
16
  | `terreno-4-brew` | Roast PASS for every in-scope task + branch/evidence | pushed head + PR + product-CI trigger check + review-bot wait + attached evidence | Taste |
17
- | `terreno-5-taste` | PR + current state | one current-head reaction; before push: pull latest `master`, lint and typecheck in a no-context subagent, then watch CI | null or fresh Taste |
17
+ | `terreno-5-taste` | PR + current state | one current-head reaction; before push: pull latest `master`, re-verify last-run failed tests locally, run root `prepush` when present (otherwise affected-package checks) in a no-context subagent, then watch CI | null or fresh Taste |
18
18
 
19
19
  Outer loops (not stages):
20
20
 
@@ -39,6 +39,8 @@ Results use `PASS`, `FAIL`, `BLOCKED`, or `PENDING` and the compact `v: 2` schem
39
39
  (`v`, `stage`, `status`, `next`, `action`; omit empty keys). Loop state follows
40
40
  [`execution-state.schema.json`](https://github.com/FlourishHealth/terreno/blob/master/plugins/terreno-planning/references/execution-state.schema.json).
41
41
  Chat and PRs show `status` / `next` / `action`; the YAML lives in a Details toggle.
42
+ When the current PR has GitHub Deployments, those demo URLs are the last visible
43
+ section of every wait-for-human or done chat.
42
44
 
43
45
  The outer loop owns state persistence, Taste `PENDING` reinvocation, Grow/Brew/Taste
44
46
  invocation, retries, and escalation. Pick owns the
@@ -49,9 +51,12 @@ until async review bots (Bugbot, CodeQL, and similar) on the current head have r
49
51
  preferring provider CLI watch hooks or harness event subscriptions over sleep polling.
50
52
  Taste then waits in a loop for product CI using GitHub CLI (`gh pr checks --watch`,
51
53
  `gh run watch`) or CircleCI CLI (`circleci run watch`) until jobs are terminal or the
52
- wait times out. Before any push, Taste always pulls latest `master`, then spawns a
53
- fresh subagent with no parent conversation to run lint, typecheck, and the locally
54
- affected tests in each affected package, then pushes and watches product CI.
54
+ wait times out. Before any push, Taste always pulls latest `master`, records last-run
55
+ failed tests and re-verifies them locally, then spawns a
56
+ fresh subagent with no parent conversation. If the repository root defines a `prepush`
57
+ package script, Taste runs it as the authoritative local gate; otherwise it falls back
58
+ to lint, typecheck, and locally affected tests in each affected package. Taste then
59
+ pushes and watches product CI.
55
60
  Taste observes product CI on every discovered host (GitHub Actions, CircleCI,
56
61
  Buildkite, and similar), not only GitHub checks. A documented not-applicable host
57
62
  counts as skipped; an unexplained untriggered host prevents Brew `PASS`. Brew still
@@ -66,11 +71,24 @@ docs, and risks, then presents that report once. It asks the human only
66
71
  for an actual product/architecture/security/data/destructive/policy decision or
67
72
  unreplaceable credential. Before asking, it gives the overall goal/state, completed
68
73
  work, decisive evidence, two to four options with impact, and a recommendation; the
69
- message ends with one exact question.
70
-
71
- GitHub communication follows a fixed attention budget: `Why`, `What changed`, and
72
- `Verification` are the only visible PR sections; optional detail is expandable; comments
73
- are reserved for blocked decisions or non-obvious review resolutions.
74
+ message ends with one exact question, then PR deployment URLs when a PR has them.
75
+
76
+ Grow's human-facing output is a standalone approval brief, not an index. It opens with a
77
+ paragraph on where the repository is and where the change takes it, adds background on
78
+ current state when a reviewer needs it, then the idea, then the plan (tasks, tracer,
79
+ verification, out of scope, risks). A Decisions table follows the plan with no row cap,
80
+ pairing every settled human decision with the question that prompted it; it is omitted
81
+ when grilling settled none. Paths to the IP and task files come last.
82
+
83
+ GitHub communication follows a fixed attention budget. The PR title is
84
+ `[ticket] Short feature title` (Linear `TEAM-n` or GitHub `#n`; omit brackets when
85
+ no ticket is attached) and names the feature only — never `feat:`, `IP Approved`,
86
+ or `Task list`. `Why` preserves the IP's initial justification, `What changed`
87
+ briefly describes the approved IP and intended outcomes, and `Verification` always
88
+ contains reproducible testing instructions. Brew updates verification as testing
89
+ changes but otherwise keeps the body stable instead of summarizing the latest turn.
90
+ These are the only visible PR sections; optional detail is expandable; comments are
91
+ reserved for blocked decisions or non-obvious review resolutions.
74
92
 
75
93
  Every stage follows the
76
94
  [documentation contract](https://github.com/FlourishHealth/terreno/blob/master/plugins/terreno-planning/references/documentation-contract.md):
@@ -107,7 +107,7 @@ Set `TERRENO_MCP_DOCS_DIR` environment variable to override default path.
107
107
 
108
108
  ## Tools
109
109
 
110
- Code generation tools that return TypeScript/JavaScript code as text. **Tools do not write files** — the AI assistant receives the code and writes it to appropriate locations.
110
+ Code generation tools that return TypeScript/JavaScript code as text. They do not write files — the AI assistant receives the code and writes it to appropriate locations. Scaffold a new app with [`create-terreno-app`](create-terreno-app.md) (`bunx create-terreno-app`), not an MCP tool.
111
111
 
112
112
  ### terreno_search_docs
113
113
 
@@ -341,47 +341,6 @@ Validate a Mongoose schema against Terreno conventions.
341
341
  - Recommendations for fixes
342
342
  - Severity levels (error, warning, info)
343
343
 
344
- ### terreno_bootstrap_app
345
-
346
- Scaffold a new full-stack Terreno application (Expo frontend, Express/Mongoose backend, Cursor rules, MCP settings).
347
-
348
- **Parameters:**
349
-
350
- ``````typescript
351
- {
352
- appName: string; // kebab-case (e.g., "my-app")
353
- appDisplayName: string; // Human-readable name
354
- description?: string;
355
- mcpServerUrl?: string; // Default: https://mcp.terreno.app
356
- }
357
- ``````
358
-
359
- **Returns:** File list, setup instructions, and full file contents for backend, frontend, CI workflows, and MCP configuration.
360
-
361
- The generated Profile tab (`frontend/app/(tabs)/profile.tsx`) uses `@terreno/ui` `TapToEdit` for name, email, and password. Each field saves independently with `PATCH /auth/me` (`usePatchMeMutation`). Name and email each have their own `useEffect`, so saving one field does not wipe an in-progress edit on the other.
362
-
363
- The generated app is expected to install and boot with no manual follow-up, so the
364
- scaffold deliberately ships no binary assets and no references to files it does not
365
- create:
366
-
367
- - **Fonts** come from `@terreno/ui`. `TerrenoProvider` wraps children in
368
- `TerrenoFontProvider`, which loads Nunito and Titillium Web from
369
- `@expo-google-fonts/*`. The generated `app/_layout.tsx` calls no `useFonts` of its own.
370
- - **Icon, splash, and favicon** are left unset in `app.json` so Expo uses its built-in
371
- defaults. Point them at real files once the app has branding.
372
- - **`metro.config.js`** pins every `jspdf` request to `jspdf/dist/jspdf.es.min.js` on web
373
- and drops it on native. `@terreno/admin-frontend` pulls jspdf in for consent-PDF export,
374
- and jspdf's CommonJS and Node builds contain an AMD-style `require(["html2canvas"], cb)`
375
- call that Metro's static transform cannot parse — without the override it fails the whole
376
- bundle, including Expo Router's static web render.
377
- - **Auth-gated routes** use `<Stack.Protected guard={...}>`. Wrapping `Stack.Screen`
378
- children in a conditional or fragment instead crashes the navigator.
379
- - **`tsconfig.json`** sets no `baseUrl`; it is deprecated in TypeScript 6 and makes `tsc`
380
- abort with TS5101 before checking a single file. `paths` resolve relative to the tsconfig.
381
- - **Declared dependencies** include every package generated frontend source imports
382
- (`react-native-reanimated`, `redux-persist`, `@reduxjs/toolkit`, `@expo/vector-icons`,
383
- `lodash`, `luxon`, and the rest). Do not rely on transitive installs for those.
384
-
385
344
  ### terreno_bootstrap_ai_rules
386
345
 
387
346
  Scaffold AI coding assistant rules (AGENTS.md, Cursor/Windsurf rules, Copilot instructions, rulesync config).
@@ -416,7 +375,7 @@ Multi-step workflow prompts that guide AI assistants through complex tasks.
416
375
 
417
376
  ### terreno_bootstrap
418
377
 
419
- Workflow prompt for scaffolding a new Terreno app. Delegates to `terreno_bootstrap_app` and `terreno_bootstrap_ai_rules` tools.
378
+ Workflow prompt for scaffolding a new Terreno app. Instructs the assistant to run `bunx create-terreno-app`, then `terreno_bootstrap_ai_rules`.
420
379
 
421
380
  **Arguments:**
422
381
 
@@ -0,0 +1,169 @@
1
+ # Scan plugin reference
2
+
3
+ Plugin: `terreno-scan` (`2.11.0`). Requires the lifecycle plugin
4
+ ([lifecycle plugin reference](lifecycle-plugin.md)).
5
+
6
+ A scan turns a long-term outcome — "cold builds under 30s", "no unbounded reads", "WCAG
7
+ AA on every screen" — into a measured campaign: charter the goal, map-reduce the
8
+ repository into verified findings, slice those findings into IPs and tasks, run each
9
+ slice through Grow/Pick/Roast/Brew/Taste, then re-measure and repeat.
10
+
11
+ Scan stages never edit product code. The lifecycle does.
12
+
13
+ | Skill | Preconditions | Primary output | PASS next |
14
+ | --- | --- | --- | --- |
15
+ | `terreno-scan-1-aim` | goal/outcome + repository | approved charter: metric, measured baseline, dry-run rules, exclusions, validity rule, severity rubric, slice policy, budget | Sweep |
16
+ | `terreno-scan-2-sweep` | approved charter | per-rule hits, deterministic shard manifest, schema-valid candidate findings from parallel workers | Sift |
17
+ | `terreno-scan-3-sift` | candidates for the round | deduped, adversarially verified, ranked findings report plus dropped-candidate summary | Plot, or Track when dry |
18
+ | `terreno-scan-4-plot` | findings report + slice policy | ordered PR-sized slices, one standalone Grow brief each, findings marked `planned` | Track, with `handoff: grow` |
19
+ | `terreno-scan-5-track` | landed slice or dry round | re-measured metric with command output, findings closed/reopened with evidence, round record, next move | Plot, Sweep, or `null` |
20
+
21
+ Outer loops (not stages):
22
+
23
+ | Skill | What it walks | Default |
24
+ | --- | --- | --- |
25
+ | `terreno-scan-campaign` | One goal, round after round | Aim once, then Sweep → Sift → Plot → lifecycle → Track until the metric hits target, the budget is spent, or a genuine human gate is reached. Stops at each gate. |
26
+ | `terreno-scan-loop` | The same, but resident | Keeps the campaign moving **and** heartbeats over every open campaign PR — routing it to a reviewer, answering review comments through Taste, fixing red CI, tracking merges — refilling work as PRs land. Runs until the goal is met, a stop argument fires, or a human decision is required. |
27
+
28
+ ## Resident loop
29
+
30
+ `terreno-scan-loop` is the "leave it running" entry point. One session holds the goal,
31
+ the state, and the schedule; every unit of work happens in a fresh subagent.
32
+
33
+ Arguments: `goal=<slug>`, `reviewer=<handle[,handle]>`, `assignee=<handle>`, `wip=<n>`,
34
+ `rounds=<n>`, `until=<ISO timestamp>`, `tick=<seconds>`. Overrides apply to the run only
35
+ and are recorded in state as overrides — they never rewrite the charter.
36
+
37
+ Each tick snapshots the campaign's PRs and sorts each into one bucket, then acts in
38
+ priority order:
39
+
40
+ | Bucket | Action |
41
+ | --- | --- |
42
+ | `unrouted` | Apply the charter's routing, then count the slice in flight |
43
+ | `answered` | A human commented or requested changes → one `terreno-5-taste` in a fresh subagent |
44
+ | `broken` | CI failed or the PR conflicts → one `terreno-5-taste` |
45
+ | `merged` | → `terreno-scan-5-track`: re-measure, close findings with evidence |
46
+ | `closed` | Mark the slice abandoned, return its findings to `open`, free its slot |
47
+ | `bot-pending`, `waiting` | Nothing — this is why the next tick exists |
48
+
49
+ Then it refills capacity up to `wipLimit` and schedules the next tick: the provider's
50
+ watch hook while CI runs, 2–5 minutes while review bots report, 15–30 minutes while a
51
+ human holds the PR, and no wait at all while backlog work remains. Ticks that find
52
+ nothing print nothing, and every comment is answered exactly once — answered ids live in
53
+ state. See
54
+ [`heartbeat.md`](https://github.com/FlourishHealth/terreno/blob/master/plugins/terreno-scan/references/heartbeat.md).
55
+
56
+ ## PR routing
57
+
58
+ Aim settles who reviews the campaign's PRs before any PR exists, and records a `review`
59
+ block: `mode` (`fixed`, `codeowners`, `blame`, `round-robin`, `none`), reviewer and
60
+ assignee handles, teams, draft policy, labels, `wipLimit`, `mergePolicy`, and how to
61
+ notify. Handles are verified with `gh api repos/{owner}/{repo}/collaborators/<handle>`
62
+ before use; an unverifiable handle is a human gate, never a silently unassigned PR.
63
+
64
+ Routing is applied right after Brew reports the PR (`gh pr edit --add-reviewer
65
+ --add-assignee --add-label`) and re-requested once when requested changes are addressed.
66
+ The campaign never approves its own PRs, never dismisses a review, and never merges when
67
+ `mergePolicy` is `human` — the default. See
68
+ [`pr-routing.md`](https://github.com/FlourishHealth/terreno/blob/master/plugins/terreno-scan/references/pr-routing.md).
69
+
70
+ ## Stage anatomy
71
+
72
+ Every stage includes Preconditions, Inputs, Procedure, Supporting skills, Evidence
73
+ produced, Success/Failure/Blocked conditions, and a recommended next stage — the same
74
+ shape as the lifecycle stages.
75
+
76
+ Results use `PASS`, `FAIL`, `BLOCKED`, or `PENDING` and the compact `v: 2`
77
+ [`scan-result.schema.json`](https://github.com/FlourishHealth/terreno/blob/master/plugins/terreno-scan/references/scan-result.schema.json)
78
+ (`v`, `stage`, `status`, `next`, `action`; omit empty keys). Campaign state follows
79
+ [`scan-state.schema.json`](https://github.com/FlourishHealth/terreno/blob/master/plugins/terreno-scan/references/scan-state.schema.json)
80
+ and findings follow
81
+ [`finding.schema.json`](https://github.com/FlourishHealth/terreno/blob/master/plugins/terreno-scan/references/finding.schema.json).
82
+ Chat shows `status` / `next` / `action` plus the current metric; the YAML lives in a
83
+ Details toggle.
84
+
85
+ ## Map-reduce
86
+
87
+ Sweep shards rule hits into batches (default: at most 25 files or 80 hits per shard,
88
+ never splitting a file) and maps one fresh worker per shard. Workers get a shard
89
+ briefing — goal, rules, validity rule, severity rubric, file list, hit lines, allowed
90
+ commands, and the return schema — and nothing else. They never search the repository at
91
+ large, never spawn nested agents, and never edit files. Sift then dedupes by
92
+ `rule + path + symbol`, re-runs evidence, refutes every high-severity candidate before
93
+ keeping it, drops what does not reproduce, and ranks by impact × confidence ÷ effort.
94
+ Dropped shards and capped coverage are always reported; silent truncation is a defect.
95
+ See
96
+ [`mapreduce.md`](https://github.com/FlourishHealth/terreno/blob/master/plugins/terreno-scan/references/mapreduce.md).
97
+
98
+ ## Goal and metric
99
+
100
+ Aim is not finished until the metric has a command that prints a number and the baseline
101
+ came from running it. Track re-runs that command with the charter's protocol every round
102
+ and records the value with its output. A finding that cannot be tied to the metric is
103
+ dismissed in Sift, not deferred. Re-baselining without recording both values, the reason,
104
+ and the round is untrustworthy and Track says so. See
105
+ [`goal-tracking.md`](https://github.com/FlourishHealth/terreno/blob/master/plugins/terreno-scan/references/goal-tracking.md).
106
+
107
+ ## Artifacts
108
+
109
+ | Artifact | Default path | Committed |
110
+ | --- | --- | --- |
111
+ | Charter | `docs/scans/<goal-slug>/charter.md` | yes |
112
+ | Findings report | `docs/scans/<goal-slug>/round-<n>-findings.md` | yes |
113
+ | Shard manifest, candidates, slice briefs | round artifact directory | per repository policy |
114
+ | Campaign state | `.terreno/scan/<goal-slug>.json` | no (git-ignored) |
115
+ | Slice IP and tasks | the repository's existing IP/task convention, written by Grow | yes |
116
+
117
+ An existing repository convention always wins over these defaults.
118
+
119
+ ## State machine
120
+
121
+ ```text
122
+ Aim PASS → Sweep → Sift
123
+ Sift PASS (findings) → Plot → handoff: grow
124
+ Sift PASS (dry round) → Track
125
+ Plot PASS → Grow → Pick/Roast inner loop → Brew → Taste → Track
126
+ Track PASS (backlog) → Plot
127
+ Track PASS (empty) → Sweep
128
+ Track PASS (target/budget/regression) → null, report and ask
129
+ Track PENDING (slice still in flight) → campaign waits → fresh Track
130
+ Any BLOCKED → named human/external gate
131
+ ```
132
+
133
+ The campaign loop owns persistence, retry, waiting, stop, and escalation. It never
134
+ implements a finding itself, never commits, and never opens a PR — Plot hands each slice
135
+ to Grow, and the lifecycle takes it from there.
136
+
137
+ ## Hosts
138
+
139
+ | Host | Plugin | Invoke Aim | Invoke the campaign |
140
+ | --- | --- | --- | --- |
141
+ | Cursor | `terreno-scan` | `/terreno-scan-1-aim` | `/terreno-scan-campaign` |
142
+ | Codex | `terreno-scan` | `$terreno-scan-1-aim` | `$terreno-scan-campaign` |
143
+ | Claude Code | `terreno-scan` | `/terreno-scan:1-aim` | `/terreno-scan:campaign` |
144
+
145
+ The resident loop is `/terreno-scan-loop` (Cursor), `$terreno-scan-loop` (Codex), and
146
+ `/terreno-scan:loop` (Claude Code).
147
+
148
+ `plugins/terreno-scan/` is canonical. `plugins/terreno-scan-claude/` is generated with
149
+ shortened skill names; regenerate it with `bun run skills:sync` and never hand-edit it.
150
+
151
+ ## Example
152
+
153
+ ```text
154
+ /terreno-scan:loop reduce cold compile time reviewer=octocat wip=2
155
+ ```
156
+
157
+ Aim grills the target (30s), measures the baseline with `bun run compile` on a clean
158
+ tree, approves four rules, and records `octocat` as reviewer with a two-PR limit. Sweep
159
+ shards 180 hits into nine batches and maps nine workers. Sift keeps 12 of 31 candidates,
160
+ refuting four high-severity claims that did not reproduce. Plot writes three slices; the
161
+ loop runs the first through Grow, Pick/Roast, and Brew, then routes the PR to `octocat`.
162
+ While that PR waits, the loop opens the second slice up to the limit and then goes quiet,
163
+ ticking every 20 minutes. `octocat` requests a change at 14:05; the next tick hands that
164
+ PR to Taste, which replies, fixes, runs the pre-push gate, pushes, and watches CI. When
165
+ the PR merges, Track re-measures at 48.2s (from 58.6s), marks five findings fixed, and
166
+ the loop refills capacity with the third slice.
167
+
168
+ Stop it with a plain "stop" — it leaves every PR routed, state consistent, and reports
169
+ what is in flight and who holds it.