@theodo/bernard 0.1.0-beta.0 → 0.1.0-beta.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 (260) hide show
  1. package/README.md +13 -12
  2. package/core/README.md +1 -1
  3. package/core/agents/review-quality.md +13 -12
  4. package/core/agents/review-security-bugs.md +1 -1
  5. package/core/agents/review-tests.md +1 -1
  6. package/core/coder/template/main.tf +14 -1
  7. package/core/skills/pull-request/SKILL.md +17 -7
  8. package/core/skills/pull-request/ci-fixer.md +3 -3
  9. package/dist/bin.mjs +4979 -3176
  10. package/dist/{config-BawD0TwQ.mjs → config-DtCwJKWe.mjs} +70 -6
  11. package/dist/config.d.mts +72 -4
  12. package/dist/config.mjs +1 -1
  13. package/dist/docs/404.html +23 -0
  14. package/dist/docs/CLAUDE.html +30 -0
  15. package/dist/docs/CONTEXT.html +30 -0
  16. package/dist/docs/CONTRIBUTING.html +62 -0
  17. package/dist/docs/assets/CLAUDE.md.BLSB-5gK.js +1 -0
  18. package/dist/docs/assets/CLAUDE.md.BLSB-5gK.lean.js +1 -0
  19. package/dist/docs/assets/CONTEXT.md.BovnvMGK.js +1 -0
  20. package/dist/docs/assets/CONTEXT.md.BovnvMGK.lean.js +1 -0
  21. package/dist/docs/assets/CONTRIBUTING.md.DwJsKrz3.js +33 -0
  22. package/dist/docs/assets/CONTRIBUTING.md.DwJsKrz3.lean.js +1 -0
  23. package/dist/docs/assets/app.URmpjWHb.js +312 -0
  24. package/dist/docs/assets/bernard.RCgrvDdX.svg +94 -0
  25. package/dist/docs/assets/chunks/@localSearchIndexroot.Ylq685Cw.js +1 -0
  26. package/dist/docs/assets/chunks/VPLocalSearchBox.C8gcRVj0.js +9 -0
  27. package/dist/docs/assets/chunks/dagre-GXQ25YYZ.2qvw8AqH.js +4 -0
  28. package/dist/docs/assets/chunks/framework.hgjl6W6o.js +20 -0
  29. package/dist/docs/assets/chunks/sizeCapture-INFHLROL.BEqL2zFi.js +1 -0
  30. package/dist/docs/assets/chunks/stateDiagram-v2-MP3YSRHH.3X7wNXKX.js +231 -0
  31. package/dist/docs/assets/chunks/theme.Cksh_UJH.js +2 -0
  32. package/dist/docs/assets/chunks/virtual_mermaid-config.DDnGl6nM.js +1 -0
  33. package/dist/docs/assets/core_README.md.DyxYsikc.js +15 -0
  34. package/dist/docs/assets/core_README.md.DyxYsikc.lean.js +1 -0
  35. package/dist/docs/assets/demo_README.md.Cpi2pUU9.js +18 -0
  36. package/dist/docs/assets/demo_README.md.Cpi2pUU9.lean.js +1 -0
  37. package/dist/docs/assets/docs_adr_0001-what-the-core-is-built-for.md.BxOoMjST.js +1 -0
  38. package/dist/docs/assets/docs_adr_0001-what-the-core-is-built-for.md.BxOoMjST.lean.js +1 -0
  39. package/dist/docs/assets/docs_adr_0002-which-harness-a-project-runs-on.md.2GwF-nj0.js +1 -0
  40. package/dist/docs/assets/docs_adr_0002-which-harness-a-project-runs-on.md.2GwF-nj0.lean.js +1 -0
  41. package/dist/docs/assets/docs_adr_0003-how-core-content-reaches-each-harness.md.BMWE00Mf.js +1 -0
  42. package/dist/docs/assets/docs_adr_0003-how-core-content-reaches-each-harness.md.BMWE00Mf.lean.js +1 -0
  43. package/dist/docs/assets/docs_adr_0004-how-the-core-uses-external-tools.md.B7dtE7MB.js +1 -0
  44. package/dist/docs/assets/docs_adr_0004-how-the-core-uses-external-tools.md.B7dtE7MB.lean.js +1 -0
  45. package/dist/docs/assets/docs_adr_0005-what-drives-the-pipeline.md.DGn0CJeq.js +1 -0
  46. package/dist/docs/assets/docs_adr_0005-what-drives-the-pipeline.md.DGn0CJeq.lean.js +1 -0
  47. package/dist/docs/assets/docs_adr_0006-what-steps-hand-off-to-each-other.md.BflT5ugJ.js +1 -0
  48. package/dist/docs/assets/docs_adr_0006-what-steps-hand-off-to-each-other.md.BflT5ugJ.lean.js +1 -0
  49. package/dist/docs/assets/docs_adr_0007-how-humans-take-part-in-the-pipeline.md.CNX3gUD-.js +1 -0
  50. package/dist/docs/assets/docs_adr_0007-how-humans-take-part-in-the-pipeline.md.CNX3gUD-.lean.js +1 -0
  51. package/dist/docs/assets/docs_adr_0008-where-a-steps-reviewers-run.md.aGbaBOpB.js +1 -0
  52. package/dist/docs/assets/docs_adr_0008-where-a-steps-reviewers-run.md.aGbaBOpB.lean.js +1 -0
  53. package/dist/docs/assets/docs_adr_0009-where-pipeline-state-and-handoffs-live.md.C3tKmV-E.js +1 -0
  54. package/dist/docs/assets/docs_adr_0009-where-pipeline-state-and-handoffs-live.md.C3tKmV-E.lean.js +1 -0
  55. package/dist/docs/assets/docs_adr_0010-how-a-project-adapts-the-core.md.Bp7N6tUE.js +1 -0
  56. package/dist/docs/assets/docs_adr_0010-how-a-project-adapts-the-core.md.Bp7N6tUE.lean.js +1 -0
  57. package/dist/docs/assets/docs_adr_0011-how-the-factory-is-packaged-and-versioned.md.BBJIwnqa.js +1 -0
  58. package/dist/docs/assets/docs_adr_0011-how-the-factory-is-packaged-and-versioned.md.BBJIwnqa.lean.js +1 -0
  59. package/dist/docs/assets/docs_adr_0012-how-a-steps-failures-are-told-apart-and-routed.md.uCyrBfug.js +1 -0
  60. package/dist/docs/assets/docs_adr_0012-how-a-steps-failures-are-told-apart-and-routed.md.uCyrBfug.lean.js +1 -0
  61. package/dist/docs/assets/docs_adr_0013-when-code-lands.md.oXgiobfz.js +1 -0
  62. package/dist/docs/assets/docs_adr_0013-when-code-lands.md.oXgiobfz.lean.js +1 -0
  63. package/dist/docs/assets/docs_adr_0014-what-a-step-may-change.md.Zslg3bnJ.js +1 -0
  64. package/dist/docs/assets/docs_adr_0014-what-a-step-may-change.md.Zslg3bnJ.lean.js +1 -0
  65. package/dist/docs/assets/docs_adr_0015-how-a-project-shapes-its-ticket-flow.md.DNHMxwIT.js +1 -0
  66. package/dist/docs/assets/docs_adr_0015-how-a-project-shapes-its-ticket-flow.md.DNHMxwIT.lean.js +1 -0
  67. package/dist/docs/assets/docs_adr_0016-which-core-the-factory-repo-runs.md.CLN6LqBZ.js +1 -0
  68. package/dist/docs/assets/docs_adr_0016-which-core-the-factory-repo-runs.md.CLN6LqBZ.lean.js +1 -0
  69. package/dist/docs/assets/docs_adr_0017-where-a-tickets-record-lives.md.jjUeM-ex.js +1 -0
  70. package/dist/docs/assets/docs_adr_0017-where-a-tickets-record-lives.md.jjUeM-ex.lean.js +1 -0
  71. package/dist/docs/assets/docs_adr_0018-the-products-name.md.6xaX9vDs.js +1 -0
  72. package/dist/docs/assets/docs_adr_0018-the-products-name.md.6xaX9vDs.lean.js +1 -0
  73. package/dist/docs/assets/docs_adr_0019-how-a-finding-is-answered-and-judged.md.CKKoO9-3.js +1 -0
  74. package/dist/docs/assets/docs_adr_0019-how-a-finding-is-answered-and-judged.md.CKKoO9-3.lean.js +1 -0
  75. package/dist/docs/assets/docs_adr_0020-how-an-interrupted-run-resumes.md.DTIP-MBK.js +1 -0
  76. package/dist/docs/assets/docs_adr_0020-how-an-interrupted-run-resumes.md.DTIP-MBK.lean.js +1 -0
  77. package/dist/docs/assets/docs_adr_0021-how-a-step-runs-in-its-workspace.md.B2r3-FPg.js +1 -0
  78. package/dist/docs/assets/docs_adr_0021-how-a-step-runs-in-its-workspace.md.B2r3-FPg.lean.js +1 -0
  79. package/dist/docs/assets/docs_adr_0022-who-names-the-branches.md.DaVk8X0i.js +1 -0
  80. package/dist/docs/assets/docs_adr_0022-who-names-the-branches.md.DaVk8X0i.lean.js +1 -0
  81. package/dist/docs/assets/docs_adr_0023-how-a-version-ships-before-npm.md.C7-wuCbq.js +1 -0
  82. package/dist/docs/assets/docs_adr_0023-how-a-version-ships-before-npm.md.C7-wuCbq.lean.js +1 -0
  83. package/dist/docs/assets/docs_adr_0024-how-the-config-type-carries-its-docs.md.CQmwhWXs.js +1 -0
  84. package/dist/docs/assets/docs_adr_0024-how-the-config-type-carries-its-docs.md.CQmwhWXs.lean.js +1 -0
  85. package/dist/docs/assets/docs_adr_0025-how-a-steps-handoff-is-written-and-checked.md.CgXYdF2v.js +1 -0
  86. package/dist/docs/assets/docs_adr_0025-how-a-steps-handoff-is-written-and-checked.md.CgXYdF2v.lean.js +1 -0
  87. package/dist/docs/assets/docs_adr_0026-support-for-github-copilot.md.DJsawm-R.js +1 -0
  88. package/dist/docs/assets/docs_adr_0026-support-for-github-copilot.md.DJsawm-R.lean.js +1 -0
  89. package/dist/docs/assets/docs_adr_0027-which-forge-a-project-is-on.md.CY0qy9xh.js +1 -0
  90. package/dist/docs/assets/docs_adr_0027-which-forge-a-project-is-on.md.CY0qy9xh.lean.js +1 -0
  91. package/dist/docs/assets/docs_adr_0028-several-ticket-flows-in-one-pipeline.md.oxffo2qg.js +1 -0
  92. package/dist/docs/assets/docs_adr_0028-several-ticket-flows-in-one-pipeline.md.oxffo2qg.lean.js +1 -0
  93. package/dist/docs/assets/docs_adr_0029-the-branch-a-feature-works-on.md.CrgkP6YJ.js +1 -0
  94. package/dist/docs/assets/docs_adr_0029-the-branch-a-feature-works-on.md.CrgkP6YJ.lean.js +1 -0
  95. package/dist/docs/assets/docs_adr_0030-a-runner-without-coder.md.CW8sF7jK.js +1 -0
  96. package/dist/docs/assets/docs_adr_0030-a-runner-without-coder.md.CW8sF7jK.lean.js +1 -0
  97. package/dist/docs/assets/docs_adr_0031-how-the-run-viewer-reads-its-runs.md.opQYnVF2.js +1 -0
  98. package/dist/docs/assets/docs_adr_0031-how-the-run-viewer-reads-its-runs.md.opQYnVF2.lean.js +1 -0
  99. package/dist/docs/assets/docs_adr_0032-how-a-project-gets-the-viewer.md.DB9NjYJf.js +1 -0
  100. package/dist/docs/assets/docs_adr_0032-how-a-project-gets-the-viewer.md.DB9NjYJf.lean.js +1 -0
  101. package/dist/docs/assets/docs_adr_0033-the-repos-license.md.NPlkrPj8.js +1 -0
  102. package/dist/docs/assets/docs_adr_0033-the-repos-license.md.NPlkrPj8.lean.js +1 -0
  103. package/dist/docs/assets/docs_adr_0034-how-code-review-launches-its-reviewers.md.DSNpkfaL.js +1 -0
  104. package/dist/docs/assets/docs_adr_0034-how-code-review-launches-its-reviewers.md.DSNpkfaL.lean.js +1 -0
  105. package/dist/docs/assets/docs_adr_0035-how-the-package-is-published.md.C1f4B5eb.js +1 -0
  106. package/dist/docs/assets/docs_adr_0035-how-the-package-is-published.md.C1f4B5eb.lean.js +1 -0
  107. package/dist/docs/assets/docs_adr_0036-what-the-cores-pull-request-skill-does.md.op39HLAT.js +1 -0
  108. package/dist/docs/assets/docs_adr_0036-what-the-cores-pull-request-skill-does.md.op39HLAT.lean.js +1 -0
  109. package/dist/docs/assets/docs_adr_0037-how-a-team-reads-the-factorys-documentation.md.Dar_Xo4t.js +1 -0
  110. package/dist/docs/assets/docs_adr_0037-how-a-team-reads-the-factorys-documentation.md.Dar_Xo4t.lean.js +1 -0
  111. package/dist/docs/assets/docs_adr_0038-where-adapters-live.md.CkVD3riv.js +1 -0
  112. package/dist/docs/assets/docs_adr_0038-where-adapters-live.md.CkVD3riv.lean.js +1 -0
  113. package/dist/docs/assets/docs_adr_0039-the-repos-license.md.C_rraWpb.js +1 -0
  114. package/dist/docs/assets/docs_adr_0039-the-repos-license.md.C_rraWpb.lean.js +1 -0
  115. package/dist/docs/assets/docs_adr_0040-how-a-version-is-released.md.CCdwC07z.js +1 -0
  116. package/dist/docs/assets/docs_adr_0040-how-a-version-is-released.md.CCdwC07z.lean.js +1 -0
  117. package/dist/docs/assets/docs_adr_0041-who-opens-the-features-pull-request.md.qLu9WLaG.js +1 -0
  118. package/dist/docs/assets/docs_adr_0041-who-opens-the-features-pull-request.md.qLu9WLaG.lean.js +1 -0
  119. package/dist/docs/assets/docs_adr_0042-how-secrets-are-kept-out-of-committed-records.md.DJWazI4H.js +1 -0
  120. package/dist/docs/assets/docs_adr_0042-how-secrets-are-kept-out-of-committed-records.md.DJWazI4H.lean.js +1 -0
  121. package/dist/docs/assets/docs_getting-started.md.BpQFS8Gr.js +12 -0
  122. package/dist/docs/assets/docs_getting-started.md.BpQFS8Gr.lean.js +1 -0
  123. package/dist/docs/assets/docs_github.md.D4gkW71G.js +1 -0
  124. package/dist/docs/assets/docs_github.md.D4gkW71G.lean.js +1 -0
  125. package/dist/docs/assets/docs_overlay.md.DoWVb6OM.js +35 -0
  126. package/dist/docs/assets/docs_overlay.md.DoWVb6OM.lean.js +1 -0
  127. package/dist/docs/assets/docs_pipeline.md.DdHywWSk.js +1 -0
  128. package/dist/docs/assets/docs_pipeline.md.DdHywWSk.lean.js +1 -0
  129. package/dist/docs/assets/docs_recipes.md.8Xf6BsRA.js +45 -0
  130. package/dist/docs/assets/docs_recipes.md.8Xf6BsRA.lean.js +1 -0
  131. package/dist/docs/assets/fixtures_README.md.DzbQ0hft.js +1 -0
  132. package/dist/docs/assets/fixtures_README.md.DzbQ0hft.lean.js +1 -0
  133. package/dist/docs/assets/index.md.DHgL6CzS.js +28 -0
  134. package/dist/docs/assets/index.md.DHgL6CzS.lean.js +1 -0
  135. package/dist/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  136. package/dist/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  137. package/dist/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  138. package/dist/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  139. package/dist/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  140. package/dist/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  141. package/dist/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  142. package/dist/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  143. package/dist/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  144. package/dist/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  145. package/dist/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  146. package/dist/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  147. package/dist/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  148. package/dist/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  149. package/dist/docs/assets/packages_docs_README.md.PV1a27nC.js +3 -0
  150. package/dist/docs/assets/packages_docs_README.md.PV1a27nC.lean.js +1 -0
  151. package/dist/docs/assets/packages_viewer_README.md.BynKZSdN.js +3 -0
  152. package/dist/docs/assets/packages_viewer_README.md.BynKZSdN.lean.js +1 -0
  153. package/dist/docs/assets/style.C3E8Ltp6.css +1 -0
  154. package/dist/docs/bernard.svg +94 -0
  155. package/dist/docs/core/README.html +44 -0
  156. package/dist/docs/demo/README.html +47 -0
  157. package/dist/docs/docs/adr/0001-what-the-core-is-built-for.html +30 -0
  158. package/dist/docs/docs/adr/0002-which-harness-a-project-runs-on.html +30 -0
  159. package/dist/docs/docs/adr/0003-how-core-content-reaches-each-harness.html +30 -0
  160. package/dist/docs/docs/adr/0004-how-the-core-uses-external-tools.html +30 -0
  161. package/dist/docs/docs/adr/0005-what-drives-the-pipeline.html +30 -0
  162. package/dist/docs/docs/adr/0006-what-steps-hand-off-to-each-other.html +30 -0
  163. package/dist/docs/docs/adr/0007-how-humans-take-part-in-the-pipeline.html +30 -0
  164. package/dist/docs/docs/adr/0008-where-a-steps-reviewers-run.html +30 -0
  165. package/dist/docs/docs/adr/0009-where-pipeline-state-and-handoffs-live.html +30 -0
  166. package/dist/docs/docs/adr/0010-how-a-project-adapts-the-core.html +30 -0
  167. package/dist/docs/docs/adr/0011-how-the-factory-is-packaged-and-versioned.html +30 -0
  168. package/dist/docs/docs/adr/0012-how-a-steps-failures-are-told-apart-and-routed.html +30 -0
  169. package/dist/docs/docs/adr/0013-when-code-lands.html +30 -0
  170. package/dist/docs/docs/adr/0014-what-a-step-may-change.html +30 -0
  171. package/dist/docs/docs/adr/0015-how-a-project-shapes-its-ticket-flow.html +30 -0
  172. package/dist/docs/docs/adr/0016-which-core-the-factory-repo-runs.html +30 -0
  173. package/dist/docs/docs/adr/0017-where-a-tickets-record-lives.html +30 -0
  174. package/dist/docs/docs/adr/0018-the-products-name.html +30 -0
  175. package/dist/docs/docs/adr/0019-how-a-finding-is-answered-and-judged.html +30 -0
  176. package/dist/docs/docs/adr/0020-how-an-interrupted-run-resumes.html +30 -0
  177. package/dist/docs/docs/adr/0021-how-a-step-runs-in-its-workspace.html +30 -0
  178. package/dist/docs/docs/adr/0022-who-names-the-branches.html +30 -0
  179. package/dist/docs/docs/adr/0023-how-a-version-ships-before-npm.html +30 -0
  180. package/dist/docs/docs/adr/0024-how-the-config-type-carries-its-docs.html +30 -0
  181. package/dist/docs/docs/adr/0025-how-a-steps-handoff-is-written-and-checked.html +30 -0
  182. package/dist/docs/docs/adr/0026-support-for-github-copilot.html +30 -0
  183. package/dist/docs/docs/adr/0027-which-forge-a-project-is-on.html +30 -0
  184. package/dist/docs/docs/adr/0028-several-ticket-flows-in-one-pipeline.html +30 -0
  185. package/dist/docs/docs/adr/0029-the-branch-a-feature-works-on.html +30 -0
  186. package/dist/docs/docs/adr/0030-a-runner-without-coder.html +30 -0
  187. package/dist/docs/docs/adr/0031-how-the-run-viewer-reads-its-runs.html +30 -0
  188. package/dist/docs/docs/adr/0032-how-a-project-gets-the-viewer.html +30 -0
  189. package/dist/docs/docs/adr/0033-the-repos-license.html +30 -0
  190. package/dist/docs/docs/adr/0034-how-code-review-launches-its-reviewers.html +30 -0
  191. package/dist/docs/docs/adr/0035-how-the-package-is-published.html +30 -0
  192. package/dist/docs/docs/adr/0036-what-the-cores-pull-request-skill-does.html +30 -0
  193. package/dist/docs/docs/adr/0037-how-a-team-reads-the-factorys-documentation.html +30 -0
  194. package/dist/docs/docs/adr/0038-where-adapters-live.html +30 -0
  195. package/dist/docs/docs/adr/0039-the-repos-license.html +30 -0
  196. package/dist/docs/docs/adr/0040-how-a-version-is-released.html +30 -0
  197. package/dist/docs/docs/adr/0041-who-opens-the-features-pull-request.html +30 -0
  198. package/dist/docs/docs/adr/0042-how-secrets-are-kept-out-of-committed-records.html +30 -0
  199. package/dist/docs/docs/getting-started.html +41 -0
  200. package/dist/docs/docs/github.html +30 -0
  201. package/dist/docs/docs/overlay.html +64 -0
  202. package/dist/docs/docs/pipeline.html +30 -0
  203. package/dist/docs/docs/recipes.html +74 -0
  204. package/dist/docs/fixtures/README.html +30 -0
  205. package/dist/docs/hashmap.json +1 -0
  206. package/dist/docs/index.html +57 -0
  207. package/dist/docs/packages/docs/README.html +32 -0
  208. package/dist/docs/packages/viewer/README.html +32 -0
  209. package/dist/docs/vp-icons.css +0 -0
  210. package/dist/viewer/assets/Heading-C5Va5XMQ.js +1 -0
  211. package/dist/viewer/assets/SelectionIndicator-DxHiKGW_.js +1 -0
  212. package/dist/viewer/assets/alert-BI2omIo2.js +1 -0
  213. package/dist/viewer/assets/attempt-Dunc67N6.js +1 -0
  214. package/dist/viewer/assets/card-IsZ2ZhSn.js +1 -0
  215. package/dist/viewer/assets/{chunk-WQQL4CDK-DSpW9ZO8.js → chunk-WQQL4CDK-Bd4bkond.js} +1 -1
  216. package/dist/viewer/assets/context-CzP3x3DO.js +7 -0
  217. package/dist/viewer/assets/{dagre-NK4GIZVX-BzzW8pOj.js → dagre-NK4GIZVX-Bhq8pl9Y.js} +1 -1
  218. package/dist/viewer/assets/{index-CnzUhLCJ.css → index-B90XVeY-.css} +1 -1
  219. package/dist/viewer/assets/index-tH-KNwJL.js +10 -0
  220. package/dist/viewer/assets/{mermaid.core-D1FXi6mg.js → mermaid.core-I426eCuN.js} +4 -4
  221. package/dist/viewer/assets/pipeline-Hw_7tocN.js +1 -0
  222. package/dist/viewer/assets/routes-BOl7_JgK.js +1 -0
  223. package/dist/viewer/assets/runs._runId-D4q1mLCt.js +6 -0
  224. package/dist/viewer/assets/runs._runId.index-B-woIhKA.js +1 -0
  225. package/dist/viewer/assets/runs._runId.machines-SkWqrTI6.js +2 -0
  226. package/dist/viewer/assets/runs._runId.sessions-DBVWEbLY.js +1 -0
  227. package/dist/viewer/assets/{stateDiagram-v2-A6IESKWZ-J9KnYRFT.js → stateDiagram-v2-A6IESKWZ-DUwIje_A.js} +1 -1
  228. package/dist/viewer/assets/surface-B6LupC_E.js +1 -0
  229. package/dist/viewer/assets/table-B8t5lQz5.js +1 -0
  230. package/dist/viewer/assets/useEvent-DXj38gmC.js +1 -0
  231. package/dist/viewer/assets/useToggleState-D_NUC1vr.js +2 -0
  232. package/dist/viewer/index.html +5 -5
  233. package/fixtures/starters/azure-devops/.factory/factory.config.ts +2 -7
  234. package/fixtures/starters/azure-devops/.factory/factory.lock.json +7 -8
  235. package/fixtures/starters/github/.factory/factory.config.ts +2 -0
  236. package/fixtures/starters/github/.factory/factory.lock.json +6 -7
  237. package/fixtures/starters/gitlab/.factory/factory.config.ts +9 -0
  238. package/fixtures/starters/gitlab/.factory/factory.lock.json +19 -0
  239. package/fixtures/starters/gitlab/.factory/slots/checks.command.md +1 -0
  240. package/fixtures/starters/gitlab/.factory/slots/context.sources.md +3 -0
  241. package/fixtures/starters/gitlab/.factory/slots/review.detectors.md +5 -0
  242. package/package.json +9 -7
  243. package/core/skills/pull-request/scripts/pr-drive.sh +0 -208
  244. package/dist/viewer/assets/Heading-Dm1Q0gV2.js +0 -1
  245. package/dist/viewer/assets/api-C4lMOGdC.js +0 -7
  246. package/dist/viewer/assets/attempt-lbW5voCR.js +0 -1
  247. package/dist/viewer/assets/card-CtJKot3P.js +0 -1
  248. package/dist/viewer/assets/context-DEB-Y3Iz.js +0 -1
  249. package/dist/viewer/assets/index-Co9KwXRI.js +0 -10
  250. package/dist/viewer/assets/jsx-runtime-CkXRJ162.js +0 -1
  251. package/dist/viewer/assets/pipeline-B-myvM6K.js +0 -1
  252. package/dist/viewer/assets/routes-BzvVN6y-.js +0 -1
  253. package/dist/viewer/assets/runs._runId-BVT2VH_w.js +0 -6
  254. package/dist/viewer/assets/runs._runId.index-C3bbzwAC.js +0 -1
  255. package/dist/viewer/assets/runs._runId.machines-Cj21KdLd.js +0 -2
  256. package/dist/viewer/assets/runs._runId.sessions-Tg6Ou3_9.js +0 -1
  257. package/dist/viewer/assets/table-DzduWiYu.js +0 -1
  258. package/dist/viewer/assets/useEvent-CtTfOmE7.js +0 -1
  259. package/dist/viewer/assets/useToggleState-DfzJwOaN.js +0 -2
  260. package/fixtures/starters/azure-devops/.factory/skills/pull-request/SKILL.md +0 -29
