@mnstry/atelier 0.2.0-alpha.12 → 0.2.0-alpha.14

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 (531) hide show
  1. package/CHANGELOG.md +894 -0
  2. package/README.md +159 -6
  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-personal-workspace-manifest.v1.schema.json +87 -0
  21. package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
  22. package/contracts/atelier-template-adoption.v1.schema.json +223 -0
  23. package/contracts/atelier-template-profile.v1.schema.json +1168 -0
  24. package/contracts/atelier-trackable.v1.schema.json +781 -0
  25. package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
  26. package/docs/architecture.md +89 -0
  27. package/docs/assurance-controls.md +2 -2
  28. package/docs/blocks/capability-stewardship.md +17 -0
  29. package/docs/blocks/claims.md +16 -3
  30. package/docs/blocks/inquiry-harnesses.md +19 -0
  31. package/docs/blocks/learning-harnesses.md +17 -0
  32. package/docs/capability-stewardship.md +267 -0
  33. package/docs/coordination.md +51 -0
  34. package/docs/decision-practices.md +111 -0
  35. package/docs/decisions.md +29 -0
  36. package/docs/evidence-navigation.md +237 -0
  37. package/docs/exact-upgrades.md +3 -2
  38. package/docs/ingestion.md +181 -0
  39. package/docs/inquiry-harnesses.md +210 -0
  40. package/docs/install.md +80 -3
  41. package/docs/intake-and-guides.md +5 -0
  42. package/docs/integration-contract-decisions.md +24 -2
  43. package/docs/interaction-and-reflection.md +72 -0
  44. package/docs/knowledge-graph.md +20 -0
  45. package/docs/knowledge-health/api.md +30 -0
  46. package/docs/knowledge-health/assessment/api.md +96 -0
  47. package/docs/knowledge-health/assessment/capabilities.md +40 -0
  48. package/docs/knowledge-health/assessment/evaluation.md +27 -0
  49. package/docs/knowledge-health/assessment/facilitator.md +30 -0
  50. package/docs/knowledge-health/assessment/quickstart.md +61 -0
  51. package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
  52. package/docs/knowledge-health/devday/facilitator.md +16 -0
  53. package/docs/knowledge-health/devday/participant-response.md +22 -0
  54. package/docs/knowledge-health/devday/public-default.md +107 -0
  55. package/docs/knowledge-health/devday/quickstart.md +18 -0
  56. package/docs/knowledge-health/devday/support.md +22 -0
  57. package/docs/knowledge-health/exercise.md +17 -0
  58. package/docs/knowledge-health/facilitator.md +20 -0
  59. package/docs/knowledge-health/quickstart.md +94 -0
  60. package/docs/knowledge-health/readme.md +15 -0
  61. package/docs/knowledge-health/support.md +34 -0
  62. package/docs/knowledge-setup.md +248 -0
  63. package/docs/knowledge-workspace.md +264 -0
  64. package/docs/learning-harnesses.md +237 -0
  65. package/docs/learning.md +215 -0
  66. package/docs/local-services.md +223 -19
  67. package/docs/obsidian-contract.md +246 -15
  68. package/docs/obsidian-plugin.md +252 -7
  69. package/docs/obsidian.md +874 -52
  70. package/docs/optional-ingestion.md +83 -0
  71. package/docs/personal-workspace.md +266 -0
  72. package/docs/practical-judgment.md +92 -0
  73. package/docs/progressive-ingestion-acceptance.md +89 -0
  74. package/docs/progressive-ingestion.md +235 -0
  75. package/docs/project-command-inventory.md +1 -0
  76. package/docs/release-engineering.md +50 -0
  77. package/docs/repo-boundary-guard.md +120 -3
  78. package/docs/research-starter-quickstart.md +66 -0
  79. package/docs/research-starter.md +114 -0
  80. package/docs/semantic-ingestion.md +198 -0
  81. package/docs/semantic-operation.md +318 -0
  82. package/docs/skill-steward.md +6 -0
  83. package/docs/template-profile.md +164 -0
  84. package/docs/template-upgrades.md +114 -0
  85. package/docs/trackables.md +111 -0
  86. package/docs/upgrade.md +219 -2
  87. package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
  88. package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
  89. package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
  90. package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
  91. package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
  92. package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
  93. package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
  94. package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
  95. package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
  96. package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
  97. package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
  98. package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
  99. package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
  100. package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
  101. package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
  102. package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
  103. package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
  104. package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
  105. package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
  106. package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
  107. package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
  108. package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
  109. package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
  110. package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
  111. package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
  112. package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
  113. package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
  114. package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
  115. package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
  116. package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
  117. package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
  118. package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
  119. package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
  120. package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
  121. package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
  122. package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
  123. package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
  124. package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
  125. package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
  126. package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
  127. package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
  128. package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
  129. package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
  130. package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
  131. package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
  132. package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
  133. package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
  134. package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
  135. package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
  136. package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
  137. package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
  138. package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
  139. package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
  140. package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
  141. package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
  142. package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
  143. package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
  144. package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
  145. package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
  146. package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
  147. package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
  148. package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
  149. package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
  150. package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
  151. package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
  152. package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
  153. package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
  154. package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
  155. package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
  156. package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
  157. package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
  158. package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
  159. package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
  160. package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
  161. package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
  162. package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
  163. package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
  164. package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
  165. package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
  166. package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
  167. package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
  168. package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
  169. package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
  170. package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
  171. package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
  172. package/fixtures/atelier-interaction/act/valid/document.json +24 -0
  173. package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
  174. package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
  175. package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
  176. package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
  177. package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
  178. package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
  179. package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
  180. package/fixtures/atelier-judgment/case/valid/document.json +75 -0
  181. package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
  182. package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
  183. package/fixtures/atelier-judgment/native-decision.md +3 -0
  184. package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
  185. package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
  186. package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
  187. package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
  188. package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
  189. package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
  190. package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
  191. package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
  192. package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
  193. package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
  194. package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
  195. package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
  196. package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
  197. package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
  198. package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
  199. package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
  200. package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
  201. package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
  202. package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
  203. package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
  204. package/fixtures/atelier-learning/invalid/authority.json +14 -0
  205. package/fixtures/atelier-learning/valid/capture.json +13 -0
  206. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
  207. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
  208. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
  209. package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
  210. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
  211. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
  212. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
  213. package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
  214. package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
  215. package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
  216. package/fixtures/atelier-trackable/request/valid/document.json +22 -0
  217. package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
  218. package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
  219. package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
  220. package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
  221. package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
  222. package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
  223. package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
  224. package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
  225. package/fixtures/capability-packages/adoption.example.json +70 -0
  226. package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
  227. package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
  228. package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
  229. package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
  230. package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
  231. package/fixtures/capability-packages/research/capability-package.json +88 -0
  232. package/fixtures/capability-packages/research/capability-release.json +134 -0
  233. package/fixtures/capability-packages/research/evaluation.md +15 -0
  234. package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
  235. package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
  236. package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
  237. package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
  238. package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
  239. package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
  240. package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
  241. package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
  242. package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
  243. package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
  244. package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
  245. package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
  246. package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
  247. package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
  248. package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
  249. package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
  250. package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
  251. package/fixtures/evidence-navigation/valid/claim.json +36 -0
  252. package/fixtures/evidence-navigation/valid/host.json +16 -0
  253. package/fixtures/evidence-navigation/valid/profile.json +16 -0
  254. package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
  255. package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
  256. package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
  257. package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
  258. package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
  259. package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
  260. package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
  261. package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
  262. package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
  263. package/fixtures/harnesses/learning-cycle.json +780 -0
  264. package/fixtures/inquiry/workshop.json +675 -0
  265. package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
  266. package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
  267. package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
  268. package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
  269. package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
  270. package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
  271. package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
  272. package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
  273. package/fixtures/knowledge-health/calibration-suite.json +26 -0
  274. package/fixtures/knowledge-health/calibration-trial.json +37 -0
  275. package/fixtures/knowledge-health/corrected/checklist.md +18 -0
  276. package/fixtures/knowledge-health/devday/participant-record.json +19 -0
  277. package/fixtures/knowledge-health/expected-summary.json +27 -0
  278. package/fixtures/knowledge-health/workshop/README.md +20 -0
  279. package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
  280. package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
  281. package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
  282. package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
  283. package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
  284. package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
  285. package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
  286. package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
  287. package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
  288. package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
  289. package/fixtures/personal-workspace/overlay.json +24 -0
  290. package/fixtures/templates/local-library.v1.json +76 -0
  291. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
  292. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
  293. package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
  294. package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
  295. package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
  296. package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
  297. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
  298. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
  299. package/package.json +60 -5
  300. package/plugins/obsidian/main.js +57 -8
  301. package/plugins/obsidian/manifest.json +1 -1
  302. package/skills/claude/atelier-build-harness/SKILL.md +59 -0
  303. package/skills/claude/atelier-coordination/SKILL.md +32 -0
  304. package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
  305. package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
  306. package/skills/claude/atelier-interaction/SKILL.md +34 -0
  307. package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
  308. package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
  309. package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
  310. package/skills/claude/atelier-research-harness/SKILL.md +43 -0
  311. package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
  312. package/skills/claude/atelier-trackables/SKILL.md +40 -0
  313. package/skills/codex/atelier-build-harness/SKILL.md +59 -0
  314. package/skills/codex/atelier-coordination/SKILL.md +32 -0
  315. package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
  316. package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
  317. package/skills/codex/atelier-interaction/SKILL.md +34 -0
  318. package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
  319. package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
  320. package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
  321. package/skills/codex/atelier-research-harness/SKILL.md +43 -0
  322. package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
  323. package/skills/codex/atelier-trackables/SKILL.md +40 -0
  324. package/src/architecture/catalog.mjs +355 -0
  325. package/src/architecture/index.mjs +93 -0
  326. package/src/boundary/content-rules.mjs +536 -69
  327. package/src/boundary/delegation-command.mjs +158 -0
  328. package/src/boundary/delegation.mjs +396 -0
  329. package/src/boundary/policy.mjs +185 -64
  330. package/src/build/git.mjs +18 -0
  331. package/src/build/index.mjs +3 -0
  332. package/src/build/ledger.mjs +88 -0
  333. package/src/capabilities/adoption.mjs +381 -0
  334. package/src/capabilities/evidence.mjs +140 -0
  335. package/src/capabilities/files.mjs +124 -0
  336. package/src/capabilities/index.mjs +4 -0
  337. package/src/capabilities/instructions.mjs +250 -0
  338. package/src/capabilities/package.mjs +136 -0
  339. package/src/cli/command-failure.mjs +36 -0
  340. package/src/cli/execute-command.mjs +16 -2
  341. package/src/cli/run.mjs +66 -9
  342. package/src/coauthor/store.mjs +43 -2
  343. package/src/collaboration/proposals.mjs +189 -0
  344. package/src/commands/architecture.mjs +27 -0
  345. package/src/commands/boundary.mjs +3 -0
  346. package/src/commands/capability.mjs +73 -0
  347. package/src/commands/enroll.mjs +3 -0
  348. package/src/commands/harness.mjs +79 -0
  349. package/src/commands/ingest.mjs +39 -0
  350. package/src/commands/init.mjs +4 -1
  351. package/src/commands/inquiry.mjs +66 -0
  352. package/src/commands/knowledge-semantic.mjs +128 -0
  353. package/src/commands/knowledge.mjs +119 -0
  354. package/src/commands/learn.mjs +61 -0
  355. package/src/commands/obsidian.mjs +772 -65
  356. package/src/commands/practice.mjs +24 -0
  357. package/src/commands/setup.mjs +22 -2
  358. package/src/commands/trackable.mjs +19 -0
  359. package/src/contracts/corpus.mjs +30 -0
  360. package/src/coordination/index.mjs +110 -0
  361. package/src/evidence-navigation/contracts.mjs +60 -0
  362. package/src/evidence-navigation/evaluate.mjs +69 -0
  363. package/src/evidence-navigation/generation.mjs +22 -0
  364. package/src/evidence-navigation/index.mjs +2 -0
  365. package/src/evidence-navigation/local.mjs +167 -0
  366. package/src/evidence-navigation/schema.generated.mjs +604 -0
  367. package/src/evidence-navigation/types.mjs +12 -0
  368. package/src/export/atelier-export-contract.mjs +35 -8
  369. package/src/graph/enroll.mjs +248 -0
  370. package/src/graph/graph.mjs +30 -6
  371. package/src/graph/knowledge-graph.mjs +77 -9
  372. package/src/harnesses/contracts.mjs +41 -0
  373. package/src/harnesses/exchange.mjs +154 -0
  374. package/src/harnesses/feedback.mjs +16 -0
  375. package/src/harnesses/history.mjs +37 -0
  376. package/src/harnesses/index.mjs +4 -0
  377. package/src/harnesses/store.mjs +29 -0
  378. package/src/ingestion/contracts.mjs +41 -0
  379. package/src/ingestion/evaluation.mjs +119 -0
  380. package/src/ingestion/optional/composition/index.mjs +171 -0
  381. package/src/ingestion/optional/evaluation/index.mjs +219 -0
  382. package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
  383. package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
  384. package/src/ingestion/optional/graphify/graphify_supplier.py +938 -0
  385. package/src/ingestion/optional/graphify/json_bridge.py +163 -0
  386. package/src/ingestion/optional/graphify/native-closure-pins.json +275 -0
  387. package/src/ingestion/optional/graphify/native-pins.json +306 -0
  388. package/src/ingestion/optional/graphify/query_receiving.py +209 -0
  389. package/src/ingestion/optional/index.mjs +9 -0
  390. package/src/ingestion/optional/jev/bridge.mjs +166 -0
  391. package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
  392. package/src/ingestion/optional/jev/judgments.mjs +174 -0
  393. package/src/ingestion/optional/jev/native-translation.mjs +32 -0
  394. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
  395. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
  396. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
  397. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
  398. package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
  399. package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
  400. package/src/ingestion/optional/vanilla/index.mjs +218 -0
  401. package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
  402. package/src/ingestion/processors.mjs +218 -0
  403. package/src/ingestion/semantic.mjs +203 -0
  404. package/src/ingestion/store.mjs +372 -0
  405. package/src/inquiry/beliefs.mjs +32 -0
  406. package/src/inquiry/contracts.mjs +23 -0
  407. package/src/inquiry/index.mjs +5 -0
  408. package/src/inquiry/ledger.mjs +135 -0
  409. package/src/inquiry/lenses.mjs +15 -0
  410. package/src/inquiry/projection.mjs +77 -0
  411. package/src/inquiry/store.mjs +35 -0
  412. package/src/intake/read-scope.mjs +12 -0
  413. package/src/intake/store.mjs +69 -6
  414. package/src/interaction/index.mjs +91 -0
  415. package/src/judgment/index.mjs +82 -0
  416. package/src/judgment/practice-evaluation.mjs +70 -0
  417. package/src/judgment/practice.mjs +144 -0
  418. package/src/knowledge/context.mjs +217 -0
  419. package/src/knowledge/index.mjs +5 -0
  420. package/src/knowledge/ingestion.mjs +79 -0
  421. package/src/knowledge/intake.mjs +33 -0
  422. package/src/knowledge/ledger.mjs +110 -0
  423. package/src/knowledge/participatory/guidance.mjs +36 -0
  424. package/src/knowledge/participatory/guided-contribution.mjs +44 -0
  425. package/src/knowledge/participatory/index.mjs +2 -0
  426. package/src/knowledge/participatory/installed-workshop.mjs +108 -0
  427. package/src/knowledge/plan.mjs +67 -0
  428. package/src/knowledge/projection.mjs +43 -0
  429. package/src/knowledge/semantic-operation.mjs +567 -0
  430. package/src/knowledge/sessions.mjs +467 -0
  431. package/src/knowledge/workspace.mjs +295 -0
  432. package/src/knowledge-health/assessment.mjs +165 -0
  433. package/src/knowledge-health/capabilities.mjs +350 -0
  434. package/src/knowledge-health/context.mjs +62 -0
  435. package/src/knowledge-health/example.mjs +47 -0
  436. package/src/knowledge-health/index.mjs +10 -0
  437. package/src/knowledge-health/owner-join.mjs +88 -0
  438. package/src/knowledge-health/proof/common.mjs +24 -0
  439. package/src/knowledge-health/proof/installed-check.mjs +100 -0
  440. package/src/knowledge-health/proof/native-readback.mjs +37 -0
  441. package/src/knowledge-health/proof/rehearse.mjs +68 -0
  442. package/src/knowledge-health/proof/validate-participant.mjs +17 -0
  443. package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
  444. package/src/knowledge-health/proof/workshop-check.mjs +36 -0
  445. package/src/knowledge-health/rule-identity.mjs +49 -0
  446. package/src/knowledge-health/workshop-composition.mjs +19 -0
  447. package/src/knowledge-health/workshop-profile.mjs +176 -0
  448. package/src/knowledge-health/workshop.mjs +99 -0
  449. package/src/learning/adapters.mjs +68 -0
  450. package/src/learning/assistance.mjs +127 -0
  451. package/src/learning/contracts.mjs +65 -0
  452. package/src/learning/store.mjs +272 -0
  453. package/src/personal-workspace/index.mjs +391 -0
  454. package/src/personal-workspace/selection.mjs +401 -0
  455. package/src/project/config.mjs +8 -0
  456. package/src/project/durable-state.mjs +3 -1
  457. package/src/project/file-class.mjs +1 -1
  458. package/src/project/path-match.mjs +2 -1
  459. package/src/projection/obsidian/edits/apply.mjs +55 -16
  460. package/src/projection/obsidian/materialize/prepare-view.mjs +87 -5
  461. package/src/projection/obsidian/personal-workspace.mjs +519 -0
  462. package/src/projection/obsidian/proposals/adapter.mjs +79 -17
  463. package/src/projection/obsidian/proposals/observation.mjs +50 -4
  464. package/src/projection/obsidian/publication/publisher.mjs +214 -18
  465. package/src/projection/obsidian/publication/vault-list.mjs +4 -3
  466. package/src/projection/obsidian/recovery/index.mjs +4 -1
  467. package/src/projection/obsidian/recovery/store.mjs +160 -7
  468. package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
  469. package/src/projection/project.mjs +3 -2
  470. package/src/readiness/readiness.mjs +10 -3
  471. package/src/reflection/index.mjs +56 -0
  472. package/src/runtime/obsidian/app-production-seams.mjs +48 -16
  473. package/src/runtime/obsidian/app-registration.mjs +175 -28
  474. package/src/runtime/obsidian/app-restart.mjs +154 -0
  475. package/src/runtime/obsidian/engine.mjs +437 -56
  476. package/src/runtime/obsidian/extension-points.mjs +1 -1
  477. package/src/runtime/obsidian/index.mjs +10 -7
  478. package/src/runtime/obsidian/launch-plan.mjs +7 -4
  479. package/src/runtime/obsidian/lifecycle.mjs +108 -23
  480. package/src/runtime/obsidian/login-item.mjs +335 -0
  481. package/src/runtime/obsidian/machine-settings.mjs +119 -12
  482. package/src/runtime/obsidian/observation.mjs +36 -4
  483. package/src/runtime/obsidian/opening.mjs +173 -38
  484. package/src/runtime/obsidian/personal-selection.mjs +125 -0
  485. package/src/runtime/obsidian/pipeline.mjs +115 -6
  486. package/src/runtime/obsidian/plugin-channel.mjs +11 -3
  487. package/src/runtime/obsidian/plugin-choice.mjs +16 -4
  488. package/src/runtime/obsidian/plugin-drift.mjs +59 -11
  489. package/src/runtime/obsidian/plugin-presence.mjs +14 -1
  490. package/src/runtime/obsidian/private-lock.mjs +39 -4
  491. package/src/runtime/obsidian/process-identity.mjs +20 -10
  492. package/src/runtime/obsidian/project-views.mjs +282 -0
  493. package/src/runtime/obsidian/questions.mjs +48 -0
  494. package/src/runtime/obsidian/release-watch.mjs +56 -0
  495. package/src/runtime/obsidian/service-main.mjs +148 -19
  496. package/src/runtime/obsidian/service-manager-production.mjs +61 -0
  497. package/src/runtime/obsidian/service-managers.mjs +275 -0
  498. package/src/runtime/obsidian/service-record.mjs +87 -23
  499. package/src/runtime/obsidian/service.mjs +102 -9
  500. package/src/runtime/obsidian/startup-adapters.mjs +59 -21
  501. package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
  502. package/src/runtime/obsidian/vault-location.mjs +300 -0
  503. package/src/runtime/obsidian/view-counts.mjs +34 -0
  504. package/src/runtime/obsidian/view-permission.mjs +334 -0
  505. package/src/server/local-sidecar.mjs +169 -2
  506. package/src/server/server.mjs +28 -2
  507. package/src/template-bindings/project.mjs +92 -0
  508. package/src/templates/profile.mjs +225 -0
  509. package/src/trackables/domain.mjs +175 -0
  510. package/src/trackables/store.mjs +114 -0
  511. package/src/ui/control-system.mjs +201 -0
  512. package/src/ui/html-primitives.mjs +3 -0
  513. package/src/ui/knowledge-page.mjs +1388 -0
  514. package/src/upgrade/closure-diagnostics.mjs +135 -0
  515. package/src/upgrade/closure.mjs +649 -0
  516. package/src/upgrade/explanation.mjs +1 -1
  517. package/src/upgrade/template-participant.mjs +171 -0
  518. package/src/upgrade/transaction.mjs +72 -25
  519. package/src/upgrade/upgrade.mjs +20 -7
  520. package/templates/external-project-workspace/pack-lifecycle.json +7 -1
  521. package/templates/knowledge-workspace/.gitattributes +2 -0
  522. package/templates/knowledge-workspace/AGENTS.md +27 -0
  523. package/templates/knowledge-workspace/README.md +44 -0
  524. package/templates/knowledge-workspace/atelier.project.json +22 -0
  525. package/templates/knowledge-workspace/gitignore +6 -0
  526. package/templates/knowledge-workspace/knowledge-plan.json +111 -0
  527. package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
  528. package/templates/knowledge-workspace/records/forecast.md +22 -0
  529. package/templates/knowledge-workspace/records/inspection.md +25 -0
  530. package/templates/knowledge-workspace/records/telescope.md +24 -0
  531. package/templates/knowledge-workspace/repo-access.v1.json +9 -0
