openplanr 3.3.0 → 3.4.0
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 +52 -0
- package/dist/cli/commands/artifact-design-share.d.ts +9 -0
- package/dist/cli/commands/artifact-design-share.d.ts.map +1 -0
- package/dist/cli/commands/artifact-design-share.js +200 -0
- package/dist/cli/commands/artifact-design-share.js.map +1 -0
- package/dist/cli/commands/artifact.d.ts.map +1 -1
- package/dist/cli/commands/artifact.js +5 -18
- package/dist/cli/commands/artifact.js.map +1 -1
- package/dist/cli/commands/ship.d.ts.map +1 -1
- package/dist/cli/commands/ship.js +2 -0
- package/dist/cli/commands/ship.js.map +1 -1
- package/dist/dashboard/.vite/manifest.json +2 -2
- package/dist/dashboard/assets/index-Cv4Vwhuw.css +2 -0
- package/dist/dashboard/assets/index-CvQAlBy3.js +75 -0
- package/dist/dashboard/assets/index-CvQAlBy3.js.map +1 -0
- package/dist/dashboard/dashboard-manifest.json +14 -14
- package/dist/dashboard/index.html +2 -2
- package/dist/services/design-artifact-service.d.ts +20 -1
- package/dist/services/design-artifact-service.d.ts.map +1 -1
- package/dist/services/design-artifact-service.js +137 -18
- package/dist/services/design-artifact-service.js.map +1 -1
- package/dist/services/ship-checks/contract.d.ts +3 -1
- package/dist/services/ship-checks/contract.d.ts.map +1 -1
- package/dist/services/ship-checks/contract.js +1 -1
- package/dist/services/ship-checks/contract.js.map +1 -1
- package/dist/services/ship-checks/discover.d.ts.map +1 -1
- package/dist/services/ship-checks/discover.js +2 -1
- package/dist/services/ship-checks/discover.js.map +1 -1
- package/dist/services/ship-checks/package-contents.d.ts +4 -0
- package/dist/services/ship-checks/package-contents.d.ts.map +1 -0
- package/dist/services/ship-checks/package-contents.js +91 -0
- package/dist/services/ship-checks/package-contents.js.map +1 -0
- package/dist/services/ship-checks/packages.d.ts +2 -0
- package/dist/services/ship-checks/packages.d.ts.map +1 -1
- package/dist/services/ship-checks/packages.js +4 -0
- package/dist/services/ship-checks/packages.js.map +1 -1
- package/dist/services/ship-checks/scope.js +2 -1
- package/dist/services/ship-checks/scope.js.map +1 -1
- package/dist/utils/splice-managed-block.js +1 -1
- package/dist/utils/splice-managed-block.js.map +1 -1
- package/docs/CLI.md +38 -1
- package/lib/host-packages/adapter-registry.json +5 -5
- package/lib/host-packages/capability-map.json +1 -1
- package/lib/host-packages/claude/.claude-plugin/marketplace.json +3 -3
- package/lib/host-packages/claude/openplanr/.claude-plugin/plugin.json +6 -6
- package/lib/host-packages/claude/openplanr/.openplanr-content.json +67 -123
- package/lib/host-packages/claude/openplanr/README.md +12 -12
- package/lib/host-packages/claude/openplanr/references/agents/shared/modes/shared/task-context-resolution.md +4 -4
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-artifact-shell.mjs +2 -4
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-document.mjs +26 -26
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-handoff.mjs +850 -56
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-review-state-store.mjs +45 -0
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-review.mjs +65 -74
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-runtime-home.mjs +0 -1
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-share-cli-bridge.mjs +321 -0
- package/lib/host-packages/{openai/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-3d9b1a86.mjs → claude/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs} +762 -223
- package/lib/host-packages/{openai/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-ea2cd15e.mjs → claude/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-9f6918b0.mjs} +537 -5
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-design-support-0bac378d.mjs +137 -0
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design.mjs +107 -37
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -27
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +138 -133
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +1 -5684
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-artifact-review-ui.js +2568 -0
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +10 -74
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/package.json +3 -3
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +8 -2
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/claude/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +545 -4781
- package/lib/host-packages/claude/openplanr/runtime/plan/scripts/design.mjs +219 -452
- package/lib/host-packages/claude/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/claude/openplanr/skills/delegate/SKILL.md +2 -1
- package/lib/host-packages/claude/openplanr/skills/delegate/references/integration-review.md +26 -20
- package/lib/host-packages/claude/openplanr/skills/delegate/references/operator-guide.md +25 -1
- package/lib/host-packages/claude/openplanr/skills/delegate/references/run-handoff.md +2 -2
- package/lib/host-packages/claude/openplanr/skills/delegate/references/worktree-custody.md +17 -9
- package/lib/host-packages/claude/openplanr/skills/delegate/scripts/adapters/claude.mjs +15 -3
- package/lib/host-packages/claude/openplanr/skills/delegate/scripts/adapters/codex.mjs +38 -2
- package/lib/host-packages/claude/openplanr/skills/delegate/scripts/adapters/native.mjs +5 -0
- package/lib/host-packages/claude/openplanr/skills/delegate/scripts/custody.mjs +217 -10
- package/lib/host-packages/claude/openplanr/skills/delegate/scripts/handoff.mjs +1080 -0
- package/lib/host-packages/claude/openplanr/skills/delegate/scripts/integrate.mjs +96 -201
- package/lib/host-packages/claude/openplanr/skills/delegate/scripts/integration-files.mjs +3 -382
- package/lib/host-packages/claude/openplanr/skills/delegate/scripts/presentation.mjs +29 -12
- package/lib/host-packages/claude/openplanr/skills/delegate/scripts/run-contract.mjs +11 -0
- package/lib/host-packages/claude/openplanr/skills/delegate/scripts/run-execution.mjs +1 -1
- package/lib/host-packages/claude/openplanr/skills/delegate/scripts/run-lifecycle.mjs +14 -10
- package/lib/host-packages/claude/openplanr/skills/delegate/scripts/run-preparation.mjs +4 -0
- package/lib/host-packages/claude/openplanr/skills/delegate/scripts/run-record.mjs +2 -2
- package/lib/host-packages/claude/openplanr/skills/design/references/utilities.md +14 -8
- package/lib/host-packages/claude/openplanr/skills/design-loop/references/utilities.md +14 -8
- package/lib/host-packages/claude/openplanr/skills/design-review/references/utilities.md +14 -8
- package/lib/host-packages/claude/openplanr/skills/investigate/SKILL.md +1 -0
- package/lib/host-packages/claude/openplanr/skills/investigate/references/fail-first.md +40 -0
- package/lib/host-packages/claude/openplanr/skills/ship/SKILL.md +22 -10
- package/lib/host-packages/claude/openplanr/skills/ship/references/fail-first.md +40 -0
- package/lib/host-packages/claude/openplanr/skills/ship/references/result-contract.md +7 -3
- package/lib/host-packages/cursor/openplanr/.openplanr-content.json +65 -121
- package/lib/host-packages/cursor/openplanr/manifest.json +1 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/references/integration-review.md +26 -20
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/references/operator-guide.md +25 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/references/run-handoff.md +2 -2
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/references/worktree-custody.md +17 -9
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/adapters/claude.mjs +15 -3
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/adapters/codex.mjs +38 -2
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/adapters/native.mjs +5 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/custody.mjs +217 -10
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/handoff.mjs +1080 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/integrate.mjs +96 -201
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/integration-files.mjs +3 -382
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/presentation.mjs +29 -12
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/run-contract.mjs +11 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/run-execution.mjs +1 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/run-lifecycle.mjs +14 -10
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/run-preparation.mjs +4 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/run-record.mjs +2 -2
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate.mdc +2 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-design/references/utilities.md +14 -8
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/utilities.md +14 -8
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/utilities.md +14 -8
- package/lib/host-packages/cursor/openplanr/rules/planr-investigate/references/fail-first.md +40 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-investigate.mdc +1 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-ship/references/fail-first.md +40 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-ship/references/result-contract.md +7 -3
- package/lib/host-packages/cursor/openplanr/rules/planr-ship.mdc +22 -10
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-artifact-shell.mjs +2 -4
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-document.mjs +26 -26
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-handoff.mjs +850 -56
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-review-state-store.mjs +45 -0
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-review.mjs +65 -74
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-runtime-home.mjs +0 -1
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-share-cli-bridge.mjs +321 -0
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/{design-shared-artifact-support-dependencies-design-support-protocol-contracts-3d9b1a86.mjs → design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs} +762 -223
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/{design-shared-artifact-support-protocol-contracts-ea2cd15e.mjs → design-shared-artifact-support-protocol-contracts-9f6918b0.mjs} +537 -5
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-design-support-0bac378d.mjs +137 -0
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design.mjs +107 -37
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -27
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +138 -133
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +1 -5684
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-artifact-review-ui.js +2568 -0
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +10 -74
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/package.json +3 -3
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +8 -2
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +545 -4781
- package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/design.mjs +219 -452
- package/lib/host-packages/cursor/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/openai/.claude-plugin/marketplace.json +3 -3
- package/lib/host-packages/openai/openplanr/.codex-plugin/plugin.json +2 -2
- package/lib/host-packages/openai/openplanr/.openplanr-content.json +68 -124
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-artifact-shell.mjs +2 -4
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-document.mjs +26 -26
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-handoff.mjs +850 -56
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-review-state-store.mjs +45 -0
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-review.mjs +65 -74
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-runtime-home.mjs +0 -1
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-share-cli-bridge.mjs +321 -0
- package/lib/host-packages/{claude/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-3d9b1a86.mjs → openai/openplanr/runtime/design/scripts/design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs} +762 -223
- package/lib/host-packages/{claude/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-ea2cd15e.mjs → openai/openplanr/runtime/design/scripts/design-shared-artifact-support-protocol-contracts-9f6918b0.mjs} +537 -5
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-design-support-0bac378d.mjs +137 -0
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design.mjs +107 -37
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/notices/THIRD_PARTY_NOTICES.txt +0 -27
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/02-artifact-review-ui.js +138 -133
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/03-protocol-contracts.js +1 -5684
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-artifact-review-ui.js +2568 -0
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources.json +10 -74
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/package.json +3 -3
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/design/templates/studio/share.js +8 -2
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/openai/openplanr/runtime/plan/scripts/design-protocol-contracts.mjs +545 -4781
- package/lib/host-packages/openai/openplanr/runtime/plan/scripts/design.mjs +219 -452
- package/lib/host-packages/openai/openplanr/runtime/plan/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/openai/openplanr/skills/delegate/SKILL.md +2 -1
- package/lib/host-packages/openai/openplanr/skills/delegate/openplanr.skill.json +1 -1
- package/lib/host-packages/openai/openplanr/skills/delegate/references/integration-review.md +26 -20
- package/lib/host-packages/openai/openplanr/skills/delegate/references/operator-guide.md +25 -1
- package/lib/host-packages/openai/openplanr/skills/delegate/references/run-handoff.md +2 -2
- package/lib/host-packages/openai/openplanr/skills/delegate/references/worktree-custody.md +17 -9
- package/lib/host-packages/openai/openplanr/skills/delegate/scripts/adapters/claude.mjs +15 -3
- package/lib/host-packages/openai/openplanr/skills/delegate/scripts/adapters/codex.mjs +38 -2
- package/lib/host-packages/openai/openplanr/skills/delegate/scripts/adapters/native.mjs +5 -0
- package/lib/host-packages/openai/openplanr/skills/delegate/scripts/custody.mjs +217 -10
- package/lib/host-packages/openai/openplanr/skills/delegate/scripts/handoff.mjs +1080 -0
- package/lib/host-packages/openai/openplanr/skills/delegate/scripts/integrate.mjs +96 -201
- package/lib/host-packages/openai/openplanr/skills/delegate/scripts/integration-files.mjs +3 -382
- package/lib/host-packages/openai/openplanr/skills/delegate/scripts/presentation.mjs +29 -12
- package/lib/host-packages/openai/openplanr/skills/delegate/scripts/run-contract.mjs +11 -0
- package/lib/host-packages/openai/openplanr/skills/delegate/scripts/run-execution.mjs +1 -1
- package/lib/host-packages/openai/openplanr/skills/delegate/scripts/run-lifecycle.mjs +14 -10
- package/lib/host-packages/openai/openplanr/skills/delegate/scripts/run-preparation.mjs +4 -0
- package/lib/host-packages/openai/openplanr/skills/delegate/scripts/run-record.mjs +2 -2
- package/lib/host-packages/openai/openplanr/skills/design/references/utilities.md +14 -8
- package/lib/host-packages/openai/openplanr/skills/design-loop/references/utilities.md +14 -8
- package/lib/host-packages/openai/openplanr/skills/design-review/references/utilities.md +14 -8
- package/lib/host-packages/openai/openplanr/skills/investigate/SKILL.md +1 -0
- package/lib/host-packages/openai/openplanr/skills/investigate/openplanr.skill.json +12 -1
- package/lib/host-packages/openai/openplanr/skills/investigate/references/fail-first.md +40 -0
- package/lib/host-packages/openai/openplanr/skills/ship/SKILL.md +22 -10
- package/lib/host-packages/openai/openplanr/skills/ship/openplanr.skill.json +12 -1
- package/lib/host-packages/openai/openplanr/skills/ship/references/fail-first.md +40 -0
- package/lib/host-packages/openai/openplanr/skills/ship/references/result-contract.md +7 -3
- package/package.json +8 -8
- package/dist/dashboard/assets/index-BU5fzjyz.css +0 -1
- package/dist/dashboard/assets/index-B_96uzCl.js +0 -572
- package/dist/dashboard/assets/index-B_96uzCl.js.map +0 -1
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-chunked-workspace-client.mjs +0 -555
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-compression.mjs +0 -4180
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-encrypted-workspace-client.mjs +0 -647
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-owner-custody.mjs +0 -129
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-resource-pack.mjs +0 -698
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-runtime-support-25485dbf1cb2.mjs +0 -24
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-share.mjs +0 -1993
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-protocol-contracts-75a938cc.mjs +0 -222
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-shared-protocol-contracts-b5f28f94.mjs +0 -5907
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/design-upload-spool.mjs +0 -198
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-compression.js +0 -4175
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/05-artifact-sharing.js +0 -65
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/06-artifact-review-ui.js +0 -1755
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/07-artifact-sharing.js +0 -62
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/08-artifact-review-ui.js +0 -2633
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-paste.schema.json +0 -49
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-review-workspace.schema.json +0 -108
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-create.schema.json +0 -170
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-event.schema.json +0 -97
- package/lib/host-packages/claude/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-revision.schema.json +0 -56
- package/lib/host-packages/claude/openplanr/skills/delegate/scripts/integration-transaction.mjs +0 -958
- package/lib/host-packages/cursor/openplanr/rules/planr-delegate/scripts/integration-transaction.mjs +0 -958
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-chunked-workspace-client.mjs +0 -555
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-compression.mjs +0 -4180
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-encrypted-workspace-client.mjs +0 -647
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-owner-custody.mjs +0 -129
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-resource-pack.mjs +0 -698
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-runtime-support-25485dbf1cb2.mjs +0 -24
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-share.mjs +0 -1993
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-protocol-contracts-75a938cc.mjs +0 -222
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-shared-protocol-contracts-b5f28f94.mjs +0 -5907
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/design-upload-spool.mjs +0 -198
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-compression.js +0 -4175
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/05-artifact-sharing.js +0 -65
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/06-artifact-review-ui.js +0 -1755
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/07-artifact-sharing.js +0 -62
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/08-artifact-review-ui.js +0 -2633
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-paste.schema.json +0 -49
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-review-workspace.schema.json +0 -108
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-create.schema.json +0 -170
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-event.schema.json +0 -97
- package/lib/host-packages/cursor/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-revision.schema.json +0 -56
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-chunked-workspace-client.mjs +0 -555
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-compression.mjs +0 -4180
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-encrypted-workspace-client.mjs +0 -647
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-owner-custody.mjs +0 -129
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-resource-pack.mjs +0 -698
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-runtime-support-25485dbf1cb2.mjs +0 -24
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-share.mjs +0 -1993
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-protocol-contracts-75a938cc.mjs +0 -222
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-shared-protocol-contracts-b5f28f94.mjs +0 -5907
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/design-upload-spool.mjs +0 -198
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/04-compression.js +0 -4175
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/05-artifact-sharing.js +0 -65
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/06-artifact-review-ui.js +0 -1755
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/07-artifact-sharing.js +0 -62
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/artifact/templates/artifact-review-stage.js.sources/08-artifact-review-ui.js +0 -2633
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.1.0/artifact-paste.schema.json +0 -49
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-review-workspace.schema.json +0 -108
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-create.schema.json +0 -170
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-event.schema.json +0 -97
- package/lib/host-packages/openai/openplanr/runtime/design/scripts/runtime/packages/protocol/schemas/v1.9.0/design-workspace-revision.schema.json +0 -56
- package/lib/host-packages/openai/openplanr/skills/delegate/scripts/integration-transaction.mjs +0 -958
|
@@ -1,1993 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
assertDesignReviewBundle,
|
|
3
|
-
assertDesignReviewMetadata,
|
|
4
|
-
atomicJson,
|
|
5
|
-
bundleDesignRevision,
|
|
6
|
-
currentDesign,
|
|
7
|
-
hash,
|
|
8
|
-
readJson
|
|
9
|
-
} from "./design-shared-artifact-support-dependencies-design-support-protocol-contracts-3d9b1a86.mjs";
|
|
10
|
-
import {
|
|
11
|
-
createEncryptedWorkspaceClient,
|
|
12
|
-
createWorkspaceAddress
|
|
13
|
-
} from "./design-encrypted-workspace-client.mjs";
|
|
14
|
-
import {
|
|
15
|
-
copyUploadSpool,
|
|
16
|
-
persistUploadSpool,
|
|
17
|
-
spoolChunkReader
|
|
18
|
-
} from "./design-upload-spool.mjs";
|
|
19
|
-
import {
|
|
20
|
-
createChunkedWorkspaceClient
|
|
21
|
-
} from "./design-chunked-workspace-client.mjs";
|
|
22
|
-
import {
|
|
23
|
-
PLANNING_FOLDER,
|
|
24
|
-
ensurePrivateDirectory,
|
|
25
|
-
readCustody,
|
|
26
|
-
writeCustody
|
|
27
|
-
} from "./design-owner-custody.mjs";
|
|
28
|
-
import {
|
|
29
|
-
configuredPlanrHome,
|
|
30
|
-
planrHome
|
|
31
|
-
} from "./design-runtime-home.mjs";
|
|
32
|
-
import {
|
|
33
|
-
acquireStartLock
|
|
34
|
-
} from "./design-loopback-server.mjs";
|
|
35
|
-
import {
|
|
36
|
-
ARTIFACT_ERROR_CODES,
|
|
37
|
-
PipelineError,
|
|
38
|
-
canonicalSerialize,
|
|
39
|
-
digestArtifactEnvelope,
|
|
40
|
-
validateArtifactEnvelope,
|
|
41
|
-
validateArtifactReview
|
|
42
|
-
} from "./design-shared-artifact-support-protocol-contracts-ea2cd15e.mjs";
|
|
43
|
-
import {
|
|
44
|
-
DESIGN_WORKSPACE_API,
|
|
45
|
-
DESIGN_WORKSPACE_CREATE_SCHEMA,
|
|
46
|
-
DESIGN_WORKSPACE_EVENT_SCHEMA,
|
|
47
|
-
DESIGN_WORKSPACE_MAX_BYTES,
|
|
48
|
-
DESIGN_WORKSPACE_MAX_EVENT_BYTES,
|
|
49
|
-
DESIGN_WORKSPACE_REVISION_SCHEMA,
|
|
50
|
-
DESIGN_WORKSPACE_SCHEMA,
|
|
51
|
-
DESIGN_WORKSPACE_VERSION,
|
|
52
|
-
assertWorkspaceContract
|
|
53
|
-
} from "./design-shared-protocol-contracts-b5f28f94.mjs";
|
|
54
|
-
import {
|
|
55
|
-
canonicalizeJson,
|
|
56
|
-
deepFreeze,
|
|
57
|
-
sha256Hex
|
|
58
|
-
} from "./design-shared-protocol-contracts-75a938cc.mjs";
|
|
59
|
-
|
|
60
|
-
// packages/design/lib/design/share-status.mjs
|
|
61
|
-
import { existsSync, realpathSync } from "node:fs";
|
|
62
|
-
import { homedir } from "node:os";
|
|
63
|
-
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
64
|
-
|
|
65
|
-
// packages/design/lib/design/workspace-address.mjs
|
|
66
|
-
var DESIGN_SHARE_BASE_URL = "https://share.openplanr.dev";
|
|
67
|
-
var DESIGN_SHARE_REVIEW_PATH = "/d";
|
|
68
|
-
var address = createWorkspaceAddress({
|
|
69
|
-
label: "Design",
|
|
70
|
-
reviewPath: DESIGN_SHARE_REVIEW_PATH,
|
|
71
|
-
defaultBaseUrl: DESIGN_SHARE_BASE_URL
|
|
72
|
-
});
|
|
73
|
-
var normalizeWorkspaceBase = address.normalizeWorkspaceBase;
|
|
74
|
-
var workspaceReviewUrl = (access) => `${address.workspaceReviewUrl(access)}${access.schemaVersion === "2.0.0" ? "?v=2" : ""}`;
|
|
75
|
-
|
|
76
|
-
// packages/design/lib/design/share-status.mjs
|
|
77
|
-
var FORMAT = "openplanr-design-owner-custody";
|
|
78
|
-
function custodyLocation(file, options = {}, { allowMissing = false } = {}) {
|
|
79
|
-
const current = currentDesign(file, options);
|
|
80
|
-
const env = options.env ?? process.env;
|
|
81
|
-
const root = resolve(options.custodyRoot ?? join(planrHome(env), "design-shares"));
|
|
82
|
-
let project = current.root;
|
|
83
|
-
for (let candidate = current.root; dirname(candidate) !== candidate; candidate = dirname(candidate)) {
|
|
84
|
-
if (existsSync(join(candidate, ".git")) || existsSync(join(candidate, PLANNING_FOLDER))) {
|
|
85
|
-
project = candidate;
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
const key = hash(`${current.root}
|
|
90
|
-
${current.document.id}`);
|
|
91
|
-
const path = join(root, `${key}.json`);
|
|
92
|
-
const within = relative(project, root);
|
|
93
|
-
if ((!allowMissing || existsSync(path)) && (within === "" || !within.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) && within !== ".." && !isAbsolute(within)))
|
|
94
|
-
throw new Error(
|
|
95
|
-
"Design owner keys must be stored outside the project. Set PLANR_HOME to a private user-level directory."
|
|
96
|
-
);
|
|
97
|
-
const legacyPath = !options.custodyRoot && !configuredPlanrHome(env) ? join(realpathSync(env.HOME || homedir()), ".openplanr", "design-shares", `${key}.json`) : null;
|
|
98
|
-
return { root, path, current, legacyPath };
|
|
99
|
-
}
|
|
100
|
-
function presentationFingerprint(current, { legacy = false } = {}) {
|
|
101
|
-
const state = readJson(join(current.root, ".design/studio-state.json"), { state: {} }).state;
|
|
102
|
-
return hash(
|
|
103
|
-
JSON.stringify({
|
|
104
|
-
revision: current.revision,
|
|
105
|
-
selectedVariant: state.selectedVariant ?? current.document.selectedVariant,
|
|
106
|
-
...legacy ? { positions: state.positions ?? {} } : {},
|
|
107
|
-
verification: current.verification.status
|
|
108
|
-
})
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
var safeStatus = (record, current) => ({
|
|
112
|
-
ok: true,
|
|
113
|
-
shared: Boolean(record),
|
|
114
|
-
title: current.document.title,
|
|
115
|
-
localRevision: current.revision,
|
|
116
|
-
retention: "until-revoked",
|
|
117
|
-
...record ? {
|
|
118
|
-
id: record.custody.id,
|
|
119
|
-
url: workspaceReviewUrl(record.custody),
|
|
120
|
-
revision: record.custody.currentRevision ?? record.publishedRevision ?? null,
|
|
121
|
-
publishedRevision: record.publishedRevision ?? null,
|
|
122
|
-
hasUpdate: record.publishedRevision !== current.revision || ![
|
|
123
|
-
presentationFingerprint(current),
|
|
124
|
-
presentationFingerprint(current, { legacy: true })
|
|
125
|
-
].includes(record.publishedPresentation),
|
|
126
|
-
epoch: record.custody.epoch,
|
|
127
|
-
commentsPaused: Boolean(record.custody.commentsPaused ?? record.commentsPaused),
|
|
128
|
-
revoked: Boolean(record.revoked),
|
|
129
|
-
deleted: Boolean(record.deleted),
|
|
130
|
-
pending: Boolean(
|
|
131
|
-
record.custody.pendingCreate || record.custody.pendingMutation || record.pendingReviewMetadata?.length
|
|
132
|
-
),
|
|
133
|
-
pendingAction: record.custody.pendingCreate ? "create" : record.custody.pendingMutation?.action ?? (record.pendingReviewMetadata?.length ? "review-metadata" : null),
|
|
134
|
-
pendingReviewMetadata: Boolean(record.pendingReviewMetadata?.length)
|
|
135
|
-
} : {}
|
|
136
|
-
});
|
|
137
|
-
function getDesignShareStatus(file, options = {}) {
|
|
138
|
-
const { path, current, legacyPath } = custodyLocation(file, options);
|
|
139
|
-
const record = readCustody(path, { label: "Design", format: FORMAT }) ?? (legacyPath ? readCustody(legacyPath, { label: "Design", format: FORMAT }) : null);
|
|
140
|
-
return safeStatus(record, current);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// packages/design/lib/design/share.mjs
|
|
144
|
-
import {
|
|
145
|
-
closeSync,
|
|
146
|
-
existsSync as existsSync4,
|
|
147
|
-
fsyncSync,
|
|
148
|
-
linkSync,
|
|
149
|
-
mkdirSync as mkdirSync3,
|
|
150
|
-
openSync,
|
|
151
|
-
unlinkSync,
|
|
152
|
-
writeFileSync as writeFileSync3
|
|
153
|
-
} from "node:fs";
|
|
154
|
-
import { basename, dirname as dirname4, join as join3, resolve as resolve3 } from "node:path";
|
|
155
|
-
|
|
156
|
-
// packages/artifact/lib/artifact/import.mjs
|
|
157
|
-
import {
|
|
158
|
-
existsSync as existsSync3,
|
|
159
|
-
lstatSync as lstatSync2,
|
|
160
|
-
mkdirSync as mkdirSync2,
|
|
161
|
-
readFileSync as readFileSync2,
|
|
162
|
-
realpathSync as realpathSync2,
|
|
163
|
-
renameSync as renameSync2,
|
|
164
|
-
rmSync as rmSync2,
|
|
165
|
-
statSync as statSync2,
|
|
166
|
-
writeFileSync as writeFileSync2
|
|
167
|
-
} from "node:fs";
|
|
168
|
-
import { homedir as homedir2 } from "node:os";
|
|
169
|
-
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve2 } from "node:path";
|
|
170
|
-
|
|
171
|
-
// packages/artifact/lib/artifact/internal/feedback.mjs
|
|
172
|
-
var FEEDBACK_FILE = "feedback.json";
|
|
173
|
-
|
|
174
|
-
// packages/artifact/lib/artifact/merge.mjs
|
|
175
|
-
var ARTIFACT_REVIEW_STATE_VERSION = "1.0.0";
|
|
176
|
-
var ARTIFACT_REVIEW_STATE_KIND = "artifact-review-state";
|
|
177
|
-
var SHA256_RE = /^[a-f0-9]{64}$/;
|
|
178
|
-
var ARTIFACT_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
179
|
-
function conflict(message, details = void 0) {
|
|
180
|
-
throw new PipelineError(
|
|
181
|
-
ARTIFACT_ERROR_CODES.MERGE_CONFLICT,
|
|
182
|
-
message,
|
|
183
|
-
"Keep both reviews under different stable IDs or resolve the conflicting item explicitly.",
|
|
184
|
-
details
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
function assertUniqueReviewItemIds(review) {
|
|
188
|
-
const pinIds = /* @__PURE__ */ new Set();
|
|
189
|
-
for (const pin of review.pins ?? []) {
|
|
190
|
-
if (pinIds.has(pin.id)) {
|
|
191
|
-
throw new PipelineError(
|
|
192
|
-
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
193
|
-
"Artifact review contains a duplicate pin ID."
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
pinIds.add(pin.id);
|
|
197
|
-
const replyIds = /* @__PURE__ */ new Set();
|
|
198
|
-
for (const reply of pin.replies ?? []) {
|
|
199
|
-
if (replyIds.has(reply.id)) {
|
|
200
|
-
throw new PipelineError(
|
|
201
|
-
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
202
|
-
"Artifact review contains a duplicate reply ID in one thread."
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
replyIds.add(reply.id);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return review;
|
|
209
|
-
}
|
|
210
|
-
function clone(value) {
|
|
211
|
-
return structuredClone(value);
|
|
212
|
-
}
|
|
213
|
-
function same(a, b) {
|
|
214
|
-
return canonicalSerialize(a) === canonicalSerialize(b);
|
|
215
|
-
}
|
|
216
|
-
function timeValue(value) {
|
|
217
|
-
if (typeof value !== "string") return Number.NEGATIVE_INFINITY;
|
|
218
|
-
const parsed = Date.parse(value);
|
|
219
|
-
return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;
|
|
220
|
-
}
|
|
221
|
-
function compareStable(a, b, idKey) {
|
|
222
|
-
const aTime = a.createdAt ?? a.updatedAt ?? "";
|
|
223
|
-
const bTime = b.createdAt ?? b.updatedAt ?? "";
|
|
224
|
-
return aTime.localeCompare(bTime) || String(a[idKey]).localeCompare(String(b[idKey]));
|
|
225
|
-
}
|
|
226
|
-
function replyMap(replies) {
|
|
227
|
-
const map = /* @__PURE__ */ new Map();
|
|
228
|
-
for (const reply of replies ?? []) {
|
|
229
|
-
const previous = map.get(reply.id);
|
|
230
|
-
if (previous && !same(previous, reply)) {
|
|
231
|
-
conflict("A reply ID refers to different immutable reply content.", { entity: "reply" });
|
|
232
|
-
}
|
|
233
|
-
map.set(reply.id, clone(reply));
|
|
234
|
-
}
|
|
235
|
-
return map;
|
|
236
|
-
}
|
|
237
|
-
function mergeReplies(stored = [], incoming = []) {
|
|
238
|
-
const merged = replyMap(stored);
|
|
239
|
-
for (const reply of incoming) {
|
|
240
|
-
const previous = merged.get(reply.id);
|
|
241
|
-
if (previous && !same(previous, reply)) {
|
|
242
|
-
conflict("A reply ID refers to different immutable reply content.", { entity: "reply" });
|
|
243
|
-
}
|
|
244
|
-
if (!previous) merged.set(reply.id, clone(reply));
|
|
245
|
-
}
|
|
246
|
-
return [...merged.values()].sort((a, b) => compareStable(a, b, "id"));
|
|
247
|
-
}
|
|
248
|
-
var immutablePin = (pin) => ({
|
|
249
|
-
id: pin.id,
|
|
250
|
-
author: pin.author,
|
|
251
|
-
artifactId: pin.artifactId,
|
|
252
|
-
...pin.variant === void 0 ? {} : { variant: pin.variant },
|
|
253
|
-
region: pin.region,
|
|
254
|
-
viewport: pin.viewport,
|
|
255
|
-
...pin.anchor === void 0 ? {} : { anchor: pin.anchor },
|
|
256
|
-
createdAt: pin.createdAt
|
|
257
|
-
});
|
|
258
|
-
var mutablePin = (pin) => ({
|
|
259
|
-
intent: pin.intent,
|
|
260
|
-
status: pin.status,
|
|
261
|
-
comment: pin.comment
|
|
262
|
-
});
|
|
263
|
-
function mergePin(stored, incoming) {
|
|
264
|
-
if (!same(immutablePin(stored), immutablePin(incoming))) {
|
|
265
|
-
conflict("A pin ID refers to different immutable geometry, author, or artifact content.", {
|
|
266
|
-
entity: "pin"
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
const replies = mergeReplies(stored.replies, incoming.replies);
|
|
270
|
-
const storedTime = timeValue(stored.updatedAt);
|
|
271
|
-
const incomingTime = timeValue(incoming.updatedAt);
|
|
272
|
-
let selected = stored;
|
|
273
|
-
if (incomingTime > storedTime) selected = incoming;
|
|
274
|
-
if (incomingTime === storedTime && !same(mutablePin(stored), mutablePin(incoming))) {
|
|
275
|
-
conflict("A pin has divergent mutable content at the same update time.", { entity: "pin" });
|
|
276
|
-
}
|
|
277
|
-
return { ...clone(selected), replies };
|
|
278
|
-
}
|
|
279
|
-
function mergePins(stored = [], incoming = []) {
|
|
280
|
-
const merged = /* @__PURE__ */ new Map();
|
|
281
|
-
for (const pin of stored) merged.set(pin.id, clone(pin));
|
|
282
|
-
for (const pin of incoming) {
|
|
283
|
-
const previous = merged.get(pin.id);
|
|
284
|
-
merged.set(pin.id, previous ? mergePin(previous, pin) : clone(pin));
|
|
285
|
-
}
|
|
286
|
-
return [...merged.values()].sort((a, b) => compareStable(a, b, "id"));
|
|
287
|
-
}
|
|
288
|
-
var immutableReview = (review) => ({
|
|
289
|
-
schemaVersion: review.schemaVersion,
|
|
290
|
-
reviewId: review.reviewId,
|
|
291
|
-
reviewOf: review.reviewOf,
|
|
292
|
-
...review.createdAt === void 0 ? {} : { createdAt: review.createdAt }
|
|
293
|
-
});
|
|
294
|
-
var mutableReview = (review) => ({ decision: review.decision, overall: review.overall });
|
|
295
|
-
function mergeArtifactReviews(stored, incoming) {
|
|
296
|
-
validateArtifactReview(stored);
|
|
297
|
-
validateArtifactReview(incoming);
|
|
298
|
-
assertUniqueReviewItemIds(stored);
|
|
299
|
-
assertUniqueReviewItemIds(incoming);
|
|
300
|
-
if (stored.reviewId !== incoming.reviewId) {
|
|
301
|
-
conflict("Only reviews with the same review ID can be revision-merged.", {
|
|
302
|
-
entity: "review"
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
if (same(stored, incoming)) return stored;
|
|
306
|
-
if (!same(immutableReview(stored), immutableReview(incoming))) {
|
|
307
|
-
conflict("A review ID refers to a different digest or creation identity.", {
|
|
308
|
-
entity: "review"
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
const storedTime = timeValue(stored.updatedAt);
|
|
312
|
-
const incomingTime = timeValue(incoming.updatedAt);
|
|
313
|
-
let selected = stored;
|
|
314
|
-
if (incomingTime > storedTime) selected = incoming;
|
|
315
|
-
if (incomingTime === storedTime && !same(mutableReview(stored), mutableReview(incoming))) {
|
|
316
|
-
conflict("A review has divergent verdict or overall feedback at the same update time.", {
|
|
317
|
-
entity: "review"
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
const merged = {
|
|
321
|
-
...clone(selected),
|
|
322
|
-
pins: mergePins(stored.pins, incoming.pins)
|
|
323
|
-
};
|
|
324
|
-
validateArtifactReview(merged);
|
|
325
|
-
return merged;
|
|
326
|
-
}
|
|
327
|
-
function validateReviewLedger(ledger) {
|
|
328
|
-
if (!ledger || typeof ledger !== "object" || Array.isArray(ledger) || ledger.schemaVersion !== ARTIFACT_REVIEW_STATE_VERSION || ledger.kind !== ARTIFACT_REVIEW_STATE_KIND || !ARTIFACT_ID_RE.test(ledger.artifactId ?? "") || !SHA256_RE.test(ledger.currentReviewOf ?? "") || !Array.isArray(ledger.reviews)) {
|
|
329
|
-
throw new PipelineError(
|
|
330
|
-
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
331
|
-
"Artifact review state is not a valid versioned review ledger."
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
const ids = /* @__PURE__ */ new Set();
|
|
335
|
-
for (const entry of ledger.reviews) {
|
|
336
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry) || typeof entry.stale !== "boolean" || !entry.review) {
|
|
337
|
-
throw new PipelineError(
|
|
338
|
-
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
339
|
-
"Artifact review ledger entry is invalid."
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
validateArtifactReview(entry.review);
|
|
343
|
-
assertUniqueReviewItemIds(entry.review);
|
|
344
|
-
if (ids.has(entry.review.reviewId)) {
|
|
345
|
-
throw new PipelineError(
|
|
346
|
-
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
347
|
-
"Artifact review ledger IDs must be unique."
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
ids.add(entry.review.reviewId);
|
|
351
|
-
}
|
|
352
|
-
return ledger;
|
|
353
|
-
}
|
|
354
|
-
function createReviewLedger({ artifactId, currentReviewOf, reviews = [] } = {}) {
|
|
355
|
-
const ledger = {
|
|
356
|
-
schemaVersion: ARTIFACT_REVIEW_STATE_VERSION,
|
|
357
|
-
kind: ARTIFACT_REVIEW_STATE_KIND,
|
|
358
|
-
artifactId,
|
|
359
|
-
currentReviewOf,
|
|
360
|
-
reviews: reviews.map((entry) => ({ review: clone(entry.review), stale: Boolean(entry.stale) }))
|
|
361
|
-
};
|
|
362
|
-
ledger.reviews.sort((a, b) => compareStable(a.review, b.review, "reviewId"));
|
|
363
|
-
return validateReviewLedger(ledger);
|
|
364
|
-
}
|
|
365
|
-
function mergeReviewLedger(ledger, incoming, { stale = false } = {}) {
|
|
366
|
-
validateReviewLedger(ledger);
|
|
367
|
-
const reviews = Array.isArray(incoming) ? incoming : [incoming];
|
|
368
|
-
const byId = new Map(
|
|
369
|
-
ledger.reviews.map((entry) => [
|
|
370
|
-
entry.review.reviewId,
|
|
371
|
-
{
|
|
372
|
-
review: clone(entry.review),
|
|
373
|
-
stale: entry.stale
|
|
374
|
-
}
|
|
375
|
-
])
|
|
376
|
-
);
|
|
377
|
-
for (const review of reviews) {
|
|
378
|
-
validateArtifactReview(review);
|
|
379
|
-
const previous = byId.get(review.reviewId);
|
|
380
|
-
if (previous) {
|
|
381
|
-
byId.set(review.reviewId, {
|
|
382
|
-
review: mergeArtifactReviews(previous.review, review),
|
|
383
|
-
stale: previous.stale || Boolean(stale)
|
|
384
|
-
});
|
|
385
|
-
} else {
|
|
386
|
-
byId.set(review.reviewId, { review: clone(review), stale: Boolean(stale) });
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
const merged = createReviewLedger({
|
|
390
|
-
artifactId: ledger.artifactId,
|
|
391
|
-
currentReviewOf: ledger.currentReviewOf,
|
|
392
|
-
reviews: [...byId.values()]
|
|
393
|
-
});
|
|
394
|
-
return same(ledger, merged) ? ledger : merged;
|
|
395
|
-
}
|
|
396
|
-
function effectiveReviewDecision(value) {
|
|
397
|
-
const entries = Array.isArray(value) ? value : value?.reviews ?? [];
|
|
398
|
-
const decisions = entries.map((entry) => entry.review?.decision ?? entry.decision);
|
|
399
|
-
if (decisions.includes("changes_requested")) return "changes_requested";
|
|
400
|
-
if (decisions.includes("pending")) return "pending";
|
|
401
|
-
return decisions.length > 0 && decisions.every((decision) => decision === "approved") ? "approved" : "pending";
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// packages/artifact/lib/artifact/review.mjs
|
|
405
|
-
import { randomBytes, randomUUID } from "node:crypto";
|
|
406
|
-
import {
|
|
407
|
-
existsSync as existsSync2,
|
|
408
|
-
lstatSync,
|
|
409
|
-
mkdirSync,
|
|
410
|
-
readFileSync,
|
|
411
|
-
renameSync,
|
|
412
|
-
rmSync,
|
|
413
|
-
statSync,
|
|
414
|
-
writeFileSync
|
|
415
|
-
} from "node:fs";
|
|
416
|
-
import { dirname as dirname2 } from "node:path";
|
|
417
|
-
var ARTIFACT_REVIEW_MAX_STATE_BYTES = 5 * 1024 * 1024;
|
|
418
|
-
var reviewPathQueues = /* @__PURE__ */ new Map();
|
|
419
|
-
function finalEntry(path, fs = { lstatSync }) {
|
|
420
|
-
try {
|
|
421
|
-
return fs.lstatSync(path);
|
|
422
|
-
} catch (error) {
|
|
423
|
-
if (error?.code === "ENOENT") return null;
|
|
424
|
-
throw error;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
function clone2(value) {
|
|
428
|
-
return structuredClone(value);
|
|
429
|
-
}
|
|
430
|
-
function canonicalObject(value) {
|
|
431
|
-
if (Array.isArray(value)) return value.map(canonicalObject);
|
|
432
|
-
if (!value || typeof value !== "object") return value;
|
|
433
|
-
return Object.fromEntries(
|
|
434
|
-
Object.keys(value).sort().map((key) => [key, canonicalObject(value[key])])
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
function stableItemSort(a, b) {
|
|
438
|
-
return String(a.createdAt ?? a.updatedAt ?? "").localeCompare(
|
|
439
|
-
String(b.createdAt ?? b.updatedAt ?? "")
|
|
440
|
-
) || String(a.id ?? a.reviewId).localeCompare(String(b.id ?? b.reviewId));
|
|
441
|
-
}
|
|
442
|
-
function normalizeArtifactReview(review) {
|
|
443
|
-
let normalized;
|
|
444
|
-
try {
|
|
445
|
-
normalized = clone2(review);
|
|
446
|
-
} catch {
|
|
447
|
-
throw new PipelineError(
|
|
448
|
-
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
449
|
-
"Artifact review is not cloneable."
|
|
450
|
-
);
|
|
451
|
-
}
|
|
452
|
-
try {
|
|
453
|
-
validateArtifactReview(normalized);
|
|
454
|
-
assertUniqueReviewItemIds(normalized);
|
|
455
|
-
} catch (error) {
|
|
456
|
-
if (error instanceof PipelineError) {
|
|
457
|
-
throw new PipelineError(
|
|
458
|
-
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
459
|
-
"Artifact review does not satisfy the Protocol v1.1 review contract.",
|
|
460
|
-
"",
|
|
461
|
-
{ issues: error.details?.issues ?? [] }
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
throw error;
|
|
465
|
-
}
|
|
466
|
-
normalized.pins = normalized.pins.map((pin) => ({
|
|
467
|
-
...pin,
|
|
468
|
-
replies: [...pin.replies].sort(stableItemSort)
|
|
469
|
-
})).sort(stableItemSort);
|
|
470
|
-
validateArtifactReview(normalized);
|
|
471
|
-
return normalized;
|
|
472
|
-
}
|
|
473
|
-
function serializeReviewState(value) {
|
|
474
|
-
const normalized = value?.kind === "artifact-review-state" ? validateReviewLedger(clone2(value)) : normalizeArtifactReview(value);
|
|
475
|
-
return `${JSON.stringify(canonicalObject(normalized), null, 2)}
|
|
476
|
-
`;
|
|
477
|
-
}
|
|
478
|
-
function readArtifactReviewState(path, { allowMissing = false } = {}) {
|
|
479
|
-
const entry = finalEntry(path);
|
|
480
|
-
if (!entry) {
|
|
481
|
-
if (allowMissing) return null;
|
|
482
|
-
throw new PipelineError(
|
|
483
|
-
ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
|
|
484
|
-
"Artifact review state does not exist."
|
|
485
|
-
);
|
|
486
|
-
}
|
|
487
|
-
try {
|
|
488
|
-
if (entry.isSymbolicLink() || !entry.isFile()) {
|
|
489
|
-
throw new PipelineError(
|
|
490
|
-
ARTIFACT_ERROR_CODES.SYMLINK_ESCAPE,
|
|
491
|
-
"Artifact review state destination is not a regular file."
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
|
-
if (statSync(path).size > ARTIFACT_REVIEW_MAX_STATE_BYTES) {
|
|
495
|
-
throw new PipelineError(
|
|
496
|
-
ARTIFACT_ERROR_CODES.REQUEST_LIMIT,
|
|
497
|
-
`Artifact review state exceeds ${ARTIFACT_REVIEW_MAX_STATE_BYTES} bytes.`
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
return validateReviewLedger(JSON.parse(readFileSync(path, "utf8")));
|
|
501
|
-
} catch (error) {
|
|
502
|
-
if (error instanceof PipelineError) throw error;
|
|
503
|
-
throw new PipelineError(
|
|
504
|
-
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
505
|
-
"Artifact review state is malformed."
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
function writeArtifactReviewState(path, ledger, { fileSystem = {}, suffix = `${process.pid}.${randomBytes(8).toString("hex")}` } = {}) {
|
|
510
|
-
validateReviewLedger(ledger);
|
|
511
|
-
const fs = { existsSync: existsSync2, lstatSync, mkdirSync, writeFileSync, renameSync, rmSync, ...fileSystem };
|
|
512
|
-
const temporary = `${path}.${suffix}.tmp`;
|
|
513
|
-
const serialized = serializeReviewState(ledger);
|
|
514
|
-
if (Buffer.byteLength(serialized, "utf8") > ARTIFACT_REVIEW_MAX_STATE_BYTES) {
|
|
515
|
-
throw new PipelineError(
|
|
516
|
-
ARTIFACT_ERROR_CODES.REQUEST_LIMIT,
|
|
517
|
-
`Artifact review state exceeds ${ARTIFACT_REVIEW_MAX_STATE_BYTES} bytes.`
|
|
518
|
-
);
|
|
519
|
-
}
|
|
520
|
-
try {
|
|
521
|
-
const initial = finalEntry(path, fs);
|
|
522
|
-
if (initial) {
|
|
523
|
-
const target = initial;
|
|
524
|
-
if (target.isSymbolicLink() || !target.isFile()) throw new Error("unsafe final target");
|
|
525
|
-
}
|
|
526
|
-
fs.mkdirSync(dirname2(path), { recursive: true, mode: 448 });
|
|
527
|
-
fs.writeFileSync(temporary, serialized, { mode: 384, flag: "wx" });
|
|
528
|
-
const final = finalEntry(path, fs);
|
|
529
|
-
if (final) {
|
|
530
|
-
const target = final;
|
|
531
|
-
if (target.isSymbolicLink() || !target.isFile()) throw new Error("unsafe final target");
|
|
532
|
-
}
|
|
533
|
-
fs.renameSync(temporary, path);
|
|
534
|
-
} catch {
|
|
535
|
-
try {
|
|
536
|
-
fs.rmSync(temporary, { force: true });
|
|
537
|
-
} catch {
|
|
538
|
-
}
|
|
539
|
-
throw new PipelineError(
|
|
540
|
-
ARTIFACT_ERROR_CODES.REVIEW_WRITE,
|
|
541
|
-
"Artifact review state could not be written atomically.",
|
|
542
|
-
"Check destination permissions and retry; the previous review state was left unchanged."
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
return ledger;
|
|
546
|
-
}
|
|
547
|
-
function withArtifactReviewLock(path, action) {
|
|
548
|
-
const previous = reviewPathQueues.get(path) ?? Promise.resolve();
|
|
549
|
-
const run = async () => {
|
|
550
|
-
let release;
|
|
551
|
-
try {
|
|
552
|
-
release = await acquireStartLock(`${path}.lock`, { timeout: 15e3, stale: 3e4 });
|
|
553
|
-
return await action();
|
|
554
|
-
} catch (error) {
|
|
555
|
-
if (error instanceof PipelineError) throw error;
|
|
556
|
-
throw new PipelineError(
|
|
557
|
-
ARTIFACT_ERROR_CODES.REVIEW_WRITE,
|
|
558
|
-
"Artifact review state is busy or its cross-process lock is unavailable.",
|
|
559
|
-
"Wait for the active review write to finish, then retry."
|
|
560
|
-
);
|
|
561
|
-
} finally {
|
|
562
|
-
release?.();
|
|
563
|
-
}
|
|
564
|
-
};
|
|
565
|
-
const operation = previous.then(run, run);
|
|
566
|
-
const tail = operation.then(
|
|
567
|
-
() => void 0,
|
|
568
|
-
() => void 0
|
|
569
|
-
);
|
|
570
|
-
reviewPathQueues.set(path, tail);
|
|
571
|
-
tail.finally(() => {
|
|
572
|
-
if (reviewPathQueues.get(path) === tail) reviewPathQueues.delete(path);
|
|
573
|
-
});
|
|
574
|
-
return operation;
|
|
575
|
-
}
|
|
576
|
-
function normalizedExportInput(value) {
|
|
577
|
-
if (value?.kind === "artifact-review-state") return validateReviewLedger(clone2(value));
|
|
578
|
-
if (value?.review && value?.artifacts) return normalizeArtifactReview(value.review);
|
|
579
|
-
return normalizeArtifactReview(value);
|
|
580
|
-
}
|
|
581
|
-
function markdownText(value) {
|
|
582
|
-
return String(value ?? "").replace(/\r\n?/g, "\n").trim();
|
|
583
|
-
}
|
|
584
|
-
function renderPinMarkdown(pin, number) {
|
|
585
|
-
const region = `${pin.region.x}, ${pin.region.y}, ${pin.region.w}, ${pin.region.h}`;
|
|
586
|
-
const lines = [
|
|
587
|
-
`### ${number}. ${pin.intent.toUpperCase()} \xB7 ${pin.status}`,
|
|
588
|
-
"",
|
|
589
|
-
`- Pin: \`${pin.id}\``,
|
|
590
|
-
`- Artifact: \`${pin.artifactId}\`${pin.variant ? ` \xB7 variant \`${pin.variant}\`` : ""}`,
|
|
591
|
-
`- Author: ${pin.author.name}`,
|
|
592
|
-
`- Region: \`${region}\` at ${pin.viewport.width}\xD7${pin.viewport.height}`,
|
|
593
|
-
...pin.anchor ? [
|
|
594
|
-
`- Anchor: \`${pin.anchor.planrId}\`${pin.anchor.screen ? ` \xB7 screen \`${pin.anchor.screen}\`` : ""}`
|
|
595
|
-
] : [],
|
|
596
|
-
`- Updated: ${pin.updatedAt}`,
|
|
597
|
-
"",
|
|
598
|
-
markdownText(pin.comment)
|
|
599
|
-
];
|
|
600
|
-
if (pin.replies.length > 0) {
|
|
601
|
-
lines.push("", "#### Thread", "");
|
|
602
|
-
for (const reply of pin.replies) {
|
|
603
|
-
lines.push(`- **${reply.author.name}** (${reply.createdAt}): ${markdownText(reply.comment)}`);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
return lines.join("\n");
|
|
607
|
-
}
|
|
608
|
-
function reviewMarkdown(review, headingLevel = 2, stale = false) {
|
|
609
|
-
const mark = "#".repeat(headingLevel);
|
|
610
|
-
const lines = [
|
|
611
|
-
`${mark} Review ${review.reviewId}${stale ? " \xB7 STALE" : ""}`,
|
|
612
|
-
"",
|
|
613
|
-
`- Digest: \`${review.reviewOf}\``,
|
|
614
|
-
`- Decision: **${review.decision}**`,
|
|
615
|
-
`- Pins: ${review.pins.length}`,
|
|
616
|
-
...review.createdAt ? [`- Created: ${review.createdAt}`] : [],
|
|
617
|
-
...review.updatedAt ? [`- Updated: ${review.updatedAt}`] : [],
|
|
618
|
-
"",
|
|
619
|
-
`${mark}# Overall feedback`,
|
|
620
|
-
"",
|
|
621
|
-
markdownText(review.overall) || "_No overall feedback._"
|
|
622
|
-
];
|
|
623
|
-
if (review.pins.length > 0) {
|
|
624
|
-
lines.push("", `${mark}# Pins`, "");
|
|
625
|
-
review.pins.forEach((pin, index) => lines.push(renderPinMarkdown(pin, index + 1), ""));
|
|
626
|
-
if (lines.at(-1) === "") lines.pop();
|
|
627
|
-
}
|
|
628
|
-
return lines.join("\n");
|
|
629
|
-
}
|
|
630
|
-
function exportArtifactReview(value, { format = "json" } = {}) {
|
|
631
|
-
let normalized;
|
|
632
|
-
try {
|
|
633
|
-
normalized = normalizedExportInput(value);
|
|
634
|
-
} catch (error) {
|
|
635
|
-
if (error instanceof PipelineError) throw error;
|
|
636
|
-
throw new PipelineError(
|
|
637
|
-
ARTIFACT_ERROR_CODES.REVIEW_EXPORT,
|
|
638
|
-
"Artifact review cannot be exported."
|
|
639
|
-
);
|
|
640
|
-
}
|
|
641
|
-
if (format === "json") return serializeReviewState(normalized);
|
|
642
|
-
if (format !== "markdown") {
|
|
643
|
-
throw new PipelineError(
|
|
644
|
-
ARTIFACT_ERROR_CODES.REVIEW_EXPORT,
|
|
645
|
-
"Artifact review export format must be json or markdown."
|
|
646
|
-
);
|
|
647
|
-
}
|
|
648
|
-
if (normalized.kind !== "artifact-review-state") {
|
|
649
|
-
return `# OpenPlanr Artifact Review
|
|
650
|
-
|
|
651
|
-
${reviewMarkdown(normalized)}
|
|
652
|
-
`;
|
|
653
|
-
}
|
|
654
|
-
const lines = [
|
|
655
|
-
"# OpenPlanr Artifact Reviews",
|
|
656
|
-
"",
|
|
657
|
-
`- Artifact: \`${normalized.artifactId}\``,
|
|
658
|
-
`- Current digest: \`${normalized.currentReviewOf}\``,
|
|
659
|
-
`- Effective decision: **${effectiveReviewDecision(normalized)}**`,
|
|
660
|
-
`- Reviews: ${normalized.reviews.length}`
|
|
661
|
-
];
|
|
662
|
-
for (const entry of normalized.reviews) {
|
|
663
|
-
lines.push("", reviewMarkdown(entry.review, 2, entry.stale));
|
|
664
|
-
}
|
|
665
|
-
return `${lines.join("\n")}
|
|
666
|
-
`;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// packages/artifact/lib/artifact/import.mjs
|
|
670
|
-
var ARTIFACT_ID_RE2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
671
|
-
function pathError(code, message) {
|
|
672
|
-
throw new PipelineError(
|
|
673
|
-
code,
|
|
674
|
-
message,
|
|
675
|
-
"Choose a real, non-symlinked project or user review destination."
|
|
676
|
-
);
|
|
677
|
-
}
|
|
678
|
-
function pathEntry(path, fs = { lstatSync: lstatSync2 }) {
|
|
679
|
-
try {
|
|
680
|
-
return fs.lstatSync(path);
|
|
681
|
-
} catch (error) {
|
|
682
|
-
if (error?.code === "ENOENT") return null;
|
|
683
|
-
throw error;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
function inside(base, candidate) {
|
|
687
|
-
const rel = relative2(base, candidate);
|
|
688
|
-
return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
|
|
689
|
-
}
|
|
690
|
-
function parseablePlanrConfig(root) {
|
|
691
|
-
const path = join2(root, PLANNING_FOLDER, "config.json");
|
|
692
|
-
if (!existsSync3(path)) return false;
|
|
693
|
-
try {
|
|
694
|
-
if (lstatSync2(path).isSymbolicLink()) return false;
|
|
695
|
-
const value = JSON.parse(readFileSync2(path, "utf8"));
|
|
696
|
-
return value && typeof value === "object" && !Array.isArray(value) && (typeof value.projectName === "string" && value.projectName.trim() !== "" || value.idPrefix && typeof value.idPrefix === "object" && !Array.isArray(value.idPrefix) && Object.keys(value.idPrefix).length > 0);
|
|
697
|
-
} catch {
|
|
698
|
-
return false;
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
function hasGitMarker(root) {
|
|
702
|
-
const marker = join2(root, ".git");
|
|
703
|
-
if (!existsSync3(marker)) return false;
|
|
704
|
-
try {
|
|
705
|
-
const stat = lstatSync2(marker);
|
|
706
|
-
if (stat.isSymbolicLink()) return false;
|
|
707
|
-
if (stat.isDirectory()) {
|
|
708
|
-
const head = join2(marker, "HEAD");
|
|
709
|
-
return existsSync3(head) && lstatSync2(head).isFile();
|
|
710
|
-
}
|
|
711
|
-
if (!stat.isFile() || stat.size > 4096) return false;
|
|
712
|
-
const match = /^gitdir:\s*(.+?)\s*$/u.exec(readFileSync2(marker, "utf8"));
|
|
713
|
-
if (!match) return false;
|
|
714
|
-
const gitDir = resolve2(root, match[1]);
|
|
715
|
-
return existsSync3(gitDir) && statSync2(gitDir).isDirectory() && existsSync3(join2(gitDir, "HEAD")) && lstatSync2(join2(gitDir, "HEAD")).isFile();
|
|
716
|
-
} catch {
|
|
717
|
-
return false;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
function findArtifactProjectRoot(start = process.cwd(), { env = process.env } = {}) {
|
|
721
|
-
let current;
|
|
722
|
-
try {
|
|
723
|
-
current = realpathSync2(resolve2(start));
|
|
724
|
-
} catch {
|
|
725
|
-
pathError(
|
|
726
|
-
ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
|
|
727
|
-
"Artifact review working directory does not exist."
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
const homeCandidate = resolve2(env.HOME ?? homedir2());
|
|
731
|
-
let home = homeCandidate;
|
|
732
|
-
try {
|
|
733
|
-
home = realpathSync2(homeCandidate);
|
|
734
|
-
} catch {
|
|
735
|
-
}
|
|
736
|
-
while (true) {
|
|
737
|
-
if (current !== home && (parseablePlanrConfig(current) || hasGitMarker(current)))
|
|
738
|
-
return current;
|
|
739
|
-
const parent = dirname3(current);
|
|
740
|
-
if (parent === current) return null;
|
|
741
|
-
current = parent;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
function assertSafeDestination(base, relativeParts) {
|
|
745
|
-
const absoluteBase = resolve2(base);
|
|
746
|
-
if (existsSync3(absoluteBase) && lstatSync2(absoluteBase).isSymbolicLink()) {
|
|
747
|
-
pathError(
|
|
748
|
-
ARTIFACT_ERROR_CODES.SYMLINK_ESCAPE,
|
|
749
|
-
"Artifact review destination base is a symlink."
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
let realBase = absoluteBase;
|
|
753
|
-
if (existsSync3(absoluteBase)) realBase = realpathSync2(absoluteBase);
|
|
754
|
-
let current = absoluteBase;
|
|
755
|
-
for (const part of relativeParts) {
|
|
756
|
-
if (!part || part === "." || part === ".." || part.includes("/") || part.includes("\\") || part.includes("\0")) {
|
|
757
|
-
pathError(
|
|
758
|
-
ARTIFACT_ERROR_CODES.PATH_TRAVERSAL,
|
|
759
|
-
"Artifact review destination contains an unsafe segment."
|
|
760
|
-
);
|
|
761
|
-
}
|
|
762
|
-
current = join2(current, part);
|
|
763
|
-
const entry = pathEntry(current);
|
|
764
|
-
if (!entry) continue;
|
|
765
|
-
if (entry.isSymbolicLink()) {
|
|
766
|
-
pathError(
|
|
767
|
-
ARTIFACT_ERROR_CODES.SYMLINK_ESCAPE,
|
|
768
|
-
"Artifact review destination contains a symlink."
|
|
769
|
-
);
|
|
770
|
-
}
|
|
771
|
-
if (!inside(realBase, realpathSync2(current))) {
|
|
772
|
-
pathError(
|
|
773
|
-
ARTIFACT_ERROR_CODES.PATH_TRAVERSAL,
|
|
774
|
-
"Artifact review destination escapes its storage root."
|
|
775
|
-
);
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
if (!inside(absoluteBase, current)) {
|
|
779
|
-
pathError(
|
|
780
|
-
ARTIFACT_ERROR_CODES.PATH_TRAVERSAL,
|
|
781
|
-
"Artifact review destination escapes its storage root."
|
|
782
|
-
);
|
|
783
|
-
}
|
|
784
|
-
return current;
|
|
785
|
-
}
|
|
786
|
-
function resolveArtifactReviewDestination({
|
|
787
|
-
cwd = process.cwd(),
|
|
788
|
-
env = process.env,
|
|
789
|
-
artifactId,
|
|
790
|
-
designDir
|
|
791
|
-
} = {}) {
|
|
792
|
-
if (!ARTIFACT_ID_RE2.test(artifactId ?? "")) {
|
|
793
|
-
pathError(
|
|
794
|
-
ARTIFACT_ERROR_CODES.PATH_TRAVERSAL,
|
|
795
|
-
"Artifact review ID is unsafe for local storage."
|
|
796
|
-
);
|
|
797
|
-
}
|
|
798
|
-
if (designDir !== void 0) {
|
|
799
|
-
const lexical = resolve2(designDir);
|
|
800
|
-
if (!existsSync3(lexical) || !statSync2(lexical).isDirectory()) {
|
|
801
|
-
pathError(ARTIFACT_ERROR_CODES.REVIEW_IMPORT, "Design review destination does not exist.");
|
|
802
|
-
}
|
|
803
|
-
if (lstatSync2(lexical).isSymbolicLink()) {
|
|
804
|
-
pathError(
|
|
805
|
-
ARTIFACT_ERROR_CODES.SYMLINK_ESCAPE,
|
|
806
|
-
"Design review destination must be a real directory."
|
|
807
|
-
);
|
|
808
|
-
}
|
|
809
|
-
const requested = realpathSync2(lexical);
|
|
810
|
-
return Object.freeze({
|
|
811
|
-
kind: "design",
|
|
812
|
-
artifactId,
|
|
813
|
-
directory: requested,
|
|
814
|
-
path: join2(requested, FEEDBACK_FILE),
|
|
815
|
-
reviewStatePath: join2(requested, "artifact-review-state.json")
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
const projectRoot = findArtifactProjectRoot(cwd, { env });
|
|
819
|
-
if (projectRoot) {
|
|
820
|
-
const directory2 = assertSafeDestination(projectRoot, [
|
|
821
|
-
PLANNING_FOLDER,
|
|
822
|
-
"artifacts",
|
|
823
|
-
artifactId
|
|
824
|
-
]);
|
|
825
|
-
return Object.freeze({
|
|
826
|
-
kind: "project",
|
|
827
|
-
artifactId,
|
|
828
|
-
root: projectRoot,
|
|
829
|
-
directory: directory2,
|
|
830
|
-
path: join2(directory2, "review-state.json")
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
const root = resolve2(planrHome(env));
|
|
834
|
-
const directory = assertSafeDestination(root, ["artifacts", artifactId]);
|
|
835
|
-
return Object.freeze({
|
|
836
|
-
kind: "user",
|
|
837
|
-
artifactId,
|
|
838
|
-
root,
|
|
839
|
-
directory,
|
|
840
|
-
path: join2(directory, "review-state.json")
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
function reviewsFromDecoded(value) {
|
|
844
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
845
|
-
throw new PipelineError(
|
|
846
|
-
ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
|
|
847
|
-
"Decoded review payload is not an object."
|
|
848
|
-
);
|
|
849
|
-
}
|
|
850
|
-
if (value.kind === "artifact-review-state") {
|
|
851
|
-
validateReviewLedger(value);
|
|
852
|
-
return value.reviews.map((entry) => ({ review: entry.review, stale: entry.stale }));
|
|
853
|
-
}
|
|
854
|
-
if (value.artifacts && value.viewer) {
|
|
855
|
-
validateArtifactEnvelope(value);
|
|
856
|
-
if (!value.review) {
|
|
857
|
-
throw new PipelineError(
|
|
858
|
-
ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
|
|
859
|
-
"Decoded artifact envelope contains no review."
|
|
860
|
-
);
|
|
861
|
-
}
|
|
862
|
-
return [{ review: value.review, stale: false }];
|
|
863
|
-
}
|
|
864
|
-
if (value.review && !value.reviewId) return reviewsFromDecoded(value.review);
|
|
865
|
-
validateArtifactReview(value);
|
|
866
|
-
return [{ review: value, stale: false }];
|
|
867
|
-
}
|
|
868
|
-
async function decodeArtifactReviewSources(sources, { decodeSource, withMetadata = false } = {}) {
|
|
869
|
-
const list = Array.isArray(sources) ? sources : [sources];
|
|
870
|
-
if (list.length === 0) {
|
|
871
|
-
throw new PipelineError(
|
|
872
|
-
ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
|
|
873
|
-
"At least one artifact review source is required."
|
|
874
|
-
);
|
|
875
|
-
}
|
|
876
|
-
const entries = [];
|
|
877
|
-
for (let index = 0; index < list.length; index += 1) {
|
|
878
|
-
const source = list[index];
|
|
879
|
-
let decoded = source;
|
|
880
|
-
if (typeof source === "string") {
|
|
881
|
-
if (typeof decodeSource !== "function") {
|
|
882
|
-
throw new PipelineError(
|
|
883
|
-
ARTIFACT_ERROR_CODES.REVIEW_DECODER_REQUIRED,
|
|
884
|
-
"String and URL review sources require an injected async decoder.",
|
|
885
|
-
"Provide decodeSource(source, { index }); the review lifecycle has no transport dependency.",
|
|
886
|
-
{ sourceIndex: index }
|
|
887
|
-
);
|
|
888
|
-
}
|
|
889
|
-
try {
|
|
890
|
-
decoded = await decodeSource(source, { index });
|
|
891
|
-
} catch {
|
|
892
|
-
throw new PipelineError(
|
|
893
|
-
ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
|
|
894
|
-
"Artifact review source could not be decoded.",
|
|
895
|
-
"Verify the immutable review link and retry.",
|
|
896
|
-
{ sourceIndex: index }
|
|
897
|
-
);
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
try {
|
|
901
|
-
entries.push(
|
|
902
|
-
...reviewsFromDecoded(decoded).map((entry) => ({
|
|
903
|
-
review: structuredClone(entry.review),
|
|
904
|
-
stale: Boolean(entry.stale)
|
|
905
|
-
}))
|
|
906
|
-
);
|
|
907
|
-
} catch (error) {
|
|
908
|
-
if (error instanceof PipelineError && error.code === ARTIFACT_ERROR_CODES.REVIEW_IMPORT) {
|
|
909
|
-
error.details = { sourceIndex: index };
|
|
910
|
-
throw error;
|
|
911
|
-
}
|
|
912
|
-
throw new PipelineError(
|
|
913
|
-
ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
|
|
914
|
-
"Decoded artifact review does not satisfy the Protocol v1.1 contract.",
|
|
915
|
-
"",
|
|
916
|
-
{ sourceIndex: index }
|
|
917
|
-
);
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
return withMetadata ? entries : entries.map((entry) => entry.review);
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
// packages/design/lib/design/workspace-client.mjs
|
|
924
|
-
var encoder = new TextEncoder();
|
|
925
|
-
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
926
|
-
var PACKED_BUNDLE_KIND = "openplanr-design-review-bundle-packed";
|
|
927
|
-
var PACKED_BUNDLE_MAX_BYTES = 128 * 1024 * 1024;
|
|
928
|
-
var SHARED_BLOCK_MIN_LENGTH = 2048;
|
|
929
|
-
var sharedBlockPattern = /(<(script|style)\b[^>]*>)([\s\S]*?)<\/\2\s*>/gi;
|
|
930
|
-
async function deflateRaw(bytes) {
|
|
931
|
-
const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("deflate-raw"));
|
|
932
|
-
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
933
|
-
}
|
|
934
|
-
async function inflateRaw(bytes, limit) {
|
|
935
|
-
const reader = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("deflate-raw")).getReader();
|
|
936
|
-
const chunks = [];
|
|
937
|
-
let size = 0;
|
|
938
|
-
for (; ; ) {
|
|
939
|
-
const { done, value } = await reader.read();
|
|
940
|
-
if (done) break;
|
|
941
|
-
size += value.byteLength;
|
|
942
|
-
if (size > limit) {
|
|
943
|
-
await reader.cancel();
|
|
944
|
-
throw new RangeError("The shared design expands beyond its size limit.");
|
|
945
|
-
}
|
|
946
|
-
chunks.push(value);
|
|
947
|
-
}
|
|
948
|
-
const inflated = new Uint8Array(size);
|
|
949
|
-
let offset = 0;
|
|
950
|
-
for (const chunk of chunks) {
|
|
951
|
-
inflated.set(chunk, offset);
|
|
952
|
-
offset += chunk.byteLength;
|
|
953
|
-
}
|
|
954
|
-
return inflated;
|
|
955
|
-
}
|
|
956
|
-
async function packDesignReviewBundle(bundle) {
|
|
957
|
-
if (bundle.envelope?.schemaVersion === "1.1.0")
|
|
958
|
-
return {
|
|
959
|
-
kind: PACKED_BUNDLE_KIND,
|
|
960
|
-
version: 2,
|
|
961
|
-
data: encodeWorkspaceBytes(await deflateRaw(encoder.encode(JSON.stringify(bundle))))
|
|
962
|
-
};
|
|
963
|
-
const pages = [];
|
|
964
|
-
const pageIndex = /* @__PURE__ */ new Map();
|
|
965
|
-
const blocks = [];
|
|
966
|
-
const blockIndex = /* @__PURE__ */ new Map();
|
|
967
|
-
const segmentsOf = (html) => {
|
|
968
|
-
const segments = [];
|
|
969
|
-
let offset = 0;
|
|
970
|
-
for (const match of html.matchAll(sharedBlockPattern)) {
|
|
971
|
-
const body = match[3];
|
|
972
|
-
if (body.length < SHARED_BLOCK_MIN_LENGTH) continue;
|
|
973
|
-
const start = match.index + match[1].length;
|
|
974
|
-
segments.push(html.slice(offset, start));
|
|
975
|
-
if (!blockIndex.has(body)) blockIndex.set(body, blocks.push(body) - 1);
|
|
976
|
-
segments.push(blockIndex.get(body));
|
|
977
|
-
offset = start + body.length;
|
|
978
|
-
}
|
|
979
|
-
segments.push(html.slice(offset));
|
|
980
|
-
return segments;
|
|
981
|
-
};
|
|
982
|
-
const artifactPages = [];
|
|
983
|
-
const artifacts = bundle.envelope.artifacts.map(({ html, ...artifact }) => {
|
|
984
|
-
if (!pageIndex.has(html)) pageIndex.set(html, pages.push(segmentsOf(html)) - 1);
|
|
985
|
-
artifactPages.push(pageIndex.get(html));
|
|
986
|
-
return artifact;
|
|
987
|
-
});
|
|
988
|
-
const packed = {
|
|
989
|
-
bundle: { ...bundle, envelope: { ...bundle.envelope, artifacts } },
|
|
990
|
-
artifactPages,
|
|
991
|
-
pages,
|
|
992
|
-
blocks
|
|
993
|
-
};
|
|
994
|
-
return {
|
|
995
|
-
kind: PACKED_BUNDLE_KIND,
|
|
996
|
-
version: 1,
|
|
997
|
-
data: encodeWorkspaceBytes(await deflateRaw(encoder.encode(JSON.stringify(packed))))
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
async function unpackDesignReviewBundle(value, { maxBytes = PACKED_BUNDLE_MAX_BYTES } = {}) {
|
|
1001
|
-
if (value?.kind !== PACKED_BUNDLE_KIND) return value;
|
|
1002
|
-
if (![1, 2].includes(value.version) || typeof value.data !== "string")
|
|
1003
|
-
throw new Error("The shared design uses an unsupported packing.");
|
|
1004
|
-
if (value.version === 2) {
|
|
1005
|
-
const bundle2 = JSON.parse(
|
|
1006
|
-
decoder.decode(await inflateRaw(decodeWorkspaceBytes(value.data), maxBytes))
|
|
1007
|
-
);
|
|
1008
|
-
return assertDesignReviewBundle(bundle2);
|
|
1009
|
-
}
|
|
1010
|
-
const { bundle, artifactPages, pages, blocks } = JSON.parse(
|
|
1011
|
-
decoder.decode(await inflateRaw(decodeWorkspaceBytes(value.data), maxBytes))
|
|
1012
|
-
);
|
|
1013
|
-
const invalid2 = () => new Error("The shared design packing is invalid.");
|
|
1014
|
-
if (!Array.isArray(bundle?.envelope?.artifacts) || !Array.isArray(pages) || !Array.isArray(blocks) || !Array.isArray(artifactPages) || artifactPages.length !== bundle.envelope.artifacts.length)
|
|
1015
|
-
throw invalid2();
|
|
1016
|
-
let expanded = 0;
|
|
1017
|
-
const html = pages.map((segments) => {
|
|
1018
|
-
if (!Array.isArray(segments)) throw invalid2();
|
|
1019
|
-
const page = segments.map((segment) => {
|
|
1020
|
-
if (typeof segment === "string") return segment;
|
|
1021
|
-
if (!Number.isInteger(segment) || typeof blocks[segment] !== "string") throw invalid2();
|
|
1022
|
-
return blocks[segment];
|
|
1023
|
-
}).join("");
|
|
1024
|
-
expanded += page.length;
|
|
1025
|
-
if (expanded > maxBytes)
|
|
1026
|
-
throw new RangeError("The shared design expands beyond its size limit.");
|
|
1027
|
-
return page;
|
|
1028
|
-
});
|
|
1029
|
-
const artifacts = bundle.envelope.artifacts.map((artifact, index) => {
|
|
1030
|
-
const page = html[artifactPages[index]];
|
|
1031
|
-
if (page === void 0) throw invalid2();
|
|
1032
|
-
return { ...artifact, html: page };
|
|
1033
|
-
});
|
|
1034
|
-
return { ...bundle, envelope: { ...bundle.envelope, artifacts } };
|
|
1035
|
-
}
|
|
1036
|
-
var client = createEncryptedWorkspaceClient({
|
|
1037
|
-
domain: "openplanr-design-workspace/v1",
|
|
1038
|
-
apiPath: DESIGN_WORKSPACE_API,
|
|
1039
|
-
reviewPath: DESIGN_SHARE_REVIEW_PATH,
|
|
1040
|
-
label: "Design",
|
|
1041
|
-
defaultBaseUrl: DESIGN_SHARE_BASE_URL,
|
|
1042
|
-
version: DESIGN_WORKSPACE_VERSION,
|
|
1043
|
-
maxBytes: DESIGN_WORKSPACE_MAX_BYTES,
|
|
1044
|
-
maxEventBytes: DESIGN_WORKSPACE_MAX_EVENT_BYTES,
|
|
1045
|
-
schemas: {
|
|
1046
|
-
create: DESIGN_WORKSPACE_CREATE_SCHEMA,
|
|
1047
|
-
event: DESIGN_WORKSPACE_EVENT_SCHEMA,
|
|
1048
|
-
revision: DESIGN_WORKSPACE_REVISION_SCHEMA,
|
|
1049
|
-
workspace: DESIGN_WORKSPACE_SCHEMA
|
|
1050
|
-
},
|
|
1051
|
-
assertContract: assertWorkspaceContract,
|
|
1052
|
-
assertBundle: assertDesignReviewBundle,
|
|
1053
|
-
assertFeedback(payload) {
|
|
1054
|
-
if (["category", "disposition"].includes(payload.kind)) assertDesignReviewMetadata(payload);
|
|
1055
|
-
if (!["review", "direction", "category", "disposition"].includes(payload.kind))
|
|
1056
|
-
throw new TypeError("Unknown design feedback kind.");
|
|
1057
|
-
if (typeof payload.author !== "string" || !payload.author.trim() || payload.author.length > 160)
|
|
1058
|
-
throw new TypeError("Enter your name before leaving feedback.");
|
|
1059
|
-
return payload;
|
|
1060
|
-
},
|
|
1061
|
-
digestInput: (bundle) => bundle.envelope,
|
|
1062
|
-
bundleDigest: (envelope) => sha256Hex(
|
|
1063
|
-
canonicalizeJson({
|
|
1064
|
-
schemaVersion: envelope.schemaVersion,
|
|
1065
|
-
...envelope.schemaVersion === "1.1.0" ? { sources: envelope.sources } : {},
|
|
1066
|
-
artifacts: envelope.artifacts,
|
|
1067
|
-
viewer: envelope.viewer
|
|
1068
|
-
})
|
|
1069
|
-
),
|
|
1070
|
-
packBundle: packDesignReviewBundle,
|
|
1071
|
-
unpackBundle: unpackDesignReviewBundle
|
|
1072
|
-
});
|
|
1073
|
-
var {
|
|
1074
|
-
encodeWorkspaceBytes,
|
|
1075
|
-
decodeWorkspaceBytes,
|
|
1076
|
-
newWorkspaceToken,
|
|
1077
|
-
newWorkspaceId,
|
|
1078
|
-
deriveWorkspaceAuthentication,
|
|
1079
|
-
canonicalWorkspacePublicKey,
|
|
1080
|
-
createWorkspaceSigner,
|
|
1081
|
-
signWorkspaceValue,
|
|
1082
|
-
verifyWorkspaceSignature,
|
|
1083
|
-
workspaceEnvelopeDigest
|
|
1084
|
-
} = client;
|
|
1085
|
-
var chunked = createChunkedWorkspaceClient({
|
|
1086
|
-
legacy: client,
|
|
1087
|
-
domain: "openplanr-design-workspace/v1",
|
|
1088
|
-
apiPath: DESIGN_WORKSPACE_API,
|
|
1089
|
-
assertBundle: assertDesignReviewBundle,
|
|
1090
|
-
assertFeedback: (payload) => {
|
|
1091
|
-
if (["category", "disposition"].includes(payload.kind)) assertDesignReviewMetadata(payload);
|
|
1092
|
-
if (!["review", "direction", "category", "disposition"].includes(payload.kind) || typeof payload.author !== "string" || !payload.author.trim() || payload.author.length > 160)
|
|
1093
|
-
throw new TypeError("Invalid design feedback.");
|
|
1094
|
-
return payload;
|
|
1095
|
-
},
|
|
1096
|
-
digestInput: (bundle) => bundle.envelope
|
|
1097
|
-
});
|
|
1098
|
-
var discoverWorkspaceCapabilities = chunked.capabilities;
|
|
1099
|
-
var prepareChunkedWorkspace = chunked.prepareWorkspace;
|
|
1100
|
-
var openWorkspaceRevision = chunked.openRevision;
|
|
1101
|
-
var prepareWorkspace = (bundle, options = {}) => options.transport === "2" ? chunked.prepareWorkspace(bundle, options) : client.prepareWorkspace(bundle, options);
|
|
1102
|
-
var commitWorkspace = (access, ...args) => (access.schemaVersion === "2.0.0" ? chunked : client).commitWorkspace(access, ...args);
|
|
1103
|
-
var getWorkspace = (access, ...args) => (access.schemaVersion === "2.0.0" ? chunked : client).getWorkspace(access, ...args);
|
|
1104
|
-
var decryptWorkspaceRevision = (access, ...args) => (access.schemaVersion === "2.0.0" ? chunked : client).decryptWorkspaceRevision(access, ...args);
|
|
1105
|
-
var prepareWorkspaceMutation = (access, ...args) => (access.schemaVersion === "2.0.0" ? chunked : client).prepareWorkspaceMutation(access, ...args);
|
|
1106
|
-
var commitWorkspaceMutation = (access, ...args) => (access.schemaVersion === "2.0.0" ? chunked : client).commitWorkspaceMutation(access, ...args);
|
|
1107
|
-
var prepareWorkspaceEvent = (access, ...args) => (access.schemaVersion === "2.0.0" ? chunked : client).prepareWorkspaceEvent(access, ...args);
|
|
1108
|
-
var appendWorkspaceEvent = (access, ...args) => (access.schemaVersion === "2.0.0" ? chunked : client).appendWorkspaceEvent(access, ...args);
|
|
1109
|
-
var readWorkspaceEvents = (access, ...args) => (access.schemaVersion === "2.0.0" ? chunked : client).readWorkspaceEvents(access, ...args);
|
|
1110
|
-
|
|
1111
|
-
// packages/artifact/lib/artifact/ui/annotations.mjs
|
|
1112
|
-
var ARTIFACT_ANNOTATION_EVENTS = Object.freeze({
|
|
1113
|
-
draft: "planr:artifact-annotation-draft",
|
|
1114
|
-
focus: "planr:artifact-annotation-focus"
|
|
1115
|
-
});
|
|
1116
|
-
var ARTIFACT_ANNOTATION_LIMITS = Object.freeze({
|
|
1117
|
-
dragThreshold: 4,
|
|
1118
|
-
maxCommentLength: 65536,
|
|
1119
|
-
maxIdentityLength: 256
|
|
1120
|
-
});
|
|
1121
|
-
var INTENTS = Object.freeze(["fix", "improve", "question"]);
|
|
1122
|
-
|
|
1123
|
-
// packages/artifact/lib/artifact/ui/feedback-rail.mjs
|
|
1124
|
-
var ARTIFACT_REVIEW_LIMITS = Object.freeze({
|
|
1125
|
-
id: 128,
|
|
1126
|
-
authorName: 256,
|
|
1127
|
-
artifactId: 128,
|
|
1128
|
-
variant: 128,
|
|
1129
|
-
anchor: 512,
|
|
1130
|
-
screen: 128,
|
|
1131
|
-
text: 65536,
|
|
1132
|
-
pins: 1e4,
|
|
1133
|
-
replies: 1e4,
|
|
1134
|
-
viewport: 16384
|
|
1135
|
-
});
|
|
1136
|
-
var ARTIFACT_REVIEW_DECISIONS = Object.freeze([
|
|
1137
|
-
"pending",
|
|
1138
|
-
"approved",
|
|
1139
|
-
"changes_requested"
|
|
1140
|
-
]);
|
|
1141
|
-
var ARTIFACT_REVIEW_INTENTS = Object.freeze(["fix", "improve", "question"]);
|
|
1142
|
-
var ARTIFACT_REVIEW_STATUSES = Object.freeze(["open", "addressed", "resolved"]);
|
|
1143
|
-
var REVIEW_OF_RE = /^[a-f0-9]{64}$/;
|
|
1144
|
-
var ArtifactReviewStateError = class extends Error {
|
|
1145
|
-
constructor(code, message) {
|
|
1146
|
-
super(message);
|
|
1147
|
-
this.name = "ArtifactReviewStateError";
|
|
1148
|
-
this.code = code;
|
|
1149
|
-
}
|
|
1150
|
-
};
|
|
1151
|
-
function invalid(message) {
|
|
1152
|
-
throw new ArtifactReviewStateError("E_ARTIFACT_REVIEW_INVALID", message);
|
|
1153
|
-
}
|
|
1154
|
-
function identityRequired() {
|
|
1155
|
-
throw new ArtifactReviewStateError(
|
|
1156
|
-
"E_ARTIFACT_REVIEW_IDENTITY_REQUIRED",
|
|
1157
|
-
"Enter your name before adding a comment."
|
|
1158
|
-
);
|
|
1159
|
-
}
|
|
1160
|
-
function boundedString(value, label, { min = 0, max, trim = false, pattern } = {}) {
|
|
1161
|
-
if (typeof value !== "string") invalid(`${label} must be a string.`);
|
|
1162
|
-
const normalized = trim ? value.trim() : value;
|
|
1163
|
-
if (normalized.length < min || max !== void 0 && normalized.length > max) {
|
|
1164
|
-
invalid(`${label} must contain ${min} through ${max ?? "unlimited"} characters.`);
|
|
1165
|
-
}
|
|
1166
|
-
if (pattern && !pattern.test(normalized)) invalid(`${label} has an invalid format.`);
|
|
1167
|
-
return normalized;
|
|
1168
|
-
}
|
|
1169
|
-
function optionalString(value, label, options) {
|
|
1170
|
-
if (value === void 0) return void 0;
|
|
1171
|
-
return boundedString(value, label, options);
|
|
1172
|
-
}
|
|
1173
|
-
function enumValue(value, values, label) {
|
|
1174
|
-
if (!values.includes(value)) invalid(`${label} must be one of: ${values.join(", ")}.`);
|
|
1175
|
-
return value;
|
|
1176
|
-
}
|
|
1177
|
-
function isoTimestamp(value, label) {
|
|
1178
|
-
const timestamp = value instanceof Date ? value.toISOString() : value;
|
|
1179
|
-
if (typeof timestamp !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(timestamp) || !Number.isFinite(Date.parse(timestamp))) {
|
|
1180
|
-
invalid(`${label} must be an ISO-8601 date-time.`);
|
|
1181
|
-
}
|
|
1182
|
-
return timestamp;
|
|
1183
|
-
}
|
|
1184
|
-
function normalizeArtifactReviewIdentity(value, { allowEmpty = false } = {}) {
|
|
1185
|
-
if (value === null || value === void 0 || value === "") {
|
|
1186
|
-
if (allowEmpty) return null;
|
|
1187
|
-
identityRequired();
|
|
1188
|
-
}
|
|
1189
|
-
const source = typeof value === "string" ? { name: value } : value;
|
|
1190
|
-
if (!source || typeof source !== "object" || Array.isArray(source)) identityRequired();
|
|
1191
|
-
const name = boundedString(source.name, "author.name", {
|
|
1192
|
-
min: 1,
|
|
1193
|
-
max: ARTIFACT_REVIEW_LIMITS.authorName,
|
|
1194
|
-
trim: true
|
|
1195
|
-
});
|
|
1196
|
-
const id = optionalString(source.id, "author.id", {
|
|
1197
|
-
min: 1,
|
|
1198
|
-
max: ARTIFACT_REVIEW_LIMITS.id,
|
|
1199
|
-
trim: true
|
|
1200
|
-
});
|
|
1201
|
-
return deepFreeze(id === void 0 ? { name } : { id, name });
|
|
1202
|
-
}
|
|
1203
|
-
function normalizeRegion(region) {
|
|
1204
|
-
if (!region || typeof region !== "object" || Array.isArray(region)) {
|
|
1205
|
-
invalid("pin.region must be an object.");
|
|
1206
|
-
}
|
|
1207
|
-
const finite = (value, label) => {
|
|
1208
|
-
if (typeof value !== "number" || !Number.isFinite(value)) invalid(`${label} must be finite.`);
|
|
1209
|
-
return value;
|
|
1210
|
-
};
|
|
1211
|
-
const x = finite(region.x, "pin.region.x");
|
|
1212
|
-
const y = finite(region.y, "pin.region.y");
|
|
1213
|
-
const w = finite(region.w, "pin.region.w");
|
|
1214
|
-
const h = finite(region.h, "pin.region.h");
|
|
1215
|
-
if (x >= 0 && y >= 0 && w >= 0 && h >= 0 && x + w <= 1 && y + h <= 1) {
|
|
1216
|
-
return { x, y, w, h };
|
|
1217
|
-
}
|
|
1218
|
-
const unit = (value) => Math.round(Math.min(1, Math.max(0, value)) * 1e6) / 1e6;
|
|
1219
|
-
const boundedX = unit(x);
|
|
1220
|
-
const boundedY = unit(y);
|
|
1221
|
-
return {
|
|
1222
|
-
x: boundedX,
|
|
1223
|
-
y: boundedY,
|
|
1224
|
-
w: Math.round(Math.min(unit(w), 1 - boundedX) * 1e6) / 1e6,
|
|
1225
|
-
h: Math.round(Math.min(unit(h), 1 - boundedY) * 1e6) / 1e6
|
|
1226
|
-
};
|
|
1227
|
-
}
|
|
1228
|
-
function normalizeViewport(viewport) {
|
|
1229
|
-
if (!viewport || typeof viewport !== "object" || Array.isArray(viewport)) {
|
|
1230
|
-
invalid("pin.viewport must be an object.");
|
|
1231
|
-
}
|
|
1232
|
-
const dimension = (value, label) => {
|
|
1233
|
-
if (!Number.isInteger(value) || value < 1 || value > ARTIFACT_REVIEW_LIMITS.viewport) {
|
|
1234
|
-
invalid(`${label} must be an integer from 1 through ${ARTIFACT_REVIEW_LIMITS.viewport}.`);
|
|
1235
|
-
}
|
|
1236
|
-
return value;
|
|
1237
|
-
};
|
|
1238
|
-
return {
|
|
1239
|
-
width: dimension(viewport.width, "pin.viewport.width"),
|
|
1240
|
-
height: dimension(viewport.height, "pin.viewport.height")
|
|
1241
|
-
};
|
|
1242
|
-
}
|
|
1243
|
-
function normalizeAnchor(anchor) {
|
|
1244
|
-
if (anchor === void 0 || anchor === null) return void 0;
|
|
1245
|
-
if (typeof anchor !== "object" || Array.isArray(anchor)) invalid("pin.anchor must be an object.");
|
|
1246
|
-
const planrId = boundedString(anchor.planrId, "pin.anchor.planrId", {
|
|
1247
|
-
min: 1,
|
|
1248
|
-
max: ARTIFACT_REVIEW_LIMITS.anchor,
|
|
1249
|
-
trim: true
|
|
1250
|
-
});
|
|
1251
|
-
const screen = optionalString(anchor.screen, "pin.anchor.screen", {
|
|
1252
|
-
min: 1,
|
|
1253
|
-
max: ARTIFACT_REVIEW_LIMITS.screen,
|
|
1254
|
-
trim: true
|
|
1255
|
-
});
|
|
1256
|
-
return screen === void 0 ? { planrId } : { planrId, screen };
|
|
1257
|
-
}
|
|
1258
|
-
function normalizeReply(reply, label = "reply") {
|
|
1259
|
-
if (!reply || typeof reply !== "object" || Array.isArray(reply))
|
|
1260
|
-
invalid(`${label} must be an object.`);
|
|
1261
|
-
return {
|
|
1262
|
-
id: boundedString(reply.id, `${label}.id`, {
|
|
1263
|
-
min: 1,
|
|
1264
|
-
max: ARTIFACT_REVIEW_LIMITS.id,
|
|
1265
|
-
trim: true
|
|
1266
|
-
}),
|
|
1267
|
-
author: normalizeArtifactReviewIdentity(reply.author),
|
|
1268
|
-
comment: boundedString(reply.comment, `${label}.comment`, {
|
|
1269
|
-
min: 1,
|
|
1270
|
-
max: ARTIFACT_REVIEW_LIMITS.text,
|
|
1271
|
-
trim: true
|
|
1272
|
-
}),
|
|
1273
|
-
createdAt: isoTimestamp(reply.createdAt, `${label}.createdAt`)
|
|
1274
|
-
};
|
|
1275
|
-
}
|
|
1276
|
-
function compareTimestampThenId(left, right) {
|
|
1277
|
-
const byTime = left.createdAt.localeCompare(right.createdAt);
|
|
1278
|
-
return byTime === 0 ? left.id.localeCompare(right.id) : byTime;
|
|
1279
|
-
}
|
|
1280
|
-
function normalizePin(pin, label = "pin") {
|
|
1281
|
-
if (!pin || typeof pin !== "object" || Array.isArray(pin)) invalid(`${label} must be an object.`);
|
|
1282
|
-
if (!Array.isArray(pin.replies) || pin.replies.length > ARTIFACT_REVIEW_LIMITS.replies) {
|
|
1283
|
-
invalid(`${label}.replies must contain no more than ${ARTIFACT_REVIEW_LIMITS.replies} items.`);
|
|
1284
|
-
}
|
|
1285
|
-
const replies = pin.replies.map(
|
|
1286
|
-
(reply, index) => normalizeReply(reply, `${label}.replies[${index}]`)
|
|
1287
|
-
);
|
|
1288
|
-
const replyIds = new Set(replies.map(({ id }) => id));
|
|
1289
|
-
if (replyIds.size !== replies.length) invalid(`${label}.replies must have unique ids.`);
|
|
1290
|
-
const normalized = {
|
|
1291
|
-
id: boundedString(pin.id, `${label}.id`, {
|
|
1292
|
-
min: 1,
|
|
1293
|
-
max: ARTIFACT_REVIEW_LIMITS.id,
|
|
1294
|
-
trim: true
|
|
1295
|
-
}),
|
|
1296
|
-
author: normalizeArtifactReviewIdentity(pin.author),
|
|
1297
|
-
artifactId: boundedString(pin.artifactId, `${label}.artifactId`, {
|
|
1298
|
-
min: 1,
|
|
1299
|
-
max: ARTIFACT_REVIEW_LIMITS.artifactId,
|
|
1300
|
-
trim: true
|
|
1301
|
-
}),
|
|
1302
|
-
region: normalizeRegion(pin.region),
|
|
1303
|
-
viewport: normalizeViewport(pin.viewport),
|
|
1304
|
-
intent: enumValue(pin.intent, ARTIFACT_REVIEW_INTENTS, `${label}.intent`),
|
|
1305
|
-
status: enumValue(pin.status, ARTIFACT_REVIEW_STATUSES, `${label}.status`),
|
|
1306
|
-
comment: boundedString(pin.comment, `${label}.comment`, {
|
|
1307
|
-
min: 1,
|
|
1308
|
-
max: ARTIFACT_REVIEW_LIMITS.text,
|
|
1309
|
-
trim: true
|
|
1310
|
-
}),
|
|
1311
|
-
replies: replies.sort(compareTimestampThenId),
|
|
1312
|
-
createdAt: isoTimestamp(pin.createdAt, `${label}.createdAt`),
|
|
1313
|
-
updatedAt: isoTimestamp(pin.updatedAt, `${label}.updatedAt`)
|
|
1314
|
-
};
|
|
1315
|
-
const variant = optionalString(pin.variant, `${label}.variant`, {
|
|
1316
|
-
min: 1,
|
|
1317
|
-
max: ARTIFACT_REVIEW_LIMITS.variant,
|
|
1318
|
-
trim: true
|
|
1319
|
-
});
|
|
1320
|
-
const anchor = normalizeAnchor(pin.anchor);
|
|
1321
|
-
if (variant !== void 0) normalized.variant = variant;
|
|
1322
|
-
if (anchor !== void 0) normalized.anchor = anchor;
|
|
1323
|
-
return normalized;
|
|
1324
|
-
}
|
|
1325
|
-
function normalizeArtifactReview2(review) {
|
|
1326
|
-
if (!review || typeof review !== "object" || Array.isArray(review)) {
|
|
1327
|
-
invalid("Artifact review must be an object.");
|
|
1328
|
-
}
|
|
1329
|
-
if (!Array.isArray(review.pins) || review.pins.length > ARTIFACT_REVIEW_LIMITS.pins) {
|
|
1330
|
-
invalid(`review.pins must contain no more than ${ARTIFACT_REVIEW_LIMITS.pins} items.`);
|
|
1331
|
-
}
|
|
1332
|
-
const pins = review.pins.map((pin, index) => normalizePin(pin, `review.pins[${index}]`));
|
|
1333
|
-
const pinIds = new Set(pins.map(({ id }) => id));
|
|
1334
|
-
if (pinIds.size !== pins.length) invalid("review.pins must have unique ids.");
|
|
1335
|
-
const normalized = {
|
|
1336
|
-
schemaVersion: enumValue(review.schemaVersion, ["1.0.0"], "review.schemaVersion"),
|
|
1337
|
-
reviewId: boundedString(review.reviewId, "review.reviewId", {
|
|
1338
|
-
min: 1,
|
|
1339
|
-
max: ARTIFACT_REVIEW_LIMITS.id,
|
|
1340
|
-
trim: true
|
|
1341
|
-
}),
|
|
1342
|
-
reviewOf: boundedString(review.reviewOf, "review.reviewOf", {
|
|
1343
|
-
min: 64,
|
|
1344
|
-
max: 64,
|
|
1345
|
-
pattern: REVIEW_OF_RE
|
|
1346
|
-
}),
|
|
1347
|
-
decision: enumValue(review.decision, ARTIFACT_REVIEW_DECISIONS, "review.decision"),
|
|
1348
|
-
overall: boundedString(review.overall, "review.overall", {
|
|
1349
|
-
max: ARTIFACT_REVIEW_LIMITS.text
|
|
1350
|
-
}),
|
|
1351
|
-
pins: pins.sort(compareTimestampThenId)
|
|
1352
|
-
};
|
|
1353
|
-
if (review.createdAt !== void 0)
|
|
1354
|
-
normalized.createdAt = isoTimestamp(review.createdAt, "review.createdAt");
|
|
1355
|
-
if (review.updatedAt !== void 0)
|
|
1356
|
-
normalized.updatedAt = isoTimestamp(review.updatedAt, "review.updatedAt");
|
|
1357
|
-
return deepFreeze(normalized);
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
// packages/design/lib/design/workspace-feedback.mjs
|
|
1361
|
-
function workspaceReviewerId(publicKey) {
|
|
1362
|
-
return sha256Hex(
|
|
1363
|
-
canonicalizeJson({ kty: publicKey.kty, crv: publicKey.crv, x: publicKey.x, y: publicKey.y })
|
|
1364
|
-
);
|
|
1365
|
-
}
|
|
1366
|
-
function mergeWorkspaceFeedback(events, { revisionId, reviewOf, ownerPublicKey } = {}) {
|
|
1367
|
-
if (!Array.isArray(events) || !revisionId || !/^[a-f0-9]{64}$/u.test(reviewOf))
|
|
1368
|
-
throw new TypeError("Feedback merge requires a revision and content digest.");
|
|
1369
|
-
const pins = /* @__PURE__ */ new Map();
|
|
1370
|
-
const pinOwners = /* @__PURE__ */ new Map();
|
|
1371
|
-
const replyOwners = /* @__PURE__ */ new Map();
|
|
1372
|
-
const overall = /* @__PURE__ */ new Map();
|
|
1373
|
-
const directions = /* @__PURE__ */ new Map();
|
|
1374
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1375
|
-
const revisionBases = /* @__PURE__ */ new Map();
|
|
1376
|
-
const acceptedEventIds = [];
|
|
1377
|
-
const issues = [];
|
|
1378
|
-
const categories = {}, dispositions = {};
|
|
1379
|
-
for (const event of [...events].sort((a, b) => a.sequence - b.sequence)) {
|
|
1380
|
-
try {
|
|
1381
|
-
if (!event || typeof event.id !== "string" || !event.id || typeof event.revisionId !== "string" || !/^[a-f0-9]{64}$/u.test(event.reviewOf ?? "") || !Number.isSafeInteger(event.sequence) || event.sequence < 1)
|
|
1382
|
-
throw new TypeError("Shared feedback has an invalid event identity or revision.");
|
|
1383
|
-
const eventHash = sha256Hex(canonicalizeJson(event));
|
|
1384
|
-
if (seen.has(event.id)) {
|
|
1385
|
-
if (seen.get(event.id) !== eventHash)
|
|
1386
|
-
throw new TypeError("Shared feedback reuses an event identity with changed bytes.");
|
|
1387
|
-
continue;
|
|
1388
|
-
}
|
|
1389
|
-
seen.set(event.id, eventHash);
|
|
1390
|
-
const boundReviewOf = revisionBases.get(event.revisionId);
|
|
1391
|
-
if (boundReviewOf && boundReviewOf !== event.reviewOf)
|
|
1392
|
-
throw new TypeError("A shared revision identity is bound to conflicting design content.");
|
|
1393
|
-
revisionBases.set(event.revisionId, event.reviewOf);
|
|
1394
|
-
if (event.revisionId !== revisionId) continue;
|
|
1395
|
-
if (event.reviewOf !== reviewOf || !event.publicKey?.x || !event.publicKey?.y)
|
|
1396
|
-
throw new TypeError("Shared feedback has an invalid revision or signer.");
|
|
1397
|
-
const signerId = workspaceReviewerId(event.publicKey);
|
|
1398
|
-
const payload = event.payload;
|
|
1399
|
-
if (!payload || typeof payload.author !== "string" || !payload.author.trim() || payload.author.length > 160 || payload.reviewOf !== reviewOf)
|
|
1400
|
-
throw new TypeError("Shared feedback has an invalid author or digest.");
|
|
1401
|
-
const author = { id: signerId, name: payload.author.trim() };
|
|
1402
|
-
if (["category", "disposition"].includes(payload.kind)) {
|
|
1403
|
-
assertDesignReviewMetadata(payload);
|
|
1404
|
-
if (!pins.has(payload.pinId))
|
|
1405
|
-
throw new TypeError("Review metadata targets an unknown pin.");
|
|
1406
|
-
const owner = ownerPublicKey?.x === event.publicKey.x && ownerPublicKey?.y === event.publicKey.y;
|
|
1407
|
-
if (payload.kind === "disposition" && !owner)
|
|
1408
|
-
throw new TypeError("Only the design owner can record a disposition.");
|
|
1409
|
-
if (payload.kind === "category" && !owner && pinOwners.get(payload.pinId) !== signerId)
|
|
1410
|
-
throw new TypeError("Only the comment author or owner can change its category.");
|
|
1411
|
-
if (payload.kind === "category") categories[payload.pinId] = payload.category;
|
|
1412
|
-
else
|
|
1413
|
-
dispositions[payload.pinId] = {
|
|
1414
|
-
disposition: payload.disposition,
|
|
1415
|
-
reason: payload.reason,
|
|
1416
|
-
updatedAt: payload.updatedAt,
|
|
1417
|
-
author: author.name
|
|
1418
|
-
};
|
|
1419
|
-
acceptedEventIds.push(event.id);
|
|
1420
|
-
continue;
|
|
1421
|
-
}
|
|
1422
|
-
if (payload.kind === "direction") {
|
|
1423
|
-
for (const value of [payload.ratings ?? {}, payload.remix ?? {}])
|
|
1424
|
-
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length > 256)
|
|
1425
|
-
throw new TypeError("Shared direction feedback is too large or invalid.");
|
|
1426
|
-
const ratings = {};
|
|
1427
|
-
const remix = {};
|
|
1428
|
-
for (const [id, rating] of Object.entries(payload.ratings ?? {})) {
|
|
1429
|
-
if (Number.isInteger(rating) && rating >= 1 && rating <= 5) ratings[id] = rating;
|
|
1430
|
-
}
|
|
1431
|
-
for (const [id, note] of Object.entries(payload.remix ?? {})) {
|
|
1432
|
-
if (typeof note === "string" && note.length <= 8192) remix[id] = note;
|
|
1433
|
-
}
|
|
1434
|
-
directions.set(signerId, { signerId, author: author.name, revisionId, ratings, remix });
|
|
1435
|
-
acceptedEventIds.push(event.id);
|
|
1436
|
-
continue;
|
|
1437
|
-
}
|
|
1438
|
-
if (payload.kind !== "review" || payload.review?.reviewOf !== reviewOf)
|
|
1439
|
-
throw new TypeError("Shared review snapshot does not match its signed revision.");
|
|
1440
|
-
const review2 = normalizeArtifactReview2(payload.review);
|
|
1441
|
-
const additions = review2.pins.filter((pin) => !pins.has(pin.id)).length;
|
|
1442
|
-
if (pins.size + additions > ARTIFACT_REVIEW_LIMITS.pins)
|
|
1443
|
-
throw new TypeError("Shared feedback exceeds the total pin limit.");
|
|
1444
|
-
for (const incoming of review2.pins) {
|
|
1445
|
-
const previous = pins.get(incoming.id);
|
|
1446
|
-
const replies = /* @__PURE__ */ new Set([
|
|
1447
|
-
...(previous?.replies ?? []).map((reply) => reply.id),
|
|
1448
|
-
...incoming.replies.map((reply) => reply.id)
|
|
1449
|
-
]);
|
|
1450
|
-
if (replies.size > ARTIFACT_REVIEW_LIMITS.replies)
|
|
1451
|
-
throw new TypeError("Shared feedback exceeds the thread reply limit.");
|
|
1452
|
-
}
|
|
1453
|
-
overall.set(signerId, { author: author.name, note: review2.overall });
|
|
1454
|
-
for (const incoming of review2.pins) {
|
|
1455
|
-
const previous = pins.get(incoming.id);
|
|
1456
|
-
if (!previous) {
|
|
1457
|
-
pins.set(incoming.id, { ...structuredClone(incoming), author, replies: [] });
|
|
1458
|
-
pinOwners.set(incoming.id, signerId);
|
|
1459
|
-
} else if (pinOwners.get(incoming.id) === signerId) {
|
|
1460
|
-
pins.set(incoming.id, {
|
|
1461
|
-
...previous,
|
|
1462
|
-
comment: incoming.comment,
|
|
1463
|
-
status: incoming.status,
|
|
1464
|
-
updatedAt: incoming.updatedAt,
|
|
1465
|
-
author,
|
|
1466
|
-
replies: previous.replies
|
|
1467
|
-
});
|
|
1468
|
-
}
|
|
1469
|
-
const pin = pins.get(incoming.id);
|
|
1470
|
-
for (const reply of incoming.replies) {
|
|
1471
|
-
const key = `${incoming.id}:${reply.id}`;
|
|
1472
|
-
if (!replyOwners.has(key)) {
|
|
1473
|
-
replyOwners.set(key, signerId);
|
|
1474
|
-
pin.replies.push({ ...structuredClone(reply), author });
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
acceptedEventIds.push(event.id);
|
|
1479
|
-
} catch (error) {
|
|
1480
|
-
issues.push({ eventId: event.id, sequence: event.sequence, reason: error.message });
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
const review = normalizeArtifactReview2({
|
|
1484
|
-
schemaVersion: "1.0.0",
|
|
1485
|
-
reviewId: `shared-${revisionId}`,
|
|
1486
|
-
reviewOf,
|
|
1487
|
-
decision: "pending",
|
|
1488
|
-
overall: [...overall.values()].filter(({ note }) => note).map(({ author, note }) => `${author}: ${note}`).join("\n\n").slice(0, 16384),
|
|
1489
|
-
pins: [...pins.values()]
|
|
1490
|
-
});
|
|
1491
|
-
return {
|
|
1492
|
-
review,
|
|
1493
|
-
directions: [...directions.values()],
|
|
1494
|
-
metadata: { categories, dispositions },
|
|
1495
|
-
acceptedEventIds,
|
|
1496
|
-
issues
|
|
1497
|
-
};
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
// packages/design/lib/design/share.mjs
|
|
1501
|
-
function prepareDesignShareBundle(file) {
|
|
1502
|
-
const current = currentDesign(file);
|
|
1503
|
-
const saved = readJson(join3(current.root, ".design/studio-state.json"), { state: {} }).state;
|
|
1504
|
-
return bundleDesignRevision(current, saved);
|
|
1505
|
-
}
|
|
1506
|
-
async function withCustody(file, options, action) {
|
|
1507
|
-
const location = custodyLocation(file, options);
|
|
1508
|
-
ensurePrivateDirectory(location.root, { label: "Design" });
|
|
1509
|
-
const unlock = await acquireStartLock(`${location.path}.lock`);
|
|
1510
|
-
try {
|
|
1511
|
-
let record = readCustody(location.path, { label: "Design", format: FORMAT });
|
|
1512
|
-
if (!record && location.legacyPath && existsSync4(location.legacyPath)) {
|
|
1513
|
-
readCustody(location.legacyPath, { label: "Design", format: FORMAT });
|
|
1514
|
-
const legacyUnlock = await acquireStartLock(`${location.legacyPath}.lock`);
|
|
1515
|
-
try {
|
|
1516
|
-
const legacy = readCustody(location.legacyPath, { label: "Design", format: FORMAT });
|
|
1517
|
-
if (legacy && !existsSync4(location.path)) {
|
|
1518
|
-
const temporary = `${location.path}.${newWorkspaceId()}.migration`;
|
|
1519
|
-
const fd = openSync(temporary, "wx", 384);
|
|
1520
|
-
try {
|
|
1521
|
-
writeFileSync3(fd, `${JSON.stringify(legacy)}
|
|
1522
|
-
`);
|
|
1523
|
-
fsyncSync(fd);
|
|
1524
|
-
} finally {
|
|
1525
|
-
closeSync(fd);
|
|
1526
|
-
}
|
|
1527
|
-
try {
|
|
1528
|
-
linkSync(temporary, location.path);
|
|
1529
|
-
const directory = openSync(location.root, "r");
|
|
1530
|
-
try {
|
|
1531
|
-
fsyncSync(directory);
|
|
1532
|
-
} finally {
|
|
1533
|
-
closeSync(directory);
|
|
1534
|
-
}
|
|
1535
|
-
} finally {
|
|
1536
|
-
unlinkSync(temporary);
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
record = readCustody(location.path, { label: "Design", format: FORMAT });
|
|
1540
|
-
} finally {
|
|
1541
|
-
legacyUnlock();
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
return await action({
|
|
1545
|
-
...location,
|
|
1546
|
-
record,
|
|
1547
|
-
save(value = record) {
|
|
1548
|
-
record = value;
|
|
1549
|
-
writeCustody(location.path, value, { label: "Design" });
|
|
1550
|
-
}
|
|
1551
|
-
});
|
|
1552
|
-
} finally {
|
|
1553
|
-
unlock();
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
function uploadOptions(custody, options) {
|
|
1557
|
-
const body = custody.pendingCreate ?? (custody.pendingMutation?.action === "publish" ? custody.pendingMutation.body : null);
|
|
1558
|
-
return {
|
|
1559
|
-
fetchImpl: options.fetchImpl,
|
|
1560
|
-
...custody.schemaVersion === "2.0.0" && body ? { readChunk: spoolChunkReader(custody.spoolDirectory, body) } : {}
|
|
1561
|
-
};
|
|
1562
|
-
}
|
|
1563
|
-
async function commitMutation(record, save, options) {
|
|
1564
|
-
try {
|
|
1565
|
-
return await commitWorkspaceMutation(
|
|
1566
|
-
record.custody,
|
|
1567
|
-
uploadOptions(record.custody, options)
|
|
1568
|
-
);
|
|
1569
|
-
} catch (error) {
|
|
1570
|
-
if (error.status === 409 && record.custody.pendingMutation) {
|
|
1571
|
-
const pending = structuredClone(record.custody.pendingMutation);
|
|
1572
|
-
try {
|
|
1573
|
-
const remote = await getWorkspace(record.custody, {
|
|
1574
|
-
fetchImpl: options.fetchImpl
|
|
1575
|
-
});
|
|
1576
|
-
if (remote.version > pending.body.expectedVersion) {
|
|
1577
|
-
record.conflictedMutation = {
|
|
1578
|
-
...pending,
|
|
1579
|
-
localRevision: record.pendingRevision ?? null,
|
|
1580
|
-
...record.custody.spoolDirectory ? { spoolDirectory: record.custody.spoolDirectory } : {}
|
|
1581
|
-
};
|
|
1582
|
-
delete record.custody.spoolDirectory;
|
|
1583
|
-
delete record.custody.pendingMutation;
|
|
1584
|
-
delete record.pendingRevision;
|
|
1585
|
-
save(record);
|
|
1586
|
-
}
|
|
1587
|
-
} catch {
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
throw error;
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
async function shareDesign(file, options = {}) {
|
|
1594
|
-
return withCustody(file, options, async ({ record, current, save, root }) => {
|
|
1595
|
-
if (record?.deleted)
|
|
1596
|
-
throw new Error(
|
|
1597
|
-
"This shared review was deleted. Create a new design identity to share a new review."
|
|
1598
|
-
);
|
|
1599
|
-
if (!record) {
|
|
1600
|
-
let transport = options.transport;
|
|
1601
|
-
if (!transport) {
|
|
1602
|
-
try {
|
|
1603
|
-
await discoverWorkspaceCapabilities({
|
|
1604
|
-
baseUrl: options.baseUrl ?? options.env?.OPENPLANR_SHARE_BASE ?? process.env.OPENPLANR_SHARE_BASE ?? "https://share.openplanr.dev",
|
|
1605
|
-
fetchImpl: options.fetchImpl
|
|
1606
|
-
});
|
|
1607
|
-
transport = "2";
|
|
1608
|
-
} catch (error) {
|
|
1609
|
-
if (![404, 426].includes(error.status)) throw error;
|
|
1610
|
-
transport = "1";
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
let custody;
|
|
1614
|
-
try {
|
|
1615
|
-
custody = await prepareWorkspace(prepareDesignShareBundle(file), {
|
|
1616
|
-
transport,
|
|
1617
|
-
baseUrl: options.baseUrl ?? options.env?.OPENPLANR_SHARE_BASE ?? process.env.OPENPLANR_SHARE_BASE ?? "https://share.openplanr.dev"
|
|
1618
|
-
});
|
|
1619
|
-
} catch (error) {
|
|
1620
|
-
if (transport === "1" && error.code === "E_WORKSPACE_PAYLOAD_TOO_LARGE")
|
|
1621
|
-
throw Object.assign(
|
|
1622
|
-
new Error(
|
|
1623
|
-
"This sharing service needs the bounded resource upload upgrade. Retry after the hosted service is updated; your local work is unchanged."
|
|
1624
|
-
),
|
|
1625
|
-
{ code: "E_WORKSPACE_TRANSPORT_UNSUPPORTED", status: 426 }
|
|
1626
|
-
);
|
|
1627
|
-
throw error;
|
|
1628
|
-
}
|
|
1629
|
-
if (custody.schemaVersion === "2.0.0")
|
|
1630
|
-
await persistUploadSpool(custody, join3(root, "uploads"));
|
|
1631
|
-
record = {
|
|
1632
|
-
schemaVersion: "1.0.0",
|
|
1633
|
-
kind: FORMAT,
|
|
1634
|
-
designId: current.document.id,
|
|
1635
|
-
custody,
|
|
1636
|
-
publishedRevision: null,
|
|
1637
|
-
pendingRevision: current.revision,
|
|
1638
|
-
pendingPresentation: presentationFingerprint(current),
|
|
1639
|
-
lastEvent: 0
|
|
1640
|
-
};
|
|
1641
|
-
save(record);
|
|
1642
|
-
}
|
|
1643
|
-
if (record.custody.pendingCreate) {
|
|
1644
|
-
await commitWorkspace(record.custody, uploadOptions(record.custody, options));
|
|
1645
|
-
record.publishedRevision = record.pendingRevision;
|
|
1646
|
-
record.publishedPresentation = record.pendingPresentation;
|
|
1647
|
-
delete record.pendingRevision;
|
|
1648
|
-
delete record.pendingPresentation;
|
|
1649
|
-
save(record);
|
|
1650
|
-
}
|
|
1651
|
-
return safeStatus(record, current);
|
|
1652
|
-
});
|
|
1653
|
-
}
|
|
1654
|
-
async function publishDesignShare(file, options = {}) {
|
|
1655
|
-
return withCustody(file, options, async ({ record, current, save, root }) => {
|
|
1656
|
-
if (!record || record.custody.pendingCreate)
|
|
1657
|
-
throw new Error("Create the shared review before publishing an update.");
|
|
1658
|
-
if (record.deleted || record.revoked)
|
|
1659
|
-
throw new Error("Access was revoked or the review was deleted.");
|
|
1660
|
-
if (record.custody.pendingMutation && record.custody.pendingMutation.action !== "publish")
|
|
1661
|
-
throw new Error(
|
|
1662
|
-
`Retry the pending ${record.custody.pendingMutation.action} operation first.`
|
|
1663
|
-
);
|
|
1664
|
-
if (!record.custody.pendingMutation) {
|
|
1665
|
-
await getWorkspace(record.custody, { fetchImpl: options.fetchImpl });
|
|
1666
|
-
await prepareWorkspaceMutation(
|
|
1667
|
-
record.custody,
|
|
1668
|
-
"publish",
|
|
1669
|
-
prepareDesignShareBundle(file)
|
|
1670
|
-
);
|
|
1671
|
-
if (record.custody.schemaVersion === "2.0.0")
|
|
1672
|
-
await persistUploadSpool(record.custody, join3(root, "uploads"));
|
|
1673
|
-
record.pendingRevision = current.revision;
|
|
1674
|
-
record.pendingPresentation = presentationFingerprint(current);
|
|
1675
|
-
save(record);
|
|
1676
|
-
}
|
|
1677
|
-
await commitMutation(record, save, options);
|
|
1678
|
-
record.publishedRevision = record.pendingRevision;
|
|
1679
|
-
record.publishedPresentation = record.pendingPresentation;
|
|
1680
|
-
delete record.pendingRevision;
|
|
1681
|
-
delete record.pendingPresentation;
|
|
1682
|
-
save(record);
|
|
1683
|
-
return safeStatus(record, current);
|
|
1684
|
-
});
|
|
1685
|
-
}
|
|
1686
|
-
async function manageDesignShare(file, action, options = {}) {
|
|
1687
|
-
if (!["rotate", "pause", "resume", "revoke", "delete", "access"].includes(action))
|
|
1688
|
-
throw new Error("Unknown design sharing action.");
|
|
1689
|
-
return withCustody(file, options, async ({ record, current, save, root }) => {
|
|
1690
|
-
if (!record || record.custody.pendingCreate) throw new Error("Create the shared review first.");
|
|
1691
|
-
if (action === "access") {
|
|
1692
|
-
if (record.revoked || record.deleted) throw new Error("This review is no longer accessible.");
|
|
1693
|
-
return { ...safeStatus(record, current), token: record.custody.token };
|
|
1694
|
-
}
|
|
1695
|
-
if (action === "rotate") await flushOwnerMetadata(record, save, options);
|
|
1696
|
-
if (record.custody.pendingMutation && record.custody.pendingMutation.action !== action)
|
|
1697
|
-
throw new Error(
|
|
1698
|
-
`Retry the pending ${record.custody.pendingMutation.action} operation first.`
|
|
1699
|
-
);
|
|
1700
|
-
if (!record.custody.pendingMutation) {
|
|
1701
|
-
if (!record.revoked)
|
|
1702
|
-
await getWorkspace(record.custody, { fetchImpl: options.fetchImpl });
|
|
1703
|
-
await prepareWorkspaceMutation(record.custody, action);
|
|
1704
|
-
save(record);
|
|
1705
|
-
}
|
|
1706
|
-
await commitMutation(record, save, options);
|
|
1707
|
-
if (action === "pause" || action === "resume") record.commentsPaused = action === "pause";
|
|
1708
|
-
if (action === "revoke") record.revoked = true;
|
|
1709
|
-
if (action === "delete") record.deleted = true;
|
|
1710
|
-
save(record);
|
|
1711
|
-
return safeStatus(record, current);
|
|
1712
|
-
});
|
|
1713
|
-
}
|
|
1714
|
-
async function flushOwnerMetadata(record, save, options) {
|
|
1715
|
-
while (record.pendingReviewMetadata?.length) {
|
|
1716
|
-
await appendWorkspaceEvent(record.custody, null, {
|
|
1717
|
-
preparedEvent: record.pendingReviewMetadata[0],
|
|
1718
|
-
fetchImpl: options.fetchImpl
|
|
1719
|
-
});
|
|
1720
|
-
record.pendingReviewMetadata.shift();
|
|
1721
|
-
save(record);
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
|
-
async function publishDesignReviewMetadata(file, payload, { revisionId, ...options } = {}) {
|
|
1725
|
-
const location = custodyLocation(file, options, { allowMissing: true });
|
|
1726
|
-
if (!existsSync4(location.path) || !revisionId) return { shared: false };
|
|
1727
|
-
return withCustody(file, options, async ({ record, save }) => {
|
|
1728
|
-
if (!record || record.deleted || record.revoked || record.custody.pendingCreate)
|
|
1729
|
-
return { shared: false };
|
|
1730
|
-
const event = await prepareWorkspaceEvent(record.custody, payload, {
|
|
1731
|
-
revisionId,
|
|
1732
|
-
reviewOf: payload.reviewOf,
|
|
1733
|
-
signer: {
|
|
1734
|
-
privateKey: record.custody.ownerPrivateKey,
|
|
1735
|
-
publicKey: record.custody.ownerPublicKey
|
|
1736
|
-
}
|
|
1737
|
-
});
|
|
1738
|
-
record.pendingReviewMetadata ??= [];
|
|
1739
|
-
record.pendingReviewMetadata.push(event);
|
|
1740
|
-
save(record);
|
|
1741
|
-
try {
|
|
1742
|
-
await flushOwnerMetadata(record, save, options);
|
|
1743
|
-
return { shared: true, pending: false };
|
|
1744
|
-
} catch (error) {
|
|
1745
|
-
return { shared: true, pending: true, error: error.message };
|
|
1746
|
-
}
|
|
1747
|
-
});
|
|
1748
|
-
}
|
|
1749
|
-
async function exportDesignShareRecovery(file, { output, ...options } = {}) {
|
|
1750
|
-
if (!output) throw new Error("Recovery export requires a new private output path.");
|
|
1751
|
-
return withCustody(file, options, async ({ record }) => {
|
|
1752
|
-
if (!record) throw new Error("Create the shared review first.");
|
|
1753
|
-
const target = resolve3(output);
|
|
1754
|
-
mkdirSync3(dirname4(target), { recursive: true, mode: 448 });
|
|
1755
|
-
const recovery = structuredClone(record);
|
|
1756
|
-
if (record.custody.schemaVersion === "2.0.0" && (record.custody.pendingCreate || record.custody.pendingMutation?.action === "publish")) {
|
|
1757
|
-
const spool = `${target}.upload`;
|
|
1758
|
-
await copyUploadSpool(record.custody, spool);
|
|
1759
|
-
recovery.recoverySpool = basename(spool);
|
|
1760
|
-
recovery.custody.spoolDirectory = spool;
|
|
1761
|
-
}
|
|
1762
|
-
writeFileSync3(target, `${JSON.stringify(recovery, null, 2)}
|
|
1763
|
-
`, { flag: "wx", mode: 384 });
|
|
1764
|
-
return { ok: true, output: target };
|
|
1765
|
-
});
|
|
1766
|
-
}
|
|
1767
|
-
async function importDesignShareRecovery(file, { input, ...options } = {}) {
|
|
1768
|
-
if (!input) throw new Error("Recovery restore requires --input <private recovery file>.");
|
|
1769
|
-
const recovered = readCustody(resolve3(input), {
|
|
1770
|
-
label: "Design",
|
|
1771
|
-
format: FORMAT,
|
|
1772
|
-
recoveryInput: true
|
|
1773
|
-
});
|
|
1774
|
-
if (!recovered) throw new Error("Recovery file could not be found.");
|
|
1775
|
-
const custody = recovered.custody;
|
|
1776
|
-
workspaceReviewUrl(custody);
|
|
1777
|
-
const proof = await signWorkspaceValue(
|
|
1778
|
-
{ recovery: custody.id, nonce: newWorkspaceId() },
|
|
1779
|
-
custody.ownerPrivateKey
|
|
1780
|
-
);
|
|
1781
|
-
if (!await verifyWorkspaceSignature(proof, custody.ownerPublicKey))
|
|
1782
|
-
throw new Error("Recovery private key does not match its owner identity.");
|
|
1783
|
-
await deriveWorkspaceAuthentication(custody.token, custody.id);
|
|
1784
|
-
if (!/^[A-Za-z0-9_-]{43}$/u.test(custody.ownerAuth ?? ""))
|
|
1785
|
-
throw new Error("Recovery owner capability is invalid.");
|
|
1786
|
-
return withCustody(file, options, async ({ record, current, save, root }) => {
|
|
1787
|
-
if (recovered.designId !== current.document.id)
|
|
1788
|
-
throw new Error("Recovery belongs to a different design.");
|
|
1789
|
-
if (record && (record.custody.id !== custody.id || JSON.stringify(record.custody.ownerPublicKey) !== JSON.stringify(custody.ownerPublicKey)))
|
|
1790
|
-
throw new Error(
|
|
1791
|
-
"This design already has different owner keys. Recovery will not overwrite them."
|
|
1792
|
-
);
|
|
1793
|
-
if (record && record.custody.version > custody.version)
|
|
1794
|
-
throw new Error("This recovery file is older than the locally saved owner keys.");
|
|
1795
|
-
if (!custody.pendingCreate && !recovered.deleted && !recovered.revoked) {
|
|
1796
|
-
await getWorkspace(custody, { fetchImpl: options.fetchImpl });
|
|
1797
|
-
const bundle = await decryptWorkspaceRevision(custody, custody.currentRevision, {
|
|
1798
|
-
fetchImpl: options.fetchImpl
|
|
1799
|
-
});
|
|
1800
|
-
if (bundle.design.id !== current.document.id)
|
|
1801
|
-
throw new Error("Recovery belongs to a different design.");
|
|
1802
|
-
}
|
|
1803
|
-
if (custody.schemaVersion === "2.0.0" && (custody.pendingCreate || custody.pendingMutation?.action === "publish")) {
|
|
1804
|
-
if (recovered.recoverySpool !== `${basename(resolve3(input))}.upload`)
|
|
1805
|
-
throw new Error("Pending upload recovery requires its matching binary sidecar.");
|
|
1806
|
-
custody.spoolDirectory = join3(dirname4(resolve3(input)), recovered.recoverySpool);
|
|
1807
|
-
const pending = custody.pendingCreate ?? custody.pendingMutation.body;
|
|
1808
|
-
const destination = join3(root, "uploads", custody.id, pending.operationId);
|
|
1809
|
-
if (existsSync4(destination)) {
|
|
1810
|
-
const { spoolChunkReader: spoolChunkReader2 } = await import("./design-runtime-support-25485dbf1cb2.mjs");
|
|
1811
|
-
const reader = spoolChunkReader2(destination, pending);
|
|
1812
|
-
for (const part of pending.manifest.chunks) await reader(part.index);
|
|
1813
|
-
} else await copyUploadSpool(custody, destination);
|
|
1814
|
-
custody.spoolDirectory = destination;
|
|
1815
|
-
delete recovered.recoverySpool;
|
|
1816
|
-
}
|
|
1817
|
-
recovered.lastEvent = 0;
|
|
1818
|
-
save(recovered);
|
|
1819
|
-
return { ...safeStatus(recovered, current), restored: true };
|
|
1820
|
-
});
|
|
1821
|
-
}
|
|
1822
|
-
async function syncDesignShare(file, options = {}) {
|
|
1823
|
-
const location = custodyLocation(file, options, { allowMissing: true });
|
|
1824
|
-
if (!existsSync4(location.path)) return { ok: true, shared: false, imported: 0 };
|
|
1825
|
-
return withCustody(file, options, async ({ record, current, save, root }) => {
|
|
1826
|
-
if (!record || record.custody.pendingCreate || record.deleted)
|
|
1827
|
-
return { ok: true, shared: Boolean(record), imported: 0 };
|
|
1828
|
-
await flushOwnerMetadata(record, save, options);
|
|
1829
|
-
const reviewKey = `design-${hash(current.document.id).slice(0, 24)}`;
|
|
1830
|
-
const reviewPath = resolveArtifactReviewDestination({
|
|
1831
|
-
cwd: current.root,
|
|
1832
|
-
env: options.env ?? process.env,
|
|
1833
|
-
artifactId: reviewKey
|
|
1834
|
-
}).path;
|
|
1835
|
-
const currentDigest = digestArtifactEnvelope(current.envelope);
|
|
1836
|
-
let imported = 0, hasMore = true, issues = [];
|
|
1837
|
-
const ledgerPath = join3(current.root, ".design/shared-feedback.json");
|
|
1838
|
-
while (hasMore) {
|
|
1839
|
-
const page = await readWorkspaceEvents(record.custody, {
|
|
1840
|
-
after: record.lastEvent ?? 0,
|
|
1841
|
-
fetchImpl: options.fetchImpl
|
|
1842
|
-
});
|
|
1843
|
-
const earlier = readJson(ledgerPath, {
|
|
1844
|
-
schemaVersion: "1.0.0",
|
|
1845
|
-
events: [],
|
|
1846
|
-
issues: [],
|
|
1847
|
-
importedReviews: {}
|
|
1848
|
-
});
|
|
1849
|
-
const events = [...earlier.events];
|
|
1850
|
-
const eventBytes = new Map(events.map((event) => [event.id, canonicalizeJson(event)]));
|
|
1851
|
-
const revisionBases = /* @__PURE__ */ new Map();
|
|
1852
|
-
for (const event of events) {
|
|
1853
|
-
const previous = revisionBases.get(event.revisionId);
|
|
1854
|
-
if (previous && previous !== event.reviewOf)
|
|
1855
|
-
throw new Error(
|
|
1856
|
-
"Saved shared feedback binds one revision to conflicting design content."
|
|
1857
|
-
);
|
|
1858
|
-
revisionBases.set(event.revisionId, event.reviewOf);
|
|
1859
|
-
}
|
|
1860
|
-
const pageIssues = [...page.issues ?? []];
|
|
1861
|
-
for (const event of page.events ?? []) {
|
|
1862
|
-
if (eventBytes.has(event.id)) {
|
|
1863
|
-
if (eventBytes.get(event.id) !== canonicalizeJson(event))
|
|
1864
|
-
pageIssues.push({
|
|
1865
|
-
id: event.id,
|
|
1866
|
-
sequence: event.sequence,
|
|
1867
|
-
reason: "Shared feedback reuses an event identity with changed bytes."
|
|
1868
|
-
});
|
|
1869
|
-
continue;
|
|
1870
|
-
}
|
|
1871
|
-
try {
|
|
1872
|
-
const boundReviewOf = revisionBases.get(event.revisionId);
|
|
1873
|
-
if (boundReviewOf && boundReviewOf !== event.reviewOf)
|
|
1874
|
-
throw new Error("A shared revision identity is bound to conflicting design content.");
|
|
1875
|
-
const candidate = mergeWorkspaceFeedback([...events, event], {
|
|
1876
|
-
revisionId: event.revisionId,
|
|
1877
|
-
reviewOf: event.reviewOf,
|
|
1878
|
-
ownerPublicKey: record.custody.ownerPublicKey
|
|
1879
|
-
});
|
|
1880
|
-
const invalid2 = candidate.issues?.find(
|
|
1881
|
-
(issue) => (issue.id ?? issue.eventId) === event.id
|
|
1882
|
-
);
|
|
1883
|
-
if (invalid2) throw new Error(invalid2.reason);
|
|
1884
|
-
events.push(event);
|
|
1885
|
-
eventBytes.set(event.id, canonicalizeJson(event));
|
|
1886
|
-
revisionBases.set(event.revisionId, event.reviewOf);
|
|
1887
|
-
imported++;
|
|
1888
|
-
} catch (error) {
|
|
1889
|
-
pageIssues.push({ id: event.id, sequence: event.sequence, reason: error.message });
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
const revisions = /* @__PURE__ */ new Map();
|
|
1893
|
-
for (const event of events) {
|
|
1894
|
-
const previous = revisions.get(event.revisionId);
|
|
1895
|
-
if (previous && previous !== event.reviewOf)
|
|
1896
|
-
throw new Error(
|
|
1897
|
-
"Saved shared feedback binds one revision to conflicting design content."
|
|
1898
|
-
);
|
|
1899
|
-
revisions.set(event.revisionId, event.reviewOf);
|
|
1900
|
-
}
|
|
1901
|
-
const importedReviews = { ...earlier.importedReviews ?? {} }, directions = [], metadataByRevision = {};
|
|
1902
|
-
await withArtifactReviewLock(reviewPath, () => {
|
|
1903
|
-
const ledger = readArtifactReviewState(reviewPath, { allowMissing: true }) ?? createReviewLedger({ artifactId: reviewKey, currentReviewOf: currentDigest });
|
|
1904
|
-
const entries = new Map(ledger.reviews.map((entry) => [entry.review.reviewId, entry]));
|
|
1905
|
-
for (const [revisionId, reviewOf] of revisions) {
|
|
1906
|
-
const merged = mergeWorkspaceFeedback(events, {
|
|
1907
|
-
revisionId,
|
|
1908
|
-
reviewOf,
|
|
1909
|
-
ownerPublicKey: record.custody.ownerPublicKey
|
|
1910
|
-
});
|
|
1911
|
-
metadataByRevision[revisionId] = merged.metadata;
|
|
1912
|
-
directions.push(...merged.directions);
|
|
1913
|
-
pageIssues.push(...merged.issues ?? []);
|
|
1914
|
-
const review = structuredClone(merged.review);
|
|
1915
|
-
const previous = entries.get(review.reviewId)?.review;
|
|
1916
|
-
const previousImport = earlier.importedReviews?.[review.reviewId];
|
|
1917
|
-
importedReviews[review.reviewId] = structuredClone(review);
|
|
1918
|
-
if (previous) {
|
|
1919
|
-
review.decision = previous.decision;
|
|
1920
|
-
if (previousImport && previous.overall !== previousImport.overall)
|
|
1921
|
-
review.overall = previous.overall;
|
|
1922
|
-
review.pins = review.pins.map((pin) => {
|
|
1923
|
-
const saved = previous.pins.find((item) => item.id === pin.id);
|
|
1924
|
-
const importedPin = previousImport?.pins.find((item) => item.id === pin.id);
|
|
1925
|
-
if (!saved) return pin;
|
|
1926
|
-
const replies = new Map(
|
|
1927
|
-
[...pin.replies, ...saved.replies].map((reply) => [reply.id, reply])
|
|
1928
|
-
);
|
|
1929
|
-
return {
|
|
1930
|
-
...pin,
|
|
1931
|
-
...importedPin && saved.status !== importedPin.status ? { status: saved.status, updatedAt: saved.updatedAt } : {},
|
|
1932
|
-
replies: [...replies.values()]
|
|
1933
|
-
};
|
|
1934
|
-
});
|
|
1935
|
-
}
|
|
1936
|
-
entries.set(review.reviewId, { review, stale: reviewOf !== currentDigest });
|
|
1937
|
-
}
|
|
1938
|
-
writeArtifactReviewState(
|
|
1939
|
-
reviewPath,
|
|
1940
|
-
createReviewLedger({
|
|
1941
|
-
artifactId: reviewKey,
|
|
1942
|
-
currentReviewOf: currentDigest,
|
|
1943
|
-
reviews: [...entries.values()]
|
|
1944
|
-
})
|
|
1945
|
-
);
|
|
1946
|
-
});
|
|
1947
|
-
issues = [
|
|
1948
|
-
...new Map(
|
|
1949
|
-
[...earlier.issues ?? [], ...pageIssues].map((issue) => [
|
|
1950
|
-
`${issue.id ?? issue.eventId}:${issue.sequence}`,
|
|
1951
|
-
issue
|
|
1952
|
-
])
|
|
1953
|
-
).values()
|
|
1954
|
-
];
|
|
1955
|
-
atomicJson(ledgerPath, {
|
|
1956
|
-
schemaVersion: "1.0.0",
|
|
1957
|
-
workspaceId: record.custody.id,
|
|
1958
|
-
events,
|
|
1959
|
-
issues,
|
|
1960
|
-
directions,
|
|
1961
|
-
metadataByRevision,
|
|
1962
|
-
importedReviews
|
|
1963
|
-
});
|
|
1964
|
-
const next = page.cursor;
|
|
1965
|
-
hasMore = Boolean(page.hasMore) && next > record.lastEvent;
|
|
1966
|
-
record.lastEvent = next;
|
|
1967
|
-
save(record);
|
|
1968
|
-
}
|
|
1969
|
-
return { ok: true, shared: true, imported, issues, reviewPath, ...safeStatus(record, current) };
|
|
1970
|
-
});
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
export {
|
|
1974
|
-
createReviewLedger,
|
|
1975
|
-
mergeReviewLedger,
|
|
1976
|
-
effectiveReviewDecision,
|
|
1977
|
-
ARTIFACT_REVIEW_MAX_STATE_BYTES,
|
|
1978
|
-
normalizeArtifactReview,
|
|
1979
|
-
readArtifactReviewState,
|
|
1980
|
-
writeArtifactReviewState,
|
|
1981
|
-
withArtifactReviewLock,
|
|
1982
|
-
exportArtifactReview,
|
|
1983
|
-
resolveArtifactReviewDestination,
|
|
1984
|
-
decodeArtifactReviewSources,
|
|
1985
|
-
getDesignShareStatus,
|
|
1986
|
-
shareDesign,
|
|
1987
|
-
publishDesignShare,
|
|
1988
|
-
manageDesignShare,
|
|
1989
|
-
publishDesignReviewMetadata,
|
|
1990
|
-
exportDesignShareRecovery,
|
|
1991
|
-
importDesignShareRecovery,
|
|
1992
|
-
syncDesignShare
|
|
1993
|
-
};
|