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

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 (414) hide show
  1. package/CHANGELOG.md +413 -0
  2. package/README.md +402 -205
  3. package/SECURITY.md +6 -1
  4. package/contracts/atelier-adoption-policy.v1.schema.json +49 -0
  5. package/contracts/atelier-coauthor.v1.schema.json +163 -0
  6. package/contracts/atelier-decision-request.v1.schema.json +476 -0
  7. package/contracts/atelier-decision-result.v1.schema.json +500 -0
  8. package/contracts/atelier-guide.v1.schema.json +227 -0
  9. package/contracts/atelier-intake.v1.schema.json +151 -0
  10. package/contracts/atelier-migration.v2.schema.json +61 -0
  11. package/contracts/atelier-obsidian-acceptance-receipt.v1.schema.json +211 -0
  12. package/contracts/atelier-obsidian-apply-policy.v1.schema.json +298 -0
  13. package/contracts/atelier-obsidian-corpus-profile.v1.schema.json +98 -0
  14. package/contracts/atelier-obsidian-edit-operation.v1.schema.json +138 -0
  15. package/contracts/atelier-obsidian-ext-settings.v1.schema.json +287 -0
  16. package/contracts/atelier-obsidian-generation-manifest.v1.schema.json +353 -0
  17. package/contracts/atelier-obsidian-proposal-receipt.v1.schema.json +110 -0
  18. package/contracts/atelier-obsidian-publication-journal.v1.schema.json +167 -0
  19. package/contracts/atelier-obsidian-scope.v1.schema.json +264 -0
  20. package/contracts/atelier-obsidian-service-state.v1.schema.json +149 -0
  21. package/contracts/atelier-obsidian-source-snapshot.v1.schema.json +179 -0
  22. package/contracts/atelier-pane-presentation.v1.schema.json +76 -0
  23. package/contracts/atelier-presentation.v1.schema.json +1293 -0
  24. package/contracts/atelier-repository-observation.v1.schema.json +163 -0
  25. package/contracts/atelier-review.v1.schema.json +1601 -0
  26. package/contracts/atelier-skill-steward.v1.schema.json +692 -0
  27. package/contracts/atelier-upgrade-plan.v2.schema.json +398 -0
  28. package/contracts/atelier-upgrade-receipt.v1.schema.json +99 -0
  29. package/contracts/atelier-vault-publication.v1.schema.json +58 -0
  30. package/contracts/public-api-baseline.json +57 -0
  31. package/docs/assurance-controls.md +49 -0
  32. package/docs/atelier-runtime.md +28 -2
  33. package/docs/atelier-sync.md +172 -0
  34. package/docs/blocks/audience-visibility.md +9 -0
  35. package/docs/blocks/claims.md +61 -0
  36. package/docs/blocks/conformance-admission.md +15 -0
  37. package/docs/blocks/will-not-do.md +18 -0
  38. package/docs/coauthor-session.md +57 -0
  39. package/docs/connected-composition.md +75 -0
  40. package/docs/continuity.md +3 -4
  41. package/docs/decisions.md +167 -0
  42. package/docs/design.md +167 -0
  43. package/docs/exact-upgrades.md +159 -0
  44. package/docs/extension-pack-lifecycle.md +33 -0
  45. package/docs/guided-upgrades.md +99 -0
  46. package/docs/install.md +99 -16
  47. package/docs/intake-and-guides.md +65 -0
  48. package/docs/integration-and-review-plan.md +409 -0
  49. package/docs/integration-contract-decisions.md +22 -0
  50. package/docs/knowledge-graph.md +8 -4
  51. package/docs/local-review.md +84 -0
  52. package/docs/local-services.md +328 -0
  53. package/docs/local-state.md +107 -0
  54. package/docs/obsidian-contract.md +698 -0
  55. package/docs/obsidian.md +484 -0
  56. package/docs/portable-presentation.md +328 -0
  57. package/docs/presentation-consumer-boundaries.md +75 -0
  58. package/docs/presentation-review-disposition.md +47 -0
  59. package/docs/project-command-inventory.md +38 -0
  60. package/docs/project-options.md +53 -0
  61. package/docs/release-engineering.md +174 -12
  62. package/docs/repo-boundary-guard.md +12 -2
  63. package/docs/review-portability.md +55 -0
  64. package/docs/skill-steward.md +156 -0
  65. package/docs/upgrade.md +155 -5
  66. package/docs/vault-service.md +292 -0
  67. package/fixtures/atelier-coauthor/invalid/authority.v1.json +5 -0
  68. package/fixtures/atelier-coauthor/valid/config.v1.json +12 -0
  69. package/fixtures/atelier-guide/invalid/authority.v1.json +11 -0
  70. package/fixtures/atelier-guide/valid/capability.v1.json +10 -0
  71. package/fixtures/atelier-guide/valid/consent.v1.json +11 -0
  72. package/fixtures/atelier-guide/valid/engagement.v1.json +7 -0
  73. package/fixtures/atelier-guide/valid/offer.v1.json +10 -0
  74. package/fixtures/atelier-intake/invalid/authority.v1.json +9 -0
  75. package/fixtures/atelier-intake/valid/attempt.v1.json +8 -0
  76. package/fixtures/atelier-intake/valid/completion.v1.json +9 -0
  77. package/fixtures/atelier-intake/valid/source.v1.json +7 -0
  78. package/fixtures/atelier-repository-observation/invalid/complete-with-blocker.v1.json +18 -0
  79. package/fixtures/atelier-repository-observation/valid/complete-local.v1.json +48 -0
  80. package/fixtures/atelier-review/invalid/authenticated-identity.v1.json +22 -0
  81. package/fixtures/atelier-review/valid/bound.v1.json +229 -0
  82. package/fixtures/atelier-review/valid/bundle.v1.json +267 -0
  83. package/fixtures/atelier-review/valid/contribution.v1.json +22 -0
  84. package/fixtures/atelier-review/valid/lifecycle.v1.json +14 -0
  85. package/fixtures/atelier-skill-steward/audit/invalid/source-mutation.v1.json +26 -0
  86. package/fixtures/atelier-skill-steward/audit/valid/clean.v1.json +26 -0
  87. package/fixtures/atelier-skill-steward/candidates/invalid/workflow-key.v1.json +37 -0
  88. package/fixtures/atelier-skill-steward/candidates/valid/create.v1.json +37 -0
  89. package/fixtures/atelier-skill-steward/lock/invalid/bad-digest.v1.json +18 -0
  90. package/fixtures/atelier-skill-steward/lock/valid/installed.v1.json +23 -0
  91. package/fixtures/atelier-skill-steward/plan/invalid/delete-action.v1.json +33 -0
  92. package/fixtures/atelier-skill-steward/plan/valid/add.v1.json +33 -0
  93. package/fixtures/atelier-upgrade-transaction/adoption-policy/invalid/example.json +12 -0
  94. package/fixtures/atelier-upgrade-transaction/adoption-policy/valid/example.json +11 -0
  95. package/fixtures/atelier-upgrade-transaction/migration/invalid/example.json +20 -0
  96. package/fixtures/atelier-upgrade-transaction/migration/valid/example.json +19 -0
  97. package/fixtures/atelier-upgrade-transaction/upgrade-plan/invalid/example.json +58 -0
  98. package/fixtures/atelier-upgrade-transaction/upgrade-plan/valid/example.json +57 -0
  99. package/fixtures/atelier-upgrade-transaction/upgrade-receipt/invalid/example.json +15 -0
  100. package/fixtures/atelier-upgrade-transaction/upgrade-receipt/valid/example.json +14 -0
  101. package/fixtures/atelier-vault-publication/invalid/publication.json +5 -0
  102. package/fixtures/atelier-vault-publication/valid/publication.json +11 -0
  103. package/fixtures/decisions/request/invalid/unknown-authority.v1.json +61 -0
  104. package/fixtures/decisions/request/valid/triage.v1.json +60 -0
  105. package/fixtures/decisions/result/invalid/execution-authority.v1.json +50 -0
  106. package/fixtures/decisions/result/valid/abstained.v1.json +23 -0
  107. package/fixtures/decisions/result/valid/assessed.v1.json +50 -0
  108. package/fixtures/obsidian/acceptance/ap05-extra-notes.json +14 -0
  109. package/fixtures/obsidian/acceptance/receipts/G07.valid.v1.json +63 -0
  110. package/fixtures/obsidian/acceptance/receipts/G13.valid.v1.json +69 -0
  111. package/fixtures/obsidian/acceptance/receipts/G14.valid.v1.json +62 -0
  112. package/fixtures/obsidian/acceptance/receipts/G15.valid.v1.json +56 -0
  113. package/fixtures/obsidian/acceptance/receipts/G16.valid.v1.json +73 -0
  114. package/fixtures/obsidian/acceptance/receipts/G17.valid.v1.json +75 -0
  115. package/fixtures/obsidian/acceptance/receipts/G18.valid.v1.json +64 -0
  116. package/fixtures/obsidian/acceptance/service-sentinels.json +10 -0
  117. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/duplicate-evidence-name.v1.json +39 -0
  118. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/evidence-by-absolute-path.v1.json +34 -0
  119. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/missing-candidate-tree.v1.json +33 -0
  120. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/no-evidence.v1.json +28 -0
  121. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/unknown-outcome.v1.json +34 -0
  122. package/fixtures/obsidian/contracts/acceptance-receipt/invalid/unknown-top-level-field.v1.json +35 -0
  123. package/fixtures/obsidian/contracts/acceptance-receipt/valid/node-gate.v1.json +34 -0
  124. package/fixtures/obsidian/contracts/apply-policy/invalid/automatic-without-edit-class.v1.json +27 -0
  125. package/fixtures/obsidian/contracts/apply-policy/invalid/conflict-overwrite.v1.json +29 -0
  126. package/fixtures/obsidian/contracts/apply-policy/invalid/missing-policy-digest.v1.json +28 -0
  127. package/fixtures/obsidian/contracts/apply-policy/invalid/unbounded-batch.v1.json +29 -0
  128. package/fixtures/obsidian/contracts/apply-policy/invalid/unimplemented-edit-class.v1.json +30 -0
  129. package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-mode.v1.json +29 -0
  130. package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-selector-operator.v1.json +22 -0
  131. package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-top-level-field.v1.json +30 -0
  132. package/fixtures/obsidian/contracts/apply-policy/valid/automatic-scoped.v1.json +29 -0
  133. package/fixtures/obsidian/contracts/apply-policy/valid/manual-default.v1.json +20 -0
  134. package/fixtures/obsidian/contracts/corpus-profile/invalid/absolute-path-in-ext.v1.json +30 -0
  135. package/fixtures/obsidian/contracts/corpus-profile/invalid/absolute-repository-root.v1.json +27 -0
  136. package/fixtures/obsidian/contracts/corpus-profile/invalid/duplicate-repository-id.v1.json +27 -0
  137. package/fixtures/obsidian/contracts/corpus-profile/invalid/overlapping-managed-roots.v1.json +27 -0
  138. package/fixtures/obsidian/contracts/corpus-profile/invalid/unknown-enrollment.v1.json +27 -0
  139. package/fixtures/obsidian/contracts/corpus-profile/invalid/unknown-top-level-field.v1.json +28 -0
  140. package/fixtures/obsidian/contracts/corpus-profile/valid/three-repositories.v1.json +27 -0
  141. package/fixtures/obsidian/contracts/edit-operation/invalid/absolute-recovery-ref.v1.json +21 -0
  142. package/fixtures/obsidian/contracts/edit-operation/invalid/inline-observed-bytes.v1.json +22 -0
  143. package/fixtures/obsidian/contracts/edit-operation/invalid/missing-base-digest.v1.json +20 -0
  144. package/fixtures/obsidian/contracts/edit-operation/invalid/short-idempotency-key.v1.json +21 -0
  145. package/fixtures/obsidian/contracts/edit-operation/invalid/unknown-kind.v1.json +21 -0
  146. package/fixtures/obsidian/contracts/edit-operation/valid/pending-body-replacement.v1.json +21 -0
  147. package/fixtures/obsidian/contracts/ext-settings/invalid/absolute-path-prefix.v1.json +28 -0
  148. package/fixtures/obsidian/contracts/ext-settings/invalid/apply-policy-in-portable-settings.v1.json +30 -0
  149. package/fixtures/obsidian/contracts/ext-settings/invalid/duplicate-scope-id.v1.json +27 -0
  150. package/fixtures/obsidian/contracts/ext-settings/invalid/machine-local-vault-path.v1.json +28 -0
  151. package/fixtures/obsidian/contracts/ext-settings/invalid/service-port-in-portable-settings.v1.json +28 -0
  152. package/fixtures/obsidian/contracts/ext-settings/invalid/unknown-default-scope.v1.json +27 -0
  153. package/fixtures/obsidian/contracts/ext-settings/valid/disabled.v1.json +5 -0
  154. package/fixtures/obsidian/contracts/ext-settings/valid/enabled-with-scopes.v1.json +27 -0
  155. package/fixtures/obsidian/contracts/generation-manifest/invalid/absolute-note-path.v1.json +114 -0
  156. package/fixtures/obsidian/contracts/generation-manifest/invalid/complete-with-unwritten-notes.v1.json +114 -0
  157. package/fixtures/obsidian/contracts/generation-manifest/invalid/derived-link-claimed-declared.v1.json +114 -0
  158. package/fixtures/obsidian/contracts/generation-manifest/invalid/duplicate-note-identity.v1.json +114 -0
  159. package/fixtures/obsidian/contracts/generation-manifest/invalid/in-scope-endpoint-missing.v1.json +114 -0
  160. package/fixtures/obsidian/contracts/generation-manifest/invalid/title-only-note-path.v1.json +114 -0
  161. package/fixtures/obsidian/contracts/generation-manifest/invalid/unknown-note-field.v1.json +115 -0
  162. package/fixtures/obsidian/contracts/generation-manifest/invalid/unknown-relation-type.v1.json +114 -0
  163. package/fixtures/obsidian/contracts/generation-manifest/invalid/withheld-endpoint.v1.json +114 -0
  164. package/fixtures/obsidian/contracts/generation-manifest/valid/two-notes.v1.json +114 -0
  165. package/fixtures/obsidian/contracts/oracles/scope-cases.json +412 -0
  166. package/fixtures/obsidian/contracts/proposal-receipt/invalid/accepted-without-proposal-id.v1.json +12 -0
  167. package/fixtures/obsidian/contracts/proposal-receipt/invalid/missing-adapter-operation-id.v1.json +11 -0
  168. package/fixtures/obsidian/contracts/proposal-receipt/invalid/unknown-backpressure-outcome.v1.json +12 -0
  169. package/fixtures/obsidian/contracts/proposal-receipt/invalid/unknown-top-level-field.v1.json +13 -0
  170. package/fixtures/obsidian/contracts/proposal-receipt/valid/accepted-new.v1.json +12 -0
  171. package/fixtures/obsidian/contracts/proposal-receipt/valid/deferred.v1.json +12 -0
  172. package/fixtures/obsidian/contracts/publication-journal/invalid/absolute-recovery-ref.v1.json +47 -0
  173. package/fixtures/obsidian/contracts/publication-journal/invalid/duplicate-sequence.v1.json +47 -0
  174. package/fixtures/obsidian/contracts/publication-journal/invalid/empty-protocol-id.v1.json +47 -0
  175. package/fixtures/obsidian/contracts/publication-journal/invalid/missing-protocol-id.v1.json +46 -0
  176. package/fixtures/obsidian/contracts/publication-journal/invalid/unknown-entry-field.v1.json +48 -0
  177. package/fixtures/obsidian/contracts/publication-journal/invalid/unknown-state.v1.json +47 -0
  178. package/fixtures/obsidian/contracts/publication-journal/valid/committed.v1.json +47 -0
  179. package/fixtures/obsidian/contracts/publication-journal/valid/updating-after-restart.v1.json +22 -0
  180. package/fixtures/obsidian/contracts/scope/invalid/absolute-path-prefix.v1.json +15 -0
  181. package/fixtures/obsidian/contracts/scope/invalid/duplicate-ids.v1.json +18 -0
  182. package/fixtures/obsidian/contracts/scope/invalid/expansion-without-node-budget.v1.json +40 -0
  183. package/fixtures/obsidian/contracts/scope/invalid/full-mode-with-subset.v1.json +8 -0
  184. package/fixtures/obsidian/contracts/scope/invalid/unknown-mode.v1.json +41 -0
  185. package/fixtures/obsidian/contracts/scope/invalid/unknown-selector-key.v1.json +15 -0
  186. package/fixtures/obsidian/contracts/scope/invalid/unknown-selector-operator.v1.json +14 -0
  187. package/fixtures/obsidian/contracts/scope/invalid/unknown-top-level-field.v1.json +42 -0
  188. package/fixtures/obsidian/contracts/scope/valid/explicit-empty.v1.json +8 -0
  189. package/fixtures/obsidian/contracts/scope/valid/focus.v1.json +11 -0
  190. package/fixtures/obsidian/contracts/scope/valid/full.v1.json +8 -0
  191. package/fixtures/obsidian/contracts/scope/valid/scoped-set-expression.v1.json +41 -0
  192. package/fixtures/obsidian/contracts/service-state/invalid/hostname-instead-of-literal-loopback.v1.json +23 -0
  193. package/fixtures/obsidian/contracts/service-state/invalid/missing-consent.v1.json +18 -0
  194. package/fixtures/obsidian/contracts/service-state/invalid/privileged-port.v1.json +23 -0
  195. package/fixtures/obsidian/contracts/service-state/invalid/unknown-top-level-field.v1.json +24 -0
  196. package/fixtures/obsidian/contracts/service-state/invalid/wildcard-bind.v1.json +23 -0
  197. package/fixtures/obsidian/contracts/service-state/valid/healthy.v1.json +23 -0
  198. package/fixtures/obsidian/contracts/source-snapshot/invalid/absolute-file-path.v1.json +48 -0
  199. package/fixtures/obsidian/contracts/source-snapshot/invalid/duplicate-repository-id.v1.json +48 -0
  200. package/fixtures/obsidian/contracts/source-snapshot/invalid/mixed-read.v1.json +48 -0
  201. package/fixtures/obsidian/contracts/source-snapshot/invalid/parent-traversal-path.v1.json +48 -0
  202. package/fixtures/obsidian/contracts/source-snapshot/invalid/unknown-file-field.v1.json +49 -0
  203. package/fixtures/obsidian/contracts/source-snapshot/valid/two-repositories.v1.json +48 -0
  204. package/fixtures/obsidian/edits/workspace.json +65 -0
  205. package/fixtures/obsidian/materialization/expected-full.json +99 -0
  206. package/fixtures/obsidian/materialization/expected-scoped.json +39 -0
  207. package/fixtures/obsidian/materialization/workspace.json +54 -0
  208. package/fixtures/obsidian/proposals/expected-shapes.json +11 -0
  209. package/fixtures/projects/sample-workspace/content/source.html.kg.json +4 -1
  210. package/fixtures/projects/source-formats-workspace/content/data.json.kg.json +4 -1
  211. package/fixtures/projects/source-formats-workspace/content/logo.png.kg.json +4 -1
  212. package/fixtures/projects/source-formats-workspace/content/metrics.csv.kg.json +4 -1
  213. package/fixtures/projects/source-formats-workspace/content/pipeline.yaml.kg.json +4 -1
  214. package/fixtures/ui/presentation/pane.v1.json +7 -0
  215. package/fixtures/ui/presentation/reference.v1.json +293 -0
  216. package/package.json +86 -10
  217. package/skills/claude/atelier-guided-coauthor/SKILL.md +49 -0
  218. package/skills/claude/atelier-guided-upgrade/SKILL.md +97 -0
  219. package/skills/claude/atelier-local-service/SKILL.md +47 -0
  220. package/skills/claude/atelier-open/SKILL.md +11 -5
  221. package/skills/claude/atelier-public-boundary/SKILL.md +31 -0
  222. package/skills/claude/atelier-skill-steward/SKILL.md +62 -0
  223. package/skills/claude/mnstry-readiness/SKILL.md +5 -0
  224. package/skills/codex/atelier-guided-coauthor/SKILL.md +49 -0
  225. package/skills/codex/atelier-guided-upgrade/SKILL.md +97 -0
  226. package/skills/codex/atelier-local-service/SKILL.md +47 -0
  227. package/skills/codex/atelier-open/SKILL.md +11 -5
  228. package/skills/codex/atelier-public-boundary/SKILL.md +31 -0
  229. package/skills/codex/atelier-skill-steward/SKILL.md +62 -0
  230. package/skills/codex/mnstry-readiness/SKILL.md +5 -0
  231. package/src/access/preview-enforcer.mjs +84 -0
  232. package/src/boundary/content-rules.mjs +283 -20
  233. package/src/boundary/policy.mjs +213 -91
  234. package/src/cli/execute-command.mjs +36 -0
  235. package/src/cli/project-options.mjs +58 -0
  236. package/src/cli/run.mjs +78 -10
  237. package/src/coauthor/session.mjs +161 -0
  238. package/src/coauthor/store.mjs +173 -0
  239. package/src/collaboration/event-ledger.mjs +348 -0
  240. package/src/collaboration/index.mjs +17 -0
  241. package/src/collaboration/inspection-bundle.mjs +223 -0
  242. package/src/collaboration/proposals.mjs +265 -65
  243. package/src/collaboration/review-contracts.mjs +25 -0
  244. package/src/collaboration/review-store.mjs +305 -0
  245. package/src/commands/attestation.mjs +20 -6
  246. package/src/commands/coauthor.mjs +26 -0
  247. package/src/commands/config.mjs +6 -1
  248. package/src/commands/disclosure.mjs +133 -0
  249. package/src/commands/distribution.mjs +2 -1
  250. package/src/commands/extension-pack.mjs +17 -9
  251. package/src/commands/init.mjs +46 -6
  252. package/src/commands/obsidian.mjs +371 -0
  253. package/src/commands/review.mjs +147 -0
  254. package/src/commands/server.mjs +1 -4
  255. package/src/commands/setup.mjs +51 -14
  256. package/src/commands/skills.mjs +199 -0
  257. package/src/commands/sync.mjs +107 -0
  258. package/src/composition/registry.mjs +27 -0
  259. package/src/composition/wire.mjs +122 -0
  260. package/src/contracts/corpus.mjs +35 -0
  261. package/src/decisions/contracts.d.mts +96 -0
  262. package/src/decisions/contracts.mjs +321 -0
  263. package/src/disclosure/content-scan.mjs +206 -0
  264. package/src/egress/check.mjs +7 -38
  265. package/src/egress/forbidden-egress.mjs +32 -18
  266. package/src/export/atelier-export-contract.mjs +16 -1
  267. package/src/extension-packs/lifecycle.mjs +154 -0
  268. package/src/graph/graph.mjs +176 -316
  269. package/src/graph/knowledge-graph.mjs +643 -43
  270. package/src/guides/contracts.mjs +54 -0
  271. package/src/harness/context-client.mjs +9 -1
  272. package/src/index.mjs +41 -0
  273. package/src/intake/store.mjs +119 -0
  274. package/src/preview/channel.mjs +89 -0
  275. package/src/project/config.mjs +112 -52
  276. package/src/project/durable-state.mjs +161 -0
  277. package/src/project/file-class.mjs +180 -0
  278. package/src/project/package-root.mjs +10 -0
  279. package/src/project/path-match.mjs +38 -15
  280. package/src/project/private-state.mjs +120 -0
  281. package/src/projection/obsidian/contracts.mjs +571 -0
  282. package/src/projection/obsidian/edits/align.mjs +169 -0
  283. package/src/projection/obsidian/edits/apply.mjs +889 -0
  284. package/src/projection/obsidian/edits/arbitrate.mjs +414 -0
  285. package/src/projection/obsidian/edits/contribution.mjs +108 -0
  286. package/src/projection/obsidian/edits/index.mjs +19 -0
  287. package/src/projection/obsidian/edits/object-identity.mjs +61 -0
  288. package/src/projection/obsidian/edits/object-store.mjs +517 -0
  289. package/src/projection/obsidian/edits/observe.mjs +123 -0
  290. package/src/projection/obsidian/edits/policy.mjs +123 -0
  291. package/src/projection/obsidian/edits/regions.mjs +648 -0
  292. package/src/projection/obsidian/materialize/byte-lens.mjs +98 -0
  293. package/src/projection/obsidian/materialize/index.mjs +7 -0
  294. package/src/projection/obsidian/materialize/path-registry.mjs +105 -0
  295. package/src/projection/obsidian/materialize/prepare-view.mjs +746 -0
  296. package/src/projection/obsidian/materialize/settings.mjs +57 -0
  297. package/src/projection/obsidian/materialize/stage.mjs +27 -0
  298. package/src/projection/obsidian/proposals/adapter.mjs +572 -0
  299. package/src/projection/obsidian/proposals/backpressure.mjs +113 -0
  300. package/src/projection/obsidian/proposals/contribution.mjs +55 -0
  301. package/src/projection/obsidian/proposals/index.mjs +23 -0
  302. package/src/projection/obsidian/proposals/observation.mjs +216 -0
  303. package/src/projection/obsidian/proposals/queue.mjs +277 -0
  304. package/src/projection/obsidian/proposals/router.mjs +168 -0
  305. package/src/projection/obsidian/publication/bridge-script.mjs +266 -0
  306. package/src/projection/obsidian/publication/exchange.mjs +117 -0
  307. package/src/projection/obsidian/publication/index.mjs +5 -0
  308. package/src/projection/obsidian/publication/publisher.mjs +588 -0
  309. package/src/projection/obsidian/publication/test-seam.mjs +16 -0
  310. package/src/projection/obsidian/publication/transport.mjs +212 -0
  311. package/src/projection/obsidian/recovery/index.mjs +6 -0
  312. package/src/projection/obsidian/recovery/journal.mjs +134 -0
  313. package/src/projection/obsidian/recovery/late-writer.mjs +46 -0
  314. package/src/projection/obsidian/recovery/restart.mjs +316 -0
  315. package/src/projection/obsidian/recovery/store.mjs +234 -0
  316. package/src/projection/obsidian/selection-ui/conflict-view.mjs +91 -0
  317. package/src/projection/obsidian/selection-ui/contribution.mjs +141 -0
  318. package/src/projection/obsidian/selection-ui/focus.mjs +82 -0
  319. package/src/projection/obsidian/selection-ui/index.mjs +12 -0
  320. package/src/projection/obsidian/selection-ui/policy-setup.mjs +107 -0
  321. package/src/projection/obsidian/selection-ui/receipt.mjs +173 -0
  322. package/src/projection/obsidian/selection-ui/selection-state.mjs +122 -0
  323. package/src/projection/obsidian/selection-ui/selection.mjs +109 -0
  324. package/src/projection/project.mjs +28 -19
  325. package/src/readiness-protocols/evidence.mjs +282 -0
  326. package/src/readiness-protocols/runtime.mjs +4 -3
  327. package/src/readiness-protocols/source-read.mjs +39 -0
  328. package/src/runtime/git-adapter.mjs +189 -0
  329. package/src/runtime/local-state.mjs +439 -0
  330. package/src/runtime/obsidian/app-capability.mjs +138 -0
  331. package/src/runtime/obsidian/app-production-seams.mjs +107 -0
  332. package/src/runtime/obsidian/contributions/proposal-adapter.mjs +4 -0
  333. package/src/runtime/obsidian/contributions/selection-ui.mjs +4 -0
  334. package/src/runtime/obsidian/contributions/source-apply.mjs +4 -0
  335. package/src/runtime/obsidian/contributions.mjs +27 -0
  336. package/src/runtime/obsidian/documents.mjs +37 -0
  337. package/src/runtime/obsidian/enablement.mjs +53 -0
  338. package/src/runtime/obsidian/engine.mjs +532 -0
  339. package/src/runtime/obsidian/errors.mjs +14 -0
  340. package/src/runtime/obsidian/extension-points.mjs +103 -0
  341. package/src/runtime/obsidian/index.mjs +34 -0
  342. package/src/runtime/obsidian/lifecycle.mjs +258 -0
  343. package/src/runtime/obsidian/machine-settings.mjs +260 -0
  344. package/src/runtime/obsidian/observation.mjs +158 -0
  345. package/src/runtime/obsidian/opening.mjs +280 -0
  346. package/src/runtime/obsidian/pending-edits.mjs +141 -0
  347. package/src/runtime/obsidian/pipeline.mjs +122 -0
  348. package/src/runtime/obsidian/private-lock.mjs +156 -0
  349. package/src/runtime/obsidian/process-identity.mjs +58 -0
  350. package/src/runtime/obsidian/service-client.mjs +66 -0
  351. package/src/runtime/obsidian/service-entry-path.mjs +9 -0
  352. package/src/runtime/obsidian/service-main.mjs +93 -0
  353. package/src/runtime/obsidian/service-record.mjs +169 -0
  354. package/src/runtime/obsidian/service-server.mjs +121 -0
  355. package/src/runtime/obsidian/service.mjs +216 -0
  356. package/src/runtime/obsidian/startup-adapters.mjs +87 -0
  357. package/src/runtime/obsidian/state-store.mjs +180 -0
  358. package/src/runtime/obsidian/sync-notice.mjs +33 -0
  359. package/src/runtime/obsidian/tick-loop.mjs +84 -0
  360. package/src/runtime/obsidian/watchers.mjs +36 -0
  361. package/src/runtime/repository-observation.mjs +491 -0
  362. package/src/runtime/supervisor.mjs +803 -0
  363. package/src/server/local-sidecar.mjs +125 -59
  364. package/src/server/security.mjs +89 -4
  365. package/src/server/server.mjs +4 -2
  366. package/src/skills/steward.mjs +780 -0
  367. package/src/support/feedback-report.mjs +4 -3
  368. package/src/ui/presentation/browser.mjs +278 -0
  369. package/src/ui/presentation/contract.mjs +116 -0
  370. package/src/ui/presentation/index.mjs +7 -0
  371. package/src/ui/presentation/native.mjs +153 -0
  372. package/src/ui/presentation/proof.mjs +57 -0
  373. package/src/ui/presentation/schema-check.mjs +30 -0
  374. package/src/ui/presentation/schema.generated.mjs +1371 -0
  375. package/src/ui/presentation/state.mjs +43 -0
  376. package/src/ui/presentation/styles.mjs +67 -0
  377. package/src/ui/presentation/tokens.mjs +74 -0
  378. package/src/ui/presentation/web.mjs +85 -0
  379. package/src/ui/review-page.mjs +32 -0
  380. package/src/upgrade/explanation.mjs +33 -0
  381. package/src/upgrade/provenance.mjs +338 -0
  382. package/src/upgrade/transaction-files.mjs +103 -0
  383. package/src/upgrade/transaction.mjs +475 -0
  384. package/src/upgrade/upgrade.mjs +65 -29
  385. package/src/vault/hosts.mjs +21 -0
  386. package/src/vault/identity.mjs +19 -0
  387. package/src/vault/index.mjs +7 -0
  388. package/src/vault/interface.mjs +11 -0
  389. package/src/vault/metadata.mjs +31 -0
  390. package/src/vault/privacy.mjs +148 -0
  391. package/src/vault/probe.mjs +89 -0
  392. package/src/vault/service.mjs +131 -0
  393. package/src/vault/source.mjs +45 -0
  394. package/src/vault/storage.mjs +23 -0
  395. package/templates/distribution-workspace/.claude/launch.json +6 -2
  396. package/templates/distribution-workspace/gitignore +1 -0
  397. package/templates/distribution-workspace/project/README.md +2 -2
  398. package/templates/external-project-workspace/README.md +45 -0
  399. package/templates/external-project-workspace/answers.example.json +4 -0
  400. package/templates/external-project-workspace/atelier.project.json +40 -0
  401. package/templates/external-project-workspace/boundary-policy.v1.json +60 -0
  402. package/templates/external-project-workspace/gitignore +8 -0
  403. package/templates/external-project-workspace/pack-lifecycle.json +14 -0
  404. package/templates/external-project-workspace/packs/protocols/contract-gate.v1.json +109 -0
  405. package/templates/external-project-workspace/packs/sample-pack.v1.json +48 -0
  406. package/templates/external-project-workspace/repo-access.v1.json +9 -0
  407. package/templates/external-project-workspace/source/README.md +11 -0
  408. package/templates/launch.json +6 -2
  409. package/templates/private-domain-workspace/.claude/launch.json +6 -2
  410. package/templates/private-domain-workspace/domain/README.md +2 -2
  411. package/templates/private-domain-workspace/gitignore +1 -0
  412. package/templates/shared-project-workspace/.claude/launch.json +6 -2
  413. package/templates/shared-project-workspace/gitignore +1 -0
  414. package/templates/shared-project-workspace/project/README.md +2 -2