@@ -0,0 +1,334 @@
1
+ import { createHash } from 'node:crypto'
2
+ import fs from 'node:fs'
3
+ import { readObsidianEnablement } from './enablement.mjs'
4
+ import { DEFAULT_FULL_RECONCILIATION_INTERVAL_MS, configFilesOf } from './engine.mjs'
5
+ import { readFileFacts } from './observation.mjs'
6
+
7
+ // Whether the project allows a view of this workspace right now.
8
+ //
9
+ // What the service stored about a view (its freshness, its pending edits) says
10
+ // what was true when it was written. A view can be turned off, or taken out of
11
+ // the project, or the project can point at another workspace, well before the
12
+ // next tick writes that down; and what a tick then writes still carries the
13
+ // view's generations and counts. So before a stored report is told to whoever
14
+ // holds the view open, `permits(scopeId)` asks the project as it is at that
15
+ // moment:
16
+ //
17
+ // the view the Obsidian settings are enabled and declare it
18
+ // the workspace the project's pointer names this workspace, and its
19
+ // private state is where this service keeps it
20
+ //
21
+ // Whatever cannot be established is "no": a project that does not load,
22
+ // settings that refuse, a pointer that cannot be read, a load that has not
23
+ // answered yet. Nothing is written. This is not about who may see a note; the
24
+ // audiences of machine-settings.mjs decide that when a view is prepared.
25
+ //
26
+ // Cost. Loading a project runs Git in child processes, and a plugin asks every
27
+ // two seconds for every vault it holds open. A project is therefore kept, and
28
+ // loaded again only when a file that decides what it is changed: the files the
29
+ // engine observes as configuration (configFilesOf), compared at every request
30
+ // by a digest of what they hold, never by their times. The pointer and the
31
+ // place of the workspace's private state are read at every request.
32
+ //
33
+ // what a file holds where it really is and the bytes there: its path
34
+ // resolved as the system resolves it when the loader
35
+ // opens it (realpath(3), through a link anywhere on the
36
+ // way), so a link pointed elsewhere, its own or a
37
+ // folder's above it, is a change. Nothing there is a
38
+ // state like any other. A folder in a file's place, a
39
+ // link that leads nowhere or to anything but a regular
40
+ // file, and a file that does not read vouch for nothing.
41
+ // Where the system resolves no path for a regular file
42
+ // that is there and is not a link (some volumes on
43
+ // Windows; a C library that needs /proc and has none),
44
+ // the file holds its own path as given and its bytes: a
45
+ // link above it pointed elsewhere is not seen there
46
+ // a project is kept when two loads in a row, each started after the
47
+ // files were read and found to hold the same, returned
48
+ // the same project as far as this permission reads one:
49
+ // the views it enables, and what the service's
50
+ // workspace resolution reads of it, handed in as
51
+ // `workspaceInputsOf` (where its pointer
52
+ // is, the data root its local overlay prefers, the
53
+ // roots it protects). One load is not enough: a file
54
+ // changed and changed back while it ran leaves the same
55
+ // bytes around a project read from other ones, and
56
+ // reading the files once more when it answers shows
57
+ // nothing. The second load reads the project again.
58
+ // The first load of all only tells which files decide
59
+ // the project, so three loads keep the first project
60
+ // and two keep one after a change
61
+ // and used only while the files hold what they held before
62
+ // those loads, read again at every request
63
+ // and not for long a kept project is loaded again once it is half as old
64
+ // as the engine's full reconciliations are apart, and
65
+ // is never used past that interval: whatever no file
66
+ // here names is seen by then
67
+ //
68
+ // While a file vouches for nothing no project is kept: a loader that answers
69
+ // at once is asked at every request, and its answer decides that request, as
70
+ // it does for the two requests that come before a project is kept.
71
+ //
72
+ // A loader may answer a promise (one that composes a personal workspace off
73
+ // the event loop). The answer to a request never waits for it: a plugin gives
74
+ // a request a second and a half, and takes an answer that does not come for a
75
+ // service that is gone. The load is started, one at a time, and a request is
76
+ // only ever decided from a kept project: until two loads agreed the view is
77
+ // not permitted, and while a kept project is loaded again it goes on deciding,
78
+ // within its time. A load that has not answered by its deadline is given up,
79
+ // whether a request or its own late answer is the first to see the deadline
80
+ // passed, and what it answers then is ignored.
81
+ //
82
+ // A load that fails (it throws, it is refused, it is given up, or it answers a
83
+ // promise while a file vouches for nothing) ends whatever was kept, and is not
84
+ // asked for again at once: after two seconds, then four, up to thirty, for as
85
+ // long as the files hold the same. A change to them is tried at once, once a
86
+ // load has named them: until a load has answered, a corrected configuration
87
+ // waits for the next try. And while one of them vouches for nothing there is
88
+ // no digest to change: after a load that failed then (one that answers a
89
+ // promise, or one that answers at once and threw), a change to another file
90
+ // waits for the next try as well.
91
+ //
92
+ // `onLoad({ durationMs, outcome, promised, kept, sameFiles, vouched, at })` is
93
+ // told how long each load took and how it ended (`answered`, `failed` or
94
+ // `given-up`), whether a project is kept after it, whether it named the files
95
+ // that decide the project as they were when it started (not the first load,
96
+ // which started over none, nor one that named other files), whether those
97
+ // files vouched then, and when it ended (`now()`): times, codes and flags
98
+ // only. createLoadReport, below, counts them and decides which are worth a
99
+ // log line.
100
+
101
+ // A kept project is never used for longer than the engine goes without a full reconciliation.
102
+ export const DEFAULT_KEEP_FOR_MS = DEFAULT_FULL_RECONCILIATION_INTERVAL_MS
103
+ // Longer than the two minutes a composition in a worker is given, so one that runs out of its own time answers first.
104
+ export const DEFAULT_LOAD_DEADLINE_MS = 150 * 1000
105
+ export const DEFAULT_RETRY_AFTER_MS = 2 * 1000
106
+ export const DEFAULT_RETRY_AT_MOST_MS = 30 * 1000
107
+
108
+ // What one file holds, as [path, its real path, digest of the bytes there], or [path, null, null] when it is absent;
109
+ // undefined when it vouches for nothing. The real path is the system's own (realpath(3)): JavaScript's realpathSync
110
+ // takes a `..` after a link away before following the link, and names a file the loader never opens.
111
+ function heldBy(file, realpath) {
112
+ let real
113
+ try { real = realpath(file) } catch {
114
+ // Unresolved. Nothing there is absent. A regular file that is there, and is not a link, is read where it is given
115
+ // (readFileFacts follows no link at its end). A link, resolved or not, never is: it leads nowhere, or nobody can say where.
116
+ let stat
117
+ try { stat = fs.lstatSync(file, { throwIfNoEntry: false }) } catch (inner) { return inner.code === 'ENOTDIR' ? [file, null, null] : undefined }
118
+ if (stat === undefined) return [file, null, null]
119
+ if (!stat.isFile()) return undefined
120
+ real = file
121
+ }
122
+ let facts
123
+ try { facts = readFileFacts(real) } catch { return undefined }
124
+ return facts === null ? undefined : [file, real, facts.digest]
125
+ }
126
+
127
+ // One digest over what every file holds, in order. Null when one of them vouches for nothing.
128
+ function digestOver(files, realpath) {
129
+ const hash = createHash('sha256')
130
+ for (const file of files) {
131
+ const held = heldBy(file, realpath)
132
+ if (held === undefined) return null
133
+ hash.update(`${JSON.stringify(held)}\n`)
134
+ }
135
+ return hash.digest('hex')
136
+ }
137
+
138
+ // The views the project enables. Settings that refuse enable none.
139
+ function viewsOf(project) {
140
+ try {
141
+ const enablement = readObsidianEnablement(project)
142
+ return new Set(enablement.state === 'enabled' ? enablement.scopes.map((scope) => scope.scopeId) : [])
143
+ } catch { return new Set() }
144
+ }
145
+
146
+ export const STATUS_LOAD_EVENT = 'status-project-loaded'
147
+ // A second: most of the second and a half a plugin gives a request, spent on the listener's own event loop.
148
+ export const DEFAULT_SLOW_LOAD_MS = 1000
149
+ // How long a row of failed or slow loads goes on through loads that answered in time between them.
150
+ export const DEFAULT_ROW_ENDS_AFTER_MS = 60 * 1000
151
+
152
+ // Counts every load a permission is told about (onLoad), and writes a line for the ones somebody has to see:
153
+ //
154
+ // failed, given up the first in a row, then when the row is 2, 4, 8, ... long, so a loader that keeps failing
155
+ // writes a line less and less often, never one per try
156
+ // answered the one that ends a row of failures; one that answered at once and held the event loop for
157
+ // `slowMs` or more, on the same doubling row while slow loads follow each other; and one after
158
+ // which nothing is kept although it named the files it started over, from the second in a row
159
+ // on the same doubling row: a load at every request
160
+ //
161
+ // A row of failures ends only at a load that answers `rowEndsAfterMs` or more after its last failure, and a row of
162
+ // slow loads at a load that answers in time that long after its last slow one. While loads come closer together than
163
+ // that (a load at every request, or the retries after a failure), a loader that fails, or is slow, every other load or
164
+ // every third keeps one row, written less and less often, not a line per load. A kept project is loaded again only
165
+ // every half of its keep time, so there a row of failures ends at the next reload that answers: a loader that answers
166
+ // at least one reload between two failures writes each failure and the load that ends its row, as before, and one that
167
+ // fails at every reload and answers only at its retry keeps one row. A row of loads after which nothing is kept ends
168
+ // at the next that keeps a project. A load that named other files than it started over (the first, or one after a
169
+ // change that names other files) neither counts nor ends that row, so a start and a change, each with one load that
170
+ // has nothing to agree with yet, write none of these, unless the change lands while a load reads the files. Any other
171
+ // load that answered in time and kept the project writes nothing unless it ends a row of failures: that is every load
172
+ // of a healthy service. A line holds the event, milliseconds, the outcome, flags and counts; never a path, a view or a
173
+ // message.
174
+ export function createLoadReport({ write, slowMs = DEFAULT_SLOW_LOAD_MS, rowEndsAfterMs = DEFAULT_ROW_ENDS_AFTER_MS }) {
175
+ if (typeof write !== 'function') throw new TypeError('a load report needs somewhere to write')
176
+ const counts = { answered: 0, failed: 0, givenUp: 0, slow: 0, notKept: 0, longestMs: 0 }
177
+ const doubled = (row) => (row & (row - 1)) === 0
178
+ let failedInARow = 0
179
+ let lastFailedAt = 0
180
+ let slowInARow = 0
181
+ let lastSlowAt = 0
182
+ let notKeptInARow = 0
183
+ return {
184
+ // `kept` and `sameFiles` default to a load that kept its project, `vouched` to files that vouched, and `at` to now.
185
+ onLoad({ durationMs, outcome, promised, kept = true, sameFiles = true, vouched = true, at = performance.now() }) {
186
+ const ms = Math.round(durationMs)
187
+ counts.longestMs = Math.max(counts.longestMs, ms)
188
+ if (outcome !== 'answered') {
189
+ counts[outcome === 'given-up' ? 'givenUp' : 'failed'] += 1
190
+ failedInARow += 1
191
+ lastFailedAt = at
192
+ if (doubled(failedInARow)) write({ event: STATUS_LOAD_EVENT, durationMs: ms, outcome, promised, inARow: failedInARow })
193
+ return
194
+ }
195
+ counts.answered += 1
196
+ const afterFailures = failedInARow > 0 && at - lastFailedAt >= rowEndsAfterMs ? failedInARow : 0
197
+ if (afterFailures > 0) failedInARow = 0
198
+ const slow = !promised && ms >= slowMs
199
+ if (slow) {
200
+ counts.slow += 1
201
+ slowInARow += 1
202
+ lastSlowAt = at
203
+ } else if (slowInARow > 0 && at - lastSlowAt >= rowEndsAfterMs) slowInARow = 0
204
+ const notKept = sameFiles && !kept
205
+ if (notKept) {
206
+ counts.notKept += 1
207
+ notKeptInARow += 1
208
+ } else if (sameFiles) notKeptInARow = 0
209
+ const writeSlow = slow && doubled(slowInARow)
210
+ const writeNotKept = notKept && notKeptInARow >= 2 && doubled(notKeptInARow)
211
+ if (afterFailures > 0 || writeSlow || writeNotKept) {
212
+ write({
213
+ event: STATUS_LOAD_EVENT, durationMs: ms, outcome, promised,
214
+ ...(afterFailures > 0 ? { afterFailures } : {}),
215
+ ...(writeSlow ? { slow: true, inARow: slowInARow } : {}),
216
+ ...(writeNotKept ? { notKept: true, notKeptInARow, vouched } : {}),
217
+ })
218
+ }
219
+ },
220
+ // answered, failed, givenUp, slow and notKept loads so far, and the longest in milliseconds.
221
+ counts: () => ({ ...counts }),
222
+ }
223
+ }
224
+
225
+ // `resolveWorkspace(project)` answers { workspaceId, workspaceRoot } as the project names them now, or null, and
226
+ // `workspaceInputsOf(project)` what it reads of a project to do so, as JSON: the service hands in its own of both
227
+ // (resolveServiceWorkspace, serviceWorkspaceInputs), so what two loads are compared by is never a copy kept here.
228
+ // `now()` answers milliseconds that only go up; the times are this module's defaults unless a test gives its own.
229
+ // `realpath(file)` resolves a path as the system does; a test hands in one that fails.
230
+ export function createViewPermission({
231
+ loadProject, resolveWorkspace, workspaceInputsOf, workspaceId, workspaceRoot, now = () => performance.now(),
232
+ keepForMs = DEFAULT_KEEP_FOR_MS, loadDeadlineMs = DEFAULT_LOAD_DEADLINE_MS, retryAfterMs = DEFAULT_RETRY_AFTER_MS, retryAtMostMs = DEFAULT_RETRY_AT_MOST_MS,
233
+ onLoad = () => {}, realpath = fs.realpathSync.native,
234
+ }) {
235
+ if (typeof loadProject !== 'function') throw new TypeError('a view permission needs loadProject')
236
+ if (typeof resolveWorkspace !== 'function') throw new TypeError('a view permission needs resolveWorkspace')
237
+ if (typeof workspaceInputsOf !== 'function') throw new TypeError('a view permission needs workspaceInputsOf')
238
+ const digestOf = (files) => digestOver(files, realpath)
239
+ // What two loads have to agree on: the views, and what the workspace is resolved from. Nothing else of a project is
240
+ // compared, so a field that differs from load to load (a time, a binding under a symbol key) does not keep a project
241
+ // from being kept, and nothing a decision reads is left out.
242
+ const decidedBy = (project, views) => JSON.stringify([[...views].sort(), workspaceInputsOf(project)])
243
+ // The files that decided the project last loaded.
244
+ let files = null
245
+ // What the last load returned that was started over files that vouched: { digest, shape }.
246
+ let seen = null
247
+ // The project two such loads agreed on: { project, views, digest, since }, `since` the start of the second.
248
+ let kept = null
249
+ // A load that answered a promise and has not answered yet: { id, digest, startedAt }.
250
+ let underWay = null
251
+ // Loads that failed in a row while the files held the same: { digest, times, at }.
252
+ let failed = null
253
+ let started = 0
254
+
255
+ function fail(digest, at) {
256
+ kept = null
257
+ failed = { digest, times: failed !== null && failed.digest === digest ? failed.times + 1 : 1, at }
258
+ }
259
+ const tooSoon = (digest, at) => failed !== null && failed.digest === digest && at - failed.at < Math.min(retryAtMostMs, retryAfterMs * 2 ** (failed.times - 1))
260
+ // `before` and `vouched`: the files that decided the project when the load started, and whether they vouched then.
261
+ const sameList = (left, right) => left !== null && right !== null && left.length === right.length && left.every((file, index) => file === right[index])
262
+ const told = (startedAt, outcome, promised, { before, vouched }) => {
263
+ try { const at = now(); onLoad({ durationMs: Math.max(0, at - startedAt), outcome, promised, kept: kept !== null, sameFiles: sameList(before, files), vouched, at }) } catch { /* only told */ }
264
+ }
265
+
266
+ // What a load returned; `digest` was read before it started. A project that names other files than the ones read
267
+ // is kept under a digest the next request cannot read again, so it is dropped there, before it decides anything.
268
+ function answered(project, digest, since) {
269
+ files = configFilesOf(project).map((file) => file.absolute)
270
+ const views = viewsOf(project)
271
+ const loaded = { project, views, digest, since }
272
+ const shape = digest === null ? null : decidedBy(project, views)
273
+ kept = digest !== null && seen !== null && seen.digest === digest && seen.shape === shape ? loaded : null
274
+ seen = digest === null ? null : { digest, shape }
275
+ return loaded
276
+ }
277
+
278
+ // Asks the loader. What it returned, when it answers at once; null when it answers a promise.
279
+ function load(digest, at) {
280
+ const as = { before: files, vouched: digest !== null }
281
+ let answer
282
+ try {
283
+ answer = loadProject()
284
+ if (typeof answer?.then !== 'function') {
285
+ const loaded = answered(answer, digest, at)
286
+ failed = null
287
+ told(at, 'answered', false, as)
288
+ return loaded
289
+ }
290
+ } catch (error) { fail(digest, at); told(at, 'failed', false, as); throw error }
291
+ const id = (started += 1)
292
+ underWay = { id, digest, startedAt: at, as }
293
+ // Only the load under way is listened to: one given up answers nobody. An answer that comes once the deadline has
294
+ // passed gives the load up itself, when no request was there to see the deadline pass first.
295
+ const mine = () => {
296
+ if (underWay?.id !== id) return false
297
+ underWay = null
298
+ if (now() - at < loadDeadlineMs) return true
299
+ fail(digest, now())
300
+ told(at, 'given-up', true, as)
301
+ return false
302
+ }
303
+ Promise.resolve(answer).then((project) => {
304
+ if (!mine()) return
305
+ answered(project, digest, at)
306
+ // While a file vouches for nothing, nothing can be kept from this answer, and no request is here to be decided by it.
307
+ if (digestOf(files) === null) { fail(null, now()); told(at, 'failed', true, as) } else { failed = null; told(at, 'answered', true, as) }
308
+ }, () => { if (mine()) { fail(digest, now()); told(at, 'failed', true, as) } }).catch(() => { fail(digest, now()); told(at, 'failed', true, as) })
309
+ return null
310
+ }
311
+
312
+ // The project as it is now, or null while that is not known.
313
+ function current() {
314
+ const at = now()
315
+ const digest = files === null ? null : digestOf(files)
316
+ if (kept !== null && (digest !== kept.digest || at - kept.since >= keepForMs)) kept = null
317
+ if (underWay !== null && at - underWay.startedAt >= loadDeadlineMs) { const given = underWay; underWay = null; fail(given.digest, at); told(given.startedAt, 'given-up', true, given.as) }
318
+ if (kept !== null && at - kept.since < keepForMs / 2) return kept
319
+ if (underWay === null && !tooSoon(digest, at)) {
320
+ const loaded = load(digest, at)
321
+ if (loaded !== null) return loaded
322
+ }
323
+ return kept
324
+ }
325
+
326
+ return function permits(scopeId) {
327
+ try {
328
+ const known = current()
329
+ if (known === null || !known.views.has(scopeId)) return false
330
+ const workspace = resolveWorkspace(known.project)
331
+ return workspace?.workspaceId === workspaceId && workspace.workspaceRoot === workspaceRoot
332
+ } catch { return false }
333
+ }
334
+ }
@@ -25,6 +25,7 @@ import {
25
25
  trustedMutationRequest,
26
26
  trustedReadRequest,
27
27
  } from './security.mjs'
