blacksmith-organization-system 0.4.2 → 1.0.0-dev.3

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 (355) hide show
  1. package/.claude-plugin/marketplace.json +17 -0
  2. package/.claude-plugin/mcp.json +8 -0
  3. package/.claude-plugin/plugin.json +16 -0
  4. package/AGENTS.md +114 -0
  5. package/CLAUDE.md +1 -0
  6. package/LICENSE +1 -1
  7. package/README.md +74 -57
  8. package/bin/bos +3 -0
  9. package/hooks/hooks.json +40 -0
  10. package/package.json +82 -22
  11. package/resources/house/HOUSE.md +22 -0
  12. package/resources/house/bloodlines.json +35 -0
  13. package/resources/house/children/deepseeks-of-anubis.json +4 -0
  14. package/resources/house/children/eon.json +7 -0
  15. package/resources/house/children/hermetic-officer.json +1 -0
  16. package/resources/house/children/ifrit.json +1 -0
  17. package/resources/house/children/license-keepers.json +4 -0
  18. package/resources/house/children/sebas.json +10 -0
  19. package/resources/house/children/shakespeare.json +10 -0
  20. package/resources/house/orders/ravensarmy.json +6 -0
  21. package/resources/house/orders/seiaic-ucdi.json +5 -0
  22. package/resources/ravens/skryba.md +19 -0
  23. package/resources/workshop-root/AGENTS.md +92 -0
  24. package/resources/workshop-root/CLAUDE.md +1 -0
  25. package/resources/workshop.default.json +6 -0
  26. package/skills/bos-archive/SKILL.md +28 -0
  27. package/skills/bos-craftbook/SKILL.md +32 -0
  28. package/skills/bos-craftset/SKILL.md +36 -0
  29. package/skills/bos-devarmory/SKILL.md +34 -0
  30. package/skills/bos-exhibit/SKILL.md +28 -0
  31. package/skills/bos-forge/SKILL.md +28 -0
  32. package/skills/bos-house/SKILL.md +52 -0
  33. package/skills/bos-project/SKILL.md +27 -0
  34. package/skills/bos-sarcophag/SKILL.md +26 -0
  35. package/skills/bos-scope/SKILL.md +33 -0
  36. package/skills/bos-sheme/SKILL.md +38 -0
  37. package/skills/bos-throwbox/SKILL.md +27 -0
  38. package/skills/bos-workshop/SKILL.md +65 -0
  39. package/{config/README.md → skills/bos-workshop/assets/.gitkeep} +0 -0
  40. package/skills/bos-workshop/references/bos-codebase.md +116 -0
  41. package/skills/bos-workshop/references/bos-concept.md +121 -0
  42. package/skills/bos-workshop/scripts/find-workshop.sh +30 -0
  43. package/src/agent-hook.mjs +28 -0
  44. package/src/bridge/_index.mjs +24 -0
  45. package/src/bridge/bos-instances-link/_index.mjs +18 -0
  46. package/src/bridge/docker-host/_index.mjs +43 -0
  47. package/src/bridge/docker-host/container.mjs +32 -0
  48. package/src/bridge/docker-host/image.mjs +23 -0
  49. package/src/bridge/docker-host/volume.mjs +12 -0
  50. package/src/bridge/docker-publish/_index.mjs +24 -0
  51. package/src/bridge/git-client/_index.mjs +121 -0
  52. package/src/bridge/git-client/author.mjs +26 -0
  53. package/src/bridge/gitea-api/_index.mjs +62 -0
  54. package/src/bridge/github-api/_index.mjs +45 -0
  55. package/src/bridge/npm-publish/_index.mjs +19 -0
  56. package/src/bridge/ollama-link/_index.mjs +93 -0
  57. package/src/bridge/ollama-link/ravens.mjs +29 -0
  58. package/src/bridge/ssh-link/_index.mjs +33 -0
  59. package/src/bridge/subprocess-runner/_index.mjs +33 -0
  60. package/src/cli.mjs +29 -0
  61. package/src/commands/_index.mjs +51 -0
  62. package/src/commands/bootstrap/exec.mjs +7 -0
  63. package/src/commands/bootstrap/help.md +5 -0
  64. package/src/commands/bootstrap/index.json +7 -0
  65. package/src/commands/close/exec.mjs +4 -0
  66. package/src/commands/close/help.md +5 -0
  67. package/src/commands/close/index.json +7 -0
  68. package/src/commands/describe/exec.mjs +11 -0
  69. package/src/commands/describe/help.md +5 -0
  70. package/src/commands/describe/index.json +7 -0
  71. package/src/commands/help/exec.mjs +6 -0
  72. package/src/commands/help/help.md +3 -0
  73. package/src/commands/help/index.json +6 -0
  74. package/src/commands/hook/exec.mjs +3 -0
  75. package/src/commands/hook/help.md +4 -0
  76. package/src/commands/hook/index.json +7 -0
  77. package/src/commands/hooks/exec.mjs +5 -0
  78. package/src/commands/hooks/help.md +4 -0
  79. package/src/commands/hooks/index.json +7 -0
  80. package/src/commands/house/exec.mjs +8 -0
  81. package/src/commands/house/help.md +8 -0
  82. package/src/commands/house/index.json +7 -0
  83. package/src/commands/locate/exec.mjs +2 -0
  84. package/src/commands/locate/help.md +6 -0
  85. package/src/commands/locate/index.json +6 -0
  86. package/src/commands/mode/exec.mjs +17 -0
  87. package/src/commands/mode/help.md +14 -0
  88. package/src/commands/mode/index.json +8 -0
  89. package/src/commands/mode/repl.mjs +18 -0
  90. package/src/commands/open/exec.mjs +5 -0
  91. package/src/commands/open/help.md +4 -0
  92. package/src/commands/open/index.json +7 -0
  93. package/src/commands/promote/exec.mjs +5 -0
  94. package/src/commands/promote/help.md +6 -0
  95. package/src/commands/promote/index.json +7 -0
  96. package/src/commands/repl/exec.mjs +3 -0
  97. package/src/commands/repl/help.md +4 -0
  98. package/src/commands/repl/index.json +6 -0
  99. package/src/commands/seal/exec.mjs +8 -0
  100. package/src/commands/seal/help.md +4 -0
  101. package/src/commands/seal/index.json +7 -0
  102. package/src/commands/sink/exec.mjs +2 -0
  103. package/src/commands/sink/help.md +6 -0
  104. package/src/commands/sink/index.json +7 -0
  105. package/src/commands/skills/exec.mjs +5 -0
  106. package/src/commands/skills/help.md +5 -0
  107. package/src/commands/skills/index.json +7 -0
  108. package/src/commands/status/exec.mjs +2 -0
  109. package/src/commands/status/help.md +6 -0
  110. package/src/commands/status/index.json +7 -0
  111. package/src/commands/status/repl.mjs +14 -0
  112. package/src/commands/verify/exec.mjs +9 -0
  113. package/src/commands/verify/help.md +4 -0
  114. package/src/commands/verify/index.json +7 -0
  115. package/src/controllers/_index.mjs +5 -0
  116. package/src/controllers/skills-controll.mjs +13 -0
  117. package/src/controllers/workshop-controll.mjs +48 -0
  118. package/src/core/basic-bridge.mjs +29 -0
  119. package/src/core/basic-controll.mjs +26 -0
  120. package/src/core/basic-element.mjs +149 -0
  121. package/src/core/basic-model.mjs +7 -0
  122. package/src/core/basic-procedure.mjs +37 -0
  123. package/src/core/basic-submodule.mjs +21 -0
  124. package/src/core/basic-view.mjs +21 -0
  125. package/src/core/self.mjs +10 -0
  126. package/src/index.mjs +24 -0
  127. package/src/main.ts +82 -0
  128. package/src/mcp.mjs +8 -0
  129. package/src/models/_index.mjs +43 -0
  130. package/src/models/archive/class.mjs +9 -0
  131. package/src/models/craftbook/class.mjs +11 -0
  132. package/src/models/craftbook/hang.scrapnotes.mjs +10 -0
  133. package/src/models/craftset/class.mjs +14 -0
  134. package/src/models/devarmory/class.mjs +10 -0
  135. package/src/models/exhibit/class.mjs +8 -0
  136. package/src/models/forge/class.mjs +19 -0
  137. package/src/models/house/bloodlines.mjs +27 -0
  138. package/src/models/house/class.mjs +53 -0
  139. package/src/models/house/groups.mjs +38 -0
  140. package/src/models/project/anatomy.mjs +11 -0
  141. package/src/models/project/class.mjs +20 -0
  142. package/src/models/sarcophag/class.mjs +8 -0
  143. package/src/models/scope/class.mjs +19 -0
  144. package/src/models/sheme/class.mjs +8 -0
  145. package/src/models/throwbox/class.mjs +9 -0
  146. package/src/models/workshop/class.mjs +30 -0
  147. package/src/models/workshop/hang.data.mjs +10 -0
  148. package/src/models/workshop/hang.nests.mjs +14 -0
  149. package/src/models/workshop/hang.setup.mjs +10 -0
  150. package/src/models/workshop/hang.storylines.logs.mjs +10 -0
  151. package/src/models/workshop/hang.storylines.manifests.mjs +10 -0
  152. package/src/models/workshop/hang.storylines.mjs +39 -0
  153. package/src/models/workshop/hang.storylines.ttystories.mjs +10 -0
  154. package/src/models/workshop/hang.system.mjs +25 -0
  155. package/src/models/workshop/host-roles.mjs +9 -0
  156. package/src/models/workshop/locate.mjs +60 -0
  157. package/src/models/workshop/modes.mjs +31 -0
  158. package/src/models/workshop/taskfile.mjs +21 -0
  159. package/src/procedures/_index.mjs +32 -0
  160. package/src/procedures/agent-recording/_index.mjs +34 -0
  161. package/src/procedures/agent-recording/situate.mjs +11 -0
  162. package/src/procedures/bootstrapping/DESCRIPTION.md +6 -0
  163. package/src/procedures/bootstrapping/_index.mjs +13 -0
  164. package/src/procedures/bootstrapping/deploy-guides.mjs +15 -0
  165. package/src/procedures/bootstrapping/ensure-tree.mjs +4 -0
  166. package/src/procedures/closing/DESCRIPTION.md +6 -0
  167. package/src/procedures/closing/_index.mjs +11 -0
  168. package/src/procedures/closing/capture-ttystory.mjs +14 -0
  169. package/src/procedures/closing/record-closing.mjs +6 -0
  170. package/src/procedures/describing/DESCRIPTION.md +23 -0
  171. package/src/procedures/describing/_index.mjs +15 -0
  172. package/src/procedures/describing/ask-skryba.mjs +82 -0
  173. package/src/procedures/describing/compose-message.mjs +25 -0
  174. package/src/procedures/describing/read-staged.mjs +7 -0
  175. package/src/procedures/describing/scopes.mjs +38 -0
  176. package/src/procedures/describing/staged-diff.mjs +25 -0
  177. package/src/procedures/describing/write-message.mjs +15 -0
  178. package/src/procedures/gathering/DESCRIPTION.md +13 -0
  179. package/src/procedures/gathering/_index.mjs +13 -0
  180. package/src/procedures/gathering/list-served.mjs +9 -0
  181. package/src/procedures/gathering/merge-residents.mjs +31 -0
  182. package/src/procedures/gathering/read-registry.mjs +7 -0
  183. package/src/procedures/hooking/DESCRIPTION.md +6 -0
  184. package/src/procedures/hooking/_index.mjs +12 -0
  185. package/src/procedures/hooking/hooks.mjs +10 -0
  186. package/src/procedures/hooking/install-hooks.mjs +34 -0
  187. package/src/procedures/hooking/record-hook.mjs +13 -0
  188. package/src/procedures/inspecting/DESCRIPTION.md +3 -0
  189. package/src/procedures/inspecting/_index.mjs +9 -0
  190. package/src/procedures/loading/DESCRIPTION.md +4 -0
  191. package/src/procedures/loading/_index.mjs +15 -0
  192. package/src/procedures/loading/load-config.mjs +16 -0
  193. package/src/procedures/loading/validate-mode.mjs +8 -0
  194. package/src/procedures/loading/validate-role.mjs +8 -0
  195. package/src/procedures/locating/DESCRIPTION.md +6 -0
  196. package/src/procedures/locating/_index.mjs +13 -0
  197. package/src/procedures/locating/locate-self.mjs +7 -0
  198. package/src/procedures/locating/locate-workshop.mjs +16 -0
  199. package/src/procedures/locating/read-environment.mjs +12 -0
  200. package/src/procedures/mode-switching/DESCRIPTION.md +7 -0
  201. package/src/procedures/mode-switching/_index.mjs +10 -0
  202. package/src/procedures/mode-switching/record-mode.mjs +8 -0
  203. package/src/procedures/mode-switching/switch-mode.mjs +16 -0
  204. package/src/procedures/opening/DESCRIPTION.md +6 -0
  205. package/src/procedures/opening/_index.mjs +8 -0
  206. package/src/procedures/opening/record-opening.mjs +4 -0
  207. package/src/procedures/promoting/DESCRIPTION.md +8 -0
  208. package/src/procedures/promoting/_index.mjs +14 -0
  209. package/src/procedures/promoting/apply.mjs +28 -0
  210. package/src/procedures/promoting/branch-flow.mjs +23 -0
  211. package/src/procedures/promoting/plan.mjs +14 -0
  212. package/src/procedures/promoting/read-position.mjs +15 -0
  213. package/src/procedures/sinking/DESCRIPTION.md +8 -0
  214. package/src/procedures/sinking/_index.mjs +12 -0
  215. package/src/procedures/sinking/inventory-forge.mjs +18 -0
  216. package/src/procedures/sinking/inventory-tools.mjs +7 -0
  217. package/src/procedures/skilling/DESCRIPTION.md +11 -0
  218. package/src/procedures/skilling/_index.mjs +15 -0
  219. package/src/procedures/skilling/collect-models.mjs +15 -0
  220. package/src/procedures/skilling/compose-skill.mjs +49 -0
  221. package/src/procedures/skilling/glossary.mjs +23 -0
  222. package/src/procedures/skilling/register-skills.mjs +21 -0
  223. package/src/procedures/skilling/write-skills.mjs +19 -0
  224. package/src/views/_index.mjs +23 -0
  225. package/src/views/agent/_index.mjs +2 -0
  226. package/src/views/agent/brief.mjs +34 -0
  227. package/src/views/help/_index.mjs +41 -0
  228. package/src/views/house/_index.mjs +56 -0
  229. package/src/views/inventory/_index.mjs +27 -0
  230. package/src/views/mcp/_index.mjs +31 -0
  231. package/src/views/mcp/tools.mjs +87 -0
  232. package/src/views/mode/_index.mjs +34 -0
  233. package/src/views/mode/chooser.mjs +37 -0
  234. package/src/views/promotion/_index.mjs +20 -0
  235. package/src/views/repl/_index.mjs +30 -0
  236. package/src/views/skills/_index.mjs +24 -0
  237. package/src/views/status/_index.mjs +57 -0
  238. package/src/views/status/tree.mjs +19 -0
  239. package/src/views/terminal/paint.mjs +22 -0
  240. package/template/SKILL.md +16 -0
  241. package/docs/README.md +0 -15
  242. package/docs/devlog.md +0 -20
  243. package/invoke-api.js +0 -0
  244. package/invoke-cli.js +0 -105
  245. package/invoke-desktop.js +0 -0
  246. package/invoke-exec.js +0 -0
  247. package/invoke-webapp.js +0 -0
  248. package/main.js +0 -33
  249. package/shell-procedures/deploy-new-workshop.sh +0 -15
  250. package/shell-procedures/new-project.sh +0 -0
  251. package/shell-procedures/new-sheme.sh +0 -0
  252. package/shell-procedures/new-superproject.sh +0 -6
  253. package/shell-procedures/open-project.sh +0 -0
  254. package/shell-procedures/pack-backup.sh +0 -22
  255. package/shell-procedures/project-to-sarcophag.sh +0 -0
  256. package/shell-procedures/publicate.sh +0 -0
  257. package/shell-procedures/pull-archive.sh +0 -0
  258. package/shell-procedures/purge-workshop.sh +0 -4
  259. package/shell-procedures/push-to-bos-server.sh +0 -0
  260. package/shell-procedures/start-cli-interface.sh +0 -1
  261. package/shell-procedures/sweep-into-throwbox.sh +0 -0
  262. package/shell-procedures/synchronize.sh +0 -7
  263. package/system/bridges/README.md +0 -4
  264. package/system/bridges/main.js +0 -41
  265. package/system/commands/README.md +0 -0
  266. package/system/commands/debug.js +0 -20
  267. package/system/commands/exit.js +0 -19
  268. package/system/core/README.md +0 -0
  269. package/system/core/basic-subject-model.js +0 -200
  270. package/system/core/config-controll.js +0 -51
  271. package/system/core/contain-controll.js +0 -28
  272. package/system/core/event-controll.js +0 -12
  273. package/system/core/extending-mod.js +0 -145
  274. package/system/core/main.js +0 -184
  275. package/system/core/raport-controll.js +0 -10
  276. package/system/core/static-extender.js +0 -19
  277. package/system/factors/README.md +0 -0
  278. package/system/factors/bridge-factory.js +0 -5
  279. package/system/factors/command-factory.js +0 -45
  280. package/system/factors/config-factory.js +0 -6
  281. package/system/factors/controller-factory.js +0 -5
  282. package/system/factors/interface-factory.js +0 -70
  283. package/system/factors/model-factory.js +0 -0
  284. package/system/interfaces/README.md +0 -28
  285. package/system/interfaces/cli/index.js +0 -96
  286. package/system/interfaces/cli/init.js +0 -47
  287. package/system/interfaces/cli/loop.js +0 -7
  288. package/system/interfaces/cli/on-close.js +0 -17
  289. package/system/interfaces/cli/on-line.js +0 -24
  290. package/system/interfaces/cli/on-resume.js +0 -15
  291. package/system/interfaces/cli/setup.js +0 -8
  292. package/system/models/README.md +0 -47
  293. package/system/models/archive/README.md +0 -0
  294. package/system/models/archive/actions/README.md +0 -0
  295. package/system/models/archive/class.js +0 -23
  296. package/system/models/archive/events/README.md +0 -0
  297. package/system/models/archive/index.json +0 -18
  298. package/system/models/archive/listeners/README.md +0 -0
  299. package/system/models/archive/methods/README.md +0 -0
  300. package/system/models/forge/README.md +0 -0
  301. package/system/models/forge/actions/README.md +0 -0
  302. package/system/models/forge/class.js +0 -23
  303. package/system/models/forge/events/README.md +0 -0
  304. package/system/models/forge/index.json +0 -18
  305. package/system/models/forge/listeners/README.md +0 -0
  306. package/system/models/forge/methods/README.md +0 -0
  307. package/system/models/project/README.md +0 -0
  308. package/system/models/project/actions/README.md +0 -0
  309. package/system/models/project/class.js +0 -23
  310. package/system/models/project/events/README.md +0 -0
  311. package/system/models/project/index.json +0 -18
  312. package/system/models/project/listeners/README.md +0 -0
  313. package/system/models/project/methods/README.md +0 -0
  314. package/system/models/sarcophag/README.md +0 -0
  315. package/system/models/sarcophag/actions/README.md +0 -0
  316. package/system/models/sarcophag/class.js +0 -22
  317. package/system/models/sarcophag/events/README.md +0 -0
  318. package/system/models/sarcophag/index.json +0 -18
  319. package/system/models/sarcophag/listeners/README.md +0 -0
  320. package/system/models/sarcophag/methods/README.md +0 -0
  321. package/system/models/sheme/README.md +0 -0
  322. package/system/models/sheme/actions/README.md +0 -0
  323. package/system/models/sheme/class.js +0 -23
  324. package/system/models/sheme/events/README.md +0 -0
  325. package/system/models/sheme/index.json +0 -18
  326. package/system/models/sheme/listeners/README.md +0 -0
  327. package/system/models/sheme/methods/README.md +0 -0
  328. package/system/models/superproject/README.md +0 -0
  329. package/system/models/superproject/actions/README.md +0 -0
  330. package/system/models/superproject/class.js +0 -23
  331. package/system/models/superproject/events/README.md +0 -0
  332. package/system/models/superproject/index.json +0 -18
  333. package/system/models/superproject/listeners/README.md +0 -0
  334. package/system/models/superproject/methods/README.md +0 -0
  335. package/system/models/throwbox/README.md +0 -0
  336. package/system/models/throwbox/actions/README.md +0 -0
  337. package/system/models/throwbox/class.js +0 -23
  338. package/system/models/throwbox/events/README.md +0 -0
  339. package/system/models/throwbox/index.json +0 -18
  340. package/system/models/throwbox/listeners/README.md +0 -0
  341. package/system/models/throwbox/methods/README.md +0 -0
  342. package/system/models/workshop/README.md +0 -0
  343. package/system/models/workshop/actions/README.md +0 -0
  344. package/system/models/workshop/class.js +0 -22
  345. package/system/models/workshop/events/README.md +0 -0
  346. package/system/models/workshop/index.json +0 -18
  347. package/system/models/workshop/listeners/README.md +0 -0
  348. package/system/models/workshop/methods/README.md +0 -0
  349. package/system/resources/textarts/big-logo-title.txt +0 -24
  350. package/system/resources/textarts/big-shortcut-logo-title.txt +0 -10
  351. package/system/resources/textarts/medium-logo-title.txt +0 -13
  352. package/system/resources/textarts/minimenu-header.ptxml +0 -13
  353. package/system/resources/textarts/minimenu-header.txt +0 -11
  354. package/system/resources/textarts/small-logo-title.txt +0 -10
  355. package/system/resources/textarts/wellcome.txt +0 -10