@@ -1,261 +1,182 @@
1
1
  import fs from 'node:fs'
2
2
  import path from 'node:path'
3
- import { commandProject, gitRemoteUrl, loadRepoAccess, readJson, remoteHost, writeJson } from '../project/config.mjs'
4
- import { gitIgnoreFilter } from '../project/git-ignore.mjs'
3
+ import { commandProject, gitRemoteUrl, loadRepoAccess, remoteHost, writeJson } from '../project/config.mjs'
4
+ import { VALID_AUDIENCES as PROJECTION_AUDIENCES } from '../projection/policy.mjs'
5
+ import {
6
+ REPO_ACCESS_SCHEMA,
7
+ VALID_RELATION_TYPES,
8
+ buildKnowledgeGraph,
9
+ createGraphFileCache,
10
+ parseYamlSubset,
11
+ } from './knowledge-graph.mjs'
12
+
13
+ export { createGraphFileCache }
14
+
15
+ // Compatibility exports. Parsing, census, validation, and diagnostics all live
16
+ // in knowledge-graph.mjs; this module only projects its canonical result into
17
+ // the long-standing `atelier graph` artifact shape.
18
+ export const VALID_AUDIENCES = PROJECTION_AUDIENCES
19
+ export const VALID_RELATIONS = VALID_RELATION_TYPES
20
+ export const parseFrontmatterYaml = parseYamlSubset
21
+
22
+ function canonicalInput(project) {
23
+ const loadedAccess = loadRepoAccess(project)
24
+ const managed = []
25
+ const missing = []
26
+ const accessRepos = {}
5
27
 
6
- export const VALID_AUDIENCES = new Set(['private', 'team', 'operator', 'staff', 'public', 'sensitive'])
7
- export const VALID_RELATIONS = new Set(['related', 'supports', 'supersedes', 'implements', 'depends_on', 'evidences', 'contradicts', 'belongs_to'])
8
- const DOC_EXTS = new Set(['.md', '.html', '.pdf', '.docx'])
9
- const SKIP_DIRS = new Set(['.git', 'node_modules', 'atelier-output', 'atelier-readers', '.mnstry', '.atelier-local'])
10
-
11
- const slug = (value) => String(value ?? '').toLowerCase().replace(/\.[^.]+$/, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
12
- const relPath = (root, file) => path.relative(root, file).split(path.sep).join('/')
13
-
14
- // Sidecar-first census. Document extensions are always sources; any other
15
- // file — json, yaml, csv, binary assets — opts in by carrying an adjacent
16
- // .kg.json sidecar. The sidecar itself is metadata, never a source asset.
17
- //
18
- // Membership asks whether the sidecar is VISIBLE, not whether it happens to
19
- // exist on this disk. A git-ignored sidecar is machine-local: obeying one
20
- // enrolls a node that no tracked file declares — audience and all — so a
21
- // clean checkout builds a different graph than the machine that wrote it.
22
- function isSourceFile(name, sidecarVisible) {
23
- if (name.endsWith('.kg.json')) return false
24
- if (DOC_EXTS.has(path.extname(name).toLowerCase())) return true
25
- return sidecarVisible === true
26
- }
27
-
28
- function sidecarIsVisible(abs, root, isIgnored) {
29
- const sidecar = sidecarFor(abs)
30
- return fs.existsSync(sidecar) && !isIgnored(relPath(root, sidecar))
31
- }
32
-
33
- function walk(dir, root, acc = [], isIgnored = gitIgnoreFilter(root)) {
34
- for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
35
- if (ent.name.startsWith('.') || SKIP_DIRS.has(ent.name)) continue
36
- const abs = path.join(dir, ent.name)
37
- if (isIgnored(relPath(root, abs))) continue
38
- if (ent.isDirectory()) walk(abs, root, acc, isIgnored)
39
- if (!ent.isFile() || ent.name.endsWith('.kg.json')) continue
40
- const ext = path.extname(ent.name).toLowerCase()
41
- // Markdown carries its own front matter and never consults a sidecar.
42
- const sidecarVisible = ext === '.md' ? false : sidecarIsVisible(abs, root, isIgnored)
43
- if (isSourceFile(ent.name, sidecarVisible)) acc.push({ abs, rel: relPath(root, abs), ext, sidecarVisible })
44
- }
45
- return acc
46
- }
47
-
48
- function splitFrontmatter(raw) {
49
- if (!raw.startsWith('---\n')) return null
50
- const end = raw.indexOf('\n---', 4)
51
- if (end === -1) return null
52
- return raw.slice(4, end)
53
- }
54
-
55
- function parseValue(value) {
56
- const text = String(value ?? '').trim()
57
- if (!text) return ''
58
- if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) return text.slice(1, -1)
59
- if (text === 'true') return true
60
- if (text === 'false') return false
61
- return text
62
- }
63
-
64
- function setPath(target, keys, value) {
65
- let cursor = target
66
- for (const key of keys.slice(0, -1)) {
67
- cursor[key] ??= {}
68
- cursor = cursor[key]
69
- }
70
- cursor[keys.at(-1)] = value
71
- }
72
-
73
- export function parseFrontmatterYaml(yaml) {
74
- const out = {}
75
- const lines = String(yaml ?? '').split('\n')
76
- const stack = [{ indent: -1, value: out, key: null }]
77
- for (let index = 0; index < lines.length; index += 1) {
78
- const raw = lines[index]
79
- if (!raw.trim() || raw.trim().startsWith('#')) continue
80
- const indent = raw.match(/^\s*/)[0].length
81
- const line = raw.trim()
82
- while (stack.length > 1 && indent <= stack.at(-1).indent) stack.pop()
83
- const parent = stack.at(-1).value
84
- if (line.startsWith('- ')) {
85
- if (!Array.isArray(parent)) continue
86
- parent.push(parseValue(line.slice(2)))
28
+ for (const repo of project.repos ?? []) {
29
+ if (repo.external) continue
30
+ if (!repo.name || !repo.path || !fs.existsSync(repo.path)) {
31
+ missing.push(`configured repo is missing or unreadable: ${repo.name ?? '(unnamed)'}`)
87
32
  continue
88
33
  }
89
- const match = line.match(/^([A-Za-z0-9_.-]+):\s*(.*)$/)
90
- if (!match) continue
91
- const [, key, rest] = match
92
- if (rest === '') {
93
- parent[key] = nextYamlContainer(lines, index, indent)
94
- stack.push({ indent, value: parent[key], key })
95
- } else if (rest === '[]') {
96
- parent[key] = []
97
- stack.push({ indent, value: parent[key], key })
98
- } else {
99
- parent[key] = parseValue(rest)
34
+ managed.push({ name: repo.name, path: repo.path })
35
+ accessRepos[repo.name] = {
36
+ readBoundary:
37
+ loadedAccess.repos?.[repo.name]?.readBoundary ?? repo.readBoundary ?? loadedAccess.defaultReadBoundary ?? 'team',
100
38
  }
101
39
  }
102
- return out
103
- }
104
-
105
- function nextYamlContainer(lines, fromIndex, indent) {
106
- for (let index = fromIndex + 1; index < lines.length; index += 1) {
107
- const raw = lines[index]
108
- if (!raw.trim() || raw.trim().startsWith('#')) continue
109
- const nextIndent = raw.match(/^\s*/)[0].length
110
- if (nextIndent <= indent) return {}
111
- return raw.trim().startsWith('- ') ? [] : {}
112
- }
113
- return {}
114
- }
115
-
116
- function normalizeRelations(relations) {
117
- const out = {}
118
- if (!relations || typeof relations !== 'object') return out
119
- for (const [type, targets] of Object.entries(relations)) {
120
- if (Array.isArray(targets)) out[type] = targets.map(String)
121
- else if (typeof targets === 'string') out[type] = [targets]
122
- else out[type] = []
123
- }
124
- return out
125
- }
126
-
127
- function sidecarFor(file) {
128
- return `${file}.kg.json`
129
- }
130
-
131
- function validateSidecar(meta, repo, file, errors) {
132
- const allowed = new Set(['assetFilename', 'title', 'summary', 'tags', 'kg'])
133
- const kgAllowed = new Set(['id', 'title', 'type', 'domain', 'lifecycle', 'status', 'audience', 'relations'])
134
- for (const key of Object.keys(meta || {})) {
135
- if (!allowed.has(key)) errors.push(`${repo.name}/${file.rel}: sidecar has unknown key ${key}`)
136
- }
137
- for (const key of Object.keys(meta?.kg || {})) {
138
- if (!kgAllowed.has(key)) errors.push(`${repo.name}/${file.rel}: sidecar kg has unknown key ${key}`)
139
- }
140
- }
141
40
 
142
- function nodeFromMarkdown(repo, file, errors) {
143
- const raw = fs.readFileSync(file.abs, 'utf8')
144
- const fm = splitFrontmatter(raw)
145
- if (!fm) return null
146
- const parsed = parseFrontmatterYaml(fm)
147
- const kg = parsed.kg ?? {}
148
- if (!kg.id) errors.push(`${repo.name}/${file.rel}: kg.id is required`)
149
- if (!kg.audience) errors.push(`${repo.name}/${file.rel}: kg.audience is required`)
150
- if (kg.visibility) errors.push(`${repo.name}/${file.rel}: kg.visibility is invalid; use kg.audience`)
151
41
  return {
152
- id: kg.id || `${repo.name}:${slug(file.rel)}`,
153
- title: parsed.title || kg.title || path.basename(file.rel, file.ext),
154
- summary: parsed.summary || '',
155
- repo: repo.name,
156
- path: file.rel,
157
- type: kg.type || 'document',
158
- status: kg.status || 'active',
159
- audience: kg.audience || 'private',
160
- tags: Array.isArray(parsed.tags) ? parsed.tags : [],
161
- relations: normalizeRelations(kg.relations),
162
- }
163
- }
164
-
165
- function nodeFromSidecar(repo, file, errors) {
166
- const sidecar = sidecarFor(file.abs)
167
- // Visibility again, and for the same reason: a git-ignored sidecar must not
168
- // supply identity, audience, or relations. A document-extension asset whose
169
- // only sidecar is ignored therefore fails closed with the ordinary
170
- // missing-sidecar error — the same verdict a clean checkout reaches.
171
- if (!file.sidecarVisible) {
172
- errors.push(`${repo.name}/${file.rel}: non-Markdown source requires sidecar ${path.basename(sidecar)}`)
173
- return null
174
- }
175
- const meta = readJson(sidecar)
176
- validateSidecar(meta, repo, file, errors)
177
- const kg = meta.kg ?? {}
178
- if (meta.assetFilename && meta.assetFilename !== path.basename(file.abs)) errors.push(`${repo.name}/${file.rel}: sidecar assetFilename must match source filename`)
179
- if (!kg.id) errors.push(`${repo.name}/${file.rel}: sidecar kg.id is required`)
180
- if (!kg.audience) errors.push(`${repo.name}/${file.rel}: sidecar kg.audience is required`)
181
- if (kg.visibility) errors.push(`${repo.name}/${file.rel}: sidecar kg.visibility is invalid; use kg.audience`)
42
+ missing,
43
+ options: {
44
+ workspaceRoot: project.workspaceRoot,
45
+ repoEntries: managed,
46
+ repoAccessConfig: {
47
+ schema: REPO_ACCESS_SCHEMA,
48
+ defaultReadBoundary: loadedAccess.defaultReadBoundary ?? 'team',
49
+ repos: accessRepos,
50
+ },
51
+ repoAccessConfigPath: project.repoAccessPath,
52
+ externalRelationPrefixes: Array.isArray(project.config.graph?.externalRelationPrefixes)
53
+ ? project.config.graph.externalRelationPrefixes
54
+ : [],
55
+ externalRelationIds: Array.isArray(project.config.graph?.externalRelationIds) ? project.config.graph.externalRelationIds : [],
56
+ },
57
+ }
58
+ }
59
+
60
+ function compatibilityError(message) {
61
+ return String(message)
62
+ .replace(': missing non-Markdown sidecar ', ': non-Markdown source requires sidecar ')
63
+ .replace(/: active orphan sidecar has no adjacent source .+$/, ': sidecar has no matching source asset')
64
+ }
65
+
66
+ function compatibilityNode(node) {
67
+ return {
68
+ id: node.id,
69
+ title: node.title,
70
+ summary: node.summary,
71
+ repo: node.repo,
72
+ path: node.path,
73
+ type: node.kgType,
74
+ status: node.status,
75
+ audience: node.audience,
76
+ ...(node.classification === 'unclassified'
77
+ ? { classification: 'unclassified', classificationReason: node.classificationReason }
78
+ : {}),
79
+ tags: node.tags,
80
+ relations: node.relations,
81
+ repoAccess: node.repoAccess,
82
+ }
83
+ }
84
+
85
+ function compatibilityDiagnostic(diagnostic) {
86
+ if (diagnostic.code === 'unclassified-content') return diagnostic
87
+ return {
88
+ severity: diagnostic.severity,
89
+ code: 'audience-wider-repo-boundary',
90
+ node: diagnostic.node,
91
+ message: diagnostic.message,
92
+ }
93
+ }
94
+
95
+ function externalRepos(project) {
96
+ return (project.repos ?? [])
97
+ .filter((repo) => repo.external)
98
+ .map((repo) => ({
99
+ name: repo.name,
100
+ path: repo.path ?? null,
101
+ remoteHost: remoteHost(gitRemoteUrl(repo.path)),
102
+ }))
103
+ .sort((a, b) => String(a.name).localeCompare(String(b.name)))
104
+ }
105
+
106
+ function canonicalBuild(project, overrides = {}) {
107
+ const input = canonicalInput(project)
108
+ const result = buildKnowledgeGraph({ ...input.options, ...overrides })
109
+ return { input, result }
110
+ }
111
+
112
+ // The supported project-to-options seam: the exact buildKnowledgeGraph options
113
+ // every graph consumer derives from a loaded project, plus the configured
114
+ // repositories that could not be read.
115
+ export function canonicalGraphOptions(project) {
116
+ return canonicalInput(project)
117
+ }
118
+
119
+ const canonicalEdgeId = (edge) => JSON.stringify([edge.source, edge.type, edge.target])
120
+
121
+ // The canonical graph for a loaded project, for consumers that need derived
122
+ // edges and the source offsets behind them. Nothing is resolved here: nodes,
123
+ // edges, link occurrences and link findings all come from
124
+ // buildKnowledgeGraph. Each edge gains a stable id and an origin; a derived
125
+ // edge carries every source occurrence that produced it. Embedded assets are
126
+ // reported beside the graph, like link occurrences: `embeds` holds one record
127
+ // per embed occurrence and `assets` each embedded file once. Neither adds a
128
+ // node or an edge.
129
+ // `fileCache` (createGraphFileCache) lets repeated builds of one workspace
130
+ // reuse the per-file census and link scan of every Markdown source whose
131
+ // bytes did not change; the result is the same as without it. `observedDigest`
132
+ // (see createGraphFileCache) lets a caller that observes sources by digest
133
+ // spare the read of a source it knows unchanged.
134
+ export function buildCanonicalGraph(project, { isLinkTargetEligible, isAssetEligible, fileCache = null, observedDigest = null } = {}) {
135
+ const { input, result } = canonicalBuild(project, {
136
+ ...(isLinkTargetEligible ? { isLinkTargetEligible } : {}),
137
+ ...(isAssetEligible ? { isAssetEligible } : {}),
138
+ ...(fileCache ? { fileCache } : {}),
139
+ ...(observedDigest ? { observedDigest } : {}),
140
+ })
141
+ const canonical = result.workspaceGraph ?? { nodes: [], edges: [], diagnostics: [] }
142
+ const occurrences = new Map()
143
+ for (const link of result.resolvedLinks ?? []) {
144
+ const id = canonicalEdgeId(link)
145
+ if (!occurrences.has(id)) occurrences.set(id, [])
146
+ occurrences.get(id).push(link)
147
+ }
148
+ const edges = canonical.edges.map((edge) => {
149
+ const id = canonicalEdgeId(edge)
150
+ return edge.declared === true
151
+ ? { id, ...edge, origin: 'declared' }
152
+ : { id, ...edge, origin: 'ordinary-link', occurrences: occurrences.get(id) ?? [] }
153
+ })
154
+ const embeds = result.resolvedEmbeds ?? []
155
+ const assets = [...new Map(embeds.map((embed) => [embed.asset.id, embed.asset])).values()].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
182
156
  return {
183
- id: kg.id || `${repo.name}:${slug(file.rel)}-${file.ext.slice(1)}`,
184
- title: meta.title || kg.title || path.basename(file.rel, file.ext),
185
- summary: meta.summary || '',
186
- repo: repo.name,
187
- path: file.rel,
188
- type: kg.type || file.ext.slice(1),
189
- status: kg.status || 'active',
190
- audience: kg.audience || 'private',
191
- tags: Array.isArray(meta.tags) ? meta.tags : [],
192
- relations: normalizeRelations(kg.relations),
157
+ ok: result.ok && input.missing.length === 0,
158
+ errors: [...input.missing, ...result.errors],
159
+ options: input.options,
160
+ nodes: canonical.nodes,
161
+ edges,
162
+ diagnostics: canonical.diagnostics,
163
+ links: result.resolvedLinks ?? [],
164
+ embeds,
165
+ assets,
166
+ linkDiagnostics: result.linkDiagnostics ?? [],
167
+ ...(result.fileCensus ? { fileCensus: result.fileCensus } : {}),
193
168
  }
194
169
  }
195
170
 
196
171
  export function buildGraph(project) {
197
- const repoAccess = loadRepoAccess(project)
198
- const externalRelationPrefixes = new Set(Array.isArray(project.config.graph?.externalRelationPrefixes) ? project.config.graph.externalRelationPrefixes : [])
199
- const externalRelationIds = new Set(Array.isArray(project.config.graph?.externalRelationIds) ? project.config.graph.externalRelationIds : [])
200
- const errors = []
201
- const diagnostics = []
202
- const nodes = []
203
- const edges = []
204
- const external = []
205
-
206
- for (const repo of project.repos) {
207
- if (repo.external) {
208
- // Identity acknowledged, nothing walked: the workspace does not own this
209
- // content and cannot answer for its metadata.
210
- external.push({ name: repo.name, path: repo.path ?? null, remoteHost: remoteHost(gitRemoteUrl(repo.path)) })
211
- continue
212
- }
213
- if (!repo.name || !repo.path || !fs.existsSync(repo.path)) {
214
- errors.push(`configured repo is missing or unreadable: ${repo.name ?? '(unnamed)'}`)
215
- continue
216
- }
217
- // One batched ignore lookup per repo, shared by every walk below.
218
- const isIgnored = gitIgnoreFilter(repo.path)
219
- for (const file of walk(repo.path, repo.path, [], isIgnored)) {
220
- // Markdown is the one inline metadata format; every other source is
221
- // sidecar-described and its own bytes are never read.
222
- const node = file.ext === '.md' ? nodeFromMarkdown(repo, file, errors) : nodeFromSidecar(repo, file, errors)
223
- if (!node) continue
224
- const boundary = repoAccess.repos?.[repo.name]?.readBoundary ?? repo.readBoundary ?? repoAccess.defaultReadBoundary ?? 'team'
225
- node.repoAccess = { readBoundary: boundary }
226
- if (['private', 'sensitive'].includes(node.audience) && boundary !== 'private') {
227
- diagnostics.push({
228
- severity: 'warning',
229
- code: 'audience-wider-repo-boundary',
230
- node: node.id,
231
- message: `${node.id}: audience ${node.audience} is in a ${boundary}-readable repo`,
232
- })
233
- }
234
- nodes.push(node)
235
- }
236
- for (const orphan of findOrphanSidecars(repo.path, isIgnored)) {
237
- errors.push(`${repo.name}/${orphan}: sidecar has no matching source asset`)
238
- }
239
- }
240
-
241
- const ids = new Set()
242
- for (const node of nodes) {
243
- if (ids.has(node.id)) errors.push(`duplicate kg.id: ${node.id}`)
244
- ids.add(node.id)
245
- if (!VALID_AUDIENCES.has(node.audience)) errors.push(`${node.id}: invalid audience ${node.audience}`)
246
- }
247
-
248
- for (const node of nodes) {
249
- for (const [type, targets] of Object.entries(node.relations ?? {})) {
250
- if (!VALID_RELATIONS.has(type)) errors.push(`${node.id}: invalid relation type ${type}`)
251
- for (const target of targets) {
252
- if (!ids.has(target) && !isExternalRelationTarget(target, { externalRelationPrefixes, externalRelationIds })) {
253
- errors.push(`${node.id}: relation target not found: ${target}`)
254
- }
255
- edges.push({ source: node.id, target, type, declared: true })
256
- }
257
- }
258
- }
172
+ const { input, result } = canonicalBuild(project)
173
+ const canonical = result.workspaceGraph ?? { nodes: [], edges: [], diagnostics: [] }
174
+ const nodes = canonical.nodes.map(compatibilityNode)
175
+ // The legacy artifact exposes declared relations only. Derived Markdown-link
176
+ // edges remain available from buildKnowledgeGraph without changing this API.
177
+ const edges = canonical.edges.filter((edge) => edge.declared === true)
178
+ const diagnostics = canonical.diagnostics.map(compatibilityDiagnostic)
179
+ const errors = [...input.missing, ...result.errors.map(compatibilityError)]
259
180
 
260
181
  return {
261
182
  schema: 'mnstry.atelier-knowledge-graph@v1',
@@ -265,83 +186,22 @@ export function buildGraph(project) {
265
186
  nodes,
266
187
  edges,
267
188
  diagnostics,
268
- external: external.sort((a, b) => String(a.name).localeCompare(String(b.name))),
189
+ external: externalRepos(project),
269
190
  errors,
270
191
  }
271
192
  }
272
193
 
273
- function isExternalRelationTarget(target, { externalRelationPrefixes, externalRelationIds }) {
274
- if (externalRelationIds.has(target)) return true
275
- const prefix = String(target).split(':')[0]
276
- return externalRelationPrefixes.has(prefix)
277
- }
278
-
279
- function findOrphanSidecars(root, isIgnored = gitIgnoreFilter(root)) {
280
- const orphans = []
281
- function visit(dir) {
282
- for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
283
- if (ent.name.startsWith('.') || SKIP_DIRS.has(ent.name)) continue
284
- const abs = path.join(dir, ent.name)
285
- if (isIgnored(relPath(root, abs))) continue
286
- if (ent.isDirectory()) visit(abs)
287
- else if (ent.isFile() && ent.name.endsWith('.kg.json')) {
288
- const asset = abs.slice(0, -'.kg.json'.length)
289
- if (!fs.existsSync(asset)) orphans.push(relPath(root, abs))
290
- }
291
- }
292
- }
293
- visit(root)
294
- return orphans
295
- }
296
-
297
- // Sidecars that exist on disk but are git-ignored, next to a file the census
298
- // can see. Refused, never obeyed — and never silently: dropping them without a
299
- // word is what let an untracked sidecar mint a public node.
300
- function findIgnoredSidecars(root, isIgnored = gitIgnoreFilter(root)) {
301
- const found = []
302
- function visit(dir) {
303
- for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
304
- if (ent.name.startsWith('.') || SKIP_DIRS.has(ent.name)) continue
305
- const abs = path.join(dir, ent.name)
306
- const rel = relPath(root, abs)
307
- if (ent.isDirectory()) {
308
- if (!isIgnored(rel)) visit(abs)
309
- continue
310
- }
311
- if (!ent.isFile() || !ent.name.endsWith('.kg.json') || !isIgnored(rel)) continue
312
- const assetRel = rel.slice(0, -'.kg.json'.length)
313
- // Nothing was refused if the asset is itself machine-local or absent, and
314
- // Markdown never reads a sidecar in the first place.
315
- if (assetRel.toLowerCase().endsWith('.md')) continue
316
- if (isIgnored(assetRel) || !fs.existsSync(path.join(root, assetRel))) continue
317
- found.push({ path: assetRel, sidecar: rel })
318
- }
319
- }
320
- visit(root)
321
- return found
322
- }
323
-
324
- // Deliberately NOT part of buildGraph's return value: that object is written
325
- // verbatim to the graph artifact, so a machine-local observation inside it
326
- // would make the artifact differ between a developer's tree and a clean
327
- // checkout — the exact churn the ignore filter exists to prevent. The operator
328
- // hears about the refusal; the shared artifact never does.
194
+ // Machine-local ignored-sidecar observations stay outside generated artifacts.
329
195
  export function ignoredSidecarWarnings(project) {
330
- const warnings = []
331
- for (const repo of project.repos ?? []) {
332
- if (repo.external || !repo.name || !repo.path || !fs.existsSync(repo.path)) continue
333
- for (const found of findIgnoredSidecars(repo.path)) {
334
- warnings.push({
335
- severity: 'warning',
336
- code: 'ignored-sidecar',
337
- repo: repo.name,
338
- path: found.path,
339
- sidecar: found.sidecar,
340
- message: `${repo.name}/${found.sidecar}: sidecar is git-ignored and was refused; it cannot enroll ${found.path} or describe it`,
341
- })
342
- }
343
- }
344
- return warnings
196
+ const { result } = canonicalBuild(project)
197
+ return (result.ignoredSidecars ?? []).map((found) => ({
198
+ severity: 'warning',
199
+ code: 'ignored-sidecar',
200
+ repo: found.repo,
201
+ path: found.path,
202
+ sidecar: found.sidecar,
203
+ message: `${found.repo}/${found.sidecar}: sidecar is git-ignored and was refused; it cannot enroll ${found.path} or describe it`,
204
+ }))
345
205
  }
346
206
 
347
207
  export function runGraphCommand(argv = process.argv.slice(2)) {