28
+ import { canonicalize } from '../attestation/jcs.mjs'
28
29
  import { createProposalStore } from '../collaboration/proposals.mjs'
29
30
  import {
30
31
  renderProposalDetailPageHtml,
@@ -34,6 +35,9 @@ import {
34
35
  import { createReviewStore } from '../collaboration/review-store.mjs'
35
36
  import { loadBoundRun, currentRunEligibility } from '../readiness-protocols/evidence.mjs'
36
37
  import { renderReviewPage } from '../ui/review-page.mjs'
38
+ import { renderKnowledgePage } from '../ui/knowledge-page.mjs'
39
+ import { knowledgeDashboard, knowledgeQuestionContext, loadKnowledgeWorkspace } from '../knowledge/workspace.mjs'
40
+ import { createKnowledgeSessions } from '../knowledge/sessions.mjs'
37
41
 
38
42
  export const ATELIER_CONTEXT_SCHEMA = 'atelier-context@v1'
39
43
  export const ATELIER_PRESENCE_SCHEMA = 'atelier-presence@v1'
@@ -216,6 +220,28 @@ function listenFailure(error, port) {
216
220
  return error
217
221
  }
218
222
 
223
+ function knowledgeError(error) {
224
+ const message = error instanceof Error ? error.message : ''
225
+ if (message === 'coauthor requires a Git workspace with untracked, ignored .atelier-local/ state')
226
+ return 'coauthor requires a Git workspace with untracked, ignored local state. Initialize Git and ignore the .atelier-local directory.'
227
+ if (/^collaboration (ledger|event).*ceiling/.test(message))
228
+ return 'knowledge history capacity reached. Preserve the ledger and saved values for operator inspection; do not compact active session chains.'
229
+ if (message.startsWith('EEXIST: private state is locked'))
230
+ return 'knowledge writer is busy. Preserve the request and retry after the active writer finishes.'
231
+ const safe = [
232
+ 'workspace changed', 'stale revision', 'event id conflict', 'source changed',
233
+ 'coauthor session not found', 'coauthor requires', 'unknown knowledge',
234
+ 'invalid knowledge', 'request UUID', 'retry budget exhausted',
235
+ 'receipt and failure events are store-owned', 'event refused in',
236
+ 'coauthor plan must', 'knowledge session configuration mismatch',
237
+ 'knowledge session identity mismatch', 'knowledge session exceeds',
238
+ 'knowledge session list exceeds',
239
+ 'knowledge history unavailable', 'knowledge start marker mismatch',
240
+ ]
241
+ return safe.some(prefix => message.startsWith(prefix)) && !message.includes('/')
242
+ ? message : 'Knowledge operation unavailable. Preserve your draft and inspect the local workspace or active writer.'
243
+ }
244
+
219
245
  function doctorEnvelope() {
220
246
  return {
221
247
  ok: true,
@@ -231,6 +257,7 @@ export function createAtelierSidecarServer({
231
257
  workspaceRoot = process.cwd(),
232
258
  stateDir = workspaceRoot,
233
259
  reviewProject = null,
260
+ knowledgeProject = null,
234
261
  port = 0,
235
262
  presenceTtlMs = Number(process.env.ATELIER_PRESENCE_TTL_MS || 5 * 60 * 1000),
236
263
  } = {}) {
@@ -243,6 +270,7 @@ export function createAtelierSidecarServer({
243
270
  })
244
271
  const publication = loadPublishedWorkspaceManifest(root)
245
272
  const review = reviewProject ? createReviewStore(reviewProject) : null
273
+ const knowledge = knowledgeProject ? createKnowledgeSessions(knowledgeProject) : null
246
274
  const workspaceId = workspaceIdForRoot(root)
247
275
  const noncePath = path.join(stateDirReal, '.atelier-nonce')
248
276
  const presencePath = path.join(stateDirReal, '.atelier-presence.json')
@@ -253,6 +281,95 @@ export function createAtelierSidecarServer({
253
281
  workspaceId,
254
282
  })
255
283
 
284
+ // One copy-only declaration is retained from the workshop: this path, this
285
+ // action, from this view. The request stays `{ sessionId }`; everything
286
+ // retained is derived here from fresh owning readbacks, and the generic
287
+ // /api/proposals route keeps its published-HTML fence.
288
+ const WORKSHOP_HANDOFF_TARGET = Object.freeze({ path: 'records/checklist.md', action: 'copy.repoPath', viewId: 'knowledge-workshop' })
289
+ const sameJson = (left, right) => canonicalize(left) === canonicalize(right)
290
+ const refusedRetention = (error, status = 'refused') => ({ status: 409, body: { ok: false, error, retention: { status } } })
291
+ const retentionSummary = (record, outcome, sourceCurrent) => ({
292
+ status: 'retained',
293
+ outcome,
294
+ sourceCurrent,
295
+ proposalId: record.proposal.id,
296
+ readback: `/api/proposals/${record.proposal.id}`,
297
+ receipt: {
298
+ sessionId: record.retention.sessionId,
299
+ requestId: record.retention.requestId,
300
+ valueDigest: record.retention.valueDigest,
301
+ },
302
+ payloadDigest: record.retention.payloadDigest,
303
+ proposalStatus: record.proposal.status,
304
+ eventVersion: record.proposal.eventVersion,
305
+ })
306
+
307
+ function retainWorkshopHandoff(body) {
308
+ if (!body || Array.isArray(body) || Object.keys(body).join(',') !== 'sessionId' || typeof body.sessionId !== 'string') {
309
+ return refusedRetention('workshop handoff accepts a session id only')
310
+ }
311
+ // A fresh owning readback: this throws when the session, its ledger or a
312
+ // saved value is missing, redirected or altered.
313
+ const session = knowledge.read(body.sessionId)
314
+ const selected = session.record.flow === 'workshop-review'
315
+ ? session.state.saved.find((value) => value.fieldId === 'authored-revision')
316
+ : null
317
+ if (!selected) {
318
+ // No separately selected draft receipt: prepared only, as before. An
319
+ // incomplete Review or a response that selects nothing is refused by
320
+ // the existing preparation.
321
+ return { status: 200, body: { ...knowledge.workshopHandoff(body), retention: { status: 'not-retained', reason: 'no separately selected draft receipt' } } }
322
+ }
323
+ const own = session.owningReadbacks?.verified === true
324
+ ? session.owningReadbacks.immutableValues.find((value) => sameJson(value.receipt, selected.receipt))
325
+ : null
326
+ if (!own || own.text !== selected.text || selected.receipt.sessionId !== session.record.id) {
327
+ return refusedRetention('the selected draft receipt is not confirmed by its owning readback')
328
+ }
329
+ if (!session.current) {
330
+ // Reopen after a source change: read back what was retained, never append.
331
+ const known = proposals.lookupRetainedProposal(selected.receipt)
332
+ if (known.outcome === 'retained') {
333
+ return {
334
+ status: 200,
335
+ body: {
336
+ ok: true,
337
+ handoff: proposals.retainedHandoff(known.record),
338
+ sourceEditsApplied: false,
339
+ directWrite: false,
340
+ retention: retentionSummary(known.record, 'existing', false),
341
+ },
342
+ }
343
+ }
344
+ return known.outcome === 'not-found'
345
+ ? refusedRetention('the source changed before this draft was retained; nothing is retained for this receipt', 'not-retained')
346
+ : refusedRetention(known.error, known.outcome === 'conflict' ? 'refused' : 'unknown')
347
+ }
348
+ const prepared = knowledge.workshopHandoff(body)
349
+ const handoff = prepared.handoff
350
+ if (
351
+ !sameJson(handoff.proposal.selectedDraftReceipt, selected.receipt) || handoff.sessionId !== session.record.id ||
352
+ handoff.path !== WORKSHOP_HANDOFF_TARGET.path || handoff.action !== WORKSHOP_HANDOFF_TARGET.action ||
353
+ handoff.viewId !== WORKSHOP_HANDOFF_TARGET.viewId || handoff.directWrite !== false ||
354
+ handoff.applyEndpoint !== null || handoff.diff?.replacement !== selected.text
355
+ ) {
356
+ return refusedRetention('the prepared handoff does not match the selected draft readback')
357
+ }
358
+ // The original contribution is read back from its own session as well.
359
+ const original = knowledge.read(handoff.proposal.contributionSessionId)
360
+ const values = original.owningReadbacks?.verified === true ? original.owningReadbacks.immutableValues : null
361
+ if (
362
+ !values ||
363
+ !sameJson([...values.map((value) => value.receipt), selected.receipt], handoff.proposal.savedDraftReceipts) ||
364
+ values.find((value) => value.receipt.fieldId === 'original-words')?.text !== handoff.proposal.originalWords
365
+ ) {
366
+ return refusedRetention('the original contribution is not confirmed by its owning readback')
367
+ }
368
+ const result = proposals.retainProposal(handoff)
369
+ if (!result.ok) return refusedRetention(result.error, result.outcome === 'unknown' ? 'unknown' : 'refused')
370
+ return { status: 200, body: { ...prepared, retention: retentionSummary(result.record, result.outcome, true) } }
371
+ }
372
+
256
373
  function resolvePublishedPath({ rel, requireFile = false, requireHtml = false } = {}) {
257
374
  const resolved = resolveWorkspacePath({
258
375
  workspaceRoot: root,
@@ -395,8 +512,8 @@ export function createAtelierSidecarServer({
395
512
  // Browsers send Origin on POST; same-origin GET normally omits it. Keep
396
513
  // the strict origin gate for nonce disclosure instead of relaxing the
397
514
  // existing session-auth GET contract or accepting unclassified reads.
398
- if (review && url.pathname === '/api/review/session') {
399
- if (!trustedMutationRequest(req.headers) || !body || Array.isArray(body) || Object.keys(body).length) {
515
+ if ((review && url.pathname === '/api/review/session') || (knowledge && url.pathname === '/api/knowledge/session')) {
516
+ if (!trustedMutationRequest(req.headers) || !body || Array.isArray(body) || Object.keys(body).length || (url.pathname === '/api/knowledge/session' && url.search)) {
400
517
  json(res, 403, { ok: false, error: 'review session requires a same-origin empty request' })
401
518
  return
402
519
  }
@@ -411,6 +528,22 @@ export function createAtelierSidecarServer({
411
528
  return
412
529
  }
413
530
 
531
+ if (knowledge && url.pathname.startsWith('/api/knowledge/')) {
532
+ const operation = url.pathname.slice('/api/knowledge/'.length)
533
+ if (!['start', 'event', 'recover', 'lookup', 'workshop-handoff', 'workshop-review'].includes(operation) || url.search) {
534
+ json(res, 404, { ok: false, error: 'unknown knowledge action' }); return
535
+ }
536
+ try {
537
+ if (operation === 'workshop-handoff') {
538
+ const retained = retainWorkshopHandoff(body)
539
+ json(res, retained.status, retained.body)
540
+ } else {
541
+ json(res, 200, knowledge[operation === 'workshop-review' ? 'workshopReview' : operation](body))
542
+ }
543
+ } catch (error) { json(res, 409, { ok: false, error: knowledgeError(error) }) }
544
+ return
545
+ }
546
+
414
547
  if (review && url.pathname === '/api/review/contributions') {
415
548
  const result = review.contribute(body)
416
549
  json(res, result.status, result)
@@ -528,6 +661,38 @@ export function createAtelierSidecarServer({
528
661
  return
529
662
  }
530
663
 
664
+ if (knowledge && url.pathname === '/knowledge' && !url.search) {
665
+ const body = renderKnowledgePage()
666
+ res.writeHead(200, htmlDocumentHeaders(Buffer.byteLength(body)))
667
+ res.end(body)
668
+ return
669
+ }
670
+ if (knowledge && url.pathname.startsWith('/api/knowledge/')) {
671
+ try {
672
+ let result
673
+ const operation = url.pathname.slice('/api/knowledge/'.length)
674
+ const parameters = operation === 'context' ? ['id', 'mode'] : operation === 'read' ? ['id'] : []
675
+ if ([...url.searchParams.keys()].some(k => !parameters.includes(k)) || [...url.searchParams.keys()].length !== new Set(url.searchParams.keys()).size) throw new Error('invalid knowledge query')
676
+ if (operation === 'dashboard') {
677
+ const dashboard = knowledgeDashboard(loadKnowledgeWorkspace(knowledgeProject))
678
+ try {
679
+ const profile = knowledge.workshopProfile()
680
+ dashboard.flows = [profile.flow, ...dashboard.flows]
681
+ dashboard.workshop = { available: true, context: profile.context }
682
+ } catch {
683
+ dashboard.workshop = { available: false, reason: 'No current actionable public workshop finding.' }
684
+ }
685
+ result = { dashboard }
686
+ }
687
+ else if (operation === 'context') result = { context: knowledgeQuestionContext(loadKnowledgeWorkspace(knowledgeProject), url.searchParams.get('id'), url.searchParams.get('mode') || 'graph') }
688
+ else if (operation === 'sessions') result = knowledge.list()
689
+ else if (operation === 'read') result = knowledge.read(url.searchParams.get('id'))
690
+ else { json(res, 404, { ok: false, error: 'unknown knowledge action' }); return }
691
+ json(res, 200, { ok: true, ...result })
692
+ } catch (error) { json(res, 409, { ok: false, error: knowledgeError(error) }) }
693
+ return
694
+ }
695
+
531
696
  if (review && url.pathname === '/review') {
532
697
  const body = renderReviewPage()
533
698
  res.writeHead(200, htmlDocumentHeaders(Buffer.byteLength(body)))
@@ -626,6 +791,8 @@ export function createAtelierSidecarServer({
626
791
  proposal: record.proposal,
627
792
  diff: record.diff,
628
793
  copyable: record.copyable,
794
+ // A retained workshop handoff is read back whole; other proposals are unchanged.
795
+ ...(record.retention ? { payload: record.payload, retention: record.retention } : {}),
629
796
  } : { ok: false, error: result.error, diagnostics: result.diagnostics })
630
797
  return
631
798
  }
@@ -1,6 +1,27 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
1
3
  import { AtelierDiagnosticError, resolveProjectConfig } from '../project/config.mjs'
2
4
  import { createAtelierSidecarServer } from './local-sidecar.mjs'
3
5
 
6
+ // dev serves what build wrote and builds nothing itself. Before build, the
7
+ // output folder (or, after graph alone, its manifest) is absent, and the
8
+ // sidecar failed with a raw ENOENT carrying an absolute path or a redacted
9
+ // internal error. Name the missing steps instead, and name the folder only
10
+ // relative to the project config. A non-directory is left to the sidecar.
11
+ function assertProjectionBuilt(project) {
12
+ const stat = fs.statSync(project.outputRoot, { throwIfNoEntry: false })
13
+ if (stat && !stat.isDirectory()) return
14
+ const manifestMissing = stat && !fs.existsSync(path.join(project.outputRoot, 'atelier.manifest.json'))
15
+ if (stat && !manifestMissing) return
16
+ const rel = path.relative(project.configDir, project.outputRoot)
17
+ const contained = rel !== '' && rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel)
18
+ const folder = contained ? `projection output folder ${rel}` : 'the projection output folder'
19
+ const state = manifestMissing ? 'has no atelier.manifest.json' : 'does not exist'
20
+ throw new AtelierDiagnosticError('projection-output-missing', `${folder} ${state}; this project has not been built yet`, {
21
+ hint: 'Run atelier graph, then atelier build, with the same --project path, then retry.',
22
+ })
23
+ }
24
+
4
25
  function parseServerArgs(argv = []) {
5
26
  const args = { projectArgs: [] }
6
27
  for (const arg of argv) {
@@ -30,11 +51,16 @@ function resolvePort(args, env = process.env) {
30
51
  export async function runServerCommand(argv = process.argv.slice(2)) {
31
52
  const args = parseServerArgs(argv)
32
53
  const project = resolveProjectConfig({ argv: args.projectArgs })
54
+ // An unusable --port or PORT is refused before the project's build state,
55
+ // so a mistyped argument is named even in a project not built yet.
56
+ const port = args.smoke ? 0 : resolvePort(args)
57
+ assertProjectionBuilt(project)
33
58
  const sidecar = createAtelierSidecarServer({
34
59
  workspaceRoot: project.outputRoot,
35
60
  stateDir: project.outputRoot,
36
61
  reviewProject: argv.includes('--review') ? project : null,
37
- port: args.smoke ? 0 : resolvePort(args),
62
+ knowledgeProject: argv.includes('--knowledge') ? project : null,
63
+ port,
38
64
  })
39
65
 
40
66
  if (args.smoke) {
@@ -53,5 +79,5 @@ export async function runServerCommand(argv = process.argv.slice(2)) {
53
79
  }
54
80
 
55
81
  const address = await sidecar.listen()
56
- console.log(`MNSTRY Atelier listening on http://127.0.0.1:${address.port}/`)
82
+ console.log(`MNSTRY Atelier listening on http://127.0.0.1:${address.port}/${argv.includes('--knowledge') ? 'knowledge' : ''}`)
57
83
  }