@mnstry/atelier 0.2.0-alpha.5 → 0.2.0-alpha.8
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.
- package/CHANGELOG.md +191 -0
- package/README.md +86 -25
- package/SECURITY.md +6 -1
- package/contracts/atelier-adoption-policy.v1.schema.json +49 -0
- package/contracts/atelier-coauthor.v1.schema.json +163 -0
- package/contracts/atelier-decision-request.v1.schema.json +476 -0
- package/contracts/atelier-decision-result.v1.schema.json +500 -0
- package/contracts/atelier-guide.v1.schema.json +227 -0
- package/contracts/atelier-intake.v1.schema.json +151 -0
- package/contracts/atelier-migration.v2.schema.json +61 -0
- package/contracts/atelier-obsidian-acceptance-receipt.v1.schema.json +211 -0
- package/contracts/atelier-obsidian-apply-policy.v1.schema.json +298 -0
- package/contracts/atelier-obsidian-corpus-profile.v1.schema.json +98 -0
- package/contracts/atelier-obsidian-edit-operation.v1.schema.json +138 -0
- package/contracts/atelier-obsidian-ext-settings.v1.schema.json +287 -0
- package/contracts/atelier-obsidian-generation-manifest.v1.schema.json +353 -0
- package/contracts/atelier-obsidian-proposal-receipt.v1.schema.json +110 -0
- package/contracts/atelier-obsidian-publication-journal.v1.schema.json +167 -0
- package/contracts/atelier-obsidian-scope.v1.schema.json +264 -0
- package/contracts/atelier-obsidian-service-state.v1.schema.json +149 -0
- package/contracts/atelier-obsidian-source-snapshot.v1.schema.json +179 -0
- package/contracts/atelier-pane-presentation.v1.schema.json +76 -0
- package/contracts/atelier-presentation.v1.schema.json +1293 -0
- package/contracts/atelier-repository-observation.v1.schema.json +163 -0
- package/contracts/atelier-review.v1.schema.json +1601 -0
- package/contracts/atelier-skill-steward.v1.schema.json +692 -0
- package/contracts/atelier-upgrade-plan.v2.schema.json +398 -0
- package/contracts/atelier-upgrade-receipt.v1.schema.json +99 -0
- package/contracts/atelier-vault-publication.v1.schema.json +58 -0
- package/docs/assurance-controls.md +12 -2
- package/docs/atelier-runtime.md +13 -2
- package/docs/atelier-sync.md +172 -0
- package/docs/blocks/claims.md +28 -18
- package/docs/blocks/will-not-do.md +12 -3
- package/docs/coauthor-session.md +57 -0
- package/docs/connected-composition.md +75 -0
- package/docs/decisions.md +167 -0
- package/docs/exact-upgrades.md +159 -0
- package/docs/extension-pack-lifecycle.md +33 -0
- package/docs/guided-upgrades.md +99 -0
- package/docs/install.md +65 -9
- package/docs/intake-and-guides.md +65 -0
- package/docs/integration-and-review-plan.md +409 -0
- package/docs/integration-contract-decisions.md +22 -0
- package/docs/local-review.md +84 -0
- package/docs/local-services.md +227 -0
- package/docs/local-state.md +107 -0
- package/docs/obsidian-contract.md +687 -0
- package/docs/obsidian.md +387 -0
- package/docs/portable-presentation.md +328 -0
- package/docs/presentation-consumer-boundaries.md +75 -0
- package/docs/presentation-review-disposition.md +47 -0
- package/docs/project-command-inventory.md +38 -0
- package/docs/project-options.md +53 -0
- package/docs/release-engineering.md +99 -2
- package/docs/review-portability.md +55 -0
- package/docs/skill-steward.md +156 -0
- package/docs/upgrade.md +70 -2
- package/docs/vault-service.md +292 -0
- package/fixtures/atelier-coauthor/invalid/authority.v1.json +5 -0
- package/fixtures/atelier-coauthor/valid/config.v1.json +12 -0
- package/fixtures/atelier-guide/invalid/authority.v1.json +11 -0
- package/fixtures/atelier-guide/valid/capability.v1.json +10 -0
- package/fixtures/atelier-guide/valid/consent.v1.json +11 -0
- package/fixtures/atelier-guide/valid/engagement.v1.json +7 -0
- package/fixtures/atelier-guide/valid/offer.v1.json +10 -0
- package/fixtures/atelier-intake/invalid/authority.v1.json +9 -0
- package/fixtures/atelier-intake/valid/attempt.v1.json +8 -0
- package/fixtures/atelier-intake/valid/completion.v1.json +9 -0
- package/fixtures/atelier-intake/valid/source.v1.json +7 -0
- package/fixtures/atelier-repository-observation/invalid/complete-with-blocker.v1.json +18 -0
- package/fixtures/atelier-repository-observation/valid/complete-local.v1.json +48 -0
- package/fixtures/atelier-review/invalid/authenticated-identity.v1.json +22 -0
- package/fixtures/atelier-review/valid/bound.v1.json +229 -0
- package/fixtures/atelier-review/valid/bundle.v1.json +267 -0
- package/fixtures/atelier-review/valid/contribution.v1.json +22 -0
- package/fixtures/atelier-review/valid/lifecycle.v1.json +14 -0
- package/fixtures/atelier-skill-steward/audit/invalid/source-mutation.v1.json +26 -0
- package/fixtures/atelier-skill-steward/audit/valid/clean.v1.json +26 -0
- package/fixtures/atelier-skill-steward/candidates/invalid/workflow-key.v1.json +37 -0
- package/fixtures/atelier-skill-steward/candidates/valid/create.v1.json +37 -0
- package/fixtures/atelier-skill-steward/lock/invalid/bad-digest.v1.json +18 -0
- package/fixtures/atelier-skill-steward/lock/valid/installed.v1.json +23 -0
- package/fixtures/atelier-skill-steward/plan/invalid/delete-action.v1.json +33 -0
- package/fixtures/atelier-skill-steward/plan/valid/add.v1.json +33 -0
- package/fixtures/atelier-upgrade-transaction/adoption-policy/invalid/example.json +12 -0
- package/fixtures/atelier-upgrade-transaction/adoption-policy/valid/example.json +11 -0
- package/fixtures/atelier-upgrade-transaction/migration/invalid/example.json +20 -0
- package/fixtures/atelier-upgrade-transaction/migration/valid/example.json +19 -0
- package/fixtures/atelier-upgrade-transaction/upgrade-plan/invalid/example.json +58 -0
- package/fixtures/atelier-upgrade-transaction/upgrade-plan/valid/example.json +57 -0
- package/fixtures/atelier-upgrade-transaction/upgrade-receipt/invalid/example.json +15 -0
- package/fixtures/atelier-upgrade-transaction/upgrade-receipt/valid/example.json +14 -0
- package/fixtures/atelier-vault-publication/invalid/publication.json +5 -0
- package/fixtures/atelier-vault-publication/valid/publication.json +11 -0
- package/fixtures/decisions/request/invalid/unknown-authority.v1.json +61 -0
- package/fixtures/decisions/request/valid/triage.v1.json +60 -0
- package/fixtures/decisions/result/invalid/execution-authority.v1.json +50 -0
- package/fixtures/decisions/result/valid/abstained.v1.json +23 -0
- package/fixtures/decisions/result/valid/assessed.v1.json +50 -0
- package/fixtures/obsidian/acceptance/ap05-extra-notes.json +14 -0
- package/fixtures/obsidian/acceptance/receipts/G07.valid.v1.json +63 -0
- package/fixtures/obsidian/acceptance/receipts/G13.valid.v1.json +69 -0
- package/fixtures/obsidian/acceptance/receipts/G14.valid.v1.json +62 -0
- package/fixtures/obsidian/acceptance/receipts/G15.valid.v1.json +56 -0
- package/fixtures/obsidian/acceptance/receipts/G16.valid.v1.json +73 -0
- package/fixtures/obsidian/acceptance/receipts/G17.valid.v1.json +75 -0
- package/fixtures/obsidian/acceptance/receipts/G18.valid.v1.json +64 -0
- package/fixtures/obsidian/acceptance/service-sentinels.json +10 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/duplicate-evidence-name.v1.json +39 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/evidence-by-absolute-path.v1.json +34 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/missing-candidate-tree.v1.json +33 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/no-evidence.v1.json +28 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/unknown-outcome.v1.json +34 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/unknown-top-level-field.v1.json +35 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/valid/node-gate.v1.json +34 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/automatic-without-edit-class.v1.json +27 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/conflict-overwrite.v1.json +29 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/missing-policy-digest.v1.json +28 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unbounded-batch.v1.json +29 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unimplemented-edit-class.v1.json +30 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-mode.v1.json +29 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-selector-operator.v1.json +22 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-top-level-field.v1.json +30 -0
- package/fixtures/obsidian/contracts/apply-policy/valid/automatic-scoped.v1.json +29 -0
- package/fixtures/obsidian/contracts/apply-policy/valid/manual-default.v1.json +20 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/absolute-path-in-ext.v1.json +30 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/absolute-repository-root.v1.json +27 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/duplicate-repository-id.v1.json +27 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/overlapping-managed-roots.v1.json +27 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/unknown-enrollment.v1.json +27 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/unknown-top-level-field.v1.json +28 -0
- package/fixtures/obsidian/contracts/corpus-profile/valid/three-repositories.v1.json +27 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/absolute-recovery-ref.v1.json +21 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/inline-observed-bytes.v1.json +22 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/missing-base-digest.v1.json +20 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/short-idempotency-key.v1.json +21 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/unknown-kind.v1.json +21 -0
- package/fixtures/obsidian/contracts/edit-operation/valid/pending-body-replacement.v1.json +21 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/absolute-path-prefix.v1.json +28 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/apply-policy-in-portable-settings.v1.json +30 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/duplicate-scope-id.v1.json +27 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/machine-local-vault-path.v1.json +28 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/service-port-in-portable-settings.v1.json +28 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/unknown-default-scope.v1.json +27 -0
- package/fixtures/obsidian/contracts/ext-settings/valid/disabled.v1.json +5 -0
- package/fixtures/obsidian/contracts/ext-settings/valid/enabled-with-scopes.v1.json +27 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/absolute-note-path.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/complete-with-unwritten-notes.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/derived-link-claimed-declared.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/duplicate-note-identity.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/in-scope-endpoint-missing.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/title-only-note-path.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/unknown-note-field.v1.json +115 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/unknown-relation-type.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/withheld-endpoint.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/valid/two-notes.v1.json +114 -0
- package/fixtures/obsidian/contracts/oracles/scope-cases.json +412 -0
- package/fixtures/obsidian/contracts/proposal-receipt/invalid/accepted-without-proposal-id.v1.json +12 -0
- package/fixtures/obsidian/contracts/proposal-receipt/invalid/missing-adapter-operation-id.v1.json +11 -0
- package/fixtures/obsidian/contracts/proposal-receipt/invalid/unknown-backpressure-outcome.v1.json +12 -0
- package/fixtures/obsidian/contracts/proposal-receipt/invalid/unknown-top-level-field.v1.json +13 -0
- package/fixtures/obsidian/contracts/proposal-receipt/valid/accepted-new.v1.json +12 -0
- package/fixtures/obsidian/contracts/proposal-receipt/valid/deferred.v1.json +12 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/absolute-recovery-ref.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/duplicate-sequence.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/empty-protocol-id.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/missing-protocol-id.v1.json +46 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/unknown-entry-field.v1.json +48 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/unknown-state.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/valid/committed.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/valid/updating-after-restart.v1.json +22 -0
- package/fixtures/obsidian/contracts/scope/invalid/absolute-path-prefix.v1.json +15 -0
- package/fixtures/obsidian/contracts/scope/invalid/duplicate-ids.v1.json +18 -0
- package/fixtures/obsidian/contracts/scope/invalid/expansion-without-node-budget.v1.json +40 -0
- package/fixtures/obsidian/contracts/scope/invalid/full-mode-with-subset.v1.json +8 -0
- package/fixtures/obsidian/contracts/scope/invalid/unknown-mode.v1.json +41 -0
- package/fixtures/obsidian/contracts/scope/invalid/unknown-selector-key.v1.json +15 -0
- package/fixtures/obsidian/contracts/scope/invalid/unknown-selector-operator.v1.json +14 -0
- package/fixtures/obsidian/contracts/scope/invalid/unknown-top-level-field.v1.json +42 -0
- package/fixtures/obsidian/contracts/scope/valid/explicit-empty.v1.json +8 -0
- package/fixtures/obsidian/contracts/scope/valid/focus.v1.json +11 -0
- package/fixtures/obsidian/contracts/scope/valid/full.v1.json +8 -0
- package/fixtures/obsidian/contracts/scope/valid/scoped-set-expression.v1.json +41 -0
- package/fixtures/obsidian/contracts/service-state/invalid/hostname-instead-of-literal-loopback.v1.json +23 -0
- package/fixtures/obsidian/contracts/service-state/invalid/missing-consent.v1.json +18 -0
- package/fixtures/obsidian/contracts/service-state/invalid/privileged-port.v1.json +23 -0
- package/fixtures/obsidian/contracts/service-state/invalid/unknown-top-level-field.v1.json +24 -0
- package/fixtures/obsidian/contracts/service-state/invalid/wildcard-bind.v1.json +23 -0
- package/fixtures/obsidian/contracts/service-state/valid/healthy.v1.json +23 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/absolute-file-path.v1.json +48 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/duplicate-repository-id.v1.json +48 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/mixed-read.v1.json +48 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/parent-traversal-path.v1.json +48 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/unknown-file-field.v1.json +49 -0
- package/fixtures/obsidian/contracts/source-snapshot/valid/two-repositories.v1.json +48 -0
- package/fixtures/obsidian/edits/workspace.json +65 -0
- package/fixtures/obsidian/materialization/expected-full.json +99 -0
- package/fixtures/obsidian/materialization/expected-scoped.json +39 -0
- package/fixtures/obsidian/materialization/workspace.json +54 -0
- package/fixtures/obsidian/proposals/expected-shapes.json +11 -0
- package/fixtures/ui/presentation/pane.v1.json +7 -0
- package/fixtures/ui/presentation/reference.v1.json +293 -0
- package/package.json +61 -6
- package/skills/claude/atelier-guided-coauthor/SKILL.md +49 -0
- package/skills/claude/atelier-guided-upgrade/SKILL.md +97 -0
- package/skills/claude/atelier-open/SKILL.md +5 -0
- package/skills/claude/atelier-skill-steward/SKILL.md +62 -0
- package/skills/claude/mnstry-readiness/SKILL.md +5 -0
- package/skills/codex/atelier-guided-coauthor/SKILL.md +49 -0
- package/skills/codex/atelier-guided-upgrade/SKILL.md +97 -0
- package/skills/codex/atelier-open/SKILL.md +5 -0
- package/skills/codex/atelier-skill-steward/SKILL.md +62 -0
- package/skills/codex/mnstry-readiness/SKILL.md +5 -0
- package/src/access/preview-enforcer.mjs +84 -0
- package/src/boundary/content-rules.mjs +9 -4
- package/src/boundary/policy.mjs +74 -42
- package/src/cli/project-options.mjs +58 -0
- package/src/cli/run.mjs +61 -3
- package/src/coauthor/session.mjs +161 -0
- package/src/coauthor/store.mjs +173 -0
- package/src/collaboration/event-ledger.mjs +16 -33
- package/src/collaboration/inspection-bundle.mjs +223 -0
- package/src/collaboration/review-contracts.mjs +25 -0
- package/src/collaboration/review-store.mjs +305 -0
- package/src/commands/coauthor.mjs +26 -0
- package/src/commands/config.mjs +6 -1
- package/src/commands/extension-pack.mjs +15 -8
- package/src/commands/init.mjs +44 -5
- package/src/commands/obsidian.mjs +368 -0
- package/src/commands/review.mjs +147 -0
- package/src/commands/setup.mjs +51 -14
- package/src/commands/skills.mjs +199 -0
- package/src/commands/sync.mjs +107 -0
- package/src/composition/registry.mjs +27 -0
- package/src/composition/wire.mjs +122 -0
- package/src/contracts/corpus.mjs +35 -0
- package/src/decisions/contracts.d.mts +96 -0
- package/src/decisions/contracts.mjs +321 -0
- package/src/disclosure/content-scan.mjs +13 -0
- package/src/export/atelier-export-contract.mjs +16 -1
- package/src/extension-packs/lifecycle.mjs +154 -0
- package/src/graph/graph.mjs +64 -2
- package/src/graph/knowledge-graph.mjs +548 -24
- package/src/guides/contracts.mjs +54 -0
- package/src/index.mjs +29 -0
- package/src/intake/store.mjs +119 -0
- package/src/preview/channel.mjs +89 -0
- package/src/project/config.mjs +50 -49
- package/src/project/durable-state.mjs +161 -0
- package/src/project/file-class.mjs +166 -0
- package/src/project/private-state.mjs +11 -1
- package/src/projection/obsidian/contracts.mjs +571 -0
- package/src/projection/obsidian/edits/align.mjs +169 -0
- package/src/projection/obsidian/edits/apply.mjs +889 -0
- package/src/projection/obsidian/edits/arbitrate.mjs +414 -0
- package/src/projection/obsidian/edits/contribution.mjs +108 -0
- package/src/projection/obsidian/edits/index.mjs +19 -0
- package/src/projection/obsidian/edits/object-identity.mjs +61 -0
- package/src/projection/obsidian/edits/object-store.mjs +517 -0
- package/src/projection/obsidian/edits/observe.mjs +123 -0
- package/src/projection/obsidian/edits/policy.mjs +123 -0
- package/src/projection/obsidian/edits/regions.mjs +648 -0
- package/src/projection/obsidian/materialize/byte-lens.mjs +98 -0
- package/src/projection/obsidian/materialize/index.mjs +7 -0
- package/src/projection/obsidian/materialize/path-registry.mjs +105 -0
- package/src/projection/obsidian/materialize/prepare-view.mjs +746 -0
- package/src/projection/obsidian/materialize/settings.mjs +57 -0
- package/src/projection/obsidian/materialize/stage.mjs +27 -0
- package/src/projection/obsidian/proposals/adapter.mjs +572 -0
- package/src/projection/obsidian/proposals/backpressure.mjs +113 -0
- package/src/projection/obsidian/proposals/contribution.mjs +55 -0
- package/src/projection/obsidian/proposals/index.mjs +23 -0
- package/src/projection/obsidian/proposals/observation.mjs +216 -0
- package/src/projection/obsidian/proposals/queue.mjs +277 -0
- package/src/projection/obsidian/proposals/router.mjs +168 -0
- package/src/projection/obsidian/publication/bridge-script.mjs +266 -0
- package/src/projection/obsidian/publication/exchange.mjs +117 -0
- package/src/projection/obsidian/publication/index.mjs +5 -0
- package/src/projection/obsidian/publication/publisher.mjs +588 -0
- package/src/projection/obsidian/publication/test-seam.mjs +16 -0
- package/src/projection/obsidian/publication/transport.mjs +139 -0
- package/src/projection/obsidian/recovery/index.mjs +6 -0
- package/src/projection/obsidian/recovery/journal.mjs +134 -0
- package/src/projection/obsidian/recovery/late-writer.mjs +46 -0
- package/src/projection/obsidian/recovery/restart.mjs +316 -0
- package/src/projection/obsidian/recovery/store.mjs +234 -0
- package/src/projection/obsidian/selection-ui/conflict-view.mjs +91 -0
- package/src/projection/obsidian/selection-ui/contribution.mjs +141 -0
- package/src/projection/obsidian/selection-ui/focus.mjs +82 -0
- package/src/projection/obsidian/selection-ui/index.mjs +12 -0
- package/src/projection/obsidian/selection-ui/policy-setup.mjs +107 -0
- package/src/projection/obsidian/selection-ui/receipt.mjs +173 -0
- package/src/projection/obsidian/selection-ui/selection-state.mjs +122 -0
- package/src/projection/obsidian/selection-ui/selection.mjs +109 -0
- package/src/projection/project.mjs +28 -19
- package/src/readiness-protocols/evidence.mjs +282 -0
- package/src/readiness-protocols/runtime.mjs +4 -3
- package/src/readiness-protocols/source-read.mjs +39 -0
- package/src/runtime/git-adapter.mjs +189 -0
- package/src/runtime/local-state.mjs +439 -0
- package/src/runtime/obsidian/app-capability.mjs +110 -0
- package/src/runtime/obsidian/app-production-seams.mjs +96 -0
- package/src/runtime/obsidian/contributions/proposal-adapter.mjs +4 -0
- package/src/runtime/obsidian/contributions/selection-ui.mjs +4 -0
- package/src/runtime/obsidian/contributions/source-apply.mjs +4 -0
- package/src/runtime/obsidian/contributions.mjs +27 -0
- package/src/runtime/obsidian/documents.mjs +37 -0
- package/src/runtime/obsidian/enablement.mjs +53 -0
- package/src/runtime/obsidian/engine.mjs +532 -0
- package/src/runtime/obsidian/errors.mjs +14 -0
- package/src/runtime/obsidian/extension-points.mjs +103 -0
- package/src/runtime/obsidian/index.mjs +34 -0
- package/src/runtime/obsidian/lifecycle.mjs +258 -0
- package/src/runtime/obsidian/machine-settings.mjs +260 -0
- package/src/runtime/obsidian/observation.mjs +158 -0
- package/src/runtime/obsidian/opening.mjs +243 -0
- package/src/runtime/obsidian/pending-edits.mjs +141 -0
- package/src/runtime/obsidian/pipeline.mjs +122 -0
- package/src/runtime/obsidian/private-lock.mjs +156 -0
- package/src/runtime/obsidian/process-identity.mjs +58 -0
- package/src/runtime/obsidian/service-client.mjs +66 -0
- package/src/runtime/obsidian/service-main.mjs +89 -0
- package/src/runtime/obsidian/service-record.mjs +169 -0
- package/src/runtime/obsidian/service-server.mjs +121 -0
- package/src/runtime/obsidian/service.mjs +216 -0
- package/src/runtime/obsidian/startup-adapters.mjs +87 -0
- package/src/runtime/obsidian/state-store.mjs +180 -0
- package/src/runtime/obsidian/sync-notice.mjs +33 -0
- package/src/runtime/obsidian/tick-loop.mjs +84 -0
- package/src/runtime/obsidian/watchers.mjs +36 -0
- package/src/runtime/repository-observation.mjs +491 -0
- package/src/runtime/supervisor.mjs +803 -0
- package/src/server/local-sidecar.mjs +44 -0
- package/src/server/server.mjs +1 -0
- package/src/skills/steward.mjs +780 -0
- package/src/ui/presentation/browser.mjs +278 -0
- package/src/ui/presentation/contract.mjs +116 -0
- package/src/ui/presentation/index.mjs +7 -0
- package/src/ui/presentation/native.mjs +153 -0
- package/src/ui/presentation/proof.mjs +57 -0
- package/src/ui/presentation/schema-check.mjs +30 -0
- package/src/ui/presentation/schema.generated.mjs +1371 -0
- package/src/ui/presentation/state.mjs +43 -0
- package/src/ui/presentation/styles.mjs +67 -0
- package/src/ui/presentation/tokens.mjs +74 -0
- package/src/ui/presentation/web.mjs +85 -0
- package/src/ui/review-page.mjs +32 -0
- package/src/upgrade/explanation.mjs +33 -0
- package/src/upgrade/provenance.mjs +338 -0
- package/src/upgrade/transaction-files.mjs +103 -0
- package/src/upgrade/transaction.mjs +475 -0
- package/src/upgrade/upgrade.mjs +63 -28
- package/src/vault/hosts.mjs +21 -0
- package/src/vault/identity.mjs +19 -0
- package/src/vault/index.mjs +7 -0
- package/src/vault/interface.mjs +11 -0
- package/src/vault/metadata.mjs +31 -0
- package/src/vault/privacy.mjs +148 -0
- package/src/vault/probe.mjs +89 -0
- package/src/vault/service.mjs +131 -0
- package/src/vault/source.mjs +45 -0
- package/src/vault/storage.mjs +23 -0
- package/templates/distribution-workspace/.claude/launch.json +6 -2
- package/templates/distribution-workspace/gitignore +1 -0
- package/templates/distribution-workspace/project/README.md +2 -2
- package/templates/external-project-workspace/README.md +45 -0
- package/templates/external-project-workspace/answers.example.json +4 -0
- package/templates/external-project-workspace/atelier.project.json +40 -0
- package/templates/external-project-workspace/boundary-policy.v1.json +60 -0
- package/templates/external-project-workspace/gitignore +8 -0
- package/templates/external-project-workspace/pack-lifecycle.json +14 -0
- package/templates/external-project-workspace/packs/protocols/contract-gate.v1.json +109 -0
- package/templates/external-project-workspace/packs/sample-pack.v1.json +48 -0
- package/templates/external-project-workspace/repo-access.v1.json +9 -0
- package/templates/external-project-workspace/source/README.md +11 -0
- package/templates/launch.json +6 -2
- package/templates/private-domain-workspace/.claude/launch.json +6 -2
- package/templates/private-domain-workspace/domain/README.md +2 -2
- package/templates/private-domain-workspace/gitignore +1 -0
- package/templates/shared-project-workspace/.claude/launch.json +6 -2
- package/templates/shared-project-workspace/gitignore +1 -0
- package/templates/shared-project-workspace/project/README.md +2 -2
package/src/commands/setup.mjs
CHANGED
|
@@ -9,8 +9,11 @@ import {
|
|
|
9
9
|
firstString,
|
|
10
10
|
parseArgs,
|
|
11
11
|
resolveProjectConfig,
|
|
12
|
+
validateProjectConfigDoc,
|
|
12
13
|
writeJson,
|
|
13
14
|
} from '../project/config.mjs'
|
|
15
|
+
import { writeAtelierLock, loadAtelierLock, checkAtelierLock } from '../upgrade/upgrade.mjs'
|
|
16
|
+
import { loadBoundaryPolicy, validateBoundaryPolicy } from '../boundary/policy.mjs'
|
|
14
17
|
import { auditRepoIdentities } from '../project/repo-identity.mjs'
|
|
15
18
|
|
|
16
19
|
const PROFILE_SET = new Set(['single-repo', 'private-domain', 'shared-project', 'multi-repo', 'monorepo', 'control-workspace'])
|
|
@@ -121,8 +124,10 @@ function runSetup(argv) {
|
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
function baseBoundaryPolicy({ repoName, profile, actor }) {
|
|
124
|
-
const privateDomain = profile === 'shared-project' ? `${actor}-private` : repoName
|
|
125
127
|
const shared = profile === 'shared-project'
|
|
128
|
+
const privateDomain = shared
|
|
129
|
+
? (repoName === `${actor}-private` ? `${actor}-private-domain` : `${actor}-private`)
|
|
130
|
+
: repoName
|
|
126
131
|
return {
|
|
127
132
|
schema: 'mnstry.atelier-boundary-policy@v1',
|
|
128
133
|
mode: 'strict',
|
|
@@ -134,6 +139,11 @@ function baseBoundaryPolicy({ repoName, profile, actor }) {
|
|
|
134
139
|
},
|
|
135
140
|
},
|
|
136
141
|
repos: {
|
|
142
|
+
...(shared ? { [privateDomain]: {
|
|
143
|
+
kind: 'private_domain', ownerActor: actor, readBoundary: 'private',
|
|
144
|
+
allowedAudiences: ['private', 'sensitive', 'team', 'operator', 'staff', 'public'],
|
|
145
|
+
forbiddenAudiences: [], autoCommit: 'guarded',
|
|
146
|
+
} } : {}),
|
|
137
147
|
[repoName]: {
|
|
138
148
|
kind: shared ? 'shared' : 'private_domain',
|
|
139
149
|
ownerActor: shared ? undefined : actor,
|
|
@@ -178,23 +188,42 @@ function runAdopt(argv) {
|
|
|
178
188
|
if (profile === 'monorepo' && !firstString(args.include, args.includes)) {
|
|
179
189
|
throw new Error('monorepo adopt requires --include so Atelier does not scan the whole repo by accident')
|
|
180
190
|
}
|
|
181
|
-
fs.mkdirSync(target, { recursive: true })
|
|
182
|
-
appendIgnoreLines(target)
|
|
183
191
|
const repoName = slug(firstString(args.name) || path.basename(target))
|
|
184
192
|
const actor = slug(firstString(args.actor) || process.env.USER || 'owner')
|
|
185
193
|
const projectPath = path.join(target, 'atelier.project.json')
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
194
|
+
const existingProject = fs.existsSync(projectPath)
|
|
195
|
+
? resolveProjectConfig({ cwd: target, argv: ['--project', projectPath], writeLocalState: false }) : null
|
|
196
|
+
const proposedConfig = existingProject?.config || {
|
|
197
|
+
schema: 'mnstry.atelier-project-config@v1',
|
|
198
|
+
name: repoName,
|
|
199
|
+
roots: { workspace: '.', repoOps: '.' },
|
|
200
|
+
graph: { repoAccessPath: 'repo-access.v1.json', outputPath: 'atelier-output/knowledge.graph.json' },
|
|
201
|
+
projection: { outputRoot: 'atelier-output', readinessPath: 'atelier-output/atelier-readiness.json' },
|
|
202
|
+
boundaries: { policyPath: 'boundary-policy.v1.json', governanceLedgerPath: 'governance/repo-boundary-ledger.md', strictNewRepos: true },
|
|
203
|
+
setup: { profile, include: firstString(args.include, args.includes) || null, exclude: firstString(args.exclude, args.excludes) || null },
|
|
204
|
+
repos: [{ name: repoName, path: '.', readBoundary: profile === 'shared-project' ? 'team' : 'private', role: profile }],
|
|
205
|
+
}
|
|
206
|
+
const configErrors = validateProjectConfigDoc(proposedConfig)
|
|
207
|
+
if (configErrors.length) throw new Error(`adopt requires a valid project config: ${configErrors.join('; ')}`)
|
|
208
|
+
const proposedProject = existingProject || { configDir: target, config: proposedConfig, repos: proposedConfig.repos }
|
|
209
|
+
// Parse and validate retained state before any scaffold or overlay writes.
|
|
210
|
+
const prior = loadAtelierLock(existingProject || { configDir: target })
|
|
211
|
+
if (fs.existsSync(prior.lockPath) && !prior.lock) throw new Error('existing lock must contain a valid lock object')
|
|
212
|
+
if (prior.lock && !existingProject) throw new Error('existing lock requires an existing project configuration')
|
|
213
|
+
const priorPolicy = loadBoundaryPolicy(proposedProject)
|
|
214
|
+
if (existingProject || fs.existsSync(priorPolicy.policyPath)) {
|
|
215
|
+
if (!priorPolicy.ok) throw new Error(`adopt requires a valid boundary policy: ${priorPolicy.errors.join('; ')}`)
|
|
197
216
|
}
|
|
217
|
+
const proposedPolicy = priorPolicy.ok ? priorPolicy.policy : cleanUndefined(baseBoundaryPolicy({ repoName, profile, actor }))
|
|
218
|
+
const proposedPolicyErrors = validateBoundaryPolicy(proposedPolicy, proposedProject)
|
|
219
|
+
if (proposedPolicyErrors.length) throw new Error(`adopt requires a valid boundary policy: ${proposedPolicyErrors.join('; ')}`)
|
|
220
|
+
if (prior.lock) {
|
|
221
|
+
const report = checkAtelierLock(existingProject)
|
|
222
|
+
if (!report.ok) throw new Error(`adopt cannot accept existing lock drift: ${report.errors.join('; ')}`)
|
|
223
|
+
}
|
|
224
|
+
fs.mkdirSync(target, { recursive: true })
|
|
225
|
+
appendIgnoreLines(target)
|
|
226
|
+
if (!existingProject) writeJson(projectPath, proposedConfig)
|
|
198
227
|
if (!fs.existsSync(path.join(target, 'repo-access.v1.json'))) {
|
|
199
228
|
writeJson(path.join(target, 'repo-access.v1.json'), {
|
|
200
229
|
schema: 'mnstry.atelier-repo-access@v1',
|
|
@@ -206,8 +235,16 @@ function runAdopt(argv) {
|
|
|
206
235
|
writeJson(path.join(target, 'boundary-policy.v1.json'), cleanUndefined(baseBoundaryPolicy({ repoName, profile, actor })))
|
|
207
236
|
}
|
|
208
237
|
const project = resolveProjectConfig({ cwd: target, argv: ['--project', projectPath] })
|
|
238
|
+
const loadedPolicy = loadBoundaryPolicy(project)
|
|
239
|
+
const policyErrors = loadedPolicy.ok ? validateBoundaryPolicy(loadedPolicy.policy, project) : loadedPolicy.errors
|
|
240
|
+
if (policyErrors.length) throw new Error(`adopt requires a valid boundary policy: ${policyErrors.join('; ')}`)
|
|
209
241
|
ensureLocalState(project, { write: true })
|
|
210
242
|
writeLocalOverlay(project)
|
|
243
|
+
// Adoption creates the first lock, but never accepts drift in an existing one.
|
|
244
|
+
const { lockPath } = loadAtelierLock(project)
|
|
245
|
+
if (!fs.existsSync(lockPath)) writeAtelierLock({ project, templateId: `adopt:${profile}` })
|
|
246
|
+
const lockReport = checkAtelierLock(project)
|
|
247
|
+
if (!lockReport.ok) throw new Error(`adopt lock check failed: ${lockReport.errors.join('; ')}`)
|
|
211
248
|
console.log(JSON.stringify({ ok: true, command: 'adopt', profile, target, projectPath }, null, 2))
|
|
212
249
|
}
|
|
213
250
|
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
applySkillSync,
|
|
7
|
+
auditSkillCatalog,
|
|
8
|
+
buildSkillCandidates,
|
|
9
|
+
planSkillSync,
|
|
10
|
+
recordSkillObservation,
|
|
11
|
+
} from '../skills/steward.mjs'
|
|
12
|
+
|
|
13
|
+
const USAGE = `Usage: atelier skills <subcommand>
|
|
14
|
+
|
|
15
|
+
Subcommands:
|
|
16
|
+
audit [--root DIR] [--peer DIR] [--json]
|
|
17
|
+
Audit required metadata, bundle safety, relative resources, and peer parity.
|
|
18
|
+
With no roots, audits the bundled Codex and Claude skill surfaces.
|
|
19
|
+
|
|
20
|
+
observe --workflow KEY --signal SIGNAL [--skill NAME] [--outcome OUTCOME]
|
|
21
|
+
Append one content-free local observation under ignored .atelier-local state.
|
|
22
|
+
Prompts, transcripts, free-form summaries, and source content are not accepted.
|
|
23
|
+
|
|
24
|
+
candidates [--all] [--json]
|
|
25
|
+
Aggregate local observations into evidence-thresholded create, improve,
|
|
26
|
+
reconcile, or retire candidates. This command never edits a skill.
|
|
27
|
+
|
|
28
|
+
sync [--source DIR] [--target .agents/skills] [--json]
|
|
29
|
+
Produce a content-bound plan for updating a repo-scoped managed projection.
|
|
30
|
+
Apply only with --apply --confirm PLAN_DIGEST. Source skills are never changed,
|
|
31
|
+
unmanaged collisions and locally drifted managed skills block, and removals are
|
|
32
|
+
moved to ignored quarantine rather than deleted.
|
|
33
|
+
|
|
34
|
+
Exit codes: 0 clean or applied, 1 findings or blockers, 2 usage error.`
|
|
35
|
+
|
|
36
|
+
const OPTION_SPEC = {
|
|
37
|
+
root: 'value',
|
|
38
|
+
peer: 'value',
|
|
39
|
+
source: 'value',
|
|
40
|
+
target: 'value',
|
|
41
|
+
workflow: 'value',
|
|
42
|
+
signal: 'value',
|
|
43
|
+
skill: 'value',
|
|
44
|
+
outcome: 'value',
|
|
45
|
+
confirm: 'value',
|
|
46
|
+
json: 'flag',
|
|
47
|
+
all: 'flag',
|
|
48
|
+
apply: 'flag',
|
|
49
|
+
'dry-run': 'flag',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function usageError(message) {
|
|
53
|
+
console.error(`${message}\n\n${USAGE}`)
|
|
54
|
+
process.exit(2)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseOptions(argv, allowed) {
|
|
58
|
+
const options = {}
|
|
59
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
60
|
+
const arg = argv[index]
|
|
61
|
+
if (arg === '--help' || arg === '-h') {
|
|
62
|
+
console.log(USAGE)
|
|
63
|
+
process.exit(0)
|
|
64
|
+
}
|
|
65
|
+
if (!arg.startsWith('--')) usageError(`unexpected argument: ${arg}`)
|
|
66
|
+
const [rawName, inline] = arg.slice(2).split(/=(.*)/s, 2)
|
|
67
|
+
if (!allowed.has(rawName) || !(rawName in OPTION_SPEC)) usageError(`unknown option --${rawName}`)
|
|
68
|
+
if (OPTION_SPEC[rawName] === 'flag') {
|
|
69
|
+
if (inline !== undefined) usageError(`--${rawName} does not take a value`)
|
|
70
|
+
options[rawName] = true
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
let value = inline
|
|
74
|
+
if (value === undefined) {
|
|
75
|
+
index += 1
|
|
76
|
+
value = argv[index]
|
|
77
|
+
}
|
|
78
|
+
if (value === undefined || value.startsWith('--')) usageError(`--${rawName} requires a value`)
|
|
79
|
+
options[rawName] = value
|
|
80
|
+
}
|
|
81
|
+
return options
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function printAudit(report, json) {
|
|
85
|
+
if (json) {
|
|
86
|
+
console.log(JSON.stringify(report, null, 2))
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
console.log(`skills: ${report.summary.skills}; errors: ${report.summary.errors}; warnings: ${report.summary.warnings}`)
|
|
90
|
+
for (const finding of report.findings) {
|
|
91
|
+
console.log(`${finding.severity}: ${finding.code} ${finding.surface}/${finding.skill ?? '-'} ${finding.path}`)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function runAudit(argv) {
|
|
96
|
+
const options = parseOptions(argv, new Set(['root', 'peer', 'json']))
|
|
97
|
+
if (options.peer && !options.root) usageError('--peer requires --root')
|
|
98
|
+
const surfaces = options.root
|
|
99
|
+
? [
|
|
100
|
+
{ name: 'source', root: path.resolve(options.root) },
|
|
101
|
+
...(options.peer ? [{ name: 'peer', root: path.resolve(options.peer) }] : []),
|
|
102
|
+
]
|
|
103
|
+
: undefined
|
|
104
|
+
const report = auditSkillCatalog({ surfaces, reportRoot: process.cwd() })
|
|
105
|
+
printAudit(report, options.json)
|
|
106
|
+
return (process.exitCode = report.ok ? 0 : 1)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function runObserve(argv) {
|
|
110
|
+
const options = parseOptions(argv, new Set(['workflow', 'signal', 'skill', 'outcome', 'json']))
|
|
111
|
+
if (!options.workflow) usageError('--workflow is required')
|
|
112
|
+
if (!options.signal) usageError('--signal is required')
|
|
113
|
+
try {
|
|
114
|
+
const receipt = recordSkillObservation({
|
|
115
|
+
workspaceRoot: process.cwd(),
|
|
116
|
+
workflowKey: options.workflow,
|
|
117
|
+
signal: options.signal,
|
|
118
|
+
skill: options.skill ?? null,
|
|
119
|
+
outcome: options.outcome ?? 'unknown',
|
|
120
|
+
})
|
|
121
|
+
if (options.json) console.log(JSON.stringify(receipt, null, 2))
|
|
122
|
+
else console.log(`recorded ${receipt.observation.signal} for ${receipt.observation.workflowKey} as local observation ${receipt.observation.id}`)
|
|
123
|
+
return (process.exitCode = 0)
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error(error.message)
|
|
126
|
+
process.exit(1)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function runCandidates(argv) {
|
|
131
|
+
const options = parseOptions(argv, new Set(['all', 'json']))
|
|
132
|
+
try {
|
|
133
|
+
const report = buildSkillCandidates({ workspaceRoot: process.cwd(), includeInsufficient: Boolean(options.all) })
|
|
134
|
+
if (options.json) console.log(JSON.stringify(report, null, 2))
|
|
135
|
+
else if (!report.candidates.length) console.log('no skill candidates meet the evidence threshold')
|
|
136
|
+
else {
|
|
137
|
+
for (const candidate of report.candidates) {
|
|
138
|
+
console.log(`${candidate.status}: ${candidate.kind} ${candidate.workflowKey}${candidate.skill ? ` (${candidate.skill})` : ''} from ${candidate.evidenceCount} observation(s)`)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return (process.exitCode = 0)
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error(error.message)
|
|
144
|
+
process.exit(1)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function printPlan(plan, json) {
|
|
149
|
+
if (json) {
|
|
150
|
+
console.log(JSON.stringify(plan, null, 2))
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
console.log(`plan: ${plan.planDigest}`)
|
|
154
|
+
console.log(`target: ${plan.target.path}`)
|
|
155
|
+
for (const action of plan.actions) console.log(`${action.type}: ${action.name}`)
|
|
156
|
+
for (const blocker of plan.blockers) console.log(`blocker: ${blocker}`)
|
|
157
|
+
if (!plan.actions.length && !plan.blockers.length) console.log('no managed skill changes')
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function runSync(argv) {
|
|
161
|
+
const options = parseOptions(argv, new Set(['source', 'target', 'confirm', 'json', 'apply', 'dry-run']))
|
|
162
|
+
if (options.apply && options['dry-run']) usageError('--apply and --dry-run are mutually exclusive')
|
|
163
|
+
if (options.confirm && !options.apply) usageError('--confirm requires --apply')
|
|
164
|
+
if (options.apply && !options.confirm) usageError('--apply requires --confirm PLAN_DIGEST')
|
|
165
|
+
const settings = {
|
|
166
|
+
workspaceRoot: process.cwd(),
|
|
167
|
+
sourceRoot: options.source ? path.resolve(options.source) : undefined,
|
|
168
|
+
target: options.target ?? path.join('.agents', 'skills'),
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
if (options.apply) {
|
|
172
|
+
const result = applySkillSync({ ...settings, confirm: options.confirm })
|
|
173
|
+
if (options.json) console.log(JSON.stringify(result, null, 2))
|
|
174
|
+
else {
|
|
175
|
+
console.log(`applied: ${result.plan.planDigest}`)
|
|
176
|
+
for (const quarantined of result.quarantined) console.log(`quarantined: ${quarantined}`)
|
|
177
|
+
}
|
|
178
|
+
return (process.exitCode = 0)
|
|
179
|
+
}
|
|
180
|
+
const plan = planSkillSync(settings)
|
|
181
|
+
printPlan(plan, options.json)
|
|
182
|
+
return (process.exitCode = plan.applyAllowed ? 0 : 1)
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error(error.message)
|
|
185
|
+
process.exit(1)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const argv = process.argv.slice(2)
|
|
190
|
+
const subcommand = argv[0]
|
|
191
|
+
if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
|
|
192
|
+
console.log(USAGE)
|
|
193
|
+
process.exit(0)
|
|
194
|
+
}
|
|
195
|
+
if (subcommand === 'audit' || subcommand === undefined) runAudit(argv.slice(subcommand ? 1 : 0))
|
|
196
|
+
else if (subcommand === 'observe') runObserve(argv.slice(1))
|
|
197
|
+
else if (subcommand === 'candidates') runCandidates(argv.slice(1))
|
|
198
|
+
else if (subcommand === 'sync') runSync(argv.slice(1))
|
|
199
|
+
else usageError(`unknown skills subcommand: ${subcommand}`)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
enrollRepository,
|
|
4
|
+
executeUserConfirmedCommit,
|
|
5
|
+
obsidianMaintenanceNotice,
|
|
6
|
+
operationTrace,
|
|
7
|
+
planUserConfirmedCommit,
|
|
8
|
+
reconcileRepository,
|
|
9
|
+
runtimeStatus,
|
|
10
|
+
setRepositoryPaused,
|
|
11
|
+
} from '../runtime/supervisor.mjs'
|
|
12
|
+
import { firstString, parseArgs } from '../project/config.mjs'
|
|
13
|
+
|
|
14
|
+
function valuesFor(argv, name) {
|
|
15
|
+
const values = []
|
|
16
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
17
|
+
const arg = argv[index]
|
|
18
|
+
if (arg === `--${name}` && argv[index + 1] && !argv[index + 1].startsWith('--')) {
|
|
19
|
+
values.push(argv[index + 1])
|
|
20
|
+
index += 1
|
|
21
|
+
} else if (arg.startsWith(`--${name}=`)) {
|
|
22
|
+
values.push(arg.slice(name.length + 3))
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return values
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function integer(value, fallback) {
|
|
29
|
+
if (value == null || value === true) return fallback
|
|
30
|
+
const parsed = Number(value)
|
|
31
|
+
if (!Number.isInteger(parsed) || parsed < 1) throw new Error('interval and retry values must be positive integers')
|
|
32
|
+
return parsed
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function print(value) {
|
|
36
|
+
console.log(JSON.stringify(value, null, 2))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Status only gains a member when the enrolled project enables Obsidian maintenance; otherwise it is unchanged.
|
|
40
|
+
function withMaintenanceNotice(result) {
|
|
41
|
+
const notice = obsidianMaintenanceNotice({ repoPath })
|
|
42
|
+
return notice === null ? result : { ...result, obsidianMaintenance: notice }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function runLoop({ repoPath, intervalMs, fetchAttempts, once }) {
|
|
46
|
+
do {
|
|
47
|
+
const result = reconcileRepository({ repoPath, fetchAttempts })
|
|
48
|
+
print(result)
|
|
49
|
+
if (once) return result.ok ? 0 : 1
|
|
50
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs))
|
|
51
|
+
} while (true)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const argv = process.argv.slice(2)
|
|
55
|
+
const args = parseArgs(argv)
|
|
56
|
+
const subcommand = args._[0] || 'status'
|
|
57
|
+
const repoPath = firstString(args.repo) || process.cwd()
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
if (subcommand === 'enroll') {
|
|
61
|
+
const result = enrollRepository({ repoPath, projectConfig: firstString(args.project, args['project-config']), gitExecutable: firstString(args.git) })
|
|
62
|
+
print(result)
|
|
63
|
+
process.exitCode = result.ok ? 0 : 1
|
|
64
|
+
} else if (subcommand === 'status' || subcommand === 'audit') {
|
|
65
|
+
const result = runtimeStatus({ repoPath })
|
|
66
|
+
print(withMaintenanceNotice(result))
|
|
67
|
+
process.exitCode = result.ok ? 0 : 1
|
|
68
|
+
} else if (subcommand === 'reconcile') {
|
|
69
|
+
const result = reconcileRepository({ repoPath, fetchAttempts: integer(args.retries, 3) })
|
|
70
|
+
print(result)
|
|
71
|
+
process.exitCode = result.ok ? 0 : 1
|
|
72
|
+
} else if (subcommand === 'run') {
|
|
73
|
+
process.exitCode = await runLoop({
|
|
74
|
+
repoPath,
|
|
75
|
+
intervalMs: integer(args.interval, 30) * 1000,
|
|
76
|
+
fetchAttempts: integer(args.retries, 3),
|
|
77
|
+
once: Boolean(args.once),
|
|
78
|
+
})
|
|
79
|
+
} else if (subcommand === 'plan') {
|
|
80
|
+
print(planUserConfirmedCommit({
|
|
81
|
+
repoPath,
|
|
82
|
+
paths: valuesFor(argv, 'path'),
|
|
83
|
+
message: firstString(args.message),
|
|
84
|
+
publish: Boolean(args.publish),
|
|
85
|
+
fetchAttempts: integer(args.retries, 3),
|
|
86
|
+
}))
|
|
87
|
+
} else if (subcommand === 'commit') {
|
|
88
|
+
const result = executeUserConfirmedCommit({
|
|
89
|
+
repoPath,
|
|
90
|
+
operationId: firstString(args.operation),
|
|
91
|
+
confirmation: firstString(args.confirm),
|
|
92
|
+
})
|
|
93
|
+
print(result)
|
|
94
|
+
process.exitCode = result.ok ? 0 : 1
|
|
95
|
+
} else if (subcommand === 'pause' || subcommand === 'freeze') {
|
|
96
|
+
print(setRepositoryPaused({ repoPath, paused: true, reason: firstString(args.reason) || (subcommand === 'freeze' ? 'frozen by user' : 'paused by user') }))
|
|
97
|
+
} else if (subcommand === 'resume') {
|
|
98
|
+
print(setRepositoryPaused({ repoPath, paused: false }))
|
|
99
|
+
} else if (subcommand === 'trace') {
|
|
100
|
+
print({ ok: true, operations: operationTrace({ repoPath }) })
|
|
101
|
+
} else {
|
|
102
|
+
throw new Error(`unknown sync command: ${subcommand}`)
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(error.message)
|
|
106
|
+
process.exitCode = 1
|
|
107
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { parseDefinition, parsePlacement, need, isRef, deepFreeze } from './wire.mjs';
|
|
3
|
+
export const digestBytes = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
4
|
+
|
|
5
|
+
// A host-owned immutable snapshot, not a mutable catalogue or remote importer.
|
|
6
|
+
export function createRegistrySnapshot({ definitionBytes, rendererBytes, revision, lifecycle = 'active', allowDeprecated = false, validateProps, validateIntent }) {
|
|
7
|
+
const definition = deepFreeze(parseDefinition(definitionBytes));
|
|
8
|
+
need(isRef(revision) && ['active', 'deprecated', 'withdrawn', 'revoked'].includes(lifecycle));
|
|
9
|
+
need(typeof validateProps === 'function' && typeof validateIntent === 'function');
|
|
10
|
+
const definitionDigest = digestBytes(definitionBytes), rendererDigest = digestBytes(rendererBytes);
|
|
11
|
+
return Object.freeze({ definition, revision, definitionDigest, rendererDigest, lifecycle,
|
|
12
|
+
resolve({ sourceBytes, sourceRef, request, operationRef, serviceRef }) {
|
|
13
|
+
need(lifecycle === 'active' || (lifecycle === 'deprecated' && allowDeprecated));
|
|
14
|
+
const placement = deepFreeze(parsePlacement(sourceBytes));
|
|
15
|
+
need(isRef(sourceRef) && isRef(serviceRef) && isRef(operationRef));
|
|
16
|
+
need(placement.componentId === definition.componentId && placement.componentVersion === definition.version);
|
|
17
|
+
need(placement.placementId === request.placementId && digestBytes(sourceBytes) === request.sourceDigest);
|
|
18
|
+
need(definition.previewModes.includes(request.mode) && definition.projections.includes('web'));
|
|
19
|
+
need(validateProps(placement.props) === true);
|
|
20
|
+
need(placement.dataBindings.length === 1 && definition.dataContractRefs.includes(placement.dataBindings[0].contractRef));
|
|
21
|
+
if (request.intentRef) need(validateIntent(request.intentRef, request.payload) === true);
|
|
22
|
+
return deepFreeze({ resourceRef: placement.dataBindings[0].resourceRef, operationRef, serviceRef, sourceRef,
|
|
23
|
+
placementRef: placement.placementId, sourceDigest: request.sourceDigest, descriptorDigest: definitionDigest,
|
|
24
|
+
rendererDigest, registryRevision: revision, warning: lifecycle === 'deprecated' ? 'deprecated' : null });
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Opt-in experimental wire parser. No identity, URL resolution or execution.
|
|
2
|
+
export const MODES = Object.freeze(['synthetic', 'connected-read-only', 'sandbox-interactive']);
|
|
3
|
+
const REF = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,159}$/;
|
|
4
|
+
const HASH = /^[a-f0-9]{64}$/;
|
|
5
|
+
const VERSION = /^\d+\.\d+\.\d+$/;
|
|
6
|
+
const forbidden = new Set(['__proto__', 'constructor', 'prototype']);
|
|
7
|
+
const encoder = new TextEncoder();
|
|
8
|
+
const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
9
|
+
export function need(value, message = 'Invalid bounded composition data') { if (!value) throw new TypeError(message); }
|
|
10
|
+
export const isRef = value => typeof value === 'string' && REF.test(value);
|
|
11
|
+
export const isDigest = value => typeof value === 'string' && HASH.test(value);
|
|
12
|
+
export function closed(value, required, optional = []) {
|
|
13
|
+
need(value && typeof value === 'object' && !Array.isArray(value));
|
|
14
|
+
need(required.every(key => Object.hasOwn(value, key)) && Object.keys(value).every(key => required.includes(key) || optional.includes(key)));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// JSON.parse alone accepts duplicate keys. This small JSON grammar refuses them
|
|
18
|
+
// at every depth, before schema validation; it never evaluates source text.
|
|
19
|
+
export function parseBoundedJSON(input) {
|
|
20
|
+
need(typeof input === 'string' || input instanceof Uint8Array);
|
|
21
|
+
const bytes = typeof input === 'string' ? encoder.encode(input) : input;
|
|
22
|
+
need(bytes.byteLength <= 65536);
|
|
23
|
+
let text = decoder.decode(bytes);
|
|
24
|
+
if (typeof input === 'string') need(text === input, 'Lossy Unicode refused');
|
|
25
|
+
if (text.charCodeAt(0) === 0xfeff) text = text.slice(1);
|
|
26
|
+
let i = 0;
|
|
27
|
+
const ws = () => { while (/[\x20\t\r\n]/.test(text[i] || '\0')) i++; };
|
|
28
|
+
const string = () => {
|
|
29
|
+
const start = i++;
|
|
30
|
+
while (i < text.length) {
|
|
31
|
+
const c = text[i++];
|
|
32
|
+
if (c === '\\') { i++; continue; }
|
|
33
|
+
if (c === '"') {
|
|
34
|
+
const value = JSON.parse(text.slice(start, i));
|
|
35
|
+
need(decoder.decode(encoder.encode(value)) === value, 'Lossy Unicode refused');
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
throw new TypeError('Unterminated JSON string');
|
|
40
|
+
};
|
|
41
|
+
function value(depth) {
|
|
42
|
+
need(depth <= 8); ws();
|
|
43
|
+
if (text[i] === '"') return string();
|
|
44
|
+
if (text[i] === '{') {
|
|
45
|
+
i++; ws(); const result = Object.create(null);
|
|
46
|
+
if (text[i] === '}') { i++; return result; }
|
|
47
|
+
while (i < text.length) {
|
|
48
|
+
ws(); need(text[i] === '"'); const key = string();
|
|
49
|
+
need(!Object.hasOwn(result, key), 'Duplicate JSON key refused');
|
|
50
|
+
ws(); need(text[i++] === ':'); result[key] = value(depth + 1); ws();
|
|
51
|
+
if (text[i] === '}') { i++; return result; }
|
|
52
|
+
need(text[i++] === ',');
|
|
53
|
+
}
|
|
54
|
+
} else if (text[i] === '[') {
|
|
55
|
+
i++; ws(); const result = [];
|
|
56
|
+
if (text[i] === ']') { i++; return result; }
|
|
57
|
+
while (i < text.length) {
|
|
58
|
+
result.push(value(depth + 1)); ws();
|
|
59
|
+
if (text[i] === ']') { i++; return result; }
|
|
60
|
+
need(text[i++] === ',');
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
const match = /^(?:true|false|null|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)/.exec(text.slice(i));
|
|
64
|
+
need(match); i += match[0].length;
|
|
65
|
+
const result = JSON.parse(match[0]); need(typeof result !== 'number' || Number.isFinite(result)); return result;
|
|
66
|
+
}
|
|
67
|
+
throw new TypeError('Invalid JSON');
|
|
68
|
+
}
|
|
69
|
+
const result = value(0); ws(); need(i === text.length); return result;
|
|
70
|
+
}
|
|
71
|
+
export function scalarMap(value) {
|
|
72
|
+
need(value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length <= 64);
|
|
73
|
+
for (const [key, v] of Object.entries(value)) {
|
|
74
|
+
need(/^[a-z][a-zA-Z0-9_]{0,79}$/.test(key) && !forbidden.has(key));
|
|
75
|
+
need(v === null || typeof v === 'boolean' || (typeof v === 'number' && Number.isFinite(v)) || (typeof v === 'string' && v.length <= 4096));
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
function refs(values, maximum, allowed) {
|
|
80
|
+
need(Array.isArray(values) && values.length <= maximum && new Set(values).size === values.length);
|
|
81
|
+
need(values.every(v => allowed ? allowed.includes(v) : isRef(v)));
|
|
82
|
+
}
|
|
83
|
+
function version(v) { need(typeof v === 'string' && v.length <= 32 && VERSION.test(v)); }
|
|
84
|
+
export function parseDefinition(bytes) {
|
|
85
|
+
const v = parseBoundedJSON(bytes);
|
|
86
|
+
closed(v, ['schema', 'componentId', 'version', 'rendererRef', 'propsSchemaRef', 'dataContractRefs', 'projections', 'previewModes']);
|
|
87
|
+
need(v.schema === 'atelier-component-definition/proposal-v1');
|
|
88
|
+
for (const key of ['componentId', 'rendererRef', 'propsSchemaRef']) need(isRef(v[key]));
|
|
89
|
+
version(v.version); refs(v.dataContractRefs, 16); refs(v.projections, 4, ['web', 'native', 'desktop', 'document']); refs(v.previewModes, 3, MODES);
|
|
90
|
+
return v;
|
|
91
|
+
}
|
|
92
|
+
export function parsePlacement(bytes) {
|
|
93
|
+
const v = parseBoundedJSON(bytes);
|
|
94
|
+
closed(v, ['schema', 'placementId', 'componentId', 'componentVersion', 'slotRef', 'props', 'dataBindings']);
|
|
95
|
+
need(v.schema === 'atelier-component-placement/proposal-v1');
|
|
96
|
+
for (const key of ['placementId', 'componentId', 'slotRef']) need(isRef(v[key]));
|
|
97
|
+
version(v.componentVersion); scalarMap(v.props);
|
|
98
|
+
need(Array.isArray(v.dataBindings) && v.dataBindings.length <= 16);
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
for (const binding of v.dataBindings) {
|
|
101
|
+
closed(binding, ['contractRef', 'resourceRef']); need(isRef(binding.contractRef) && isRef(binding.resourceRef) && !seen.has(binding.contractRef)); seen.add(binding.contractRef);
|
|
102
|
+
}
|
|
103
|
+
return v;
|
|
104
|
+
}
|
|
105
|
+
export function parseRequest(bytes) {
|
|
106
|
+
const v = parseBoundedJSON(bytes);
|
|
107
|
+
const action = v?.schema === 'atelier-component-intent/proposal-v1';
|
|
108
|
+
closed(v, ['schema', 'status', 'requestId', 'placementId', 'sourceDigest', 'mode', ...(action ? ['intentRef', 'payload'] : [])]);
|
|
109
|
+
need(action || v.schema === 'atelier-preview-request/proposal-v1'); need(v.status === 'proposed' && MODES.includes(v.mode));
|
|
110
|
+
need(isRef(v.requestId) && isRef(v.placementId) && isDigest(v.sourceDigest));
|
|
111
|
+
if (action) { need(isRef(v.intentRef)); scalarMap(v.payload); need(v.mode === 'sandbox-interactive'); }
|
|
112
|
+
return v;
|
|
113
|
+
}
|
|
114
|
+
export function deepFreeze(value) {
|
|
115
|
+
if (value && typeof value === 'object') { for (const item of Object.values(value)) deepFreeze(item); Object.freeze(value); }
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
export function canonical(value) {
|
|
119
|
+
if (Array.isArray(value)) return '[' + value.map(canonical).join(',') + ']';
|
|
120
|
+
if (value && typeof value === 'object') return '{' + Object.keys(value).sort().map(key => JSON.stringify(key) + ':' + canonical(value[key])).join(',') + '}';
|
|
121
|
+
return JSON.stringify(value);
|
|
122
|
+
}
|
package/src/contracts/corpus.mjs
CHANGED
|
@@ -26,6 +26,31 @@ import { fileURLToPath } from 'node:url'
|
|
|
26
26
|
// document, not a schema, and is deliberately absent.
|
|
27
27
|
|
|
28
28
|
export const CONTRACT_CORPUS = [
|
|
29
|
+
{ name: 'atelier-decision-request', contractFile: 'contracts/atelier-decision-request.v1.schema.json', fixtureRoot: 'fixtures/decisions/request', registry: false },
|
|
30
|
+
{ name: 'atelier-decision-result', contractFile: 'contracts/atelier-decision-result.v1.schema.json', fixtureRoot: 'fixtures/decisions/result', registry: false },
|
|
31
|
+
{ name: 'atelier-adoption-policy-v1', contractFile: 'contracts/atelier-adoption-policy.v1.schema.json', fixtureRoot: 'fixtures/atelier-upgrade-transaction/adoption-policy', registry: false },
|
|
32
|
+
{ name: 'atelier-migration-v2', contractFile: 'contracts/atelier-migration.v2.schema.json', fixtureRoot: 'fixtures/atelier-upgrade-transaction/migration', registry: false },
|
|
33
|
+
{ name: 'atelier-upgrade-plan-v2', contractFile: 'contracts/atelier-upgrade-plan.v2.schema.json', fixtureRoot: 'fixtures/atelier-upgrade-transaction/upgrade-plan', registry: false },
|
|
34
|
+
{ name: 'atelier-upgrade-receipt-v1', contractFile: 'contracts/atelier-upgrade-receipt.v1.schema.json', fixtureRoot: 'fixtures/atelier-upgrade-transaction/upgrade-receipt', registry: false },
|
|
35
|
+
{ name: 'atelier-presentation', contractFile: 'contracts/atelier-presentation.v1.schema.json', validFiles: ['fixtures/ui/presentation/reference.v1.json'], registry: false },
|
|
36
|
+
{ name: 'atelier-pane-presentation', contractFile: 'contracts/atelier-pane-presentation.v1.schema.json', validFiles: ['fixtures/ui/presentation/pane.v1.json'], registry: false },
|
|
37
|
+
{ name: 'atelier-vault-publication', contractFile: 'contracts/atelier-vault-publication.v1.schema.json', fixtureRoot: 'fixtures/atelier-vault-publication', registry: true },
|
|
38
|
+
...['guide', 'intake'].map(kind => ({
|
|
39
|
+
name: `atelier-${kind}`, contractFile: `contracts/atelier-${kind}.v1.schema.json`,
|
|
40
|
+
fixtureRoot: `fixtures/atelier-${kind}`, registry: true,
|
|
41
|
+
})),
|
|
42
|
+
...[['audit', 'auditReport'], ['candidates', 'candidateReport'], ['plan', 'syncPlan'], ['lock', 'syncLock']].map(([kind, definition]) => ({
|
|
43
|
+
name: `atelier-skill-${kind}`, contractFile: 'contracts/atelier-skill-steward.v1.schema.json',
|
|
44
|
+
docPointer: `#/$defs/${definition}`, fixtureRoot: `fixtures/atelier-skill-steward/${kind}`, registry: true,
|
|
45
|
+
})),
|
|
46
|
+
{name: 'atelier-coauthor', contractFile: 'contracts/atelier-coauthor.v1.schema.json', fixtureRoot: 'fixtures/atelier-coauthor', registry: true},
|
|
47
|
+
{name: 'atelier-review', contractFile: 'contracts/atelier-review.v1.schema.json', fixtureRoot: 'fixtures/atelier-review', registry: false},
|
|
48
|
+
{
|
|
49
|
+
name: 'atelier-repository-observation',
|
|
50
|
+
contractFile: 'contracts/atelier-repository-observation.v1.schema.json',
|
|
51
|
+
fixtureRoot: 'fixtures/atelier-repository-observation',
|
|
52
|
+
registry: true,
|
|
53
|
+
},
|
|
29
54
|
{
|
|
30
55
|
name: 'atelier-lock',
|
|
31
56
|
contractFile: 'contracts/atelier-lock.v1.schema.json',
|
|
@@ -136,6 +161,16 @@ export const CONTRACT_CORPUS = [
|
|
|
136
161
|
invalidFiles: ['fixtures/analysis-adapter/invalid/manifest-hidden-provider.v1.json'],
|
|
137
162
|
registry: true,
|
|
138
163
|
},
|
|
164
|
+
// Obsidian projection contracts. test/obsidian-contract.test.mjs owns their
|
|
165
|
+
// positive and negative coverage (several refusals are semantic, beyond what
|
|
166
|
+
// a schema matcher can express), so they carry registry: false.
|
|
167
|
+
...[
|
|
168
|
+
'corpus-profile', 'scope', 'source-snapshot', 'generation-manifest', 'publication-journal', 'service-state',
|
|
169
|
+
'edit-operation', 'apply-policy', 'proposal-receipt', 'acceptance-receipt', 'ext-settings',
|
|
170
|
+
].map((shape) => ({
|
|
171
|
+
name: `atelier-obsidian-${shape}`, contractFile: `contracts/atelier-obsidian-${shape}.v1.schema.json`,
|
|
172
|
+
fixtureRoot: `fixtures/obsidian/contracts/${shape}`, registry: false,
|
|
173
|
+
})),
|
|
139
174
|
{
|
|
140
175
|
name: 'analysis-adapter-claim-output',
|
|
141
176
|
contractFile: 'contracts/analysis-adapter.v1.schema.json',
|