@mnstry/atelier 0.2.0-alpha.11 → 0.2.0-alpha.13

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 (560) hide show
  1. package/CHANGELOG.md +1005 -0
  2. package/README.md +149 -9
  3. package/contracts/atelier-adoption-policy.v2.schema.json +55 -0
  4. package/contracts/atelier-architecture.v1.schema.json +258 -0
  5. package/contracts/atelier-boundary-delegations.v1.schema.json +161 -0
  6. package/contracts/atelier-boundary-owner-keys.v1.schema.json +98 -0
  7. package/contracts/atelier-build.v1.schema.json +1076 -0
  8. package/contracts/atelier-capability.v1.schema.json +1252 -0
  9. package/contracts/atelier-coordination.v1.schema.json +1050 -0
  10. package/contracts/atelier-evidence-navigation.v1.schema.json +603 -0
  11. package/contracts/atelier-harness.v1.schema.json +234 -0
  12. package/contracts/atelier-ingestion.v1.schema.json +806 -0
  13. package/contracts/atelier-inquiry.v1.schema.json +1772 -0
  14. package/contracts/atelier-instruction-adoption.v1.schema.json +490 -0
  15. package/contracts/atelier-interaction.v1.schema.json +553 -0
  16. package/contracts/atelier-judgment.v1.schema.json +690 -0
  17. package/contracts/atelier-knowledge.v1.schema.json +1512 -0
  18. package/contracts/atelier-learning.v1.schema.json +1095 -0
  19. package/contracts/atelier-migration.v3.schema.json +64 -0
  20. package/contracts/atelier-obsidian-generation-manifest.v2.schema.json +372 -0
  21. package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
  22. package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
  23. package/contracts/atelier-template-adoption.v1.schema.json +223 -0
  24. package/contracts/atelier-template-profile.v1.schema.json +1168 -0
  25. package/contracts/atelier-trackable.v1.schema.json +781 -0
  26. package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
  27. package/contracts/compat-baseline.json +1 -1
  28. package/docs/architecture.md +89 -0
  29. package/docs/assurance-controls.md +4 -3
  30. package/docs/blocks/capability-stewardship.md +17 -0
  31. package/docs/blocks/claims.md +16 -3
  32. package/docs/blocks/inquiry-harnesses.md +19 -0
  33. package/docs/blocks/learning-harnesses.md +17 -0
  34. package/docs/capability-stewardship.md +269 -0
  35. package/docs/coordination.md +51 -0
  36. package/docs/decision-practices.md +111 -0
  37. package/docs/decisions.md +29 -0
  38. package/docs/evidence-navigation.md +237 -0
  39. package/docs/exact-upgrades.md +3 -2
  40. package/docs/ingestion.md +181 -0
  41. package/docs/inquiry-harnesses.md +211 -0
  42. package/docs/install.md +80 -3
  43. package/docs/intake-and-guides.md +5 -0
  44. package/docs/integration-contract-decisions.md +24 -2
  45. package/docs/interaction-and-reflection.md +72 -0
  46. package/docs/knowledge-graph.md +20 -0
  47. package/docs/knowledge-health/api.md +30 -0
  48. package/docs/knowledge-health/assessment/api.md +96 -0
  49. package/docs/knowledge-health/assessment/capabilities.md +40 -0
  50. package/docs/knowledge-health/assessment/evaluation.md +27 -0
  51. package/docs/knowledge-health/assessment/facilitator.md +30 -0
  52. package/docs/knowledge-health/assessment/quickstart.md +61 -0
  53. package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
  54. package/docs/knowledge-health/devday/facilitator.md +16 -0
  55. package/docs/knowledge-health/devday/participant-response.md +22 -0
  56. package/docs/knowledge-health/devday/public-default.md +107 -0
  57. package/docs/knowledge-health/devday/quickstart.md +18 -0
  58. package/docs/knowledge-health/devday/support.md +22 -0
  59. package/docs/knowledge-health/exercise.md +17 -0
  60. package/docs/knowledge-health/facilitator.md +20 -0
  61. package/docs/knowledge-health/quickstart.md +94 -0
  62. package/docs/knowledge-health/readme.md +15 -0
  63. package/docs/knowledge-health/support.md +34 -0
  64. package/docs/knowledge-setup.md +248 -0
  65. package/docs/knowledge-workspace.md +264 -0
  66. package/docs/learning-harnesses.md +238 -0
  67. package/docs/learning.md +215 -0
  68. package/docs/local-services.md +242 -26
  69. package/docs/obsidian-contract.md +878 -36
  70. package/docs/obsidian-plugin.md +619 -0
  71. package/docs/obsidian.md +809 -64
  72. package/docs/optional-ingestion.md +83 -0
  73. package/docs/personal-workspace.md +257 -0
  74. package/docs/practical-judgment.md +92 -0
  75. package/docs/progressive-ingestion-acceptance.md +89 -0
  76. package/docs/progressive-ingestion.md +235 -0
  77. package/docs/project-command-inventory.md +1 -0
  78. package/docs/release-engineering.md +56 -1
  79. package/docs/repo-boundary-guard.md +94 -0
  80. package/docs/research-starter-quickstart.md +66 -0
  81. package/docs/research-starter.md +114 -0
  82. package/docs/semantic-ingestion.md +198 -0
  83. package/docs/semantic-operation.md +317 -0
  84. package/docs/skill-steward.md +6 -0
  85. package/docs/template-profile.md +164 -0
  86. package/docs/template-upgrades.md +114 -0
  87. package/docs/trackables.md +111 -0
  88. package/docs/upgrade.md +68 -3
  89. package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
  90. package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
  91. package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
  92. package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
  93. package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
  94. package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
  95. package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
  96. package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
  97. package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
  98. package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
  99. package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
  100. package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
  101. package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
  102. package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
  103. package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
  104. package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
  105. package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
  106. package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
  107. package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
  108. package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
  109. package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
  110. package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
  111. package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
  112. package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
  113. package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
  114. package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
  115. package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
  116. package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
  117. package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
  118. package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
  119. package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
  120. package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
  121. package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
  122. package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
  123. package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
  124. package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
  125. package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
  126. package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
  127. package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
  128. package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
  129. package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
  130. package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
  131. package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
  132. package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
  133. package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
  134. package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
  135. package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
  136. package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
  137. package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
  138. package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
  139. package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
  140. package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
  141. package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
  142. package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
  143. package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
  144. package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
  145. package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
  146. package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
  147. package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
  148. package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
  149. package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
  150. package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
  151. package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
  152. package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
  153. package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
  154. package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
  155. package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
  156. package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
  157. package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
  158. package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
  159. package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
  160. package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
  161. package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
  162. package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
  163. package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
  164. package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
  165. package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
  166. package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
  167. package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
  168. package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
  169. package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
  170. package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
  171. package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
  172. package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
  173. package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
  174. package/fixtures/atelier-interaction/act/valid/document.json +24 -0
  175. package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
  176. package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
  177. package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
  178. package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
  179. package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
  180. package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
  181. package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
  182. package/fixtures/atelier-judgment/case/valid/document.json +75 -0
  183. package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
  184. package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
  185. package/fixtures/atelier-judgment/native-decision.md +3 -0
  186. package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
  187. package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
  188. package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
  189. package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
  190. package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
  191. package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
  192. package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
  193. package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
  194. package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
  195. package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
  196. package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
  197. package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
  198. package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
  199. package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
  200. package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
  201. package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
  202. package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
  203. package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
  204. package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
  205. package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
  206. package/fixtures/atelier-learning/invalid/authority.json +14 -0
  207. package/fixtures/atelier-learning/valid/capture.json +13 -0
  208. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
  209. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
  210. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
  211. package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
  212. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
  213. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
  214. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
  215. package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
  216. package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
  217. package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
  218. package/fixtures/atelier-trackable/request/valid/document.json +22 -0
  219. package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
  220. package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
  221. package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
  222. package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
  223. package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
  224. package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
  225. package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
  226. package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
  227. package/fixtures/capability-packages/adoption.example.json +70 -0
  228. package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
  229. package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
  230. package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
  231. package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
  232. package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
  233. package/fixtures/capability-packages/research/capability-package.json +88 -0
  234. package/fixtures/capability-packages/research/capability-release.json +134 -0
  235. package/fixtures/capability-packages/research/evaluation.md +15 -0
  236. package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
  237. package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
  238. package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
  239. package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
  240. package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
  241. package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
  242. package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
  243. package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
  244. package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
  245. package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
  246. package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
  247. package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
  248. package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
  249. package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
  250. package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
  251. package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
  252. package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
  253. package/fixtures/evidence-navigation/valid/claim.json +36 -0
  254. package/fixtures/evidence-navigation/valid/host.json +16 -0
  255. package/fixtures/evidence-navigation/valid/profile.json +16 -0
  256. package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
  257. package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
  258. package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
  259. package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
  260. package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
  261. package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
  262. package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
  263. package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
  264. package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
  265. package/fixtures/harnesses/learning-cycle.json +780 -0
  266. package/fixtures/inquiry/workshop.json +675 -0
  267. package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
  268. package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
  269. package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
  270. package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
  271. package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
  272. package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
  273. package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
  274. package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
  275. package/fixtures/knowledge-health/calibration-suite.json +26 -0
  276. package/fixtures/knowledge-health/calibration-trial.json +37 -0
  277. package/fixtures/knowledge-health/corrected/checklist.md +18 -0
  278. package/fixtures/knowledge-health/devday/participant-record.json +19 -0
  279. package/fixtures/knowledge-health/expected-summary.json +27 -0
  280. package/fixtures/knowledge-health/workshop/README.md +20 -0
  281. package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
  282. package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
  283. package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
  284. package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
  285. package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
  286. package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
  287. package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
  288. package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
  289. package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
  290. package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
  291. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/case-only-collision.v2.json +160 -0
  292. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/flat-note-path.v2.json +160 -0
  293. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/hidden-folder.v2.json +160 -0
  294. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/identity-in-the-body.v2.json +160 -0
  295. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/link-breaking-name.v2.json +160 -0
  296. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-identity-region.v2.json +156 -0
  297. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-layout-version.v2.json +159 -0
  298. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/note-named-like-a-folder.v2.json +160 -0
  299. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/unknown-region-kind.v2.json +160 -0
  300. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/v1-contract-version.v2.json +161 -0
  301. package/fixtures/obsidian/contracts/generation-manifest-v2/valid/three-notes.v2.json +160 -0
  302. package/fixtures/obsidian/edits/workspace.json +1 -1
  303. package/fixtures/obsidian/materialization/expected-full.json +21 -21
  304. package/fixtures/obsidian/materialization/expected-full.layout-1.json +99 -0
  305. package/fixtures/obsidian/materialization/expected-scoped.json +7 -7
  306. package/fixtures/obsidian/materialization/expected-scoped.layout-1.json +39 -0
  307. package/fixtures/personal-workspace/overlay.json +24 -0
  308. package/fixtures/templates/local-library.v1.json +75 -0
  309. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
  310. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
  311. package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
  312. package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
  313. package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
  314. package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
  315. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
  316. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
  317. package/package.json +64 -6
  318. package/plugins/obsidian/main.js +444 -0
  319. package/plugins/obsidian/manifest.json +9 -0
  320. package/plugins/obsidian/styles.css +22 -0
  321. package/skills/claude/atelier-build-harness/SKILL.md +59 -0
  322. package/skills/claude/atelier-coordination/SKILL.md +32 -0
  323. package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
  324. package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
  325. package/skills/claude/atelier-interaction/SKILL.md +34 -0
  326. package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
  327. package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
  328. package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
  329. package/skills/claude/atelier-research-harness/SKILL.md +43 -0
  330. package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
  331. package/skills/claude/atelier-trackables/SKILL.md +40 -0
  332. package/skills/codex/atelier-build-harness/SKILL.md +59 -0
  333. package/skills/codex/atelier-coordination/SKILL.md +32 -0
  334. package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
  335. package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
  336. package/skills/codex/atelier-interaction/SKILL.md +34 -0
  337. package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
  338. package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
  339. package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
  340. package/skills/codex/atelier-research-harness/SKILL.md +43 -0
  341. package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
  342. package/skills/codex/atelier-trackables/SKILL.md +40 -0
  343. package/src/architecture/catalog.mjs +355 -0
  344. package/src/architecture/index.mjs +93 -0
  345. package/src/boundary/content-rules.mjs +4 -2
  346. package/src/boundary/delegation-command.mjs +158 -0
  347. package/src/boundary/delegation.mjs +396 -0
  348. package/src/boundary/policy.mjs +52 -10
  349. package/src/build/git.mjs +18 -0
  350. package/src/build/index.mjs +3 -0
  351. package/src/build/ledger.mjs +88 -0
  352. package/src/capabilities/adoption.mjs +381 -0
  353. package/src/capabilities/evidence.mjs +140 -0
  354. package/src/capabilities/files.mjs +124 -0
  355. package/src/capabilities/index.mjs +4 -0
  356. package/src/capabilities/instructions.mjs +250 -0
  357. package/src/capabilities/package.mjs +136 -0
  358. package/src/cli/command-failure.mjs +36 -0
  359. package/src/cli/execute-command.mjs +16 -2
  360. package/src/cli/run.mjs +65 -9
  361. package/src/coauthor/store.mjs +43 -2
  362. package/src/collaboration/proposals.mjs +189 -0
  363. package/src/commands/architecture.mjs +27 -0
  364. package/src/commands/boundary.mjs +3 -0
  365. package/src/commands/capability.mjs +73 -0
  366. package/src/commands/enroll.mjs +3 -0
  367. package/src/commands/harness.mjs +79 -0
  368. package/src/commands/ingest.mjs +39 -0
  369. package/src/commands/init.mjs +4 -1
  370. package/src/commands/inquiry.mjs +66 -0
  371. package/src/commands/knowledge-semantic.mjs +128 -0
  372. package/src/commands/knowledge.mjs +119 -0
  373. package/src/commands/learn.mjs +61 -0
  374. package/src/commands/obsidian.mjs +832 -62
  375. package/src/commands/practice.mjs +24 -0
  376. package/src/commands/setup.mjs +22 -2
  377. package/src/commands/trackable.mjs +19 -0
  378. package/src/contracts/corpus.mjs +36 -0
  379. package/src/coordination/index.mjs +110 -0
  380. package/src/egress/forbidden-egress.mjs +2 -0
  381. package/src/evidence-navigation/contracts.mjs +60 -0
  382. package/src/evidence-navigation/evaluate.mjs +69 -0
  383. package/src/evidence-navigation/generation.mjs +22 -0
  384. package/src/evidence-navigation/index.mjs +2 -0
  385. package/src/evidence-navigation/local.mjs +167 -0
  386. package/src/evidence-navigation/schema.generated.mjs +604 -0
  387. package/src/evidence-navigation/types.mjs +12 -0
  388. package/src/export/atelier-export-contract.mjs +35 -8
  389. package/src/graph/enroll.mjs +248 -0
  390. package/src/graph/graph.mjs +17 -1
  391. package/src/graph/knowledge-graph.mjs +58 -8
  392. package/src/harnesses/contracts.mjs +41 -0
  393. package/src/harnesses/exchange.mjs +154 -0
  394. package/src/harnesses/feedback.mjs +16 -0
  395. package/src/harnesses/history.mjs +37 -0
  396. package/src/harnesses/index.mjs +4 -0
  397. package/src/harnesses/store.mjs +29 -0
  398. package/src/ingestion/contracts.mjs +41 -0
  399. package/src/ingestion/evaluation.mjs +119 -0
  400. package/src/ingestion/optional/composition/index.mjs +171 -0
  401. package/src/ingestion/optional/evaluation/index.mjs +219 -0
  402. package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
  403. package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
  404. package/src/ingestion/optional/graphify/graphify_supplier.py +933 -0
  405. package/src/ingestion/optional/graphify/json_bridge.py +163 -0
  406. package/src/ingestion/optional/graphify/query_receiving.py +209 -0
  407. package/src/ingestion/optional/index.mjs +9 -0
  408. package/src/ingestion/optional/jev/bridge.mjs +166 -0
  409. package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
  410. package/src/ingestion/optional/jev/judgments.mjs +174 -0
  411. package/src/ingestion/optional/jev/native-translation.mjs +32 -0
  412. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
  413. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
  414. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
  415. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
  416. package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
  417. package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
  418. package/src/ingestion/optional/vanilla/index.mjs +218 -0
  419. package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
  420. package/src/ingestion/processors.mjs +218 -0
  421. package/src/ingestion/semantic.mjs +203 -0
  422. package/src/ingestion/store.mjs +372 -0
  423. package/src/inquiry/beliefs.mjs +32 -0
  424. package/src/inquiry/contracts.mjs +23 -0
  425. package/src/inquiry/index.mjs +5 -0
  426. package/src/inquiry/ledger.mjs +135 -0
  427. package/src/inquiry/lenses.mjs +15 -0
  428. package/src/inquiry/projection.mjs +77 -0
  429. package/src/inquiry/store.mjs +35 -0
  430. package/src/intake/read-scope.mjs +12 -0
  431. package/src/intake/store.mjs +69 -6
  432. package/src/interaction/index.mjs +91 -0
  433. package/src/judgment/index.mjs +82 -0
  434. package/src/judgment/practice-evaluation.mjs +70 -0
  435. package/src/judgment/practice.mjs +144 -0
  436. package/src/knowledge/context.mjs +217 -0
  437. package/src/knowledge/index.mjs +5 -0
  438. package/src/knowledge/ingestion.mjs +79 -0
  439. package/src/knowledge/intake.mjs +33 -0
  440. package/src/knowledge/ledger.mjs +110 -0
  441. package/src/knowledge/participatory/guidance.mjs +36 -0
  442. package/src/knowledge/participatory/guided-contribution.mjs +44 -0
  443. package/src/knowledge/participatory/index.mjs +2 -0
  444. package/src/knowledge/participatory/installed-workshop.mjs +108 -0
  445. package/src/knowledge/plan.mjs +67 -0
  446. package/src/knowledge/projection.mjs +43 -0
  447. package/src/knowledge/semantic-operation.mjs +567 -0
  448. package/src/knowledge/sessions.mjs +467 -0
  449. package/src/knowledge/workspace.mjs +295 -0
  450. package/src/knowledge-health/assessment.mjs +165 -0
  451. package/src/knowledge-health/capabilities.mjs +350 -0
  452. package/src/knowledge-health/context.mjs +62 -0
  453. package/src/knowledge-health/example.mjs +47 -0
  454. package/src/knowledge-health/index.mjs +10 -0
  455. package/src/knowledge-health/owner-join.mjs +88 -0
  456. package/src/knowledge-health/proof/common.mjs +24 -0
  457. package/src/knowledge-health/proof/installed-check.mjs +100 -0
  458. package/src/knowledge-health/proof/native-readback.mjs +37 -0
  459. package/src/knowledge-health/proof/rehearse.mjs +68 -0
  460. package/src/knowledge-health/proof/validate-participant.mjs +17 -0
  461. package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
  462. package/src/knowledge-health/proof/workshop-check.mjs +36 -0
  463. package/src/knowledge-health/rule-identity.mjs +44 -0
  464. package/src/knowledge-health/workshop-composition.mjs +19 -0
  465. package/src/knowledge-health/workshop-profile.mjs +176 -0
  466. package/src/knowledge-health/workshop.mjs +99 -0
  467. package/src/learning/adapters.mjs +68 -0
  468. package/src/learning/assistance.mjs +127 -0
  469. package/src/learning/contracts.mjs +65 -0
  470. package/src/learning/store.mjs +272 -0
  471. package/src/personal-workspace/index.mjs +391 -0
  472. package/src/personal-workspace/selection.mjs +385 -0
  473. package/src/project/config.mjs +8 -0
  474. package/src/project/durable-state.mjs +3 -1
  475. package/src/project/file-class.mjs +1 -1
  476. package/src/project/private-state.mjs +10 -1
  477. package/src/projection/obsidian/contracts.mjs +224 -18
  478. package/src/projection/obsidian/edits/apply.mjs +63 -19
  479. package/src/projection/obsidian/edits/regions.mjs +137 -42
  480. package/src/projection/obsidian/materialize/index.mjs +6 -3
  481. package/src/projection/obsidian/materialize/path-registry.mjs +443 -57
  482. package/src/projection/obsidian/materialize/prepare-view.mjs +382 -131
  483. package/src/projection/obsidian/materialize/redaction.mjs +327 -0
  484. package/src/projection/obsidian/materialize/settings.mjs +113 -34
  485. package/src/projection/obsidian/personal-workspace.mjs +436 -0
  486. package/src/projection/obsidian/plugin-bridge/bundle.mjs +50 -0
  487. package/src/projection/obsidian/plugin-bridge/channel.mjs +174 -0
  488. package/src/projection/obsidian/plugin-bridge/index.mjs +10 -0
  489. package/src/projection/obsidian/proposals/adapter.mjs +47 -8
  490. package/src/projection/obsidian/proposals/observation.mjs +20 -7
  491. package/src/projection/obsidian/publication/bridge-script.mjs +11 -5
  492. package/src/projection/obsidian/publication/index.mjs +2 -1
  493. package/src/projection/obsidian/publication/publisher.mjs +312 -36
  494. package/src/projection/obsidian/publication/transport.mjs +63 -8
  495. package/src/projection/obsidian/publication/vault-list.mjs +200 -0
  496. package/src/projection/obsidian/recovery/index.mjs +4 -1
  497. package/src/projection/obsidian/recovery/restart.mjs +3 -1
  498. package/src/projection/obsidian/recovery/store.mjs +168 -8
  499. package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
  500. package/src/projection/obsidian/selection-ui/focus.mjs +14 -7
  501. package/src/projection/obsidian/selection-ui/selection-state.mjs +2 -2
  502. package/src/projection/obsidian/selection-ui/selection.mjs +8 -3
  503. package/src/readiness/readiness.mjs +10 -3
  504. package/src/reflection/index.mjs +56 -0
  505. package/src/runtime/obsidian/app-capability.mjs +104 -12
  506. package/src/runtime/obsidian/app-production-seams.mjs +161 -42
  507. package/src/runtime/obsidian/app-registration.mjs +313 -0
  508. package/src/runtime/obsidian/app-restart.mjs +154 -0
  509. package/src/runtime/obsidian/engine.mjs +288 -29
  510. package/src/runtime/obsidian/extension-points.mjs +1 -1
  511. package/src/runtime/obsidian/index.mjs +15 -8
  512. package/src/runtime/obsidian/launch-plan.mjs +91 -0
  513. package/src/runtime/obsidian/lifecycle.mjs +193 -22
  514. package/src/runtime/obsidian/login-item.mjs +332 -0
  515. package/src/runtime/obsidian/machine-settings.mjs +119 -12
  516. package/src/runtime/obsidian/opening.mjs +369 -63
  517. package/src/runtime/obsidian/pending-edits.mjs +10 -0
  518. package/src/runtime/obsidian/pipeline.mjs +71 -3
  519. package/src/runtime/obsidian/plugin-channel.mjs +289 -0
  520. package/src/runtime/obsidian/plugin-choice.mjs +182 -0
  521. package/src/runtime/obsidian/plugin-drift.mjs +124 -0
  522. package/src/runtime/obsidian/plugin-presence.mjs +61 -0
  523. package/src/runtime/obsidian/process-identity.mjs +20 -10
  524. package/src/runtime/obsidian/project-views.mjs +282 -0
  525. package/src/runtime/obsidian/questions.mjs +48 -0
  526. package/src/runtime/obsidian/release-watch.mjs +56 -0
  527. package/src/runtime/obsidian/service-main.mjs +113 -17
  528. package/src/runtime/obsidian/service-manager-production.mjs +61 -0
  529. package/src/runtime/obsidian/service-managers.mjs +275 -0
  530. package/src/runtime/obsidian/service-record.mjs +105 -5
  531. package/src/runtime/obsidian/service-server.mjs +50 -12
  532. package/src/runtime/obsidian/service.mjs +134 -7
  533. package/src/runtime/obsidian/startup-adapters.mjs +59 -21
  534. package/src/runtime/obsidian/state-store.mjs +13 -1
  535. package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
  536. package/src/runtime/obsidian/vault-location.mjs +300 -0
  537. package/src/runtime/obsidian/view-counts.mjs +34 -0
  538. package/src/server/local-sidecar.mjs +193 -7
  539. package/src/server/server.mjs +45 -6
  540. package/src/template-bindings/project.mjs +92 -0
  541. package/src/templates/profile.mjs +225 -0
  542. package/src/trackables/domain.mjs +175 -0
  543. package/src/trackables/store.mjs +114 -0
  544. package/src/ui/knowledge-page.mjs +1388 -0
  545. package/src/upgrade/explanation.mjs +1 -1
  546. package/src/upgrade/template-participant.mjs +171 -0
  547. package/src/upgrade/transaction.mjs +72 -25
  548. package/src/upgrade/upgrade.mjs +2 -1
  549. package/templates/external-project-workspace/pack-lifecycle.json +6 -1
  550. package/templates/knowledge-workspace/.gitattributes +2 -0
  551. package/templates/knowledge-workspace/AGENTS.md +27 -0
  552. package/templates/knowledge-workspace/README.md +44 -0
  553. package/templates/knowledge-workspace/atelier.project.json +22 -0
  554. package/templates/knowledge-workspace/gitignore +6 -0
  555. package/templates/knowledge-workspace/knowledge-plan.json +111 -0
  556. package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
  557. package/templates/knowledge-workspace/records/forecast.md +22 -0
  558. package/templates/knowledge-workspace/records/inspection.md +25 -0
  559. package/templates/knowledge-workspace/records/telescope.md +24 -0
  560. package/templates/knowledge-workspace/repo-access.v1.json +9 -0