@@ -0,0 +1,60 @@
1
+ // Finding workshops on the machine: per user (~/__WORKSHOP) and per partition (/media/**/__WORKSHOP).
2
+ import { existsSync, readdirSync } from "node:fs";
3
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
4
+
5
+ export const WORKSHOP_DIRNAME = "__WORKSHOP";
6
+
7
+ export function findAncestorWorkshop(start) {
8
+ let dir = resolve(start);
9
+ while (dir !== dirname(dir)) {
10
+ if (basename(dir) === WORKSHOP_DIRNAME) return dir;
11
+ dir = dirname(dir);
12
+ }
13
+ return null;
14
+ }
15
+
16
+ // the active workshop: first rule that answers wins
17
+ export function resolveWorkshop({ explicit, variable, cwd, self, home }) {
18
+ const rules = [
19
+ ["--workshop option", () => explicit && resolve(explicit)],
20
+ ["$BOS_WORKSHOP", () => variable && resolve(variable.replace(/^~(?=$|\/)/, home))],
21
+ ["ancestor of current directory", () => findAncestorWorkshop(cwd)],
22
+ ["ancestor of BOS installation", () => findAncestorWorkshop(self)],
23
+ ["default per user", () => join(home, WORKSHOP_DIRNAME)],
24
+ ];
25
+ for (const [source, rule] of rules) {
26
+ const root = rule();
27
+ if (root) return { root, source };
28
+ }
29
+ }
30
+
31
+ export function listWorkshops(home, mediaRoot = "/media", maxDepth = 4) {
32
+ const found = new Set();
33
+ if (existsSync(join(home, WORKSHOP_DIRNAME))) found.add(join(home, WORKSHOP_DIRNAME));
34
+ (function scan(dir, depth) {
35
+ if (depth > maxDepth) return;
36
+ let entries = [];
37
+ try {
38
+ entries = readdirSync(dir, { withFileTypes: true });
39
+ } catch {
40
+ return;
41
+ }
42
+ for (const entry of entries.filter((entry) => entry.isDirectory())) {
43
+ const path = join(dir, entry.name);
44
+ if (entry.name === WORKSHOP_DIRNAME) found.add(path);
45
+ else scan(path, depth + 1);
46
+ }
47
+ })(mediaRoot, 1);
48
+ return [...found];
49
+ }
50
+
51
+ // where a path sits inside a workshop: area, and for forge its scope and project
52
+ export function placeOf(target, workshopRoot) {
53
+ const rel = relative(workshopRoot, resolve(target));
54
+ if (rel === "") return { area: null };
55
+ if (rel.startsWith("..")) return null;
56
+ const [area, ...rest] = rel.split(sep);
57
+ if (area === "forge")
58
+ return { area, scope: rest[0] ?? null, project: rest[1] ?? null, inner: rest.slice(2).join("/") || null };
59
+ return { area, inner: rest.join("/") || null };
60
+ }
@@ -0,0 +1,31 @@
1
+ // Modes of work: what kind of need the workshop serves right now (glossary: mode).
2
+ // The digit is the key in the `bos mode` chooser; 0 leaves it.
3
+ export const MODES = {
4
+ almanac: {
5
+ digit: 1,
6
+ title: "ALMANAC",
7
+ covers: "emergency protocols: sinking",
8
+ },
9
+ conform: {
10
+ digit: 2,
11
+ title: "CONFORM",
12
+ covers: "establishing the workshop, migrations, cleaning, verifying, syncing between fortified workshops",
13
+ },
14
+ smeltry: {
15
+ digit: 3,
16
+ title: "SMELTRY",
17
+ covers: "code, development, active editing, publishing changes",
18
+ },
19
+ commandorate: {
20
+ digit: 4,
21
+ title: "COMMANDORATE",
22
+ covers: "rulesets, privileges, roles, switching modes",
23
+ },
24
+ provision: {
25
+ digit: 5,
26
+ title: "PROVISION",
27
+ covers: "presenting what a source of truth delivers, e.g. a repository on GitHub",
28
+ },
29
+ };
30
+
31
+ export const modeByDigit = (digit) => Object.keys(MODES).find((name) => MODES[name].digit === Number(digit)) ?? null;
@@ -0,0 +1,21 @@
1
+ // .BOS/Taskfile.yaml: the workshop's own procedures, pointing at this BOS installation.
2
+ import { CLI } from "../../core/self.mjs";
3
+
4
+ export const workshopTaskfile = () => `# yaml-language-server: $schema=https://taskfile.dev/schema.json
5
+ # workshop procedures, generated by BOS bootstrap; edit freely
6
+ version: '3'
7
+
8
+ vars:
9
+ BOS: node ${CLI}
10
+
11
+ tasks:
12
+ status:
13
+ desc: show the state of this workshop
14
+ cmds: ['{{.BOS}} status']
15
+ open:
16
+ desc: open the workshop (bootstrap missing areas, record opening)
17
+ cmds: ['{{.BOS}} open']
18
+ close:
19
+ desc: close the workshop (capture ttystory, record closing)
20
+ cmds: ['{{.BOS}} close']
21
+ `;
@@ -0,0 +1,32 @@
1
+ // Every procedure: steps that close a routed routine. Each lives in its own
2
+ // directory: DESCRIPTION.md (meaning), one file per step, _index.mjs (assembly).
3
+ import bootstrapping from "./bootstrapping/_index.mjs";
4
+ import closing from "./closing/_index.mjs";
5
+ import describing from "./describing/_index.mjs";
6
+ import gathering from "./gathering/_index.mjs";
7
+ import { hooking, installingHooks } from "./hooking/_index.mjs";
8
+ import inspecting from "./inspecting/_index.mjs";
9
+ import loading from "./loading/_index.mjs";
10
+ import locating from "./locating/_index.mjs";
11
+ import modeSwitching from "./mode-switching/_index.mjs";
12
+ import opening from "./opening/_index.mjs";
13
+ import promoting from "./promoting/_index.mjs";
14
+ import sinking from "./sinking/_index.mjs";
15
+ import skilling from "./skilling/_index.mjs";
16
+
17
+ export default {
18
+ locating,
19
+ loading,
20
+ inspecting,
21
+ bootstrapping,
22
+ opening,
23
+ closing,
24
+ sinking,
25
+ hooking,
26
+ installingHooks,
27
+ promoting,
28
+ skilling,
29
+ modeSwitching,
30
+ describing,
31
+ gathering,
32
+ };
@@ -0,0 +1,34 @@
1
+ // An agent's work leaves storylines like a person's: its sessions as events, its shell
2
+ // commands as its own ttystory (ttystory-<host>-claude-<session>.txt, one line per command:
3
+ // unixusat, cwd, command). Storylines hold shell history: treat them like .BOS/setup.
4
+ import { appendFileSync } from "node:fs";
5
+
6
+ import { situate } from "./situate.mjs";
7
+
8
+ export async function recordAgentEvent(input, event) {
9
+ const context = await situate(input);
10
+ if (!context) return null;
11
+ const storylines = context.workshop.storylines.ensure(context);
12
+ return storylines.record(
13
+ {
14
+ event,
15
+ agent: "claude",
16
+ session: input.session_id ?? null,
17
+ source: input.source ?? null,
18
+ model: input.model ?? null,
19
+ place: context.workshop.placeOf(input.cwd ?? process.cwd()),
20
+ },
21
+ context,
22
+ );
23
+ }
24
+
25
+ export async function recordAgentCommand(input) {
26
+ const command = input.tool_input?.command;
27
+ if (!command) return null;
28
+ const context = await situate(input);
29
+ if (!context) return null;
30
+ const storylines = context.workshop.storylines.ensure(context);
31
+ const file = storylines.ttystories.file(`ttystory-${context.env.host}-claude-${input.session_id ?? "unknown"}.txt`);
32
+ appendFileSync(file, `${Date.now()}\t${input.cwd ?? process.cwd()}\t${command.replace(/\n/g, "\\n")}\n`);
33
+ return file;
34
+ }
@@ -0,0 +1,11 @@
1
+ // Where an agent session happens: locate and load the workshop from the hook's cwd.
2
+ // Returns null when there is no workshop there; hooks never create one.
3
+ import { existsSync } from "node:fs";
4
+
5
+ import loading from "../loading/_index.mjs";
6
+
7
+ export async function situate(input) {
8
+ if (input.cwd && existsSync(input.cwd)) process.chdir(input.cwd);
9
+ const context = await loading.run({ options: { workshop: process.env.BOS_WORKSHOP } });
10
+ return context.workshop.exists() ? context : null;
11
+ }
@@ -0,0 +1,6 @@
1
+ # Bootstrapping Protocol
2
+
3
+ Makes the workshop whole: `.BOS/` with setup, data, nests (+ index and
4
+ manifest), `workshop.json` and `Taskfile.yaml`; devarmory (+ manifest), forge,
5
+ craftbook (+ scrapnotes) and archive; `AGENTS.md` and `CLAUDE.md` at the root.
6
+ It creates only what is missing and never overwrites. With `--dry-run` it only plans.
@@ -0,0 +1,13 @@
1
+ import { BOS } from "../../main.ts";
2
+ import loading from "../loading/_index.mjs";
3
+ import { deployGuides } from "./deploy-guides.mjs";
4
+ import { ensureTree } from "./ensure-tree.mjs";
5
+
6
+ export const bootstrapSteps = new BOS.Procedure("bootstrapping", [], {
7
+ description: "create missing areas and descriptors, deploy agent guides",
8
+ })
9
+ .step("ensure workshop tree", ensureTree)
10
+ .step("deploy agent guides", deployGuides);
11
+
12
+ export const bootstrapping = loading.chain(bootstrapSteps, "bootstrapping");
13
+ export default bootstrapping;
@@ -0,0 +1,15 @@
1
+ import { copyFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import { createIfMissing } from "../../core/basic-element.mjs";
5
+ import { RESOURCES } from "../../core/self.mjs";
6
+
7
+ export const AGENT_GUIDES = ["AGENTS.md", "CLAUDE.md"];
8
+
9
+ // the workshop-root guides for AI agents; an existing one is never replaced
10
+ export const deployGuides = (context) => {
11
+ for (const guide of AGENT_GUIDES) {
12
+ const target = join(context.workshopRoot, guide);
13
+ createIfMissing(target, () => copyFileSync(join(RESOURCES, "workshop-root", guide), target), context);
14
+ }
15
+ };
@@ -0,0 +1,4 @@
1
+ // every mandatory area and descriptor; creates only what is missing
2
+ export const ensureTree = (context) => {
3
+ context.workshop.ensure(context);
4
+ };
@@ -0,0 +1,6 @@
1
+ # Closing Protocol
2
+
3
+ related to all models - requires stuff to be open first, it is being done before shutdown
4
+
5
+ Steps: locating → loading → copy the shell history to
6
+ `.BOS/storylines/ttystories/ttystory-<host>-<unixusat>.txt` → record the closing.
@@ -0,0 +1,11 @@
1
+ import { BOS } from "../../main.ts";
2
+ import loading from "../loading/_index.mjs";
3
+ import { captureTtystory } from "./capture-ttystory.mjs";
4
+ import { recordClosing } from "./record-closing.mjs";
5
+
6
+ const own = new BOS.Procedure("closing")
7
+ .step("capture ttystory", captureTtystory)
8
+ .step("record closing", recordClosing);
9
+
10
+ export const closing = loading.chain(own, "closing");
11
+ export default closing;
@@ -0,0 +1,14 @@
1
+ import { copyFileSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import { createIfMissing } from "../../core/basic-element.mjs";
5
+
6
+ // bash writes history on exit: run `history -a` first to include the current shell
7
+ export const captureTtystory = (context) => {
8
+ const storylines = context.workshop.storylines.ensure(context);
9
+ const history = process.env.HISTFILE || join(context.env.home, ".bash_history");
10
+ if (!existsSync(history)) return;
11
+ const target = storylines.ttystories.file(storylines.ttystoryName(context));
12
+ createIfMissing(target, () => copyFileSync(history, target), context);
13
+ context.ttystory = target;
14
+ };
@@ -0,0 +1,6 @@
1
+ export const recordClosing = (context) => {
2
+ context.event = context.workshop.storylines.record(
3
+ { event: "close", user: context.env.user, ttystory: context.ttystory ?? null },
4
+ context,
5
+ );
6
+ };
@@ -0,0 +1,23 @@
1
+ # Describing Protocol
2
+
3
+ Part of SMELTRY. A commit should describe its own content. This protocol reads
4
+ the staged changes and drafts a conventional-commit message:
5
+
6
+ - **type**: `docs`, `test`, `ci` or `build` when only that part changed; `feat`
7
+ when new source files appear; otherwise `chore`
8
+ - **scope**: the part of the project (`procedures`, `bridge`, `models`, `docs`, …),
9
+ from `scopes.mjs`
10
+ - **subject**: add / update / remove, plus what the paths are about
11
+ - **body**: every file, marked A / M / D
12
+
13
+ Then **Skryba**, the scribe raven (`resources/ravens/skryba.md`), reads the
14
+ trimmed staged diff through ollama and proposes a better subject and a short
15
+ body. He answers in a fixed JSON shape, so the header is always a valid
16
+ conventional commit, and the file list stays. When ollama or the model is
17
+ missing, or the answer is unusable, the plain draft is used.
18
+ `BOS_SKRYBA=0` turns him off; `BOS_SKRYBA_MODEL` picks another model.
19
+
20
+ Husky's `prepare-commit-msg` runs it (`bos describe --hook`), so a plain
21
+ `git commit` opens the editor with the draft. A message given with `-m`, a
22
+ merge, a squash or an amend is never touched. commitlint then checks the
23
+ format, and semantic-release turns the type into the next version.
@@ -0,0 +1,15 @@
1
+ import { BOS } from "../../main.ts";
2
+ import { askSkryba } from "./ask-skryba.mjs";
3
+ import { composeDraft } from "./compose-message.mjs";
4
+ import { readStaged } from "./read-staged.mjs";
5
+ import { writeMessage } from "./write-message.mjs";
6
+
7
+ export const describing = new BOS.Procedure("describing", [], {
8
+ description: "draft a conventional commit message from the staged changes",
9
+ })
10
+ .step("read staged changes", readStaged)
11
+ .step("compose draft", composeDraft)
12
+ .step("ask Skryba", askSkryba)
13
+ .step("write into commit message", writeMessage);
14
+
15
+ export default describing;
@@ -0,0 +1,82 @@
1
+ // Skryba, the scribe raven (resources/ravens/skryba.md), rewrites the draft's header
2
+ // and adds a short body. He only proposes: the file list stays, and without ollama,
3
+ // or with a bad answer, the deterministic draft is used as it is.
4
+ import OllamaLink from "../../bridge/ollama-link/_index.mjs";
5
+ import SubprocessRunner from "../../bridge/subprocess-runner/_index.mjs";
6
+ import { stagedDiff } from "./staged-diff.mjs";
7
+
8
+ export const TYPES = ["feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert"];
9
+
10
+ export const ANSWER = {
11
+ type: "object",
12
+ properties: {
13
+ type: { type: "string", enum: TYPES },
14
+ scope: { type: "string" },
15
+ subject: { type: "string" },
16
+ body: { type: "string" },
17
+ },
18
+ required: ["type", "scope", "subject", "body"],
19
+ };
20
+
21
+ // Skryba's answer + the draft → a message; null when the answer can't be trusted
22
+ export function assemble(answer, draft, signature) {
23
+ let parsed;
24
+ try {
25
+ parsed = typeof answer === "string" ? JSON.parse(answer) : answer;
26
+ } catch {
27
+ return null;
28
+ }
29
+ const subject = String(parsed?.subject ?? "")
30
+ .trim()
31
+ .replace(/\.$/, "");
32
+ if (!TYPES.includes(parsed?.type) || !subject) return null;
33
+ const scope = String(parsed.scope ?? "")
34
+ .trim()
35
+ .toLowerCase()
36
+ .replace(/[^a-z0-9,._-]/g, "");
37
+ const header = `${parsed.type}${scope ? `(${scope})` : ""}: ${subject}`.slice(0, 100);
38
+ const body = String(parsed.body ?? "")
39
+ .trim()
40
+ .split("\n")
41
+ .map((line) => line.trim())
42
+ .filter(Boolean)
43
+ .join("\n");
44
+ const files = draft.split("\n\n").slice(1).join("\n\n").trim();
45
+ return `${[header, body, files, signature].filter(Boolean).join("\n\n")}\n`;
46
+ }
47
+
48
+ export const askSkryba = async (context) => {
49
+ const wanted = context.options.skryba ?? process.env.BOS_SKRYBA !== "0";
50
+ if (!wanted || !context.message) return;
51
+ const ollama = context.options.ollama ?? new OllamaLink();
52
+ const raven = ollama.raven("skryba");
53
+ const model = process.env.BOS_SKRYBA_MODEL ?? raven.model;
54
+ if (!(await ollama.available(model))) {
55
+ context.skryba = { used: false, reason: `ollama or model ${model} not available` };
56
+ return;
57
+ }
58
+ const { stat, diff } = stagedDiff(context.repo);
59
+ const recent = new SubprocessRunner().try("git", ["log", "-8", "--format=%s"], { cwd: context.repo }) ?? "";
60
+ const [, type, scope] = context.message.match(/^(\w+)(?:\(([^)]*)\))?:/) ?? [];
61
+ const prompt = [
62
+ `Hint from the file paths: type "${type}", scope "${scope ?? ""}". Keep them unless the diff clearly shows otherwise.`,
63
+ "Write the subject about the purpose of the change: what it makes possible or fixes, not which files changed.",
64
+ "",
65
+ "Recent commit subjects of this repository (for style):",
66
+ recent || "(none)",
67
+ "",
68
+ "Staged changes, stat:",
69
+ stat,
70
+ "",
71
+ "Staged changes, diff (may be trimmed):",
72
+ diff,
73
+ ].join("\n");
74
+ const answer = await ollama.ask("skryba", prompt, {
75
+ format: ANSWER,
76
+ model,
77
+ timeout: context.options.timeout ?? 90000,
78
+ });
79
+ const message = answer && assemble(answer, context.message, `Drafted-by: Skryba (${model} via ollama)`);
80
+ context.skryba = { used: Boolean(message), model, reason: message ? null : "no usable answer" };
81
+ if (message) context.message = message;
82
+ };
@@ -0,0 +1,25 @@
1
+ // A conventional-commit draft from the staged changes alone: deterministic, no guessing beyond paths.
2
+ import { scopeOf, targetOf } from "./scopes.mjs";
3
+
4
+ const ONLY = { docs: "docs", tests: "test", ci: "ci", build: "build" };
5
+ const MARK = { added: "A", modified: "M", deleted: "D" };
6
+
7
+ export function composeMessage(staged) {
8
+ if (!staged.length) return null;
9
+ const scopes = [...new Set(staged.map(([path]) => scopeOf(path)))];
10
+ const states = new Set(staged.map(([, state]) => state));
11
+ const addsCode = staged.some(([path, state]) => state === "added" && path.startsWith("src/"));
12
+ const type = scopes.length === 1 && ONLY[scopes[0]] ? ONLY[scopes[0]] : addsCode ? "feat" : "chore";
13
+ const scope = scopes.length <= 2 ? `(${scopes.join(",")})` : "";
14
+ const verb = states.size === 1 ? { added: "add", deleted: "remove", modified: "update" }[[...states][0]] : "update";
15
+ const targets = [...new Set(staged.map(([path]) => targetOf(path)))];
16
+ const named = targets.slice(0, 3).join(", ") + (targets.length > 3 ? ` +${targets.length - 3} more` : "");
17
+ const header = `${type}${scope}: ${verb} ${named}`.slice(0, 100);
18
+ const body = staged.slice(0, 40).map(([path, state]) => `${MARK[state]} ${path}`);
19
+ if (staged.length > 40) body.push(`… ${staged.length - 40} more files`);
20
+ return `${header}\n\n${body.join("\n")}\n`;
21
+ }
22
+
23
+ export const composeDraft = (context) => {
24
+ context.message = composeMessage(context.staged);
25
+ };
@@ -0,0 +1,7 @@
1
+ import GitClient from "../../bridge/git-client/_index.mjs";
2
+
3
+ export const readStaged = async (context) => {
4
+ const git = new GitClient();
5
+ context.repo = (await git.root(context.options.cwd ?? process.cwd())) ?? process.cwd();
6
+ context.staged = await git.staged(context.repo);
7
+ };
@@ -0,0 +1,38 @@
1
+ // Which part of the project a path belongs to: the scope of a conventional commit.
2
+ const BUILD = new Set([
3
+ "package.json",
4
+ "package-lock.json",
5
+ "Taskfile.yml",
6
+ "vitest.config.mjs",
7
+ "commitlint.config.mjs",
8
+ "release.config.mjs",
9
+ "biome.json",
10
+ "Dockerfile",
11
+ "compose.yaml",
12
+ ".dockerignore",
13
+ "mkdocs.yml",
14
+ ".readthedocs.yaml",
15
+ ".gitignore",
16
+ ]);
17
+
18
+ export function scopeOf(path) {
19
+ const [top, part] = path.split("/");
20
+ if (top === "src") return ["main.ts", "cli.mjs", "index.mjs"].includes(part) ? "core" : part;
21
+ if (top === "tests") return "tests";
22
+ if (top === "docs") return "docs";
23
+ if (top === ".github") return "ci";
24
+ if (top === "resources") return "resources";
25
+ if (["skills", "template", ".claude-plugin"].includes(top)) return "skills";
26
+ if (top === ".husky" || BUILD.has(path)) return "build";
27
+ return "repo";
28
+ }
29
+
30
+ // the thing a path is about: src/procedures/closing/x.mjs → closing; docs/glossary/mode.md → mode
31
+ export function targetOf(path) {
32
+ const parts = path.split("/");
33
+ const base = parts.at(-1).replace(/\.[^.]+$/, "");
34
+ if (parts[0] === "src" && parts.length > 3) return parts[2];
35
+ return ["_index", "index", "class", "README", "exec", "help"].includes(base) && parts.length > 1
36
+ ? parts.at(-2)
37
+ : base;
38
+ }
@@ -0,0 +1,25 @@
1
+ // What Skryba reads: the stat of the staged changes, and a trimmed diff
2
+ // (lock files skipped, each file and the whole capped) so it fits a small model.
3
+ import SubprocessRunner from "../../bridge/subprocess-runner/_index.mjs";
4
+
5
+ const SKIP = /(^|\/)(package-lock\.json|uv\.lock|yarn\.lock|pnpm-lock\.yaml)$/;
6
+
7
+ export function stagedDiff(repo, { perFile = 1500, total = 8000 } = {}) {
8
+ const git = new SubprocessRunner();
9
+ const stat = git.try("git", ["diff", "--cached", "--stat"], { cwd: repo }) ?? "";
10
+ const diff = git.try("git", ["diff", "--cached", "-U2", "--no-color"], { cwd: repo }) ?? "";
11
+ let budget = total;
12
+ const parts = diff
13
+ .split(/^(?=diff --git )/m)
14
+ .filter((part) => {
15
+ const path = part.match(/^diff --git a\/(\S+)/)?.[1] ?? "";
16
+ return path && !SKIP.test(path);
17
+ })
18
+ .map((part) => {
19
+ const cut = part.slice(0, Math.max(0, Math.min(perFile, budget)));
20
+ budget -= cut.length;
21
+ return cut.length < part.length ? `${cut}\n… (trimmed)\n` : cut;
22
+ })
23
+ .filter(Boolean);
24
+ return { stat, diff: parts.join("") };
25
+ }
@@ -0,0 +1,15 @@
1
+ import { readFileSync, writeFileSync } from "node:fs";
2
+
3
+ // prepare-commit-msg: fill the editor only for a plain `git commit`, never over a
4
+ // message given with -m, a merge, a squash or an amend
5
+ const KEEP = new Set(["message", "merge", "squash", "commit"]);
6
+
7
+ export const writeMessage = (context) => {
8
+ const { file, source } = context.options;
9
+ if (!file || !context.message || KEEP.has(source)) return;
10
+ const current = readFileSync(file, "utf8");
11
+ if (current.split("\n").some((line) => line.trim() && !line.startsWith("#"))) return;
12
+ const hint = "# drafted by BOS from the staged changes: edit the type, scope and subject freely\n";
13
+ if (!context.dryRun) writeFileSync(file, context.message + hint + current);
14
+ context.written = file;
15
+ };
@@ -0,0 +1,13 @@
1
+ # Gathering Protocol
2
+
3
+ Part of PROVISION. Shows the House of Anubis as it is, along three axes:
4
+
5
+ - **children**: the adopted, named residents of `resources/house/children/`,
6
+ with their state (active, resting, frozen) and presence
7
+ - **orders**: groups with a duty, such as RavensArmy (`resources/house/orders/`),
8
+ with local units (ravens) and remote ones
9
+ - **bloodlines**: every served model grouped by releaser, found by following
10
+ its `parent_model` back to the root (`resources/house/bloodlines.json`)
11
+
12
+ Only the list of models is read from ollama; no model is started or prompted,
13
+ so resting residents are never disturbed.
@@ -0,0 +1,13 @@
1
+ import { BOS } from "../../main.ts";
2
+ import { listServed } from "./list-served.mjs";
3
+ import { mergeResidents } from "./merge-residents.mjs";
4
+ import { readRegistry } from "./read-registry.mjs";
5
+
6
+ export const gathering = new BOS.Procedure("gathering", [], {
7
+ description: "the House of Anubis: registry merged with what ollama serves",
8
+ })
9
+ .step("read the House registry", readRegistry)
10
+ .step("list served models", listServed)
11
+ .step("merge residents", mergeResidents);
12
+
13
+ export default gathering;
@@ -0,0 +1,9 @@
1
+ import OllamaLink from "../../bridge/ollama-link/_index.mjs";
2
+
3
+ // what ollama serves right now (names, architectures, parents); listing starts no model.
4
+ // null when ollama is unreachable
5
+ export const listServed = async (context) => {
6
+ const ollama = context.options.ollama ?? new OllamaLink();
7
+ const catalog = await ollama.catalog();
8
+ context.catalog = catalog.length ? catalog : null;
9
+ };
@@ -0,0 +1,31 @@
1
+ import { groupOf, sameModel } from "../../models/house/groups.mjs";
2
+
3
+ // children and orders with presence, bloodline of each served model, and bloodlines grouped by releaser
4
+ export const mergeResidents = (context) => {
5
+ const catalog = context.catalog ?? [];
6
+ const served = catalog.map((entry) => entry.name);
7
+ const present = (model) => (context.catalog ? served.some((name) => sameModel(name, model)) : null);
8
+ const withPresence = (group) => {
9
+ const claimed = served
10
+ .filter((model) => groupOf([group], model) && !group.members.some((m) => m.model && sameModel(m.model, model)))
11
+ .map((model) => ({ model, kind: "local" }));
12
+ const members = [...group.members, ...claimed].map((member) => ({
13
+ ...member,
14
+ state: member.state ?? group.state,
15
+ present: member.kind === "remote" ? null : present(member.model),
16
+ bloodline: member.model && context.catalog ? context.house.bloodlineOf(catalog, member.model) : null,
17
+ }));
18
+ return { ...group, members };
19
+ };
20
+ context.children = context.children.map(withPresence);
21
+ context.orders = context.orders.map(withPresence);
22
+ const lines = {};
23
+ for (const model of served) {
24
+ const { releaser } = context.house.bloodlineOf(catalog, model);
25
+ lines[releaser] ??= [];
26
+ lines[releaser].push(model);
27
+ }
28
+ context.bloodlines = Object.entries(lines)
29
+ .map(([releaser, models]) => ({ releaser, models }))
30
+ .sort((a, b) => b.models.length - a.models.length);
31
+ };
@@ -0,0 +1,7 @@
1
+ import House from "../../models/house/class.mjs";
2
+
3
+ export const readRegistry = (context) => {
4
+ context.house = new House(context.options.housePath);
5
+ context.children = context.house.children();
6
+ context.orders = context.house.orders();
7
+ };
@@ -0,0 +1,6 @@
1
+ # Hooking Protocol
2
+
3
+ Git hooks of any repository inside the workshop report to storylines: the
4
+ commit, branch or merge, and where the repository sits
5
+ (`forge / <scope> / <project>`). `bos hooks install` links a repository's
6
+ `post-commit`, `post-checkout` and `post-merge` to `bos hook <name>`.
@@ -0,0 +1,12 @@
1
+ import { BOS } from "../../main.ts";
2
+ import loading from "../loading/_index.mjs";
3
+ import locating from "../locating/_index.mjs";
4
+ import { installHooks } from "./install-hooks.mjs";
5
+ import { recordHook } from "./record-hook.mjs";
6
+
7
+ export const hooking = loading.chain(new BOS.Procedure("hooking").step("record hook", recordHook), "hooking");
8
+ export const installingHooks = locating.chain(
9
+ new BOS.Procedure("installing-hooks").step("link hooks", installHooks),
10
+ "installing-hooks",
11
+ );
12
+ export default hooking;
@@ -0,0 +1,10 @@
1
+ // what each git hook adds to its storyline entry
2
+ import GitClient from "../../bridge/git-client/_index.mjs";
3
+
4
+ const git = new GitClient();
5
+
6
+ export const HOOKS = {
7
+ "post-commit": async (repo) => ({ commit: (await git.log(repo, 1))[0] ?? null }),
8
+ "post-checkout": async (repo) => ({ branch: await git.branch(repo) }),
9
+ "post-merge": async (repo) => ({ head: (await git.log(repo, 1))[0]?.oid ?? null }),
10
+ };