@unbrained/pm-cli 2026.9.6 → 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 +218 -182
- 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/RELEASING.md +1 -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
package/dist/sdk/testing.js
CHANGED
|
@@ -1,1392 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { getMigrationRuntimeDefinition, resolveRegisteredSearchProvider, resolveRegisteredVectorStoreAdapter, } from "../core/extensions/runtime-registrations.js";
|
|
6
|
-
import { collectUsedExtensionCapabilities } from "../core/extensions/capability-usage.js";
|
|
7
|
-
import { normalizeKnownExtensionCapability } from "../core/extensions/extension-capability-aliases.js";
|
|
8
|
-
import { checkExtensionManifestCompatibility, describeExtensionBlueprint, lintExtensionBlueprint, preflightExtension, } from "./compose.js";
|
|
9
|
-
import { describeExtensionActivation } from "../core/extensions/activation-summary.js";
|
|
10
|
-
import { lintProjectProfile, } from "../core/profile/profile-lint.js";
|
|
11
|
-
import { renderExtensionSurfaceMarkdown } from "../core/extensions/activation-summary-markdown.js";
|
|
12
|
-
import { asPropertyRecord, resolveActivatablePropertyRecord, } from "../core/shared/primitives.js";
|
|
13
|
-
import { createExtensionCommandSdk } from "./extension-command-context.js";
|
|
14
|
-
import { PmClient } from "./runtime.js";
|
|
15
|
-
// `describeExtensionActivation` is the `describe` (enumerate-all) verb that
|
|
16
|
-
// pairs with the `assert*` (verify-one) and `run*` (invoke-one) helpers below.
|
|
17
|
-
// It lives in core (it walks the same registries the loader populates) and is
|
|
18
|
-
// surfaced here so package authors get the whole testing surface from the
|
|
19
|
-
// `@unbrained/pm-cli/sdk/testing` subpath.
|
|
20
|
-
export { describeExtensionActivation, renderExtensionSurfaceMarkdown };
|
|
21
|
-
// `composeExtension`'s author-time companions: describe a blueprint's surface map
|
|
22
|
-
// and preflight it for capability drift / duplicate / empty-surface footguns
|
|
23
|
-
// without activating. Surfaced through the `@unbrained/pm-cli/sdk/testing` subpath
|
|
24
|
-
// so a package author gets the full author → describe → preflight → test loop here,
|
|
25
|
-
// alongside the assert*/run* helpers; `assertExtensionBlueprint` below is the
|
|
26
|
-
// throwing assertion that pairs with the non-throwing `lintExtensionBlueprint`.
|
|
27
|
-
export { describeExtensionBlueprint, lintExtensionBlueprint };
|
|
28
|
-
function registeredExpectationName(expectation, legacyValue) {
|
|
29
|
-
return expectation.name ?? legacyValue ?? "";
|
|
30
|
-
}
|
|
31
|
-
const HOOK_KIND_TO_REGISTRY_FIELD = {
|
|
32
|
-
before_command: "beforeCommand",
|
|
33
|
-
before_mutation: "beforeMutation",
|
|
34
|
-
after_command: "afterCommand",
|
|
35
|
-
on_read: "onRead",
|
|
36
|
-
on_write: "onWrite",
|
|
37
|
-
on_index: "onIndex",
|
|
38
|
-
};
|
|
39
|
-
const RUNNABLE_HOOK_KIND_TO_REGISTRY_FIELD = {
|
|
40
|
-
before_command: "beforeCommand",
|
|
41
|
-
after_command: "afterCommand",
|
|
42
|
-
on_read: "onRead",
|
|
43
|
-
on_write: "onWrite",
|
|
44
|
-
on_index: "onIndex",
|
|
45
|
-
};
|
|
46
|
-
function normalizeSdkIdentifier(value) {
|
|
47
|
-
return value.trim().toLowerCase();
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Match key for profile-name assertions. Extends {@link normalizeSdkIdentifier}
|
|
51
|
-
* with hyphen→underscore folding so the assertion mirrors the runtime profile
|
|
52
|
-
* resolution (`pm profile`), which treats `my-flow` and `my_flow` as the same
|
|
53
|
-
* archetype. Profiles are the one registration surface that resolves
|
|
54
|
-
* hyphen-insensitively (item types resolve by alias, migrations by exact id), so
|
|
55
|
-
* — unlike the other `assertRegistered*` helpers — this one folds the separator
|
|
56
|
-
* to stay in lockstep with how a profile is actually looked up by name.
|
|
57
|
-
*/
|
|
58
|
-
function normalizeSdkProfileMatchKey(value) {
|
|
59
|
-
return normalizeSdkIdentifier(value).replaceAll("-", "_");
|
|
60
|
-
}
|
|
61
|
-
function extensionNameSuffix(extensionName) {
|
|
62
|
-
return extensionName ? ` from extension "${extensionName}"` : "";
|
|
63
|
-
}
|
|
64
|
-
function sortedUnique(values) {
|
|
65
|
-
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
66
|
-
}
|
|
67
|
-
function normalizeSdkCommandName(command) {
|
|
68
|
-
if (typeof command !== "string") {
|
|
69
|
-
// Positional misuse like runRegisteredCommandForTest(activation, "name", opts)
|
|
70
|
-
// reaches here with `undefined` at runtime; guide instead of crashing on .trim().
|
|
71
|
-
throw new Error('A command name string is required. Pass it via the options object, e.g. runRegisteredCommandForTest(activation, { command: "my command" }) — positional command arguments are not supported.');
|
|
72
|
-
}
|
|
73
|
-
return command
|
|
74
|
-
.trim()
|
|
75
|
-
.toLowerCase()
|
|
76
|
-
.split(/\s+/)
|
|
77
|
-
.filter((part) => part.length > 0)
|
|
78
|
-
.join(" ");
|
|
79
|
-
}
|
|
80
|
-
function formatAvailable(values) {
|
|
81
|
-
return values.length > 0 ? values.join(", ") : "(none)";
|
|
82
|
-
}
|
|
83
|
-
function assertStringSetIncludes(actual, expected, label) {
|
|
84
|
-
const actualValues = new Set(actual ?? []);
|
|
85
|
-
const missing = sortedUnique(expected.filter((value) => !actualValues.has(value)));
|
|
86
|
-
if (missing.length > 0) {
|
|
87
|
-
throw new Error(`Expected package manifest ${label} to include ${missing.join(", ")}; available: ${formatAvailable(sortedUnique(actual ?? []))}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
function collectFlagLabels(flags) {
|
|
91
|
-
const labels = new Set();
|
|
92
|
-
for (const flag of flags) {
|
|
93
|
-
if (typeof flag.long === "string" && flag.long.trim().length > 0) {
|
|
94
|
-
labels.add(flag.long.trim());
|
|
95
|
-
}
|
|
96
|
-
if (typeof flag.short === "string" && flag.short.trim().length > 0) {
|
|
97
|
-
labels.add(flag.short.trim());
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return labels;
|
|
101
|
-
}
|
|
102
|
-
function readTestExtensionManifest(module) {
|
|
103
|
-
const moduleRecord = asPropertyRecord(module);
|
|
104
|
-
if (!moduleRecord) {
|
|
105
|
-
return {};
|
|
106
|
-
}
|
|
107
|
-
const manifest = asPropertyRecord(moduleRecord.manifest);
|
|
108
|
-
if (manifest) {
|
|
109
|
-
return manifest;
|
|
110
|
-
}
|
|
111
|
-
const defaultExport = asPropertyRecord(moduleRecord.default);
|
|
112
|
-
const defaultManifest = asPropertyRecord(defaultExport?.manifest);
|
|
113
|
-
if (defaultManifest) {
|
|
114
|
-
return defaultManifest;
|
|
115
|
-
}
|
|
116
|
-
if (defaultExport &&
|
|
117
|
-
typeof moduleRecord.default !== "function" &&
|
|
118
|
-
("name" in defaultExport || "capabilities" in defaultExport)) {
|
|
119
|
-
return defaultExport;
|
|
120
|
-
}
|
|
121
|
-
if (typeof module !== "function" &&
|
|
122
|
-
("name" in moduleRecord || "capabilities" in moduleRecord)) {
|
|
123
|
-
return moduleRecord;
|
|
124
|
-
}
|
|
125
|
-
return {};
|
|
126
|
-
}
|
|
127
|
-
function resolveTestExtensionName(manifest, explicitName) {
|
|
128
|
-
return (explicitName ??
|
|
129
|
-
(typeof manifest.name === "string" && manifest.name.trim().length > 0
|
|
130
|
-
? manifest.name.trim()
|
|
131
|
-
: "test-extension"));
|
|
132
|
-
}
|
|
133
|
-
function readTestExtensionCapabilities(manifest, options) {
|
|
134
|
-
if (Array.isArray(options.capabilities)) {
|
|
135
|
-
return [...options.capabilities];
|
|
136
|
-
}
|
|
137
|
-
if (Array.isArray(manifest.capabilities)) {
|
|
138
|
-
return manifest.capabilities.filter((capability) => typeof capability === "string");
|
|
139
|
-
}
|
|
140
|
-
return [];
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Build the synthetic single-extension {@link ExtensionLoadResult} shared by the
|
|
144
|
-
* activate/deactivate test helpers, so both stay aligned with the loader's load
|
|
145
|
-
* contract as it evolves. The `loaded` entry mirrors the real on-disk shape
|
|
146
|
-
* (including `capabilities` and the compatibility fields) while leaving
|
|
147
|
-
* filesystem paths empty, since the module is supplied in memory.
|
|
148
|
-
*/
|
|
149
|
-
function buildSingleExtensionLoadResult(module, manifest, identity) {
|
|
150
|
-
return {
|
|
151
|
-
disabled_by_flag: false,
|
|
152
|
-
roots: { global: "", project: "" },
|
|
153
|
-
configured_enabled: [],
|
|
154
|
-
configured_disabled: [],
|
|
155
|
-
discovered: [],
|
|
156
|
-
effective: [],
|
|
157
|
-
warnings: [],
|
|
158
|
-
policy: identity.policy,
|
|
159
|
-
failed: [],
|
|
160
|
-
loaded: [
|
|
161
|
-
{
|
|
162
|
-
layer: identity.layer,
|
|
163
|
-
directory: "",
|
|
164
|
-
manifest_path: "",
|
|
165
|
-
name: identity.name,
|
|
166
|
-
version: typeof manifest.version === "string" ? manifest.version : "0.0.0",
|
|
167
|
-
entry: typeof manifest.entry === "string" ? manifest.entry : "./index.js",
|
|
168
|
-
priority: typeof manifest.priority === "number" ? manifest.priority : 0,
|
|
169
|
-
entry_path: "",
|
|
170
|
-
capabilities: identity.capabilities,
|
|
171
|
-
manifest_version: typeof manifest.manifest_version === "number"
|
|
172
|
-
? manifest.manifest_version
|
|
173
|
-
: undefined,
|
|
174
|
-
pm_min_version: typeof manifest.pm_min_version === "string"
|
|
175
|
-
? manifest.pm_min_version
|
|
176
|
-
: undefined,
|
|
177
|
-
pm_max_version: typeof manifest.pm_max_version === "string"
|
|
178
|
-
? manifest.pm_max_version
|
|
179
|
-
: undefined,
|
|
180
|
-
engines: manifest.engines,
|
|
181
|
-
trusted: manifest.trusted,
|
|
182
|
-
provenance: manifest.provenance,
|
|
183
|
-
sandbox_profile: manifest.sandbox_profile,
|
|
184
|
-
permissions: manifest.permissions,
|
|
185
|
-
activation: manifest.activation,
|
|
186
|
-
module,
|
|
187
|
-
},
|
|
188
|
-
],
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Activate one in-memory extension module for package tests.
|
|
193
|
-
*
|
|
194
|
-
* This uses pm's real registration validation and activation engine while
|
|
195
|
-
* avoiding private loader imports, filesystem manifests, or workspace setup.
|
|
196
|
-
*/
|
|
197
|
-
export async function activateExtensionForTest(module, options = {}) {
|
|
198
|
-
const manifest = readTestExtensionManifest(module);
|
|
199
|
-
return activateExtensions(buildSingleExtensionLoadResult(module, manifest, {
|
|
200
|
-
name: resolveTestExtensionName(manifest, options.name),
|
|
201
|
-
layer: options.layer ?? "project",
|
|
202
|
-
capabilities: readTestExtensionCapabilities(manifest, options),
|
|
203
|
-
policy: options.policy ?? createDefaultExtensionGovernancePolicy(),
|
|
204
|
-
}));
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Deactivate one in-memory extension module for package tests — the teardown
|
|
208
|
-
* counterpart to {@link activateExtensionForTest}.
|
|
209
|
-
*
|
|
210
|
-
* Runs pm's real `deactivateExtensions` engine (including its bounded per-hook
|
|
211
|
-
* timeout and best-effort failure capture) over a single synthetic load entry,
|
|
212
|
-
* so authors can prove an extension's `deactivate` releases the resources its
|
|
213
|
-
* `activate` opened — without importing private loader internals or staging a
|
|
214
|
-
* workspace. Resolve `name`/`layer` the same way {@link activateExtensionForTest}
|
|
215
|
-
* does so a forwarded `activation` result skips a failed extension correctly.
|
|
216
|
-
*/
|
|
217
|
-
export async function deactivateExtensionForTest(module, options = {}) {
|
|
218
|
-
const manifest = readTestExtensionManifest(module);
|
|
219
|
-
return deactivateExtensions(buildSingleExtensionLoadResult(module, manifest, {
|
|
220
|
-
name: resolveTestExtensionName(manifest, options.name),
|
|
221
|
-
layer: options.layer ?? "project",
|
|
222
|
-
capabilities: readTestExtensionCapabilities(manifest, {}),
|
|
223
|
-
policy: createDefaultExtensionGovernancePolicy(),
|
|
224
|
-
}), options.activation, options.deactivateTimeoutMs === undefined
|
|
225
|
-
? {}
|
|
226
|
-
: { deactivate_timeout_ms: options.deactivateTimeoutMs });
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Invoke a registered extension command handler and return its real
|
|
230
|
-
* {@link CommandHandlerResult} so package tests can assert on behavior — not
|
|
231
|
-
* just that the command was registered.
|
|
232
|
-
*
|
|
233
|
-
* This is the "invoke" verb that completes the package-author testing loop:
|
|
234
|
-
* `activateExtensionForTest` → `assertRegisteredCommandContract` → **run** →
|
|
235
|
-
* `deactivateExtensionForTest`. It runs pm's real handler-dispatch engine
|
|
236
|
-
* (`runCommandHandler`) over `activation.commands`, building the
|
|
237
|
-
* `CommandHandlerContext` with agent-safe global defaults
|
|
238
|
-
* (`{ json: true, quiet: true, noPager: true }`) that callers may override.
|
|
239
|
-
*
|
|
240
|
-
* Because `registerImporter`/`registerExporter` register their handlers under
|
|
241
|
-
* the `"<name> import"` / `"<name> export"` command paths, this same helper
|
|
242
|
-
* exercises importer and exporter handlers too.
|
|
243
|
-
*
|
|
244
|
-
* Throws a descriptive error (listing the available handler command paths) when
|
|
245
|
-
* no handler is registered for `command`, since that is a wiring/typo bug in the
|
|
246
|
-
* test rather than a behavior under test. When a handler is found, the result is
|
|
247
|
-
* returned verbatim: a clean run yields `{ handled: true, result, warnings: [] }`,
|
|
248
|
-
* while a handler that throws a non-exit error yields
|
|
249
|
-
* `{ handled: false, warnings: [code], errorMessage }` so the failure can be
|
|
250
|
-
* asserted. A handler that throws an error carrying a numeric `exitCode`
|
|
251
|
-
* propagates the throw, matching runtime semantics.
|
|
252
|
-
*/
|
|
253
|
-
export async function runRegisteredCommandForTest(commands, options) {
|
|
254
|
-
const command = normalizeSdkCommandName(options.command);
|
|
255
|
-
if (command.length === 0) {
|
|
256
|
-
throw new Error("Expected command name must be a non-empty string");
|
|
257
|
-
}
|
|
258
|
-
const hasHandler = commands.handlers.some((entry) => entry.command === command);
|
|
259
|
-
if (!hasHandler) {
|
|
260
|
-
const available = sortedUnique(commands.handlers.map((entry) => entry.command));
|
|
261
|
-
throw new Error(`Expected a registered command handler for "${command}" to invoke. Available command handlers: ${formatAvailable(available)}`);
|
|
262
|
-
}
|
|
263
|
-
if (options.sdk !== undefined && options.sdkFactory !== undefined) {
|
|
264
|
-
throw new TypeError("runRegisteredCommandForTest accepts sdk or sdkFactory, not both");
|
|
265
|
-
}
|
|
266
|
-
const pmRoot = options.pmRoot ?? "";
|
|
267
|
-
const global = { json: true, quiet: true, noPager: true, ...options.global };
|
|
268
|
-
const sdk = options.sdk ??
|
|
269
|
-
options.sdkFactory?.({ pmRoot, global }) ??
|
|
270
|
-
(pmRoot.length > 0
|
|
271
|
-
? createExtensionCommandSdk(pmRoot, new PmClient({ pmRoot }))
|
|
272
|
-
: undefined);
|
|
273
|
-
return runCommandHandler(commands, {
|
|
274
|
-
command,
|
|
275
|
-
args: options.args ? [...options.args] : [],
|
|
276
|
-
options: options.options ? { ...options.options } : {},
|
|
277
|
-
global,
|
|
278
|
-
pm_root: pmRoot,
|
|
279
|
-
...(sdk === undefined ? {} : { sdk }),
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
/** Invoke registered transactional mutation guards through the same fail-closed runtime used by real writes. */
|
|
283
|
-
export async function runRegisteredMutationGuardForTest(hooks, options) {
|
|
284
|
-
if ((hooks.beforeMutation?.length ?? 0) === 0) {
|
|
285
|
-
throw new Error("Expected a registered before_mutation hook to invoke");
|
|
286
|
-
}
|
|
287
|
-
await runBeforeMutationHooks(hooks, options.context);
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Fire every registered lifecycle hook of a given `kind` through pm's real hook
|
|
291
|
-
* runner and return the warnings array so package tests can assert on behavior —
|
|
292
|
-
* not just that a hook was registered.
|
|
293
|
-
*
|
|
294
|
-
* This is the hook counterpart to {@link runRegisteredCommandForTest}, extending
|
|
295
|
-
* the package-author "invoke" verb to the `api.hooks.*` surface. Pass the
|
|
296
|
-
* `ExtensionHookRegistry` from `activateExtensionForTest(...).hooks` together with
|
|
297
|
-
* the lifecycle `kind` and a synthetic context. Hooks observe their inputs via a
|
|
298
|
-
* cloned context snapshot (mutations never leak back to the caller), so the
|
|
299
|
-
* observable signal is twofold: any side effects the hook performs, and the
|
|
300
|
-
* returned warnings. A clean run returns `[]`; a hook that throws contributes one
|
|
301
|
-
* `extension_hook_failed:<layer>:<name>:<hookName>` warning, mirroring runtime
|
|
302
|
-
* dispatch which isolates a failing hook without aborting the others.
|
|
303
|
-
*
|
|
304
|
-
* Throws a descriptive error (listing the hook kinds that do have registrations)
|
|
305
|
-
* when no hook of `kind` is registered, since firing a hook the extension never
|
|
306
|
-
* registered is a wiring/typo bug in the test rather than a behavior under test.
|
|
307
|
-
*/
|
|
308
|
-
export async function runRegisteredHookForTest(hooks, options) {
|
|
309
|
-
const kind = options.kind;
|
|
310
|
-
if (kind === "before_mutation") {
|
|
311
|
-
throw new Error("Invoke before_mutation hooks with runRegisteredMutationGuardForTest so denials and failures retain fail-closed semantics.");
|
|
312
|
-
}
|
|
313
|
-
if ((hooks[RUNNABLE_HOOK_KIND_TO_REGISTRY_FIELD[kind]]?.length ?? 0) === 0) {
|
|
314
|
-
const populatedKinds = sortedUnique(Object.keys(RUNNABLE_HOOK_KIND_TO_REGISTRY_FIELD).filter((candidateKind) => (hooks[RUNNABLE_HOOK_KIND_TO_REGISTRY_FIELD[candidateKind]]
|
|
315
|
-
?.length ?? 0) > 0));
|
|
316
|
-
throw new Error(`Expected a registered "${kind}" hook to invoke. Hook kinds with registrations: ${formatAvailable(populatedKinds)}`);
|
|
317
|
-
}
|
|
318
|
-
switch (options.kind) {
|
|
319
|
-
case "before_command":
|
|
320
|
-
return runBeforeCommandHooks(hooks, options.context);
|
|
321
|
-
case "after_command":
|
|
322
|
-
return runAfterCommandHooks(hooks, options.context);
|
|
323
|
-
case "on_read":
|
|
324
|
-
return runOnReadHooks(hooks, options.context);
|
|
325
|
-
case "on_write":
|
|
326
|
-
return runOnWriteHooks(hooks, options.context);
|
|
327
|
-
case "on_index":
|
|
328
|
-
return runOnIndexHooks(hooks, options.context);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
|
-
* Invoke a registered parser override through pm's real runner and return the
|
|
333
|
-
* resolved {@link ParserOverrideResult}, so package tests can assert the rewritten
|
|
334
|
-
* args/options/global an override produces for a command before dispatch.
|
|
335
|
-
*
|
|
336
|
-
* Guards that a parser override is registered for the (normalized) target command
|
|
337
|
-
* before delegating: without a match `runParserOverride` would silently return
|
|
338
|
-
* `overridden: false`, hiding a typo'd command name in the test. When a match
|
|
339
|
-
* exists the result is returned verbatim — `overridden: true` with the rewritten
|
|
340
|
-
* context on success, or `overridden: false` with an
|
|
341
|
-
* `extension_parser_override_failed:*` warning when the override throws.
|
|
342
|
-
*/
|
|
343
|
-
export async function runRegisteredParserOverrideForTest(parsers, context) {
|
|
344
|
-
const command = normalizeSdkCommandName(context.command);
|
|
345
|
-
if (!parsers.overrides.some((entry) => entry.command === command)) {
|
|
346
|
-
throw new Error(`Expected a registered parser override for command "${command}" to invoke. Available parser override commands: ${formatAvailable(sortedUnique(parsers.overrides.map((entry) => entry.command)))}`);
|
|
347
|
-
}
|
|
348
|
-
return runParserOverride(parsers, context);
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* Invoke the active preflight override through pm's real runner and return the
|
|
352
|
-
* resolved {@link PreflightOverrideResult}, so package tests can assert the
|
|
353
|
-
* migration/format gate decision an override yields.
|
|
354
|
-
*
|
|
355
|
-
* Unlike command-scoped overrides, the runtime applies the **last** registered
|
|
356
|
-
* preflight override regardless of command, so this helper guards only that at
|
|
357
|
-
* least one preflight override is registered before delegating. The result is
|
|
358
|
-
* returned verbatim — `overridden: true` with the adjusted decision on success,
|
|
359
|
-
* or `overridden: false` with an `extension_preflight_override_failed:*` warning
|
|
360
|
-
* when the override throws.
|
|
361
|
-
*/
|
|
362
|
-
export async function runRegisteredPreflightOverrideForTest(preflight, context) {
|
|
363
|
-
if (preflight.overrides.length === 0) {
|
|
364
|
-
throw new Error("Expected a registered preflight override to invoke, but none are registered.");
|
|
365
|
-
}
|
|
366
|
-
return runPreflightOverride(preflight, context);
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Invoke a registered command-result override through pm's real runner and return
|
|
370
|
-
* the resolved {@link CommandOverrideResult}, so package tests can assert how an
|
|
371
|
-
* override transforms a command's result payload.
|
|
372
|
-
*
|
|
373
|
-
* Accepts the same `ExtensionCommandRegistry` exposed as
|
|
374
|
-
* `activateExtensionForTest(...).commands` (it carries both handlers and
|
|
375
|
-
* overrides). Guards that an override is registered for the (normalized) target
|
|
376
|
-
* command before delegating, so a typo'd command name surfaces as a clear error
|
|
377
|
-
* rather than a silent `overridden: false`. The result is returned verbatim —
|
|
378
|
-
* including the `extension_command_override_async_unsupported:*` warning the
|
|
379
|
-
* runtime emits when an override returns a Promise (command overrides are
|
|
380
|
-
* synchronous), or an `extension_command_override_failed:*` warning on throw.
|
|
381
|
-
*/
|
|
382
|
-
export async function runRegisteredCommandOverrideForTest(commands, context) {
|
|
383
|
-
const command = normalizeSdkCommandName(context.command);
|
|
384
|
-
if (!commands.overrides.some((entry) => entry.command === command)) {
|
|
385
|
-
throw new Error(`Expected a registered command override for command "${command}" to invoke. Available command override commands: ${formatAvailable(sortedUnique(commands.overrides.map((entry) => entry.command)))}`);
|
|
386
|
-
}
|
|
387
|
-
return runCommandOverride(commands, context);
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Invoke a registered renderer override through pm's real runner and return the
|
|
391
|
-
* resolved {@link RendererOverrideResult}, so package tests can assert the custom
|
|
392
|
-
* string an override renders for a given output `format`.
|
|
393
|
-
*
|
|
394
|
-
* Guards that a renderer override is registered for `context.format` before
|
|
395
|
-
* delegating, so a wrong format surfaces as a clear error rather than a silent
|
|
396
|
-
* `overridden: false`. The result is returned verbatim — `overridden: true` with
|
|
397
|
-
* the rendered string on success, or `overridden: false` (with an
|
|
398
|
-
* `extension_renderer_invalid_result:*` / `extension_renderer_failed:*` warning
|
|
399
|
-
* where applicable) when the override returns a non-string or throws.
|
|
400
|
-
*/
|
|
401
|
-
export async function runRegisteredRendererOverrideForTest(renderers, context) {
|
|
402
|
-
if (!renderers.overrides.some((entry) => entry.format === context.format)) {
|
|
403
|
-
throw new Error(`Expected a registered renderer override for format "${context.format}" to invoke. Available renderer override formats: ${formatAvailable(sortedUnique(renderers.overrides.map((entry) => entry.format)))}`);
|
|
404
|
-
}
|
|
405
|
-
return runRendererOverride(renderers, context);
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* Invoke a registered service override through pm's real runner and return the
|
|
409
|
-
* resolved {@link ServiceOverrideResult}, so package tests can assert how an
|
|
410
|
-
* override handles an internal service payload (e.g. `output_format`).
|
|
411
|
-
*
|
|
412
|
-
* Guards that a service override is registered for `context.service` before
|
|
413
|
-
* delegating, so a wrong service name surfaces as a clear error rather than a
|
|
414
|
-
* silent `handled: false`. The result is returned verbatim — `handled: true` with
|
|
415
|
-
* the override's result when it claims the payload, or `handled: false` with the
|
|
416
|
-
* original payload (and any `extension_service_override_*` warnings) otherwise.
|
|
417
|
-
*/
|
|
418
|
-
export async function runRegisteredServiceOverrideForTest(services, context) {
|
|
419
|
-
if (!services.overrides.some((entry) => entry.service === context.service)) {
|
|
420
|
-
throw new Error(`Expected a registered service override for service "${context.service}" to invoke. Available service override services: ${formatAvailable(sortedUnique(services.overrides.map((entry) => entry.service)))}`);
|
|
421
|
-
}
|
|
422
|
-
return runServiceOverride(services, context);
|
|
423
|
-
}
|
|
424
|
-
/** Maps each search-provider operation to the runtime-definition keys that may hold its function, in priority order. The host accepts both camelCase and snake_case spellings for the multi-word operations, so this helper mirrors that by trying `embedBatch` before `embed_batch` and `queryExpansion` before `query_expansion`. */
|
|
425
|
-
const SEARCH_PROVIDER_OPERATION_DEFINITION_KEYS = {
|
|
426
|
-
query: ["query"],
|
|
427
|
-
embed: ["embed"],
|
|
428
|
-
embedBatch: ["embedBatch", "embed_batch"],
|
|
429
|
-
queryExpansion: ["queryExpansion", "query_expansion"],
|
|
430
|
-
rerank: ["rerank"],
|
|
431
|
-
};
|
|
432
|
-
/**
|
|
433
|
-
* Invoke an operation of a registered search provider through the same name
|
|
434
|
-
* resolution the host uses and return the operation's result, so package tests
|
|
435
|
-
* can assert a custom provider's runtime behavior — not just that it registered.
|
|
436
|
-
*
|
|
437
|
-
* This is the search-provider counterpart to {@link runRegisteredCommandForTest},
|
|
438
|
-
* extending the package-author "invoke" verb to the `api.registerSearchProvider`
|
|
439
|
-
* surface. Pass the `ExtensionRegistrationRegistry` from
|
|
440
|
-
* `activateExtensionForTest(...).registrations`, the registered provider name, the
|
|
441
|
-
* `operation` to exercise (`query`, `embed`, `embedBatch`, `queryExpansion`, or
|
|
442
|
-
* `rerank`), and a synthetic context. The provider is resolved via
|
|
443
|
-
* {@link resolveRegisteredSearchProvider} (case-insensitive, last registration
|
|
444
|
-
* wins) and the operation is invoked on its `runtime_definition` — the clone that
|
|
445
|
-
* preserves live functions, matching what the runtime dispatches.
|
|
446
|
-
*
|
|
447
|
-
* Throws a descriptive error listing the available providers when the name is not
|
|
448
|
-
* registered, and a separate error when the resolved provider does not implement
|
|
449
|
-
* the requested operation, since invoking an absent provider/operation is a
|
|
450
|
-
* wiring bug in the test rather than a behavior under test.
|
|
451
|
-
*/
|
|
452
|
-
export async function runRegisteredSearchProviderForTest(registrations, options) {
|
|
453
|
-
const registration = resolveRegisteredSearchProvider(registrations, options.provider);
|
|
454
|
-
if (!registration) {
|
|
455
|
-
const available = sortedUnique(registrations.search_providers.map((entry) => entry.definition.name));
|
|
456
|
-
throw new Error(`Expected a registered search provider "${options.provider}" to invoke. Available search providers: ${formatAvailable(available)}`);
|
|
457
|
-
}
|
|
458
|
-
const runtimeDefinition = registration.runtime_definition;
|
|
459
|
-
const operationFn = SEARCH_PROVIDER_OPERATION_DEFINITION_KEYS[options.operation]
|
|
460
|
-
.map((key) => runtimeDefinition[key])
|
|
461
|
-
.find((value) => typeof value === "function");
|
|
462
|
-
if (typeof operationFn !== "function") {
|
|
463
|
-
throw new Error(`Registered search provider "${options.provider}" does not implement the "${options.operation}" operation.`);
|
|
464
|
-
}
|
|
465
|
-
return (await operationFn(options.context));
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* Invoke an operation of a registered vector store adapter through the same name
|
|
469
|
-
* resolution the host uses and return the operation's result, so package tests
|
|
470
|
-
* can assert a custom adapter's runtime behavior — not just that it registered.
|
|
471
|
-
*
|
|
472
|
-
* This is the vector-store counterpart to {@link runRegisteredSearchProviderForTest}.
|
|
473
|
-
* Pass the `ExtensionRegistrationRegistry` from
|
|
474
|
-
* `activateExtensionForTest(...).registrations`, the registered adapter name, the
|
|
475
|
-
* `operation` to exercise (`query`, `upsert`, or `delete`), and a synthetic
|
|
476
|
-
* context. The adapter is resolved via {@link resolveRegisteredVectorStoreAdapter}
|
|
477
|
-
* (case-insensitive, last registration wins) and the operation is invoked on its
|
|
478
|
-
* `runtime_definition` — the clone that preserves live functions, matching what
|
|
479
|
-
* the runtime dispatches.
|
|
480
|
-
*
|
|
481
|
-
* Throws a descriptive error listing the available adapters when the name is not
|
|
482
|
-
* registered, and a separate error when the resolved adapter does not implement
|
|
483
|
-
* the requested operation.
|
|
484
|
-
*/
|
|
485
|
-
export async function runRegisteredVectorStoreAdapterForTest(registrations, options) {
|
|
486
|
-
const registration = resolveRegisteredVectorStoreAdapter(registrations, options.adapter);
|
|
487
|
-
if (!registration) {
|
|
488
|
-
const available = sortedUnique(registrations.vector_store_adapters.map((entry) => entry.definition.name));
|
|
489
|
-
throw new Error(`Expected a registered vector store adapter "${options.adapter}" to invoke. Available vector store adapters: ${formatAvailable(available)}`);
|
|
490
|
-
}
|
|
491
|
-
const operationFn = registration.runtime_definition[options.operation];
|
|
492
|
-
if (typeof operationFn !== "function") {
|
|
493
|
-
throw new Error(`Registered vector store adapter "${options.adapter}" does not implement the "${options.operation}" operation.`);
|
|
494
|
-
}
|
|
495
|
-
return (await operationFn(options.context));
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* Invoke a registered schema migration's `run` function through the same runtime
|
|
499
|
-
* definition the host executes and return its result, so package tests can assert
|
|
500
|
-
* a migration's behavior — not just that it registered.
|
|
501
|
-
*
|
|
502
|
-
* This completes the package-author "invoke" verb across every executable
|
|
503
|
-
* registration surface. The migration is resolved by id via
|
|
504
|
-
* {@link assertRegisteredMigration} (which throws a descriptive "available
|
|
505
|
-
* migrations" error when absent), then `run` is invoked on its
|
|
506
|
-
* `runtime_definition` with a context mirroring the one
|
|
507
|
-
* `executeRegisteredRuntimeMigrations` builds at runtime: the resolved id,
|
|
508
|
-
* `command: "migration"`, the registering extension's layer/name, the supplied
|
|
509
|
-
* `pmRoot`, and the migration's normalized status. Unlike the runtime — which
|
|
510
|
-
* skips already-applied migrations and folds a throw into a warning — this helper
|
|
511
|
-
* always invokes `run` and lets a throw propagate, so authors can assert both the
|
|
512
|
-
* success result and failure via rejection.
|
|
513
|
-
*
|
|
514
|
-
* Throws when the resolved migration declares no `run` function, since invoking a
|
|
515
|
-
* runless migration is a wiring bug in the test rather than a behavior under test.
|
|
516
|
-
*/
|
|
517
|
-
export async function runRegisteredMigrationForTest(registrations, options) {
|
|
518
|
-
const migration = assertRegisteredMigration(registrations, {
|
|
519
|
-
migration: options.migration,
|
|
520
|
-
extensionName: options.extensionName,
|
|
521
|
-
});
|
|
522
|
-
const run = getMigrationRuntimeDefinition(migration).run;
|
|
523
|
-
if (typeof run !== "function") {
|
|
524
|
-
throw new Error(`Registered migration "${options.migration}" does not implement a run function to invoke.`);
|
|
525
|
-
}
|
|
526
|
-
const declaredStatus = migration.definition.status;
|
|
527
|
-
const context = {
|
|
528
|
-
id: migration.definition.id,
|
|
529
|
-
command: "migration",
|
|
530
|
-
layer: migration.layer,
|
|
531
|
-
extension: migration.name,
|
|
532
|
-
pm_root: options.pmRoot ?? "",
|
|
533
|
-
status: (typeof declaredStatus === "string"
|
|
534
|
-
? declaredStatus.trim().toLowerCase()
|
|
535
|
-
: "") || "pending",
|
|
536
|
-
};
|
|
537
|
-
return await run(context);
|
|
538
|
-
}
|
|
539
|
-
/**
|
|
540
|
-
* Invoke a registered extension importer through pm's real handler-dispatch
|
|
541
|
-
* engine and return its {@link CommandHandlerResult}, so package tests can assert
|
|
542
|
-
* an importer's behavior — not just that it registered.
|
|
543
|
-
*
|
|
544
|
-
* This is the importer counterpart to {@link runRegisteredCommandForTest},
|
|
545
|
-
* extending the package-author "invoke" verb to the `api.registerImporter`
|
|
546
|
-
* surface. Because `registerImporter(name, fn)` wraps `fn` into a command handler
|
|
547
|
-
* at the `"<name> import"` path, invoking it through `runRegisteredCommandForTest`
|
|
548
|
-
* requires the author to know that naming convention and to remember that an
|
|
549
|
-
* importer is reachable as a command at all. This helper closes both gaps: it
|
|
550
|
-
* accepts the registered importer name directly, validates via
|
|
551
|
-
* {@link assertRegisteredImporter} that it is genuinely a registered importer
|
|
552
|
-
* (not merely some command parked at that path), derives the command path, and
|
|
553
|
-
* dispatches through the same engine.
|
|
554
|
-
*
|
|
555
|
-
* The full activation result is required because importer execution spans two
|
|
556
|
-
* sub-registries: `registrations.importers` proves the importer exists, while
|
|
557
|
-
* `commands` holds the wrapped handler. The result is returned verbatim from the
|
|
558
|
-
* command engine: a clean run yields `{ handled: true, result, warnings: [] }`
|
|
559
|
-
* (where `result` is the importer's return value), a non-exit throw yields
|
|
560
|
-
* `{ handled: false, warnings: [code], errorMessage }`, and an error carrying a
|
|
561
|
-
* numeric `exitCode` propagates — matching runtime import semantics.
|
|
562
|
-
*
|
|
563
|
-
* Throws a descriptive "available importers" error (via `assertRegisteredImporter`)
|
|
564
|
-
* when no importer matches, since that is a wiring/typo bug in the test rather
|
|
565
|
-
* than a behavior under test.
|
|
566
|
-
*/
|
|
567
|
-
export async function runRegisteredImporterForTest(activation, options) {
|
|
568
|
-
const importer = assertRegisteredImporter(activation.registrations, {
|
|
569
|
-
importer: options.importer,
|
|
570
|
-
extensionName: options.extensionName,
|
|
571
|
-
});
|
|
572
|
-
return runRegisteredCommandForTest(activation.commands, {
|
|
573
|
-
command: `${importer.importer} import`,
|
|
574
|
-
args: options.args,
|
|
575
|
-
options: options.options,
|
|
576
|
-
global: options.global,
|
|
577
|
-
pmRoot: options.pmRoot,
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* Invoke a registered extension exporter through pm's real handler-dispatch
|
|
582
|
-
* engine and return its {@link CommandHandlerResult}, so package tests can assert
|
|
583
|
-
* an exporter's behavior — not just that it registered.
|
|
584
|
-
*
|
|
585
|
-
* This is the exporter counterpart to {@link runRegisteredImporterForTest} and
|
|
586
|
-
* the final surface in the package-author "invoke" verb. Because
|
|
587
|
-
* `registerExporter(name, fn)` wraps `fn` into a command handler at the
|
|
588
|
-
* `"<name> export"` path, this helper accepts the registered exporter name
|
|
589
|
-
* directly, validates via {@link assertRegisteredExporter} that it is genuinely a
|
|
590
|
-
* registered exporter, derives the command path, and dispatches through the same
|
|
591
|
-
* engine. See {@link runRegisteredImporterForTest} for the full activation
|
|
592
|
-
* rationale and return semantics (which apply identically here).
|
|
593
|
-
*
|
|
594
|
-
* Throws a descriptive "available exporters" error (via `assertRegisteredExporter`)
|
|
595
|
-
* when no exporter matches, since that is a wiring/typo bug in the test rather
|
|
596
|
-
* than a behavior under test.
|
|
597
|
-
*/
|
|
598
|
-
export async function runRegisteredExporterForTest(activation, options) {
|
|
599
|
-
const exporter = assertRegisteredExporter(activation.registrations, {
|
|
600
|
-
exporter: options.exporter,
|
|
601
|
-
extensionName: options.extensionName,
|
|
602
|
-
});
|
|
603
|
-
return runRegisteredCommandForTest(activation.commands, {
|
|
604
|
-
command: `${exporter.exporter} export`,
|
|
605
|
-
args: options.args,
|
|
606
|
-
options: options.options,
|
|
607
|
-
global: options.global,
|
|
608
|
-
pmRoot: options.pmRoot,
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* Assert that an {@link ExtensionDeactivationResult} reports the expected clean
|
|
613
|
-
* teardown counts, throwing a descriptive error otherwise. Defaults assert the
|
|
614
|
-
* single-extension happy path — exactly one extension deactivated and none
|
|
615
|
-
* failed. Returns the result so assertions can chain.
|
|
616
|
-
*/
|
|
617
|
-
export function assertExtensionDeactivated(result, expectation = {}) {
|
|
618
|
-
const expectedDeactivated = expectation.deactivated ?? 1;
|
|
619
|
-
if (result.deactivated !== expectedDeactivated) {
|
|
620
|
-
throw new Error(`Expected ${expectedDeactivated} extension${expectedDeactivated === 1 ? "" : "s"} to deactivate cleanly, ` +
|
|
621
|
-
`but ${result.deactivated} did.`);
|
|
622
|
-
}
|
|
623
|
-
const expectedFailed = expectation.failed ?? 0;
|
|
624
|
-
if (result.failed.length !== expectedFailed) {
|
|
625
|
-
const detail = result.failed
|
|
626
|
-
.map((failure) => `${failure.layer}:${failure.name} (${failure.error})`)
|
|
627
|
-
.join(", ");
|
|
628
|
-
throw new Error(`Expected ${expectedFailed} teardown failure${expectedFailed === 1 ? "" : "s"}, ` +
|
|
629
|
-
`but observed ${result.failed.length}${detail.length > 0 ? `: ${detail}` : ""}.`);
|
|
630
|
-
}
|
|
631
|
-
return result;
|
|
632
|
-
}
|
|
633
|
-
/** Assert that a normalized package manifest advertises expected package resources. Pair with `readPmPackageManifest(packageRoot)` in package tests. */
|
|
634
|
-
export function assertPackageManifest(manifest, expectation) {
|
|
635
|
-
if (expectation.packageName !== undefined &&
|
|
636
|
-
manifest.package_name !== expectation.packageName) {
|
|
637
|
-
throw new Error(`Expected package manifest package_name to be "${expectation.packageName}"; received "${manifest.package_name ?? "(none)"}"`);
|
|
638
|
-
}
|
|
639
|
-
if (expectation.aliases !== undefined) {
|
|
640
|
-
assertStringSetIncludes(manifest.aliases, expectation.aliases, "aliases");
|
|
641
|
-
}
|
|
642
|
-
if (expectation.resources !== undefined) {
|
|
643
|
-
for (const [kind, expectedPaths] of Object.entries(expectation.resources)) {
|
|
644
|
-
assertStringSetIncludes(manifest.resources[kind], expectedPaths, `pm.${kind}`);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
return manifest;
|
|
648
|
-
}
|
|
649
|
-
/** Assert that an activated extension registration registry contains a command contract with the expected public metadata. */
|
|
650
|
-
export function assertRegisteredCommandContract(registrations, expectation) {
|
|
651
|
-
const expectedCommand = normalizeSdkCommandName(registeredExpectationName(expectation, expectation.command));
|
|
652
|
-
if (expectedCommand.length === 0) {
|
|
653
|
-
throw new Error("Expected command name must be a non-empty string");
|
|
654
|
-
}
|
|
655
|
-
const commandCandidates = registrations.commands.filter((entry) => entry.command === expectedCommand);
|
|
656
|
-
const command = expectation.extensionName
|
|
657
|
-
? commandCandidates.find((entry) => entry.name === expectation.extensionName)
|
|
658
|
-
: commandCandidates[0];
|
|
659
|
-
if (!command) {
|
|
660
|
-
const available = registrations.commands
|
|
661
|
-
.map((entry) => entry.command)
|
|
662
|
-
.sort((left, right) => left.localeCompare(right));
|
|
663
|
-
const extensionSuffix = expectation.extensionName
|
|
664
|
-
? ` from extension "${expectation.extensionName}"`
|
|
665
|
-
: "";
|
|
666
|
-
throw new Error(`Expected extension command "${expectedCommand}"${extensionSuffix} to be registered. Available commands: ${formatAvailable(available)}`);
|
|
667
|
-
}
|
|
668
|
-
if (expectation.action !== undefined &&
|
|
669
|
-
command.action !== expectation.action) {
|
|
670
|
-
throw new Error(`Expected extension command "${expectedCommand}" action "${expectation.action}", received "${command.action}"`);
|
|
671
|
-
}
|
|
672
|
-
if (expectation.arguments !== undefined) {
|
|
673
|
-
const actualArguments = (command.arguments ?? []).map((argument) => argument.name);
|
|
674
|
-
const missingArguments = expectation.arguments.filter((argument) => !actualArguments.includes(argument));
|
|
675
|
-
if (missingArguments.length > 0) {
|
|
676
|
-
throw new Error(`Expected extension command "${expectedCommand}" arguments ${formatAvailable(expectation.arguments)}; missing ${formatAvailable(missingArguments)}; available ${formatAvailable(actualArguments)}`);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
const flags = registrations.flags
|
|
680
|
-
.filter((entry) => entry.target_command === expectedCommand &&
|
|
681
|
-
(expectation.extensionName === undefined ||
|
|
682
|
-
entry.name === expectation.extensionName))
|
|
683
|
-
.flatMap((entry) => entry.flags);
|
|
684
|
-
if (expectation.flags !== undefined) {
|
|
685
|
-
const actualFlagLabels = collectFlagLabels(flags);
|
|
686
|
-
const missingFlags = expectation.flags.filter((flag) => !actualFlagLabels.has(flag));
|
|
687
|
-
if (missingFlags.length > 0) {
|
|
688
|
-
throw new Error(`Expected extension command "${expectedCommand}" flags ${formatAvailable(expectation.flags)}; missing ${formatAvailable(missingFlags)}; available ${formatAvailable([...actualFlagLabels].sort((left, right) => left.localeCompare(right)))}`);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
return { command, flags };
|
|
692
|
-
}
|
|
693
|
-
/** Assert that an activated extension registration registry contains flags injected into an existing command through `api.registerFlags(...)`. */
|
|
694
|
-
export function assertRegisteredFlags(registrations, expectation) {
|
|
695
|
-
const expectedCommand = normalizeSdkCommandName(registeredExpectationName(expectation, expectation.targetCommand));
|
|
696
|
-
if (expectedCommand.length === 0) {
|
|
697
|
-
throw new Error("Expected target command name must be a non-empty string");
|
|
698
|
-
}
|
|
699
|
-
const candidates = registrations.flags.filter((entry) => entry.target_command === expectedCommand);
|
|
700
|
-
let registration;
|
|
701
|
-
if (expectation.extensionName) {
|
|
702
|
-
registration = candidates.find((entry) => entry.name === expectation.extensionName);
|
|
703
|
-
}
|
|
704
|
-
else if (candidates.length === 1) {
|
|
705
|
-
registration = candidates[0];
|
|
706
|
-
}
|
|
707
|
-
else if (candidates.length > 1) {
|
|
708
|
-
const availableExtensions = sortedUnique(candidates.map((entry) => entry.name));
|
|
709
|
-
throw new Error(`Expected flags for target command "${expectedCommand}" matched multiple extensions: ${formatAvailable(availableExtensions)}. Specify extensionName to choose one registration.`);
|
|
710
|
-
}
|
|
711
|
-
if (!registration) {
|
|
712
|
-
const available = sortedUnique(registrations.flags.map((entry) => entry.target_command));
|
|
713
|
-
const availableExtensions = sortedUnique(candidates.map((entry) => entry.name));
|
|
714
|
-
throw new Error(`Expected flags for target command "${expectedCommand}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available flag target commands: ${formatAvailable(available)}; matching extensions: ${formatAvailable(availableExtensions)}`);
|
|
715
|
-
}
|
|
716
|
-
if (expectation.flags !== undefined) {
|
|
717
|
-
const actualFlagLabels = collectFlagLabels(registration.flags);
|
|
718
|
-
const missingFlags = expectation.flags.filter((flag) => !actualFlagLabels.has(flag));
|
|
719
|
-
if (missingFlags.length > 0) {
|
|
720
|
-
throw new Error(`Expected flags for target command "${expectedCommand}" to include ${formatAvailable(expectation.flags)}; missing ${formatAvailable(missingFlags)}; available ${formatAvailable([...actualFlagLabels].sort((left, right) => left.localeCompare(right)))}`);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
return registration;
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* Assert that an activated extension hook registry contains a hook of the
|
|
727
|
-
* expected lifecycle kind (optionally scoped to a specific extension).
|
|
728
|
-
*
|
|
729
|
-
* Hooks are surfaced via `ExtensionActivationResult.hooks`, not the command
|
|
730
|
-
* registration registry, so this helper accepts an `ExtensionHookRegistry`.
|
|
731
|
-
*/
|
|
732
|
-
export function assertRegisteredHook(hooks, expectation) {
|
|
733
|
-
const field = HOOK_KIND_TO_REGISTRY_FIELD[expectation.kind];
|
|
734
|
-
const candidates = (hooks[field] ?? []);
|
|
735
|
-
const hook = expectation.extensionName
|
|
736
|
-
? candidates.find((entry) => entry.name === expectation.extensionName)
|
|
737
|
-
: candidates[0];
|
|
738
|
-
if (!hook) {
|
|
739
|
-
const available = sortedUnique(candidates.map((entry) => entry.name));
|
|
740
|
-
throw new Error(`Expected a "${expectation.kind}" hook${extensionNameSuffix(expectation.extensionName)} to be registered. Available "${expectation.kind}" hooks: ${formatAvailable(available)}`);
|
|
741
|
-
}
|
|
742
|
-
return hook;
|
|
743
|
-
}
|
|
744
|
-
/** Assert that an activated extension registration registry contains a search provider with the expected name (optionally scoped to a specific extension). */
|
|
745
|
-
export function assertRegisteredSearchProvider(registrations, expectation) {
|
|
746
|
-
const expectedProvider = normalizeSdkIdentifier(registeredExpectationName(expectation, expectation.provider));
|
|
747
|
-
if (expectedProvider.length === 0) {
|
|
748
|
-
throw new Error("Expected search provider name must be a non-empty string");
|
|
749
|
-
}
|
|
750
|
-
const candidates = registrations.search_providers.filter((entry) => normalizeSdkIdentifier(entry.definition.name) === expectedProvider);
|
|
751
|
-
const provider = expectation.extensionName
|
|
752
|
-
? candidates.find((entry) => entry.name === expectation.extensionName)
|
|
753
|
-
: candidates[0];
|
|
754
|
-
if (!provider) {
|
|
755
|
-
const available = sortedUnique(registrations.search_providers.map((entry) => entry.definition.name));
|
|
756
|
-
throw new Error(`Expected search provider "${expectedProvider}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available search providers: ${formatAvailable(available)}`);
|
|
757
|
-
}
|
|
758
|
-
return provider;
|
|
759
|
-
}
|
|
760
|
-
/** Assert that an activated extension registration registry contains a vector store adapter with the expected name (optionally scoped to a specific extension). */
|
|
761
|
-
export function assertRegisteredVectorStoreAdapter(registrations, expectation) {
|
|
762
|
-
const expectedAdapter = normalizeSdkIdentifier(registeredExpectationName(expectation, expectation.adapter));
|
|
763
|
-
if (expectedAdapter.length === 0) {
|
|
764
|
-
throw new Error("Expected vector store adapter name must be a non-empty string");
|
|
765
|
-
}
|
|
766
|
-
const candidates = registrations.vector_store_adapters.filter((entry) => normalizeSdkIdentifier(entry.definition.name) === expectedAdapter);
|
|
767
|
-
const adapter = expectation.extensionName
|
|
768
|
-
? candidates.find((entry) => entry.name === expectation.extensionName)
|
|
769
|
-
: candidates[0];
|
|
770
|
-
if (!adapter) {
|
|
771
|
-
const available = sortedUnique(registrations.vector_store_adapters.map((entry) => entry.definition.name));
|
|
772
|
-
throw new Error(`Expected vector store adapter "${expectedAdapter}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available vector store adapters: ${formatAvailable(available)}`);
|
|
773
|
-
}
|
|
774
|
-
return adapter;
|
|
775
|
-
}
|
|
776
|
-
/** Assert that an activated extension registration registry contains an importer for the expected format/name (optionally scoped to a specific extension). */
|
|
777
|
-
export function assertRegisteredImporter(registrations, expectation) {
|
|
778
|
-
const expectedImporter = normalizeSdkIdentifier(registeredExpectationName(expectation, expectation.importer));
|
|
779
|
-
if (expectedImporter.length === 0) {
|
|
780
|
-
throw new Error("Expected importer name must be a non-empty string");
|
|
781
|
-
}
|
|
782
|
-
const candidates = registrations.importers.filter((entry) => normalizeSdkIdentifier(entry.importer) === expectedImporter);
|
|
783
|
-
const importer = expectation.extensionName
|
|
784
|
-
? candidates.find((entry) => entry.name === expectation.extensionName)
|
|
785
|
-
: candidates[0];
|
|
786
|
-
if (!importer) {
|
|
787
|
-
const available = sortedUnique(registrations.importers.map((entry) => entry.importer));
|
|
788
|
-
throw new Error(`Expected importer "${expectedImporter}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available importers: ${formatAvailable(available)}`);
|
|
789
|
-
}
|
|
790
|
-
return importer;
|
|
791
|
-
}
|
|
792
|
-
/** Assert that an activated extension registration registry contains an exporter for the expected format/name (optionally scoped to a specific extension). */
|
|
793
|
-
export function assertRegisteredExporter(registrations, expectation) {
|
|
794
|
-
const expectedExporter = normalizeSdkIdentifier(registeredExpectationName(expectation, expectation.exporter));
|
|
795
|
-
if (expectedExporter.length === 0) {
|
|
796
|
-
throw new Error("Expected exporter name must be a non-empty string");
|
|
797
|
-
}
|
|
798
|
-
const candidates = registrations.exporters.filter((entry) => normalizeSdkIdentifier(entry.exporter) === expectedExporter);
|
|
799
|
-
const exporter = expectation.extensionName
|
|
800
|
-
? candidates.find((entry) => entry.name === expectation.extensionName)
|
|
801
|
-
: candidates[0];
|
|
802
|
-
if (!exporter) {
|
|
803
|
-
const available = sortedUnique(registrations.exporters.map((entry) => entry.exporter));
|
|
804
|
-
throw new Error(`Expected exporter "${expectedExporter}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available exporters: ${formatAvailable(available)}`);
|
|
805
|
-
}
|
|
806
|
-
return exporter;
|
|
807
|
-
}
|
|
808
|
-
/** Assert that an activated extension registration registry contains a custom item field definition (optionally scoped to a specific extension). */
|
|
809
|
-
export function assertRegisteredItemField(registrations, expectation) {
|
|
810
|
-
const expectedField = normalizeSdkIdentifier(registeredExpectationName(expectation, expectation.field));
|
|
811
|
-
if (expectedField.length === 0) {
|
|
812
|
-
throw new Error("Expected item field name must be a non-empty string");
|
|
813
|
-
}
|
|
814
|
-
const candidates = registrations.item_fields
|
|
815
|
-
.filter((entry) => expectation.extensionName === undefined ||
|
|
816
|
-
entry.name === expectation.extensionName)
|
|
817
|
-
.map((registration) => ({
|
|
818
|
-
registration,
|
|
819
|
-
field: registration.fields.find((field) => normalizeSdkIdentifier(field.name) === expectedField),
|
|
820
|
-
}))
|
|
821
|
-
.filter((entry) => entry.field !== undefined);
|
|
822
|
-
const match = candidates.find((entry) => expectation.type === undefined || entry.field.type === expectation.type);
|
|
823
|
-
if (!match) {
|
|
824
|
-
const available = sortedUnique(registrations.item_fields.flatMap((entry) => entry.fields.map((field) => `${field.name}:${field.type}`)));
|
|
825
|
-
throw new Error(`Expected item field "${expectedField}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available item fields: ${formatAvailable(available)}`);
|
|
826
|
-
}
|
|
827
|
-
return match;
|
|
828
|
-
}
|
|
829
|
-
/** Assert that an activated extension registration registry contains a custom item type definition (optionally scoped to a specific extension). */
|
|
830
|
-
export function assertRegisteredItemType(registrations, expectation) {
|
|
831
|
-
const expectedType = normalizeSdkIdentifier(registeredExpectationName(expectation, expectation.itemType));
|
|
832
|
-
if (expectedType.length === 0) {
|
|
833
|
-
throw new Error("Expected item type name must be a non-empty string");
|
|
834
|
-
}
|
|
835
|
-
const candidates = registrations.item_types
|
|
836
|
-
.filter((entry) => expectation.extensionName === undefined ||
|
|
837
|
-
entry.name === expectation.extensionName)
|
|
838
|
-
.map((registration) => ({
|
|
839
|
-
registration,
|
|
840
|
-
itemType: registration.types.find((itemType) => normalizeSdkIdentifier(itemType.name) === expectedType),
|
|
841
|
-
}))
|
|
842
|
-
.filter((entry) => entry.itemType !== undefined);
|
|
843
|
-
const match = candidates.find((entry) => expectation.folder === undefined ||
|
|
844
|
-
entry.itemType.folder === expectation.folder);
|
|
845
|
-
if (!match) {
|
|
846
|
-
const available = sortedUnique(registrations.item_types.flatMap((entry) => entry.types.map((itemType) => `${itemType.name}:${itemType.folder}`)));
|
|
847
|
-
throw new Error(`Expected item type "${expectedType}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available item types: ${formatAvailable(available)}`);
|
|
848
|
-
}
|
|
849
|
-
return match;
|
|
850
|
-
}
|
|
851
|
-
/**
|
|
852
|
-
* Assert that an activated extension registration registry contains a project
|
|
853
|
-
* profile registered via `api.registerProfile(profile)` (optionally scoped to a
|
|
854
|
-
* specific extension).
|
|
855
|
-
*
|
|
856
|
-
* This is the test-time counterpart to the declarative `defineProjectProfile`
|
|
857
|
-
* authoring helper and the `pm profile list/show/apply` runtime surface: it
|
|
858
|
-
* proves a package's archetype reached the registry so a downstream consumer can
|
|
859
|
-
* resolve and apply it by name. Like the other declarative-surface assertions
|
|
860
|
-
* (item types, item fields), a profile is verified by presence — it is staged,
|
|
861
|
-
* not executed — so there is no `runRegisteredProfileForTest` counterpart.
|
|
862
|
-
*/
|
|
863
|
-
export function assertRegisteredProfile(registrations, expectation) {
|
|
864
|
-
const expectedProfile = normalizeSdkProfileMatchKey(registeredExpectationName(expectation, expectation.profile));
|
|
865
|
-
if (expectedProfile.length === 0) {
|
|
866
|
-
throw new Error("Expected profile name must be a non-empty string");
|
|
867
|
-
}
|
|
868
|
-
const match = registrations.profiles
|
|
869
|
-
.filter((entry) => expectation.extensionName === undefined ||
|
|
870
|
-
entry.name === expectation.extensionName)
|
|
871
|
-
.find((entry) => normalizeSdkProfileMatchKey(entry.profile.name) === expectedProfile);
|
|
872
|
-
if (!match) {
|
|
873
|
-
const available = sortedUnique(registrations.profiles.map((entry) => entry.profile.name));
|
|
874
|
-
throw new Error(`Expected profile "${expectedProfile}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available profiles: ${formatAvailable(available)}`);
|
|
875
|
-
}
|
|
876
|
-
return { registration: match, profile: match.profile };
|
|
877
|
-
}
|
|
878
|
-
/**
|
|
879
|
-
* Assert that an activated extension command registry contains a command
|
|
880
|
-
* override registered via `api.registerCommand(command, override)` (optionally
|
|
881
|
-
* scoped to a specific extension).
|
|
882
|
-
*
|
|
883
|
-
* Command overrides are surfaced via `ExtensionActivationResult.commands`, not
|
|
884
|
-
* the command registration registry, so this helper accepts an
|
|
885
|
-
* `ExtensionCommandRegistry`.
|
|
886
|
-
*/
|
|
887
|
-
export function assertRegisteredCommandOverride(commands, expectation) {
|
|
888
|
-
const expectedCommand = normalizeSdkCommandName(registeredExpectationName(expectation, expectation.command));
|
|
889
|
-
if (expectedCommand.length === 0) {
|
|
890
|
-
throw new Error("Expected command name must be a non-empty string");
|
|
891
|
-
}
|
|
892
|
-
const candidates = commands.overrides.filter((entry) => entry.command === expectedCommand);
|
|
893
|
-
const override = expectation.extensionName
|
|
894
|
-
? candidates.find((entry) => entry.name === expectation.extensionName)
|
|
895
|
-
: candidates[0];
|
|
896
|
-
if (!override) {
|
|
897
|
-
const available = sortedUnique(commands.overrides.map((entry) => entry.command));
|
|
898
|
-
throw new Error(`Expected command override "${expectedCommand}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available command overrides: ${formatAvailable(available)}`);
|
|
899
|
-
}
|
|
900
|
-
return override;
|
|
901
|
-
}
|
|
902
|
-
/**
|
|
903
|
-
* Assert that an activated extension parser registry contains a parser override
|
|
904
|
-
* registered via `api.registerParser(command, override)` (optionally scoped to
|
|
905
|
-
* a specific extension).
|
|
906
|
-
*
|
|
907
|
-
* Parser overrides are surfaced via `ExtensionActivationResult.parsers`, not the
|
|
908
|
-
* command registration registry, so this helper accepts an
|
|
909
|
-
* `ExtensionParserRegistry`.
|
|
910
|
-
*/
|
|
911
|
-
export function assertRegisteredParserOverride(parsers, expectation) {
|
|
912
|
-
const expectedCommand = normalizeSdkCommandName(registeredExpectationName(expectation, expectation.command));
|
|
913
|
-
if (expectedCommand.length === 0) {
|
|
914
|
-
throw new Error("Expected command name must be a non-empty string");
|
|
915
|
-
}
|
|
916
|
-
const candidates = parsers.overrides.filter((entry) => entry.command === expectedCommand);
|
|
917
|
-
const override = expectation.extensionName
|
|
918
|
-
? candidates.find((entry) => entry.name === expectation.extensionName)
|
|
919
|
-
: candidates[0];
|
|
920
|
-
if (!override) {
|
|
921
|
-
const available = sortedUnique(parsers.overrides.map((entry) => entry.command));
|
|
922
|
-
throw new Error(`Expected parser override "${expectedCommand}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available parser overrides: ${formatAvailable(available)}`);
|
|
923
|
-
}
|
|
924
|
-
return override;
|
|
925
|
-
}
|
|
926
|
-
/**
|
|
927
|
-
* Assert that an activated extension preflight registry contains a preflight
|
|
928
|
-
* override registered via `api.registerPreflight(override)` (optionally scoped
|
|
929
|
-
* to a specific extension).
|
|
930
|
-
*
|
|
931
|
-
* Preflight overrides are global (they run for every command and carry no
|
|
932
|
-
* `command` field), so the expectation only accepts an optional
|
|
933
|
-
* `extensionName`. Preflight overrides are surfaced via
|
|
934
|
-
* `ExtensionActivationResult.preflight`.
|
|
935
|
-
*/
|
|
936
|
-
export function assertRegisteredPreflightOverride(preflight, expectation = {}) {
|
|
937
|
-
const override = expectation.extensionName
|
|
938
|
-
? preflight.overrides.find((entry) => entry.name === expectation.extensionName)
|
|
939
|
-
: preflight.overrides[0];
|
|
940
|
-
if (!override) {
|
|
941
|
-
const available = sortedUnique(preflight.overrides.map((entry) => entry.name));
|
|
942
|
-
throw new Error(`Expected a preflight override${extensionNameSuffix(expectation.extensionName)} to be registered. Available preflight overrides: ${formatAvailable(available)}`);
|
|
943
|
-
}
|
|
944
|
-
return override;
|
|
945
|
-
}
|
|
946
|
-
/**
|
|
947
|
-
* Assert that an activated extension renderer registry contains a renderer
|
|
948
|
-
* override registered via `api.registerRenderer(format, renderer)` for the
|
|
949
|
-
* expected output format (optionally scoped to a specific extension).
|
|
950
|
-
*
|
|
951
|
-
* Renderer overrides are surfaced via `ExtensionActivationResult.renderers`.
|
|
952
|
-
*/
|
|
953
|
-
export function assertRegisteredRendererOverride(renderers, expectation) {
|
|
954
|
-
const expectedFormat = normalizeSdkIdentifier(registeredExpectationName(expectation, expectation.format));
|
|
955
|
-
if (expectedFormat.length === 0) {
|
|
956
|
-
throw new Error("Expected renderer format must be a non-empty string");
|
|
957
|
-
}
|
|
958
|
-
const candidates = renderers.overrides.filter((entry) => entry.format === expectedFormat);
|
|
959
|
-
const override = expectation.extensionName
|
|
960
|
-
? candidates.find((entry) => entry.name === expectation.extensionName)
|
|
961
|
-
: candidates[0];
|
|
962
|
-
if (!override) {
|
|
963
|
-
const available = sortedUnique(renderers.overrides.map((entry) => entry.format));
|
|
964
|
-
throw new Error(`Expected renderer override "${expectedFormat}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available renderer overrides: ${formatAvailable(available)}`);
|
|
965
|
-
}
|
|
966
|
-
return override;
|
|
967
|
-
}
|
|
968
|
-
/**
|
|
969
|
-
* Assert that an activated extension service registry contains a service override
|
|
970
|
-
* registered via `api.registerService(service, override)` for the expected
|
|
971
|
-
* service name (optionally scoped to a specific extension).
|
|
972
|
-
*
|
|
973
|
-
* Service overrides are surfaced via `ExtensionActivationResult.services`, not the
|
|
974
|
-
* command registration registry, so this helper accepts an
|
|
975
|
-
* `ExtensionServiceRegistry`.
|
|
976
|
-
*/
|
|
977
|
-
export function assertRegisteredServiceOverride(services, expectation) {
|
|
978
|
-
const expectedService = normalizeSdkIdentifier(registeredExpectationName(expectation, expectation.service));
|
|
979
|
-
if (expectedService.length === 0) {
|
|
980
|
-
throw new Error("Expected service name must be a non-empty string");
|
|
981
|
-
}
|
|
982
|
-
const candidates = services.overrides.filter((entry) => normalizeSdkIdentifier(entry.service) === expectedService);
|
|
983
|
-
const override = expectation.extensionName
|
|
984
|
-
? candidates.find((entry) => entry.name === expectation.extensionName)
|
|
985
|
-
: candidates[0];
|
|
986
|
-
if (!override) {
|
|
987
|
-
const available = sortedUnique(services.overrides.map((entry) => entry.service));
|
|
988
|
-
throw new Error(`Expected service override "${expectedService}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available service overrides: ${formatAvailable(available)}`);
|
|
989
|
-
}
|
|
990
|
-
return override;
|
|
991
|
-
}
|
|
992
|
-
/**
|
|
993
|
-
* Assert that an activated extension registration registry contains a schema
|
|
994
|
-
* migration registered via `api.registerMigration(definition)` with the expected
|
|
995
|
-
* id (optionally scoped to a specific extension and asserting the `mandatory`
|
|
996
|
-
* governance flag, where an unset flag is treated as non-mandatory).
|
|
997
|
-
*
|
|
998
|
-
* Migrations are surfaced via `ExtensionActivationResult.registrations.migrations`.
|
|
999
|
-
*/
|
|
1000
|
-
export function assertRegisteredMigration(registrations, expectation) {
|
|
1001
|
-
const expectedMigration = normalizeSdkIdentifier(registeredExpectationName(expectation, expectation.migration));
|
|
1002
|
-
if (expectedMigration.length === 0) {
|
|
1003
|
-
throw new Error("Expected migration id must be a non-empty string");
|
|
1004
|
-
}
|
|
1005
|
-
const candidates = registrations.migrations.filter((entry) => (expectation.extensionName === undefined ||
|
|
1006
|
-
entry.name === expectation.extensionName) &&
|
|
1007
|
-
typeof entry.definition.id === "string" &&
|
|
1008
|
-
normalizeSdkIdentifier(entry.definition.id) === expectedMigration);
|
|
1009
|
-
const match = candidates.find((entry) => expectation.mandatory === undefined ||
|
|
1010
|
-
(entry.definition.mandatory ?? false) === expectation.mandatory);
|
|
1011
|
-
if (match) {
|
|
1012
|
-
return match;
|
|
1013
|
-
}
|
|
1014
|
-
// The id (and any extension scope) matched but the `mandatory` flag did not:
|
|
1015
|
-
// report the mismatch directly rather than the misleading "not registered"
|
|
1016
|
-
// listing. Reaching here with a candidate implies `expectation.mandatory` was
|
|
1017
|
-
// set, since an unset flag matches any candidate above.
|
|
1018
|
-
const idMatch = candidates[0];
|
|
1019
|
-
if (idMatch !== undefined) {
|
|
1020
|
-
throw new Error(`Expected migration "${expectedMigration}"${extensionNameSuffix(expectation.extensionName)} to have ` +
|
|
1021
|
-
`mandatory=${expectation.mandatory}, but it is mandatory=${idMatch.definition.mandatory ?? false}.`);
|
|
1022
|
-
}
|
|
1023
|
-
const available = sortedUnique(registrations.migrations.map((entry) => {
|
|
1024
|
-
const id = typeof entry.definition.id === "string" &&
|
|
1025
|
-
entry.definition.id.trim().length > 0
|
|
1026
|
-
? entry.definition.id.trim()
|
|
1027
|
-
: "(unnamed)";
|
|
1028
|
-
return `${id}:${entry.definition.mandatory === true}`;
|
|
1029
|
-
}));
|
|
1030
|
-
throw new Error(`Expected migration "${expectedMigration}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available migrations: ${formatAvailable(available)}`);
|
|
1031
|
-
}
|
|
1032
|
-
/**
|
|
1033
|
-
* Assert that an activated extension declares no capability it never uses
|
|
1034
|
-
* (least privilege). Pass the same capabilities as `manifest.capabilities` via
|
|
1035
|
-
* `expectation.declared`; the helper computes what the extension actually
|
|
1036
|
-
* registered against and throws when any declared capability is unused.
|
|
1037
|
-
*
|
|
1038
|
-
* This is the package-test counterpart of the advisory
|
|
1039
|
-
* `extension_capability_unused` warning `pm package doctor` emits, letting an
|
|
1040
|
-
* author catch an over-broad manifest at `npm test` time. Returns the
|
|
1041
|
-
* declared/used/unused breakdown for further assertions.
|
|
1042
|
-
*/
|
|
1043
|
-
export function assertExtensionCapabilityUsage(activation, expectation) {
|
|
1044
|
-
const toKnownCapabilitySet = (capabilities, field) => {
|
|
1045
|
-
const known = new Set();
|
|
1046
|
-
for (const capability of capabilities) {
|
|
1047
|
-
const normalized = normalizeKnownExtensionCapability(capability);
|
|
1048
|
-
if (normalized === null) {
|
|
1049
|
-
throw new Error(`Expected ${field} capability "${capability}" to be a known extension capability. ` +
|
|
1050
|
-
"Use canonical capability names (see manifest.capabilities).");
|
|
1051
|
-
}
|
|
1052
|
-
known.add(normalized);
|
|
1053
|
-
}
|
|
1054
|
-
return known;
|
|
1055
|
-
};
|
|
1056
|
-
const declared = [
|
|
1057
|
-
...toKnownCapabilitySet(expectation.declared, "declared"),
|
|
1058
|
-
].sort((left, right) => left.localeCompare(right));
|
|
1059
|
-
const allowUnused = toKnownCapabilitySet(expectation.allowUnused ?? [], "allowUnused");
|
|
1060
|
-
const used = collectUsedExtensionCapabilities(activation, {
|
|
1061
|
-
extensionName: expectation.extensionName,
|
|
1062
|
-
});
|
|
1063
|
-
const usedSet = new Set(used);
|
|
1064
|
-
const unused = declared.filter((capability) => !usedSet.has(capability) && !allowUnused.has(capability));
|
|
1065
|
-
if (unused.length > 0) {
|
|
1066
|
-
const scopeSuffix = extensionNameSuffix(expectation.extensionName);
|
|
1067
|
-
throw new Error(`Expected every declared capability${scopeSuffix} to be exercised, but [${unused.join(", ")}] ${unused.length === 1 ? "is" : "are"} declared yet never registered against. ` +
|
|
1068
|
-
`Remove ${unused.length === 1 ? "it" : "them"} from manifest.capabilities for least privilege, ` +
|
|
1069
|
-
"or pass allowUnused for capabilities registered only behind a runtime flag.");
|
|
1070
|
-
}
|
|
1071
|
-
return { declared, used, unused };
|
|
1072
|
-
}
|
|
1073
|
-
/**
|
|
1074
|
-
* Preflight a declarative {@link ExtensionBlueprint} in a test, throwing if it has
|
|
1075
|
-
* any `error`-severity issue (today: capability the blueprint exercises but the
|
|
1076
|
-
* declared set omits, which would fail activation with `extension_capability_missing`).
|
|
1077
|
-
*
|
|
1078
|
-
* The throwing counterpart to {@link lintExtensionBlueprint} and the `assert*`
|
|
1079
|
-
* family member for declarative authoring: drop `assertExtensionBlueprint(blueprint)`
|
|
1080
|
-
* into a package's `node:test`/Vitest suite to fail CI before the blueprint is
|
|
1081
|
-
* ever activated. The full {@link ExtensionBlueprintLintResult} is returned on
|
|
1082
|
-
* success so a test can still inspect advisory warnings (unused capability,
|
|
1083
|
-
* duplicate command, empty surface) without failing on them.
|
|
1084
|
-
*/
|
|
1085
|
-
export function assertExtensionBlueprint(blueprint, options = {}) {
|
|
1086
|
-
const result = lintExtensionBlueprint(blueprint, options);
|
|
1087
|
-
if (!result.ok) {
|
|
1088
|
-
const errors = result.findings.filter((finding) => finding.severity === "error");
|
|
1089
|
-
throw new Error(`Extension blueprint failed preflight with ${errors.length} ${errors.length === 1 ? "error" : "errors"}:\n` +
|
|
1090
|
-
errors
|
|
1091
|
-
.map((finding) => ` - [${finding.code}] ${finding.message}`)
|
|
1092
|
-
.join("\n"));
|
|
1093
|
-
}
|
|
1094
|
-
return result;
|
|
1095
|
-
}
|
|
1096
|
-
/**
|
|
1097
|
-
* Assert a {@link ProjectProfileDefinition} is internally consistent in a test,
|
|
1098
|
-
* throwing on any `error`-severity finding (and on `warning`-severity findings
|
|
1099
|
-
* too when `strict` is set). The author-time counterpart to `defineProjectProfile`
|
|
1100
|
-
* and the project-profile analogue of {@link assertExtensionBlueprint}: drop
|
|
1101
|
-
* `assertProjectProfile(myProfile)` into a package's `node:test`/Vitest suite to
|
|
1102
|
-
* fail CI before the profile is ever registered via `api.registerProfile` or
|
|
1103
|
-
* applied via `pm profile apply`. The full {@link ProjectProfileLintReport} is
|
|
1104
|
-
* returned on success so a test can still inspect advisory warnings without
|
|
1105
|
-
* failing on them.
|
|
1106
|
-
*/
|
|
1107
|
-
export function assertProjectProfile(profile, options = {}) {
|
|
1108
|
-
const report = lintProjectProfile(profile);
|
|
1109
|
-
const blocking = report.findings.filter((finding) => finding.severity === "error" ||
|
|
1110
|
-
(options.strict === true && finding.severity === "warning"));
|
|
1111
|
-
if (blocking.length > 0) {
|
|
1112
|
-
throw new Error(`Project profile "${report.profile}" failed lint with ${blocking.length} ${blocking.length === 1 ? "issue" : "issues"}:\n` +
|
|
1113
|
-
blocking
|
|
1114
|
-
.map((finding) => ` - ${finding.severity} [${finding.code}] ${finding.message}`)
|
|
1115
|
-
.join("\n"));
|
|
1116
|
-
}
|
|
1117
|
-
return report;
|
|
1118
|
-
}
|
|
1119
|
-
/**
|
|
1120
|
-
* Strictly assert a manifest's `capabilities` equal the least-privilege set a
|
|
1121
|
-
* declarative {@link ExtensionBlueprint} exercises, throwing on any drift in
|
|
1122
|
-
* either direction.
|
|
1123
|
-
*
|
|
1124
|
-
* This is the strict bookend to the lenient {@link assertExtensionBlueprint}:
|
|
1125
|
-
* where that helper fails only on an *undeclared* capability (the `error`-severity
|
|
1126
|
-
* `extension_capability_missing` activation crash) and merely *warns* on an unused
|
|
1127
|
-
* one, this assertion fails on **both** — an undeclared capability (`missing`) and
|
|
1128
|
-
* a declared-but-unused one (`unused`) — so a hand-maintained `manifest.json` stays
|
|
1129
|
-
* exactly the set the blueprint requires. Drop it into a package's
|
|
1130
|
-
* `node:test`/Vitest suite to guard against capability drift in CI, the natural
|
|
1131
|
-
* companion to {@link ../sdk/compose.js#synthesizeExtensionManifest} (assert what
|
|
1132
|
-
* you would otherwise generate). Only `capabilities` are reconciled because that is
|
|
1133
|
-
* the one manifest field a blueprint determines.
|
|
1134
|
-
*
|
|
1135
|
-
* The reconciliation is returned on success so a test can still inspect advisory
|
|
1136
|
-
* warnings (duplicate command, empty surface) without failing on them.
|
|
1137
|
-
*/
|
|
1138
|
-
export function assertExtensionManifestMatchesBlueprint(manifest, blueprint) {
|
|
1139
|
-
// Coerce a malformed/absent capabilities field to an explicit empty declared set
|
|
1140
|
-
// so an untyped `.js` caller gets a deterministic `missing` list rather than the
|
|
1141
|
-
// lint silently falling back to the blueprint's in-module manifest mirror.
|
|
1142
|
-
const declaredCapabilities = Array.isArray(manifest.capabilities)
|
|
1143
|
-
? manifest.capabilities
|
|
1144
|
-
: [];
|
|
1145
|
-
const result = lintExtensionBlueprint(blueprint, { declaredCapabilities });
|
|
1146
|
-
// `declared` is guaranteed non-null here: we always hand lint an array, so it
|
|
1147
|
-
// never takes its "no declared set" branch. The cast drops the unreachable null.
|
|
1148
|
-
const declared = result.declared;
|
|
1149
|
-
const declaredSet = new Set(declared);
|
|
1150
|
-
const usedSet = new Set(result.used);
|
|
1151
|
-
const missing = result.used.filter((capability) => !declaredSet.has(capability));
|
|
1152
|
-
const unused = declared.filter((capability) => !usedSet.has(capability));
|
|
1153
|
-
if (missing.length > 0 || unused.length > 0) {
|
|
1154
|
-
const parts = [];
|
|
1155
|
-
if (missing.length > 0) {
|
|
1156
|
-
parts.push(`missing [${missing.join(", ")}] (the blueprint registers ${missing.length === 1 ? "a surface" : "surfaces"} requiring ${missing.length === 1 ? "it" : "them"}; activation throws extension_capability_missing)`);
|
|
1157
|
-
}
|
|
1158
|
-
if (unused.length > 0) {
|
|
1159
|
-
parts.push(`unused [${unused.join(", ")}] (declared but no surface exercises ${unused.length === 1 ? "it" : "them"}; drop for least privilege)`);
|
|
1160
|
-
}
|
|
1161
|
-
throw new Error(`Manifest capabilities do not match the blueprint: ${parts.join("; ")}. ` +
|
|
1162
|
-
"Set capabilities to synthesizeExtensionManifest(blueprint, identity).capabilities to stay in sync.");
|
|
1163
|
-
}
|
|
1164
|
-
return {
|
|
1165
|
-
used: result.used,
|
|
1166
|
-
declared,
|
|
1167
|
-
missing,
|
|
1168
|
-
unused,
|
|
1169
|
-
findings: result.findings,
|
|
1170
|
-
};
|
|
1171
|
-
}
|
|
1172
|
-
/**
|
|
1173
|
-
* Assert an extension manifest's declared version bounds permit it to load on a
|
|
1174
|
-
* target pm CLI version, throwing when a bound blocks the load.
|
|
1175
|
-
*
|
|
1176
|
-
* This is the throwing CI/test counterpart to
|
|
1177
|
-
* {@link ../sdk/compose.js#checkExtensionManifestCompatibility}: it runs the same
|
|
1178
|
-
* author-time version-bound analysis and fails on any blocking incompatibility — a
|
|
1179
|
-
* malformed bound (`*_invalid`), a `pm_min_version` the target is below
|
|
1180
|
-
* (`pm_min_version_unmet`), or a `block`-mode `pm_max_version` the target exceeds
|
|
1181
|
-
* (`pm_max_version_exceeded`). Advisory `warning` findings (`*_unchecked`, or a
|
|
1182
|
-
* `warn`-mode `pm_max_version_exceeded_warn`) never throw, because the loader
|
|
1183
|
-
* would still load the extension. Pin it to the pm version a package commits to
|
|
1184
|
-
* supporting so a too-tight or malformed bound fails the package's own suite, not
|
|
1185
|
-
* a user's install. The full compatibility result is returned on success so a test
|
|
1186
|
-
* can still inspect advisory warnings.
|
|
1187
|
-
*/
|
|
1188
|
-
export function assertExtensionManifestCompatible(manifest, target) {
|
|
1189
|
-
const result = checkExtensionManifestCompatibility(manifest, target);
|
|
1190
|
-
const blocking = result.findings.filter((finding) => finding.severity === "error");
|
|
1191
|
-
if (blocking.length > 0) {
|
|
1192
|
-
throw new Error(`Extension manifest is not compatible with pm ${result.pmVersion}: ${blocking
|
|
1193
|
-
.map((finding) => finding.message)
|
|
1194
|
-
.join("; ")}`);
|
|
1195
|
-
}
|
|
1196
|
-
return result;
|
|
1197
|
-
}
|
|
1198
|
-
/**
|
|
1199
|
-
* Preflight a declarative {@link ExtensionBlueprint} through every author-time
|
|
1200
|
-
* check in one test, throwing if any stage produced an `error`-severity finding.
|
|
1201
|
-
*
|
|
1202
|
-
* This is the throwing CI/test bookend over
|
|
1203
|
-
* {@link ../sdk/compose.js#preflightExtension} — the single guard that replaces
|
|
1204
|
-
* chaining {@link assertExtensionBlueprint}, {@link assertExtensionManifestMatchesBlueprint},
|
|
1205
|
-
* and {@link assertExtensionManifestCompatible} in a package's
|
|
1206
|
-
* `node:test`/Vitest suite. It runs the same consolidated analysis (always lints
|
|
1207
|
-
* the blueprint; synthesizes the manifest when `options.identity` is given; checks
|
|
1208
|
-
* version bounds when `options.target` is given) and throws one error listing every
|
|
1209
|
-
* blocking finding tagged by its `source:code`. Advisory `warning` findings (an
|
|
1210
|
-
* unused capability, a duplicate command, an `*_unchecked` bound) never throw,
|
|
1211
|
-
* matching the underlying stages. The full {@link ExtensionPreflightReport} is
|
|
1212
|
-
* returned on success so a test can still inspect those warnings, the synthesized
|
|
1213
|
-
* manifest, and the derived capability set without failing on them.
|
|
1214
|
-
*/
|
|
1215
|
-
export function assertExtensionPreflight(blueprint, options = {}) {
|
|
1216
|
-
const report = preflightExtension(blueprint, options);
|
|
1217
|
-
if (!report.ok) {
|
|
1218
|
-
const errors = report.findings.filter((finding) => finding.severity === "error");
|
|
1219
|
-
throw new Error(`Extension failed preflight with ${errors.length} ${errors.length === 1 ? "error" : "errors"}:\n` +
|
|
1220
|
-
errors
|
|
1221
|
-
.map((finding) => ` - [${finding.source}:${finding.code}] ${finding.message}`)
|
|
1222
|
-
.join("\n"));
|
|
1223
|
-
}
|
|
1224
|
-
return report;
|
|
1225
|
-
}
|
|
1226
|
-
/** Reject obviously wrong harness input (e.g. an options object passed as the module) instead of silently producing an empty activation. Mirrors the loader's activatable-extension shapes: an `activate` function on the module or on its default export. */
|
|
1227
|
-
function assertTestModuleHasActivateExport(module) {
|
|
1228
|
-
if (resolveActivatablePropertyRecord(module)) {
|
|
1229
|
-
return;
|
|
1230
|
-
}
|
|
1231
|
-
throw new Error("createExtensionTestHarness received a module with no activate export. Pass the extension module (with an activate(api) function on the module or its default export) as the first argument, and options second.");
|
|
1232
|
-
}
|
|
1233
|
-
/**
|
|
1234
|
-
* Activate one in-memory extension module and return a fluent
|
|
1235
|
-
* {@link ExtensionTestHarness} that binds every SDK testing helper to the right
|
|
1236
|
-
* sub-registry of the resulting activation.
|
|
1237
|
-
*
|
|
1238
|
-
* This is the ergonomic capstone over the standalone helpers: instead of
|
|
1239
|
-
* `activateExtensionForTest(module)` followed by repeatedly threading
|
|
1240
|
-
* `activation.registrations` / `activation.commands` / `activation.hooks` into
|
|
1241
|
-
* each `assertRegistered*` / `runRegistered*` call, an author writes
|
|
1242
|
-
* `const ext = await createExtensionTestHarness(module, { capabilities });` and
|
|
1243
|
-
* then `ext.assertCommandContract(...)`, `await ext.runCommand(...)`,
|
|
1244
|
-
* `await ext.deactivate()` — never needing to know pm's internal registry layout.
|
|
1245
|
-
*
|
|
1246
|
-
* `name` and `layer` are resolved exactly as {@link activateExtensionForTest}
|
|
1247
|
-
* resolves them, and captured so {@link ExtensionTestHarness.deactivate} forwards
|
|
1248
|
-
* a matching skip-key. Activation runs pm's real validation/activation engine, so
|
|
1249
|
-
* every bound helper exercises real wiring and dispatch — not mocks.
|
|
1250
|
-
*
|
|
1251
|
-
* Fails fast: if the module does not activate cleanly (e.g. a registration is
|
|
1252
|
-
* dropped because the manifest omits the required capability), this throws a
|
|
1253
|
-
* descriptive error listing each failure instead of returning a harness whose
|
|
1254
|
-
* registries are empty — which would otherwise surface later as the confusing
|
|
1255
|
-
* `available: (none)` assertion error this helper exists to prevent. Tests that
|
|
1256
|
-
* deliberately exercise a *failed* activation should call
|
|
1257
|
-
* {@link activateExtensionForTest} directly and inspect `activation.failed`.
|
|
1258
|
-
*/
|
|
1259
|
-
export async function createExtensionTestHarness(module, options = {}) {
|
|
1260
|
-
assertTestModuleHasActivateExport(module);
|
|
1261
|
-
const manifest = readTestExtensionManifest(module);
|
|
1262
|
-
const name = resolveTestExtensionName(manifest, options.name);
|
|
1263
|
-
const layer = options.layer ?? "project";
|
|
1264
|
-
const activation = await activateExtensionForTest(module, options);
|
|
1265
|
-
if (activation.failed.length > 0) {
|
|
1266
|
-
const detail = activation.failed
|
|
1267
|
-
.map((failure) => {
|
|
1268
|
-
const missingCapability = failure.trace?.missing_capability;
|
|
1269
|
-
const reason = missingCapability
|
|
1270
|
-
? `${failure.error}; missing capability "${missingCapability}"`
|
|
1271
|
-
: failure.error;
|
|
1272
|
-
return `${failure.layer}:${failure.name} (${reason})`;
|
|
1273
|
-
})
|
|
1274
|
-
.join(", ");
|
|
1275
|
-
throw new Error(`createExtensionTestHarness could not activate the extension cleanly: ${detail}. ` +
|
|
1276
|
-
"Declare the required capability in the manifest (or pass it via options.capabilities), " +
|
|
1277
|
-
"or use activateExtensionForTest directly to inspect a failed activation.");
|
|
1278
|
-
}
|
|
1279
|
-
return {
|
|
1280
|
-
module,
|
|
1281
|
-
name,
|
|
1282
|
-
layer,
|
|
1283
|
-
activation,
|
|
1284
|
-
activationSummary(describeOptions) {
|
|
1285
|
-
return describeExtensionActivation(activation, describeOptions);
|
|
1286
|
-
},
|
|
1287
|
-
renderMarkdown(markdownOptions = {}) {
|
|
1288
|
-
const { extensionName, ...renderOptions } = markdownOptions ?? {};
|
|
1289
|
-
return renderExtensionSurfaceMarkdown(describeExtensionActivation(activation, { extensionName }), renderOptions);
|
|
1290
|
-
},
|
|
1291
|
-
assertCommandContract(expectation) {
|
|
1292
|
-
return assertRegisteredCommandContract(activation.registrations, expectation);
|
|
1293
|
-
},
|
|
1294
|
-
assertFlags(expectation) {
|
|
1295
|
-
return assertRegisteredFlags(activation.registrations, expectation);
|
|
1296
|
-
},
|
|
1297
|
-
assertItemField(expectation) {
|
|
1298
|
-
return assertRegisteredItemField(activation.registrations, expectation);
|
|
1299
|
-
},
|
|
1300
|
-
assertItemType(expectation) {
|
|
1301
|
-
return assertRegisteredItemType(activation.registrations, expectation);
|
|
1302
|
-
},
|
|
1303
|
-
assertProfile(expectation) {
|
|
1304
|
-
return assertRegisteredProfile(activation.registrations, expectation);
|
|
1305
|
-
},
|
|
1306
|
-
assertHook(expectation) {
|
|
1307
|
-
return assertRegisteredHook(activation.hooks, expectation);
|
|
1308
|
-
},
|
|
1309
|
-
assertCommandOverride(expectation) {
|
|
1310
|
-
return assertRegisteredCommandOverride(activation.commands, expectation);
|
|
1311
|
-
},
|
|
1312
|
-
assertParserOverride(expectation) {
|
|
1313
|
-
return assertRegisteredParserOverride(activation.parsers, expectation);
|
|
1314
|
-
},
|
|
1315
|
-
assertPreflightOverride(expectation) {
|
|
1316
|
-
return assertRegisteredPreflightOverride(activation.preflight, expectation);
|
|
1317
|
-
},
|
|
1318
|
-
assertRendererOverride(expectation) {
|
|
1319
|
-
return assertRegisteredRendererOverride(activation.renderers, expectation);
|
|
1320
|
-
},
|
|
1321
|
-
assertServiceOverride(expectation) {
|
|
1322
|
-
return assertRegisteredServiceOverride(activation.services, expectation);
|
|
1323
|
-
},
|
|
1324
|
-
assertSearchProvider(expectation) {
|
|
1325
|
-
return assertRegisteredSearchProvider(activation.registrations, expectation);
|
|
1326
|
-
},
|
|
1327
|
-
assertVectorStoreAdapter(expectation) {
|
|
1328
|
-
return assertRegisteredVectorStoreAdapter(activation.registrations, expectation);
|
|
1329
|
-
},
|
|
1330
|
-
assertImporter(expectation) {
|
|
1331
|
-
return assertRegisteredImporter(activation.registrations, expectation);
|
|
1332
|
-
},
|
|
1333
|
-
assertExporter(expectation) {
|
|
1334
|
-
return assertRegisteredExporter(activation.registrations, expectation);
|
|
1335
|
-
},
|
|
1336
|
-
assertMigration(expectation) {
|
|
1337
|
-
return assertRegisteredMigration(activation.registrations, expectation);
|
|
1338
|
-
},
|
|
1339
|
-
assertCapabilityUsage(expectation) {
|
|
1340
|
-
return assertExtensionCapabilityUsage(activation, expectation);
|
|
1341
|
-
},
|
|
1342
|
-
runCommand(runOptions) {
|
|
1343
|
-
return runRegisteredCommandForTest(activation.commands, runOptions);
|
|
1344
|
-
},
|
|
1345
|
-
runHook(runOptions) {
|
|
1346
|
-
return runRegisteredHookForTest(activation.hooks, runOptions);
|
|
1347
|
-
},
|
|
1348
|
-
runMutationGuard(runOptions) {
|
|
1349
|
-
return runRegisteredMutationGuardForTest(activation.hooks, runOptions);
|
|
1350
|
-
},
|
|
1351
|
-
runCommandOverride(context) {
|
|
1352
|
-
return runRegisteredCommandOverrideForTest(activation.commands, context);
|
|
1353
|
-
},
|
|
1354
|
-
runParserOverride(context) {
|
|
1355
|
-
return runRegisteredParserOverrideForTest(activation.parsers, context);
|
|
1356
|
-
},
|
|
1357
|
-
runPreflightOverride(context) {
|
|
1358
|
-
return runRegisteredPreflightOverrideForTest(activation.preflight, context);
|
|
1359
|
-
},
|
|
1360
|
-
runRendererOverride(context) {
|
|
1361
|
-
return runRegisteredRendererOverrideForTest(activation.renderers, context);
|
|
1362
|
-
},
|
|
1363
|
-
runServiceOverride(context) {
|
|
1364
|
-
return runRegisteredServiceOverrideForTest(activation.services, context);
|
|
1365
|
-
},
|
|
1366
|
-
runSearchProvider(runOptions) {
|
|
1367
|
-
return runRegisteredSearchProviderForTest(activation.registrations, runOptions);
|
|
1368
|
-
},
|
|
1369
|
-
runVectorStoreAdapter(runOptions) {
|
|
1370
|
-
return runRegisteredVectorStoreAdapterForTest(activation.registrations, runOptions);
|
|
1371
|
-
},
|
|
1372
|
-
runMigration(runOptions) {
|
|
1373
|
-
return runRegisteredMigrationForTest(activation.registrations, runOptions);
|
|
1374
|
-
},
|
|
1375
|
-
runImporter(runOptions) {
|
|
1376
|
-
return runRegisteredImporterForTest(activation, runOptions);
|
|
1377
|
-
},
|
|
1378
|
-
runExporter(runOptions) {
|
|
1379
|
-
return runRegisteredExporterForTest(activation, runOptions);
|
|
1380
|
-
},
|
|
1381
|
-
deactivate(deactivateOptions = {}) {
|
|
1382
|
-
return deactivateExtensionForTest(module, {
|
|
1383
|
-
name,
|
|
1384
|
-
layer,
|
|
1385
|
-
activation,
|
|
1386
|
-
deactivateTimeoutMs: deactivateOptions.deactivateTimeoutMs,
|
|
1387
|
-
});
|
|
1388
|
-
},
|
|
1389
|
-
};
|
|
1390
|
-
}
|
|
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]="fa9e95e5-7782-509d-96af-093a47daaed7")}catch(e2){}})();import{activateExtensions,deactivateExtensions,runAfterCommandHooks,runBeforeCommandHooks,runBeforeMutationHooks,runCommandHandler,runCommandOverride,runOnIndexHooks,runOnReadHooks,runOnWriteHooks,runParserOverride,runPreflightOverride,runRendererOverride,runServiceOverride}from"../core/extensions/loader.js";import{createDefaultExtensionGovernancePolicy}from"../core/extensions/extension-types.js";import{getMigrationRuntimeDefinition,resolveRegisteredSearchProvider,resolveRegisteredVectorStoreAdapter}from"../core/extensions/runtime-registrations.js";import{collectUsedExtensionCapabilities}from"../core/extensions/capability-usage.js";import{normalizeKnownExtensionCapability}from"../core/extensions/extension-capability-aliases.js";import{checkExtensionManifestCompatibility,describeExtensionBlueprint,lintExtensionBlueprint,preflightExtension}from"./compose.js";import{describeExtensionActivation}from"../core/extensions/activation-summary.js";import{lintProjectProfile}from"../core/profile/profile-lint.js";import{renderExtensionSurfaceMarkdown}from"../core/extensions/activation-summary-markdown.js";import{asPropertyRecord,resolveActivatablePropertyRecord}from"../core/shared/primitives.js";import{createExtensionCommandSdk}from"./extension-command-context.js";import{PmClient}from"./runtime.js";function registeredExpectationName(expectation,legacyValue){return expectation.name??legacyValue??""}const HOOK_KIND_TO_REGISTRY_FIELD={before_command:"beforeCommand",before_mutation:"beforeMutation",after_command:"afterCommand",on_read:"onRead",on_write:"onWrite",on_index:"onIndex"};const RUNNABLE_HOOK_KIND_TO_REGISTRY_FIELD={before_command:"beforeCommand",after_command:"afterCommand",on_read:"onRead",on_write:"onWrite",on_index:"onIndex"};function normalizeSdkIdentifier(value){return value.trim().toLowerCase()}function normalizeSdkProfileMatchKey(value){return normalizeSdkIdentifier(value).replaceAll("-","_")}function extensionNameSuffix(extensionName){return extensionName?` from extension "${extensionName}"`:""}function sortedUnique(values){return[...new Set(values)].sort((left,right)=>left.localeCompare(right))}function normalizeSdkCommandName(command){if(typeof command!=="string"){throw new Error('A command name string is required. Pass it via the options object, e.g. runRegisteredCommandForTest(activation, { command: "my command" }) \u2014 positional command arguments are not supported.')}return command.trim().toLowerCase().split(/\s+/).filter(part=>part.length>0).join(" ")}function formatAvailable(values){return values.length>0?values.join(", "):"(none)"}function assertStringSetIncludes(actual,expected,label){const actualValues=new Set(actual??[]);const missing=sortedUnique(expected.filter(value=>!actualValues.has(value)));if(missing.length>0){throw new Error(`Expected package manifest ${label} to include ${missing.join(", ")}; available: ${formatAvailable(sortedUnique(actual??[]))}`)}}function collectFlagLabels(flags){const labels=new Set;for(const flag of flags){if(typeof flag.long==="string"&&flag.long.trim().length>0){labels.add(flag.long.trim())}if(typeof flag.short==="string"&&flag.short.trim().length>0){labels.add(flag.short.trim())}}return labels}function readTestExtensionManifest(module){const moduleRecord=asPropertyRecord(module);if(!moduleRecord){return{}}const manifest=asPropertyRecord(moduleRecord.manifest);if(manifest){return manifest}const defaultExport=asPropertyRecord(moduleRecord.default);const defaultManifest=asPropertyRecord(defaultExport?.manifest);if(defaultManifest){return defaultManifest}if(defaultExport&&typeof moduleRecord.default!=="function"&&("name"in defaultExport||"capabilities"in defaultExport)){return defaultExport}if(typeof module!=="function"&&("name"in moduleRecord||"capabilities"in moduleRecord)){return moduleRecord}return{}}function resolveTestExtensionName(manifest,explicitName){return explicitName??(typeof manifest.name==="string"&&manifest.name.trim().length>0?manifest.name.trim():"test-extension")}function readTestExtensionCapabilities(manifest,options){if(Array.isArray(options.capabilities)){return[...options.capabilities]}if(Array.isArray(manifest.capabilities)){return manifest.capabilities.filter(capability=>typeof capability==="string")}return[]}function buildSingleExtensionLoadResult(module,manifest,identity){return{disabled_by_flag:false,roots:{global:"",project:""},configured_enabled:[],configured_disabled:[],discovered:[],effective:[],warnings:[],policy:identity.policy,failed:[],loaded:[{layer:identity.layer,directory:"",manifest_path:"",name:identity.name,version:typeof manifest.version==="string"?manifest.version:"0.0.0",entry:typeof manifest.entry==="string"?manifest.entry:"./index.js",priority:typeof manifest.priority==="number"?manifest.priority:0,entry_path:"",capabilities:identity.capabilities,manifest_version:typeof manifest.manifest_version==="number"?manifest.manifest_version:void 0,pm_min_version:typeof manifest.pm_min_version==="string"?manifest.pm_min_version:void 0,pm_max_version:typeof manifest.pm_max_version==="string"?manifest.pm_max_version:void 0,engines:manifest.engines,trusted:manifest.trusted,provenance:manifest.provenance,sandbox_profile:manifest.sandbox_profile,permissions:manifest.permissions,activation:manifest.activation,module}]}}async function activateExtensionForTest(module,options={}){const manifest=readTestExtensionManifest(module);return activateExtensions(buildSingleExtensionLoadResult(module,manifest,{name:resolveTestExtensionName(manifest,options.name),layer:options.layer??"project",capabilities:readTestExtensionCapabilities(manifest,options),policy:options.policy??createDefaultExtensionGovernancePolicy()}))}async function deactivateExtensionForTest(module,options={}){const manifest=readTestExtensionManifest(module);return deactivateExtensions(buildSingleExtensionLoadResult(module,manifest,{name:resolveTestExtensionName(manifest,options.name),layer:options.layer??"project",capabilities:readTestExtensionCapabilities(manifest,{}),policy:createDefaultExtensionGovernancePolicy()}),options.activation,options.deactivateTimeoutMs===void 0?{}:{deactivate_timeout_ms:options.deactivateTimeoutMs})}async function runRegisteredCommandForTest(commands,options){const command=normalizeSdkCommandName(options.command);if(command.length===0){throw new Error("Expected command name must be a non-empty string")}const hasHandler=commands.handlers.some(entry=>entry.command===command);if(!hasHandler){const available=sortedUnique(commands.handlers.map(entry=>entry.command));throw new Error(`Expected a registered command handler for "${command}" to invoke. Available command handlers: ${formatAvailable(available)}`)}if(options.sdk!==void 0&&options.sdkFactory!==void 0){throw new TypeError("runRegisteredCommandForTest accepts sdk or sdkFactory, not both")}const pmRoot=options.pmRoot??"";const global2={json:true,quiet:true,noPager:true,...options.global};const sdk=options.sdk??options.sdkFactory?.({pmRoot,global:global2})??(pmRoot.length>0?createExtensionCommandSdk(pmRoot,new PmClient({pmRoot})):void 0);return runCommandHandler(commands,{command,args:options.args?[...options.args]:[],options:options.options?{...options.options}:{},global:global2,pm_root:pmRoot,...sdk===void 0?{}:{sdk}})}async function runRegisteredMutationGuardForTest(hooks,options){if((hooks.beforeMutation?.length??0)===0){throw new Error("Expected a registered before_mutation hook to invoke")}await runBeforeMutationHooks(hooks,options.context)}async function runRegisteredHookForTest(hooks,options){const kind=options.kind;if(kind==="before_mutation"){throw new Error("Invoke before_mutation hooks with runRegisteredMutationGuardForTest so denials and failures retain fail-closed semantics.")}if((hooks[RUNNABLE_HOOK_KIND_TO_REGISTRY_FIELD[kind]]?.length??0)===0){const populatedKinds=sortedUnique(Object.keys(RUNNABLE_HOOK_KIND_TO_REGISTRY_FIELD).filter(candidateKind=>(hooks[RUNNABLE_HOOK_KIND_TO_REGISTRY_FIELD[candidateKind]]?.length??0)>0));throw new Error(`Expected a registered "${kind}" hook to invoke. Hook kinds with registrations: ${formatAvailable(populatedKinds)}`)}switch(options.kind){case"before_command":return runBeforeCommandHooks(hooks,options.context);case"after_command":return runAfterCommandHooks(hooks,options.context);case"on_read":return runOnReadHooks(hooks,options.context);case"on_write":return runOnWriteHooks(hooks,options.context);case"on_index":return runOnIndexHooks(hooks,options.context)}}async function runRegisteredParserOverrideForTest(parsers,context){const command=normalizeSdkCommandName(context.command);if(!parsers.overrides.some(entry=>entry.command===command)){throw new Error(`Expected a registered parser override for command "${command}" to invoke. Available parser override commands: ${formatAvailable(sortedUnique(parsers.overrides.map(entry=>entry.command)))}`)}return runParserOverride(parsers,context)}async function runRegisteredPreflightOverrideForTest(preflight,context){if(preflight.overrides.length===0){throw new Error("Expected a registered preflight override to invoke, but none are registered.")}return runPreflightOverride(preflight,context)}async function runRegisteredCommandOverrideForTest(commands,context){const command=normalizeSdkCommandName(context.command);if(!commands.overrides.some(entry=>entry.command===command)){throw new Error(`Expected a registered command override for command "${command}" to invoke. Available command override commands: ${formatAvailable(sortedUnique(commands.overrides.map(entry=>entry.command)))}`)}return runCommandOverride(commands,context)}async function runRegisteredRendererOverrideForTest(renderers,context){if(!renderers.overrides.some(entry=>entry.format===context.format)){throw new Error(`Expected a registered renderer override for format "${context.format}" to invoke. Available renderer override formats: ${formatAvailable(sortedUnique(renderers.overrides.map(entry=>entry.format)))}`)}return runRendererOverride(renderers,context)}async function runRegisteredServiceOverrideForTest(services,context){if(!services.overrides.some(entry=>entry.service===context.service)){throw new Error(`Expected a registered service override for service "${context.service}" to invoke. Available service override services: ${formatAvailable(sortedUnique(services.overrides.map(entry=>entry.service)))}`)}return runServiceOverride(services,context)}const SEARCH_PROVIDER_OPERATION_DEFINITION_KEYS={query:["query"],embed:["embed"],embedBatch:["embedBatch","embed_batch"],queryExpansion:["queryExpansion","query_expansion"],rerank:["rerank"]};async function runRegisteredSearchProviderForTest(registrations,options){const registration=resolveRegisteredSearchProvider(registrations,options.provider);if(!registration){const available=sortedUnique(registrations.search_providers.map(entry=>entry.definition.name));throw new Error(`Expected a registered search provider "${options.provider}" to invoke. Available search providers: ${formatAvailable(available)}`)}const runtimeDefinition=registration.runtime_definition;const operationFn=SEARCH_PROVIDER_OPERATION_DEFINITION_KEYS[options.operation].map(key=>runtimeDefinition[key]).find(value=>typeof value==="function");if(typeof operationFn!=="function"){throw new Error(`Registered search provider "${options.provider}" does not implement the "${options.operation}" operation.`)}return await operationFn(options.context)}async function runRegisteredVectorStoreAdapterForTest(registrations,options){const registration=resolveRegisteredVectorStoreAdapter(registrations,options.adapter);if(!registration){const available=sortedUnique(registrations.vector_store_adapters.map(entry=>entry.definition.name));throw new Error(`Expected a registered vector store adapter "${options.adapter}" to invoke. Available vector store adapters: ${formatAvailable(available)}`)}const operationFn=registration.runtime_definition[options.operation];if(typeof operationFn!=="function"){throw new Error(`Registered vector store adapter "${options.adapter}" does not implement the "${options.operation}" operation.`)}return await operationFn(options.context)}async function runRegisteredMigrationForTest(registrations,options){const migration=assertRegisteredMigration(registrations,{migration:options.migration,extensionName:options.extensionName});const run=getMigrationRuntimeDefinition(migration).run;if(typeof run!=="function"){throw new Error(`Registered migration "${options.migration}" does not implement a run function to invoke.`)}const declaredStatus=migration.definition.status;const context={id:migration.definition.id,command:"migration",layer:migration.layer,extension:migration.name,pm_root:options.pmRoot??"",status:(typeof declaredStatus==="string"?declaredStatus.trim().toLowerCase():"")||"pending"};return await run(context)}async function runRegisteredImporterForTest(activation,options){const importer=assertRegisteredImporter(activation.registrations,{importer:options.importer,extensionName:options.extensionName});return runRegisteredCommandForTest(activation.commands,{command:`${importer.importer} import`,args:options.args,options:options.options,global:options.global,pmRoot:options.pmRoot})}async function runRegisteredExporterForTest(activation,options){const exporter=assertRegisteredExporter(activation.registrations,{exporter:options.exporter,extensionName:options.extensionName});return runRegisteredCommandForTest(activation.commands,{command:`${exporter.exporter} export`,args:options.args,options:options.options,global:options.global,pmRoot:options.pmRoot})}function assertExtensionDeactivated(result,expectation={}){const expectedDeactivated=expectation.deactivated??1;if(result.deactivated!==expectedDeactivated){throw new Error(`Expected ${expectedDeactivated} extension${expectedDeactivated===1?"":"s"} to deactivate cleanly, but ${result.deactivated} did.`)}const expectedFailed=expectation.failed??0;if(result.failed.length!==expectedFailed){const detail=result.failed.map(failure=>`${failure.layer}:${failure.name} (${failure.error})`).join(", ");throw new Error(`Expected ${expectedFailed} teardown failure${expectedFailed===1?"":"s"}, but observed ${result.failed.length}${detail.length>0?`: ${detail}`:""}.`)}return result}function assertPackageManifest(manifest,expectation){if(expectation.packageName!==void 0&&manifest.package_name!==expectation.packageName){throw new Error(`Expected package manifest package_name to be "${expectation.packageName}"; received "${manifest.package_name??"(none)"}"`)}if(expectation.aliases!==void 0){assertStringSetIncludes(manifest.aliases,expectation.aliases,"aliases")}if(expectation.resources!==void 0){for(const[kind,expectedPaths]of Object.entries(expectation.resources)){assertStringSetIncludes(manifest.resources[kind],expectedPaths,`pm.${kind}`)}}return manifest}function assertRegisteredCommandContract(registrations,expectation){const expectedCommand=normalizeSdkCommandName(registeredExpectationName(expectation,expectation.command));if(expectedCommand.length===0){throw new Error("Expected command name must be a non-empty string")}const commandCandidates=registrations.commands.filter(entry=>entry.command===expectedCommand);const command=expectation.extensionName?commandCandidates.find(entry=>entry.name===expectation.extensionName):commandCandidates[0];if(!command){const available=registrations.commands.map(entry=>entry.command).sort((left,right)=>left.localeCompare(right));const extensionSuffix=expectation.extensionName?` from extension "${expectation.extensionName}"`:"";throw new Error(`Expected extension command "${expectedCommand}"${extensionSuffix} to be registered. Available commands: ${formatAvailable(available)}`)}if(expectation.action!==void 0&&command.action!==expectation.action){throw new Error(`Expected extension command "${expectedCommand}" action "${expectation.action}", received "${command.action}"`)}if(expectation.arguments!==void 0){const actualArguments=(command.arguments??[]).map(argument=>argument.name);const missingArguments=expectation.arguments.filter(argument=>!actualArguments.includes(argument));if(missingArguments.length>0){throw new Error(`Expected extension command "${expectedCommand}" arguments ${formatAvailable(expectation.arguments)}; missing ${formatAvailable(missingArguments)}; available ${formatAvailable(actualArguments)}`)}}const flags=registrations.flags.filter(entry=>entry.target_command===expectedCommand&&(expectation.extensionName===void 0||entry.name===expectation.extensionName)).flatMap(entry=>entry.flags);if(expectation.flags!==void 0){const actualFlagLabels=collectFlagLabels(flags);const missingFlags=expectation.flags.filter(flag=>!actualFlagLabels.has(flag));if(missingFlags.length>0){throw new Error(`Expected extension command "${expectedCommand}" flags ${formatAvailable(expectation.flags)}; missing ${formatAvailable(missingFlags)}; available ${formatAvailable([...actualFlagLabels].sort((left,right)=>left.localeCompare(right)))}`)}}return{command,flags}}function assertRegisteredFlags(registrations,expectation){const expectedCommand=normalizeSdkCommandName(registeredExpectationName(expectation,expectation.targetCommand));if(expectedCommand.length===0){throw new Error("Expected target command name must be a non-empty string")}const candidates=registrations.flags.filter(entry=>entry.target_command===expectedCommand);let registration;if(expectation.extensionName){registration=candidates.find(entry=>entry.name===expectation.extensionName)}else if(candidates.length===1){registration=candidates[0]}else if(candidates.length>1){const availableExtensions=sortedUnique(candidates.map(entry=>entry.name));throw new Error(`Expected flags for target command "${expectedCommand}" matched multiple extensions: ${formatAvailable(availableExtensions)}. Specify extensionName to choose one registration.`)}if(!registration){const available=sortedUnique(registrations.flags.map(entry=>entry.target_command));const availableExtensions=sortedUnique(candidates.map(entry=>entry.name));throw new Error(`Expected flags for target command "${expectedCommand}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available flag target commands: ${formatAvailable(available)}; matching extensions: ${formatAvailable(availableExtensions)}`)}if(expectation.flags!==void 0){const actualFlagLabels=collectFlagLabels(registration.flags);const missingFlags=expectation.flags.filter(flag=>!actualFlagLabels.has(flag));if(missingFlags.length>0){throw new Error(`Expected flags for target command "${expectedCommand}" to include ${formatAvailable(expectation.flags)}; missing ${formatAvailable(missingFlags)}; available ${formatAvailable([...actualFlagLabels].sort((left,right)=>left.localeCompare(right)))}`)}}return registration}function assertRegisteredHook(hooks,expectation){const field=HOOK_KIND_TO_REGISTRY_FIELD[expectation.kind];const candidates=hooks[field]??[];const hook=expectation.extensionName?candidates.find(entry=>entry.name===expectation.extensionName):candidates[0];if(!hook){const available=sortedUnique(candidates.map(entry=>entry.name));throw new Error(`Expected a "${expectation.kind}" hook${extensionNameSuffix(expectation.extensionName)} to be registered. Available "${expectation.kind}" hooks: ${formatAvailable(available)}`)}return hook}function assertRegisteredSearchProvider(registrations,expectation){const expectedProvider=normalizeSdkIdentifier(registeredExpectationName(expectation,expectation.provider));if(expectedProvider.length===0){throw new Error("Expected search provider name must be a non-empty string")}const candidates=registrations.search_providers.filter(entry=>normalizeSdkIdentifier(entry.definition.name)===expectedProvider);const provider=expectation.extensionName?candidates.find(entry=>entry.name===expectation.extensionName):candidates[0];if(!provider){const available=sortedUnique(registrations.search_providers.map(entry=>entry.definition.name));throw new Error(`Expected search provider "${expectedProvider}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available search providers: ${formatAvailable(available)}`)}return provider}function assertRegisteredVectorStoreAdapter(registrations,expectation){const expectedAdapter=normalizeSdkIdentifier(registeredExpectationName(expectation,expectation.adapter));if(expectedAdapter.length===0){throw new Error("Expected vector store adapter name must be a non-empty string")}const candidates=registrations.vector_store_adapters.filter(entry=>normalizeSdkIdentifier(entry.definition.name)===expectedAdapter);const adapter=expectation.extensionName?candidates.find(entry=>entry.name===expectation.extensionName):candidates[0];if(!adapter){const available=sortedUnique(registrations.vector_store_adapters.map(entry=>entry.definition.name));throw new Error(`Expected vector store adapter "${expectedAdapter}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available vector store adapters: ${formatAvailable(available)}`)}return adapter}function assertRegisteredImporter(registrations,expectation){const expectedImporter=normalizeSdkIdentifier(registeredExpectationName(expectation,expectation.importer));if(expectedImporter.length===0){throw new Error("Expected importer name must be a non-empty string")}const candidates=registrations.importers.filter(entry=>normalizeSdkIdentifier(entry.importer)===expectedImporter);const importer=expectation.extensionName?candidates.find(entry=>entry.name===expectation.extensionName):candidates[0];if(!importer){const available=sortedUnique(registrations.importers.map(entry=>entry.importer));throw new Error(`Expected importer "${expectedImporter}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available importers: ${formatAvailable(available)}`)}return importer}function assertRegisteredExporter(registrations,expectation){const expectedExporter=normalizeSdkIdentifier(registeredExpectationName(expectation,expectation.exporter));if(expectedExporter.length===0){throw new Error("Expected exporter name must be a non-empty string")}const candidates=registrations.exporters.filter(entry=>normalizeSdkIdentifier(entry.exporter)===expectedExporter);const exporter=expectation.extensionName?candidates.find(entry=>entry.name===expectation.extensionName):candidates[0];if(!exporter){const available=sortedUnique(registrations.exporters.map(entry=>entry.exporter));throw new Error(`Expected exporter "${expectedExporter}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available exporters: ${formatAvailable(available)}`)}return exporter}function assertRegisteredItemField(registrations,expectation){const expectedField=normalizeSdkIdentifier(registeredExpectationName(expectation,expectation.field));if(expectedField.length===0){throw new Error("Expected item field name must be a non-empty string")}const candidates=registrations.item_fields.filter(entry=>expectation.extensionName===void 0||entry.name===expectation.extensionName).map(registration=>({registration,field:registration.fields.find(field=>normalizeSdkIdentifier(field.name)===expectedField)})).filter(entry=>entry.field!==void 0);const match=candidates.find(entry=>expectation.type===void 0||entry.field.type===expectation.type);if(!match){const available=sortedUnique(registrations.item_fields.flatMap(entry=>entry.fields.map(field=>`${field.name}:${field.type}`)));throw new Error(`Expected item field "${expectedField}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available item fields: ${formatAvailable(available)}`)}return match}function assertRegisteredItemType(registrations,expectation){const expectedType=normalizeSdkIdentifier(registeredExpectationName(expectation,expectation.itemType));if(expectedType.length===0){throw new Error("Expected item type name must be a non-empty string")}const candidates=registrations.item_types.filter(entry=>expectation.extensionName===void 0||entry.name===expectation.extensionName).map(registration=>({registration,itemType:registration.types.find(itemType=>normalizeSdkIdentifier(itemType.name)===expectedType)})).filter(entry=>entry.itemType!==void 0);const match=candidates.find(entry=>expectation.folder===void 0||entry.itemType.folder===expectation.folder);if(!match){const available=sortedUnique(registrations.item_types.flatMap(entry=>entry.types.map(itemType=>`${itemType.name}:${itemType.folder}`)));throw new Error(`Expected item type "${expectedType}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available item types: ${formatAvailable(available)}`)}return match}function assertRegisteredProfile(registrations,expectation){const expectedProfile=normalizeSdkProfileMatchKey(registeredExpectationName(expectation,expectation.profile));if(expectedProfile.length===0){throw new Error("Expected profile name must be a non-empty string")}const match=registrations.profiles.filter(entry=>expectation.extensionName===void 0||entry.name===expectation.extensionName).find(entry=>normalizeSdkProfileMatchKey(entry.profile.name)===expectedProfile);if(!match){const available=sortedUnique(registrations.profiles.map(entry=>entry.profile.name));throw new Error(`Expected profile "${expectedProfile}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available profiles: ${formatAvailable(available)}`)}return{registration:match,profile:match.profile}}function assertRegisteredCommandOverride(commands,expectation){const expectedCommand=normalizeSdkCommandName(registeredExpectationName(expectation,expectation.command));if(expectedCommand.length===0){throw new Error("Expected command name must be a non-empty string")}const candidates=commands.overrides.filter(entry=>entry.command===expectedCommand);const override=expectation.extensionName?candidates.find(entry=>entry.name===expectation.extensionName):candidates[0];if(!override){const available=sortedUnique(commands.overrides.map(entry=>entry.command));throw new Error(`Expected command override "${expectedCommand}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available command overrides: ${formatAvailable(available)}`)}return override}function assertRegisteredParserOverride(parsers,expectation){const expectedCommand=normalizeSdkCommandName(registeredExpectationName(expectation,expectation.command));if(expectedCommand.length===0){throw new Error("Expected command name must be a non-empty string")}const candidates=parsers.overrides.filter(entry=>entry.command===expectedCommand);const override=expectation.extensionName?candidates.find(entry=>entry.name===expectation.extensionName):candidates[0];if(!override){const available=sortedUnique(parsers.overrides.map(entry=>entry.command));throw new Error(`Expected parser override "${expectedCommand}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available parser overrides: ${formatAvailable(available)}`)}return override}function assertRegisteredPreflightOverride(preflight,expectation={}){const override=expectation.extensionName?preflight.overrides.find(entry=>entry.name===expectation.extensionName):preflight.overrides[0];if(!override){const available=sortedUnique(preflight.overrides.map(entry=>entry.name));throw new Error(`Expected a preflight override${extensionNameSuffix(expectation.extensionName)} to be registered. Available preflight overrides: ${formatAvailable(available)}`)}return override}function assertRegisteredRendererOverride(renderers,expectation){const expectedFormat=normalizeSdkIdentifier(registeredExpectationName(expectation,expectation.format));if(expectedFormat.length===0){throw new Error("Expected renderer format must be a non-empty string")}const candidates=renderers.overrides.filter(entry=>entry.format===expectedFormat);const override=expectation.extensionName?candidates.find(entry=>entry.name===expectation.extensionName):candidates[0];if(!override){const available=sortedUnique(renderers.overrides.map(entry=>entry.format));throw new Error(`Expected renderer override "${expectedFormat}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available renderer overrides: ${formatAvailable(available)}`)}return override}function assertRegisteredServiceOverride(services,expectation){const expectedService=normalizeSdkIdentifier(registeredExpectationName(expectation,expectation.service));if(expectedService.length===0){throw new Error("Expected service name must be a non-empty string")}const candidates=services.overrides.filter(entry=>normalizeSdkIdentifier(entry.service)===expectedService);const override=expectation.extensionName?candidates.find(entry=>entry.name===expectation.extensionName):candidates[0];if(!override){const available=sortedUnique(services.overrides.map(entry=>entry.service));throw new Error(`Expected service override "${expectedService}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available service overrides: ${formatAvailable(available)}`)}return override}function assertRegisteredMigration(registrations,expectation){const expectedMigration=normalizeSdkIdentifier(registeredExpectationName(expectation,expectation.migration));if(expectedMigration.length===0){throw new Error("Expected migration id must be a non-empty string")}const candidates=registrations.migrations.filter(entry=>(expectation.extensionName===void 0||entry.name===expectation.extensionName)&&typeof entry.definition.id==="string"&&normalizeSdkIdentifier(entry.definition.id)===expectedMigration);const match=candidates.find(entry=>expectation.mandatory===void 0||(entry.definition.mandatory??false)===expectation.mandatory);if(match){return match}const idMatch=candidates[0];if(idMatch!==void 0){throw new Error(`Expected migration "${expectedMigration}"${extensionNameSuffix(expectation.extensionName)} to have mandatory=${expectation.mandatory}, but it is mandatory=${idMatch.definition.mandatory??false}.`)}const available=sortedUnique(registrations.migrations.map(entry=>{const id=typeof entry.definition.id==="string"&&entry.definition.id.trim().length>0?entry.definition.id.trim():"(unnamed)";return`${id}:${entry.definition.mandatory===true}`}));throw new Error(`Expected migration "${expectedMigration}"${extensionNameSuffix(expectation.extensionName)} to be registered. Available migrations: ${formatAvailable(available)}`)}function assertExtensionCapabilityUsage(activation,expectation){const toKnownCapabilitySet=(capabilities,field)=>{const known=new Set;for(const capability of capabilities){const normalized=normalizeKnownExtensionCapability(capability);if(normalized===null){throw new Error(`Expected ${field} capability "${capability}" to be a known extension capability. Use canonical capability names (see manifest.capabilities).`)}known.add(normalized)}return known};const declared=[...toKnownCapabilitySet(expectation.declared,"declared")].sort((left,right)=>left.localeCompare(right));const allowUnused=toKnownCapabilitySet(expectation.allowUnused??[],"allowUnused");const used=collectUsedExtensionCapabilities(activation,{extensionName:expectation.extensionName});const usedSet=new Set(used);const unused=declared.filter(capability=>!usedSet.has(capability)&&!allowUnused.has(capability));if(unused.length>0){const scopeSuffix=extensionNameSuffix(expectation.extensionName);throw new Error(`Expected every declared capability${scopeSuffix} to be exercised, but [${unused.join(", ")}] ${unused.length===1?"is":"are"} declared yet never registered against. Remove ${unused.length===1?"it":"them"} from manifest.capabilities for least privilege, or pass allowUnused for capabilities registered only behind a runtime flag.`)}return{declared,used,unused}}function assertExtensionBlueprint(blueprint,options={}){const result=lintExtensionBlueprint(blueprint,options);if(!result.ok){const errors=result.findings.filter(finding=>finding.severity==="error");throw new Error(`Extension blueprint failed preflight with ${errors.length} ${errors.length===1?"error":"errors"}:
|
|
2
|
+
`+errors.map(finding=>` - [${finding.code}] ${finding.message}`).join("\n"))}return result}function assertProjectProfile(profile,options={}){const report=lintProjectProfile(profile);const blocking=report.findings.filter(finding=>finding.severity==="error"||options.strict===true&&finding.severity==="warning");if(blocking.length>0){throw new Error(`Project profile "${report.profile}" failed lint with ${blocking.length} ${blocking.length===1?"issue":"issues"}:
|
|
3
|
+
`+blocking.map(finding=>` - ${finding.severity} [${finding.code}] ${finding.message}`).join("\n"))}return report}function assertExtensionManifestMatchesBlueprint(manifest,blueprint){const declaredCapabilities=Array.isArray(manifest.capabilities)?manifest.capabilities:[];const result=lintExtensionBlueprint(blueprint,{declaredCapabilities});const declared=result.declared;const declaredSet=new Set(declared);const usedSet=new Set(result.used);const missing=result.used.filter(capability=>!declaredSet.has(capability));const unused=declared.filter(capability=>!usedSet.has(capability));if(missing.length>0||unused.length>0){const parts=[];if(missing.length>0){parts.push(`missing [${missing.join(", ")}] (the blueprint registers ${missing.length===1?"a surface":"surfaces"} requiring ${missing.length===1?"it":"them"}; activation throws extension_capability_missing)`)}if(unused.length>0){parts.push(`unused [${unused.join(", ")}] (declared but no surface exercises ${unused.length===1?"it":"them"}; drop for least privilege)`)}throw new Error(`Manifest capabilities do not match the blueprint: ${parts.join("; ")}. Set capabilities to synthesizeExtensionManifest(blueprint, identity).capabilities to stay in sync.`)}return{used:result.used,declared,missing,unused,findings:result.findings}}function assertExtensionManifestCompatible(manifest,target){const result=checkExtensionManifestCompatibility(manifest,target);const blocking=result.findings.filter(finding=>finding.severity==="error");if(blocking.length>0){throw new Error(`Extension manifest is not compatible with pm ${result.pmVersion}: ${blocking.map(finding=>finding.message).join("; ")}`)}return result}function assertExtensionPreflight(blueprint,options={}){const report=preflightExtension(blueprint,options);if(!report.ok){const errors=report.findings.filter(finding=>finding.severity==="error");throw new Error(`Extension failed preflight with ${errors.length} ${errors.length===1?"error":"errors"}:
|
|
4
|
+
`+errors.map(finding=>` - [${finding.source}:${finding.code}] ${finding.message}`).join("\n"))}return report}function assertTestModuleHasActivateExport(module){if(resolveActivatablePropertyRecord(module)){return}throw new Error("createExtensionTestHarness received a module with no activate export. Pass the extension module (with an activate(api) function on the module or its default export) as the first argument, and options second.")}async function createExtensionTestHarness(module,options={}){assertTestModuleHasActivateExport(module);const manifest=readTestExtensionManifest(module);const name=resolveTestExtensionName(manifest,options.name);const layer=options.layer??"project";const activation=await activateExtensionForTest(module,options);if(activation.failed.length>0){const detail=activation.failed.map(failure=>{const missingCapability=failure.trace?.missing_capability;const reason=missingCapability?`${failure.error}; missing capability "${missingCapability}"`:failure.error;return`${failure.layer}:${failure.name} (${reason})`}).join(", ");throw new Error(`createExtensionTestHarness could not activate the extension cleanly: ${detail}. Declare the required capability in the manifest (or pass it via options.capabilities), or use activateExtensionForTest directly to inspect a failed activation.`)}return{module,name,layer,activation,activationSummary(describeOptions){return describeExtensionActivation(activation,describeOptions)},renderMarkdown(markdownOptions={}){const{extensionName,...renderOptions}=markdownOptions??{};return renderExtensionSurfaceMarkdown(describeExtensionActivation(activation,{extensionName}),renderOptions)},assertCommandContract(expectation){return assertRegisteredCommandContract(activation.registrations,expectation)},assertFlags(expectation){return assertRegisteredFlags(activation.registrations,expectation)},assertItemField(expectation){return assertRegisteredItemField(activation.registrations,expectation)},assertItemType(expectation){return assertRegisteredItemType(activation.registrations,expectation)},assertProfile(expectation){return assertRegisteredProfile(activation.registrations,expectation)},assertHook(expectation){return assertRegisteredHook(activation.hooks,expectation)},assertCommandOverride(expectation){return assertRegisteredCommandOverride(activation.commands,expectation)},assertParserOverride(expectation){return assertRegisteredParserOverride(activation.parsers,expectation)},assertPreflightOverride(expectation){return assertRegisteredPreflightOverride(activation.preflight,expectation)},assertRendererOverride(expectation){return assertRegisteredRendererOverride(activation.renderers,expectation)},assertServiceOverride(expectation){return assertRegisteredServiceOverride(activation.services,expectation)},assertSearchProvider(expectation){return assertRegisteredSearchProvider(activation.registrations,expectation)},assertVectorStoreAdapter(expectation){return assertRegisteredVectorStoreAdapter(activation.registrations,expectation)},assertImporter(expectation){return assertRegisteredImporter(activation.registrations,expectation)},assertExporter(expectation){return assertRegisteredExporter(activation.registrations,expectation)},assertMigration(expectation){return assertRegisteredMigration(activation.registrations,expectation)},assertCapabilityUsage(expectation){return assertExtensionCapabilityUsage(activation,expectation)},runCommand(runOptions){return runRegisteredCommandForTest(activation.commands,runOptions)},runHook(runOptions){return runRegisteredHookForTest(activation.hooks,runOptions)},runMutationGuard(runOptions){return runRegisteredMutationGuardForTest(activation.hooks,runOptions)},runCommandOverride(context){return runRegisteredCommandOverrideForTest(activation.commands,context)},runParserOverride(context){return runRegisteredParserOverrideForTest(activation.parsers,context)},runPreflightOverride(context){return runRegisteredPreflightOverrideForTest(activation.preflight,context)},runRendererOverride(context){return runRegisteredRendererOverrideForTest(activation.renderers,context)},runServiceOverride(context){return runRegisteredServiceOverrideForTest(activation.services,context)},runSearchProvider(runOptions){return runRegisteredSearchProviderForTest(activation.registrations,runOptions)},runVectorStoreAdapter(runOptions){return runRegisteredVectorStoreAdapterForTest(activation.registrations,runOptions)},runMigration(runOptions){return runRegisteredMigrationForTest(activation.registrations,runOptions)},runImporter(runOptions){return runRegisteredImporterForTest(activation,runOptions)},runExporter(runOptions){return runRegisteredExporterForTest(activation,runOptions)},deactivate(deactivateOptions={}){return deactivateExtensionForTest(module,{name,layer,activation,deactivateTimeoutMs:deactivateOptions.deactivateTimeoutMs})}}}export{activateExtensionForTest,assertExtensionBlueprint,assertExtensionCapabilityUsage,assertExtensionDeactivated,assertExtensionManifestCompatible,assertExtensionManifestMatchesBlueprint,assertExtensionPreflight,assertPackageManifest,assertProjectProfile,assertRegisteredCommandContract,assertRegisteredCommandOverride,assertRegisteredExporter,assertRegisteredFlags,assertRegisteredHook,assertRegisteredImporter,assertRegisteredItemField,assertRegisteredItemType,assertRegisteredMigration,assertRegisteredParserOverride,assertRegisteredPreflightOverride,assertRegisteredProfile,assertRegisteredRendererOverride,assertRegisteredSearchProvider,assertRegisteredServiceOverride,assertRegisteredVectorStoreAdapter,createExtensionTestHarness,deactivateExtensionForTest,describeExtensionActivation,describeExtensionBlueprint,lintExtensionBlueprint,renderExtensionSurfaceMarkdown,runRegisteredCommandForTest,runRegisteredCommandOverrideForTest,runRegisteredExporterForTest,runRegisteredHookForTest,runRegisteredImporterForTest,runRegisteredMigrationForTest,runRegisteredMutationGuardForTest,runRegisteredParserOverrideForTest,runRegisteredPreflightOverrideForTest,runRegisteredRendererOverrideForTest,runRegisteredSearchProviderForTest,runRegisteredServiceOverrideForTest,runRegisteredVectorStoreAdapterForTest};
|
|
1391
5
|
//# sourceMappingURL=testing.js.map
|
|
1392
|
-
//# debugId=a09f5e0d-fccc-5a22-a6f3-0a37cbeea3b0
|