@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
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import crypto from 'node:crypto'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { canonicalize } from '../attestation/jcs.mjs'
|
|
6
|
+
import { buildGraph } from '../graph/graph.mjs'
|
|
7
|
+
import {
|
|
8
|
+
loadExtensionPacks,
|
|
9
|
+
createProtocolRegistry,
|
|
10
|
+
} from '../extension-packs/loader.mjs'
|
|
11
|
+
import { readRegularTextNoFollow } from '../project/private-state.mjs'
|
|
12
|
+
import { createCollaborationEventLedger } from '../collaboration/event-ledger.mjs'
|
|
13
|
+
import { inspectPackLifecycle } from '../extension-packs/lifecycle.mjs'
|
|
14
|
+
import { readLocalOverlay } from '../project/config.mjs'
|
|
15
|
+
import { readBoundedSource } from './source-read.mjs'
|
|
16
|
+
export { readBoundedSource } from './source-read.mjs'
|
|
17
|
+
import { validReviewArtifact } from '../collaboration/review-contracts.mjs'
|
|
18
|
+
import { buildReadinessRun, answerForField } from './runtime.mjs'
|
|
19
|
+
|
|
20
|
+
export const hashEvidence = (value) =>
|
|
21
|
+
`sha256:${crypto
|
|
22
|
+
.createHash('sha256')
|
|
23
|
+
.update(canonicalize(value))
|
|
24
|
+
.digest('hex')}`
|
|
25
|
+
const hashBytes = (value) =>
|
|
26
|
+
`sha256:${crypto.createHash('sha256').update(value).digest('hex')}`
|
|
27
|
+
const packageRoot = fileURLToPath(new URL('../../', import.meta.url))
|
|
28
|
+
function evaluatorInventory() {
|
|
29
|
+
const files = ['package.json']
|
|
30
|
+
function visit(relative) {
|
|
31
|
+
for (const name of fs
|
|
32
|
+
.readdirSync(path.join(packageRoot, relative))
|
|
33
|
+
.sort()) {
|
|
34
|
+
const next = path.posix.join(relative, name),
|
|
35
|
+
stat = fs.lstatSync(path.join(packageRoot, next))
|
|
36
|
+
if (stat.isSymbolicLink()) throw new Error('redirected evaluator input')
|
|
37
|
+
if (stat.isDirectory()) visit(next)
|
|
38
|
+
else if (stat.isFile() && /\.(mjs|json)$/.test(name)) files.push(next)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
visit('src')
|
|
42
|
+
visit('contracts')
|
|
43
|
+
return files
|
|
44
|
+
.sort()
|
|
45
|
+
.map((file) => ({
|
|
46
|
+
file,
|
|
47
|
+
digest: hashBytes(readRegularTextNoFollow(path.join(packageRoot, file))),
|
|
48
|
+
}))
|
|
49
|
+
}
|
|
50
|
+
const evaluator = hashEvidence(evaluatorInventory())
|
|
51
|
+
|
|
52
|
+
function currentInputs(project, protocolId, answers) {
|
|
53
|
+
if (
|
|
54
|
+
project.configPath &&
|
|
55
|
+
hashEvidence(JSON.parse(readRegularTextNoFollow(project.configPath))) !==
|
|
56
|
+
hashEvidence(project.config)
|
|
57
|
+
)
|
|
58
|
+
throw new Error('project config changed; resolve the project again')
|
|
59
|
+
if (hashEvidence(evaluatorInventory()) !== evaluator)
|
|
60
|
+
throw new Error('evaluator changed; restart before review')
|
|
61
|
+
if (
|
|
62
|
+
project.localOverlay &&
|
|
63
|
+
hashEvidence(readLocalOverlay({ configDir: project.configDir }).overlay) !==
|
|
64
|
+
hashEvidence(project.localOverlay.overlay)
|
|
65
|
+
)
|
|
66
|
+
throw new Error('local overlay changed; resolve again')
|
|
67
|
+
const loaded = loadExtensionPacks(project)
|
|
68
|
+
const packs = loaded.packs ?? loaded
|
|
69
|
+
const lifecycle = inspectPackLifecycle(project, packs)
|
|
70
|
+
if (!lifecycle.ok) throw new Error(lifecycle.errors.join('; '))
|
|
71
|
+
const registry = createProtocolRegistry({ packs })
|
|
72
|
+
const protocol = registry.protocolById(protocolId)
|
|
73
|
+
if (!protocol) throw new Error('protocol unavailable for evidence binding')
|
|
74
|
+
const graph = buildGraph(project)
|
|
75
|
+
if (graph.errors.length)
|
|
76
|
+
throw new Error('source graph is invalid; evidence capture refused')
|
|
77
|
+
if (graph.nodes.length > 256)
|
|
78
|
+
throw new Error('review evidence exceeds bounded source count')
|
|
79
|
+
const sources = [],
|
|
80
|
+
excerpts = new Map()
|
|
81
|
+
for (const node of graph.nodes) {
|
|
82
|
+
const repo = project.repos.find(
|
|
83
|
+
(entry) => entry.name === node.repo && !entry.external,
|
|
84
|
+
)
|
|
85
|
+
if (!repo?.path) throw new Error('source repository unavailable')
|
|
86
|
+
const source = readBoundedSource(repo.path, node.path)
|
|
87
|
+
const metadata = !/\.md$/i.test(node.path)
|
|
88
|
+
? readBoundedSource(repo.path, `${node.path}.kg.json`).digest
|
|
89
|
+
: null
|
|
90
|
+
excerpts.set(node.id, source.text.slice(0, 2000))
|
|
91
|
+
sources.push({
|
|
92
|
+
id: node.id,
|
|
93
|
+
repo: node.repo,
|
|
94
|
+
path: node.path,
|
|
95
|
+
audience: node.audience,
|
|
96
|
+
digest: source.digest,
|
|
97
|
+
metadata,
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
sources.sort((a, b) =>
|
|
101
|
+
`${a.repo}/${a.path}`.localeCompare(`${b.repo}/${b.path}`, 'en'),
|
|
102
|
+
)
|
|
103
|
+
const claimEvidence = protocol.claimMappings.map((mapping) => {
|
|
104
|
+
const raw = answerForField(answers, mapping.evidence)
|
|
105
|
+
const references = (Array.isArray(raw) ? raw.flat(Infinity) : [raw])
|
|
106
|
+
.filter((value) => typeof value === 'string' && value.trim())
|
|
107
|
+
.map((value) => value.trim())
|
|
108
|
+
const resolved = [],
|
|
109
|
+
unresolved = []
|
|
110
|
+
for (const reference of references) {
|
|
111
|
+
const source = sources.find((item) => item.id === reference)
|
|
112
|
+
if (source)
|
|
113
|
+
resolved.push({
|
|
114
|
+
id: source.id,
|
|
115
|
+
repo: source.repo,
|
|
116
|
+
path: source.path,
|
|
117
|
+
digest: source.digest,
|
|
118
|
+
excerpt: excerpts.get(source.id),
|
|
119
|
+
})
|
|
120
|
+
else unresolved.push(reference)
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
mappingId: mapping.id,
|
|
124
|
+
sourceFields: mapping.sourceFields,
|
|
125
|
+
answers: Object.fromEntries(
|
|
126
|
+
mapping.sourceFields.map((field) => [
|
|
127
|
+
field,
|
|
128
|
+
answerForField(answers, field) ?? null,
|
|
129
|
+
]),
|
|
130
|
+
),
|
|
131
|
+
evidenceField: mapping.evidence,
|
|
132
|
+
references,
|
|
133
|
+
resolved,
|
|
134
|
+
unresolved,
|
|
135
|
+
status: !references.length
|
|
136
|
+
? 'missing'
|
|
137
|
+
: unresolved.length
|
|
138
|
+
? 'unresolved'
|
|
139
|
+
: 'linked-source',
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
const readPolicy = (file) =>
|
|
143
|
+
fs.existsSync(file) ? hashBytes(readRegularTextNoFollow(file)) : null
|
|
144
|
+
return {
|
|
145
|
+
schema: 'atelier-review-evidence@v1',
|
|
146
|
+
protocolId,
|
|
147
|
+
protocol,
|
|
148
|
+
protocolDigest: hashEvidence(protocol),
|
|
149
|
+
answers,
|
|
150
|
+
answersDigest: hashEvidence(answers),
|
|
151
|
+
configDigest: hashEvidence(project.config),
|
|
152
|
+
lifecycleDigest: hashEvidence(lifecycle),
|
|
153
|
+
policyDigest: hashEvidence({
|
|
154
|
+
access: readPolicy(project.repoAccessPath),
|
|
155
|
+
boundary: readPolicy(project.boundaryPolicyPath),
|
|
156
|
+
}),
|
|
157
|
+
packs: packs
|
|
158
|
+
.map(({ id, version, digest }) => ({ id, version, digest }))
|
|
159
|
+
.sort((a, b) => a.id.localeCompare(b.id, 'en')),
|
|
160
|
+
evaluator: { id: 'answer-completeness/v1', digest: evaluator },
|
|
161
|
+
sources,
|
|
162
|
+
claimEvidence,
|
|
163
|
+
limitations: [
|
|
164
|
+
'Answers are operator assertions; source presence does not substantiate each claim.',
|
|
165
|
+
'No calibrated evidence confidence or runtime acceptance is established.',
|
|
166
|
+
],
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function captureReviewEvidence(project, protocolId, answers) {
|
|
171
|
+
const first = currentInputs(project, protocolId, answers)
|
|
172
|
+
const second = currentInputs(project, protocolId, answers)
|
|
173
|
+
const digest = hashEvidence(first)
|
|
174
|
+
if (digest !== hashEvidence(second))
|
|
175
|
+
throw new Error('review inputs changed during capture')
|
|
176
|
+
const snapshot = { ...first, digest }
|
|
177
|
+
if (!validReviewArtifact('evidence', snapshot))
|
|
178
|
+
throw new Error('evidence contract invalid or exceeds bounds')
|
|
179
|
+
return snapshot
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function evidenceLedger(project) {
|
|
183
|
+
return createCollaborationEventLedger({
|
|
184
|
+
workspaceRoot: project.configDir,
|
|
185
|
+
ledgerPath: path.join(
|
|
186
|
+
project.configDir,
|
|
187
|
+
'.atelier-local/review/evidence.ndjson',
|
|
188
|
+
),
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function recordReviewEvidence(project, run) {
|
|
193
|
+
const snapshot = captureReviewEvidence(project, run.protocolId, run.answers)
|
|
194
|
+
if (
|
|
195
|
+
hashEvidence(
|
|
196
|
+
buildReadinessRun({
|
|
197
|
+
project,
|
|
198
|
+
protocol: snapshot.protocol,
|
|
199
|
+
answers: run.answers,
|
|
200
|
+
createdAt: run.createdAt,
|
|
201
|
+
}),
|
|
202
|
+
) !== hashEvidence(run)
|
|
203
|
+
)
|
|
204
|
+
throw new Error('run no longer matches its current protocol and evaluator')
|
|
205
|
+
const payload = {
|
|
206
|
+
schema: 'atelier-bound-run@v1',
|
|
207
|
+
run,
|
|
208
|
+
runDigest: hashEvidence(run),
|
|
209
|
+
snapshot,
|
|
210
|
+
}
|
|
211
|
+
if (!validBoundRun(payload)) throw new Error('bound run contract invalid')
|
|
212
|
+
const ledger = evidenceLedger(project)
|
|
213
|
+
const existing = ledger.eventsFor(run.runId)
|
|
214
|
+
if (!existing.ok) throw new Error(existing.error)
|
|
215
|
+
if (existing.events.length) {
|
|
216
|
+
if (hashEvidence(existing.events[0].payload) !== hashEvidence(payload))
|
|
217
|
+
throw new Error('run identity already has different evidence')
|
|
218
|
+
return existing.events[0].payload
|
|
219
|
+
}
|
|
220
|
+
const result = ledger.append({
|
|
221
|
+
aggregateId: run.runId,
|
|
222
|
+
expectedVersion: 0,
|
|
223
|
+
type: 'run-bound',
|
|
224
|
+
actor: 'local-runtime',
|
|
225
|
+
payload,
|
|
226
|
+
})
|
|
227
|
+
if (!result.ok) throw new Error(result.error)
|
|
228
|
+
return payload
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function validBoundRun(payload) {
|
|
232
|
+
if (!validReviewArtifact('bound', payload)) return false
|
|
233
|
+
const { digest, ...body } = payload.snapshot
|
|
234
|
+
const { run, snapshot } = payload
|
|
235
|
+
return (
|
|
236
|
+
payload.runDigest === hashEvidence(run) &&
|
|
237
|
+
digest === hashEvidence(body) &&
|
|
238
|
+
snapshot.protocolDigest === hashEvidence(snapshot.protocol) &&
|
|
239
|
+
snapshot.answersDigest === hashEvidence(snapshot.answers) &&
|
|
240
|
+
snapshot.answersDigest === hashEvidence(run.answers) &&
|
|
241
|
+
snapshot.protocol.id === run.protocolId &&
|
|
242
|
+
snapshot.protocolId === run.protocolId &&
|
|
243
|
+
snapshot.claimEvidence.length === run.claims.length &&
|
|
244
|
+
snapshot.claimEvidence.every(
|
|
245
|
+
(entry, index) =>
|
|
246
|
+
entry.mappingId === snapshot.protocol.claimMappings[index]?.id,
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function loadBoundRun(project, runId) {
|
|
252
|
+
const result = evidenceLedger(project).eventsFor(runId)
|
|
253
|
+
if (!result.ok) return result
|
|
254
|
+
if (result.events.length !== 1 || result.events[0].type !== 'run-bound')
|
|
255
|
+
return {
|
|
256
|
+
ok: false,
|
|
257
|
+
status: 409,
|
|
258
|
+
error: 'run lacks immutable review evidence',
|
|
259
|
+
}
|
|
260
|
+
const payload = result.events[0].payload
|
|
261
|
+
const { digest, ...body } = payload.snapshot ?? {}
|
|
262
|
+
if (
|
|
263
|
+
!validBoundRun(payload) ||
|
|
264
|
+
payload.schema !== 'atelier-bound-run@v1' ||
|
|
265
|
+
payload.run?.runId !== runId ||
|
|
266
|
+
payload.runDigest !== hashEvidence(payload.run) ||
|
|
267
|
+
digest !== hashEvidence(body)
|
|
268
|
+
)
|
|
269
|
+
return { ok: false, status: 422, error: 'run evidence is invalid' }
|
|
270
|
+
return { ok: true, status: 200, ...payload }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function currentRunEligibility(project, bound) {
|
|
274
|
+
try {
|
|
275
|
+
return (
|
|
276
|
+
captureReviewEvidence(project, bound.run.protocolId, bound.run.answers)
|
|
277
|
+
.digest === bound.snapshot.digest
|
|
278
|
+
)
|
|
279
|
+
} catch {
|
|
280
|
+
return false
|
|
281
|
+
}
|
|
282
|
+
}
|
|
@@ -73,7 +73,7 @@ function nestedAnswer(value, parts) {
|
|
|
73
73
|
return nestedAnswer(value[head], rest)
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function answerForField(answers, fieldId) {
|
|
76
|
+
export function answerForField(answers, fieldId) {
|
|
77
77
|
for (const key of pathVariants(fieldId)) {
|
|
78
78
|
if (Object.hasOwn(answers, key)) return answers[key]
|
|
79
79
|
}
|
|
@@ -119,9 +119,8 @@ export function claimsForProtocolRun({ protocol, answers, projectName, runId, cr
|
|
|
119
119
|
provider: 'atelier-readiness',
|
|
120
120
|
status: 'proposed',
|
|
121
121
|
promoted: false,
|
|
122
|
-
confidence: Object.values(answers).some(answerPresent) ? 0.7 : 0.2,
|
|
123
122
|
evidence: [`readiness-run:${runId}`],
|
|
124
|
-
notes: [`Proposed ${mapping.id} from ${protocol.title}
|
|
123
|
+
notes: [`Proposed ${mapping.id} from ${protocol.title}.`, 'Answer presence is not evidence confidence; human judgment is required.'],
|
|
125
124
|
createdAt,
|
|
126
125
|
}
|
|
127
126
|
})
|
|
@@ -147,6 +146,7 @@ export function buildReadinessRun({ project, protocol, answers = {}, createdAt =
|
|
|
147
146
|
claims: claimsForProtocolRun({ protocol, answers: normalizedAnswers, projectName, runId, createdAt }),
|
|
148
147
|
safety: {
|
|
149
148
|
runtimeMutation: false,
|
|
149
|
+
runtimeImport: false,
|
|
150
150
|
canonicalWrites: false,
|
|
151
151
|
claimOnly: true,
|
|
152
152
|
storage: 'ignored-local',
|
|
@@ -367,6 +367,7 @@ export function buildTenantPacket(project, { runs = listProtocolRuns(project), r
|
|
|
367
367
|
ext: {
|
|
368
368
|
'mnstry.atelier': {
|
|
369
369
|
extensionPacks: packContribution,
|
|
370
|
+
scoreMeaning: 'Legacy readinessScore measures completed input dimensions; it is not evidence confidence or runtime admission.',
|
|
370
371
|
},
|
|
371
372
|
},
|
|
372
373
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import crypto from 'node:crypto'
|
|
4
|
+
import { openRegularFileNoFollow } from '../project/private-state.mjs'
|
|
5
|
+
const hashBytes = (value) =>
|
|
6
|
+
`sha256:${crypto.createHash('sha256').update(value).digest('hex')}`
|
|
7
|
+
|
|
8
|
+
export function readBoundedSource(root, relative) {
|
|
9
|
+
if (
|
|
10
|
+
typeof relative !== 'string' ||
|
|
11
|
+
path.isAbsolute(relative) ||
|
|
12
|
+
relative.split(/[\\/]/).some((part) => part === '..' || part === '.git')
|
|
13
|
+
)
|
|
14
|
+
throw new Error('source reference escapes declared boundary')
|
|
15
|
+
const realRoot = fs.realpathSync(root)
|
|
16
|
+
const file = path.resolve(realRoot, relative)
|
|
17
|
+
let cursor = realRoot
|
|
18
|
+
for (const part of path.relative(realRoot, file).split(path.sep)) {
|
|
19
|
+
cursor = path.join(cursor, part)
|
|
20
|
+
if (fs.lstatSync(cursor).isSymbolicLink())
|
|
21
|
+
throw new Error('redirected source reference refused')
|
|
22
|
+
}
|
|
23
|
+
const descriptor = openRegularFileNoFollow(file)
|
|
24
|
+
try {
|
|
25
|
+
const before = fs.fstatSync(descriptor)
|
|
26
|
+
if (before.size > 1024 * 1024)
|
|
27
|
+
throw new Error('source exceeds review byte bound')
|
|
28
|
+
const bytes = fs.readFileSync(descriptor)
|
|
29
|
+
const after = fs.fstatSync(descriptor)
|
|
30
|
+
if (before.size !== after.size || before.mtimeMs !== after.mtimeMs)
|
|
31
|
+
throw new Error('source changed during read')
|
|
32
|
+
return {
|
|
33
|
+
digest: hashBytes(bytes),
|
|
34
|
+
text: new TextDecoder('utf-8', { fatal: true }).decode(bytes),
|
|
35
|
+
}
|
|
36
|
+
} finally {
|
|
37
|
+
fs.closeSync(descriptor)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 30_000
|
|
6
|
+
const DEFAULT_MAX_BUFFER = 16 * 1024 * 1024
|
|
7
|
+
|
|
8
|
+
function executableNames(platform = process.platform, env = process.env) {
|
|
9
|
+
if (platform !== 'win32') return ['git']
|
|
10
|
+
const extensions = String(env.PATHEXT || '.EXE;.CMD;.BAT;.COM')
|
|
11
|
+
.split(';')
|
|
12
|
+
.map((value) => value.trim().toLowerCase())
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
return ['git.exe', ...extensions.map((extension) => `git${extension}`).filter((name) => name !== 'git.exe')]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isExecutable(file, platform = process.platform) {
|
|
18
|
+
try {
|
|
19
|
+
fs.accessSync(file, platform === 'win32' ? fs.constants.F_OK : fs.constants.X_OK)
|
|
20
|
+
return fs.statSync(file).isFile()
|
|
21
|
+
} catch {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function canonicalExecutable(file) {
|
|
27
|
+
try {
|
|
28
|
+
return fs.realpathSync.native(file)
|
|
29
|
+
} catch {
|
|
30
|
+
return fs.realpathSync(file)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolve one exact Git executable without invoking a shell. The selected path
|
|
36
|
+
* is carried through every later operation so PATH changes cannot swap engines
|
|
37
|
+
* underneath a running supervisor.
|
|
38
|
+
*/
|
|
39
|
+
export function resolveGitExecutable({ env = process.env, platform = process.platform } = {}) {
|
|
40
|
+
const configured = String(env.ATELIER_GIT_PATH || '').trim()
|
|
41
|
+
if (configured) {
|
|
42
|
+
if (!path.isAbsolute(configured)) throw new Error('ATELIER_GIT_PATH must be an absolute path')
|
|
43
|
+
if (!isExecutable(configured, platform)) throw new Error(`configured Git executable is unavailable: ${configured}`)
|
|
44
|
+
return canonicalExecutable(configured)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const searchPath = String(env.PATH || '')
|
|
48
|
+
const delimiter = platform === 'win32' ? ';' : path.delimiter
|
|
49
|
+
const names = executableNames(platform, env)
|
|
50
|
+
for (const directory of searchPath.split(delimiter).filter(Boolean)) {
|
|
51
|
+
const cleanDir = directory.replace(/^"|"$/g, '')
|
|
52
|
+
for (const name of names) {
|
|
53
|
+
const candidate = path.join(cleanDir, name)
|
|
54
|
+
if (isExecutable(candidate, platform)) return canonicalExecutable(candidate)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
throw new Error('compatible system Git was not found on PATH')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function sanitizedGitEnvironment(env = process.env, allowPrompt = false) {
|
|
61
|
+
const next = { ...env }
|
|
62
|
+
for (const key of Object.keys(next)) {
|
|
63
|
+
if (key.toUpperCase().startsWith('GIT_')) delete next[key]
|
|
64
|
+
}
|
|
65
|
+
if (!allowPrompt) next.GIT_TERMINAL_PROMPT = '0'
|
|
66
|
+
next.GIT_OPTIONAL_LOCKS = next.GIT_OPTIONAL_LOCKS || '1'
|
|
67
|
+
next.GIT_NO_REPLACE_OBJECTS = '1'
|
|
68
|
+
return next
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function redactGitDiagnostic(value) {
|
|
72
|
+
return String(value || '')
|
|
73
|
+
.replace(/https?:\/\/[^\s]+/gi, (url) => sanitizeRemoteUrl(url))
|
|
74
|
+
.replace(/\b((?:password|passwd|token|secret|authorization|proxy-authorization|extraheader)\s*[=:]\s*)[^\s]+/gi, '$1[redacted]')
|
|
75
|
+
.replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, '$1 [redacted]')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class GitCommandError extends Error {
|
|
79
|
+
constructor(message, result) {
|
|
80
|
+
super(message)
|
|
81
|
+
this.name = 'GitCommandError'
|
|
82
|
+
this.result = result
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function runGit(gitExecutable, repoRoot, args, {
|
|
87
|
+
allowFailure = false,
|
|
88
|
+
allowPrompt = false,
|
|
89
|
+
env = process.env,
|
|
90
|
+
input = undefined,
|
|
91
|
+
timeout = DEFAULT_TIMEOUT_MS,
|
|
92
|
+
} = {}) {
|
|
93
|
+
if (!path.isAbsolute(gitExecutable)) throw new Error('Git executable must be an absolute path')
|
|
94
|
+
if (!Array.isArray(args) || args.some((value) => typeof value !== 'string')) {
|
|
95
|
+
throw new Error('Git arguments must be a string array')
|
|
96
|
+
}
|
|
97
|
+
const argv = repoRoot ? ['-C', repoRoot, ...args] : args
|
|
98
|
+
const child = spawnSync(gitExecutable, argv, {
|
|
99
|
+
encoding: 'utf8',
|
|
100
|
+
env: sanitizedGitEnvironment(env, allowPrompt),
|
|
101
|
+
input,
|
|
102
|
+
maxBuffer: DEFAULT_MAX_BUFFER,
|
|
103
|
+
shell: false,
|
|
104
|
+
timeout,
|
|
105
|
+
windowsHide: true,
|
|
106
|
+
})
|
|
107
|
+
const result = {
|
|
108
|
+
ok: !child.error && child.status === 0,
|
|
109
|
+
status: child.status,
|
|
110
|
+
signal: child.signal ?? null,
|
|
111
|
+
stdout: child.stdout || '',
|
|
112
|
+
stderr: redactGitDiagnostic(child.stderr || ''),
|
|
113
|
+
error: child.error?.message ? redactGitDiagnostic(child.error.message) : null,
|
|
114
|
+
executable: gitExecutable,
|
|
115
|
+
args: argv,
|
|
116
|
+
}
|
|
117
|
+
if (!result.ok && !allowFailure) {
|
|
118
|
+
const detail = result.error || result.stderr.trim() || `exit ${String(result.status)}`
|
|
119
|
+
throw new GitCommandError(`Git ${args[0] || 'command'} failed: ${detail}`, result)
|
|
120
|
+
}
|
|
121
|
+
return result
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function gitText(gitExecutable, repoRoot, args, options = {}) {
|
|
125
|
+
return runGit(gitExecutable, repoRoot, args, options).stdout.trim()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function parseGitVersion(text) {
|
|
129
|
+
const match = String(text || '').match(/git version (\d+)\.(\d+)\.(\d+)/i)
|
|
130
|
+
if (!match) return null
|
|
131
|
+
return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]), text: match[0] }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function isSupportedGitVersion(version) {
|
|
135
|
+
return Boolean(version && (version.major > 2 || (version.major === 2 && version.minor >= 40)))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function inspectGitEngine(gitExecutable, { env = process.env } = {}) {
|
|
139
|
+
const result = runGit(gitExecutable, null, ['--version'], { env })
|
|
140
|
+
const version = parseGitVersion(result.stdout)
|
|
141
|
+
if (!version) throw new Error(`unrecognized Git version output from ${gitExecutable}`)
|
|
142
|
+
return {
|
|
143
|
+
executable: gitExecutable,
|
|
144
|
+
version: `${version.major}.${version.minor}.${version.patch}`,
|
|
145
|
+
supported: isSupportedGitVersion(version),
|
|
146
|
+
minimum: '2.40.0',
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function classifyRemoteAuthentication(url) {
|
|
151
|
+
const value = String(url || '').trim()
|
|
152
|
+
if (!value) return 'none'
|
|
153
|
+
if (/^(?:ssh:\/\/|[^/@\s]+@[^:/\s]+:)/i.test(value)) return 'ssh'
|
|
154
|
+
if (/^https?:\/\//i.test(value)) return 'https'
|
|
155
|
+
if (/^(?:file:\/\/|\.{0,2}[\\/]|[a-z]:[\\/]|[\\/])/i.test(value)) return 'local'
|
|
156
|
+
return 'unknown'
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Remote URLs are evidence, not authentication material. Git may carry HTTP
|
|
161
|
+
* credentials in user-info or query fragments, so local observations retain
|
|
162
|
+
* only the provider/repository address needed for diagnosis.
|
|
163
|
+
*/
|
|
164
|
+
export function sanitizeRemoteUrl(url) {
|
|
165
|
+
const value = String(url || '').trim()
|
|
166
|
+
const scpLike = value.match(/^[^/@\s]+@([^:/\s]+:.+)$/)
|
|
167
|
+
if (scpLike) return scpLike[1]
|
|
168
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) return value
|
|
169
|
+
try {
|
|
170
|
+
const parsed = new URL(value)
|
|
171
|
+
parsed.username = ''
|
|
172
|
+
Reflect.set(parsed, 'password', '')
|
|
173
|
+
parsed.search = ''
|
|
174
|
+
parsed.hash = ''
|
|
175
|
+
return parsed.toString()
|
|
176
|
+
} catch {
|
|
177
|
+
return value.replace(/^((?:https?|ssh):\/\/)(?:[^/@:]+:)?[^/@]+@/i, '$1')
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function parseNullConfig(text) {
|
|
182
|
+
const entries = []
|
|
183
|
+
for (const record of String(text || '').split('\0').filter(Boolean)) {
|
|
184
|
+
const newline = record.indexOf('\n')
|
|
185
|
+
if (newline === -1) entries.push({ key: record, value: '' })
|
|
186
|
+
else entries.push({ key: record.slice(0, newline), value: record.slice(newline + 1) })
|
|
187
|
+
}
|
|
188
|
+
return entries
|
|
189
|
+
}
|