@unbrained/pm-cli 2026.9.7 → 2026.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/pm-developer/SKILL.md +12 -4
- package/.agents/skills/pm-developer/references/MULTI_AGENT_MERGE.md +19 -10
- package/.agents/skills/pm-extensions/SKILL.md +6 -0
- package/.agents/skills/pm-sdk/SKILL.md +11 -2
- package/.agents/skills/pm-user/SKILL.md +5 -0
- package/.agents/skills/pm-user/references/ECOSYSTEM_PLANNING.md +95 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +211 -181
- package/dist/cli/after-command-context-usage.js +1 -48
- package/dist/cli/argv-utils.js +1 -40
- package/dist/cli/bootstrap-args.js +1 -9
- package/dist/cli/bundle-integrity.js +2 -110
- package/dist/cli/commander-usage.js +11 -771
- package/dist/cli/commands/activity.js +1 -9
- package/dist/cli/commands/aggregate.js +1 -5
- package/dist/cli/commands/append.js +1 -5
- package/dist/cli/commands/claim.js +1 -5
- package/dist/cli/commands/close-many.js +1 -5
- package/dist/cli/commands/close.js +1 -9
- package/dist/cli/commands/comments.js +1 -5
- package/dist/cli/commands/config.js +1 -9
- package/dist/cli/commands/context.js +1 -9
- package/dist/cli/commands/contracts.js +1 -5
- package/dist/cli/commands/copy.js +1 -9
- package/dist/cli/commands/create.js +1 -9
- package/dist/cli/commands/delete.js +1 -9
- package/dist/cli/commands/deps.js +1 -9
- package/dist/cli/commands/docs.js +1 -9
- package/dist/cli/commands/duplicates.js +1 -9
- package/dist/cli/commands/eval.js +1 -9
- package/dist/cli/commands/extension/describe.js +1 -5
- package/dist/cli/commands/extension/scaffold.js +1 -5
- package/dist/cli/commands/extension/shared.js +1 -5
- package/dist/cli/commands/extension.js +1 -9
- package/dist/cli/commands/files.js +1 -9
- package/dist/cli/commands/focus.js +1 -9
- package/dist/cli/commands/gc.js +1 -9
- package/dist/cli/commands/get.js +1 -9
- package/dist/cli/commands/graph.js +1 -10
- package/dist/cli/commands/health.js +1 -9
- package/dist/cli/commands/history-compact.js +1 -10
- package/dist/cli/commands/history-redact.js +1 -11
- package/dist/cli/commands/history-repair.js +1 -10
- package/dist/cli/commands/history.js +1 -5
- package/dist/cli/commands/index.js +1 -58
- package/dist/cli/commands/init-agent-guidance.js +1 -9
- package/dist/cli/commands/init.js +1 -9
- package/dist/cli/commands/learnings.js +1 -5
- package/dist/cli/commands/lifecycle-transitions.js +1 -5
- package/dist/cli/commands/list.js +1 -9
- package/dist/cli/commands/merge.js +1 -11
- package/dist/cli/commands/next.js +1 -9
- package/dist/cli/commands/notes.js +1 -5
- package/dist/cli/commands/plan.js +1 -9
- package/dist/cli/commands/profile.js +1 -9
- package/dist/cli/commands/restore.js +1 -9
- package/dist/cli/commands/scheduling-shortcuts.js +1 -9
- package/dist/cli/commands/schema.js +1 -10
- package/dist/cli/commands/search.js +1 -9
- package/dist/cli/commands/stats.js +1 -9
- package/dist/cli/commands/telemetry.js +1 -9
- package/dist/cli/commands/test-all.js +1 -9
- package/dist/cli/commands/test-runs.js +1 -9
- package/dist/cli/commands/test.js +1 -9
- package/dist/cli/commands/update-many.js +1 -5
- package/dist/cli/commands/update.js +1 -9
- package/dist/cli/commands/upgrade.js +1 -5
- package/dist/cli/commands/validate.js +1 -15
- package/dist/cli/context-intent-invocation.js +1 -20
- package/dist/cli/description-stdin.js +1 -17
- package/dist/cli/error-guidance.js +1 -1353
- package/dist/cli/extension-command-help.js +5 -636
- package/dist/cli/extension-command-options.js +1 -427
- package/dist/cli/extension-recovery.js +1 -126
- package/dist/cli/help-content.js +2 -1001
- package/dist/cli/help-json-payload.js +4 -570
- package/dist/cli/main.d.ts +4 -0
- package/dist/cli/main.js +5 -2573
- package/dist/cli/migration-gates.js +1 -289
- package/dist/cli/public.js +1 -20
- package/dist/cli/register-annotations.js +1 -270
- package/dist/cli/register-assurance.js +1 -62
- package/dist/cli/register-files-lookup.js +1 -56
- package/dist/cli/register-history-activity.d.ts +8 -0
- package/dist/cli/register-history-activity.js +2 -0
- package/dist/cli/register-history-author.js +1 -38
- package/dist/cli/register-history-maintenance.d.ts +9 -0
- package/dist/cli/register-history-maintenance.js +2 -0
- package/dist/cli/register-list-query.js +10 -943
- package/dist/cli/register-mutation-options.js +1 -9
- package/dist/cli/register-mutation.js +19 -1893
- package/dist/cli/register-operations.js +1 -920
- package/dist/cli/register-setup.js +1 -716
- package/dist/cli/register-structured-mutation.js +1 -196
- package/dist/cli/registration-helpers.js +1 -5
- package/dist/cli/runtime-compatibility-boundary.js +4 -42
- package/dist/cli/schema-registration-helpers.js +1 -73
- package/dist/cli/search-refresh.js +1 -13
- package/dist/cli/stats-analytics-json.js +1 -68
- package/dist/cli/telemetry-flush.js +1 -10
- package/dist/cli-bundle/bundle-manifest.json +164 -156
- package/dist/cli-bundle/chunks/chunk-3BZZA6A3.js +5 -0
- package/dist/cli-bundle/chunks/chunk-AMRDVX36.js +235 -0
- package/dist/cli-bundle/chunks/chunk-DAM4CBBR.js +2202 -0
- package/dist/cli-bundle/chunks/{chunk-WYRZZ5ZE.js → chunk-DEZPZOQ7.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-FLDWYEND.js +77 -0
- package/dist/cli-bundle/chunks/{chunk-NPN5I67J.js → chunk-G5ADGQ2D.js} +3 -3
- package/dist/cli-bundle/chunks/chunk-JAAEYHUA.js +2 -0
- package/dist/cli-bundle/chunks/{chunk-JX4DH4WC.js → chunk-KR3RP4FH.js} +3 -3
- package/dist/cli-bundle/chunks/chunk-LEZOUXG6.js +2 -0
- package/dist/cli-bundle/chunks/chunk-NSAPNEYH.js +2 -0
- package/dist/cli-bundle/chunks/chunk-QOEKOECV.js +29 -0
- package/dist/cli-bundle/chunks/chunk-RHSFOMHU.js +2 -0
- package/dist/cli-bundle/chunks/chunk-XOD2PCB6.js +20 -0
- package/dist/cli-bundle/chunks/{chunk-JT6XF6GE.js → chunk-YVEQ2AMO.js} +4 -4
- package/dist/cli-bundle/chunks/register-list-query-KSK3R63M.js +11 -0
- package/dist/cli-bundle/chunks/register-mutation-IQ5C7U6O.js +20 -0
- package/dist/cli-bundle/chunks/{register-operations-I5W3KMGB.js → register-operations-DWVHR2NC.js} +2 -2
- package/dist/cli-bundle/chunks/register-setup-BQ5SVD4B.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-3WRPWLA7.js +3 -0
- package/dist/cli-bundle/focused-chunks/chunk-7MBUII7U.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-COPBO5HW.js +8 -0
- package/dist/cli-bundle/focused-chunks/chunk-DUN4IXYN.js +20 -0
- package/dist/cli-bundle/focused-chunks/{chunk-LIJIA4TY.js → chunk-EE5QSM4Z.js} +3 -3
- package/dist/cli-bundle/focused-chunks/chunk-HJVARYM3.js +6 -0
- package/dist/cli-bundle/focused-chunks/chunk-IAUOUGVR.js +177 -0
- package/dist/cli-bundle/focused-chunks/chunk-KOOMJ54F.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-MZDXX7JY.js +56 -0
- package/dist/cli-bundle/focused-chunks/{chunk-M6PBRFSH.js → chunk-OPFHFGDD.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-QHNETP77.js +9 -0
- package/dist/cli-bundle/focused-chunks/{chunk-3BZEOXLT.js → chunk-RMWES4M6.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-RXT4P5QH.js +10 -0
- package/dist/cli-bundle/focused-chunks/chunk-SDOL6FM2.js +12 -0
- package/dist/cli-bundle/focused-chunks/chunk-SQV4AAUC.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-PEFABF7E.js → chunk-WE3IUNIW.js} +3 -3
- package/dist/cli-bundle/focused-chunks/chunk-YALOUBWJ.js +2 -0
- package/dist/cli-bundle/main.js +20 -20
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +2 -2
- package/dist/cli-bundle/sdk-core.js +29 -29
- package/dist/cli-bundle/sdk-governance.js +1 -1
- package/dist/cli-bundle/sdk-graph.js +1 -1
- package/dist/cli-bundle/sdk-merge.js +42 -42
- package/dist/cli-bundle/sdk-query.js +1 -1
- package/dist/cli-bundle/sdk-runtime.js +1 -1
- package/dist/cli-bundle/sdk-testing.js +1 -1
- package/dist/cli-bundle/sdk.js +11 -11
- package/dist/cli.js +1 -104
- package/dist/core/checkpoint/checkpoint-gc.js +1 -169
- package/dist/core/checkpoint/mutation-checkpoint.js +2 -133
- package/dist/core/config/nested-settings.js +1 -534
- package/dist/core/config/positional-value.js +1 -109
- package/dist/core/diagnostics/remediation.js +1 -506
- package/dist/core/extensions/activation-summary-markdown.js +2 -145
- package/dist/core/extensions/activation-summary.js +1 -92
- package/dist/core/extensions/activation-transaction.js +1 -46
- package/dist/core/extensions/capability-usage.js +1 -173
- package/dist/core/extensions/command-hook-context.js +1 -13
- package/dist/core/extensions/command-metadata-contract.js +1 -22
- package/dist/core/extensions/command-visibility-tier.js +1 -29
- package/dist/core/extensions/contribution-inventory.js +1 -173
- package/dist/core/extensions/exporter-output-contract.js +1 -66
- package/dist/core/extensions/extension-candidate.js +1 -18
- package/dist/core/extensions/extension-capability-aliases.js +1 -158
- package/dist/core/extensions/extension-hook-runtime.js +1 -699
- package/dist/core/extensions/extension-policy.js +1 -599
- package/dist/core/extensions/extension-registries.js +1 -62
- package/dist/core/extensions/extension-runtime-helpers.js +1 -33
- package/dist/core/extensions/extension-types.js +1 -102
- package/dist/core/extensions/flag-definition-validation.js +1 -52
- package/dist/core/extensions/flag-value-types.js +1 -64
- package/dist/core/extensions/index.js +1 -526
- package/dist/core/extensions/item-field-types.js +1 -39
- package/dist/core/extensions/item-fields.js +1 -261
- package/dist/core/extensions/loader.js +1 -2223
- package/dist/core/extensions/manifest-schema.js +1 -69
- package/dist/core/extensions/module-graph-snapshot.js +1 -33
- package/dist/core/extensions/preflight-ownership.js +1 -41
- package/dist/core/extensions/registration-validation.js +1 -82
- package/dist/core/extensions/registration-values.js +1 -209
- package/dist/core/extensions/reserved-host-flags.js +1 -36
- package/dist/core/extensions/runtime-registrations.js +1 -67
- package/dist/core/extensions/service-ownership.js +1 -19
- package/dist/core/extensions/version-compat.js +1 -216
- package/dist/core/fs/fs-utils.js +2 -158
- package/dist/core/fs/host-environment-errors.js +1 -141
- package/dist/core/fs/index.js +1 -9
- package/dist/core/fs/path-utils.js +1 -17
- package/dist/core/governance/content-fields.js +1 -160
- package/dist/core/governance/issue-codes.js +1 -151
- package/dist/core/governance/metadata-coverage.js +1 -358
- package/dist/core/history/drift-cache.js +3 -72
- package/dist/core/history/drift-scan.js +1 -342
- package/dist/core/history/event-classification.js +1 -109
- package/dist/core/history/event-index.js +8 -784
- package/dist/core/history/history-compact-bulk.js +1 -104
- package/dist/core/history/history-diff.js +1 -158
- package/dist/core/history/history-rewrite.d.ts +6 -1
- package/dist/core/history/history-rewrite.js +1 -107
- package/dist/core/history/history-storage-stats.js +1 -102
- package/dist/core/history/history-stream-policy.js +1 -62
- package/dist/core/history/history.js +2 -617
- package/dist/core/history/identity.js +1 -30
- package/dist/core/history/index.js +1 -9
- package/dist/core/history/projection.js +1 -196
- package/dist/core/history/read.d.ts +9 -0
- package/dist/core/history/read.js +1 -57
- package/dist/core/history/replay.js +2 -439
- package/dist/core/history/version-address.js +1 -32
- package/dist/core/history/workspace-history.js +2 -433
- package/dist/core/io/body-file.js +1 -62
- package/dist/core/io/bulk-ids-input.js +1 -112
- package/dist/core/item/actionability.js +1 -327
- package/dist/core/item/dependency-reference.js +1 -20
- package/dist/core/item/id.js +1 -112
- package/dist/core/item/index.js +1 -12
- package/dist/core/item/item-format-version.js +1 -78
- package/dist/core/item/item-format.js +5 -1473
- package/dist/core/item/item-record.js +1 -23
- package/dist/core/item/item-type-definition.js +1 -133
- package/dist/core/item/parent-reference-policy.js +1 -57
- package/dist/core/item/parse.js +2 -603
- package/dist/core/item/plan-lifecycle.js +1 -19
- package/dist/core/item/priority.js +1 -61
- package/dist/core/item/sprint-release-format.js +1 -38
- package/dist/core/item/status-filter.js +1 -115
- package/dist/core/item/status.js +1 -43
- package/dist/core/item/toon-decode.js +1 -29
- package/dist/core/item/type-registry.js +1 -715
- package/dist/core/item/type-synonyms.js +1 -38
- package/dist/core/lock/index.js +1 -9
- package/dist/core/lock/lock-gc.js +1 -240
- package/dist/core/lock/lock.js +3 -590
- package/dist/core/output/mutation-projection.js +1 -217
- package/dist/core/output/output-control.js +1 -25
- package/dist/core/output/output.js +11 -494
- package/dist/core/output/query-summary.js +1 -58
- package/dist/core/output/tabular.js +1 -77
- package/dist/core/packages/manifest.js +1 -244
- package/dist/core/packages/root.js +1 -84
- package/dist/core/profile/profile-describe.js +1 -83
- package/dist/core/profile/profile-lint.js +1 -415
- package/dist/core/profile/profile-plan.js +0 -0
- package/dist/core/profile/profile-presets.js +1 -370
- package/dist/core/reproducibility/context.js +1 -82
- package/dist/core/schema/fields-file.js +2 -288
- package/dist/core/schema/item-types-file.js +2 -296
- package/dist/core/schema/runtime-field-filters.js +1 -49
- package/dist/core/schema/runtime-field-values.js +1 -313
- package/dist/core/schema/runtime-schema.js +2 -826
- package/dist/core/schema/status-defs-file.js +2 -227
- package/dist/core/schema/status-token.js +1 -31
- package/dist/core/schema/type-inference.js +1 -95
- package/dist/core/schema/type-presets.js +1 -87
- package/dist/core/schema/type-workflows.js +1 -104
- package/dist/core/search/background-refresh.js +2 -286
- package/dist/core/search/bm25.js +1 -146
- package/dist/core/search/cache.js +2 -534
- package/dist/core/search/corpus.js +1 -311
- package/dist/core/search/embedding-batches.js +1 -163
- package/dist/core/search/eval.js +1 -225
- package/dist/core/search/http-client.js +1 -75
- package/dist/core/search/providers.js +1 -242
- package/dist/core/search/relevance.js +1 -244
- package/dist/core/search/semantic-defaults.js +1 -199
- package/dist/core/search/staleness.js +1 -30
- package/dist/core/search/vector-stores.js +2 -664
- package/dist/core/search/vectorization-metadata.js +1 -81
- package/dist/core/sentry/helpers.js +1 -272
- package/dist/core/sentry/instrument.js +1 -387
- package/dist/core/session/session-state.js +1 -260
- package/dist/core/shared/agent-session-context.js +1 -133
- package/dist/core/shared/author.js +1 -1172
- package/dist/core/shared/command-types.js +1 -4
- package/dist/core/shared/conflict-markers.js +1 -31
- package/dist/core/shared/constants.js +1 -405
- package/dist/core/shared/errors.js +1 -20
- package/dist/core/shared/html-entity-decode.js +1 -131
- package/dist/core/shared/index.js +1 -12
- package/dist/core/shared/item-metadata-contract.js +1 -89
- package/dist/core/shared/lazy-module.js +1 -16
- package/dist/core/shared/levenshtein.js +1 -61
- package/dist/core/shared/memo.js +1 -19
- package/dist/core/shared/numeric-parsers.js +1 -20
- package/dist/core/shared/option-alias-visibility.js +1 -67
- package/dist/core/shared/primitives.js +1 -127
- package/dist/core/shared/serial-queue.js +1 -79
- package/dist/core/shared/serialization.js +1 -210
- package/dist/core/shared/split-comma-list.js +1 -30
- package/dist/core/shared/text-normalization.js +1 -38
- package/dist/core/shared/time.js +1 -306
- package/dist/core/store/index.js +1 -12
- package/dist/core/store/item-format-migration.js +1 -125
- package/dist/core/store/item-metadata-cache.js +1 -1116
- package/dist/core/store/item-metadata-query-index.js +9 -394
- package/dist/core/store/item-metadata-read-work.js +1 -43
- package/dist/core/store/item-store.js +1 -693
- package/dist/core/store/paths.d.ts +1 -1
- package/dist/core/store/paths.js +1 -214
- package/dist/core/store/settings-read-cache.js +1 -85
- package/dist/core/store/settings-validator.js +1 -421
- package/dist/core/store/settings.js +3 -1309
- package/dist/core/store/tracker-preflight.js +1 -141
- package/dist/core/telemetry/consent.js +5 -108
- package/dist/core/telemetry/observability.js +1 -246
- package/dist/core/telemetry/runtime.js +4 -1988
- package/dist/core/test/background-runs.js +4 -945
- package/dist/core/test/item-test-run-tracking.js +1 -63
- package/dist/core/test/run-selectors.js +1 -120
- package/dist/core/validate/estimate-defaults.js +1 -89
- package/dist/core/validate/fix-planning.js +1 -189
- package/dist/core/validate/linked-artifact-reference.js +1 -36
- package/dist/core/validate/missing-by-type.js +1 -34
- package/dist/core/validate/missing-link-owners.js +1 -95
- package/dist/core/validate/stale-file-classification.js +1 -82
- package/dist/mcp/http-server.js +5 -448
- package/dist/mcp/legacy-adapter.js +1 -77
- package/dist/mcp/runtime-capabilities.js +1 -200
- package/dist/mcp/server.js +4 -1233
- package/dist/mcp/tool-definitions.js +1 -804
- package/dist/sdk/actionability.js +1 -13
- package/dist/sdk/agent/closed-domain-contracts.js +1 -161
- package/dist/sdk/agent/command-recovery.js +1 -59
- package/dist/sdk/agent/command-suggestions.js +1 -66
- package/dist/sdk/agent/create-option-policy.js +1 -31
- package/dist/sdk/agent/identity-config.js +1 -106
- package/dist/sdk/agent/item-addressing.js +1 -207
- package/dist/sdk/agent/refusal-closure-census.js +1 -151
- package/dist/sdk/agent/refusal-closure.js +1 -133
- package/dist/sdk/agent/refusal-corpus-contracts.js +1 -193
- package/dist/sdk/agent/refusal-reachability.js +1 -593
- package/dist/sdk/agent/subcommand-recovery.js +1 -83
- package/dist/sdk/agent/task-transcript-contracts.js +1 -197
- package/dist/sdk/agent/tracker-preflight-contracts.js +1 -126
- package/dist/sdk/agent-capability-contracts.js +1 -428
- package/dist/sdk/agent-session-context.js +1 -9
- package/dist/sdk/annotations.js +1 -448
- package/dist/sdk/author-attribution.js +1 -468
- package/dist/sdk/authoring.js +1 -20
- package/dist/sdk/cli-bootstrap.js +1 -838
- package/dist/sdk/cli-contracts/agent-output-contracts.d.ts +1 -1
- package/dist/sdk/cli-contracts/agent-output-contracts.js +4 -476
- package/dist/sdk/cli-contracts/bootstrap-command-scanner.js +1 -79
- package/dist/sdk/cli-contracts/command-aliases.d.ts +4 -0
- package/dist/sdk/cli-contracts/command-aliases.js +1 -107
- package/dist/sdk/cli-contracts/command-exit-contracts.js +1 -151
- package/dist/sdk/cli-contracts/commander-mutation-options.js +1 -1081
- package/dist/sdk/cli-contracts/commander-types.js +1 -159
- package/dist/sdk/cli-contracts/completeness.js +1 -393
- package/dist/sdk/cli-contracts/enum-contracts.js +1 -222
- package/dist/sdk/cli-contracts/flag-contracts.js +1 -1947
- package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +1 -347
- package/dist/sdk/cli-contracts/grammar-contracts.js +1 -986
- package/dist/sdk/cli-contracts/registration-helpers.js +7 -990
- package/dist/sdk/cli-contracts/runtime-contracts.d.ts +5 -0
- package/dist/sdk/cli-contracts/runtime-contracts.js +1 -2090
- package/dist/sdk/cli-contracts/schemas/assurance-definition.js +1 -324
- package/dist/sdk/cli-contracts/string-lists.js +1 -16
- package/dist/sdk/cli-contracts/tool-option-contracts.js +1 -461
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +1 -2167
- package/dist/sdk/cli-contracts/tool-schema.d.ts +3 -1
- package/dist/sdk/cli-contracts/tool-schema.js +1 -1664
- package/dist/sdk/cli-contracts/typed-action-inputs.js +1 -4
- package/dist/sdk/cli-contracts.d.ts +1 -1
- package/dist/sdk/cli-contracts.js +1 -17
- package/dist/sdk/cli-program.js +1 -49
- package/dist/sdk/command-line.d.ts +2 -5
- package/dist/sdk/command-line.js +1 -78
- package/dist/sdk/comments.js +1 -133
- package/dist/sdk/compile-cache.js +1 -73
- package/dist/sdk/completion.js +157 -837
- package/dist/sdk/compose.js +1 -1090
- package/dist/sdk/config.js +1 -1204
- package/dist/sdk/context/recency.js +1 -33
- package/dist/sdk/context/semantic-session-attribution.js +1 -82
- package/dist/sdk/context/usage-egress.js +1 -29
- package/dist/sdk/context/usage-ledger.js +3 -185
- package/dist/sdk/context-intent-contracts.js +1 -709
- package/dist/sdk/context-intent-runtime.js +1 -115
- package/dist/sdk/context-packing.js +1 -218
- package/dist/sdk/context-relevance.js +1 -503
- package/dist/sdk/context-signal-store.js +2 -426
- package/dist/sdk/context-usage.js +1 -367
- package/dist/sdk/contracts.js +1 -23
- package/dist/sdk/core-governance.js +1 -14
- package/dist/sdk/core.js +1 -14
- package/dist/sdk/corpus-shape.js +1 -401
- package/dist/sdk/define.js +1 -220
- package/dist/sdk/dependencies.js +1 -718
- package/dist/sdk/dependency-flag-validation.js +1 -156
- package/dist/sdk/dependency-provenance.js +1 -135
- package/dist/sdk/docs.js +1 -14
- package/dist/sdk/duplicates.js +1 -40
- package/dist/sdk/environment/agent-environment.js +1 -466
- package/dist/sdk/environment/host-environment-errors.js +1 -8
- package/dist/sdk/environment/project-runtime-compatibility.js +1 -481
- package/dist/sdk/environment/source-context.js +1 -28
- package/dist/sdk/environment/tracker-preflight.js +1 -9
- package/dist/sdk/error-code-catalog.js +1 -153
- package/dist/sdk/error-runtime.js +1 -58
- package/dist/sdk/errors.js +1 -58
- package/dist/sdk/eval.js +1 -190
- package/dist/sdk/extension/author-manifest.js +1 -92
- package/dist/sdk/extension/bundled-catalog.js +1 -332
- package/dist/sdk/extension/concurrency.js +1 -25
- package/dist/sdk/extension/copy-scope.d.ts +2 -0
- package/dist/sdk/extension/copy-scope.js +2 -0
- package/dist/sdk/extension/custom-field-collisions.js +1 -30
- package/dist/sdk/extension/describe.js +3 -189
- package/dist/sdk/extension/doctor.js +1 -606
- package/dist/sdk/extension/install-plan.d.ts +69 -0
- package/dist/sdk/extension/install-plan.js +2 -0
- package/dist/sdk/extension/install-receipts.d.ts +7 -0
- package/dist/sdk/extension/install-receipts.js +2 -0
- package/dist/sdk/extension/install-runtime.js +3 -313
- package/dist/sdk/extension/install-snapshot.js +1 -66
- package/dist/sdk/extension/install-sources.js +5 -1057
- package/dist/sdk/extension/managed-state.js +2 -252
- package/dist/sdk/extension/migrations.js +2 -174
- package/dist/sdk/extension/output-ownership.js +1 -25
- package/dist/sdk/extension/runtime-summary.js +1 -18
- package/dist/sdk/extension/scaffold.js +4 -2308
- package/dist/sdk/extension/shared.js +1 -147
- package/dist/sdk/extension/source-resolution.js +1 -65
- package/dist/sdk/extension/update-check.js +1 -67
- package/dist/sdk/extension-command-context.js +1 -44
- package/dist/sdk/extension-contracts.js +1 -73
- package/dist/sdk/extension.d.ts +7 -2
- package/dist/sdk/extension.js +1 -2196
- package/dist/sdk/files.js +1 -535
- package/dist/sdk/flag-invocation-contracts.d.ts +0 -5
- package/dist/sdk/flag-invocation-contracts.js +1 -344
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -2224
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1 -2236
- package/dist/sdk/generated-error-code-catalog.js +1 -16
- package/dist/sdk/governance/assurance-action-contracts.js +1 -4
- package/dist/sdk/governance/assurance-action.js +1 -411
- package/dist/sdk/governance/assurance-limits.js +1 -10
- package/dist/sdk/governance/assurance-mutation-error.js +1 -81
- package/dist/sdk/governance/assurance-presets.js +1 -135
- package/dist/sdk/governance/assurance-runtime.js +1 -238
- package/dist/sdk/governance/assurance.js +4 -1182
- package/dist/sdk/governance/boundary-fixtures.js +1 -200
- package/dist/sdk/governance/coverage-assurance.js +1 -60
- package/dist/sdk/governance/defect-recurrence-coverage.js +1 -188
- package/dist/sdk/governance/defect-recurrence-graph.js +1 -120
- package/dist/sdk/governance/defect-recurrence-signals.js +1 -24
- package/dist/sdk/governance/defect-recurrence.js +1 -623
- package/dist/sdk/governance/extension-host-version.js +1 -115
- package/dist/sdk/governance/gc.js +1 -368
- package/dist/sdk/governance/health.js +1 -2367
- package/dist/sdk/governance/hierarchy-validation.js +1 -108
- package/dist/sdk/governance/linked-file-report.js +1 -71
- package/dist/sdk/governance/merge-receipt-history.js +1 -206
- package/dist/sdk/governance/provenance-health.js +1 -282
- package/dist/sdk/governance/reindex.js +4 -761
- package/dist/sdk/governance/stale-work.js +1 -82
- package/dist/sdk/governance/status-role-diagnostics.js +1 -35
- package/dist/sdk/governance/storage-integrity.js +1 -280
- package/dist/sdk/governance/tracked-runtime-cache.js +1 -106
- package/dist/sdk/governance/upgrade.js +2 -327
- package/dist/sdk/governance/validate-history-drift.js +1 -81
- package/dist/sdk/governance/validate-item-reader.js +1 -17
- package/dist/sdk/governance/validate-normalization.js +1 -36
- package/dist/sdk/governance/validate.js +1 -2261
- package/dist/sdk/governance/workspace-position.js +1 -163
- package/dist/sdk/governance.d.ts +1 -0
- package/dist/sdk/governance.js +1 -31
- package/dist/sdk/graph/adapter.js +1 -181
- package/dist/sdk/graph/analytics.js +1 -474
- package/dist/sdk/graph/assembly.js +1 -377
- package/dist/sdk/graph/cache.js +1 -160
- package/dist/sdk/graph/centrality.js +1 -258
- package/dist/sdk/graph/conformance.js +1 -130
- package/dist/sdk/graph/deadline-scheduling.d.ts +90 -0
- package/dist/sdk/graph/deadline-scheduling.js +2 -0
- package/dist/sdk/graph/durable-cache.d.ts +1 -1
- package/dist/sdk/graph/durable-cache.js +1 -361
- package/dist/sdk/graph/governance-contracts.js +1 -34
- package/dist/sdk/graph/governance.d.ts +13 -1
- package/dist/sdk/graph/governance.js +1 -735
- package/dist/sdk/graph/hierarchy-integrity.js +1 -310
- package/dist/sdk/graph/index.d.ts +3 -2
- package/dist/sdk/graph/index.js +1 -26
- package/dist/sdk/graph/lifecycle-coverage.d.ts +32 -0
- package/dist/sdk/graph/lifecycle-coverage.js +2 -0
- package/dist/sdk/graph/mutation-advisory.js +1 -176
- package/dist/sdk/graph/remediation.js +1 -197
- package/dist/sdk/graph/run.d.ts +15 -1
- package/dist/sdk/graph/run.js +1 -768
- package/dist/sdk/graph/scheduling.js +1 -124
- package/dist/sdk/graph/traversal.js +1 -284
- package/dist/sdk/graph/workspace-relationship-kind-registry.js +1 -27
- package/dist/sdk/graph.js +1 -15
- package/dist/sdk/guide-topics.js +1 -707
- package/dist/sdk/guide.js +1 -279
- package/dist/sdk/history/maintenance.d.ts +71 -0
- package/dist/sdk/history/maintenance.js +2 -0
- package/dist/sdk/history/salvage.js +2 -247
- package/dist/sdk/history/subject.d.ts +19 -0
- package/dist/sdk/history/subject.js +2 -0
- package/dist/sdk/history-analytics.js +1 -282
- package/dist/sdk/history-compact.js +1 -527
- package/dist/sdk/history-mcp.js +1 -68
- package/dist/sdk/history-provenance.js +1 -182
- package/dist/sdk/history-read.js +1 -96
- package/dist/sdk/history-redact.d.ts +1 -14
- package/dist/sdk/history-redact.js +1 -643
- package/dist/sdk/history-repair.d.ts +5 -0
- package/dist/sdk/history-repair.js +1 -657
- package/dist/sdk/improvement-ledger-validation.js +1 -62
- package/dist/sdk/improvement-ledger.js +2 -257
- package/dist/sdk/index.d.ts +4 -0
- package/dist/sdk/index.js +1 -183
- package/dist/sdk/init-agent-guidance.js +2 -496
- package/dist/sdk/init.js +2 -820
- package/dist/sdk/invocation-author.js +1 -39
- package/dist/sdk/item-children.js +1 -90
- package/dist/sdk/item-metadata-index.js +1 -15
- package/dist/sdk/item-schedule.js +1 -27
- package/dist/sdk/item-transaction.js +1 -396
- package/dist/sdk/learnings.js +1 -29
- package/dist/sdk/lifecycle/append.js +2 -53
- package/dist/sdk/lifecycle/claim.d.ts +2 -0
- package/dist/sdk/lifecycle/claim.js +1 -242
- package/dist/sdk/lifecycle/close-many.js +1 -438
- package/dist/sdk/lifecycle/close.js +1 -468
- package/dist/sdk/lifecycle/copy.js +1 -212
- package/dist/sdk/lifecycle/create.js +1 -1949
- package/dist/sdk/lifecycle/delete.js +1 -45
- package/dist/sdk/lifecycle/event-validation-messages.js +1 -91
- package/dist/sdk/lifecycle/focus.js +1 -91
- package/dist/sdk/lifecycle/hierarchy-mutation-lock.js +1 -24
- package/dist/sdk/lifecycle/legacy-none-tokens.js +1 -149
- package/dist/sdk/lifecycle/lifecycle-transitions.js +1 -40
- package/dist/sdk/lifecycle/mcp-actions.d.ts +2 -0
- package/dist/sdk/lifecycle/mcp-actions.js +1 -51
- package/dist/sdk/lifecycle/metadata-normalizers.js +1 -46
- package/dist/sdk/lifecycle/mutation-command-options.js +1 -9
- package/dist/sdk/lifecycle/plan.js +1 -1606
- package/dist/sdk/lifecycle/recurrence-parsers.js +1 -147
- package/dist/sdk/lifecycle/reopen.js +1 -68
- package/dist/sdk/lifecycle/repeatable-metadata-parsers.js +1 -143
- package/dist/sdk/lifecycle/restore.d.ts +1 -0
- package/dist/sdk/lifecycle/restore.js +1 -330
- package/dist/sdk/lifecycle/shared-unset-fields.js +1 -295
- package/dist/sdk/lifecycle/update-many.js +1 -861
- package/dist/sdk/lifecycle/update.js +1 -2136
- package/dist/sdk/lifecycle-completion.js +1 -92
- package/dist/sdk/lifecycle-policy.js +1 -120
- package/dist/sdk/linked-artifacts.js +1 -530
- package/dist/sdk/mcp/apps.js +4 -133
- package/dist/sdk/mcp/authorization.js +1 -404
- package/dist/sdk/mcp/discovery.js +1 -494
- package/dist/sdk/mcp/interactions.js +1 -336
- package/dist/sdk/mcp/protocol.d.ts +7 -0
- package/dist/sdk/mcp/protocol.js +1 -184
- package/dist/sdk/mcp/skills.js +1 -389
- package/dist/sdk/mcp/subscriptions.js +1 -211
- package/dist/sdk/mcp/tasks.js +2 -430
- package/dist/sdk/mcp/transport.js +1 -260
- package/dist/sdk/merge/abandoned-receipts.js +1 -46
- package/dist/sdk/merge/driver.js +1 -212
- package/dist/sdk/merge/index.js +1 -16
- package/dist/sdk/merge/install.js +3 -561
- package/dist/sdk/merge/receipt-file-boundary.js +1 -46
- package/dist/sdk/merge/receipt-operation.js +1 -96
- package/dist/sdk/merge/receipt-schema.js +1 -397
- package/dist/sdk/merge/receipts.js +4 -525
- package/dist/sdk/merge/reconcile.js +1 -352
- package/dist/sdk/merge/three-way.js +3 -775
- package/dist/sdk/merge.js +1 -10
- package/dist/sdk/mutation-events.js +1 -292
- package/dist/sdk/mutation-guard.js +1 -216
- package/dist/sdk/notes.js +1 -139
- package/dist/sdk/output-contracts.js +1 -172
- package/dist/sdk/output-projection.js +1 -430
- package/dist/sdk/output-token-accounting.js +1 -140
- package/dist/sdk/output.js +1 -64
- package/dist/sdk/ownership-source.js +1 -21
- package/dist/sdk/package-import-adapters.js +1 -442
- package/dist/sdk/package-migrations.js +1 -20
- package/dist/sdk/package-runtime-options.js +1 -80
- package/dist/sdk/pagination.js +1 -162
- package/dist/sdk/profile.js +1 -454
- package/dist/sdk/provenance.js +1 -258
- package/dist/sdk/public-surface.json +1 -1
- package/dist/sdk/query/activity.js +1 -401
- package/dist/sdk/query/aggregate.d.ts +16 -0
- package/dist/sdk/query/aggregate.js +1 -390
- package/dist/sdk/query/calendar.js +1 -964
- package/dist/sdk/query/complete-list.js +1 -215
- package/dist/sdk/query/context.js +1 -1702
- package/dist/sdk/query/duplicate-candidates.js +1 -70
- package/dist/sdk/query/get.js +1 -450
- package/dist/sdk/query/history.js +1 -186
- package/dist/sdk/query/item-filter-options.js +1 -9
- package/dist/sdk/query/light-metadata.js +1 -18
- package/dist/sdk/query/list-filter-shared.js +1 -81
- package/dist/sdk/query/list.js +1 -1304
- package/dist/sdk/query/multi-value-filters.js +1 -78
- package/dist/sdk/query/next.js +1 -513
- package/dist/sdk/query/parsers.js +1 -44
- package/dist/sdk/query/projection-contracts.js +1 -184
- package/dist/sdk/query/search-contracts.js +1 -230
- package/dist/sdk/query/search-pagination.js +1 -35
- package/dist/sdk/query/search-rendering.js +1 -105
- package/dist/sdk/query/search.js +2 -1987
- package/dist/sdk/query.js +1 -35
- package/dist/sdk/read-output/continuation.js +1 -194
- package/dist/sdk/read-output-budget.js +1 -135
- package/dist/sdk/read-output-contracts.js +1 -1225
- package/dist/sdk/read-output-rows.js +1 -186
- package/dist/sdk/read-output-session.js +1 -208
- package/dist/sdk/relationship-analytics.js +1 -410
- package/dist/sdk/relationship-context.js +1 -284
- package/dist/sdk/relationship-history.js +4 -519
- package/dist/sdk/relationship-kinds/contract.js +1 -22
- package/dist/sdk/relationship-migration.js +1 -77
- package/dist/sdk/relationships.js +1 -579
- package/dist/sdk/reproducibility/process.js +1 -87
- package/dist/sdk/runtime-action-aliases.js +1 -59
- package/dist/sdk/runtime-extended-actions.js +1 -177
- package/dist/sdk/runtime-input.js +1 -614
- package/dist/sdk/runtime-primitives.js +1 -84
- package/dist/sdk/runtime-public-contracts.js +1 -4
- package/dist/sdk/runtime-stats-options.js +1 -38
- package/dist/sdk/runtime.js +1 -1928
- package/dist/sdk/scheduling-shortcuts.js +1 -63
- package/dist/sdk/schema-migration.js +3 -856
- package/dist/sdk/schema.js +1 -1376
- package/dist/sdk/search-refresh-worker.js +1 -18
- package/dist/sdk/similarity-scoring.js +1 -53
- package/dist/sdk/similarity.js +1 -326
- package/dist/sdk/start-task-status.js +1 -14
- package/dist/sdk/stats.js +1 -270
- package/dist/sdk/structured-mutations.js +1 -580
- package/dist/sdk/telemetry-flush.js +1 -13
- package/dist/sdk/telemetry.js +1 -405
- package/dist/sdk/templates.js +2 -292
- package/dist/sdk/test/batch.js +2 -509
- package/dist/sdk/test/entry.js +1 -70
- package/dist/sdk/test/execution.js +6 -2211
- package/dist/sdk/test/linked-command-detection.js +1 -546
- package/dist/sdk/test/measurements.js +1 -91
- package/dist/sdk/test/parsers.js +1 -545
- package/dist/sdk/test/runs.js +1 -193
- package/dist/sdk/test/trust.js +2 -206
- package/dist/sdk/testing.js +4 -1391
- package/dist/sdk/traceability/runtime-files-lookup.js +1 -32
- package/dist/sdk/traceability/source-traceability.js +1 -292
- package/dist/sdk/tracker-root-discovery.js +1 -9
- package/dist/sdk/workspace-contracts-cache.js +1 -28
- package/dist/sdk/workspace-contracts.js +1 -33
- package/dist/sdk/workspace-memory.js +2 -318
- package/dist/sdk/workspace-recipe.js +1 -71
- package/dist/sdk/workspace-snapshot.js +4 -613
- package/dist/sdk/workspace-transaction-gc.js +1 -149
- package/dist/sdk/workspace-transaction.js +2 -363
- package/dist/sdk/workspace.js +3 -127
- package/dist/types/index.js +1 -9
- package/dist/types.js +1 -245
- package/docs/AGENT_GUIDE.md +1 -1
- package/docs/CONTEXT_ALGORITHM_PORTFOLIO.md +167 -0
- package/docs/DIAGNOSTIC_OUTPUT_CONTRACTS.md +9 -3
- package/docs/EXTENSIONS.md +1 -0
- package/docs/HISTORY_MAINTENANCE.md +125 -0
- package/docs/MCP_2026_07_28_CONFORMANCE.md +20 -10
- package/docs/PACKAGE_EVIDENCE.md +92 -0
- package/docs/PLANNING_MEASUREMENTS.md +183 -0
- package/docs/README.md +3 -0
- package/docs/SCRIPTING.md +21 -0
- package/docs/TESTING.md +23 -0
- package/docs/generated/FLAG_LEXICON_BUDGETS.md +5 -5
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +2 -2
- package/marketplace.json +2 -2
- package/package.json +1 -1
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/scripts/finalize-build.mjs +45 -3
- package/dist/cli-bundle/chunks/chunk-AZDFJGH6.js +0 -9
- package/dist/cli-bundle/chunks/chunk-BAX2BYUW.js +0 -2111
- package/dist/cli-bundle/chunks/chunk-CKOL5U7J.js +0 -77
- package/dist/cli-bundle/chunks/chunk-OF76PSDQ.js +0 -5
- package/dist/cli-bundle/chunks/chunk-QUWFABDM.js +0 -232
- package/dist/cli-bundle/chunks/chunk-QWRRFYK3.js +0 -2
- package/dist/cli-bundle/chunks/chunk-RWWBOWJ5.js +0 -2
- package/dist/cli-bundle/chunks/chunk-WGAOYSYH.js +0 -40
- package/dist/cli-bundle/chunks/chunk-WI2AYPA7.js +0 -2
- package/dist/cli-bundle/chunks/register-list-query-5JL52D33.js +0 -11
- package/dist/cli-bundle/chunks/register-mutation-ZPG7KMVP.js +0 -20
- package/dist/cli-bundle/chunks/register-setup-H4SVAVX7.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-AL5ON6K6.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-CHC2IDXA.js +0 -6
- package/dist/cli-bundle/focused-chunks/chunk-CK6QFAPZ.js +0 -20
- package/dist/cli-bundle/focused-chunks/chunk-EZR3GHFE.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-H2WJWK7L.js +0 -56
- package/dist/cli-bundle/focused-chunks/chunk-NTPY5GSO.js +0 -3
- package/dist/cli-bundle/focused-chunks/chunk-P5O7MP5Y.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-PDMW2E36.js +0 -177
- package/dist/cli-bundle/focused-chunks/chunk-QTVCLMSZ.js +0 -6
- package/dist/cli-bundle/focused-chunks/chunk-QTZRBUYA.js +0 -8
- package/dist/cli-bundle/focused-chunks/chunk-SGQST3PQ.js +0 -12
- package/dist/cli-bundle/focused-chunks/chunk-UKHASMMP.js +0 -10
- package/dist/cli-bundle/focused-chunks/chunk-ZPEKQHOC.js +0 -2
|
@@ -1,857 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Plans deterministic, resumable schema evolution over item metadata. Storage
|
|
5
|
-
* execution is layered on the item mutation and workspace transaction
|
|
6
|
-
* primitives so every applied item keeps its normal lock, history, and index
|
|
7
|
-
* guarantees.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="60738c1f-3b44-5f8a-82c9-99d2b2de1e51")}catch(e){}}();
|
|
11
|
-
import { assertInitializedTracker } from "./environment/tracker-preflight.js";
|
|
12
|
-
import { createHash } from "node:crypto";
|
|
13
|
-
import fs from "node:fs/promises";
|
|
14
|
-
import path from "node:path";
|
|
15
|
-
import { isFileMissingError, readFileIfExists, writeFileAtomic, } from "../core/fs/fs-utils.js";
|
|
16
|
-
import { appendWorkspaceHistoryChange } from "../core/history/workspace-history.js";
|
|
17
|
-
import { resolveItemTypeRegistry } from "../core/item/type-registry.js";
|
|
18
|
-
import { parseFieldsFile, serializeFieldsFile, } from "../core/schema/fields-file.js";
|
|
19
|
-
import { parseItemTypesFile, serializeItemTypesFile, } from "../core/schema/item-types-file.js";
|
|
20
|
-
import { DEFAULT_RUNTIME_SCHEMA_FILE_PATHS, filePathForSchemaSection, normalizeRuntimeSchemaSettings, } from "../core/schema/runtime-schema.js";
|
|
21
|
-
import { parseStatusDefsFile, serializeStatusDefsFile, } from "../core/schema/status-defs-file.js";
|
|
22
|
-
import { normalizeStatusToken } from "../core/schema/status-token.js";
|
|
23
|
-
import { getActiveExtensionRegistrations } from "../core/extensions/index.js";
|
|
24
|
-
import { listAllItemMetadataLight, locateItem, mutateItem, readLocatedItem, } from "../core/store/item-store.js";
|
|
25
|
-
import { resolvePmRoot } from "../core/store/paths.js";
|
|
26
|
-
import { readSettings } from "../core/store/settings.js";
|
|
27
|
-
import { EXIT_CODE } from "../core/shared/constants.js";
|
|
28
|
-
import { PmCliError } from "../core/shared/errors.js";
|
|
29
|
-
import { resolveAuthor } from "../core/shared/author.js";
|
|
30
|
-
import { toErrorMessage } from "../core/shared/primitives.js";
|
|
31
|
-
import { readItemMetadataDerivedIndexState, queryItemMetadataIndex, } from "./item-metadata-index.js";
|
|
32
|
-
import { commitWorkspaceTransaction, } from "./workspace-transaction.js";
|
|
33
|
-
function requiredText(value, field) {
|
|
34
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
35
|
-
throw new TypeError(`Schema migration ${field} must be non-empty`);
|
|
36
|
-
}
|
|
37
|
-
return value.trim();
|
|
38
|
-
}
|
|
39
|
-
function requiredMigrationId(value) {
|
|
40
|
-
const migrationId = requiredText(value, "migrationId");
|
|
41
|
-
if (!/^[a-zA-Z0-9._-]+$/.test(migrationId)) {
|
|
42
|
-
throw new TypeError("Schema migration migrationId must match [a-zA-Z0-9._-]+");
|
|
43
|
-
}
|
|
44
|
-
return migrationId;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Derive a compact idempotency key from the normalized request and workspace
|
|
48
|
-
* scope. The workspace scope prevents unrelated trackers with the same schema
|
|
49
|
-
* change from sharing recovery identity in orchestration systems.
|
|
50
|
-
*/
|
|
51
|
-
export function deriveSchemaEvolutionMigrationId(requestInput, migrationScope) {
|
|
52
|
-
const request = normalizedRequest(requestInput);
|
|
53
|
-
const scope = requiredText(migrationScope, "migrationScope");
|
|
54
|
-
return `auto-${createHash("sha256")
|
|
55
|
-
.update(JSON.stringify({ scope, request }))
|
|
56
|
-
.digest("hex")
|
|
57
|
-
.slice(0, 20)}`;
|
|
58
|
-
}
|
|
59
|
-
function normalizedRequest(request) {
|
|
60
|
-
const normalized = request.kind === "rename-type"
|
|
61
|
-
? {
|
|
62
|
-
kind: request.kind,
|
|
63
|
-
from: requiredText(request.from, "source"),
|
|
64
|
-
to: requiredText(request.to, "target"),
|
|
65
|
-
}
|
|
66
|
-
: request.kind === "rename-field"
|
|
67
|
-
? {
|
|
68
|
-
kind: request.kind,
|
|
69
|
-
from: normalizeStatusToken(requiredText(request.from, "source")),
|
|
70
|
-
to: normalizeStatusToken(requiredText(request.to, "target")),
|
|
71
|
-
...(request.type === undefined
|
|
72
|
-
? {}
|
|
73
|
-
: { type: requiredText(request.type, "type") }),
|
|
74
|
-
}
|
|
75
|
-
: {
|
|
76
|
-
kind: request.kind,
|
|
77
|
-
from: normalizeStatusToken(requiredText(request.from, "source")),
|
|
78
|
-
to: normalizeStatusToken(requiredText(request.to, "target")),
|
|
79
|
-
};
|
|
80
|
-
if (normalized.from.toLowerCase() === normalized.to.toLowerCase()) {
|
|
81
|
-
throw new TypeError("Schema migration source and target must differ");
|
|
82
|
-
}
|
|
83
|
-
return normalized;
|
|
84
|
-
}
|
|
85
|
-
function schemaSectionFor(request) {
|
|
86
|
-
return request.kind === "rename-type"
|
|
87
|
-
? "types"
|
|
88
|
-
: request.kind === "rename-field"
|
|
89
|
-
? "fields"
|
|
90
|
-
: "statuses";
|
|
91
|
-
}
|
|
92
|
-
function schemaPathFor(pmRoot, settings, request) {
|
|
93
|
-
const schema = normalizeRuntimeSchemaSettings(settings.schema);
|
|
94
|
-
const section = schemaSectionFor(request);
|
|
95
|
-
return filePathForSchemaSection(pmRoot, schema.files[section], DEFAULT_RUNTIME_SCHEMA_FILE_PATHS[section]);
|
|
96
|
-
}
|
|
97
|
-
function planStoragePath(pmRoot, migrationId) {
|
|
98
|
-
return path.join(pmRoot, "transactions", "schema", `${migrationId}-plan.json`);
|
|
99
|
-
}
|
|
100
|
-
function parseSchemaAuditValue(raw) {
|
|
101
|
-
return raw.length === 0 ? null : JSON.parse(raw);
|
|
102
|
-
}
|
|
103
|
-
async function readSchemaFileSnapshot(filePath) {
|
|
104
|
-
const raw = await readFileIfExists(filePath);
|
|
105
|
-
return { exists: raw !== null, raw: raw ?? "" };
|
|
106
|
-
}
|
|
107
|
-
async function restoreSchemaFileSnapshot(filePath, snapshot) {
|
|
108
|
-
if (snapshot.exists) {
|
|
109
|
-
await writeFileAtomic(filePath, snapshot.raw);
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
await fs.rm(filePath, { force: true });
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
function mutateTypeDefinition(raw, request, phase) {
|
|
116
|
-
const file = parseItemTypesFile(raw);
|
|
117
|
-
const sourceIndex = file.definitions.findIndex((definition) => definition.name.trim().toLowerCase() === request.from.toLowerCase());
|
|
118
|
-
const targetIndex = file.definitions.findIndex((definition) => definition.name.trim().toLowerCase() === request.to.toLowerCase());
|
|
119
|
-
if (phase === "stage") {
|
|
120
|
-
if (targetIndex >= 0)
|
|
121
|
-
return serializeItemTypesFile(file);
|
|
122
|
-
if (sourceIndex < 0)
|
|
123
|
-
throw new TypeError(`Unknown custom type "${request.from}".`);
|
|
124
|
-
file.definitions.push({
|
|
125
|
-
...file.definitions[sourceIndex],
|
|
126
|
-
name: request.to,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
else if (sourceIndex >= 0) {
|
|
130
|
-
file.definitions.splice(sourceIndex, 1);
|
|
131
|
-
}
|
|
132
|
-
return serializeItemTypesFile(file);
|
|
133
|
-
}
|
|
134
|
-
function mutateFieldDefinition(raw, request, phase) {
|
|
135
|
-
const file = parseFieldsFile(raw);
|
|
136
|
-
const sourceIndex = file.fields.findIndex((definition) => definition.key === request.from);
|
|
137
|
-
const targetIndex = file.fields.findIndex((definition) => definition.key === request.to);
|
|
138
|
-
if (phase === "stage") {
|
|
139
|
-
if (targetIndex >= 0)
|
|
140
|
-
return serializeFieldsFile(file);
|
|
141
|
-
if (sourceIndex < 0)
|
|
142
|
-
throw new TypeError(`Unknown custom field "${request.from}".`);
|
|
143
|
-
const source = file.fields[sourceIndex];
|
|
144
|
-
file.fields.push({
|
|
145
|
-
...source,
|
|
146
|
-
key: request.to,
|
|
147
|
-
metadata_key: request.to,
|
|
148
|
-
front_matter_key: request.to,
|
|
149
|
-
cli_flag: request.to.replaceAll("_", "-"),
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
else if (sourceIndex >= 0) {
|
|
153
|
-
file.fields.splice(sourceIndex, 1);
|
|
154
|
-
}
|
|
155
|
-
return serializeFieldsFile(file);
|
|
156
|
-
}
|
|
157
|
-
function mutateStatusDefinition(raw, request, phase) {
|
|
158
|
-
const file = parseStatusDefsFile(raw);
|
|
159
|
-
const sourceIndex = file.statuses.findIndex((definition) => definition.id === request.from);
|
|
160
|
-
const targetIndex = file.statuses.findIndex((definition) => definition.id === request.to);
|
|
161
|
-
if (phase === "stage") {
|
|
162
|
-
if (targetIndex >= 0)
|
|
163
|
-
return serializeStatusDefsFile(file);
|
|
164
|
-
if (sourceIndex < 0)
|
|
165
|
-
throw new TypeError(`Unknown custom status "${request.from}".`);
|
|
166
|
-
file.statuses.push({ ...file.statuses[sourceIndex], id: request.to });
|
|
167
|
-
}
|
|
168
|
-
else if (sourceIndex >= 0) {
|
|
169
|
-
file.statuses.splice(sourceIndex, 1);
|
|
170
|
-
}
|
|
171
|
-
return serializeStatusDefsFile(file);
|
|
172
|
-
}
|
|
173
|
-
function mutateSchemaDefinition(raw, request, phase) {
|
|
174
|
-
return request.kind === "rename-type"
|
|
175
|
-
? mutateTypeDefinition(raw, request, phase)
|
|
176
|
-
: request.kind === "rename-field"
|
|
177
|
-
? mutateFieldDefinition(raw, request, phase)
|
|
178
|
-
: mutateStatusDefinition(raw, request, phase);
|
|
179
|
-
}
|
|
180
|
-
function replaceMatchingToken(value, request) {
|
|
181
|
-
return typeof value === "string" &&
|
|
182
|
-
value.trim().toLowerCase() === request.from.toLowerCase()
|
|
183
|
-
? request.to
|
|
184
|
-
: value;
|
|
185
|
-
}
|
|
186
|
-
function objectRecord(value) {
|
|
187
|
-
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
188
|
-
? value
|
|
189
|
-
: undefined;
|
|
190
|
-
}
|
|
191
|
-
function objectRecordList(value) {
|
|
192
|
-
return Array.isArray(value)
|
|
193
|
-
? value
|
|
194
|
-
.map((candidate) => objectRecord(candidate))
|
|
195
|
-
.filter((candidate) => candidate !== undefined)
|
|
196
|
-
: [];
|
|
197
|
-
}
|
|
198
|
-
function mutateTypeReferences(parsed, request) {
|
|
199
|
-
for (const field of objectRecordList(parsed.fields)) {
|
|
200
|
-
if (Array.isArray(field.required_types)) {
|
|
201
|
-
field.required_types = field.required_types.map((value) => replaceMatchingToken(value, request));
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
for (const workflow of objectRecordList(parsed.type_workflows)) {
|
|
205
|
-
workflow.type = replaceMatchingToken(workflow.type, request);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
function mutateStatusReferences(parsed, request) {
|
|
209
|
-
for (const definition of objectRecordList(parsed.definitions)) {
|
|
210
|
-
definition.default_status = replaceMatchingToken(definition.default_status, request);
|
|
211
|
-
}
|
|
212
|
-
const workflow = objectRecord(parsed.workflow);
|
|
213
|
-
if (workflow) {
|
|
214
|
-
for (const [key, value] of Object.entries(workflow)) {
|
|
215
|
-
workflow[key] = replaceMatchingToken(value, request);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
for (const typeWorkflow of objectRecordList(parsed.type_workflows)) {
|
|
219
|
-
if (Array.isArray(typeWorkflow.allowed_transitions)) {
|
|
220
|
-
typeWorkflow.allowed_transitions = typeWorkflow.allowed_transitions.map((pair) => Array.isArray(pair)
|
|
221
|
-
? pair.map((value) => replaceMatchingToken(value, request))
|
|
222
|
-
: pair);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
function mutateFieldReferences(parsed, request) {
|
|
227
|
-
for (const definition of objectRecordList(parsed.definitions)) {
|
|
228
|
-
for (const key of [
|
|
229
|
-
"required_create_fields",
|
|
230
|
-
"required_create_repeatables",
|
|
231
|
-
]) {
|
|
232
|
-
if (Array.isArray(definition[key])) {
|
|
233
|
-
definition[key] = definition[key].map((value) => replaceMatchingToken(value, request));
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
for (const key of ["options", "command_option_policies"]) {
|
|
237
|
-
for (const option of objectRecordList(definition[key])) {
|
|
238
|
-
const fieldKey = key === "options" ? "key" : "option";
|
|
239
|
-
option[fieldKey] = replaceMatchingToken(option[fieldKey], request);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
function mutateTemplateReferences(parsed, request) {
|
|
245
|
-
const options = objectRecord(parsed.options);
|
|
246
|
-
if (!options)
|
|
247
|
-
return;
|
|
248
|
-
if (request.kind === "rename-field") {
|
|
249
|
-
if (!Object.prototype.hasOwnProperty.call(options, request.from))
|
|
250
|
-
return;
|
|
251
|
-
if (Object.prototype.hasOwnProperty.call(options, request.to)) {
|
|
252
|
-
throw new TypeError(`Schema migration collision in saved template "${String(parsed.name ?? "unknown")}": target field "${request.to}" already exists.`);
|
|
253
|
-
}
|
|
254
|
-
options[request.to] = options[request.from];
|
|
255
|
-
delete options[request.from];
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
const key = request.kind === "rename-type" ? "type" : "status";
|
|
259
|
-
options[key] = replaceMatchingToken(options[key], request);
|
|
260
|
-
}
|
|
261
|
-
function mutateSchemaReferences(raw, request) {
|
|
262
|
-
const parsed = JSON.parse(raw);
|
|
263
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
264
|
-
return raw;
|
|
265
|
-
}
|
|
266
|
-
const record = parsed;
|
|
267
|
-
if (request.kind === "rename-type") {
|
|
268
|
-
mutateTypeReferences(record, request);
|
|
269
|
-
}
|
|
270
|
-
else if (request.kind === "rename-field") {
|
|
271
|
-
mutateFieldReferences(record, request);
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
mutateStatusReferences(record, request);
|
|
275
|
-
}
|
|
276
|
-
mutateTemplateReferences(record, request);
|
|
277
|
-
return `${JSON.stringify(record, null, 2)}\n`;
|
|
278
|
-
}
|
|
279
|
-
function schemaDefinitionState(raw, request) {
|
|
280
|
-
if (request.kind === "rename-type") {
|
|
281
|
-
const definitions = parseItemTypesFile(raw).definitions;
|
|
282
|
-
return {
|
|
283
|
-
source: definitions.some((entry) => entry.name.trim().toLowerCase() === request.from.toLowerCase()),
|
|
284
|
-
target: definitions.some((entry) => entry.name.trim().toLowerCase() === request.to.toLowerCase()),
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
if (request.kind === "rename-field") {
|
|
288
|
-
const definitions = parseFieldsFile(raw).fields;
|
|
289
|
-
return {
|
|
290
|
-
source: definitions.some((entry) => entry.key === request.from),
|
|
291
|
-
target: definitions.some((entry) => entry.key === request.to),
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
const definitions = parseStatusDefsFile(raw).statuses;
|
|
295
|
-
return {
|
|
296
|
-
source: definitions.some((entry) => entry.id === request.from),
|
|
297
|
-
target: definitions.some((entry) => entry.id === request.to),
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
function changedFieldsForItem(item, request) {
|
|
301
|
-
const record = item;
|
|
302
|
-
if (request.kind === "rename-type") {
|
|
303
|
-
return item.type.toLowerCase() === request.to.toLowerCase() ? [] : ["type"];
|
|
304
|
-
}
|
|
305
|
-
if (request.kind === "remap-status") {
|
|
306
|
-
return item.status.toLowerCase() === request.to.toLowerCase()
|
|
307
|
-
? []
|
|
308
|
-
: ["status"];
|
|
309
|
-
}
|
|
310
|
-
return Object.prototype.hasOwnProperty.call(record, request.to) &&
|
|
311
|
-
!Object.prototype.hasOwnProperty.call(record, request.from)
|
|
312
|
-
? []
|
|
313
|
-
: [request.from, request.to];
|
|
314
|
-
}
|
|
315
|
-
function planItem(item, request) {
|
|
316
|
-
if (request.kind === "rename-type") {
|
|
317
|
-
return item.type.toLowerCase() === request.from.toLowerCase()
|
|
318
|
-
? {
|
|
319
|
-
id: item.id,
|
|
320
|
-
changes: [{ field: "type", before: item.type, after: request.to }],
|
|
321
|
-
}
|
|
322
|
-
: null;
|
|
323
|
-
}
|
|
324
|
-
if (request.kind === "remap-status") {
|
|
325
|
-
return item.status.toLowerCase() === request.from.toLowerCase()
|
|
326
|
-
? {
|
|
327
|
-
id: item.id,
|
|
328
|
-
changes: [
|
|
329
|
-
{ field: "status", before: item.status, after: request.to },
|
|
330
|
-
],
|
|
331
|
-
}
|
|
332
|
-
: null;
|
|
333
|
-
}
|
|
334
|
-
if (request.type !== undefined &&
|
|
335
|
-
item.type.toLowerCase() !== request.type.toLowerCase()) {
|
|
336
|
-
return null;
|
|
337
|
-
}
|
|
338
|
-
const record = item;
|
|
339
|
-
if (!Object.prototype.hasOwnProperty.call(record, request.from)) {
|
|
340
|
-
return null;
|
|
341
|
-
}
|
|
342
|
-
if (Object.prototype.hasOwnProperty.call(record, request.to)) {
|
|
343
|
-
throw new TypeError(`Schema migration collision on item "${item.id}": target field "${request.to}" already exists.`);
|
|
344
|
-
}
|
|
345
|
-
return {
|
|
346
|
-
id: item.id,
|
|
347
|
-
changes: [
|
|
348
|
-
{ field: request.from, before: record[request.from], after: undefined },
|
|
349
|
-
{ field: request.to, before: undefined, after: record[request.from] },
|
|
350
|
-
],
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Build a deterministic schema evolution plan without touching storage. Input
|
|
355
|
-
* order never affects the fingerprint or item order. Completed ids filter only
|
|
356
|
-
* the pending window, leaving complete-plan identity stable across retries.
|
|
357
|
-
*/
|
|
358
|
-
export function planSchemaEvolutionMigration(items, options) {
|
|
359
|
-
const request = normalizedRequest(options.request);
|
|
360
|
-
const migrationId = options.migrationId === undefined
|
|
361
|
-
? deriveSchemaEvolutionMigrationId(request, options.migrationScope ?? "plan")
|
|
362
|
-
: requiredMigrationId(options.migrationId);
|
|
363
|
-
const completeItems = items
|
|
364
|
-
.map((item) => planItem(item, request))
|
|
365
|
-
.filter((entry) => entry !== null)
|
|
366
|
-
.sort((left, right) => left.id.localeCompare(right.id));
|
|
367
|
-
const fingerprint = createHash("sha256")
|
|
368
|
-
.update(JSON.stringify({ migrationId, request, items: completeItems }))
|
|
369
|
-
.digest("hex");
|
|
370
|
-
const completed = new Set([...(options.completedItemIds ?? [])]
|
|
371
|
-
.map((id) => id.trim())
|
|
372
|
-
.filter((id) => id.length > 0));
|
|
373
|
-
const pendingItems = completeItems.filter((entry) => !completed.has(entry.id));
|
|
374
|
-
return Object.freeze({
|
|
375
|
-
migration_id: migrationId,
|
|
376
|
-
request,
|
|
377
|
-
fingerprint,
|
|
378
|
-
affected_item_count: completeItems.length,
|
|
379
|
-
pending_item_count: pendingItems.length,
|
|
380
|
-
skipped_completed_count: completeItems.length - pendingItems.length,
|
|
381
|
-
items: Object.freeze(pendingItems),
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
async function candidateItems(params) {
|
|
385
|
-
const typeRegistry = resolveItemTypeRegistry(params.settings, getActiveExtensionRegistrations());
|
|
386
|
-
const folders = [...new Set(Object.values(typeRegistry.type_to_folder))];
|
|
387
|
-
const indexState = await readItemMetadataDerivedIndexState(params.pmRoot, folders);
|
|
388
|
-
const query = params.request.kind === "rename-type"
|
|
389
|
-
? { types: [params.request.from] }
|
|
390
|
-
: params.request.kind === "remap-status"
|
|
391
|
-
? { statuses: [params.request.from] }
|
|
392
|
-
: {
|
|
393
|
-
metadataKeys: [params.request.from],
|
|
394
|
-
...(params.request.type === undefined
|
|
395
|
-
? {}
|
|
396
|
-
: { types: [params.request.type] }),
|
|
397
|
-
};
|
|
398
|
-
const indexed = indexState
|
|
399
|
-
? await queryItemMetadataIndex({
|
|
400
|
-
pmRoot: params.pmRoot,
|
|
401
|
-
expectedSourceCursor: indexState.source_cursor,
|
|
402
|
-
query,
|
|
403
|
-
})
|
|
404
|
-
: null;
|
|
405
|
-
if (indexed) {
|
|
406
|
-
return {
|
|
407
|
-
items: indexed.items,
|
|
408
|
-
selectionSource: "derived_index",
|
|
409
|
-
typeToFolder: typeRegistry.type_to_folder,
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
return {
|
|
413
|
-
items: await listAllItemMetadataLight(params.pmRoot, params.settings.item_format, typeRegistry.type_to_folder, [], params.settings.schema),
|
|
414
|
-
selectionSource: "scan",
|
|
415
|
-
typeToFolder: typeRegistry.type_to_folder,
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
async function readStoredPlan(pmRoot, migrationId) {
|
|
419
|
-
const raw = await readFileIfExists(planStoragePath(pmRoot, migrationId));
|
|
420
|
-
if (raw === null)
|
|
421
|
-
return null;
|
|
422
|
-
try {
|
|
423
|
-
const stored = JSON.parse(raw);
|
|
424
|
-
if (stored.schema_version !== 1 ||
|
|
425
|
-
stored.plan.migration_id !== migrationId ||
|
|
426
|
-
!Array.isArray(stored.plan.items)) {
|
|
427
|
-
throw new TypeError("invalid plan envelope");
|
|
428
|
-
}
|
|
429
|
-
return stored.plan;
|
|
430
|
-
}
|
|
431
|
-
catch (error) {
|
|
432
|
-
throw new PmCliError(`Stored schema migration plan "${migrationId}" is invalid: ${toErrorMessage(error)}`, EXIT_CODE.GENERIC_FAILURE);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
async function writeStoredPlan(pmRoot, plan) {
|
|
436
|
-
const target = planStoragePath(pmRoot, plan.migration_id);
|
|
437
|
-
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
438
|
-
const stored = {
|
|
439
|
-
schema_version: 1,
|
|
440
|
-
plan,
|
|
441
|
-
created_at: new Date().toISOString(),
|
|
442
|
-
};
|
|
443
|
-
await writeFileAtomic(target, `${JSON.stringify(stored, null, 2)}\n`);
|
|
444
|
-
}
|
|
445
|
-
function requestsEqual(left, right) {
|
|
446
|
-
return JSON.stringify(left) === JSON.stringify(right);
|
|
447
|
-
}
|
|
448
|
-
async function readCurrentItem(params) {
|
|
449
|
-
const located = await locateItem(params.pmRoot, params.id, params.settings.id_prefix, params.settings.item_format, params.typeToFolder);
|
|
450
|
-
if (!located) {
|
|
451
|
-
throw new PmCliError(`Schema migration item "${params.id}" no longer exists.`, EXIT_CODE.NOT_FOUND);
|
|
452
|
-
}
|
|
453
|
-
return (await readLocatedItem(located, { schema: params.settings.schema }))
|
|
454
|
-
.document.metadata;
|
|
455
|
-
}
|
|
456
|
-
function applyItemRequest(document, request, direction) {
|
|
457
|
-
const source = direction === "forward" ? request.from : request.to;
|
|
458
|
-
const target = direction === "forward" ? request.to : request.from;
|
|
459
|
-
if (request.kind === "rename-type") {
|
|
460
|
-
if (document.metadata.type.toLowerCase() === target.toLowerCase())
|
|
461
|
-
return [];
|
|
462
|
-
if (document.metadata.type.toLowerCase() !== source.toLowerCase()) {
|
|
463
|
-
throw new TypeError(`Schema migration expected type "${source}" on item "${document.metadata.id}".`);
|
|
464
|
-
}
|
|
465
|
-
document.metadata.type = target;
|
|
466
|
-
return ["type"];
|
|
467
|
-
}
|
|
468
|
-
if (request.kind === "remap-status") {
|
|
469
|
-
if (document.metadata.status.toLowerCase() === target.toLowerCase())
|
|
470
|
-
return [];
|
|
471
|
-
if (document.metadata.status.toLowerCase() !== source.toLowerCase()) {
|
|
472
|
-
throw new TypeError(`Schema migration expected status "${source}" on item "${document.metadata.id}".`);
|
|
473
|
-
}
|
|
474
|
-
document.metadata.status = target;
|
|
475
|
-
return ["status"];
|
|
476
|
-
}
|
|
477
|
-
const record = document.metadata;
|
|
478
|
-
if (Object.prototype.hasOwnProperty.call(record, target) &&
|
|
479
|
-
!Object.prototype.hasOwnProperty.call(record, source)) {
|
|
480
|
-
return [];
|
|
481
|
-
}
|
|
482
|
-
if (!Object.prototype.hasOwnProperty.call(record, source) ||
|
|
483
|
-
Object.prototype.hasOwnProperty.call(record, target)) {
|
|
484
|
-
throw new TypeError(`Schema migration field state changed concurrently on item "${document.metadata.id}".`);
|
|
485
|
-
}
|
|
486
|
-
record[target] = record[source];
|
|
487
|
-
delete record[source];
|
|
488
|
-
return [source, target];
|
|
489
|
-
}
|
|
490
|
-
function schemaStep(params) {
|
|
491
|
-
return {
|
|
492
|
-
id: params.id,
|
|
493
|
-
inspect: async () => {
|
|
494
|
-
const snapshot = await readSchemaFileSnapshot(params.filePath);
|
|
495
|
-
const state = schemaDefinitionState(snapshot.raw, params.request);
|
|
496
|
-
const applied = params.phase === "stage"
|
|
497
|
-
? state.source && state.target
|
|
498
|
-
: !state.source && state.target;
|
|
499
|
-
return {
|
|
500
|
-
state: applied ? "applied" : "pending",
|
|
501
|
-
...(applied ? { result: { phase: params.phase } } : {}),
|
|
502
|
-
};
|
|
503
|
-
},
|
|
504
|
-
prepareCompensation: async () => {
|
|
505
|
-
const snapshot = await readSchemaFileSnapshot(params.filePath);
|
|
506
|
-
return { exists: snapshot.exists, raw: snapshot.raw };
|
|
507
|
-
},
|
|
508
|
-
apply: async () => {
|
|
509
|
-
const snapshot = await readSchemaFileSnapshot(params.filePath);
|
|
510
|
-
await writeFileAtomic(params.filePath, mutateSchemaDefinition(snapshot.raw, params.request, params.phase));
|
|
511
|
-
return { phase: params.phase };
|
|
512
|
-
},
|
|
513
|
-
compensate: async (data) => {
|
|
514
|
-
if (typeof data !== "object" ||
|
|
515
|
-
data === null ||
|
|
516
|
-
Array.isArray(data) ||
|
|
517
|
-
typeof data.exists !== "boolean" ||
|
|
518
|
-
typeof data.raw !== "string") {
|
|
519
|
-
throw new TypeError(`Missing schema snapshot for ${params.id} compensation.`);
|
|
520
|
-
}
|
|
521
|
-
await restoreSchemaFileSnapshot(params.filePath, {
|
|
522
|
-
exists: data.exists,
|
|
523
|
-
raw: data.raw,
|
|
524
|
-
});
|
|
525
|
-
},
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
function schemaReferenceStep(params) {
|
|
529
|
-
return {
|
|
530
|
-
id: params.stepId,
|
|
531
|
-
inspect: async () => {
|
|
532
|
-
const snapshot = await readSchemaFileSnapshot(params.filePath);
|
|
533
|
-
return {
|
|
534
|
-
state: mutateSchemaReferences(snapshot.raw, params.request) === snapshot.raw
|
|
535
|
-
? "applied"
|
|
536
|
-
: "pending",
|
|
537
|
-
};
|
|
538
|
-
},
|
|
539
|
-
prepareCompensation: async () => {
|
|
540
|
-
const snapshot = await readSchemaFileSnapshot(params.filePath);
|
|
541
|
-
return { exists: snapshot.exists, raw: snapshot.raw };
|
|
542
|
-
},
|
|
543
|
-
apply: async () => {
|
|
544
|
-
const snapshot = await readSchemaFileSnapshot(params.filePath);
|
|
545
|
-
await writeFileAtomic(params.filePath, mutateSchemaReferences(snapshot.raw, params.request));
|
|
546
|
-
return { file: params.filePath };
|
|
547
|
-
},
|
|
548
|
-
compensate: async (data) => {
|
|
549
|
-
if (typeof data !== "object" ||
|
|
550
|
-
data === null ||
|
|
551
|
-
Array.isArray(data) ||
|
|
552
|
-
typeof data.exists !== "boolean" ||
|
|
553
|
-
typeof data.raw !== "string") {
|
|
554
|
-
throw new TypeError(`Missing schema reference snapshot for ${params.stepId} compensation.`);
|
|
555
|
-
}
|
|
556
|
-
await restoreSchemaFileSnapshot(params.filePath, {
|
|
557
|
-
exists: data.exists,
|
|
558
|
-
raw: data.raw,
|
|
559
|
-
});
|
|
560
|
-
},
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
function itemStep(params) {
|
|
564
|
-
return {
|
|
565
|
-
id: `item-${params.id}`,
|
|
566
|
-
inspect: async () => {
|
|
567
|
-
const settings = await readSettings(params.pmRoot);
|
|
568
|
-
const typeToFolder = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations()).type_to_folder;
|
|
569
|
-
const item = await readCurrentItem({
|
|
570
|
-
pmRoot: params.pmRoot,
|
|
571
|
-
settings,
|
|
572
|
-
typeToFolder,
|
|
573
|
-
id: params.id,
|
|
574
|
-
});
|
|
575
|
-
const applied = changedFieldsForItem(item, params.request).length === 0;
|
|
576
|
-
return {
|
|
577
|
-
state: applied ? "applied" : "pending",
|
|
578
|
-
...(applied ? { result: { item_id: params.id } } : {}),
|
|
579
|
-
};
|
|
580
|
-
},
|
|
581
|
-
apply: async () => {
|
|
582
|
-
const settings = await readSettings(params.pmRoot);
|
|
583
|
-
const typeToFolder = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations()).type_to_folder;
|
|
584
|
-
const result = await mutateItem({
|
|
585
|
-
pmRoot: params.pmRoot,
|
|
586
|
-
settings,
|
|
587
|
-
typeToFolder,
|
|
588
|
-
id: params.id,
|
|
589
|
-
op: `schema_${params.request.kind.replaceAll("-", "_")}`,
|
|
590
|
-
author: params.author,
|
|
591
|
-
message: `Schema migration ${params.request.kind}: ${params.request.from} -> ${params.request.to}`,
|
|
592
|
-
force: params.force,
|
|
593
|
-
skipNoop: true,
|
|
594
|
-
mutate(document) {
|
|
595
|
-
return {
|
|
596
|
-
changedFields: applyItemRequest(document, params.request, "forward"),
|
|
597
|
-
};
|
|
598
|
-
},
|
|
599
|
-
});
|
|
600
|
-
return {
|
|
601
|
-
item_id: params.id,
|
|
602
|
-
changed_fields: result.changedFields,
|
|
603
|
-
};
|
|
604
|
-
},
|
|
605
|
-
compensate: async () => {
|
|
606
|
-
const settings = await readSettings(params.pmRoot);
|
|
607
|
-
const typeToFolder = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations()).type_to_folder;
|
|
608
|
-
await mutateItem({
|
|
609
|
-
pmRoot: params.pmRoot,
|
|
610
|
-
settings,
|
|
611
|
-
typeToFolder,
|
|
612
|
-
id: params.id,
|
|
613
|
-
op: `schema_${params.request.kind.replaceAll("-", "_")}_compensate`,
|
|
614
|
-
author: params.author,
|
|
615
|
-
message: `Compensate schema migration ${params.request.kind}: ${params.request.to} -> ${params.request.from}`,
|
|
616
|
-
force: true,
|
|
617
|
-
skipNoop: true,
|
|
618
|
-
mutate(document) {
|
|
619
|
-
return {
|
|
620
|
-
changedFields: applyItemRequest(document, params.request, "reverse"),
|
|
621
|
-
};
|
|
622
|
-
},
|
|
623
|
-
});
|
|
624
|
-
},
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
async function resolveMigrationPlan(params) {
|
|
628
|
-
const storedPlan = await readStoredPlan(params.pmRoot, params.migrationId);
|
|
629
|
-
if (storedPlan) {
|
|
630
|
-
if (!requestsEqual(storedPlan.request, params.request)) {
|
|
631
|
-
throw new PmCliError(`Schema migration id "${params.migrationId}" already belongs to a different request.`, EXIT_CODE.CONFLICT);
|
|
632
|
-
}
|
|
633
|
-
return { plan: storedPlan, stored: true };
|
|
634
|
-
}
|
|
635
|
-
const candidates = await candidateItems({
|
|
636
|
-
pmRoot: params.pmRoot,
|
|
637
|
-
settings: params.settings,
|
|
638
|
-
request: params.request,
|
|
639
|
-
});
|
|
640
|
-
return {
|
|
641
|
-
plan: {
|
|
642
|
-
...planSchemaEvolutionMigration(candidates.items, {
|
|
643
|
-
migrationId: params.migrationId,
|
|
644
|
-
request: params.request,
|
|
645
|
-
}),
|
|
646
|
-
selection_source: candidates.selectionSource,
|
|
647
|
-
},
|
|
648
|
-
stored: false,
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
async function validateAndStoreMigrationPlan(params) {
|
|
652
|
-
const definitionState = schemaDefinitionState((await readFileIfExists(params.schemaPath)) ?? "", params.request);
|
|
653
|
-
if (!definitionState.source) {
|
|
654
|
-
throw new PmCliError(`Schema migration source "${params.request.from}" is not a custom definition.`, EXIT_CODE.NOT_FOUND);
|
|
655
|
-
}
|
|
656
|
-
if (definitionState.target) {
|
|
657
|
-
throw new PmCliError(`Schema migration target "${params.request.to}" already exists.`, EXIT_CODE.CONFLICT);
|
|
658
|
-
}
|
|
659
|
-
await writeStoredPlan(params.pmRoot, params.plan);
|
|
660
|
-
}
|
|
661
|
-
async function collectSchemaReferenceState(params) {
|
|
662
|
-
const normalizedSchema = normalizeRuntimeSchemaSettings(params.settings.schema);
|
|
663
|
-
const paths = [
|
|
664
|
-
...new Set([
|
|
665
|
-
["types", DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.types],
|
|
666
|
-
["fields", DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.fields],
|
|
667
|
-
["workflows", DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.workflows],
|
|
668
|
-
]
|
|
669
|
-
.map(([section, fallback]) => filePathForSchemaSection(params.pmRoot, normalizedSchema.files[section], fallback))
|
|
670
|
-
.filter((filePath) => filePath !== params.schemaPath)),
|
|
671
|
-
];
|
|
672
|
-
try {
|
|
673
|
-
const templatesPath = path.join(params.pmRoot, "templates");
|
|
674
|
-
const templateEntries = await fs.readdir(templatesPath, {
|
|
675
|
-
withFileTypes: true,
|
|
676
|
-
});
|
|
677
|
-
paths.push(...templateEntries
|
|
678
|
-
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".json"))
|
|
679
|
-
.map((entry) => path.join(templatesPath, entry.name)));
|
|
680
|
-
}
|
|
681
|
-
catch (error) {
|
|
682
|
-
if (typeof error !== "object" ||
|
|
683
|
-
error === null ||
|
|
684
|
-
!("code" in error) ||
|
|
685
|
-
!isFileMissingError(error)) {
|
|
686
|
-
throw error;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
paths.sort((left, right) => left.localeCompare(right));
|
|
690
|
-
const snapshots = [];
|
|
691
|
-
for (const filePath of paths) {
|
|
692
|
-
const beforeRaw = await readFileIfExists(filePath);
|
|
693
|
-
if (beforeRaw !== null &&
|
|
694
|
-
mutateSchemaReferences(beforeRaw, params.request) !== beforeRaw) {
|
|
695
|
-
snapshots.push({ filePath, beforeRaw });
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
return { paths, snapshots };
|
|
699
|
-
}
|
|
700
|
-
async function appendSchemaReferenceHistory(params) {
|
|
701
|
-
for (const reference of params.snapshots) {
|
|
702
|
-
const afterRaw = await fs.readFile(reference.filePath, "utf8");
|
|
703
|
-
const relativePath = path
|
|
704
|
-
.relative(params.pmRoot, reference.filePath)
|
|
705
|
-
.replaceAll("\\", "/");
|
|
706
|
-
await appendWorkspaceHistoryChange({
|
|
707
|
-
pmRoot: params.pmRoot,
|
|
708
|
-
author: params.author,
|
|
709
|
-
documentPath: relativePath,
|
|
710
|
-
before: parseSchemaAuditValue(reference.beforeRaw),
|
|
711
|
-
after: parseSchemaAuditValue(afterRaw),
|
|
712
|
-
op: "schema_migration_reference",
|
|
713
|
-
idempotencyKey: `${params.plan.migration_id}:${params.plan.fingerprint}:${relativePath}`,
|
|
714
|
-
message: `${params.request.kind}: ${params.request.from} -> ${params.request.to}`,
|
|
715
|
-
lockTtlSeconds: params.settings.locks.ttl_seconds,
|
|
716
|
-
lockWaitMs: params.settings.locks.wait_ms,
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
/**
|
|
721
|
-
* Plan or execute one lossless schema evolution. The complete plan is persisted
|
|
722
|
-
* before writes and reused verbatim after interruptions. Execution stages the
|
|
723
|
-
* target definition, mutates each item through the canonical item store, then
|
|
724
|
-
* retires the source definition in one crash-recoverable transaction.
|
|
725
|
-
*/
|
|
726
|
-
export async function runSchemaEvolutionMigration(requestInput, options, global) {
|
|
727
|
-
const request = normalizedRequest(requestInput);
|
|
728
|
-
const pmRoot = resolvePmRoot(process.cwd(), global.path);
|
|
729
|
-
await assertInitializedTracker(pmRoot);
|
|
730
|
-
const migrationId = options.migrationId === undefined
|
|
731
|
-
? deriveSchemaEvolutionMigrationId(request, await fs.realpath(pmRoot))
|
|
732
|
-
: requiredMigrationId(options.migrationId);
|
|
733
|
-
const settings = await readSettings(pmRoot);
|
|
734
|
-
const author = resolveAuthor(options.author, settings.author_default);
|
|
735
|
-
const resolvedPlan = await resolveMigrationPlan({
|
|
736
|
-
pmRoot,
|
|
737
|
-
settings,
|
|
738
|
-
migrationId,
|
|
739
|
-
request,
|
|
740
|
-
});
|
|
741
|
-
const plan = resolvedPlan.plan;
|
|
742
|
-
if (options.dryRun === true) {
|
|
743
|
-
return {
|
|
744
|
-
...plan,
|
|
745
|
-
action: request.kind,
|
|
746
|
-
applied: false,
|
|
747
|
-
recovered: false,
|
|
748
|
-
migrated_item_count: 0,
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
const schemaPath = schemaPathFor(pmRoot, settings, request);
|
|
752
|
-
const schemaBefore = await fs.readFile(schemaPath, "utf8");
|
|
753
|
-
if (!resolvedPlan.stored) {
|
|
754
|
-
await validateAndStoreMigrationPlan({
|
|
755
|
-
pmRoot,
|
|
756
|
-
schemaPath,
|
|
757
|
-
request,
|
|
758
|
-
plan,
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
const references = await collectSchemaReferenceState({
|
|
762
|
-
pmRoot,
|
|
763
|
-
schemaPath,
|
|
764
|
-
settings,
|
|
765
|
-
request,
|
|
766
|
-
});
|
|
767
|
-
const steps = [
|
|
768
|
-
schemaStep({
|
|
769
|
-
id: "stage-schema",
|
|
770
|
-
filePath: schemaPath,
|
|
771
|
-
request,
|
|
772
|
-
phase: "stage",
|
|
773
|
-
}),
|
|
774
|
-
...references.paths.map((filePath, index) => schemaReferenceStep({
|
|
775
|
-
filePath,
|
|
776
|
-
request,
|
|
777
|
-
stepId: `reference-${String(index + 1).padStart(4, "0")}`,
|
|
778
|
-
})),
|
|
779
|
-
...plan.items.map((entry) => itemStep({
|
|
780
|
-
pmRoot,
|
|
781
|
-
id: entry.id,
|
|
782
|
-
request,
|
|
783
|
-
author,
|
|
784
|
-
force: options.force === true,
|
|
785
|
-
})),
|
|
786
|
-
schemaStep({
|
|
787
|
-
id: "retire-source",
|
|
788
|
-
filePath: schemaPath,
|
|
789
|
-
request,
|
|
790
|
-
phase: "retire",
|
|
791
|
-
}),
|
|
792
|
-
];
|
|
793
|
-
const committed = await commitWorkspaceTransaction({
|
|
794
|
-
pmRoot,
|
|
795
|
-
transactionId: `schema-${migrationId}`,
|
|
796
|
-
author,
|
|
797
|
-
steps,
|
|
798
|
-
lockTtlSeconds: Math.max(settings.locks.ttl_seconds, 300),
|
|
799
|
-
lockWaitMs: settings.locks.wait_ms,
|
|
800
|
-
});
|
|
801
|
-
const schemaAfter = await fs.readFile(schemaPath, "utf8");
|
|
802
|
-
const { historyPath } = await appendWorkspaceHistoryChange({
|
|
803
|
-
pmRoot,
|
|
804
|
-
author,
|
|
805
|
-
documentPath: path.relative(pmRoot, schemaPath).replaceAll("\\", "/"),
|
|
806
|
-
before: parseSchemaAuditValue(schemaBefore),
|
|
807
|
-
after: parseSchemaAuditValue(schemaAfter),
|
|
808
|
-
op: "schema_migration",
|
|
809
|
-
idempotencyKey: `${plan.migration_id}:${plan.fingerprint}`,
|
|
810
|
-
message: `${request.kind}: ${request.from} -> ${request.to}; affected=${plan.affected_item_count}`,
|
|
811
|
-
lockTtlSeconds: settings.locks.ttl_seconds,
|
|
812
|
-
lockWaitMs: settings.locks.wait_ms,
|
|
813
|
-
});
|
|
814
|
-
await appendSchemaReferenceHistory({
|
|
815
|
-
pmRoot,
|
|
816
|
-
author,
|
|
817
|
-
settings,
|
|
818
|
-
request,
|
|
819
|
-
plan,
|
|
820
|
-
snapshots: references.snapshots,
|
|
821
|
-
});
|
|
822
|
-
return {
|
|
823
|
-
...plan,
|
|
824
|
-
action: request.kind,
|
|
825
|
-
applied: true,
|
|
826
|
-
recovered: committed.recovered,
|
|
827
|
-
migrated_item_count: plan.affected_item_count,
|
|
828
|
-
workspace_history_path: historyPath,
|
|
829
|
-
};
|
|
830
|
-
}
|
|
831
|
-
/** Render a compact human summary for a schema migration plan or execution. */
|
|
832
|
-
export function formatSchemaEvolutionMigrationHuman(result) {
|
|
833
|
-
const mode = result.applied ? "applied" : "dry-run";
|
|
834
|
-
const recovery = result.recovered ? " (recovered)" : "";
|
|
835
|
-
return [
|
|
836
|
-
`${result.action}: ${result.request.from} -> ${result.request.to} [${mode}${recovery}]`,
|
|
837
|
-
`migration_id: ${result.migration_id}`,
|
|
838
|
-
`affected: ${result.affected_item_count}`,
|
|
839
|
-
`pending: ${result.pending_item_count}`,
|
|
840
|
-
`fingerprint: ${result.fingerprint}`,
|
|
841
|
-
].join("\n");
|
|
842
|
-
}
|
|
843
|
-
/** Internal pure helpers exposed only for exhaustive behavioral unit tests. */
|
|
844
|
-
export const schemaMigrationTestOnly = {
|
|
845
|
-
normalizedRequest,
|
|
846
|
-
parseSchemaAuditValue,
|
|
847
|
-
readSchemaFileSnapshot,
|
|
848
|
-
mutateSchemaDefinition,
|
|
849
|
-
mutateSchemaReferences,
|
|
850
|
-
changedFieldsForItem,
|
|
851
|
-
applyItemRequest,
|
|
852
|
-
schemaStep,
|
|
853
|
-
schemaReferenceStep,
|
|
854
|
-
validateAndStoreMigrationPlan,
|
|
855
|
-
};
|
|
1
|
+
!(function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="9052af10-ff8c-55db-8a35-b45e1e23a901")}catch(e2){}})();import{assertInitializedTracker}from"./environment/tracker-preflight.js";import{createHash}from"node:crypto";import fs from"node:fs/promises";import path from"node:path";import{isFileMissingError,readFileIfExists,writeFileAtomic}from"../core/fs/fs-utils.js";import{appendWorkspaceHistoryChange}from"../core/history/workspace-history.js";import{resolveItemTypeRegistry}from"../core/item/type-registry.js";import{parseFieldsFile,serializeFieldsFile}from"../core/schema/fields-file.js";import{parseItemTypesFile,serializeItemTypesFile}from"../core/schema/item-types-file.js";import{DEFAULT_RUNTIME_SCHEMA_FILE_PATHS,filePathForSchemaSection,normalizeRuntimeSchemaSettings}from"../core/schema/runtime-schema.js";import{parseStatusDefsFile,serializeStatusDefsFile}from"../core/schema/status-defs-file.js";import{normalizeStatusToken}from"../core/schema/status-token.js";import{getActiveExtensionRegistrations}from"../core/extensions/index.js";import{listAllItemMetadataLight,locateItem,mutateItem,readLocatedItem}from"../core/store/item-store.js";import{resolvePmRoot}from"../core/store/paths.js";import{readSettings}from"../core/store/settings.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{resolveAuthor}from"../core/shared/author.js";import{toErrorMessage}from"../core/shared/primitives.js";import{readItemMetadataDerivedIndexState,queryItemMetadataIndex}from"./item-metadata-index.js";import{commitWorkspaceTransaction}from"./workspace-transaction.js";function requiredText(value,field){if(typeof value!=="string"||value.trim().length===0){throw new TypeError(`Schema migration ${field} must be non-empty`)}return value.trim()}function requiredMigrationId(value){const migrationId=requiredText(value,"migrationId");if(!/^[a-zA-Z0-9._-]+$/.test(migrationId)){throw new TypeError("Schema migration migrationId must match [a-zA-Z0-9._-]+")}return migrationId}function deriveSchemaEvolutionMigrationId(requestInput,migrationScope){const request=normalizedRequest(requestInput);const scope=requiredText(migrationScope,"migrationScope");return`auto-${createHash("sha256").update(JSON.stringify({scope,request})).digest("hex").slice(0,20)}`}function normalizedRequest(request){const normalized=request.kind==="rename-type"?{kind:request.kind,from:requiredText(request.from,"source"),to:requiredText(request.to,"target")}:request.kind==="rename-field"?{kind:request.kind,from:normalizeStatusToken(requiredText(request.from,"source")),to:normalizeStatusToken(requiredText(request.to,"target")),...request.type===void 0?{}:{type:requiredText(request.type,"type")}}:{kind:request.kind,from:normalizeStatusToken(requiredText(request.from,"source")),to:normalizeStatusToken(requiredText(request.to,"target"))};if(normalized.from.toLowerCase()===normalized.to.toLowerCase()){throw new TypeError("Schema migration source and target must differ")}return normalized}function schemaSectionFor(request){return request.kind==="rename-type"?"types":request.kind==="rename-field"?"fields":"statuses"}function schemaPathFor(pmRoot,settings,request){const schema=normalizeRuntimeSchemaSettings(settings.schema);const section=schemaSectionFor(request);return filePathForSchemaSection(pmRoot,schema.files[section],DEFAULT_RUNTIME_SCHEMA_FILE_PATHS[section])}function planStoragePath(pmRoot,migrationId){return path.join(pmRoot,"transactions","schema",`${migrationId}-plan.json`)}function parseSchemaAuditValue(raw){return raw.length===0?null:JSON.parse(raw)}async function readSchemaFileSnapshot(filePath){const raw=await readFileIfExists(filePath);return{exists:raw!==null,raw:raw??""}}async function restoreSchemaFileSnapshot(filePath,snapshot){if(snapshot.exists){await writeFileAtomic(filePath,snapshot.raw)}else{await fs.rm(filePath,{force:true})}}function mutateTypeDefinition(raw,request,phase){const file=parseItemTypesFile(raw);const sourceIndex=file.definitions.findIndex(definition=>definition.name.trim().toLowerCase()===request.from.toLowerCase());const targetIndex=file.definitions.findIndex(definition=>definition.name.trim().toLowerCase()===request.to.toLowerCase());if(phase==="stage"){if(targetIndex>=0)return serializeItemTypesFile(file);if(sourceIndex<0)throw new TypeError(`Unknown custom type "${request.from}".`);file.definitions.push({...file.definitions[sourceIndex],name:request.to})}else if(sourceIndex>=0){file.definitions.splice(sourceIndex,1)}return serializeItemTypesFile(file)}function mutateFieldDefinition(raw,request,phase){const file=parseFieldsFile(raw);const sourceIndex=file.fields.findIndex(definition=>definition.key===request.from);const targetIndex=file.fields.findIndex(definition=>definition.key===request.to);if(phase==="stage"){if(targetIndex>=0)return serializeFieldsFile(file);if(sourceIndex<0)throw new TypeError(`Unknown custom field "${request.from}".`);const source=file.fields[sourceIndex];file.fields.push({...source,key:request.to,metadata_key:request.to,front_matter_key:request.to,cli_flag:request.to.replaceAll("_","-")})}else if(sourceIndex>=0){file.fields.splice(sourceIndex,1)}return serializeFieldsFile(file)}function mutateStatusDefinition(raw,request,phase){const file=parseStatusDefsFile(raw);const sourceIndex=file.statuses.findIndex(definition=>definition.id===request.from);const targetIndex=file.statuses.findIndex(definition=>definition.id===request.to);if(phase==="stage"){if(targetIndex>=0)return serializeStatusDefsFile(file);if(sourceIndex<0)throw new TypeError(`Unknown custom status "${request.from}".`);file.statuses.push({...file.statuses[sourceIndex],id:request.to})}else if(sourceIndex>=0){file.statuses.splice(sourceIndex,1)}return serializeStatusDefsFile(file)}function mutateSchemaDefinition(raw,request,phase){return request.kind==="rename-type"?mutateTypeDefinition(raw,request,phase):request.kind==="rename-field"?mutateFieldDefinition(raw,request,phase):mutateStatusDefinition(raw,request,phase)}function replaceMatchingToken(value,request){return typeof value==="string"&&value.trim().toLowerCase()===request.from.toLowerCase()?request.to:value}function objectRecord(value){return typeof value==="object"&&value!==null&&!Array.isArray(value)?value:void 0}function objectRecordList(value){return Array.isArray(value)?value.map(candidate=>objectRecord(candidate)).filter(candidate=>candidate!==void 0):[]}function mutateTypeReferences(parsed,request){for(const field of objectRecordList(parsed.fields)){if(Array.isArray(field.required_types)){field.required_types=field.required_types.map(value=>replaceMatchingToken(value,request))}}for(const workflow of objectRecordList(parsed.type_workflows)){workflow.type=replaceMatchingToken(workflow.type,request)}}function mutateStatusReferences(parsed,request){for(const definition of objectRecordList(parsed.definitions)){definition.default_status=replaceMatchingToken(definition.default_status,request)}const workflow=objectRecord(parsed.workflow);if(workflow){for(const[key,value]of Object.entries(workflow)){workflow[key]=replaceMatchingToken(value,request)}}for(const typeWorkflow of objectRecordList(parsed.type_workflows)){if(Array.isArray(typeWorkflow.allowed_transitions)){typeWorkflow.allowed_transitions=typeWorkflow.allowed_transitions.map(pair=>Array.isArray(pair)?pair.map(value=>replaceMatchingToken(value,request)):pair)}}}function mutateFieldReferences(parsed,request){for(const definition of objectRecordList(parsed.definitions)){for(const key of["required_create_fields","required_create_repeatables"]){if(Array.isArray(definition[key])){definition[key]=definition[key].map(value=>replaceMatchingToken(value,request))}}for(const key of["options","command_option_policies"]){for(const option of objectRecordList(definition[key])){const fieldKey=key==="options"?"key":"option";option[fieldKey]=replaceMatchingToken(option[fieldKey],request)}}}}function mutateTemplateReferences(parsed,request){const options=objectRecord(parsed.options);if(!options)return;if(request.kind==="rename-field"){if(!Object.prototype.hasOwnProperty.call(options,request.from))return;if(Object.prototype.hasOwnProperty.call(options,request.to)){throw new TypeError(`Schema migration collision in saved template "${String(parsed.name??"unknown")}": target field "${request.to}" already exists.`)}options[request.to]=options[request.from];delete options[request.from];return}const key=request.kind==="rename-type"?"type":"status";options[key]=replaceMatchingToken(options[key],request)}function mutateSchemaReferences(raw,request){const parsed=JSON.parse(raw);if(typeof parsed!=="object"||parsed===null||Array.isArray(parsed)){return raw}const record=parsed;if(request.kind==="rename-type"){mutateTypeReferences(record,request)}else if(request.kind==="rename-field"){mutateFieldReferences(record,request)}else{mutateStatusReferences(record,request)}mutateTemplateReferences(record,request);return`${JSON.stringify(record,null,2)}
|
|
2
|
+
`}function schemaDefinitionState(raw,request){if(request.kind==="rename-type"){const definitions2=parseItemTypesFile(raw).definitions;return{source:definitions2.some(entry=>entry.name.trim().toLowerCase()===request.from.toLowerCase()),target:definitions2.some(entry=>entry.name.trim().toLowerCase()===request.to.toLowerCase())}}if(request.kind==="rename-field"){const definitions2=parseFieldsFile(raw).fields;return{source:definitions2.some(entry=>entry.key===request.from),target:definitions2.some(entry=>entry.key===request.to)}}const definitions=parseStatusDefsFile(raw).statuses;return{source:definitions.some(entry=>entry.id===request.from),target:definitions.some(entry=>entry.id===request.to)}}function changedFieldsForItem(item,request){const record=item;if(request.kind==="rename-type"){return item.type.toLowerCase()===request.to.toLowerCase()?[]:["type"]}if(request.kind==="remap-status"){return item.status.toLowerCase()===request.to.toLowerCase()?[]:["status"]}return Object.prototype.hasOwnProperty.call(record,request.to)&&!Object.prototype.hasOwnProperty.call(record,request.from)?[]:[request.from,request.to]}function planItem(item,request){if(request.kind==="rename-type"){return item.type.toLowerCase()===request.from.toLowerCase()?{id:item.id,changes:[{field:"type",before:item.type,after:request.to}]}:null}if(request.kind==="remap-status"){return item.status.toLowerCase()===request.from.toLowerCase()?{id:item.id,changes:[{field:"status",before:item.status,after:request.to}]}:null}if(request.type!==void 0&&item.type.toLowerCase()!==request.type.toLowerCase()){return null}const record=item;if(!Object.prototype.hasOwnProperty.call(record,request.from)){return null}if(Object.prototype.hasOwnProperty.call(record,request.to)){throw new TypeError(`Schema migration collision on item "${item.id}": target field "${request.to}" already exists.`)}return{id:item.id,changes:[{field:request.from,before:record[request.from],after:void 0},{field:request.to,before:void 0,after:record[request.from]}]}}function planSchemaEvolutionMigration(items,options){const request=normalizedRequest(options.request);const migrationId=options.migrationId===void 0?deriveSchemaEvolutionMigrationId(request,options.migrationScope??"plan"):requiredMigrationId(options.migrationId);const completeItems=items.map(item=>planItem(item,request)).filter(entry=>entry!==null).sort((left,right)=>left.id.localeCompare(right.id));const fingerprint=createHash("sha256").update(JSON.stringify({migrationId,request,items:completeItems})).digest("hex");const completed=new Set([...options.completedItemIds??[]].map(id=>id.trim()).filter(id=>id.length>0));const pendingItems=completeItems.filter(entry=>!completed.has(entry.id));return Object.freeze({migration_id:migrationId,request,fingerprint,affected_item_count:completeItems.length,pending_item_count:pendingItems.length,skipped_completed_count:completeItems.length-pendingItems.length,items:Object.freeze(pendingItems)})}async function candidateItems(params){const typeRegistry=resolveItemTypeRegistry(params.settings,getActiveExtensionRegistrations());const folders=[...new Set(Object.values(typeRegistry.type_to_folder))];const indexState=await readItemMetadataDerivedIndexState(params.pmRoot,folders);const query=params.request.kind==="rename-type"?{types:[params.request.from]}:params.request.kind==="remap-status"?{statuses:[params.request.from]}:{metadataKeys:[params.request.from],...params.request.type===void 0?{}:{types:[params.request.type]}};const indexed=indexState?await queryItemMetadataIndex({pmRoot:params.pmRoot,expectedSourceCursor:indexState.source_cursor,query}):null;if(indexed){return{items:indexed.items,selectionSource:"derived_index",typeToFolder:typeRegistry.type_to_folder}}return{items:await listAllItemMetadataLight(params.pmRoot,params.settings.item_format,typeRegistry.type_to_folder,[],params.settings.schema),selectionSource:"scan",typeToFolder:typeRegistry.type_to_folder}}async function readStoredPlan(pmRoot,migrationId){const raw=await readFileIfExists(planStoragePath(pmRoot,migrationId));if(raw===null)return null;try{const stored=JSON.parse(raw);if(stored.schema_version!==1||stored.plan.migration_id!==migrationId||!Array.isArray(stored.plan.items)){throw new TypeError("invalid plan envelope")}return stored.plan}catch(error){throw new PmCliError(`Stored schema migration plan "${migrationId}" is invalid: ${toErrorMessage(error)}`,EXIT_CODE.GENERIC_FAILURE)}}async function writeStoredPlan(pmRoot,plan){const target=planStoragePath(pmRoot,plan.migration_id);await fs.mkdir(path.dirname(target),{recursive:true});const stored={schema_version:1,plan,created_at:new Date().toISOString()};await writeFileAtomic(target,`${JSON.stringify(stored,null,2)}
|
|
3
|
+
`)}function requestsEqual(left,right){return JSON.stringify(left)===JSON.stringify(right)}async function readCurrentItem(params){const located=await locateItem(params.pmRoot,params.id,params.settings.id_prefix,params.settings.item_format,params.typeToFolder);if(!located){throw new PmCliError(`Schema migration item "${params.id}" no longer exists.`,EXIT_CODE.NOT_FOUND)}return(await readLocatedItem(located,{schema:params.settings.schema})).document.metadata}function applyItemRequest(document,request,direction){const source=direction==="forward"?request.from:request.to;const target=direction==="forward"?request.to:request.from;if(request.kind==="rename-type"){if(document.metadata.type.toLowerCase()===target.toLowerCase())return[];if(document.metadata.type.toLowerCase()!==source.toLowerCase()){throw new TypeError(`Schema migration expected type "${source}" on item "${document.metadata.id}".`)}document.metadata.type=target;return["type"]}if(request.kind==="remap-status"){if(document.metadata.status.toLowerCase()===target.toLowerCase())return[];if(document.metadata.status.toLowerCase()!==source.toLowerCase()){throw new TypeError(`Schema migration expected status "${source}" on item "${document.metadata.id}".`)}document.metadata.status=target;return["status"]}const record=document.metadata;if(Object.prototype.hasOwnProperty.call(record,target)&&!Object.prototype.hasOwnProperty.call(record,source)){return[]}if(!Object.prototype.hasOwnProperty.call(record,source)||Object.prototype.hasOwnProperty.call(record,target)){throw new TypeError(`Schema migration field state changed concurrently on item "${document.metadata.id}".`)}record[target]=record[source];delete record[source];return[source,target]}function schemaStep(params){return{id:params.id,inspect:async()=>{const snapshot=await readSchemaFileSnapshot(params.filePath);const state=schemaDefinitionState(snapshot.raw,params.request);const applied=params.phase==="stage"?state.source&&state.target:!state.source&&state.target;return{state:applied?"applied":"pending",...applied?{result:{phase:params.phase}}:{}}},prepareCompensation:async()=>{const snapshot=await readSchemaFileSnapshot(params.filePath);return{exists:snapshot.exists,raw:snapshot.raw}},apply:async()=>{const snapshot=await readSchemaFileSnapshot(params.filePath);await writeFileAtomic(params.filePath,mutateSchemaDefinition(snapshot.raw,params.request,params.phase));return{phase:params.phase}},compensate:async data=>{if(typeof data!=="object"||data===null||Array.isArray(data)||typeof data.exists!=="boolean"||typeof data.raw!=="string"){throw new TypeError(`Missing schema snapshot for ${params.id} compensation.`)}await restoreSchemaFileSnapshot(params.filePath,{exists:data.exists,raw:data.raw})}}}function schemaReferenceStep(params){return{id:params.stepId,inspect:async()=>{const snapshot=await readSchemaFileSnapshot(params.filePath);return{state:mutateSchemaReferences(snapshot.raw,params.request)===snapshot.raw?"applied":"pending"}},prepareCompensation:async()=>{const snapshot=await readSchemaFileSnapshot(params.filePath);return{exists:snapshot.exists,raw:snapshot.raw}},apply:async()=>{const snapshot=await readSchemaFileSnapshot(params.filePath);await writeFileAtomic(params.filePath,mutateSchemaReferences(snapshot.raw,params.request));return{file:params.filePath}},compensate:async data=>{if(typeof data!=="object"||data===null||Array.isArray(data)||typeof data.exists!=="boolean"||typeof data.raw!=="string"){throw new TypeError(`Missing schema reference snapshot for ${params.stepId} compensation.`)}await restoreSchemaFileSnapshot(params.filePath,{exists:data.exists,raw:data.raw})}}}function itemStep(params){return{id:`item-${params.id}`,inspect:async()=>{const settings=await readSettings(params.pmRoot);const typeToFolder=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()).type_to_folder;const item=await readCurrentItem({pmRoot:params.pmRoot,settings,typeToFolder,id:params.id});const applied=changedFieldsForItem(item,params.request).length===0;return{state:applied?"applied":"pending",...applied?{result:{item_id:params.id}}:{}}},apply:async()=>{const settings=await readSettings(params.pmRoot);const typeToFolder=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()).type_to_folder;const result=await mutateItem({pmRoot:params.pmRoot,settings,typeToFolder,id:params.id,op:`schema_${params.request.kind.replaceAll("-","_")}`,author:params.author,message:`Schema migration ${params.request.kind}: ${params.request.from} -> ${params.request.to}`,force:params.force,skipNoop:true,mutate(document){return{changedFields:applyItemRequest(document,params.request,"forward")}}});return{item_id:params.id,changed_fields:result.changedFields}},compensate:async()=>{const settings=await readSettings(params.pmRoot);const typeToFolder=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()).type_to_folder;await mutateItem({pmRoot:params.pmRoot,settings,typeToFolder,id:params.id,op:`schema_${params.request.kind.replaceAll("-","_")}_compensate`,author:params.author,message:`Compensate schema migration ${params.request.kind}: ${params.request.to} -> ${params.request.from}`,force:true,skipNoop:true,mutate(document){return{changedFields:applyItemRequest(document,params.request,"reverse")}}})}}}async function resolveMigrationPlan(params){const storedPlan=await readStoredPlan(params.pmRoot,params.migrationId);if(storedPlan){if(!requestsEqual(storedPlan.request,params.request)){throw new PmCliError(`Schema migration id "${params.migrationId}" already belongs to a different request.`,EXIT_CODE.CONFLICT)}return{plan:storedPlan,stored:true}}const candidates=await candidateItems({pmRoot:params.pmRoot,settings:params.settings,request:params.request});return{plan:{...planSchemaEvolutionMigration(candidates.items,{migrationId:params.migrationId,request:params.request}),selection_source:candidates.selectionSource},stored:false}}async function validateAndStoreMigrationPlan(params){const definitionState=schemaDefinitionState(await readFileIfExists(params.schemaPath)??"",params.request);if(!definitionState.source){throw new PmCliError(`Schema migration source "${params.request.from}" is not a custom definition.`,EXIT_CODE.NOT_FOUND)}if(definitionState.target){throw new PmCliError(`Schema migration target "${params.request.to}" already exists.`,EXIT_CODE.CONFLICT)}await writeStoredPlan(params.pmRoot,params.plan)}async function collectSchemaReferenceState(params){const normalizedSchema=normalizeRuntimeSchemaSettings(params.settings.schema);const paths=[...new Set([["types",DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.types],["fields",DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.fields],["workflows",DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.workflows]].map(([section,fallback])=>filePathForSchemaSection(params.pmRoot,normalizedSchema.files[section],fallback)).filter(filePath=>filePath!==params.schemaPath))];try{const templatesPath=path.join(params.pmRoot,"templates");const templateEntries=await fs.readdir(templatesPath,{withFileTypes:true});paths.push(...templateEntries.filter(entry=>entry.isFile()&&entry.name.toLowerCase().endsWith(".json")).map(entry=>path.join(templatesPath,entry.name)))}catch(error){if(typeof error!=="object"||error===null||!("code"in error)||!isFileMissingError(error)){throw error}}paths.sort((left,right)=>left.localeCompare(right));const snapshots=[];for(const filePath of paths){const beforeRaw=await readFileIfExists(filePath);if(beforeRaw!==null&&mutateSchemaReferences(beforeRaw,params.request)!==beforeRaw){snapshots.push({filePath,beforeRaw})}}return{paths,snapshots}}async function appendSchemaReferenceHistory(params){for(const reference of params.snapshots){const afterRaw=await fs.readFile(reference.filePath,"utf8");const relativePath=path.relative(params.pmRoot,reference.filePath).replaceAll("\\","/");await appendWorkspaceHistoryChange({pmRoot:params.pmRoot,author:params.author,documentPath:relativePath,before:parseSchemaAuditValue(reference.beforeRaw),after:parseSchemaAuditValue(afterRaw),op:"schema_migration_reference",idempotencyKey:`${params.plan.migration_id}:${params.plan.fingerprint}:${relativePath}`,message:`${params.request.kind}: ${params.request.from} -> ${params.request.to}`,lockTtlSeconds:params.settings.locks.ttl_seconds,lockWaitMs:params.settings.locks.wait_ms})}}async function runSchemaEvolutionMigration(requestInput,options,global2){const request=normalizedRequest(requestInput);const pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const migrationId=options.migrationId===void 0?deriveSchemaEvolutionMigrationId(request,await fs.realpath(pmRoot)):requiredMigrationId(options.migrationId);const settings=await readSettings(pmRoot);const author=resolveAuthor(options.author,settings.author_default);const resolvedPlan=await resolveMigrationPlan({pmRoot,settings,migrationId,request});const plan=resolvedPlan.plan;if(options.dryRun===true){return{...plan,action:request.kind,applied:false,recovered:false,migrated_item_count:0}}const schemaPath=schemaPathFor(pmRoot,settings,request);const schemaBefore=await fs.readFile(schemaPath,"utf8");if(!resolvedPlan.stored){await validateAndStoreMigrationPlan({pmRoot,schemaPath,request,plan})}const references=await collectSchemaReferenceState({pmRoot,schemaPath,settings,request});const steps=[schemaStep({id:"stage-schema",filePath:schemaPath,request,phase:"stage"}),...references.paths.map((filePath,index)=>schemaReferenceStep({filePath,request,stepId:`reference-${String(index+1).padStart(4,"0")}`})),...plan.items.map(entry=>itemStep({pmRoot,id:entry.id,request,author,force:options.force===true})),schemaStep({id:"retire-source",filePath:schemaPath,request,phase:"retire"})];const committed=await commitWorkspaceTransaction({pmRoot,transactionId:`schema-${migrationId}`,author,steps,lockTtlSeconds:Math.max(settings.locks.ttl_seconds,300),lockWaitMs:settings.locks.wait_ms});const schemaAfter=await fs.readFile(schemaPath,"utf8");const{historyPath}=await appendWorkspaceHistoryChange({pmRoot,author,documentPath:path.relative(pmRoot,schemaPath).replaceAll("\\","/"),before:parseSchemaAuditValue(schemaBefore),after:parseSchemaAuditValue(schemaAfter),op:"schema_migration",idempotencyKey:`${plan.migration_id}:${plan.fingerprint}`,message:`${request.kind}: ${request.from} -> ${request.to}; affected=${plan.affected_item_count}`,lockTtlSeconds:settings.locks.ttl_seconds,lockWaitMs:settings.locks.wait_ms});await appendSchemaReferenceHistory({pmRoot,author,settings,request,plan,snapshots:references.snapshots});return{...plan,action:request.kind,applied:true,recovered:committed.recovered,migrated_item_count:plan.affected_item_count,workspace_history_path:historyPath}}function formatSchemaEvolutionMigrationHuman(result){const mode=result.applied?"applied":"dry-run";const recovery=result.recovered?" (recovered)":"";return[`${result.action}: ${result.request.from} -> ${result.request.to} [${mode}${recovery}]`,`migration_id: ${result.migration_id}`,`affected: ${result.affected_item_count}`,`pending: ${result.pending_item_count}`,`fingerprint: ${result.fingerprint}`].join("\n")}const schemaMigrationTestOnly={normalizedRequest,parseSchemaAuditValue,readSchemaFileSnapshot,mutateSchemaDefinition,mutateSchemaReferences,changedFieldsForItem,applyItemRequest,schemaStep,schemaReferenceStep,validateAndStoreMigrationPlan};export{deriveSchemaEvolutionMigrationId,formatSchemaEvolutionMigrationHuman,planSchemaEvolutionMigration,runSchemaEvolutionMigration,schemaMigrationTestOnly};
|
|
856
4
|
//# sourceMappingURL=schema-migration.js.map
|
|
857
|
-
//# debugId=60738c1f-3b44-5f8a-82c9-99d2b2de1e51
|