@@ -0,0 +1,124 @@
1
+ import crypto from 'node:crypto'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { execFileSync } from 'node:child_process'
5
+ import { openRegularFileNoFollow, atomicReplacePrivateText, syncPrivateDirectory } from '../project/private-state.mjs'
6
+
7
+ export const LIMITS = Object.freeze({ files: 512, directories: 256, bytes: 8 * 1024 * 1024, depth: 16 })
8
+ export const digest = (bytes) => `sha256:${crypto.createHash('sha256').update(bytes).digest('hex')}`
9
+ export function canonical(value) {
10
+ if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`
11
+ if (value && typeof value === 'object') return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`
12
+ return JSON.stringify(value)
13
+ }
14
+ export const objectDigest = (value) => digest(canonical(value))
15
+ export const jsonText = (value) => `${JSON.stringify(value, null, 2)}\n`
16
+ export function stat(file) {
17
+ try { return fs.lstatSync(file) } catch (error) { if (error.code === 'ENOENT') return null; throw error }
18
+ }
19
+
20
+ export function relativeFile(value) {
21
+ if (typeof value !== 'string' || !/^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*$/.test(value) ||
22
+ value.split('/').some(part => part === '.' || part === '..' || part.toLowerCase() === '.git' || /[. ]$/.test(part) || /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(part))) {
23
+ throw new Error('invalid portable relative path')
24
+ }
25
+ return value
26
+ }
27
+
28
+ // All callers resolve an explicitly supplied root once. No links below it are
29
+ // followed, including intermediate components and absent write destinations.
30
+ export function within(root, relative, { directory = false, create = false } = {}) {
31
+ relativeFile(relative)
32
+ const parts = relative.split('/')
33
+ let current = root
34
+ for (let index = 0; index < parts.length; index += 1) {
35
+ current = path.join(current, parts[index])
36
+ const isDirectory = index < parts.length - 1 || directory
37
+ let entry = stat(current)
38
+ if (!entry && isDirectory && create) {
39
+ fs.mkdirSync(current, { mode: 0o700 })
40
+ syncPrivateDirectory(path.dirname(current))
41
+ entry = stat(current)
42
+ }
43
+ if (entry && (entry.isSymbolicLink() || (isDirectory ? !entry.isDirectory() : !entry.isFile()))) throw new Error('redirected or non-regular path component')
44
+ }
45
+ return current
46
+ }
47
+
48
+ export function bytesAt(root, relative) {
49
+ const file = within(root, relative)
50
+ const fd = openRegularFileNoFollow(file)
51
+ try {
52
+ if (fs.fstatSync(fd).size > LIMITS.bytes) throw new Error('file exceeds byte ceiling')
53
+ const bytes = fs.readFileSync(fd)
54
+ if (bytes.length > LIMITS.bytes) throw new Error('file exceeds byte ceiling')
55
+ return bytes
56
+ } finally { fs.closeSync(fd) }
57
+ }
58
+
59
+ export function jsonAt(root, relative) {
60
+ return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytesAt(root, relative)))
61
+ }
62
+
63
+ export function tree(root, relative = '', state = { files: [], bytes: 0, directories: 0 }) {
64
+ const directory = relative ? within(root, relative, { directory: true }) : root
65
+ if (!stat(directory)?.isDirectory() || stat(directory).isSymbolicLink()) throw new Error('bundle directory missing or redirected')
66
+ if (++state.directories > LIMITS.directories || relative.split('/').length > LIMITS.depth) throw new Error('bundle directory ceiling exceeded')
67
+ for (const name of fs.readdirSync(directory).sort()) {
68
+ const file = relative ? `${relative}/${name}` : name
69
+ relativeFile(file)
70
+ const entry = stat(path.join(root, file))
71
+ if (entry.isSymbolicLink()) throw new Error('bundle contains a symbolic link')
72
+ if (entry.isDirectory()) { tree(root, file, state); continue }
73
+ if (!entry.isFile()) throw new Error('bundle contains a non-regular file')
74
+ if (state.files.length >= LIMITS.files || state.bytes + entry.size > LIMITS.bytes) throw new Error('bundle size ceiling exceeded')
75
+ const bytes = bytesAt(root, file)
76
+ state.bytes += bytes.length
77
+ if (state.bytes > LIMITS.bytes) throw new Error('bundle size ceiling exceeded')
78
+ state.files.push({ path: file, bytes })
79
+ }
80
+ return state.files
81
+ }
82
+
83
+ export const fileInventory = (files) => files.map(file => ({ path: file.path, digest: digest(file.bytes), bytes: file.bytes.length }))
84
+ export const treeDigest = (files) => objectDigest(fileInventory(files))
85
+ export function currentTree(root, relative) {
86
+ const directory = within(root, relative, { directory: true })
87
+ return stat(directory) ? treeDigest(tree(directory)) : null
88
+ }
89
+
90
+ export function writeNew(root, relative, bytes) {
91
+ const parent = path.posix.dirname(relative)
92
+ if (parent !== '.') within(root, parent, { directory: true, create: true })
93
+ const file = within(root, relative)
94
+ const fd = openRegularFileNoFollow(file, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600)
95
+ try { fs.writeFileSync(fd, bytes); fs.fsyncSync(fd) } finally { fs.closeSync(fd) }
96
+ syncPrivateDirectory(path.dirname(file))
97
+ }
98
+
99
+ export function replaceJson(root, relative, value) {
100
+ const parent = path.posix.dirname(relative)
101
+ if (parent !== '.') within(root, parent, { directory: true, create: true })
102
+ atomicReplacePrivateText(within(root, relative), jsonText(value))
103
+ }
104
+
105
+ export function workspaceRoot(input, { write = false } = {}) {
106
+ const root = fs.realpathSync(path.resolve(input))
107
+ const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.toUpperCase().startsWith('GIT_')))
108
+ const git = args => execFileSync('git', ['-C', root, ...args], { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim()
109
+ const top = fs.realpathSync(git(['rev-parse', '--show-toplevel']))
110
+ // Git can expand a Windows short-name alias. Preserve case-sensitive paths
111
+ // and compare actual directory identity when the resolved spellings differ.
112
+ const rootStat = fs.statSync(root, { bigint: true }), topStat = fs.statSync(top, { bigint: true })
113
+ if (!rootStat.isDirectory() || !topStat.isDirectory() ||
114
+ (top !== root && !(rootStat.ino !== 0n && rootStat.dev === topStat.dev && rootStat.ino === topStat.ino))) {
115
+ throw new Error('capability workspace must be the repository root')
116
+ }
117
+ if (write) {
118
+ if (process.platform === 'win32') throw new Error('capability writes require a qualified POSIX filesystem')
119
+ if (git(['ls-files', '--', '.atelier-local'])) throw new Error('private capability state is tracked')
120
+ try { git(['check-ignore', '--quiet', '.atelier-local/']) } catch { throw new Error('capability writes require ignored .atelier-local/ state') }
121
+ }
122
+ within(root, '.atelier-local', { directory: true })
123
+ return root
124
+ }
@@ -0,0 +1,4 @@
1
+ export { HOST_PROFILES, PACKAGE_FILE, RELEASE_FILE, inventorySkillSurfaces, prepareCapabilityRelease, sealCapabilityRelease, validateCapabilityDocument, verifyCapabilityRelease } from './package.mjs'
2
+ export { CAPABILITY_STATE, applyCapabilityAdoption, inspectCapabilityAdoption, inspectCapabilityFleet, inspectCapabilityRecovery, planCapabilityAdoption, recoverCapabilityAdoption } from './adoption.mjs'
3
+ export { capabilityEvidenceStatus, capabilityGraphSources, capabilityImprovementCandidates, readCapabilityEvents, recordCapabilityEvent } from './evidence.mjs'
4
+ export * from './instructions.mjs'
@@ -0,0 +1,250 @@
1
+ import fs from 'node:fs'
2
+ import { createLearningStore } from '../learning/store.mjs'
3
+ import { boundedLearningValue, learningDigest } from '../learning/contracts.mjs'
4
+ import { bytesAt, digest, jsonAt, replaceJson, stat, within, workspaceRoot, writeNew } from './files.mjs'
5
+ import { syncPrivateDirectory } from '../project/private-state.mjs'
6
+ import { publishPrivateFile, withPrivateLock } from '../project/durable-state.mjs'
7
+ import { exchangeFiles, probeExchange } from '../projection/obsidian/publication/exchange.mjs'
8
+ import path from 'node:path'
9
+ import Ajv from 'ajv/dist/2020.js'
10
+
11
+ const instructionSchema = JSON.parse(fs.readFileSync(new URL('../../contracts/atelier-instruction-adoption.v1.schema.json', import.meta.url)))
12
+ const validateDocument = new Ajv({ strict: true, allErrors: true }).compile(instructionSchema)
13
+ export function validateInstructionAdoption(value) {
14
+ try { boundedLearningValue(value) } catch (error) { return [error.message] }
15
+ return validateDocument(value) ? [] : validateDocument.errors.map(e => `${e.instancePath || '/'}: ${e.message}`)
16
+ }
17
+ function assertDocument(value) {
18
+ const errors = validateInstructionAdoption(value)
19
+ if (errors.length) throw new Error(`invalid instruction document: ${errors.slice(0, 3).join('; ')}`)
20
+ return value
21
+ }
22
+
23
+ const BASE = '.atelier-local/capabilities/instructions'
24
+ const STATE = `${BASE}/state.json`, ACTIVE = `${BASE}/active.json`
25
+ function withOperationLock(root, operation) {
26
+ within(root, BASE, { directory: true, create: true })
27
+ return withPrivateLock(within(root, `${BASE}/operation.lock`), operation)
28
+ }
29
+ const identifier = /^[a-z][a-z0-9-]{0,63}$/
30
+ const clone = value => structuredClone(value)
31
+ const seal = value => assertDocument({ ...value, digest: learningDigest(value) })
32
+ function verified(value, label) {
33
+ assertDocument(value)
34
+ const { digest: actual, ...body } = boundedLearningValue(value)
35
+ if (actual !== learningDigest(body)) throw new Error(`${label} integrity mismatch`)
36
+ return value
37
+ }
38
+ function readText(root, target) {
39
+ const file = within(root, target)
40
+ return stat(file) ? new TextDecoder('utf-8', { fatal: true }).decode(bytesAt(root, target)) : null
41
+ }
42
+ function stateAt(root) {
43
+ const state = stat(within(root, STATE)) ? verified(jsonAt(root, STATE), 'instruction state') : seal({ schema: 'atelier-instruction-state@v1', workspace: digest(root), revision: 0, bindings: [] })
44
+ if (state.schema !== 'atelier-instruction-state@v1' || state.workspace !== digest(root) || !Array.isArray(state.bindings) || !Number.isSafeInteger(state.revision)) throw new Error('invalid instruction state')
45
+ return state
46
+ }
47
+ function activeAt(root) {
48
+ return stat(within(root, ACTIVE)) ? verified(jsonAt(root, ACTIVE), 'instruction transaction') : null
49
+ }
50
+ function inputOf(options) {
51
+ const { workspaceId, scope, harnessId, lessonId, target, slot, mode = 'adopt' } = options
52
+ const input = boundedLearningValue({ workspaceId, scope, harnessId, lessonId, target, slot, mode })
53
+ if (!identifier.test(slot) || !['adopt', 'retire'].includes(mode) || typeof target !== 'string' ||
54
+ !/^(?:[A-Za-z0-9_-]+\/)*(?:AGENTS|CLAUDE)\.md$/.test(target)) throw new Error('instruction profile requires a scoped AGENTS.md or CLAUDE.md and stable slot')
55
+ return input
56
+ }
57
+ function markers(slot) { return [`<!-- atelier:practice:${slot}:start -->`, `<!-- atelier:practice:${slot}:end -->`] }
58
+ function blockOf(text, slot) {
59
+ const [start, end] = markers(slot), a = text?.indexOf(start) ?? -1, b = text?.indexOf(end) ?? -1
60
+ if (a === -1 && b === -1) return null
61
+ if (a === -1 || b === -1 || b < a || text.indexOf(start, a + 1) !== -1 || text.indexOf(end, b + 1) !== -1) throw new Error('instruction markers are ambiguous')
62
+ return text.slice(a, b + end.length)
63
+ }
64
+ function selected(root, input) {
65
+ const store = createLearningStore({ workspaceRoot: root, workspaceId: input.workspaceId })
66
+ const context = store.context({ scope: input.scope, harnessId: input.harnessId })
67
+ const lesson = context.lessons.find(item => item.id === input.lessonId)
68
+ if (!lesson || lesson.artifact.kind !== 'instruction') throw new Error('instruction requires current, scoped, explicitly activated learning')
69
+ if ([lesson.artifact.content, ...lesson.exceptions].some(text => text.includes('<!-- atelier:practice:'))) throw new Error('reserved instruction marker in guidance')
70
+ return { context, lesson }
71
+ }
72
+ function prepare(root, options) {
73
+ const input = inputOf(options), state = stateAt(root)
74
+ if (activeAt(root)) throw new Error('unfinished instruction adoption; recover first')
75
+ const previous = state.bindings.find(b => b.target.toLowerCase() === input.target.toLowerCase() && b.slot === input.slot)
76
+ if (previous && (previous.target !== input.target || previous.workspaceId !== input.workspaceId || previous.harnessId !== input.harnessId || learningDigest(previous.scope) !== learningDigest(input.scope))) throw new Error('instruction slot has another owner or scope')
77
+ if (state.bindings.length >= 128 && !previous) throw new Error('instruction binding ceiling reached')
78
+ const before = readText(root, input.target), oldBlock = blockOf(before, input.slot)
79
+ if ((previous?.block ?? null) !== oldBlock) throw new Error('instruction slot is unmanaged or drifted')
80
+ let pin = previous?.pin ?? null, block = null, learningRevision = null
81
+ if (input.mode === 'adopt') {
82
+ const { context, lesson } = selected(root, input)
83
+ learningRevision = context.revision
84
+ pin = { lessonId: lesson.id, lessonDigest: lesson.lessonDigest, artifactDigest: lesson.artifact.digest, activationId: lesson.activationId, decisionId: lesson.decisionId }
85
+ const [start, end] = markers(input.slot)
86
+ block = [start, `Scope: project ${input.scope.project}; activity ${input.scope.activity}; harness ${input.harnessId}.`,
87
+ `Practice: ${lesson.id} (${lesson.lessonDigest}). Apply only in this scope; host instructions and tool permissions retain their precedence.`, '',
88
+ lesson.artifact.content, '', 'Exceptions:', ...lesson.exceptions.map(e => `- ${e}`), end].join('\n')
89
+ } else if (!previous || previous.pin.lessonId !== input.lessonId) throw new Error('retirement requires the exact owned lesson')
90
+ const after = oldBlock ? before.replace(oldBlock, () => block ?? '') : `${before ?? ''}${before && !before.endsWith('\n') ? '\n' : ''}${block}\n`
91
+ const binding = { ...input, pin, block, blockDigest: block === null ? null : digest(block) }
92
+ const bindings = state.bindings.filter(b => !(b.target === input.target && b.slot === input.slot))
93
+ if (block !== null) bindings.push(binding)
94
+ const { digest: ignoredDigest, ...stateBody } = state
95
+ const next = seal({ ...stateBody, revision: state.revision + 1, bindings })
96
+ return seal({ schema: 'atelier-instruction-plan@v1', workspace: digest(root), input, previousState: state.digest, nextState: next,
97
+ learningRevision, pin, before, beforeDigest: before === null ? null : digest(before), after, afterDigest: digest(after),
98
+ effect: 'write-scoped-instruction-guidance', precedence: 'host-owned', authenticated: false })
99
+ }
100
+ export function planInstructionAdoption(options) {
101
+ return prepare(workspaceRoot(options.workspaceRoot, { write: true }), options)
102
+ }
103
+ function assertCurrentLearning(root, plan) {
104
+ if (plan.input.mode === 'retire') return
105
+ const { lesson } = selected(root, plan.input)
106
+ if (lesson.lessonDigest !== plan.pin.lessonDigest || lesson.activationId !== plan.pin.activationId || lesson.decisionId !== plan.pin.decisionId) throw new Error('instruction practice is no longer eligible')
107
+ }
108
+ function finish(root, plan) {
109
+ verified(plan, 'instruction plan'); inputOf(plan.input)
110
+ if (plan.workspace !== digest(root)) throw new Error('instruction plan belongs to another workspace')
111
+ const current = readText(root, plan.input.target), state = stateAt(root)
112
+ if (![plan.previousState, plan.nextState.digest].includes(state.digest)) throw new Error('instruction state changed during recovery')
113
+ if (current !== plan.before && current !== plan.after) throw new Error('instruction destination changed; preserve edits and reconcile')
114
+ // Identical re-adoption changes the adoption record, not the file. Recovery
115
+ // bytes exist only when an exchange was needed; equality alone cannot prove it.
116
+ if (current === plan.after && plan.before !== null && plan.before !== plan.after && readText(root, `${BASE}/recovery/${plan.digest.slice(7)}/displaced.md`) !== plan.before) throw new Error('displaced instruction source requires reconciliation')
117
+ // A completed write can be recorded even after withdrawal; inspection then
118
+ // reports reconsideration and an explicit retirement reconciles the file.
119
+ if (current !== plan.after) {
120
+ assertCurrentLearning(root, plan)
121
+ const target = within(root, plan.input.target)
122
+ const parent = plan.input.target.split('/').slice(0, -1).join('/')
123
+ if (parent) within(root, parent, { directory: true, create: true })
124
+ if (readText(root, plan.input.target) !== plan.before) throw new Error('instruction destination changed before write')
125
+ if (plan.before === null) {
126
+ // Non-overwriting publication: a concurrent create refuses.
127
+ publishPrivateFile(target, plan.after)
128
+ } else {
129
+ const recovery = `${BASE}/recovery/${plan.digest.slice(7)}`
130
+ const directory = within(root, recovery, { directory: true, create: true })
131
+ const candidatePath = within(root, `${recovery}/displaced.md`)
132
+ const probe = probeExchange({ directory })
133
+ if (!probe.supported || fs.statSync(directory).dev !== fs.statSync(path.dirname(target)).dev) throw new Error('instruction atomic exchange unavailable on this volume')
134
+ if (fs.lstatSync(target).nlink !== 1) throw new Error('hard-linked instruction destination refused')
135
+ if (!stat(candidatePath)) writeNew(root, `${recovery}/displaced.md`, plan.after)
136
+ if (readText(root, `${recovery}/displaced.md`) !== plan.after) throw new Error('retained displaced source requires reconciliation')
137
+ fs.chmodSync(candidatePath, fs.statSync(target).mode & 0o777)
138
+ exchangeFiles(candidatePath, target)
139
+ syncPrivateDirectory(directory); syncPrivateDirectory(path.dirname(target))
140
+ const displaced = readText(root, `${recovery}/displaced.md`)
141
+ if (displaced !== plan.before) {
142
+ // Retain the displaced writer before exchanging back. Further writers
143
+ // can change either path; both sides remain in the recovery directory.
144
+ writeNew(root, `${recovery}/concurrent-${digest(displaced).slice(7)}.md`, displaced)
145
+ if (readText(root, plan.input.target) === plan.after) {
146
+ exchangeFiles(candidatePath, target)
147
+ syncPrivateDirectory(directory); syncPrivateDirectory(path.dirname(target))
148
+ }
149
+ throw new Error('concurrent instruction writer; retained bytes require reconciliation')
150
+ }
151
+ }
152
+ }
153
+ if (readText(root, plan.input.target) !== plan.after) throw new Error('instruction readback mismatch')
154
+ if (state.digest !== plan.nextState.digest) replaceJson(root, STATE, plan.nextState)
155
+ const receipt = seal({ schema: 'atelier-instruction-receipt@v1', workspace: digest(root), planDigest: plan.digest,
156
+ target: plan.input.target, slot: plan.input.slot, pin: plan.pin, mode: plan.input.mode, resultDigest: plan.afterDigest,
157
+ stateDigest: plan.nextState.digest, status: 'persisted-and-read-back', hostLoaded: false, behaviorVerified: false })
158
+ const receiptPath = `${BASE}/receipts/${plan.digest.slice(7)}.json`
159
+ if (stat(within(root, receiptPath))) {
160
+ if (learningDigest(jsonAt(root, receiptPath)) !== learningDigest(receipt)) throw new Error('instruction receipt differs')
161
+ } else writeNew(root, receiptPath, JSON.stringify(receipt, null, 2) + '\n')
162
+ fs.unlinkSync(within(root, ACTIVE)); syncPrivateDirectory(path.dirname(within(root, ACTIVE)))
163
+ return receipt
164
+ }
165
+ export function applyInstructionAdoption(options) {
166
+ const root = workspaceRoot(options.workspaceRoot, { write: true })
167
+ return withOperationLock(root, () => {
168
+ const pending = activeAt(root)
169
+ if (pending) {
170
+ if (pending.digest !== options.confirm || learningDigest(inputOf(options)) !== learningDigest(pending.input)) throw new Error('another instruction transaction requires recovery')
171
+ return finish(root, pending)
172
+ }
173
+ if (/^sha256:[a-f0-9]{64}$/.test(options.confirm ?? '')) {
174
+ const receiptPath = `${BASE}/receipts/${options.confirm.slice(7)}.json`
175
+ if (stat(within(root, receiptPath))) {
176
+ const receipt = verified(jsonAt(root, receiptPath), 'instruction receipt')
177
+ const recordedPlan = verified(jsonAt(root, `${BASE}/plans/${options.confirm.slice(7)}.json`), 'instruction plan')
178
+ if (learningDigest(inputOf(options)) !== learningDigest(recordedPlan.input)) throw new Error('instruction retry input differs')
179
+ return { ...receipt, duplicate: true, currentDestinationDigest: readText(root, receipt.target) === null ? null : digest(readText(root, receipt.target)) }
180
+ }
181
+ }
182
+ const plan = prepare(root, options)
183
+ if (plan.digest !== options.confirm) throw new Error('instruction adoption requires the exact current plan digest')
184
+ const planFile = `${BASE}/plans/${plan.digest.slice(7)}.json`
185
+ if (stat(within(root, planFile))) {
186
+ if (learningDigest(jsonAt(root, planFile)) !== learningDigest(plan)) throw new Error('retained plan differs')
187
+ } else writeNew(root, planFile, JSON.stringify(plan, null, 2) + '\n')
188
+ replaceJson(root, ACTIVE, plan)
189
+ return finish(root, plan)
190
+ })
191
+ }
192
+ export function recoverInstructionAdoption({ workspaceRoot: input, confirm }) {
193
+ const root = workspaceRoot(input, { write: true })
194
+ return withOperationLock(root, () => {
195
+ const plan = activeAt(root)
196
+ if (!plan || plan.digest !== confirm) throw new Error('recovery requires the exact pending instruction plan digest')
197
+ return finish(root, plan)
198
+ })
199
+ }
200
+ export function abandonInstructionAdoption({ workspaceRoot: input, confirm, destinationDigest }) {
201
+ const root = workspaceRoot(input, { write: true })
202
+ return withOperationLock(root, () => {
203
+ const plan = activeAt(root)
204
+ if (!plan || plan.digest !== confirm) throw new Error('abandonment requires the exact pending instruction plan digest')
205
+ const current = readText(root, plan.input.target), actual = current === null ? null : digest(current)
206
+ if (actual !== destinationDigest) throw new Error('destination changed since abandonment inspection')
207
+ if (current === plan.after || stateAt(root).digest !== plan.previousState) throw new Error('reconcile the applied instruction before retirement')
208
+ const record = { schema: 'atelier-instruction-abandonment@v1', planDigest: plan.digest, destinationDigest: actual,
209
+ stateDigest: plan.previousState, sourceMutation: false, retainedRecovery: `${BASE}/recovery/${plan.digest.slice(7)}` }
210
+ const file = `${BASE}/abandoned/${plan.digest.slice(7)}.json`
211
+ if (stat(within(root, file))) {
212
+ if (learningDigest(jsonAt(root, file)) !== learningDigest(record)) throw new Error('prior abandonment has a different destination')
213
+ } else writeNew(root, file, JSON.stringify(record, null, 2) + '\n')
214
+ fs.unlinkSync(within(root, ACTIVE)); syncPrivateDirectory(path.dirname(within(root, ACTIVE)))
215
+ return record
216
+ })
217
+ }
218
+ export function inspectInstructionAdoption({ workspaceRoot: input }) {
219
+ const root = workspaceRoot(input), state = stateAt(root)
220
+ const bindings = state.bindings.map(binding => {
221
+ let status = 'current', reason = null
222
+ try {
223
+ if (blockOf(readText(root, binding.target), binding.slot) !== binding.block) throw new Error('destination drift')
224
+ const { lesson } = selected(root, binding)
225
+ if (lesson.lessonDigest !== binding.pin.lessonDigest || lesson.activationId !== binding.pin.activationId) throw new Error('practice revised')
226
+ } catch (error) { status = 'reconsider'; reason = error.message }
227
+ return { ...clone(binding), status, reason, hostLoaded: 'unverified' }
228
+ })
229
+ const pending = activeAt(root)
230
+ const text = pending ? readText(root, pending.input.target) : null
231
+ return { schema: 'atelier-instruction-status@v1', revision: state.revision, pending, pendingDestinationDigest: text === null ? null : digest(text), bindings }
232
+ }
233
+ // Host integration seam. This returns verified selected guidance and records what
234
+ // the consumer actually received. It cannot prove that an agent obeyed the text.
235
+ export function consumeInstructionContext({ workspaceRoot: input, target, slot, session, scope, harnessId }) {
236
+ if (!identifier.test(session)) throw new Error('bounded session identity required')
237
+ const root = workspaceRoot(input, { write: true })
238
+ return withOperationLock(root, () => {
239
+ if (activeAt(root)) throw new Error('instruction adoption is unsettled')
240
+ const binding = inspectInstructionAdoption({ workspaceRoot: root }).bindings.find(b => b.target === target && b.slot === slot)
241
+ if (!binding || binding.status !== 'current' || binding.harnessId !== harnessId || learningDigest(scope) !== learningDigest(binding.scope)) throw new Error('current scoped instruction binding required')
242
+ const { lesson } = selected(root, binding)
243
+ const context = { content: lesson.artifact.content, exceptions: lesson.exceptions, scope: binding.scope, pin: binding.pin }
244
+ const receipt = seal({ schema: 'atelier-instruction-context-receipt@v1', session, target, slot, pin: binding.pin,
245
+ contextDigest: learningDigest(context), delivery: 'returned-to-calling-consumer', behaviorVerified: false })
246
+ const file = `${BASE}/use/${receipt.digest.slice(7)}.json`
247
+ if (!stat(within(root, file))) writeNew(root, file, JSON.stringify(receipt, null, 2) + '\n')
248
+ return { context, receipt }
249
+ })
250
+ }
@@ -0,0 +1,136 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { validateJsonSchema } from '../export/atelier-export-contract.mjs'
4
+ import { parseSkillFrontmatter } from '../skills/steward.mjs'
5
+ import { bytesAt, canonical, digest, fileInventory, jsonAt, jsonText, objectDigest, relativeFile, stat, tree, treeDigest, within, writeNew } from './files.mjs'
6
+
7
+ const schema = JSON.parse(fs.readFileSync(new URL('../../contracts/atelier-capability.v1.schema.json', import.meta.url), 'utf8'))
8
+ export const PACKAGE_FILE = 'capability-package.json'
9
+ export const RELEASE_FILE = 'capability-release.json'
10
+ export const HOST_PROFILES = Object.freeze({
11
+ 'codex-repo-v1': Object.freeze({ target: '.agents/skills', adapterVersion: 1, discovery: 'repository SKILL.md projection', precedence: 'host-owned; overlapping names require inspection', permissions: 'advisory declarations only', reload: 're-observe in the intended host session after every adoption' }),
12
+ 'claude-repo-v1': Object.freeze({ target: '.claude/skills', adapterVersion: 1, discovery: 'project SKILL.md projection', precedence: 'host-owned; personal, enterprise and plugin surfaces may change resolution', permissions: 'advisory declarations only', reload: 're-observe in the intended host session after every adoption' }),
13
+ })
14
+
15
+ export function validateCapabilityDocument(document, shape) {
16
+ return validateJsonSchema({ $schema: schema.$schema, $defs: schema.$defs, $ref: `#/$defs/${shape}` }, document)
17
+ }
18
+ export function assertDocument(document, shape) {
19
+ const errors = validateCapabilityDocument(document, shape)
20
+ if (errors.length) throw new Error(`invalid capability ${shape}: ${errors.join('; ')}`)
21
+ return document
22
+ }
23
+ export function unique(values, label) {
24
+ if (new Set(values).size !== values.length) throw new Error(`duplicate ${label}`)
25
+ }
26
+
27
+ function validatePackage(descriptor, files) {
28
+ assertDocument(descriptor, 'package')
29
+ unique(descriptor.skills.map(skill => skill.id), 'skill identity')
30
+ unique(descriptor.skills.map(skill => skill.path.toLowerCase()), 'skill path')
31
+ unique(descriptor.capabilities.map(capability => capability.id), 'capability identity')
32
+ unique(descriptor.dependencies.map(dependency => dependency.id), 'dependency identity')
33
+ unique(files.map(file => file.path.toLowerCase()), 'case-insensitive payload path')
34
+ const paths = new Set(files.map(file => file.path))
35
+ for (const evaluation of descriptor.evaluations) if (!paths.has(evaluation.path)) throw new Error('evaluation evidence is missing from package')
36
+ for (const migration of descriptor.migrations) if (!paths.has(migration.path)) throw new Error('migration guidance is missing from package')
37
+ for (const skill of descriptor.skills) {
38
+ relativeFile(skill.path)
39
+ if (!skill.path.startsWith('skills/')) throw new Error('skill path must be below skills/')
40
+ if (descriptor.skills.some(other => other !== skill && other.path.startsWith(`${skill.path}/`))) throw new Error('overlapping skill roots')
41
+ if (skill.capabilities.some(id => !descriptor.capabilities.some(item => item.id === id))) throw new Error('skill refers to an unknown capability')
42
+ const skillFiles = files.filter(file => file.path.startsWith(`${skill.path}/`))
43
+ const entry = skillFiles.find(file => file.path === `${skill.path}/SKILL.md`)
44
+ if (!entry) throw new Error('skill entrypoint missing')
45
+ const parsed = parseSkillFrontmatter(new TextDecoder('utf-8', { fatal: true }).decode(entry.bytes))
46
+ if (!parsed.ok || parsed.values.name !== skill.name || !parsed.values.description || ['|', '>'].includes(parsed.values.description)) throw new Error('portable skill requires matching name and a single-line description')
47
+ // References in every Markdown resource are checked, not just SKILL.md.
48
+ // Scripts remain inert payloads; this is not static program verification.
49
+ for (const file of skillFiles.filter(file => file.path.endsWith('.md'))) {
50
+ const text = new TextDecoder('utf-8', { fatal: true }).decode(file.bytes)
51
+ for (const match of text.matchAll(/!?\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g)) {
52
+ const link = match[1].replace(/^<|>$/g, '').split('#')[0]
53
+ if (!link || /^[a-z][a-z0-9+.-]*:/i.test(link)) continue
54
+ const decoded = decodeURIComponent(link)
55
+ if (path.posix.isAbsolute(decoded) || decoded.includes('\\')) throw new Error('skill resource escapes its bundle')
56
+ const normalized = path.posix.normalize(path.posix.join(path.posix.dirname(file.path), decoded))
57
+ if (!normalized.startsWith(`${skill.path}/`) || !paths.has(normalized)) throw new Error('skill resource is missing or escapes its bundle')
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ export function prepareCapabilityRelease({ packageRoot }) {
64
+ const root = fs.realpathSync(path.resolve(packageRoot))
65
+ const files = tree(root).filter(file => file.path !== RELEASE_FILE)
66
+ const entry = files.find(file => file.path === PACKAGE_FILE)
67
+ if (!entry) throw new Error('capability-package.json is missing')
68
+ const descriptor = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(entry.bytes))
69
+ validatePackage(descriptor, files)
70
+ const payload = { schema: 'mnstry.atelier-capability-release@v1', package: descriptor, files: fileInventory(files) }
71
+ const release = { ...payload, digest: objectDigest(payload) }
72
+ assertDocument(release, 'release')
73
+ return release
74
+ }
75
+
76
+ export function sealCapabilityRelease(options) {
77
+ const release = prepareCapabilityRelease(options)
78
+ const root = fs.realpathSync(path.resolve(options.packageRoot))
79
+ // Exclusive creation prevents a release identifier from silently changing.
80
+ writeNew(root, RELEASE_FILE, jsonText(release))
81
+ return release
82
+ }
83
+
84
+ export function verifyCapabilityRelease({ packageRoot, expectedDigest }) {
85
+ const root = fs.realpathSync(path.resolve(packageRoot))
86
+ const release = assertDocument(jsonAt(root, RELEASE_FILE), 'release')
87
+ const actual = prepareCapabilityRelease({ packageRoot: root })
88
+ if (canonical(release) !== canonical(actual)) throw new Error('release payload or manifest integrity mismatch')
89
+ if (expectedDigest && release.digest !== expectedDigest) throw new Error('release does not match the adopted digest')
90
+ return release
91
+ }
92
+
93
+ export function projectSkill({ packageRoot, release, skillId, alias, host }) {
94
+ if (!HOST_PROFILES[host] || !release.package.hosts.includes(host)) throw new Error('package does not support the selected host profile')
95
+ if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(alias) || alias.length > 64) throw new Error('invalid skill alias')
96
+ const skill = release.package.skills.find(item => item.id === skillId)
97
+ if (!skill) throw new Error('binding selects an unknown skill')
98
+ const root = fs.realpathSync(packageRoot)
99
+ const files = tree(within(root, skill.path, { directory: true }))
100
+ const entry = files.find(file => file.path === 'SKILL.md')
101
+ const text = new TextDecoder('utf-8', { fatal: true }).decode(entry.bytes).replaceAll('\r\n', '\n')
102
+ const end = text.indexOf('\n---\n', 4)
103
+ const header = text.slice(0, end)
104
+ if ((header.match(/^name:/gm) ?? []).length !== 1) throw new Error('ambiguous skill name metadata')
105
+ entry.bytes = Buffer.from(header.replace(/^name:.*$/m, `name: ${alias}`) + text.slice(end))
106
+ return { files, digest: treeDigest(files), target: `${HOST_PROFILES[host].target}/${alias}` }
107
+ }
108
+
109
+ export function inventorySkillSurfaces({ surfaces }) {
110
+ if (!Array.isArray(surfaces) || surfaces.length > 32) throw new Error('inventory requires at most 32 explicit surfaces')
111
+ unique(surfaces.map(surface => surface.id), 'surface id')
112
+ const entries = [], findings = []
113
+ for (const surface of surfaces) {
114
+ if (!/^[a-z][a-z0-9.-]{0,63}$/.test(surface.id)) throw new Error('invalid inventory surface id')
115
+ if (!['personal', 'organization', 'repository', 'nested', 'plugin', 'other'].includes(surface.scope)) throw new Error('invalid inventory scope')
116
+ if (!stat(surface.root)) { findings.push({ surface: surface.id, code: 'surface-missing' }); continue }
117
+ if (stat(surface.root).isSymbolicLink() || !stat(surface.root).isDirectory()) { findings.push({ surface: surface.id, code: 'surface-redirected-or-not-directory' }); continue }
118
+ const root = fs.realpathSync(surface.root)
119
+ const names = fs.readdirSync(root).filter(name => !name.startsWith('.')).sort()
120
+ if (names.length > 256) throw new Error('inventory surface ceiling exceeded')
121
+ for (const name of names) {
122
+ try {
123
+ const skillRoot = within(root, name, { directory: true })
124
+ if (!stat(skillRoot)?.isDirectory()) continue
125
+ const files = tree(skillRoot)
126
+ const entry = files.find(file => file.path === 'SKILL.md')
127
+ if (!entry) { findings.push({ surface: surface.id, name, code: 'no-entrypoint' }); continue }
128
+ const parsed = parseSkillFrontmatter(new TextDecoder('utf-8', { fatal: true }).decode(entry.bytes))
129
+ entries.push({ surface: surface.id, scope: surface.scope, name, declaredName: parsed.values.name ?? null, digest: treeDigest(files), ownership: 'unclaimed', hostLoaded: 'unknown' })
130
+ } catch { findings.push({ surface: surface.id, name, code: 'unreadable-or-unsafe-bundle' }) }
131
+ }
132
+ }
133
+ const collisions = entries.filter((entry, index) => entries.some((other, otherIndex) => index !== otherIndex && other.declaredName === entry.declaredName))
134
+ .map(entry => ({ surface: entry.surface, name: entry.name, code: 'name-overlap-requires-host-inspection' }))
135
+ return { schema: 'mnstry.atelier-capability-inventory@v1', entries, findings: [...findings, ...collisions], scope: 'explicit-surfaces-only', mutation: false }
136
+ }
@@ -0,0 +1,36 @@
1
+ // A command's own refusal prints its message. Anything that can carry host
2
+ // detail (Node system errors, child-process failures, parser excerpts, or a
3
+ // message naming an absolute path) is rethrown to the command executor, which
4
+ // prints only a typed code or `[internal-error]` with Node's code.
5
+ // Node's system errors always carry errno and syscall; child-process failures
6
+ // carry status and stderr. Atelier's own refusals carry neither, even when
7
+ // their code is EEXIST.
8
+ const NODE_DETAIL_KEYS = ['errno', 'syscall', 'path', 'dest', 'status', 'signal', 'stderr', 'stdout', 'cmd']
9
+ const HOST_ROOT = /(?:^|[\s'"(=,])(?:\/(?:Users|home|private|var|tmp|Volumes|opt|etc|root|mnt|srv|usr|Library|System|Applications)\/|[A-Za-z]:\\|\\\\|~\/)/
10
+ // A quoted absolute path, as Node and git print them, whatever its root.
11
+ const QUOTED_ABSOLUTE = /['"`](?:\/[^'"`\s/]+\/|[A-Za-z]:[\\/]|\\\\)/
12
+
13
+ export function safeCommandMessage(error) {
14
+ if (!(error instanceof Error) || error instanceof SyntaxError) return null
15
+ if (typeof error.code === 'string' && error.code.startsWith('ERR_')) return null
16
+ if (NODE_DETAIL_KEYS.some(key => Object.hasOwn(error, key))) return null
17
+ const message = error.message
18
+ // Atelier's own messages never interpolate host paths; Node and git quote
19
+ // theirs. Unquoted text such as a schema instance path `/data/0` stays.
20
+ if (typeof message !== 'string' || HOST_ROOT.test(message) || QUOTED_ABSOLUTE.test(message)) return null
21
+ return message
22
+ }
23
+
24
+ export function reportCommandFailure(error, stream = 'stderr') {
25
+ const message = safeCommandMessage(error)
26
+ if (message === null) throw error
27
+ const text = JSON.stringify({ ok: false, error: message })
28
+ if (stream === 'stdout') console.log(text)
29
+ else console.error(text)
30
+ process.exitCode = 1
31
+ }
32
+
33
+ export function parseJsonRequest(bytes, label) {
34
+ try { return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes)) }
35
+ catch { throw new Error(`${label} must be UTF-8 JSON`) }
36
+ }
@@ -5,18 +5,32 @@ import { pathToFileURL } from 'node:url'
5
5
 
6
6
  const [, , requestedScript, ...args] = process.argv
7
7
 
8
+ // Only Atelier's own typed codes (lowercase words joined by hyphens) are
9
+ // printed verbatim. Node's system and internal errors (ENOENT, EACCES, ERR_*)
10
+ // carry a string code too, but their messages name absolute paths and no next
11
+ // step, so they are redacted to the code and system call alone.
12
+ const TYPED_CODE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/
13
+ const NODE_CODE = /^(?:E[A-Z0-9]+|ERR_[A-Z0-9_]+)$/
14
+ const SYSCALL = /^[a-z_]+$/
15
+
16
+ function untypedDetail(error) {
17
+ if (typeof error?.code !== 'string' || !NODE_CODE.test(error.code)) return ''
18
+ const syscall = typeof error.syscall === 'string' && SYSCALL.test(error.syscall) ? ` from ${error.syscall}` : ''
19
+ return ` (${error.code}${syscall})`
20
+ }
21
+
8
22
  function renderError(error) {
9
23
  const debug = process.env.ATELIER_DEBUG === '1'
10
24
  if (debug) {
11
25
  console.error(error?.stack || String(error))
12
26
  return Number.isInteger(error?.exitCode) ? error.exitCode : 1
13
27
  }
14
- if (typeof error?.code === 'string' && error.code) {
28
+ if (typeof error?.code === 'string' && TYPED_CODE.test(error.code)) {
15
29
  console.error(`[${error.code}] ${error.message}`)
16
30
  if (error.hint) console.error(`Next: ${error.hint}`)
17
31
  return Number.isInteger(error.exitCode) ? error.exitCode : 2
18
32
  }
19
- console.error('[internal-error] command failed without a safe diagnostic')
33
+ console.error(`[internal-error] command failed without a safe diagnostic${untypedDetail(error)}`)
20
34
  console.error('Next: rerun with ATELIER_DEBUG=1 to inspect the stack locally.')
21
35
  return 1
22
36
  }