@@ -0,0 +1 @@
1
+ import{_ as t,o,c as i,a3 as r}from"./chunks/framework.hgjl6W6o.js";const u=JSON.parse(`{"title":"The repo's license","description":"","frontmatter":{},"headers":[],"relativePath":"docs/adr/0039-the-repos-license.md","filePath":"docs/adr/0039-the-repos-license.md"}`),a={name:"docs/adr/0039-the-repos-license.md"};function s(n,e,d,c,l,h){return o(),i("div",null,[...e[0]||(e[0]=[r('<h1 id="the-repo-s-license" tabindex="-1">The repo&#39;s license <a class="header-anchor" href="#the-repo-s-license" aria-label="Permalink to &quot;The repo&#39;s license&quot;">​</a></h1><p>Superseded by ADR-0033 · 2026-10-23</p><h2 id="context" tabindex="-1">Context <a class="header-anchor" href="#context" aria-label="Permalink to &quot;Context&quot;">​</a></h2><p>The repo goes open-source, and it had no license: <code>package.json</code> said <code>UNLICENSED</code>, so nobody outside Theodo could use, copy or change the factory. The factory&#39;s content also ends up in other people&#39;s repositories: a project commits the package&#39;s tarball (ADR-0023), the files <code>bernard sync</code> renders from the core, and the core skills its overlay overrides (ADR-0010). The license must not stop a project, or its client&#39;s legal team, from adopting it.</p><h2 id="criteria" tabindex="-1">Criteria <a class="header-anchor" href="#criteria" aria-label="Permalink to &quot;Criteria&quot;">​</a></h2><ol><li><strong>Free to adopt</strong>: the license must not stop a project, or its client&#39;s legal team, from adopting it.</li><li><strong>No duty on changed files</strong>: a project that overrides a core skill commits it in its client&#39;s repository, so a duty to mark it as changed, or copyleft obligations, would follow it there.</li><li><strong>One license for everything</strong>: <code>core/</code> is what the package renders into each project, and a project should not have to tell which of its rendered files falls under which license.</li><li><strong>Easy to contribute</strong>: outside contributions are rare, and a CLA would have every contributor sign one before a first pull request, a DCO a sign-off on every commit.</li></ol><h2 id="options" tabindex="-1">Options <a class="header-anchor" href="#options" aria-label="Permalink to &quot;Options&quot;">​</a></h2><ul><li><strong>Apache-2.0.</strong> It adds an explicit patent grant. Whoever redistributes a modified file must mark it as changed (2). A project that overrides a core skill and commits it in its client&#39;s repository would have to, for a grant that matters little to skills and a CLI.</li><li><strong>The MIT license, with no CLA and no DCO.</strong> Anyone may use, change, redistribute and sell the factory, as long as they keep the notice (1). What a project writes in its overlay is its own: the license only asks that the notice stays with what it copies from the factory (2). One license covers the whole repo, <code>core/</code> included (3), and a contribution comes in under the license the repo goes out under (4).</li><li><strong>A copyleft license, MPL-2.0 or AGPL-3.0.</strong> Core files a project changes, or the whole factory under AGPL-3.0, would carry obligations into client repositories (2), and a client&#39;s legal team would have to clear them before a project could adopt the factory (1).</li><li><strong>A separate license for the docs and <code>core/</code>, such as CC-BY-4.0.</strong> One package would ship under two licenses, and a project would have to tell which of its rendered files falls under which (3).</li><li><strong>A CLA.</strong> It would let Theodo Group relicense the contributions. We don&#39;t plan to, and every contributor would have to sign one before a first pull request (4).</li><li><strong>A DCO, with a <code>Signed-off-by</code> line on every commit.</strong> It records that each contributor has the right to submit their change. A check and a sign-off on every commit, while outside contributions are rare (4).</li></ul><h2 id="comparison" tabindex="-1">Comparison <a class="header-anchor" href="#comparison" aria-label="Permalink to &quot;Comparison&quot;">​</a></h2><table tabindex="0"><thead><tr><th>Option</th><th>1. Free to adopt</th><th>2. No duty</th><th>3. One license</th><th>4. Easy to contribute</th></tr></thead><tbody><tr><td>Apache-2.0</td><td>⚪</td><td>🔴</td><td>🟢</td><td>🟢</td></tr><tr><td>MIT</td><td>🟢</td><td>🟢</td><td>🟢</td><td>🟢</td></tr><tr><td>MPL-2.0 or AGPL-3.0</td><td>🔴</td><td>🔴</td><td>🟢</td><td>🟢</td></tr><tr><td>A separate license for the docs and <code>core/</code></td><td>⚪</td><td>⚪</td><td>🔴</td><td>🟢</td></tr><tr><td>A CLA</td><td>🟢</td><td>🟢</td><td>🟢</td><td>🔴</td></tr><tr><td>A DCO</td><td>🟢</td><td>🟢</td><td>🟢</td><td>🟠</td></tr></tbody></table><p>🟢 meets · 🟠 partly, or at a cost · 🔴 fails · ⚪ not discussed</p><h2 id="decision" tabindex="-1">Decision <a class="header-anchor" href="#decision" aria-label="Permalink to &quot;Decision&quot;">​</a></h2><p>The MIT license, with no CLA and no DCO. So everything in this repo is under it:</p><ul><li>One license for the whole repo: the code in <code>packages/</code>, the skills, agents and fragments in <code>core/</code>, the docs and their images, the fixtures and the demo. <code>core/</code> is not documentation: it is what the package renders into each project, so it travels under the same terms as the code that renders it.</li><li>The <code>LICENSE</code> file at the root holds the MIT text. <code>pnpm pack</code> adds it to the package&#39;s tarball, so the notice comes with the factory into every project that installs it.</li><li>The copyright holder is Theodo Group, which owns the repo.</li><li>No CLA and no DCO. A contribution comes in under the license the repo goes out under, as GitHub&#39;s terms of service provide, and its author keeps its copyright. CONTRIBUTING.md says so.</li><li>The <code>license</code> field of every <code>package.json</code> says <code>MIT</code>, those of the private workspace packages and of the demo included.</li></ul><p>We revisit the DCO if outside contributions grow.</p><h2 id="consequences" tabindex="-1">Consequences <a class="header-anchor" href="#consequences" aria-label="Permalink to &quot;Consequences&quot;">​</a></h2><ul><li>Anyone may use, change, redistribute and sell the factory, as long as they keep the notice.</li><li>What a project writes in its overlay is its own: the license only asks that the notice stays with what it copies from the factory.</li><li>The rendered files carry no notice of their own: the package that renders them carries it.</li></ul>',17)])])}const f=t(a,[["render",s]]);export{u as __pageData,f as default};
@@ -0,0 +1 @@
1
+ import{_ as t,o,c as i,a3 as r}from"./chunks/framework.hgjl6W6o.js";const u=JSON.parse(`{"title":"The repo's license","description":"","frontmatter":{},"headers":[],"relativePath":"docs/adr/0039-the-repos-license.md","filePath":"docs/adr/0039-the-repos-license.md"}`),a={name:"docs/adr/0039-the-repos-license.md"};function s(n,e,d,c,l,h){return o(),i("div",null,[...e[0]||(e[0]=[r("",17)])])}const f=t(a,[["render",s]]);export{u as __pageData,f as default};
@@ -0,0 +1 @@
1
+ import{_ as t,o as a,c as o,a3 as s}from"./chunks/framework.hgjl6W6o.js";const p=JSON.parse('{"title":"How a version is released","description":"","frontmatter":{},"headers":[],"relativePath":"docs/adr/0040-how-a-version-is-released.md","filePath":"docs/adr/0040-how-a-version-is-released.md"}'),r={name:"docs/adr/0040-how-a-version-is-released.md"};function n(i,e,l,d,h,c){return a(),o("div",null,[...e[0]||(e[0]=[s('<h1 id="how-a-version-is-released" tabindex="-1">How a version is released <a class="header-anchor" href="#how-a-version-is-released" aria-label="Permalink to &quot;How a version is released&quot;">​</a></h1><p>Accepted · 2026-10-27</p><h2 id="context" tabindex="-1">Context <a class="header-anchor" href="#context" aria-label="Permalink to &quot;Context&quot;">​</a></h2><p>ADR-0023 made a release manual: a pull request bumps <code>version</code> in <code>package.json</code>, then someone tags the merge commit and pushes the tag. Releasing took four steps spread over two places, and nothing pointed to the last one. Whoever forgot it, or tagged the wrong commit or the wrong version, released nothing or the wrong thing.</p><h2 id="criteria" tabindex="-1">Criteria <a class="header-anchor" href="#criteria" aria-label="Permalink to &quot;Criteria&quot;">​</a></h2><ol><li><strong>Nothing to remember after the merge</strong>: releasing took four steps spread over two places, and nothing pointed to the last one.</li><li><strong>No tag by hand</strong>: the manual tag is the step that went wrong: whoever tagged the wrong commit or the wrong version released the wrong thing.</li><li><strong>We decide what ships</strong>: we still decide when a version ships and what it is called, as ADR-0023 wants.</li></ol><h2 id="options" tabindex="-1">Options <a class="header-anchor" href="#options" aria-label="Permalink to &quot;Options&quot;">​</a></h2><ul><li><strong>Only a script for the bump, with the manual tag better documented.</strong> The manual tag is the step that went wrong (2).</li><li><strong>Merging the version bump releases it.</strong> Once the pull request is merged, the Release workflow creates the release and its tag on the merge commit (1), and nobody pushes a tag by hand (2). We still decide when a version ships and what it is called, by merging the pull request (3). A pull request that changes <code>version</code> by mistake releases on merge.</li><li><strong>A Release workflow run by hand from GitHub Actions, which tags the version of <code>package.json</code>.</strong> It still leaves a step to remember after the merge (1).</li></ul><h2 id="comparison" tabindex="-1">Comparison <a class="header-anchor" href="#comparison" aria-label="Permalink to &quot;Comparison&quot;">​</a></h2><table tabindex="0"><thead><tr><th>Option</th><th>1. Nothing after the merge</th><th>2. No tag by hand</th><th>3. We decide</th></tr></thead><tbody><tr><td>Bump script, documented manual tag</td><td>🔴</td><td>🔴</td><td>🟢</td></tr><tr><td>Release on merge</td><td>🟢</td><td>🟢</td><td>🟢</td></tr><tr><td>Release workflow run by hand</td><td>🔴</td><td>🟢</td><td>🟢</td></tr></tbody></table><p>🟢 meets · 🟠 partly, or at a cost · 🔴 fails · ⚪ not discussed</p><h2 id="decision" tabindex="-1">Decision <a class="header-anchor" href="#decision" aria-label="Permalink to &quot;Decision&quot;">​</a></h2><p>Merging the version bump releases it. The pull request that bumps the version is now the whole release:</p><ul><li><code>pnpm release</code> asks for the next version, offering the next pre-release, patch, minor and major from the current one. It writes the version to <code>package.json</code> and runs <code>pnpm sync:all</code>, so the lock files record it.</li><li>Once that pull request is merged, the Release workflow sees a version of <code>package.json</code> with no tag yet. It checks the package, then creates the release and its tag <code>vX.Y.Z</code> on the merge commit. A merge that does not change the version releases nothing.</li><li>Nobody pushes a tag by hand. A pushed tag no longer runs the workflow.</li></ul><p>We still decide when a version ships and what it is called, as ADR-0023 wants, by merging the pull request.</p><h2 id="consequences" tabindex="-1">Consequences <a class="header-anchor" href="#consequences" aria-label="Permalink to &quot;Consequences&quot;">​</a></h2><ul><li>A pull request that changes <code>version</code> by mistake releases on merge. The review of that line is the review of the release.</li><li>The tag is created with the workflow&#39;s token, so it triggers no other workflow.</li></ul>',17)])])}const m=t(r,[["render",n]]);export{p as __pageData,m as default};
@@ -0,0 +1 @@
1
+ import{_ as t,o as a,c as o,a3 as s}from"./chunks/framework.hgjl6W6o.js";const p=JSON.parse('{"title":"How a version is released","description":"","frontmatter":{},"headers":[],"relativePath":"docs/adr/0040-how-a-version-is-released.md","filePath":"docs/adr/0040-how-a-version-is-released.md"}'),r={name:"docs/adr/0040-how-a-version-is-released.md"};function n(i,e,l,d,h,c){return a(),o("div",null,[...e[0]||(e[0]=[s("",17)])])}const m=t(r,[["render",n]]);export{p as __pageData,m as default};
@@ -0,0 +1 @@
1
+ import{_ as t,o as a,c as r,a3 as s}from"./chunks/framework.hgjl6W6o.js";const p=JSON.parse(`{"title":"Who opens the feature's pull request, and how a ticket reaches it","description":"","frontmatter":{},"headers":[],"relativePath":"docs/adr/0041-who-opens-the-features-pull-request.md","filePath":"docs/adr/0041-who-opens-the-features-pull-request.md"}`),o={name:"docs/adr/0041-who-opens-the-features-pull-request.md"};function n(d,e,i,h,c,l){return a(),r("div",null,[...e[0]||(e[0]=[s('<h1 id="who-opens-the-feature-s-pull-request-and-how-a-ticket-reaches-it" tabindex="-1">Who opens the feature&#39;s pull request, and how a ticket reaches it <a class="header-anchor" href="#who-opens-the-feature-s-pull-request-and-how-a-ticket-reaches-it" aria-label="Permalink to &quot;Who opens the feature&#39;s pull request, and how a ticket reaches it&quot;">​</a></h1><p>Proposed · 2026-11-02</p><h2 id="context" tabindex="-1">Context <a class="header-anchor" href="#context" aria-label="Permalink to &quot;Context&quot;">​</a></h2><p>Each ticket opens its own pull request, <code>ticket/&lt;feature&gt;-&lt;ticket&gt;</code> to <code>feat/&lt;feature&gt;-&lt;slug&gt;</code> (ADR-0029), then the team opens a second one, the feature branch to the base branch, by hand: Bernard never opens it. With no human gate on a ticket, the same change is reviewed twice, once as the ticket&#39;s pull request and once as part of the feature&#39;s (#144 then #145, #142 then #150, #136 then #140). A graph with a gate after <code>pull-request</code> (ADR-0015) still needs the ticket&#39;s pull request, since the gate is the review of it.</p><h2 id="criteria" tabindex="-1">Criteria <a class="header-anchor" href="#criteria" aria-label="Permalink to &quot;Criteria&quot;">​</a></h2><ol><li><strong>One review per change</strong>: a change is reviewed once, in one pull request.</li><li><strong>History kept</strong>: no force push, the commits of the tickets stay.</li><li><strong>Parallel tickets both land</strong>: tickets that finish at the same time neither lose nor block each other.</li><li><strong>A gate keeps its meaning</strong>: a graph with a gate after <code>pull-request</code> still hands the ticket&#39;s pull request to the team.</li><li><strong>Nothing without a consumer</strong>: no port or service that no code uses yet (ADR-0013).</li></ol><h2 id="options" tabindex="-1">Options <a class="header-anchor" href="#options" aria-label="Permalink to &quot;Options&quot;">​</a></h2><ul><li><strong>Today: a pull request per ticket, then one for the feature by hand.</strong> Fails 1 whenever a ticket has no gate, and the feature&#39;s pull request is never opened by the factory. Meets 2 to 5.</li><li><strong>A pull request per ticket only.</strong> The feature&#39;s pull request disappears, and the team merges each ticket. Meets 4 and 5, fails 1 for a ticket with no gate (it is a review of nothing the team wants), and leaves the feature with no pull request to the base branch.</li><li><strong>Merge mode and gate mode, as the graph decides.</strong> A graph with no gate after <code>pull-request</code> merges its tickets into the feature branch itself, with no ticket pull request. A graph with a gate keeps the ticket&#39;s pull request, merged by the team. In both, the factory opens and keeps the feature&#39;s pull request. Meets 1, 2, 4 and 5; meets 3 with a fast-forward push retried after a merge when the feature branch moved.</li><li><strong>The orchestrator merges with the forge&#39;s merge API.</strong> Meets 1 and 4, but needs the forge port (#26), which has no other consumer yet (5), and the API merges or squashes by the forge&#39;s rules, so 2 and 3 depend on them.</li><li><strong>A squash of the ticket branch into the feature branch.</strong> Meets 1 and 3, but a squash rewrites the ticket&#39;s commits and its record, so fails 2.</li></ul><h2 id="comparison" tabindex="-1">Comparison <a class="header-anchor" href="#comparison" aria-label="Permalink to &quot;Comparison&quot;">​</a></h2><table tabindex="0"><thead><tr><th>Option</th><th>1. One review per change</th><th>2. History kept</th><th>3. Parallel tickets land</th><th>4. A gate keeps its meaning</th><th>5. Nothing without a consumer</th></tr></thead><tbody><tr><td>Today&#39;s two pull requests</td><td>🔴</td><td>🟢</td><td>🟢</td><td>🟢</td><td>🟢</td></tr><tr><td>A pull request per ticket only</td><td>🔴</td><td>🟢</td><td>🟢</td><td>🟢</td><td>🟢</td></tr><tr><td>Merge mode and gate mode</td><td>🟢</td><td>🟢</td><td>🟢</td><td>🟢</td><td>🟢</td></tr><tr><td>The forge&#39;s merge API</td><td>🟢</td><td>🟠</td><td>🟠</td><td>🟢</td><td>🔴</td></tr><tr><td>A squash of the ticket branch</td><td>🟢</td><td>🔴</td><td>🟢</td><td>🟢</td><td>🟢</td></tr></tbody></table><p>🟢 meets · 🟠 partly, or at a cost · 🔴 fails · ⚪ not discussed</p><h2 id="decision" tabindex="-1">Decision <a class="header-anchor" href="#decision" aria-label="Permalink to &quot;Decision&quot;">​</a></h2><p>Merge mode and gate mode, as the graph decides. Bernard opens and keeps one pull request per feature, <code>feat/&lt;feature&gt;-&lt;slug&gt;</code> to the base branch, whoever the tickets are.</p><ul><li><strong>Merge mode</strong>: a graph with a step that runs the <code>pull-request</code> skill and no gate, of any policy, reachable from it by <code>onPass</code> edges (<code>mergesItself</code> in <code>packages/contracts/src/pipeline.ts</code>). A <code>decide-and-log</code> gate is a gate: it approves without merging, so the ticket&#39;s pull request stays. The ticket has no pull request: <code>bernard pull-request</code>, which the <code>pull-request</code> skill runs, merges the feature branch into the ticket&#39;s, pushes it, then fast-forwards <code>HEAD</code> into the feature branch. Rejected because the feature branch moved, it fetches, merges and tries again, five rounds at most, never by force. A conflict stops with <code>MERGE_CONFLICT</code>. It then opens or updates the feature&#39;s pull request and waits for its checks. On <code>DONE</code> the runner lands the ticket&#39;s record commit into the feature branch the same way, then deletes the ticket&#39;s branch.</li><li><strong>Gate mode</strong>: nothing changes for the ticket (its pull request targets its <code>baseBranch</code>, a human merges it); <code>bernard pull-request</code> also opens or updates the feature&#39;s pull request.</li><li>A ticket that merges itself counts as merged at <code>DONE</code>, and its dependents start once its landing on the feature branch succeeded (the run sends <code>TICKET.LANDED</code>), from the feature branch, and never stack. A landing that fails starts none. One that merges itself and depends on a ticket done but not merged is refused, since it would carry that ticket&#39;s unreviewed commits into the feature branch.</li><li>The feature&#39;s pull request has a Conventional Commits title from the plan, set at creation only (a human&#39;s edit survives), the plan&#39;s path, and one section per ticket between <code>&lt;!-- ticket:&lt;id&gt; --&gt;</code> markers, which only that ticket rewrites. <code>bernard pull-request</code> reads the description again after an edit and retries when another ticket&#39;s edit won.</li><li>A ticket&#39;s request carries <code>merge</code>, <code>featureBranch</code>, <code>featureTrackerKey</code> (from the orchestrator) and <code>featureBase</code> (completed by the run, which knows the runner&#39;s base).</li></ul><h2 id="consequences" tabindex="-1">Consequences <a class="header-anchor" href="#consequences" aria-label="Permalink to &quot;Consequences&quot;">​</a></h2><ul><li>Amends ADR-0029: the team no longer opens the feature&#39;s pull request.</li><li>Amends ADR-0015: a graph without a gate after <code>pull-request</code> merges itself, and the dependents of such a ticket never stack.</li><li>Amends ADR-0022: a ticket&#39;s request gains <code>merge</code>, <code>featureBranch</code>, <code>featureTrackerKey</code> and <code>featureBase</code>.</li><li>Amends ADR-0036: <code>bernard pull-request</code> has a merge mode and drives the feature&#39;s pull request.</li><li>The feature branch holds every merged ticket&#39;s commits and record, and no <code>ticket/&lt;feature&gt;-*</code> branch remains once a ticket landed: <code>bernard status</code> and a resumed run read a landed ticket&#39;s record on the feature branch.</li><li>The plan&#39;s commit must be on the feature branch before any ticket starts, so that the feature&#39;s pull request has a diff.</li><li>Two tickets that edit the same file conflict, and the session resolves it as for a base branch.</li><li>Every forge gets both modes through the forge port (ADR-0027). Azure DevOps cuts a description at 4,000 characters, so each ticket&#39;s section stays a few lines.</li></ul>',16)])])}const f=t(o,[["render",n]]);export{p as __pageData,f as default};
@@ -0,0 +1 @@
1
+ import{_ as t,o as a,c as r,a3 as s}from"./chunks/framework.hgjl6W6o.js";const p=JSON.parse(`{"title":"Who opens the feature's pull request, and how a ticket reaches it","description":"","frontmatter":{},"headers":[],"relativePath":"docs/adr/0041-who-opens-the-features-pull-request.md","filePath":"docs/adr/0041-who-opens-the-features-pull-request.md"}`),o={name:"docs/adr/0041-who-opens-the-features-pull-request.md"};function n(d,e,i,h,c,l){return a(),r("div",null,[...e[0]||(e[0]=[s("",16)])])}const f=t(o,[["render",n]]);export{p as __pageData,f as default};
@@ -0,0 +1 @@
1
+ import{_ as t,o as a,c as s,a3 as r}from"./chunks/framework.hgjl6W6o.js";const u=JSON.parse('{"title":"How secrets are kept out of committed records","description":"","frontmatter":{},"headers":[],"relativePath":"docs/adr/0042-how-secrets-are-kept-out-of-committed-records.md","filePath":"docs/adr/0042-how-secrets-are-kept-out-of-committed-records.md"}'),o={name:"docs/adr/0042-how-secrets-are-kept-out-of-committed-records.md"};function n(i,e,d,h,c,l){return a(),s("div",null,[...e[0]||(e[0]=[r('<h1 id="how-secrets-are-kept-out-of-committed-records" tabindex="-1">How secrets are kept out of committed records <a class="header-anchor" href="#how-secrets-are-kept-out-of-committed-records" aria-label="Permalink to &quot;How secrets are kept out of committed records&quot;">​</a></h1><p>Accepted · 2026-11-02</p><h2 id="context" tabindex="-1">Context <a class="header-anchor" href="#context" aria-label="Permalink to &quot;Context&quot;">​</a></h2><p>A ticket&#39;s record and the handoffs of its steps are committed on its branch and pushed (ADR-0017, ADR-0025). What goes in them comes from a session&#39;s output: an error that echoes a token, a handoff that quotes an environment. A secret that gets there reaches the forge, and stays in its history. A session can also write a file straight into the record&#39;s folder, which the runner commits with the rest.</p><h2 id="criteria" tabindex="-1">Criteria <a class="header-anchor" href="#criteria" aria-label="Permalink to &quot;Criteria&quot;">​</a></h2><ol><li><strong>No secret on the forge</strong>: neither in a line or handoff the runner writes, nor in a file a session wrote in the record.</li><li><strong>A record keeps its use</strong>: a leak in a line should not lose the line, and an old miss should not block every later commit.</li><li><strong>Every runner behaves the same</strong>: Coder and local, and any later git runner.</li><li><strong>Explicit, and simple</strong>: the code that reads a variable says whether it is a secret; no new tool to install.</li></ol><h2 id="options" tabindex="-1">Options <a class="header-anchor" href="#options" aria-label="Permalink to &quot;Options&quot;">​</a></h2><ul><li><strong>Redact only.</strong> The runner replaces known values and built-in shapes in what it writes. A file a session wrote itself is committed as it is (1 fails for it).</li><li><strong>Scan only.</strong> The runner refuses any commit that holds a match. A line with a token then crashes the run, though the line could be kept with the token replaced (2).</li><li><strong>Redact, then scan.</strong> The runner redacts what it writes (lines, handoffs), then scans the added lines of the staged record before committing, and crashes on a match. Both kinds of leak are met (1), a line keeps its use (2), and one place serves every runner (3). The patterns are a short list, so a secret of another shape goes through unless the factory reads it from a variable it marks secret (4).</li><li><strong>A secret-scanning tool, such as gitleaks.</strong> Broad rules, but a binary every runner&#39;s machine and Coder template must carry (4), and its rules are not tied to the factory&#39;s variables.</li></ul><h2 id="comparison" tabindex="-1">Comparison <a class="header-anchor" href="#comparison" aria-label="Permalink to &quot;Comparison&quot;">​</a></h2><table tabindex="0"><thead><tr><th>Option</th><th>1. No secret on the forge</th><th>2. A record keeps its use</th><th>3. Same everywhere</th><th>4. Explicit, simple</th></tr></thead><tbody><tr><td>Redact only</td><td>🟠</td><td>🟢</td><td>🟢</td><td>🟢</td></tr><tr><td>Scan only</td><td>🟢</td><td>🔴</td><td>🟢</td><td>🟢</td></tr><tr><td>Redact, then scan</td><td>🟢</td><td>🟢</td><td>🟢</td><td>🟢</td></tr><tr><td>gitleaks</td><td>🟢</td><td>🟠</td><td>🟠</td><td>🔴</td></tr></tbody></table><p>🟢 meets · 🟠 partly, or at a cost · 🔴 fails · ⚪ not discussed</p><h2 id="decision" tabindex="-1">Decision <a class="header-anchor" href="#decision" aria-label="Permalink to &quot;Decision&quot;">​</a></h2><p>Redact, then scan, in <code>createGitRunner</code>, which the Coder and the local runner share.</p><ul><li>Each part of the factory that reads env vars declares them next to its code, each marked secret or not: a harness&#39;s credentials, the Coder runner&#39;s session and GitHub tokens, the forges&#39; tokens the local runner gives its sessions, the Jira token. The overlay declares none: the factory knows what it reads. The secrets are the values of every variable marked secret, whichever harness and adapters the project uses, and the credential a login flow got at the terminal; a missing, empty or shorter than 8 characters value is skipped. A few built-in shapes need no variable: GitHub keys, AWS access keys, Anthropic keys, private keys, JWTs.</li><li>Every record line and every handoff is redacted, on its JSON value rather than its text, and a match becomes <code>[REDACTED:&lt;name&gt;]</code>.</li><li>Before a record is committed, its folder is staged over <code>HEAD</code> in an index of its own, the feature&#39;s folder for a published upstream step, and the names and added lines of its files are scanned. The tree that was scanned is the one committed, not the working tree a session may still be writing in, and nothing a session staged outside the folder joins it. A match commits and pushes nothing, and ends the run through <code>onFatal</code> with an error that names the file and the rule, never the value. The file stays untracked for a human to remove.</li><li>The slug a passed step returns names the feature&#39;s branch, which is pushed: it is scanned too, and a match ends the run the same way, before anything is committed.</li></ul><h2 id="consequences" tabindex="-1">Consequences <a class="header-anchor" href="#consequences" aria-label="Permalink to &quot;Consequences&quot;">​</a></h2><ul><li>A leak in a file stops the run, where a failing record is otherwise a warning (ADR-0017): a warning would let the run leak again on the next line.</li><li>Only added lines are scanned, so a line an older version missed does not block the later commits.</li><li>The trace is not redacted: it is git-ignored and never committed (see <em>Trace</em> in CONTEXT.md).</li><li>A secret of a shape the list does not know, in a variable the factory does not read, such as a project&#39;s own, is not found.</li><li>An adapter that reads a new secret variable declares it in its spec, and the entry point lists the spec with the others: no overlay changes.</li></ul>',16)])])}const f=t(o,[["render",n]]);export{u as __pageData,f as default};
@@ -0,0 +1 @@
1
+ import{_ as t,o as a,c as s,a3 as r}from"./chunks/framework.hgjl6W6o.js";const u=JSON.parse('{"title":"How secrets are kept out of committed records","description":"","frontmatter":{},"headers":[],"relativePath":"docs/adr/0042-how-secrets-are-kept-out-of-committed-records.md","filePath":"docs/adr/0042-how-secrets-are-kept-out-of-committed-records.md"}'),o={name:"docs/adr/0042-how-secrets-are-kept-out-of-committed-records.md"};function n(i,e,d,h,c,l){return a(),s("div",null,[...e[0]||(e[0]=[r("",16)])])}const f=t(o,[["render",n]]);export{u as __pageData,f as default};
@@ -0,0 +1,12 @@
1
+ import{_ as t,o as a,c as s,a3 as o}from"./chunks/framework.hgjl6W6o.js";const u=JSON.parse('{"title":"Getting started","description":"","frontmatter":{},"headers":[],"relativePath":"docs/getting-started.md","filePath":"docs/getting-started.md"}'),i={name:"docs/getting-started.md"};function r(n,e,d,h,l,c){return a(),s("div",null,[...e[0]||(e[0]=[o(`<h1 id="getting-started" tabindex="-1">Getting started <a class="header-anchor" href="#getting-started" aria-label="Permalink to &quot;Getting started&quot;">​</a></h1><p>This guide sets up the factory in your project, from its package to a green <code>bernard sync</code>. At the end, your project has:</p><ul><li>a <code>.factory/</code> folder: the factory&#39;s package, pinned, and your overlay, your project&#39;s settings and content, which you own;</li><li>the native files of your harness (<code>.claude/</code>, <code>.pi/</code> or <code>.github/</code>), rendered by <code>bernard sync</code> from the core and your overlay, and committed.</li></ul><p>Running a feature through the pipeline with <code>bernard run</code> needs a runner. Start with the local one, which runs the feature and each ticket in a git worktree on your machine: <code>adapters: { runner: { type: &#39;local&#39; } }</code> (<a href="./overlay.html#runner">Runner</a>). The <a href="./../demo/README.html">demo</a> shows a feature end to end, on it or on Coder.</p><p>The words this guide uses (core, overlay, slot, override, step…) are defined in <a href="./../CONTEXT.html">CONTEXT.md</a>.</p><h2 id="prerequisites" tabindex="-1">Prerequisites <a class="header-anchor" href="#prerequisites" aria-label="Permalink to &quot;Prerequisites&quot;">​</a></h2><ul><li>Node 24 and pnpm.</li><li>Your project in a git repository.</li><li>The harness your project runs: <a href="https://docs.claude.com/en/docs/claude-code" target="_blank" rel="noreferrer">Claude Code</a>, <a href="https://github.com/badlogic/pi-mono" target="_blank" rel="noreferrer">Pi</a> or <a href="https://docs.github.com/copilot/how-tos/copilot-cli" target="_blank" rel="noreferrer">GitHub Copilot CLI</a>.</li><li>The command-line tool of the forge your pull requests open on, signed in: <a href="https://cli.github.com/" target="_blank" rel="noreferrer"><code>gh</code></a> for GitHub, or <a href="https://learn.microsoft.com/cli/azure/" target="_blank" rel="noreferrer"><code>az</code></a> with the <code>azure-devops</code> extension for Azure DevOps.</li></ul><h2 id="_1-install-the-factory" tabindex="-1">1. Install the factory <a class="header-anchor" href="#_1-install-the-factory" aria-label="Permalink to &quot;1. Install the factory&quot;">​</a></h2><p>From your project&#39;s root:</p><div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">mkdir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> .factory</span></span>
2
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">pnpm</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> .factory</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --ignore-workspace</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> add</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -E</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> @theodo/bernard@next</span></span>
3
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">alias</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> bernard</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.factory/node_modules/.bin/bernard</span></span></code></pre></div><p>It installs the factory&#39;s package, <a href="https://www.npmjs.com/package/@theodo/bernard" target="_blank" rel="noreferrer"><code>@theodo/bernard</code></a>, from npm in <code>.factory/</code>. While Bernard is in beta, each version is a pre-release, under the dist-tag <code>next</code>: <code>@next</code> installs the latest one, <code>@0.1.0-beta.0</code> a given one. <code>-E</code> pins the exact version, which a minor of a 0.x version may break.</p><p><code>.factory/package.json</code> now pins the factory. Commit it, with <code>.factory/pnpm-lock.yaml</code>: your team and your CI install the factory with <code>pnpm --dir .factory --ignore-workspace install</code>. <code>--ignore-workspace</code> keeps the factory out of your own pnpm workspace, if your project has one.</p><p>Put the alias in your shell profile: it runs the factory each project pins, from that project&#39;s root. Every command below runs from your project&#39;s root; <code>--cwd &lt;project&gt;</code> runs it from elsewhere.</p><h2 id="_2-create-the-overlay" tabindex="-1">2. Create the overlay <a class="header-anchor" href="#_2-create-the-overlay" aria-label="Permalink to &quot;2. Create the overlay&quot;">​</a></h2><div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bernard</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> init</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --harness</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> claude-code</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --forge</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> github</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;My project&quot;</span></span></code></pre></div><p><code>--harness</code> is <code>claude-code</code>, <code>pi</code> or <code>copilot</code>, and <code>--forge</code> is <code>github</code>, <code>gitlab</code> or <code>azure-devops</code>: the forge where your pull requests open and your CI runs, whatever your tracker is. Jira with Azure DevOps is <code>--forge azure-devops</code>. The starter sets it as <code>adapters.forge</code> in the config; on a self-managed GitLab, add its <code>host</code> (<a href="./overlay.html#forge">the overlay</a>).</p><p>It writes the forge&#39;s <a href="https://github.com/theodo-group/bernard/tree/v0.1.0-beta.1/fixtures/starters" target="_blank" rel="noreferrer">starter overlay</a>, which CI keeps valid against the current core, next to the factory&#39;s package (ADR-0027), and makes <code>.factory/package.json</code> an ES module, as the config is written. It adds to your <code>.gitignore</code> the scratch folder, where the steps put their working files, and <code>.factory/node_modules/</code>:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>.factory/</span></span>
4
+ <span class="line"><span>├── package.json # the factory&#39;s pinned version</span></span>
5
+ <span class="line"><span>├── pnpm-lock.yaml</span></span>
6
+ <span class="line"><span>├── factory.config.ts # harness, project, paths, forge</span></span>
7
+ <span class="line"><span>├── slots/</span></span>
8
+ <span class="line"><span>│ ├── checks.command.md # the command that runs your checks</span></span>
9
+ <span class="line"><span>│ ├── context.sources.md # where your standards, ADRs and glossary live</span></span>
10
+ <span class="line"><span>│ └── review.detectors.md # what your tools already guarantee</span></span></code></pre></div><p>In <code>factory.config.ts</code>, <code>adapters.forge</code> is your forge, and <code>project.language</code> is the language of the PRD, the review findings and the PR text: English until you change it. Every other field but the harness and the project&#39;s name is optional and described in <a href="./overlay.html">overlay.md</a>. The config imports <code>defineConfig</code> from <code>@theodo/bernard/config</code>, which your editor types from the installed package.</p><h2 id="_3-fill-the-slots" tabindex="-1">3. Fill the slots <a class="header-anchor" href="#_3-fill-the-slots" aria-label="Permalink to &quot;3. Fill the slots&quot;">​</a></h2><p>A slot is a place in a core skill where your project&#39;s content goes. Each file in <code>.factory/slots/</code> replaces the default of the slot with its name. Rewrite the starter&#39;s three for your project:</p><table tabindex="0"><thead><tr><th>Slot</th><th>Write</th><th>Example</th></tr></thead><tbody><tr><td><code>checks.command</code></td><td>The command that runs your checks, and what it covers.</td><td><code>\`pnpm check\`: lint, type-check, unit tests.</code></td></tr><tr><td><code>context.sources</code></td><td>The documents that hold your standards, ADRs and glossary.</td><td><code>- \`docs/adr/\`: the decisions.</code></td></tr><tr><td><code>review.detectors</code></td><td>A table of the defect families a tool already guarantees, so the reviewers skip them.</td><td><code>A type error | \`tsc\` strict | hard</code></td></tr></tbody></table><p>A slot file is a Handlebars template like the core: <code>{{paths.features}}</code> and the other variables of <a href="./../core/README.html">core/README.md</a> work in it. Delete a slot file and the core&#39;s default applies. The other slots, such as <code>ticket.format</code> or the reviewers&#39; criteria, are listed in <a href="./overlay.html#slots">overlay.md</a>.</p><h2 id="_4-sign-in-to-your-forge" tabindex="-1">4. Sign in to your forge <a class="header-anchor" href="#_4-sign-in-to-your-forge" aria-label="Permalink to &quot;4. Sign in to your forge&quot;">​</a></h2><p>The core&#39;s <code>pull-request</code> skill runs <code>bernard pull-request</code>: it syncs the branch with its base, opens the pull request on your forge and waits for its checks, and the skill fixes them when they are red. Its commit conventions are Conventional Commits, and it starts the title with the ticket&#39;s key in your tracker, such as <code>feat(SHOP-142): …</code>, so that Jira links the pull request. It goes through your forge&#39;s tool, signed in where the steps run: <code>gh auth login</code> on GitHub, <code>glab auth login</code> on GitLab, <code>az login</code> with the <code>azure-devops</code> extension on Azure DevOps (<a href="./overlay.html#forge">the overlay</a>).</p><h2 id="_5-sync" tabindex="-1">5. Sync <a class="header-anchor" href="#_5-sync" aria-label="Permalink to &quot;5. Sync&quot;">​</a></h2><div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bernard</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> sync</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>✓ 12 file(s) in sync</span></span></code></pre></div><p><code>bernard sync</code> rendered the core skills and agents with your slots into <code>.claude/</code> (<code>.pi/</code> on Pi, <code>.github/skills/</code> and <code>.github/agents/</code> on GitHub Copilot), and wrote <code>.factory/factory.lock.json</code>, the list of what it generated. Open <code>.claude/skills/validate/SKILL.md</code>: your checks command is in it.</p><p>Commit <code>.factory/</code> and the generated files together. Never edit a generated file: change the overlay and sync again. <code>bernard sync</code> only touches the files its lock lists, so your own skills in <code>.claude/</code> stay.</p><p>When it fails, it says why: an invalid config field, a slot file that matches no slot, a delivery step whose skill nobody provides, or a file of yours where it would write one, such as your own <code>.claude/skills/implement/</code>. It then writes nothing.</p><h2 id="_6-check-it-in-ci" tabindex="-1">6. Check it in CI <a class="header-anchor" href="#_6-check-it-in-ci" aria-label="Permalink to &quot;6. Check it in CI&quot;">​</a></h2><p>Add a CI job, in GitHub Actions or Azure Pipelines, that installs the factory and fails when the generated files no longer match the overlay:</p><div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">pnpm</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> .factory</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --ignore-workspace</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> install</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --frozen-lockfile</span></span>
11
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">.factory/node_modules/.bin/bernard</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> sync</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --check</span></span></code></pre></div><p>Keep the tickets&#39; records out of your linters and formatters: <code>bernard run</code> commits them in <code>.pipeline/</code> of each feature&#39;s folder (ADR-0017), as the orchestrator writes them, and no step may touch them. A formatter that checks them, such as Biome or Prettier on JSON, fails a step&#39;s checks on a file the step cannot fix. With Biome, add <code>&quot;!**/.pipeline&quot;</code> to <code>files.includes</code>; with Prettier, <code>**/.pipeline/</code> to <code>.prettierignore</code>. Mark them as generated in <code>.gitattributes</code> too, so that your forge folds them in pull request diffs:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>**/.pipeline/** linguist-generated</span></span></code></pre></div><p>To see the pipeline your config draws, as Mermaid diagrams:</p><div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bernard</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> pipeline</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --out</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> docs/pipeline.md</span></span></code></pre></div><h2 id="update-the-factory" tabindex="-1">Update the factory <a class="header-anchor" href="#update-the-factory" aria-label="Permalink to &quot;Update the factory&quot;">​</a></h2><p>Install the latest version, or put the one you want in place of <code>next</code>, then sync:</p><div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">pnpm</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> .factory</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --ignore-workspace</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> add</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -E</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> @theodo/bernard@next</span></span>
12
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bernard</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> sync</span></span></code></pre></div><p>Review the diff of the generated files like any other change, then commit it with <code>.factory/package.json</code> and <code>.factory/pnpm-lock.yaml</code>. Renovate and Dependabot follow the pin like any npm dependency, in <code>.factory/</code>. <code>bernard sync</code> warns when the core changed a skill you override, or the default delivery graph you reshaped: review your version against it.</p><p>A teammate who pulls the upgrade reinstalls the factory with <code>pnpm --dir .factory --ignore-workspace install</code>. Until then, <code>bernard sync</code> refuses to run: the bernard they have installed is older than the one that last synced the project, and would take the generated files back to its own core.</p><h2 id="next" tabindex="-1">Next <a class="header-anchor" href="#next" aria-label="Permalink to &quot;Next&quot;">​</a></h2><ul><li><a href="./overlay.html">overlay.md</a>: every config field, every slot, and how to add or override a skill.</li><li><a href="./recipes.html">recipes.md</a>: common changes, such as adding a check to the review or removing a gate.</li><li><a href="./pipeline.html">pipeline.md</a>: the default pipeline, drawn from the orchestrator.</li><li><a href="./../demo/README.html">demo/README.md</a>: a feature delivered end to end by <code>bernard run</code>.</li><li><a href="https://github.com/theodo-group/bernard/tree/v0.1.0-beta.1/fixtures" target="_blank" rel="noreferrer">fixtures/</a>: a reference overlay per harness, and this repo&#39;s own <a href="https://github.com/theodo-group/bernard/tree/v0.1.0-beta.1/.factory" target="_blank" rel="noreferrer"><code>.factory/</code></a>.</li></ul>`,45)])])}const k=t(i,[["render",r]]);export{u as __pageData,k as default};
@@ -0,0 +1 @@
1
+ import{_ as t,o as a,c as s,a3 as o}from"./chunks/framework.hgjl6W6o.js";const u=JSON.parse('{"title":"Getting started","description":"","frontmatter":{},"headers":[],"relativePath":"docs/getting-started.md","filePath":"docs/getting-started.md"}'),i={name:"docs/getting-started.md"};function r(n,e,d,h,l,c){return a(),s("div",null,[...e[0]||(e[0]=[o("",45)])])}const k=t(i,[["render",r]]);export{u as __pageData,k as default};
@@ -0,0 +1 @@
1
+ import{_ as t,o,c as a,a3 as i}from"./chunks/framework.hgjl6W6o.js";const u=JSON.parse('{"title":"GitHub","description":"","frontmatter":{},"headers":[],"relativePath":"docs/github.md","filePath":"docs/github.md"}'),s={name:"docs/github.md"};function d(c,e,n,r,h,l){return o(),a("div",null,[...e[0]||(e[0]=[i('<h1 id="github" tabindex="-1">GitHub <a class="header-anchor" href="#github" aria-label="Permalink to &quot;GitHub&quot;">​</a></h1><p>Tickets are issues of <code>theodo-group/bernard</code>, tracked on the org project board <code>theodo-group</code> #10. Where a ticket stands lives in the board&#39;s fields, not in labels or the issue state.</p><h2 id="board" tabindex="-1">Board <a class="header-anchor" href="#board" aria-label="Permalink to &quot;Board&quot;">​</a></h2><ul><li><code>Status</code>: the team that owns the ticket. Columns in order: <code>Backlog</code>, <code>Refinement team</code>, <code>Dev team</code>, <code>Code quality team</code>, <code>Adoption</code>.</li><li><code>Team progress</code>: where the ticket is inside its column. <code>In progress</code>, then <code>Ready for next team</code>.</li></ul><p>A new ticket is added to the board as <code>Backlog</code> / <code>In progress</code>, never with an empty field.</p><p>A ticket moves one step at a time: <code>In progress</code> → <code>Ready for next team</code> → next column&#39;s <code>In progress</code>. Never skip a step.</p><p>It reaches <code>Ready for next team</code> only when it meets the column&#39;s Quality Control Point. Each QCP is the description of its <code>Status</code> option:</p><div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">gh</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> api</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> graphql</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> query=&#39;{organization(login:&quot;theodo-group&quot;){projectV2(number:10){field(name:&quot;Status&quot;){... on ProjectV2SingleSelectField{options{name description}}}}}}&#39;</span></span></code></pre></div><p>One ticket <code>In progress</code> per person at a time.</p><h3 id="question-tickets" tabindex="-1">Question tickets <a class="header-anchor" href="#question-tickets" aria-label="Permalink to &quot;Question tickets&quot;">​</a></h3><p>A ticket with the <code>question</code> label asks for a decision, not code. It is answered in <code>Refinement team</code>: its QCP is an ADR, committed on <code>main</code>, that settles the question. It then skips <code>Dev team</code>: <code>Code quality team</code> reviews the ADR and closes the ticket. The implementation goes into a new <code>Feature</code> ticket, linked to the question and the ADR, which starts in <code>Refinement team</code> / <code>Ready for next team</code>.</p><h2 id="gh" tabindex="-1">gh <a class="header-anchor" href="#gh" aria-label="Permalink to &quot;gh&quot;">​</a></h2><ul><li>Board commands need the <code>project</code> scope: <code>gh auth refresh -s project</code>.</li><li><code>gh project item-list 10 --owner theodo-group --format json</code> gives each item&#39;s fields as keys (<code>status</code>, <code>team progress</code>).</li><li><code>gh project item-edit</code> takes ids, not names: the project id (<code>gh project view</code>), the item id (<code>item-list</code>), the field and option ids (<code>field-list</code>).</li><li><code>Priority</code> is an org issue field, not a project field: <code>gh project</code> shows no options for it. Read and write it through GraphQL with the <code>GraphQL-Features: issue_fields</code> header (<code>issueFieldValues</code>, <code>setIssueFieldValue</code>).</li><li><code>Type</code> is set on the issue: <code>gh issue edit &lt;n&gt; --type Feature</code>.</li></ul><h2 id="branches" tabindex="-1">Branches <a class="header-anchor" href="#branches" aria-label="Permalink to &quot;Branches&quot;">​</a></h2><p>A feature&#39;s tickets land on its feature branch, <code>feat/&lt;feature&gt;-&lt;slug&gt;</code>, and the factory keeps one pull request from it to <code>main</code>. A ticket whose graph has no gate after <code>pull-request</code> merges itself, and its <code>ticket/&lt;feature&gt;-&lt;ticket&gt;</code> branch is deleted once landed; a graph with a gate keeps the ticket&#39;s pull request until the team merges it (<a href="./adr/0041-who-opens-the-features-pull-request.html">ADR-0041</a>).</p>',15)])])}const m=t(s,[["render",d]]);export{u as __pageData,m as default};
@@ -0,0 +1 @@
1
+ import{_ as t,o,c as a,a3 as i}from"./chunks/framework.hgjl6W6o.js";const u=JSON.parse('{"title":"GitHub","description":"","frontmatter":{},"headers":[],"relativePath":"docs/github.md","filePath":"docs/github.md"}'),s={name:"docs/github.md"};function d(c,e,n,r,h,l){return o(),a("div",null,[...e[0]||(e[0]=[i("",15)])])}const m=t(s,[["render",d]]);export{u as __pageData,m as default};
@@ -0,0 +1,35 @@
1
+ import{_ as t,o as s,c as a,a3 as i}from"./chunks/framework.hgjl6W6o.js";const k=JSON.parse('{"title":"The overlay","description":"","frontmatter":{},"headers":[],"relativePath":"docs/overlay.md","filePath":"docs/overlay.md"}'),d={name:"docs/overlay.md"};function o(r,e,n,h,c,l){return s(),a("div",null,[...e[0]||(e[0]=[i(`<h1 id="the-overlay" tabindex="-1">The overlay <a class="header-anchor" href="#the-overlay" aria-label="Permalink to &quot;The overlay&quot;">​</a></h1><p>The overlay is everything your project adds to or changes in the core. It lives in <code>.factory/</code>, in your repo, and <code>bernard sync</code> renders it with the core into your harness&#39;s native files (<a href="./adr/0010-how-a-project-adapts-the-core.html">ADR-0010</a>).</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>.factory/</span></span>
2
+ <span class="line"><span>├── factory.config.ts # the config: harness, project, paths, models, pipeline, adapters, handoffs, overrides</span></span>
3
+ <span class="line"><span>├── factory.lock.json # what the last sync generated; written by bernard sync, committed</span></span>
4
+ <span class="line"><span>├── slots/&lt;slot&gt;.md # content injected into core skills and agents</span></span>
5
+ <span class="line"><span>├── skills/&lt;name&gt;/ # your skills, or the core skills you override</span></span>
6
+ <span class="line"><span>├── agents/&lt;name&gt;.md # your agents, or the core agents you override</span></span>
7
+ <span class="line"><span>└── fragments/&lt;name&gt;.md # your fragments, or the core fragments you override</span></span></code></pre></div><p>Skills, agents and fragments follow the core&#39;s neutral format: frontmatter, templates and writing rules are in <a href="./../core/README.html">core/README.md</a>.</p><h2 id="config" tabindex="-1">Config <a class="header-anchor" href="#config" aria-label="Permalink to &quot;Config&quot;">​</a></h2><p><code>.factory/factory.config.ts</code> (or <code>.mts</code>, <code>.js</code>, <code>.mjs</code>) exports the config by default. <code>defineConfig</code> only types it: your editor shows what each field sets, and its default (<a href="./adr/0024-how-the-config-type-carries-its-docs.html">ADR-0024</a>). An unknown field is an error.</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { defineConfig } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;@theodo/bernard/config&#39;</span></span>
8
+ <span class="line"></span>
9
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> defineConfig</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
10
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> harness: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;claude-code&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
11
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> project: { name: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;My project&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
12
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><table tabindex="0"><thead><tr><th>Field</th><th>Default</th><th>What it sets</th></tr></thead><tbody><tr><td><code>harness</code></td><td>required</td><td><code>claude-code</code>, <code>pi</code> or <code>copilot</code>: the harness the project runs, and the files <code>bernard sync</code> writes (<code>.claude/</code>, <code>.pi/</code> or <code>.github/</code>).</td></tr><tr><td><code>project.name</code></td><td>required</td><td>The project&#39;s name, as <code>{{project.name}}</code> in templates.</td></tr><tr><td><code>project.language</code></td><td><code>English</code></td><td>The language of deliverables: PRD, review findings, PR text. Core instructions stay in English.</td></tr><tr><td><code>paths.features</code></td><td><code>docs/features</code></td><td>The root folder of feature documents: PRD, migration plan, tickets, and each ticket&#39;s record.</td></tr><tr><td><code>paths.scratch</code></td><td><code>tmp/factory</code></td><td>A folder for the steps&#39; working files. Git-ignore it.</td></tr><tr><td><code>models.claudeCode</code></td><td><code>haiku</code>, <code>sonnet</code>, <code>opus</code></td><td>The model of each agent tier (<code>fast</code>, <code>balanced</code>, <code>deep</code>) on Claude Code.</td></tr><tr><td><code>models.pi</code></td><td>none</td><td>The same on Pi. Without it, an agent runs on the session&#39;s model.</td></tr><tr><td><code>models.copilot</code></td><td>none</td><td>The same on GitHub Copilot, such as <code>claude-sonnet-4.5</code>. Without it, an agent runs on the session&#39;s model.</td></tr><tr><td><code>pipeline</code></td><td>the core&#39;s</td><td>The steps, gates and routes: <a href="#pipeline">below</a>.</td></tr><tr><td><code>adapters.forge</code></td><td>required</td><td>The forge the pull requests open on and the checks run on: <a href="#forge">below</a>.</td></tr><tr><td><code>adapters.runner</code></td><td>none</td><td>Where <code>bernard run</code> runs the steps: <a href="#runner">below</a>.</td></tr><tr><td><code>adapters.telemetry</code></td><td>JSON lines in <code>runs/</code> of <code>paths.scratch</code></td><td>Where <code>bernard run</code> writes each run&#39;s trace and session logs, and <code>bernard view</code> reads them: <a href="#telemetry">below</a>.</td></tr><tr><td><code>adapters.tracker</code></td><td>none</td><td>The tracker a feature starts from and its issues follow: <a href="#tracker">below</a>.</td></tr><tr><td><code>handoffs</code></td><td><code>{}</code></td><td>The fields each skill a step runs hands off to the next steps: <a href="#handoffs">below</a>.</td></tr><tr><td><code>overrides</code></td><td><code>[]</code></td><td>The core items your overlay replaces: <a href="#overrides">below</a>.</td></tr></tbody></table><h2 id="slots" tabindex="-1">Slots <a class="header-anchor" href="#slots" aria-label="Permalink to &quot;Slots&quot;">​</a></h2><p>A slot is a named place in a core skill or agent. <code>.factory/slots/&lt;slot&gt;.md</code> replaces its default; without the file, the default applies. A slot file is a template: it can use the variables of <a href="./../core/README.html#templates">core/README.md</a>. A slot file whose name matches no slot fails the sync.</p><table tabindex="0"><thead><tr><th>Slot</th><th>What you put there</th><th>Default</th><th>Used by</th></tr></thead><tbody><tr><td><code>checks.command</code></td><td>The command that runs your checks.</td><td>The one your <code>CLAUDE.md</code> or <code>AGENTS.md</code> documents.</td><td><code>implement</code>, <code>validate</code>, <code>code-review</code>, <code>apply-review</code>, <code>pull-request</code></td></tr><tr><td><code>context.sources</code></td><td>The documents that hold your standards, ADRs and glossary.</td><td><code>CLAUDE.md</code> or <code>AGENTS.md</code>, <code>README.md</code>, and what they reference.</td><td><code>implement</code>, <code>validate</code>, <code>apply-review</code>, the reviewers</td></tr><tr><td><code>feature.documents</code></td><td>Which documents a feature has, where, and which parts to read.</td><td>The migration plan and the PRD in the feature&#39;s folder.</td><td><code>implement</code>, <code>validate</code>, <code>apply-review</code></td></tr><tr><td><code>ticket.format</code></td><td>Where a ticket&#39;s file lives, and where it states its goal, scope, acceptance criteria and kept legacy behaviour.</td><td><code>&lt;features&gt;/&lt;feature&gt;/tickets/&lt;ticket&gt;-*.md</code>.</td><td><code>implement</code>, <code>validate</code>, <code>apply-review</code></td></tr><tr><td><code>legacy.extract</code></td><td>How to read one legacy function&#39;s source.</td><td>Read the function in its file.</td><td><code>implement</code>, <code>validate</code></td></tr><tr><td><code>review.generated</code></td><td>The trees your project generates from a source it also commits, comma-separated, so the review bundle lists them without content.</td><td>None: the bundle carries every touched file.</td><td><code>code-review</code></td></tr><tr><td><code>review.detectors</code></td><td>A table of the defect families a tool guarantees, so the reviewers leave them to it.</td><td>No detector: the reviewers judge every family.</td><td>the reviewers</td></tr><tr><td><code>review.quality.criteria</code></td><td>What the quality reviewer checks: shape, letter and names.</td><td>Documented architecture, ADRs, canonical patterns, module depth, the code style, the glossary&#39;s canonical terms, vague words.</td><td><code>review-quality</code></td></tr><tr><td><code>review.security-bugs.criteria</code></td><td>What the security, bug and performance reviewer checks and hunts.</td><td>Validation, authorization, injection, secrets, error leakage, absent values, boundaries, dates, numbers, state, errors, concurrency, slowness.</td><td><code>review-security-bugs</code></td></tr><tr><td><code>review.tests.criteria</code></td><td>What the test reviewer judges.</td><td>Tautologies, assertions, titles, one scenario per branch, cases that matter.</td><td><code>review-tests</code></td></tr></tbody></table><p>A <code>review.*.criteria</code> slot replaces the whole default list: copy it from the core agent and edit it, rather than starting blank.</p><h2 id="skills-agents-and-fragments" tabindex="-1">Skills, agents and fragments <a class="header-anchor" href="#skills-agents-and-fragments" aria-label="Permalink to &quot;Skills, agents and fragments&quot;">​</a></h2><p>A skill in <code>.factory/skills/&lt;name&gt;/SKILL.md</code> that the core does not have is added to the project, like an agent in <code>.factory/agents/</code> or a fragment in <code>.factory/fragments/</code>.</p><p>The core ships the delivery skills: <code>implement</code>, <code>validate</code>, <code>code-review</code>, <code>apply-review</code>, <code>pull-request</code> (for GitHub: it opens the pull request and drives its CI green), the agents of the three code reviewers, and <code>tickets</code>, which reports the feature as a single ticket. Your overlay provides the others the pipeline runs: <code>pull-request</code> on another forge than GitHub, and for <code>bernard run</code> the upstream steps. A feature split into several tickets overrides <code>tickets</code> (<a href="https://github.com/theodo-group/bernard/tree/v0.1.0-beta.1/demo/app/.factory/skills" target="_blank" rel="noreferrer">the demo&#39;s</a>).</p><p>A skill a step runs must set <code>disable-model-invocation: true</code>: only the orchestrator starts a step.</p><h2 id="handoffs" tabindex="-1">Handoffs <a class="header-anchor" href="#handoffs" aria-label="Permalink to &quot;Handoffs&quot;">​</a></h2><p>A step&#39;s session ends with its handoff, which the next steps read, constrained by its skill&#39;s schema (<a href="./adr/0025-how-a-steps-handoff-is-written-and-checked.html">ADR-0025</a>). <code>handoffs</code> adds fields to a core skill&#39;s handoff, or declares your own skill&#39;s, keyed on the skill. Build each field with the <code>z</code> the config module exports:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { defineConfig, z } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;@theodo/bernard/config&#39;</span></span>
13
+ <span class="line"></span>
14
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> defineConfig</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
15
+ <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // …</span></span>
16
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> handoffs: {</span></span>
17
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> validate: { bundleSize: z.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">number</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">describe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;The size of the built bundle, in kB.&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) },</span></span>
18
+ <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;db-migration&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { migrations: z.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">array</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(z.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">describe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;The migrations the step added.&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) },</span></span>
19
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
20
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><p>A core skill&#39;s fields cannot be declared again, so you can neither remove nor loosen one. <code>.describe()</code> tells the model what to put there. An overlay skill a step runs without an entry hands off <code>{ summary }</code> only, and <code>bernard sync</code> warns about it.</p><h2 id="overrides" tabindex="-1">Overrides <a class="header-anchor" href="#overrides" aria-label="Permalink to &quot;Overrides&quot;">​</a></h2><p>An overlay item with the name of a core item replaces it whole. It must be declared, with its reason:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">overrides</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span></span>
21
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { kind: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;skill&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, name: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;pull-request&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, reason: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;No forge: the skill pushes the branch and runs the checks on the merge with the base.&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
22
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span></code></pre></div><p><code>kind</code> is <code>skill</code>, <code>agent</code> or <code>fragment</code>. The sync fails on an undeclared replacement, and on a declared one the overlay does not provide. The lock keeps a hash of each overridden core item: when the core changes it, <code>bernard sync</code> warns you to review your version.</p><p>Prefer a slot to an override: a slot keeps the rest of the core skill up to date.</p><h2 id="pipeline" tabindex="-1">Pipeline <a class="header-anchor" href="#pipeline" aria-label="Permalink to &quot;Pipeline&quot;">​</a></h2><p><code>pipeline</code> reshapes the steps a feature goes through. Every field is optional; <a href="./pipeline.html">pipeline.md</a> draws the default, and <code>bernard pipeline</code> draws yours.</p><table tabindex="0"><thead><tr><th>Field</th><th>Default</th><th>What it sets</th></tr></thead><tbody><tr><td><code>upstream</code></td><td><code>legacy-analysis</code> (auto), <code>prd</code> (generate, pm), <code>migration-plan</code> (interactive, dev)</td><td>The steps before the tickets, in order. Each has an <code>id</code>, a <code>mode</code> (<code>auto</code>, <code>generate</code> or <code>interactive</code>), an <code>approver</code> unless <code>auto</code>, and a gate <code>policy</code>.</td></tr><tr><td><code>openQuestions</code></td><td><code>block</code></td><td>What happens when an autonomous step returns open questions: <code>block</code> waits for a human, <code>decide-and-log</code> goes on and logs the decision.</td></tr><tr><td><code>scopes</code></td><td>none</td><td>The paths a step may write, as globs, keyed by the skill it runs: <code>{ implement: { write: [&#39;src/**&#39;, &#39;test/**&#39;] } }</code>. Two steps that run one skill share its scope (<a href="./adr/0015-how-a-project-shapes-its-ticket-flow.html">ADR-0015</a>). A step without a scope may write anywhere; a change outside its scope fails the step and is restored (<a href="./adr/0014-what-a-step-may-change.html">ADR-0014</a>).</td></tr><tr><td><code>maxCrashes</code></td><td><code>2</code></td><td>How many times a crashed step is rerun.</td></tr><tr><td><code>delivery</code></td><td>the core&#39;s graph</td><td>The graphs of steps a ticket goes through: below.</td></tr></tbody></table><h3 id="the-delivery-graphs" tabindex="-1">The delivery graphs <a class="header-anchor" href="#the-delivery-graphs" aria-label="Permalink to &quot;The delivery graphs&quot;">​</a></h3><p><code>delivery.graphs</code> names the project&#39;s ticket graphs (<a href="./adr/0028-several-ticket-flows-in-one-pipeline.html">ADR-0028</a>). Without it, there is one graph, <code>ticket</code>, the core&#39;s. With one graph, every ticket goes through it. With several, every ticket names its graph in the <code>graph</code> field the upstream step that reports the tickets writes; a ticket that names none, or an unknown one, fails the feature with the list of the declared graphs.</p><p>Each graph&#39;s <code>steps</code> names its steps and where their results go (<a href="./adr/0015-how-a-project-shapes-its-ticket-flow.html">ADR-0015</a>). Its <code>start</code> is the first step (<code>implement</code>), and its <code>maxFailures</code> the failures a step may have before its budget is spent (<code>2</code>).</p><p>A step that runs a skill:</p><table tabindex="0"><thead><tr><th>Field</th><th>Default</th><th>Meaning</th></tr></thead><tbody><tr><td><code>skill</code></td><td>the step&#39;s name</td><td>The skill it runs.</td></tr><tr><td><code>onPass</code></td><td>required</td><td>The next step, or <code>DONE</code>.</td></tr><tr><td><code>onFail</code></td><td>itself</td><td>Where a failure goes: a step, or a map from failure class to step.</td></tr><tr><td><code>onExhausted</code></td><td><code>BLOCK</code></td><td>Where it goes once its failure budget is spent: a step, <code>DONE</code>, or <code>BLOCK</code>.</td></tr><tr><td><code>maxFailures</code></td><td>the graph&#39;s <code>maxFailures</code></td><td>Its own failure budget.</td></tr></tbody></table><p>A gate, where a human decides:</p><table tabindex="0"><thead><tr><th>Field</th><th>Default</th><th>Meaning</th></tr></thead><tbody><tr><td><code>gate</code></td><td>required</td><td><code>true</code>.</td></tr><tr><td><code>onPass</code></td><td>required</td><td>The next step once approved or merged, or <code>DONE</code>.</td></tr><tr><td><code>onChanges</code></td><td>none</td><td>The step that receives the reviewer&#39;s change requests. Without it, the team acts on its review itself.</td></tr><tr><td><code>policy</code></td><td><code>block</code></td><td><code>block</code> waits for a human; <code>decide-and-log</code> approves and logs it.</td></tr></tbody></table><p>A gate after the <code>pull-request</code> step keeps the ticket&#39;s own pull request to the feature branch, which the team merges. A graph with no gate after it, of any policy, merges its tickets itself: a ticket lands on the feature branch with no pull request of its own, and the checks waited for are those of the feature&#39;s pull request, which the factory opens in both cases (<a href="./adr/0041-who-opens-the-features-pull-request.html">ADR-0041</a>).</p><p>Setting <code>delivery.graphs</code> replaces the core&#39;s graph, and a graph&#39;s <code>steps</code> replaces its default steps: write every step. The sync checks every graph: every target exists, every step is reachable from <code>start</code>, the <code>onPass</code> edges reach <code>DONE</code>, and every step&#39;s skill exists with <code>disable-model-invocation: true</code>.</p><h2 id="runner" tabindex="-1">Runner <a class="header-anchor" href="#runner" aria-label="Permalink to &quot;Runner&quot;">​</a></h2><p><code>bernard run</code> runs each step on the project&#39;s runner. There are two adapters: <code>local</code>, on the developer&#39;s machine, and <code>coder</code>, which gives each ticket its own Coder workspace.</p><h3 id="local" tabindex="-1">Local <a class="header-anchor" href="#local" aria-label="Permalink to &quot;Local&quot;">​</a></h3><p>The feature&#39;s steps run in a git worktree on the feature&#39;s branch, and each ticket in its own on its branch, all under <code>&lt;scratch&gt;/worktrees/</code>. The checkout is left as it is (<a href="./adr/0030-a-runner-without-coder.html">ADR-0030</a>):</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">adapters</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
23
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> runner</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;local&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">worktree</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">copy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;.env&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">], </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">setup</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;pnpm install --frozen-lockfile&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> } },</span></span>
24
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">},</span></span></code></pre></div><table tabindex="0"><thead><tr><th>Field</th><th>Default</th><th>Meaning</th></tr></thead><tbody><tr><td><code>base</code></td><td><code>main</code></td><td>The local branch a feature branch starts from and merges into. Pull it before a run.</td></tr><tr><td><code>worktree.copy</code></td><td>none</td><td>Globs of the checkout&#39;s files that git ignores, such as <code>.env</code> or local settings, copied into each new worktree: a worktree only has the files its branch tracks. A tracked file is never copied.</td></tr><tr><td><code>worktree.setup</code></td><td>none</td><td>A shell command run in each worktree before its first step of a run, after the copies: installs the dependencies, makes a database. A resumed run runs it again in the worktrees it reuses, so it must be safe to run twice. It gets the machine&#39;s basic variables (<code>PATH</code>, <code>HOME</code>, <code>SSH_AUTH_SOCK</code>…) and the forges&#39; sign-in (<code>GH_TOKEN</code>, <code>GITLAB_TOKEN</code>…), not the factory&#39;s environment.</td></tr></tbody></table><p>The runner refuses to start unless the checkout has the branch <code>base</code> and git ignores <code>paths.scratch</code>. Nothing isolates the sessions from the machine, and worktrees in parallel share its local services: keep them apart with <code>worktree.setup</code>, such as one database per worktree, as the <a href="./../demo/README.html">demo</a> does.</p><h3 id="coder" tabindex="-1">Coder <a class="header-anchor" href="#coder" aria-label="Permalink to &quot;Coder&quot;">​</a></h3><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">adapters</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
25
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> runner</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;coder&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;my-project&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">repo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;my-project&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">base</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;main&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
26
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">},</span></span></code></pre></div><table tabindex="0"><thead><tr><th>Field</th><th>Default</th><th>Meaning</th></tr></thead><tbody><tr><td><code>template</code></td><td>required</td><td>The Coder template a ticket&#39;s workspace is created from, with the project&#39;s services.</td></tr><tr><td><code>repo</code></td><td>required</td><td>The repository the workspaces clone, as the template names it.</td></tr><tr><td><code>base</code></td><td><code>main</code></td><td>The branch a feature branch starts from and merges into. A feature&#39;s tickets start from and merge into its feature branch (ADR-0029).</td></tr><tr><td><code>local</code></td><td><code>false</code></td><td>Runs the workspaces on a Coder that <code>bernard run</code> starts in Docker on this machine (<a href="./recipes.html#a-local-coder">recipes</a>).</td></tr></tbody></table><p>The template must fulfil the runner&#39;s contract: see <a href="./recipes.html#change-the-runner">Change the runner</a> and the <a href="https://github.com/theodo-group/bernard/blob/v0.1.0-beta.1/core/coder/template/main.tf" target="_blank" rel="noreferrer">reference template</a>.</p><h2 id="telemetry" tabindex="-1">Telemetry <a class="header-anchor" href="#telemetry" aria-label="Permalink to &quot;Telemetry&quot;">​</a></h2><p><code>bernard run</code> writes what a run shows whoever follows it, its trace, its machines and their snapshots, and each session&#39;s log, to the project&#39;s telemetry. <code>bernard view</code> serves the run viewer on <a href="http://localhost:4321" target="_blank" rel="noreferrer">http://localhost:4321</a> (<code>--port</code> changes it) and reads the runs back from there, never from a run&#39;s files (ADR-0031). The factory&#39;s package ships the viewer (ADR-0035), and the documentation site, which the viewer serves under <code>/docs/</code> (ADR-0037). The only adapter so far keeps each run in its own folder as JSON lines:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">adapters</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
27
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> telemetry</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;jsonl&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">dir</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;tmp/factory/runs&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
28
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">},</span></span></code></pre></div><table tabindex="0"><thead><tr><th>Field</th><th>Default</th><th>Meaning</th></tr></thead><tbody><tr><td><code>dir</code></td><td><code>runs/</code> in <code>paths.scratch</code></td><td>The folder of the runs, from the project&#39;s root. A session&#39;s log can quote secrets: git-ignore it. The default is, since <code>bernard init</code> ignores the scratch folder.</td></tr></tbody></table><h2 id="forge" tabindex="-1">Forge <a class="header-anchor" href="#forge" aria-label="Permalink to &quot;Forge&quot;">​</a></h2><p>The <code>pull-request</code> skill runs <code>bernard pull-request</code>, which merges the base branch into the ticket&#39;s, pushes it, opens the pull request or updates the one already open, and waits for its checks (ADR-0036). The git half is the same on every forge; the forge&#39;s half goes through the adapter <code>adapters.forge</code> names, over the forge&#39;s own command-line tool, signed in as the developer. <code>bernard init --forge</code> sets it (ADR-0027):</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">adapters</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
29
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> forge</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;gitlab&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;gitlab.example.com&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
30
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">},</span></span></code></pre></div><table tabindex="0"><thead><tr><th><code>type</code></th><th>Tool</th><th>Pull request and checks</th></tr></thead><tbody><tr><td><code>github</code></td><td><code>gh</code>, signed in with <code>gh auth login</code> or <code>GH_TOKEN</code></td><td>The pull request, and the checks of its last commit. A failed check&#39;s log comes from <code>gh run view --log-failed</code>.</td></tr><tr><td><code>gitlab</code></td><td><code>glab</code>, signed in with <code>glab auth login</code></td><td>The merge request, and the jobs of its head pipeline. A job allowed to fail passes; a manual job that blocks the pipeline waits for a person. <code>host</code>, <code>gitlab.com</code> by default, is the instance, given to <code>glab</code> as <code>GITLAB_HOST</code>.</td></tr><tr><td><code>azure-devops</code></td><td><code>az</code>, with its <code>azure-devops</code> extension, signed in</td><td>The pull request, and its build and status policies: the other policies are the team&#39;s review. <code>az</code> serves no log: a failed check gives its build&#39;s link.</td></tr></tbody></table><p>The command keeps no state: it reads the forge again on each run. Its green is fail-closed: after 90 seconds for every check to register, two reads a poll apart give the same set, every check passed, within 9 minutes. It prints <code>CI_GREEN</code>, or stops on <code>CI_RED</code>, <code>CI_PENDING</code>, <code>CI_NO_CHECKS</code> or <code>CI_APPROVAL</code> with a <code>RESUME:</code> line for the session. The sessions find the <code>bernard</code> your project installs in <code>.factory/</code>: the local runner and the reference Coder template put <code>.factory/node_modules/.bin</code> on their PATH.</p><h2 id="tracker" tabindex="-1">Tracker <a class="header-anchor" href="#tracker" aria-label="Permalink to &quot;Tracker&quot;">​</a></h2><p>With a tracker, <code>bernard run --feature DATA-12</code> starts the feature <code>data-12</code> from the issue <code>DATA-12</code>: its summary and description, converted to Markdown, are the feature input, kept in the <code>start</code> line of the feature&#39;s record and given to the feature&#39;s own steps. The <code>tickets</code> step may return each ticket&#39;s <code>trackerKey</code>, the issue the team created for it. Once the step passes, <code>bernard run</code> checks each key, and fails the step, with the class <code>unknown-tracker-key</code>, on a key the project does not hold, a key two tickets share, the feature&#39;s own, or a key another feature tracks. As the run goes on, the issue of the feature and of each ticket with a key move to the status their tracker event maps to, and the <code>pull-request</code> skills put the ticket&#39;s key in the title, as its Conventional Commits scope: <code>feat(DATA-12): …</code>. The only adapter so far is Jira Cloud:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">adapters</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
31
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> tracker</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
32
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;jira&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
33
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> statuses</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">started</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;In Progress&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">inReview</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;In Review&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">blocked</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;Blocked&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">done</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;Done&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
34
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
35
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">},</span></span></code></pre></div><table tabindex="0"><thead><tr><th>Field</th><th>Default</th><th>Meaning</th></tr></thead><tbody><tr><td><code>statuses</code></td><td>required</td><td>The status each tracker event moves an issue to, named exactly as the REST API names it: <code>started</code> while a step runs, <code>inReview</code> at a gate, <code>blocked</code> on open questions or a retry, <code>done</code> once the ticket, or every ticket of the feature, is <code>DONE</code>. An event left out moves nothing. <code>bernard run</code> refuses a status that no transition of the project&#39;s workflow leads to.</td></tr></tbody></table><p>Its site and credentials come from the environment, never from the overlay. <code>bernard run</code> and <code>bernard resume</code> refuse to start without any of them, and name it:</p><table tabindex="0"><thead><tr><th>Variable</th><th>Meaning</th></tr></thead><tbody><tr><td><code>JIRA_BASE_URL</code></td><td>The site, such as <code>https://acme.atlassian.net</code>.</td></tr><tr><td><code>JIRA_EMAIL</code></td><td>The account the API token belongs to.</td></tr><tr><td><code>JIRA_API_TOKEN</code></td><td>An <a href="https://id.atlassian.com/manage-profile/security/api-tokens" target="_blank" rel="noreferrer">API token</a> of that account, which reads the project&#39;s issues and moves them.</td></tr><tr><td><code>JIRA_PROJECT</code></td><td>The key of the project the issues are in, such as <code>DATA</code>.</td></tr></tbody></table><p>A feature starts only from an issue of <code>JIRA_PROJECT</code>, and from none that another feature tracks. The issue&#39;s attachments, comments and subtasks are left out of the feature input. <code>bernard resume --feature DATA-12</code> takes the key as well; <code>bernard status</code> takes the feature&#39;s id, <code>data-12</code>, since it opens no tracker. A resumed run moves each issue once, straight to where the records leave it. Bernard creates no issue: the team creates the tickets&#39; issues, and the <code>tickets</code> skill returns their keys (<a href="https://github.com/theodo-group/bernard/issues/146" target="_blank" rel="noreferrer">#146</a>).</p><p>A move that fails, such as one the workflow forbids from the issue&#39;s current status, is a warning in the run&#39;s trace and log: the run goes on, and the issue moves again at its next event.</p>`,65)])])}const u=t(d,[["render",o]]);export{k as __pageData,u as default};
@@ -0,0 +1 @@
1
+ import{_ as t,o as s,c as a,a3 as i}from"./chunks/framework.hgjl6W6o.js";const k=JSON.parse('{"title":"The overlay","description":"","frontmatter":{},"headers":[],"relativePath":"docs/overlay.md","filePath":"docs/overlay.md"}'),d={name:"docs/overlay.md"};function o(r,e,n,h,c,l){return s(),a("div",null,[...e[0]||(e[0]=[i("",65)])])}const u=t(d,[["render",o]]);export{k as __pageData,u as default};
@@ -0,0 +1 @@
1
+ import{_ as c,C as _,o as a,c as p,a3 as d,b as n,w as t,a as i,E as s,a4 as o,j as l}from"./chunks/framework.hgjl6W6o.js";const v=JSON.parse('{"title":"Pipeline","description":"","frontmatter":{},"headers":[],"relativePath":"docs/pipeline.md","filePath":"docs/pipeline.md"}'),u={name:"docs/pipeline.md"};function m(A,e,h,k,f,E){const r=_("Mermaid");return a(),p("div",null,[e[3]||(e[3]=d('<h1 id="pipeline" tabindex="-1">Pipeline <a class="header-anchor" href="#pipeline" aria-label="Permalink to &quot;Pipeline&quot;">​</a></h1><p>A feature goes through <code>legacy-analysis</code> (auto) → <code>prd</code> (generate, validated by pm) → <code>migration-plan</code> (interactive, validated by dev), then <code>tickets</code>, then each ticket is delivered in its own machine, in dependency order. Amber states wait on a human.</p><h2 id="feature" tabindex="-1">Feature <a class="header-anchor" href="#feature" aria-label="Permalink to &quot;Feature&quot;">​</a></h2>',3)),(a(),n(o,null,{default:t(()=>[s(r,{id:"mermaid-10",class:"mermaid",graph:"stateDiagram-v2%0A%20%20%5B*%5D%20--%3E%20feature_legacy_analysis%0A%20%20state%20%22legacy-analysis%22%20as%20feature_legacy_analysis%0A%20%20state%20%22prd%22%20as%20feature_prd%20%7B%0A%20%20%20%20%5B*%5D%20--%3E%20feature_prd_generating%0A%20%20%20%20state%20%22generating%22%20as%20feature_prd_generating%0A%20%20%20%20state%20%22review%22%20as%20feature_prd_review%0A%20%20%20%20state%20%22approved%22%20as%20feature_prd_approved%0A%20%20%20%20feature_prd_approved%20--%3E%20%5B*%5D%0A%20%20%20%20feature_prd_generating%20--%3E%20feature_prd_review%20%3A%20done%0A%20%20%20%20feature_prd_review%20--%3E%20feature_prd_approved%20%3A%20PR.MERGED%0A%20%20%20%20feature_prd_review%20--%3E%20feature_prd_generating%20%3A%20GATE.CHANGES_REQUESTED%0A%20%20%7D%0A%20%20state%20%22migration-plan%22%20as%20feature_migration_plan%20%7B%0A%20%20%20%20%5B*%5D%20--%3E%20feature_migration_plan_with_human%0A%20%20%20%20state%20%22with-human%22%20as%20feature_migration_plan_with_human%0A%20%20%20%20state%20%22approval%22%20as%20feature_migration_plan_approval%0A%20%20%20%20state%20%22approved%22%20as%20feature_migration_plan_approved%0A%20%20%20%20feature_migration_plan_approved%20--%3E%20%5B*%5D%0A%20%20%20%20feature_migration_plan_with_human%20--%3E%20feature_migration_plan_approval%20%3A%20STEP.COMPLETED%0A%20%20%20%20feature_migration_plan_approval%20--%3E%20feature_migration_plan_approved%20%3A%20GATE.APPROVED%0A%20%20%20%20feature_migration_plan_approval%20--%3E%20feature_migration_plan_with_human%20%3A%20GATE.CHANGES_REQUESTED%0A%20%20%7D%0A%20%20state%20%22tickets%22%20as%20feature_tickets%0A%20%20state%20%22delivering%22%20as%20feature_delivering%0A%20%20state%20%22done%22%20as%20feature_done%0A%20%20feature_done%20--%3E%20%5B*%5D%0A%20%20feature_legacy_analysis%20--%3E%20feature_prd%20%3A%20done%0A%20%20feature_prd%20--%3E%20feature_migration_plan%20%3A%20done%0A%20%20feature_migration_plan%20--%3E%20feature_tickets%20%3A%20done%0A%20%20feature_tickets%20--%3E%20feature_delivering%20%3A%20done%0A%20%20feature_delivering%20--%3E%20feature_done%20%3A%20always%20%5BallTicketsDone%5D%0A%20%20classDef%20human%20fill%3A%23fde68a%2Cstroke%3A%23b45309%2Ccolor%3A%231f2937%0A%20%20class%20feature_prd_review%2Cfeature_migration_plan_with_human%2Cfeature_migration_plan_approval%20human%0A"})]),fallback:t(()=>[...e[0]||(e[0]=[i(" Loading... ",-1)])]),_:1})),e[4]||(e[4]=d('<h2 id="ticket" tabindex="-1">Ticket <a class="header-anchor" href="#ticket" aria-label="Permalink to &quot;Ticket&quot;">​</a></h2><p>Every ticket goes through the one graph below. Each step leaves through its pass edge. A failed result follows the step&#39;s failure edges as long as it has failures left, then its exhausted edge; <code>BLOCK</code> waits for a human <code>RETRY</code>, which resumes where the last failure was sent. A gate passes when the team approves or merges the pull request; the team acts on its review itself, unless the gate sends change requests back to a step. A graph with no gate after its <code>pull-request</code> step merges its tickets itself into the feature branch, so a ticket&#39;s <code>DONE</code> counts as merged. A ticket&#39;s dependents start once it is <code>DONE</code>: off the feature branch when it was merged, stacked on its branch when it was not.</p><h3 id="ticket-1" tabindex="-1"><code>ticket</code> <a class="header-anchor" href="#ticket-1" aria-label="Permalink to &quot;`ticket`&quot;">​</a></h3><p>It starts at <code>implement</code>.</p><table tabindex="0"><thead><tr><th>Step</th><th>Budget</th><th>Pass</th><th>Failure</th><th>Exhausted</th></tr></thead><tbody><tr><td><code>implement</code></td><td>2 failures</td><td><code>validate</code></td><td><code>implement</code></td><td><code>BLOCK</code></td></tr><tr><td><code>validate</code></td><td>2 failures</td><td><code>code-review</code></td><td><code>implement</code></td><td><code>BLOCK</code></td></tr><tr><td><code>code-review</code></td><td>2 failures</td><td><code>pull-request</code></td><td><code>apply-review</code></td><td><code>pull-request</code></td></tr><tr><td><code>apply-review</code></td><td>2 failures</td><td><code>code-review</code></td><td><code>apply-review</code></td><td><code>BLOCK</code></td></tr><tr><td><code>pull-request</code></td><td>2 failures</td><td><code>human-review</code></td><td><code>pull-request</code></td><td><code>BLOCK</code></td></tr><tr><td><code>human-review</code></td><td>gate, block</td><td><code>DONE</code></td><td></td><td></td></tr></tbody></table>',5)),(a(),n(o,null,{default:t(()=>[s(r,{id:"mermaid-148",class:"mermaid",graph:"stateDiagram-v2%0A%20%20%5B*%5D%20--%3E%20ticket_implement%0A%20%20state%20%22implement%22%20as%20ticket_implement%0A%20%20state%20%22validate%22%20as%20ticket_validate%0A%20%20state%20%22code-review%22%20as%20ticket_code_review%0A%20%20state%20%22apply-review%22%20as%20ticket_apply_review%0A%20%20state%20%22pull-request%22%20as%20ticket_pull_request%0A%20%20state%20%22human-review%22%20as%20ticket_human_review%0A%20%20state%20%22BLOCK%22%20as%20ticket_BLOCK%0A%20%20state%20%22DONE%22%20as%20ticket_DONE%0A%20%20ticket_DONE%20--%3E%20%5B*%5D%0A%20%20ticket_implement%20--%3E%20ticket_implement%20%3A%20failed%0A%20%20ticket_implement%20--%3E%20ticket_BLOCK%20%3A%20no%20failures%20left%0A%20%20ticket_implement%20--%3E%20ticket_validate%20%3A%20passed%0A%20%20ticket_validate%20--%3E%20ticket_implement%20%3A%20failed%0A%20%20ticket_validate%20--%3E%20ticket_BLOCK%20%3A%20no%20failures%20left%0A%20%20ticket_validate%20--%3E%20ticket_code_review%20%3A%20passed%0A%20%20ticket_code_review%20--%3E%20ticket_apply_review%20%3A%20failed%0A%20%20ticket_code_review%20--%3E%20ticket_pull_request%20%3A%20no%20failures%20left%0A%20%20ticket_code_review%20--%3E%20ticket_pull_request%20%3A%20passed%0A%20%20ticket_apply_review%20--%3E%20ticket_apply_review%20%3A%20failed%0A%20%20ticket_apply_review%20--%3E%20ticket_BLOCK%20%3A%20no%20failures%20left%0A%20%20ticket_apply_review%20--%3E%20ticket_code_review%20%3A%20passed%0A%20%20ticket_pull_request%20--%3E%20ticket_pull_request%20%3A%20failed%0A%20%20ticket_pull_request%20--%3E%20ticket_BLOCK%20%3A%20no%20failures%20left%0A%20%20ticket_pull_request%20--%3E%20ticket_human_review%20%3A%20passed%0A%20%20ticket_human_review%20--%3E%20ticket_DONE%20%3A%20GATE.APPROVED%0A%20%20ticket_human_review%20--%3E%20ticket_DONE%20%3A%20PR.MERGED%0A%20%20ticket_BLOCK%20--%3E%20ticket_implement%20%3A%20RETRY%0A%20%20ticket_BLOCK%20--%3E%20ticket_apply_review%20%3A%20RETRY%0A%20%20ticket_BLOCK%20--%3E%20ticket_pull_request%20%3A%20RETRY%0A%20%20classDef%20human%20fill%3A%23fde68a%2Cstroke%3A%23b45309%2Ccolor%3A%231f2937%0A%20%20class%20ticket_human_review%2Cticket_BLOCK%20human%0A"})]),fallback:t(()=>[...e[1]||(e[1]=[i(" Loading... ",-1)])]),_:1})),e[5]||(e[5]=l("h2",{id:"inside-a-step",tabindex:"-1"},[i("Inside a step "),l("a",{class:"header-anchor",href:"#inside-a-step","aria-label":'Permalink to "Inside a step"'},"​")],-1)),e[6]||(e[6]=l("p",null,"Every box above is a step that runs in a fresh harness session. A crashed run is run again up to 2 times before the step parks. Open questions park the step until a human answers. A blocked result parks the step until a human retries it, once the cause is fixed.",-1)),(a(),n(o,null,{default:t(()=>[s(r,{id:"mermaid-155",class:"mermaid",graph:"stateDiagram-v2%0A%20%20%5B*%5D%20--%3E%20ticket_implement_running%0A%20%20state%20%22running%22%20as%20ticket_implement_running%0A%20%20state%20%22awaitingAnswers%22%20as%20ticket_implement_awaitingAnswers%0A%20%20state%20%22failed%22%20as%20ticket_implement_failed%0A%20%20state%20%22blocked%22%20as%20ticket_implement_blocked%0A%20%20state%20%22completed%22%20as%20ticket_implement_completed%0A%20%20ticket_implement_completed%20--%3E%20%5B*%5D%0A%20%20ticket_implement_running%20--%3E%20ticket_implement_awaitingAnswers%20%3A%20done%20%5BblockedOnQuestions%5D%0A%20%20ticket_implement_running%20--%3E%20ticket_implement_blocked%20%3A%20blocked%0A%20%20ticket_implement_running%20--%3E%20ticket_implement_completed%20%3A%20done%0A%20%20ticket_implement_running%20--%3E%20ticket_implement_running%20%3A%20error%20%5BcrashesLeft%5D%0A%20%20ticket_implement_running%20--%3E%20ticket_implement_failed%20%3A%20error%0A%20%20ticket_implement_awaitingAnswers%20--%3E%20ticket_implement_running%20%3A%20QUESTIONS.ANSWERED%20%5BanswersEveryQuestion%5D%0A%20%20ticket_implement_failed%20--%3E%20ticket_implement_running%20%3A%20RETRY%0A%20%20ticket_implement_blocked%20--%3E%20ticket_implement_running%20%3A%20RETRY%0A%20%20classDef%20human%20fill%3A%23fde68a%2Cstroke%3A%23b45309%2Ccolor%3A%231f2937%0A%20%20class%20ticket_implement_awaitingAnswers%2Cticket_implement_failed%2Cticket_implement_blocked%20human%0A"})]),fallback:t(()=>[...e[2]||(e[2]=[i(" Loading... ",-1)])]),_:1})),e[7]||(e[7]=d('<h2 id="events" tabindex="-1">Events <a class="header-anchor" href="#events" aria-label="Permalink to &quot;Events&quot;">​</a></h2><p>A human sends an event with <code>bernard resume --feature &lt;slug&gt;</code>, adding <code>--ticket &lt;id&gt;</code> for a ticket&#39;s; a run that stalls prints the command that fits.</p><table tabindex="0"><thead><tr><th>Event</th><th>Sent when</th><th>From the command line</th></tr></thead><tbody><tr><td><code>PR.MERGED</code></td><td>the reviewer merges the pull request of an upstream step or of a ticket</td><td></td></tr><tr><td><code>GATE.CHANGES_REQUESTED</code></td><td>the reviewer or approver asks for changes, with feedback</td><td><code>--request-changes &quot;&lt;feedback&gt;&quot;</code></td></tr><tr><td><code>STEP.COMPLETED</code></td><td>a human finishes an interactive step in their harness</td><td><code>--upstream-done</code></td></tr><tr><td><code>GATE.APPROVED</code></td><td>the approver validates an interactive step, or the team approves a ticket&#39;s pull request without merging it</td><td><code>--approve</code></td></tr><tr><td><code>QUESTIONS.ANSWERED</code></td><td>a human answers a step&#39;s open questions</td><td><code>--answer &lt;questionId&gt;=&lt;text&gt;</code>, once per question</td></tr><tr><td><code>RETRY</code></td><td>a human relaunches a parked step, or a ticket out of failures</td><td><code>--retry</code></td></tr></tbody></table>',3))])}const w=c(u,[["render",m]]);export{v as __pageData,w as default};
@@ -0,0 +1 @@
1
+ import{_ as c,C as _,o as a,c as p,a3 as d,b as n,w as t,a as i,E as s,a4 as o,j as l}from"./chunks/framework.hgjl6W6o.js";const v=JSON.parse('{"title":"Pipeline","description":"","frontmatter":{},"headers":[],"relativePath":"docs/pipeline.md","filePath":"docs/pipeline.md"}'),u={name:"docs/pipeline.md"};function m(A,e,h,k,f,E){const r=_("Mermaid");return a(),p("div",null,[e[3]||(e[3]=d("",3)),(a(),n(o,null,{default:t(()=>[s(r,{id:"mermaid-10",class:"mermaid",graph:"stateDiagram-v2%0A%20%20%5B*%5D%20--%3E%20feature_legacy_analysis%0A%20%20state%20%22legacy-analysis%22%20as%20feature_legacy_analysis%0A%20%20state%20%22prd%22%20as%20feature_prd%20%7B%0A%20%20%20%20%5B*%5D%20--%3E%20feature_prd_generating%0A%20%20%20%20state%20%22generating%22%20as%20feature_prd_generating%0A%20%20%20%20state%20%22review%22%20as%20feature_prd_review%0A%20%20%20%20state%20%22approved%22%20as%20feature_prd_approved%0A%20%20%20%20feature_prd_approved%20--%3E%20%5B*%5D%0A%20%20%20%20feature_prd_generating%20--%3E%20feature_prd_review%20%3A%20done%0A%20%20%20%20feature_prd_review%20--%3E%20feature_prd_approved%20%3A%20PR.MERGED%0A%20%20%20%20feature_prd_review%20--%3E%20feature_prd_generating%20%3A%20GATE.CHANGES_REQUESTED%0A%20%20%7D%0A%20%20state%20%22migration-plan%22%20as%20feature_migration_plan%20%7B%0A%20%20%20%20%5B*%5D%20--%3E%20feature_migration_plan_with_human%0A%20%20%20%20state%20%22with-human%22%20as%20feature_migration_plan_with_human%0A%20%20%20%20state%20%22approval%22%20as%20feature_migration_plan_approval%0A%20%20%20%20state%20%22approved%22%20as%20feature_migration_plan_approved%0A%20%20%20%20feature_migration_plan_approved%20--%3E%20%5B*%5D%0A%20%20%20%20feature_migration_plan_with_human%20--%3E%20feature_migration_plan_approval%20%3A%20STEP.COMPLETED%0A%20%20%20%20feature_migration_plan_approval%20--%3E%20feature_migration_plan_approved%20%3A%20GATE.APPROVED%0A%20%20%20%20feature_migration_plan_approval%20--%3E%20feature_migration_plan_with_human%20%3A%20GATE.CHANGES_REQUESTED%0A%20%20%7D%0A%20%20state%20%22tickets%22%20as%20feature_tickets%0A%20%20state%20%22delivering%22%20as%20feature_delivering%0A%20%20state%20%22done%22%20as%20feature_done%0A%20%20feature_done%20--%3E%20%5B*%5D%0A%20%20feature_legacy_analysis%20--%3E%20feature_prd%20%3A%20done%0A%20%20feature_prd%20--%3E%20feature_migration_plan%20%3A%20done%0A%20%20feature_migration_plan%20--%3E%20feature_tickets%20%3A%20done%0A%20%20feature_tickets%20--%3E%20feature_delivering%20%3A%20done%0A%20%20feature_delivering%20--%3E%20feature_done%20%3A%20always%20%5BallTicketsDone%5D%0A%20%20classDef%20human%20fill%3A%23fde68a%2Cstroke%3A%23b45309%2Ccolor%3A%231f2937%0A%20%20class%20feature_prd_review%2Cfeature_migration_plan_with_human%2Cfeature_migration_plan_approval%20human%0A"})]),fallback:t(()=>[...e[0]||(e[0]=[i(" Loading... ",-1)])]),_:1})),e[4]||(e[4]=d("",5)),(a(),n(o,null,{default:t(()=>[s(r,{id:"mermaid-148",class:"mermaid",graph:"stateDiagram-v2%0A%20%20%5B*%5D%20--%3E%20ticket_implement%0A%20%20state%20%22implement%22%20as%20ticket_implement%0A%20%20state%20%22validate%22%20as%20ticket_validate%0A%20%20state%20%22code-review%22%20as%20ticket_code_review%0A%20%20state%20%22apply-review%22%20as%20ticket_apply_review%0A%20%20state%20%22pull-request%22%20as%20ticket_pull_request%0A%20%20state%20%22human-review%22%20as%20ticket_human_review%0A%20%20state%20%22BLOCK%22%20as%20ticket_BLOCK%0A%20%20state%20%22DONE%22%20as%20ticket_DONE%0A%20%20ticket_DONE%20--%3E%20%5B*%5D%0A%20%20ticket_implement%20--%3E%20ticket_implement%20%3A%20failed%0A%20%20ticket_implement%20--%3E%20ticket_BLOCK%20%3A%20no%20failures%20left%0A%20%20ticket_implement%20--%3E%20ticket_validate%20%3A%20passed%0A%20%20ticket_validate%20--%3E%20ticket_implement%20%3A%20failed%0A%20%20ticket_validate%20--%3E%20ticket_BLOCK%20%3A%20no%20failures%20left%0A%20%20ticket_validate%20--%3E%20ticket_code_review%20%3A%20passed%0A%20%20ticket_code_review%20--%3E%20ticket_apply_review%20%3A%20failed%0A%20%20ticket_code_review%20--%3E%20ticket_pull_request%20%3A%20no%20failures%20left%0A%20%20ticket_code_review%20--%3E%20ticket_pull_request%20%3A%20passed%0A%20%20ticket_apply_review%20--%3E%20ticket_apply_review%20%3A%20failed%0A%20%20ticket_apply_review%20--%3E%20ticket_BLOCK%20%3A%20no%20failures%20left%0A%20%20ticket_apply_review%20--%3E%20ticket_code_review%20%3A%20passed%0A%20%20ticket_pull_request%20--%3E%20ticket_pull_request%20%3A%20failed%0A%20%20ticket_pull_request%20--%3E%20ticket_BLOCK%20%3A%20no%20failures%20left%0A%20%20ticket_pull_request%20--%3E%20ticket_human_review%20%3A%20passed%0A%20%20ticket_human_review%20--%3E%20ticket_DONE%20%3A%20GATE.APPROVED%0A%20%20ticket_human_review%20--%3E%20ticket_DONE%20%3A%20PR.MERGED%0A%20%20ticket_BLOCK%20--%3E%20ticket_implement%20%3A%20RETRY%0A%20%20ticket_BLOCK%20--%3E%20ticket_apply_review%20%3A%20RETRY%0A%20%20ticket_BLOCK%20--%3E%20ticket_pull_request%20%3A%20RETRY%0A%20%20classDef%20human%20fill%3A%23fde68a%2Cstroke%3A%23b45309%2Ccolor%3A%231f2937%0A%20%20class%20ticket_human_review%2Cticket_BLOCK%20human%0A"})]),fallback:t(()=>[...e[1]||(e[1]=[i(" Loading... ",-1)])]),_:1})),e[5]||(e[5]=l("h2",{id:"inside-a-step",tabindex:"-1"},[i("Inside a step "),l("a",{class:"header-anchor",href:"#inside-a-step","aria-label":'Permalink to "Inside a step"'},"​")],-1)),e[6]||(e[6]=l("p",null,"Every box above is a step that runs in a fresh harness session. A crashed run is run again up to 2 times before the step parks. Open questions park the step until a human answers. A blocked result parks the step until a human retries it, once the cause is fixed.",-1)),(a(),n(o,null,{default:t(()=>[s(r,{id:"mermaid-155",class:"mermaid",graph:"stateDiagram-v2%0A%20%20%5B*%5D%20--%3E%20ticket_implement_running%0A%20%20state%20%22running%22%20as%20ticket_implement_running%0A%20%20state%20%22awaitingAnswers%22%20as%20ticket_implement_awaitingAnswers%0A%20%20state%20%22failed%22%20as%20ticket_implement_failed%0A%20%20state%20%22blocked%22%20as%20ticket_implement_blocked%0A%20%20state%20%22completed%22%20as%20ticket_implement_completed%0A%20%20ticket_implement_completed%20--%3E%20%5B*%5D%0A%20%20ticket_implement_running%20--%3E%20ticket_implement_awaitingAnswers%20%3A%20done%20%5BblockedOnQuestions%5D%0A%20%20ticket_implement_running%20--%3E%20ticket_implement_blocked%20%3A%20blocked%0A%20%20ticket_implement_running%20--%3E%20ticket_implement_completed%20%3A%20done%0A%20%20ticket_implement_running%20--%3E%20ticket_implement_running%20%3A%20error%20%5BcrashesLeft%5D%0A%20%20ticket_implement_running%20--%3E%20ticket_implement_failed%20%3A%20error%0A%20%20ticket_implement_awaitingAnswers%20--%3E%20ticket_implement_running%20%3A%20QUESTIONS.ANSWERED%20%5BanswersEveryQuestion%5D%0A%20%20ticket_implement_failed%20--%3E%20ticket_implement_running%20%3A%20RETRY%0A%20%20ticket_implement_blocked%20--%3E%20ticket_implement_running%20%3A%20RETRY%0A%20%20classDef%20human%20fill%3A%23fde68a%2Cstroke%3A%23b45309%2Ccolor%3A%231f2937%0A%20%20class%20ticket_implement_awaitingAnswers%2Cticket_implement_failed%2Cticket_implement_blocked%20human%0A"})]),fallback:t(()=>[...e[2]||(e[2]=[i(" Loading... ",-1)])]),_:1})),e[7]||(e[7]=d("",3))])}const w=c(u,[["render",m]]);export{v as __pageData,w as default};