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,40 +1,817 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getDesignShareStatus,
|
|
3
|
-
publishDesignReviewMetadata,
|
|
4
|
-
readArtifactReviewState,
|
|
5
|
-
resolveArtifactReviewDestination,
|
|
6
|
-
withArtifactReviewLock
|
|
7
|
-
} from "./design-share.mjs";
|
|
8
1
|
import {
|
|
9
2
|
DESIGN_HANDOFF_CONTENT_SCHEMA,
|
|
10
3
|
DESIGN_HANDOFF_SCHEMA,
|
|
4
|
+
assertPlainData,
|
|
11
5
|
assertReviewExperience,
|
|
6
|
+
canonicalizeJson,
|
|
7
|
+
deepFreeze,
|
|
8
|
+
emptyReviewContext,
|
|
9
|
+
reviewDigest,
|
|
10
|
+
sha256Hex
|
|
11
|
+
} from "./design-shared-artifact-support-dependencies-design-support-protocol-contracts-d707e49b.mjs";
|
|
12
|
+
import {
|
|
13
|
+
ARTIFACT_ERROR_CODES,
|
|
14
|
+
PipelineError,
|
|
15
|
+
canonicalSerialize,
|
|
16
|
+
digestArtifactEnvelope,
|
|
17
|
+
validateArtifactEnvelope,
|
|
18
|
+
validateArtifactReview,
|
|
19
|
+
validateJson
|
|
20
|
+
} from "./design-shared-artifact-support-protocol-contracts-9f6918b0.mjs";
|
|
21
|
+
import {
|
|
22
|
+
assertDesignShareAvailable,
|
|
23
|
+
getDesignShareStatus,
|
|
24
|
+
publishDesignReviewMetadata
|
|
25
|
+
} from "./design-share-cli-bridge.mjs";
|
|
26
|
+
import {
|
|
12
27
|
atomicJson,
|
|
13
28
|
currentDesign,
|
|
14
29
|
designSpecPath,
|
|
15
|
-
emptyReviewContext,
|
|
16
30
|
hash,
|
|
17
|
-
readJson
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
readJson
|
|
32
|
+
} from "./design-shared-design-support-0bac378d.mjs";
|
|
33
|
+
import {
|
|
34
|
+
planrHome
|
|
35
|
+
} from "./design-runtime-home.mjs";
|
|
20
36
|
import {
|
|
21
37
|
acquireStartLock
|
|
22
38
|
} from "./design-loopback-server.mjs";
|
|
39
|
+
|
|
40
|
+
// packages/design/lib/design/feedback-reader.mjs
|
|
41
|
+
import { join as join2 } from "node:path";
|
|
42
|
+
|
|
43
|
+
// packages/artifact/lib/artifact/import.mjs
|
|
23
44
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
45
|
+
existsSync as existsSync2,
|
|
46
|
+
lstatSync as lstatSync2,
|
|
47
|
+
mkdirSync as mkdirSync2,
|
|
48
|
+
readFileSync as readFileSync2,
|
|
49
|
+
realpathSync,
|
|
50
|
+
renameSync as renameSync2,
|
|
51
|
+
rmSync as rmSync2,
|
|
52
|
+
statSync as statSync2,
|
|
53
|
+
writeFileSync as writeFileSync2
|
|
54
|
+
} from "node:fs";
|
|
55
|
+
import { homedir } from "node:os";
|
|
56
|
+
import { dirname as dirname2, isAbsolute, join, relative, resolve } from "node:path";
|
|
57
|
+
|
|
58
|
+
// packages/protocol/src/names.mjs
|
|
59
|
+
var PLANNING_FOLDER = ".planr";
|
|
60
|
+
var CLI_COMMAND = "openplanr";
|
|
61
|
+
|
|
62
|
+
// packages/artifact/lib/artifact/internal/feedback.mjs
|
|
63
|
+
var FEEDBACK_FILE = "feedback.json";
|
|
64
|
+
|
|
65
|
+
// packages/artifact/lib/artifact/merge.mjs
|
|
66
|
+
var ARTIFACT_REVIEW_STATE_VERSION = "1.0.0";
|
|
67
|
+
var ARTIFACT_REVIEW_STATE_KIND = "artifact-review-state";
|
|
68
|
+
var SHA256_RE = /^[a-f0-9]{64}$/;
|
|
69
|
+
var ARTIFACT_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
70
|
+
function conflict(message2, details = void 0) {
|
|
71
|
+
throw new PipelineError(
|
|
72
|
+
ARTIFACT_ERROR_CODES.MERGE_CONFLICT,
|
|
73
|
+
message2,
|
|
74
|
+
"Keep both reviews under different stable IDs or resolve the conflicting item explicitly.",
|
|
75
|
+
details
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
function assertUniqueReviewItemIds(review) {
|
|
79
|
+
const pinIds = /* @__PURE__ */ new Set();
|
|
80
|
+
for (const pin of review.pins ?? []) {
|
|
81
|
+
if (pinIds.has(pin.id)) {
|
|
82
|
+
throw new PipelineError(
|
|
83
|
+
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
84
|
+
"Artifact review contains a duplicate pin ID."
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
pinIds.add(pin.id);
|
|
88
|
+
const replyIds = /* @__PURE__ */ new Set();
|
|
89
|
+
for (const reply of pin.replies ?? []) {
|
|
90
|
+
if (replyIds.has(reply.id)) {
|
|
91
|
+
throw new PipelineError(
|
|
92
|
+
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
93
|
+
"Artifact review contains a duplicate reply ID in one thread."
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
replyIds.add(reply.id);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return review;
|
|
100
|
+
}
|
|
101
|
+
function clone(value) {
|
|
102
|
+
return structuredClone(value);
|
|
103
|
+
}
|
|
104
|
+
function same(a, b) {
|
|
105
|
+
return canonicalSerialize(a) === canonicalSerialize(b);
|
|
106
|
+
}
|
|
107
|
+
function timeValue(value) {
|
|
108
|
+
if (typeof value !== "string") return Number.NEGATIVE_INFINITY;
|
|
109
|
+
const parsed = Date.parse(value);
|
|
110
|
+
return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;
|
|
111
|
+
}
|
|
112
|
+
function compareStable(a, b, idKey) {
|
|
113
|
+
const aTime = a.createdAt ?? a.updatedAt ?? "";
|
|
114
|
+
const bTime = b.createdAt ?? b.updatedAt ?? "";
|
|
115
|
+
return aTime.localeCompare(bTime) || String(a[idKey]).localeCompare(String(b[idKey]));
|
|
116
|
+
}
|
|
117
|
+
function replyMap(replies) {
|
|
118
|
+
const map = /* @__PURE__ */ new Map();
|
|
119
|
+
for (const reply of replies ?? []) {
|
|
120
|
+
const previous = map.get(reply.id);
|
|
121
|
+
if (previous && !same(previous, reply)) {
|
|
122
|
+
conflict("A reply ID refers to different immutable reply content.", { entity: "reply" });
|
|
123
|
+
}
|
|
124
|
+
map.set(reply.id, clone(reply));
|
|
125
|
+
}
|
|
126
|
+
return map;
|
|
127
|
+
}
|
|
128
|
+
function mergeReplies(stored = [], incoming = []) {
|
|
129
|
+
const merged = replyMap(stored);
|
|
130
|
+
for (const reply of incoming) {
|
|
131
|
+
const previous = merged.get(reply.id);
|
|
132
|
+
if (previous && !same(previous, reply)) {
|
|
133
|
+
conflict("A reply ID refers to different immutable reply content.", { entity: "reply" });
|
|
134
|
+
}
|
|
135
|
+
if (!previous) merged.set(reply.id, clone(reply));
|
|
136
|
+
}
|
|
137
|
+
return [...merged.values()].sort((a, b) => compareStable(a, b, "id"));
|
|
138
|
+
}
|
|
139
|
+
var immutablePin = (pin) => ({
|
|
140
|
+
id: pin.id,
|
|
141
|
+
author: pin.author,
|
|
142
|
+
artifactId: pin.artifactId,
|
|
143
|
+
...pin.variant === void 0 ? {} : { variant: pin.variant },
|
|
144
|
+
region: pin.region,
|
|
145
|
+
viewport: pin.viewport,
|
|
146
|
+
...pin.anchor === void 0 ? {} : { anchor: pin.anchor },
|
|
147
|
+
createdAt: pin.createdAt
|
|
148
|
+
});
|
|
149
|
+
var mutablePin = (pin) => ({
|
|
150
|
+
intent: pin.intent,
|
|
151
|
+
status: pin.status,
|
|
152
|
+
comment: pin.comment
|
|
153
|
+
});
|
|
154
|
+
function mergePin(stored, incoming) {
|
|
155
|
+
if (!same(immutablePin(stored), immutablePin(incoming))) {
|
|
156
|
+
conflict("A pin ID refers to different immutable geometry, author, or artifact content.", {
|
|
157
|
+
entity: "pin"
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const replies = mergeReplies(stored.replies, incoming.replies);
|
|
161
|
+
const storedTime = timeValue(stored.updatedAt);
|
|
162
|
+
const incomingTime = timeValue(incoming.updatedAt);
|
|
163
|
+
let selected = stored;
|
|
164
|
+
if (incomingTime > storedTime) selected = incoming;
|
|
165
|
+
if (incomingTime === storedTime && !same(mutablePin(stored), mutablePin(incoming))) {
|
|
166
|
+
conflict("A pin has divergent mutable content at the same update time.", { entity: "pin" });
|
|
167
|
+
}
|
|
168
|
+
return { ...clone(selected), replies };
|
|
169
|
+
}
|
|
170
|
+
function mergePins(stored = [], incoming = []) {
|
|
171
|
+
const merged = /* @__PURE__ */ new Map();
|
|
172
|
+
for (const pin of stored) merged.set(pin.id, clone(pin));
|
|
173
|
+
for (const pin of incoming) {
|
|
174
|
+
const previous = merged.get(pin.id);
|
|
175
|
+
merged.set(pin.id, previous ? mergePin(previous, pin) : clone(pin));
|
|
176
|
+
}
|
|
177
|
+
return [...merged.values()].sort((a, b) => compareStable(a, b, "id"));
|
|
178
|
+
}
|
|
179
|
+
var immutableReview = (review) => ({
|
|
180
|
+
schemaVersion: review.schemaVersion,
|
|
181
|
+
reviewId: review.reviewId,
|
|
182
|
+
reviewOf: review.reviewOf,
|
|
183
|
+
...review.createdAt === void 0 ? {} : { createdAt: review.createdAt }
|
|
184
|
+
});
|
|
185
|
+
var mutableReview = (review) => ({ decision: review.decision, overall: review.overall });
|
|
186
|
+
function mergeArtifactReviews(stored, incoming) {
|
|
187
|
+
validateArtifactReview(stored);
|
|
188
|
+
validateArtifactReview(incoming);
|
|
189
|
+
assertUniqueReviewItemIds(stored);
|
|
190
|
+
assertUniqueReviewItemIds(incoming);
|
|
191
|
+
if (stored.reviewId !== incoming.reviewId) {
|
|
192
|
+
conflict("Only reviews with the same review ID can be revision-merged.", {
|
|
193
|
+
entity: "review"
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (same(stored, incoming)) return stored;
|
|
197
|
+
if (!same(immutableReview(stored), immutableReview(incoming))) {
|
|
198
|
+
conflict("A review ID refers to a different digest or creation identity.", {
|
|
199
|
+
entity: "review"
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
const storedTime = timeValue(stored.updatedAt);
|
|
203
|
+
const incomingTime = timeValue(incoming.updatedAt);
|
|
204
|
+
let selected = stored;
|
|
205
|
+
if (incomingTime > storedTime) selected = incoming;
|
|
206
|
+
if (incomingTime === storedTime && !same(mutableReview(stored), mutableReview(incoming))) {
|
|
207
|
+
conflict("A review has divergent verdict or overall feedback at the same update time.", {
|
|
208
|
+
entity: "review"
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
const merged = {
|
|
212
|
+
...clone(selected),
|
|
213
|
+
pins: mergePins(stored.pins, incoming.pins)
|
|
214
|
+
};
|
|
215
|
+
validateArtifactReview(merged);
|
|
216
|
+
return merged;
|
|
217
|
+
}
|
|
218
|
+
function validateReviewLedger(ledger) {
|
|
219
|
+
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)) {
|
|
220
|
+
throw new PipelineError(
|
|
221
|
+
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
222
|
+
"Artifact review state is not a valid versioned review ledger."
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
const ids = /* @__PURE__ */ new Set();
|
|
226
|
+
for (const entry of ledger.reviews) {
|
|
227
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry) || typeof entry.stale !== "boolean" || !entry.review) {
|
|
228
|
+
throw new PipelineError(
|
|
229
|
+
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
230
|
+
"Artifact review ledger entry is invalid."
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
validateArtifactReview(entry.review);
|
|
234
|
+
assertUniqueReviewItemIds(entry.review);
|
|
235
|
+
if (ids.has(entry.review.reviewId)) {
|
|
236
|
+
throw new PipelineError(
|
|
237
|
+
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
238
|
+
"Artifact review ledger IDs must be unique."
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
ids.add(entry.review.reviewId);
|
|
242
|
+
}
|
|
243
|
+
return ledger;
|
|
244
|
+
}
|
|
245
|
+
function createReviewLedger({ artifactId, currentReviewOf, reviews = [] } = {}) {
|
|
246
|
+
const ledger = {
|
|
247
|
+
schemaVersion: ARTIFACT_REVIEW_STATE_VERSION,
|
|
248
|
+
kind: ARTIFACT_REVIEW_STATE_KIND,
|
|
249
|
+
artifactId,
|
|
250
|
+
currentReviewOf,
|
|
251
|
+
reviews: reviews.map((entry) => ({ review: clone(entry.review), stale: Boolean(entry.stale) }))
|
|
252
|
+
};
|
|
253
|
+
ledger.reviews.sort((a, b) => compareStable(a.review, b.review, "reviewId"));
|
|
254
|
+
return validateReviewLedger(ledger);
|
|
255
|
+
}
|
|
256
|
+
function mergeReviewLedger(ledger, incoming, { stale = false } = {}) {
|
|
257
|
+
validateReviewLedger(ledger);
|
|
258
|
+
const reviews = Array.isArray(incoming) ? incoming : [incoming];
|
|
259
|
+
const byId = new Map(
|
|
260
|
+
ledger.reviews.map((entry) => [
|
|
261
|
+
entry.review.reviewId,
|
|
262
|
+
{
|
|
263
|
+
review: clone(entry.review),
|
|
264
|
+
stale: entry.stale
|
|
265
|
+
}
|
|
266
|
+
])
|
|
267
|
+
);
|
|
268
|
+
for (const review of reviews) {
|
|
269
|
+
validateArtifactReview(review);
|
|
270
|
+
const previous = byId.get(review.reviewId);
|
|
271
|
+
if (previous) {
|
|
272
|
+
byId.set(review.reviewId, {
|
|
273
|
+
review: mergeArtifactReviews(previous.review, review),
|
|
274
|
+
stale: previous.stale || Boolean(stale)
|
|
275
|
+
});
|
|
276
|
+
} else {
|
|
277
|
+
byId.set(review.reviewId, { review: clone(review), stale: Boolean(stale) });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const merged = createReviewLedger({
|
|
281
|
+
artifactId: ledger.artifactId,
|
|
282
|
+
currentReviewOf: ledger.currentReviewOf,
|
|
283
|
+
reviews: [...byId.values()]
|
|
284
|
+
});
|
|
285
|
+
return same(ledger, merged) ? ledger : merged;
|
|
286
|
+
}
|
|
287
|
+
function effectiveReviewDecision(value) {
|
|
288
|
+
const entries = Array.isArray(value) ? value : value?.reviews ?? [];
|
|
289
|
+
const decisions = entries.map((entry) => entry.review?.decision ?? entry.decision);
|
|
290
|
+
if (decisions.includes("changes_requested")) return "changes_requested";
|
|
291
|
+
if (decisions.includes("pending")) return "pending";
|
|
292
|
+
return decisions.length > 0 && decisions.every((decision) => decision === "approved") ? "approved" : "pending";
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// packages/artifact/lib/artifact/review.mjs
|
|
296
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
29
297
|
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
298
|
+
existsSync,
|
|
299
|
+
lstatSync,
|
|
300
|
+
mkdirSync,
|
|
301
|
+
readFileSync,
|
|
302
|
+
renameSync,
|
|
303
|
+
rmSync,
|
|
304
|
+
statSync,
|
|
305
|
+
writeFileSync
|
|
306
|
+
} from "node:fs";
|
|
307
|
+
import { dirname } from "node:path";
|
|
308
|
+
var ARTIFACT_REVIEW_MAX_STATE_BYTES = 5 * 1024 * 1024;
|
|
309
|
+
var reviewPathQueues = /* @__PURE__ */ new Map();
|
|
310
|
+
function finalEntry(path, fs = { lstatSync }) {
|
|
311
|
+
try {
|
|
312
|
+
return fs.lstatSync(path);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
if (error?.code === "ENOENT") return null;
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function clone2(value) {
|
|
319
|
+
return structuredClone(value);
|
|
320
|
+
}
|
|
321
|
+
function canonicalObject(value) {
|
|
322
|
+
if (Array.isArray(value)) return value.map(canonicalObject);
|
|
323
|
+
if (!value || typeof value !== "object") return value;
|
|
324
|
+
return Object.fromEntries(
|
|
325
|
+
Object.keys(value).sort().map((key) => [key, canonicalObject(value[key])])
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
function stableItemSort(a, b) {
|
|
329
|
+
return String(a.createdAt ?? a.updatedAt ?? "").localeCompare(
|
|
330
|
+
String(b.createdAt ?? b.updatedAt ?? "")
|
|
331
|
+
) || String(a.id ?? a.reviewId).localeCompare(String(b.id ?? b.reviewId));
|
|
332
|
+
}
|
|
333
|
+
function normalizeArtifactReview(review) {
|
|
334
|
+
let normalized;
|
|
335
|
+
try {
|
|
336
|
+
normalized = clone2(review);
|
|
337
|
+
} catch {
|
|
338
|
+
throw new PipelineError(
|
|
339
|
+
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
340
|
+
"Artifact review is not cloneable."
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
validateArtifactReview(normalized);
|
|
345
|
+
assertUniqueReviewItemIds(normalized);
|
|
346
|
+
} catch (error) {
|
|
347
|
+
if (error instanceof PipelineError) {
|
|
348
|
+
throw new PipelineError(
|
|
349
|
+
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
350
|
+
"Artifact review does not satisfy the Protocol v1.1 review contract.",
|
|
351
|
+
"",
|
|
352
|
+
{ issues: error.details?.issues ?? [] }
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
normalized.pins = normalized.pins.map((pin) => ({
|
|
358
|
+
...pin,
|
|
359
|
+
replies: [...pin.replies].sort(stableItemSort)
|
|
360
|
+
})).sort(stableItemSort);
|
|
361
|
+
validateArtifactReview(normalized);
|
|
362
|
+
return normalized;
|
|
363
|
+
}
|
|
364
|
+
function serializeReviewState(value) {
|
|
365
|
+
const normalized = value?.kind === "artifact-review-state" ? validateReviewLedger(clone2(value)) : normalizeArtifactReview(value);
|
|
366
|
+
return `${JSON.stringify(canonicalObject(normalized), null, 2)}
|
|
367
|
+
`;
|
|
368
|
+
}
|
|
369
|
+
function readArtifactReviewState(path, { allowMissing = false } = {}) {
|
|
370
|
+
const entry = finalEntry(path);
|
|
371
|
+
if (!entry) {
|
|
372
|
+
if (allowMissing) return null;
|
|
373
|
+
throw new PipelineError(
|
|
374
|
+
ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
|
|
375
|
+
"Artifact review state does not exist."
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
if (entry.isSymbolicLink() || !entry.isFile()) {
|
|
380
|
+
throw new PipelineError(
|
|
381
|
+
ARTIFACT_ERROR_CODES.SYMLINK_ESCAPE,
|
|
382
|
+
"Artifact review state destination is not a regular file."
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (statSync(path).size > ARTIFACT_REVIEW_MAX_STATE_BYTES) {
|
|
386
|
+
throw new PipelineError(
|
|
387
|
+
ARTIFACT_ERROR_CODES.REQUEST_LIMIT,
|
|
388
|
+
`Artifact review state exceeds ${ARTIFACT_REVIEW_MAX_STATE_BYTES} bytes.`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
return validateReviewLedger(JSON.parse(readFileSync(path, "utf8")));
|
|
392
|
+
} catch (error) {
|
|
393
|
+
if (error instanceof PipelineError) throw error;
|
|
394
|
+
throw new PipelineError(
|
|
395
|
+
ARTIFACT_ERROR_CODES.REVIEW_INVALID,
|
|
396
|
+
"Artifact review state is malformed."
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function writeArtifactReviewState(path, ledger, { fileSystem = {}, suffix = `${process.pid}.${randomBytes(8).toString("hex")}` } = {}) {
|
|
401
|
+
validateReviewLedger(ledger);
|
|
402
|
+
const fs = { existsSync, lstatSync, mkdirSync, writeFileSync, renameSync, rmSync, ...fileSystem };
|
|
403
|
+
const temporary = `${path}.${suffix}.tmp`;
|
|
404
|
+
const serialized = serializeReviewState(ledger);
|
|
405
|
+
if (Buffer.byteLength(serialized, "utf8") > ARTIFACT_REVIEW_MAX_STATE_BYTES) {
|
|
406
|
+
throw new PipelineError(
|
|
407
|
+
ARTIFACT_ERROR_CODES.REQUEST_LIMIT,
|
|
408
|
+
`Artifact review state exceeds ${ARTIFACT_REVIEW_MAX_STATE_BYTES} bytes.`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
const initial = finalEntry(path, fs);
|
|
413
|
+
if (initial) {
|
|
414
|
+
const target = initial;
|
|
415
|
+
if (target.isSymbolicLink() || !target.isFile()) throw new Error("unsafe final target");
|
|
416
|
+
}
|
|
417
|
+
fs.mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
418
|
+
fs.writeFileSync(temporary, serialized, { mode: 384, flag: "wx" });
|
|
419
|
+
const final = finalEntry(path, fs);
|
|
420
|
+
if (final) {
|
|
421
|
+
const target = final;
|
|
422
|
+
if (target.isSymbolicLink() || !target.isFile()) throw new Error("unsafe final target");
|
|
423
|
+
}
|
|
424
|
+
fs.renameSync(temporary, path);
|
|
425
|
+
} catch {
|
|
426
|
+
try {
|
|
427
|
+
fs.rmSync(temporary, { force: true });
|
|
428
|
+
} catch {
|
|
429
|
+
}
|
|
430
|
+
throw new PipelineError(
|
|
431
|
+
ARTIFACT_ERROR_CODES.REVIEW_WRITE,
|
|
432
|
+
"Artifact review state could not be written atomically.",
|
|
433
|
+
"Check destination permissions and retry; the previous review state was left unchanged."
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
return ledger;
|
|
437
|
+
}
|
|
438
|
+
function withArtifactReviewLock(path, action3) {
|
|
439
|
+
const previous = reviewPathQueues.get(path) ?? Promise.resolve();
|
|
440
|
+
const run = async () => {
|
|
441
|
+
let release;
|
|
442
|
+
try {
|
|
443
|
+
release = await acquireStartLock(`${path}.lock`, { timeout: 15e3, stale: 3e4 });
|
|
444
|
+
return await action3();
|
|
445
|
+
} catch (error) {
|
|
446
|
+
if (error instanceof PipelineError) throw error;
|
|
447
|
+
throw new PipelineError(
|
|
448
|
+
ARTIFACT_ERROR_CODES.REVIEW_WRITE,
|
|
449
|
+
"Artifact review state is busy or its cross-process lock is unavailable.",
|
|
450
|
+
"Wait for the active review write to finish, then retry."
|
|
451
|
+
);
|
|
452
|
+
} finally {
|
|
453
|
+
release?.();
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
const operation = previous.then(run, run);
|
|
457
|
+
const tail = operation.then(
|
|
458
|
+
() => void 0,
|
|
459
|
+
() => void 0
|
|
460
|
+
);
|
|
461
|
+
reviewPathQueues.set(path, tail);
|
|
462
|
+
tail.finally(() => {
|
|
463
|
+
if (reviewPathQueues.get(path) === tail) reviewPathQueues.delete(path);
|
|
464
|
+
});
|
|
465
|
+
return operation;
|
|
466
|
+
}
|
|
467
|
+
function normalizedExportInput(value) {
|
|
468
|
+
if (value?.kind === "artifact-review-state") return validateReviewLedger(clone2(value));
|
|
469
|
+
if (value?.review && value?.artifacts) return normalizeArtifactReview(value.review);
|
|
470
|
+
return normalizeArtifactReview(value);
|
|
471
|
+
}
|
|
472
|
+
function markdownText(value) {
|
|
473
|
+
return String(value ?? "").replace(/\r\n?/g, "\n").trim();
|
|
474
|
+
}
|
|
475
|
+
function renderPinMarkdown(pin, number) {
|
|
476
|
+
const region = `${pin.region.x}, ${pin.region.y}, ${pin.region.w}, ${pin.region.h}`;
|
|
477
|
+
const lines = [
|
|
478
|
+
`### ${number}. ${pin.intent.toUpperCase()} \xB7 ${pin.status}`,
|
|
479
|
+
"",
|
|
480
|
+
`- Pin: \`${pin.id}\``,
|
|
481
|
+
`- Artifact: \`${pin.artifactId}\`${pin.variant ? ` \xB7 variant \`${pin.variant}\`` : ""}`,
|
|
482
|
+
`- Author: ${pin.author.name}`,
|
|
483
|
+
`- Region: \`${region}\` at ${pin.viewport.width}\xD7${pin.viewport.height}`,
|
|
484
|
+
...pin.anchor ? [
|
|
485
|
+
`- Anchor: \`${pin.anchor.planrId}\`${pin.anchor.screen ? ` \xB7 screen \`${pin.anchor.screen}\`` : ""}`
|
|
486
|
+
] : [],
|
|
487
|
+
`- Updated: ${pin.updatedAt}`,
|
|
488
|
+
"",
|
|
489
|
+
markdownText(pin.comment)
|
|
490
|
+
];
|
|
491
|
+
if (pin.replies.length > 0) {
|
|
492
|
+
lines.push("", "#### Thread", "");
|
|
493
|
+
for (const reply of pin.replies) {
|
|
494
|
+
lines.push(`- **${reply.author.name}** (${reply.createdAt}): ${markdownText(reply.comment)}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return lines.join("\n");
|
|
498
|
+
}
|
|
499
|
+
function reviewMarkdown(review, headingLevel = 2, stale = false) {
|
|
500
|
+
const mark = "#".repeat(headingLevel);
|
|
501
|
+
const lines = [
|
|
502
|
+
`${mark} Review ${review.reviewId}${stale ? " \xB7 STALE" : ""}`,
|
|
503
|
+
"",
|
|
504
|
+
`- Digest: \`${review.reviewOf}\``,
|
|
505
|
+
`- Decision: **${review.decision}**`,
|
|
506
|
+
`- Pins: ${review.pins.length}`,
|
|
507
|
+
...review.createdAt ? [`- Created: ${review.createdAt}`] : [],
|
|
508
|
+
...review.updatedAt ? [`- Updated: ${review.updatedAt}`] : [],
|
|
509
|
+
"",
|
|
510
|
+
`${mark}# Overall feedback`,
|
|
511
|
+
"",
|
|
512
|
+
markdownText(review.overall) || "_No overall feedback._"
|
|
513
|
+
];
|
|
514
|
+
if (review.pins.length > 0) {
|
|
515
|
+
lines.push("", `${mark}# Pins`, "");
|
|
516
|
+
review.pins.forEach((pin, index) => lines.push(renderPinMarkdown(pin, index + 1), ""));
|
|
517
|
+
if (lines.at(-1) === "") lines.pop();
|
|
518
|
+
}
|
|
519
|
+
return lines.join("\n");
|
|
520
|
+
}
|
|
521
|
+
function exportArtifactReview(value, { format = "json" } = {}) {
|
|
522
|
+
let normalized;
|
|
523
|
+
try {
|
|
524
|
+
normalized = normalizedExportInput(value);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
if (error instanceof PipelineError) throw error;
|
|
527
|
+
throw new PipelineError(
|
|
528
|
+
ARTIFACT_ERROR_CODES.REVIEW_EXPORT,
|
|
529
|
+
"Artifact review cannot be exported."
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
if (format === "json") return serializeReviewState(normalized);
|
|
533
|
+
if (format !== "markdown") {
|
|
534
|
+
throw new PipelineError(
|
|
535
|
+
ARTIFACT_ERROR_CODES.REVIEW_EXPORT,
|
|
536
|
+
"Artifact review export format must be json or markdown."
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
if (normalized.kind !== "artifact-review-state") {
|
|
540
|
+
return `# OpenPlanr Artifact Review
|
|
541
|
+
|
|
542
|
+
${reviewMarkdown(normalized)}
|
|
543
|
+
`;
|
|
544
|
+
}
|
|
545
|
+
const lines = [
|
|
546
|
+
"# OpenPlanr Artifact Reviews",
|
|
547
|
+
"",
|
|
548
|
+
`- Artifact: \`${normalized.artifactId}\``,
|
|
549
|
+
`- Current digest: \`${normalized.currentReviewOf}\``,
|
|
550
|
+
`- Effective decision: **${effectiveReviewDecision(normalized)}**`,
|
|
551
|
+
`- Reviews: ${normalized.reviews.length}`
|
|
552
|
+
];
|
|
553
|
+
for (const entry of normalized.reviews) {
|
|
554
|
+
lines.push("", reviewMarkdown(entry.review, 2, entry.stale));
|
|
555
|
+
}
|
|
556
|
+
return `${lines.join("\n")}
|
|
557
|
+
`;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// packages/artifact/lib/artifact/import.mjs
|
|
561
|
+
var ARTIFACT_ID_RE2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
562
|
+
function pathError(code, message2) {
|
|
563
|
+
throw new PipelineError(
|
|
564
|
+
code,
|
|
565
|
+
message2,
|
|
566
|
+
"Choose a real, non-symlinked project or user review destination."
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
function pathEntry(path, fs = { lstatSync: lstatSync2 }) {
|
|
570
|
+
try {
|
|
571
|
+
return fs.lstatSync(path);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
if (error?.code === "ENOENT") return null;
|
|
574
|
+
throw error;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function inside(base, candidate) {
|
|
578
|
+
const rel = relative(base, candidate);
|
|
579
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
580
|
+
}
|
|
581
|
+
function parseablePlanrConfig(root) {
|
|
582
|
+
const path = join(root, PLANNING_FOLDER, "config.json");
|
|
583
|
+
if (!existsSync2(path)) return false;
|
|
584
|
+
try {
|
|
585
|
+
if (lstatSync2(path).isSymbolicLink()) return false;
|
|
586
|
+
const value = JSON.parse(readFileSync2(path, "utf8"));
|
|
587
|
+
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);
|
|
588
|
+
} catch {
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function hasGitMarker(root) {
|
|
593
|
+
const marker = join(root, ".git");
|
|
594
|
+
if (!existsSync2(marker)) return false;
|
|
595
|
+
try {
|
|
596
|
+
const stat = lstatSync2(marker);
|
|
597
|
+
if (stat.isSymbolicLink()) return false;
|
|
598
|
+
if (stat.isDirectory()) {
|
|
599
|
+
const head = join(marker, "HEAD");
|
|
600
|
+
return existsSync2(head) && lstatSync2(head).isFile();
|
|
601
|
+
}
|
|
602
|
+
if (!stat.isFile() || stat.size > 4096) return false;
|
|
603
|
+
const match = /^gitdir:\s*(.+?)\s*$/u.exec(readFileSync2(marker, "utf8"));
|
|
604
|
+
if (!match) return false;
|
|
605
|
+
const gitDir = resolve(root, match[1]);
|
|
606
|
+
return existsSync2(gitDir) && statSync2(gitDir).isDirectory() && existsSync2(join(gitDir, "HEAD")) && lstatSync2(join(gitDir, "HEAD")).isFile();
|
|
607
|
+
} catch {
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function findArtifactProjectRoot(start = process.cwd(), { env = process.env } = {}) {
|
|
612
|
+
let current;
|
|
613
|
+
try {
|
|
614
|
+
current = realpathSync(resolve(start));
|
|
615
|
+
} catch {
|
|
616
|
+
pathError(
|
|
617
|
+
ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
|
|
618
|
+
"Artifact review working directory does not exist."
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
const homeCandidate = resolve(env.HOME ?? homedir());
|
|
622
|
+
let home = homeCandidate;
|
|
623
|
+
try {
|
|
624
|
+
home = realpathSync(homeCandidate);
|
|
625
|
+
} catch {
|
|
626
|
+
}
|
|
627
|
+
while (true) {
|
|
628
|
+
if (current !== home && (parseablePlanrConfig(current) || hasGitMarker(current)))
|
|
629
|
+
return current;
|
|
630
|
+
const parent = dirname2(current);
|
|
631
|
+
if (parent === current) return null;
|
|
632
|
+
current = parent;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function assertSafeDestination(base, relativeParts) {
|
|
636
|
+
const absoluteBase = resolve(base);
|
|
637
|
+
if (existsSync2(absoluteBase) && lstatSync2(absoluteBase).isSymbolicLink()) {
|
|
638
|
+
pathError(
|
|
639
|
+
ARTIFACT_ERROR_CODES.SYMLINK_ESCAPE,
|
|
640
|
+
"Artifact review destination base is a symlink."
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
let realBase = absoluteBase;
|
|
644
|
+
if (existsSync2(absoluteBase)) realBase = realpathSync(absoluteBase);
|
|
645
|
+
let current = absoluteBase;
|
|
646
|
+
for (const part of relativeParts) {
|
|
647
|
+
if (!part || part === "." || part === ".." || part.includes("/") || part.includes("\\") || part.includes("\0")) {
|
|
648
|
+
pathError(
|
|
649
|
+
ARTIFACT_ERROR_CODES.PATH_TRAVERSAL,
|
|
650
|
+
"Artifact review destination contains an unsafe segment."
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
current = join(current, part);
|
|
654
|
+
const entry = pathEntry(current);
|
|
655
|
+
if (!entry) continue;
|
|
656
|
+
if (entry.isSymbolicLink()) {
|
|
657
|
+
pathError(
|
|
658
|
+
ARTIFACT_ERROR_CODES.SYMLINK_ESCAPE,
|
|
659
|
+
"Artifact review destination contains a symlink."
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
if (!inside(realBase, realpathSync(current))) {
|
|
663
|
+
pathError(
|
|
664
|
+
ARTIFACT_ERROR_CODES.PATH_TRAVERSAL,
|
|
665
|
+
"Artifact review destination escapes its storage root."
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
if (!inside(absoluteBase, current)) {
|
|
670
|
+
pathError(
|
|
671
|
+
ARTIFACT_ERROR_CODES.PATH_TRAVERSAL,
|
|
672
|
+
"Artifact review destination escapes its storage root."
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
return current;
|
|
676
|
+
}
|
|
677
|
+
function resolveArtifactReviewDestination({
|
|
678
|
+
cwd = process.cwd(),
|
|
679
|
+
env = process.env,
|
|
680
|
+
artifactId,
|
|
681
|
+
designDir
|
|
682
|
+
} = {}) {
|
|
683
|
+
if (!ARTIFACT_ID_RE2.test(artifactId ?? "")) {
|
|
684
|
+
pathError(
|
|
685
|
+
ARTIFACT_ERROR_CODES.PATH_TRAVERSAL,
|
|
686
|
+
"Artifact review ID is unsafe for local storage."
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
if (designDir !== void 0) {
|
|
690
|
+
const lexical = resolve(designDir);
|
|
691
|
+
if (!existsSync2(lexical) || !statSync2(lexical).isDirectory()) {
|
|
692
|
+
pathError(ARTIFACT_ERROR_CODES.REVIEW_IMPORT, "Design review destination does not exist.");
|
|
693
|
+
}
|
|
694
|
+
if (lstatSync2(lexical).isSymbolicLink()) {
|
|
695
|
+
pathError(
|
|
696
|
+
ARTIFACT_ERROR_CODES.SYMLINK_ESCAPE,
|
|
697
|
+
"Design review destination must be a real directory."
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
const requested = realpathSync(lexical);
|
|
701
|
+
return Object.freeze({
|
|
702
|
+
kind: "design",
|
|
703
|
+
artifactId,
|
|
704
|
+
directory: requested,
|
|
705
|
+
path: join(requested, FEEDBACK_FILE),
|
|
706
|
+
reviewStatePath: join(requested, "artifact-review-state.json")
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
const projectRoot = findArtifactProjectRoot(cwd, { env });
|
|
710
|
+
if (projectRoot) {
|
|
711
|
+
const directory2 = assertSafeDestination(projectRoot, [
|
|
712
|
+
PLANNING_FOLDER,
|
|
713
|
+
"artifacts",
|
|
714
|
+
artifactId
|
|
715
|
+
]);
|
|
716
|
+
return Object.freeze({
|
|
717
|
+
kind: "project",
|
|
718
|
+
artifactId,
|
|
719
|
+
root: projectRoot,
|
|
720
|
+
directory: directory2,
|
|
721
|
+
path: join(directory2, "review-state.json")
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
const root = resolve(planrHome(env));
|
|
725
|
+
const directory = assertSafeDestination(root, ["artifacts", artifactId]);
|
|
726
|
+
return Object.freeze({
|
|
727
|
+
kind: "user",
|
|
728
|
+
artifactId,
|
|
729
|
+
root,
|
|
730
|
+
directory,
|
|
731
|
+
path: join(directory, "review-state.json")
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
function reviewsFromDecoded(value) {
|
|
735
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
736
|
+
throw new PipelineError(
|
|
737
|
+
ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
|
|
738
|
+
"Decoded review payload is not an object."
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
if (value.kind === "artifact-review-state") {
|
|
742
|
+
validateReviewLedger(value);
|
|
743
|
+
return value.reviews.map((entry) => ({ review: entry.review, stale: entry.stale }));
|
|
744
|
+
}
|
|
745
|
+
if (value.artifacts && value.viewer) {
|
|
746
|
+
validateArtifactEnvelope(value);
|
|
747
|
+
if (!value.review) {
|
|
748
|
+
throw new PipelineError(
|
|
749
|
+
ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
|
|
750
|
+
"Decoded artifact envelope contains no review."
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
return [{ review: value.review, stale: false }];
|
|
754
|
+
}
|
|
755
|
+
if (value.review && !value.reviewId) return reviewsFromDecoded(value.review);
|
|
756
|
+
validateArtifactReview(value);
|
|
757
|
+
return [{ review: value, stale: false }];
|
|
758
|
+
}
|
|
759
|
+
async function decodeArtifactReviewSources(sources, { decodeSource, withMetadata = false } = {}) {
|
|
760
|
+
const list2 = Array.isArray(sources) ? sources : [sources];
|
|
761
|
+
if (list2.length === 0) {
|
|
762
|
+
throw new PipelineError(
|
|
763
|
+
ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
|
|
764
|
+
"At least one artifact review source is required."
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
const entries = [];
|
|
768
|
+
for (let index = 0; index < list2.length; index += 1) {
|
|
769
|
+
const source = list2[index];
|
|
770
|
+
let decoded = source;
|
|
771
|
+
if (typeof source === "string") {
|
|
772
|
+
if (typeof decodeSource !== "function") {
|
|
773
|
+
throw new PipelineError(
|
|
774
|
+
ARTIFACT_ERROR_CODES.REVIEW_DECODER_REQUIRED,
|
|
775
|
+
"String and URL review sources require an injected async decoder.",
|
|
776
|
+
"Provide decodeSource(source, { index }); the review lifecycle has no transport dependency.",
|
|
777
|
+
{ sourceIndex: index }
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
try {
|
|
781
|
+
decoded = await decodeSource(source, { index });
|
|
782
|
+
} catch {
|
|
783
|
+
throw new PipelineError(
|
|
784
|
+
ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
|
|
785
|
+
"Artifact review source could not be decoded.",
|
|
786
|
+
"Verify the immutable review link and retry.",
|
|
787
|
+
{ sourceIndex: index }
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
try {
|
|
792
|
+
entries.push(
|
|
793
|
+
...reviewsFromDecoded(decoded).map((entry) => ({
|
|
794
|
+
review: structuredClone(entry.review),
|
|
795
|
+
stale: Boolean(entry.stale)
|
|
796
|
+
}))
|
|
797
|
+
);
|
|
798
|
+
} catch (error) {
|
|
799
|
+
if (error instanceof PipelineError && error.code === ARTIFACT_ERROR_CODES.REVIEW_IMPORT) {
|
|
800
|
+
error.details = { sourceIndex: index };
|
|
801
|
+
throw error;
|
|
802
|
+
}
|
|
803
|
+
throw new PipelineError(
|
|
804
|
+
ARTIFACT_ERROR_CODES.REVIEW_IMPORT,
|
|
805
|
+
"Decoded artifact review does not satisfy the Protocol v1.1 contract.",
|
|
806
|
+
"",
|
|
807
|
+
{ sourceIndex: index }
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
return withMetadata ? entries : entries.map((entry) => entry.review);
|
|
812
|
+
}
|
|
35
813
|
|
|
36
814
|
// packages/design/lib/design/feedback-reader.mjs
|
|
37
|
-
import { join } from "node:path";
|
|
38
815
|
var designReviewKey = (document) => `design-${hash(document.id).slice(0, 24)}`;
|
|
39
816
|
function designReviewPath(file, env = process.env, options = {}) {
|
|
40
817
|
const { root, document } = currentDesign(file, options);
|
|
@@ -69,18 +846,18 @@ function readDesignFeedback(file, env = process.env, options = {}) {
|
|
|
69
846
|
revision: current.revision,
|
|
70
847
|
reviewPath: designReviewPath(file, env, options),
|
|
71
848
|
pins,
|
|
72
|
-
state: readJson(
|
|
849
|
+
state: readJson(join2(current.root, ".design/studio-state.json"), {
|
|
73
850
|
state: {},
|
|
74
851
|
stateVersion: 0
|
|
75
852
|
}).state,
|
|
76
853
|
ledger,
|
|
77
|
-
shared: readJson(
|
|
854
|
+
shared: readJson(join2(current.root, ".design/shared-feedback.json"), null)
|
|
78
855
|
};
|
|
79
856
|
}
|
|
80
857
|
|
|
81
858
|
// packages/design/lib/design/handoff-reader.mjs
|
|
82
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
83
|
-
import { dirname, join as
|
|
859
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
860
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
84
861
|
|
|
85
862
|
// packages/design/lib/design/handoff-format.mjs
|
|
86
863
|
var safeMd = (text2) => String(text2).replaceAll("[", "\\[").replaceAll("]", "\\]").replaceAll("<", "<").replaceAll(">", ">");
|
|
@@ -836,7 +1613,7 @@ var digestPattern2 = /^[a-f0-9]{64}$/u;
|
|
|
836
1613
|
var revisionOf = (pin) => pin.revisionId ?? pin.reviewId;
|
|
837
1614
|
var keyOf = (pin) => `${revisionOf(pin)}:${pin.id}`;
|
|
838
1615
|
var compare = (left, right) => left.localeCompare(right, "en");
|
|
839
|
-
var
|
|
1616
|
+
var clone3 = (value) => {
|
|
840
1617
|
assertPlainData(value, "Review resolution data");
|
|
841
1618
|
return JSON.parse(canonicalizeJson(value));
|
|
842
1619
|
};
|
|
@@ -942,7 +1719,7 @@ function unknownMetadata(metadata2, known, diagnostics) {
|
|
|
942
1719
|
}
|
|
943
1720
|
}
|
|
944
1721
|
function compileDesignHandoffResolution(input) {
|
|
945
|
-
const source =
|
|
1722
|
+
const source = clone3(input ?? {});
|
|
946
1723
|
const pins = Array.isArray(source.pins) ? source.pins : [];
|
|
947
1724
|
if (pins.length > MAX_COMMENTS)
|
|
948
1725
|
throw new RangeError("Review resolution exceeds the comment limit.");
|
|
@@ -1017,9 +1794,9 @@ function compileDesignHandoffResolution(input) {
|
|
|
1017
1794
|
anchor: pin.elementId ? { screenId: pin.screenId, elementId: pin.elementId } : pin.screenId ? { screenId: pin.screenId } : pin.anchor?.planrId ? { planrId: pin.anchor.planrId } : { artifactId: pin.artifactId },
|
|
1018
1795
|
source: {
|
|
1019
1796
|
text: pin.comment,
|
|
1020
|
-
author:
|
|
1797
|
+
author: clone3(pin.author),
|
|
1021
1798
|
status: pin.status,
|
|
1022
|
-
...resolved.disposition && typeof resolved.disposition === "object" ? { decision:
|
|
1799
|
+
...resolved.disposition && typeof resolved.disposition === "object" ? { decision: clone3(resolved.disposition) } : {}
|
|
1023
1800
|
}
|
|
1024
1801
|
};
|
|
1025
1802
|
});
|
|
@@ -1089,7 +1866,7 @@ function canApproveDesignHandoffResolution(value) {
|
|
|
1089
1866
|
|
|
1090
1867
|
// packages/design/lib/design/handoff-reader.mjs
|
|
1091
1868
|
var sections = ["agreedChanges", "openQuestions", "deferred", "rejected"];
|
|
1092
|
-
var metadataPath = (current) =>
|
|
1869
|
+
var metadataPath = (current) => join3(current.root, ".design/review-metadata.json");
|
|
1093
1870
|
var revisionOf2 = (pin) => pin.revisionId ?? pin.reviewId;
|
|
1094
1871
|
var pinKey = (pin) => `${revisionOf2(pin)}:${pin.id}`;
|
|
1095
1872
|
function metadata(current, feedback) {
|
|
@@ -1218,12 +1995,12 @@ function readDesignExperience(file, { env = process.env } = {}) {
|
|
|
1218
1995
|
}
|
|
1219
1996
|
function readDesignHandoff(file, { env = process.env, ...shareOptions } = {}) {
|
|
1220
1997
|
const value = snapshot(file, env, shareOptions);
|
|
1221
|
-
const path =
|
|
1998
|
+
const path = join3(dirname3(designSpecPath(value.current.root)), "review-handoff.json");
|
|
1222
1999
|
const draft = readJson(path, null);
|
|
1223
2000
|
if (draft) {
|
|
1224
2001
|
assertDraft(draft);
|
|
1225
2002
|
const markdownPath = path.replace(/\.json$/u, ".md");
|
|
1226
|
-
if (!
|
|
2003
|
+
if (!existsSync3(markdownPath) || readFileSync3(markdownPath, "utf8") !== draft.markdown)
|
|
1227
2004
|
throw new Error(
|
|
1228
2005
|
"The handoff Markdown differs from its approved JSON. Rebuild the handoff projection before using it in Plan."
|
|
1229
2006
|
);
|
|
@@ -1242,7 +2019,7 @@ function readDesignHandoff(file, { env = process.env, ...shareOptions } = {}) {
|
|
|
1242
2019
|
}
|
|
1243
2020
|
function readDesignHandoffReadiness(file, { env = process.env, ...shareOptions } = {}) {
|
|
1244
2021
|
const value = snapshot(file, env, shareOptions);
|
|
1245
|
-
const path =
|
|
2022
|
+
const path = join3(dirname3(designSpecPath(value.current.root)), "review-handoff.json");
|
|
1246
2023
|
const handoff = readJson(path, null);
|
|
1247
2024
|
if (handoff) assertDraft(handoff);
|
|
1248
2025
|
const outcomes = new Map(value.resolution.items.map((item) => [item.id, item]));
|
|
@@ -1259,13 +2036,13 @@ function readDesignHandoffReadiness(file, { env = process.env, ...shareOptions }
|
|
|
1259
2036
|
};
|
|
1260
2037
|
});
|
|
1261
2038
|
const specificationPath = designSpecPath(value.current.root);
|
|
1262
|
-
const specification =
|
|
2039
|
+
const specification = existsSync3(specificationPath) ? {
|
|
1263
2040
|
path: "design-spec.md",
|
|
1264
2041
|
revision: value.current.revision,
|
|
1265
|
-
digest: `sha256:${hash(
|
|
2042
|
+
digest: `sha256:${hash(readFileSync3(specificationPath))}`,
|
|
1266
2043
|
complete: true
|
|
1267
2044
|
} : void 0;
|
|
1268
|
-
const studioState = readJson(
|
|
2045
|
+
const studioState = readJson(join3(value.current.root, ".design/studio-state.json"), {
|
|
1269
2046
|
state: {}
|
|
1270
2047
|
}).state;
|
|
1271
2048
|
const readiness = compileDesignHandoffReadiness({
|
|
@@ -1311,9 +2088,9 @@ function assertDraft(draft) {
|
|
|
1311
2088
|
}
|
|
1312
2089
|
|
|
1313
2090
|
// packages/design/lib/design/handoff.mjs
|
|
1314
|
-
import { existsSync as
|
|
1315
|
-
import { dirname as
|
|
1316
|
-
var
|
|
2091
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2092
|
+
import { dirname as dirname4, join as join4 } from "node:path";
|
|
2093
|
+
var conflict2 = (message2) => Object.assign(new Error(message2), { statusCode: 409 });
|
|
1317
2094
|
function sourceItem(pin, shareUrl) {
|
|
1318
2095
|
const source = pin.revisionId ? `${shareUrl ?? ""}#revision=${encodeURIComponent(pin.revisionId)}&pin=${encodeURIComponent(pin.id)}` : `#review=${encodeURIComponent(pin.reviewId)}&pin=${encodeURIComponent(pin.id)}`;
|
|
1319
2096
|
return {
|
|
@@ -1394,28 +2171,29 @@ async function preserveReviewErrors(path, action3) {
|
|
|
1394
2171
|
if (failure) throw failure;
|
|
1395
2172
|
return result;
|
|
1396
2173
|
}
|
|
1397
|
-
async function updateDesignHandoff(file, input, { env = process.env,
|
|
2174
|
+
async function updateDesignHandoff(file, input, { env = process.env, ...shareOptions } = {}) {
|
|
1398
2175
|
if (!input || !["draft", "update", "approve", "category", "disposition"].includes(input.action))
|
|
1399
2176
|
throw new Error("Unknown design handoff action.");
|
|
1400
2177
|
const initial = currentDesign(file);
|
|
1401
|
-
const unlockRender = await acquireStartLock(
|
|
2178
|
+
const unlockRender = await acquireStartLock(join4(initial.root, ".design/render.lock"));
|
|
1402
2179
|
let outgoing;
|
|
1403
2180
|
try {
|
|
1404
|
-
const unlock = await acquireStartLock(
|
|
2181
|
+
const unlock = await acquireStartLock(join4(initial.root, ".design/handoff.lock"));
|
|
1405
2182
|
try {
|
|
1406
2183
|
await preserveReviewErrors(designReviewPath(file, env), async () => {
|
|
1407
2184
|
const value = snapshot(file, env, shareOptions);
|
|
1408
2185
|
if (input.revision !== value.current.revision)
|
|
1409
|
-
throw
|
|
2186
|
+
throw conflict2("The design changed. Refresh the review before updating its handoff.");
|
|
1410
2187
|
if (["category", "disposition"].includes(input.action)) {
|
|
1411
2188
|
if (input.version !== value.metadata.version)
|
|
1412
|
-
throw
|
|
2189
|
+
throw conflict2("Review organization changed in another window. Reload before saving.");
|
|
1413
2190
|
const pin = findPin(value.feedback.pins, input.pinId, input.revisionId ?? input.reviewId);
|
|
1414
2191
|
if (!pin) throw new Error("The comment is no longer available.");
|
|
1415
2192
|
const choices = input.action === "category" ? ["question", "suggestion", "change-request", "blocker"] : ["accepted", "deferred", "rejected"];
|
|
1416
2193
|
if (!choices.includes(input[input.action])) throw new Error(`Invalid ${input.action}.`);
|
|
1417
2194
|
if (typeof (input.reason ?? "") !== "string" || (input.reason ?? "").length > 16384)
|
|
1418
2195
|
throw new Error("Disposition reason is too long.");
|
|
2196
|
+
if (pin.revisionId) assertDesignShareAvailable({ env, ...shareOptions });
|
|
1419
2197
|
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1420
2198
|
const local = normalizedLocalMetadata(
|
|
1421
2199
|
readJson(metadataPath(value.current), { version: 0, byRevision: {} }),
|
|
@@ -1457,11 +2235,11 @@ async function updateDesignHandoff(file, input, { env = process.env, fetchImpl =
|
|
|
1457
2235
|
};
|
|
1458
2236
|
return;
|
|
1459
2237
|
}
|
|
1460
|
-
const path =
|
|
2238
|
+
const path = join4(dirname4(designSpecPath(value.current.root)), "review-handoff.json");
|
|
1461
2239
|
const previous = readJson(path, null);
|
|
1462
2240
|
if (previous) assertDraft(previous);
|
|
1463
2241
|
if (input.version !== (previous?.version ?? 0))
|
|
1464
|
-
throw
|
|
2242
|
+
throw conflict2("The handoff changed in another window. Reload before saving.");
|
|
1465
2243
|
let shareUrl;
|
|
1466
2244
|
try {
|
|
1467
2245
|
shareUrl = getDesignShareStatus(file, { env, ...shareOptions }).url;
|
|
@@ -1469,12 +2247,12 @@ async function updateDesignHandoff(file, input, { env = process.env, fetchImpl =
|
|
|
1469
2247
|
}
|
|
1470
2248
|
if (input.action === "approve") {
|
|
1471
2249
|
if (!previous || input.contentHash !== previous.contentHash || reviewDigest(previous.basis) !== reviewDigest(value.basis))
|
|
1472
|
-
throw
|
|
2250
|
+
throw conflict2(
|
|
1473
2251
|
"The handoff is out of date. Rebuild and review the current draft before approving."
|
|
1474
2252
|
);
|
|
1475
2253
|
if (!canApproveDesignHandoffResolution(value.resolution)) {
|
|
1476
2254
|
const diagnostic = value.resolution.diagnostics[0];
|
|
1477
|
-
throw
|
|
2255
|
+
throw conflict2(
|
|
1478
2256
|
diagnostic?.message ?? "Resolve the blocking review decisions before approving this handoff."
|
|
1479
2257
|
);
|
|
1480
2258
|
}
|
|
@@ -1488,19 +2266,19 @@ async function updateDesignHandoff(file, input, { env = process.env, fetchImpl =
|
|
|
1488
2266
|
};
|
|
1489
2267
|
approved.markdown = renderMarkdown(approved, value.current.document.title);
|
|
1490
2268
|
assertReviewExperience(approved, DESIGN_HANDOFF_SCHEMA);
|
|
1491
|
-
const archive =
|
|
2269
|
+
const archive = join4(
|
|
1492
2270
|
value.current.root,
|
|
1493
2271
|
".design/handoff-approvals",
|
|
1494
2272
|
`${approved.contentHash}.json`
|
|
1495
2273
|
);
|
|
1496
|
-
if (!
|
|
2274
|
+
if (!existsSync4(archive)) atomicJson(archive, approved);
|
|
1497
2275
|
atomicJson(path, approved);
|
|
1498
|
-
|
|
2276
|
+
writeFileSync3(path.replace(/\.json$/u, ".md"), approved.markdown);
|
|
1499
2277
|
return;
|
|
1500
2278
|
}
|
|
1501
2279
|
const content = input.action === "draft" ? contentFromFeedback(value, shareUrl) : enrichContent(input.content, value, shareUrl);
|
|
1502
2280
|
if (input.action === "update" && (!previous || reviewDigest(previous.basis) !== reviewDigest(value.basis)))
|
|
1503
|
-
throw
|
|
2281
|
+
throw conflict2("The review changed. Rebuild the draft before refining it.");
|
|
1504
2282
|
const affectedScreens = [
|
|
1505
2283
|
...new Set(
|
|
1506
2284
|
sections.flatMap((key) => content[key].map((item) => item.screenId).filter(Boolean))
|
|
@@ -1535,7 +2313,7 @@ async function updateDesignHandoff(file, input, { env = process.env, fetchImpl =
|
|
|
1535
2313
|
draft.markdown = renderMarkdown(draft, value.current.document.title);
|
|
1536
2314
|
assertReviewExperience(draft, DESIGN_HANDOFF_SCHEMA);
|
|
1537
2315
|
atomicJson(path, draft);
|
|
1538
|
-
|
|
2316
|
+
writeFileSync3(path.replace(/\.json$/u, ".md"), draft.markdown);
|
|
1539
2317
|
});
|
|
1540
2318
|
} finally {
|
|
1541
2319
|
unlock();
|
|
@@ -1549,11 +2327,14 @@ async function updateDesignHandoff(file, input, { env = process.env, fetchImpl =
|
|
|
1549
2327
|
synchronization = await publishDesignReviewMetadata(file, outgoing.payload, {
|
|
1550
2328
|
revisionId: outgoing.revisionId,
|
|
1551
2329
|
env,
|
|
1552
|
-
fetchImpl,
|
|
1553
2330
|
...shareOptions
|
|
1554
2331
|
});
|
|
1555
2332
|
} catch (error) {
|
|
1556
|
-
synchronization = {
|
|
2333
|
+
synchronization = {
|
|
2334
|
+
pending: false,
|
|
2335
|
+
error: error.message,
|
|
2336
|
+
...error.code ? { code: error.code } : {}
|
|
2337
|
+
};
|
|
1557
2338
|
}
|
|
1558
2339
|
}
|
|
1559
2340
|
return {
|
|
@@ -1563,6 +2344,19 @@ async function updateDesignHandoff(file, input, { env = process.env, fetchImpl =
|
|
|
1563
2344
|
}
|
|
1564
2345
|
|
|
1565
2346
|
export {
|
|
2347
|
+
PLANNING_FOLDER,
|
|
2348
|
+
CLI_COMMAND,
|
|
2349
|
+
createReviewLedger,
|
|
2350
|
+
mergeReviewLedger,
|
|
2351
|
+
effectiveReviewDecision,
|
|
2352
|
+
ARTIFACT_REVIEW_MAX_STATE_BYTES,
|
|
2353
|
+
normalizeArtifactReview,
|
|
2354
|
+
readArtifactReviewState,
|
|
2355
|
+
writeArtifactReviewState,
|
|
2356
|
+
withArtifactReviewLock,
|
|
2357
|
+
exportArtifactReview,
|
|
2358
|
+
resolveArtifactReviewDestination,
|
|
2359
|
+
decodeArtifactReviewSources,
|
|
1566
2360
|
designReviewKey,
|
|
1567
2361
|
designReviewPath,
|
|
1568
2362
|
readDesignFeedback,
|