@unbrained/pm-cli 2026.8.4 → 2026.8.6
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/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +53 -13
- package/dist/cli/error-guidance.js +4 -4
- package/dist/cli/main.js +12 -37
- package/dist/cli/register-setup.d.ts +1 -1
- package/dist/cli/register-setup.js +46 -20
- package/dist/cli/register-structured-mutation.d.ts +2 -0
- package/dist/cli/register-structured-mutation.js +124 -12
- package/dist/cli-bundle/bundle-manifest.json +397 -397
- package/dist/cli-bundle/chunks/append-XAJ5Z4XS.js +2 -0
- package/dist/cli-bundle/chunks/{chunk-QVO3VTA4.js → chunk-343QETLI.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-Q5F6OI7C.js → chunk-3KFVGGRE.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-6S7I3UKV.js → chunk-3ZJRFYY2.js} +19 -19
- package/dist/cli-bundle/chunks/{chunk-NFF3JAQR.js → chunk-4AVFKHHC.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-5H47J5FG.js → chunk-4OVGQW22.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-D2MBVMVE.js → chunk-5PBP2ZP3.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-5ZKQPA44.js +2 -0
- package/dist/cli-bundle/chunks/chunk-A3XQ7VPU.js +2 -0
- package/dist/cli-bundle/chunks/chunk-AJE3AHPD.js +8 -0
- package/dist/cli-bundle/chunks/{chunk-2POYGY53.js → chunk-AOBDLU4T.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-PYYPQLHC.js → chunk-AYRUGRNS.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-QMWUL66F.js → chunk-BP6EMEDP.js} +5 -5
- package/dist/cli-bundle/chunks/{chunk-UVZREFZU.js → chunk-C3YABSJK.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-RB65T4RX.js → chunk-CA3BURYF.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-KIKLF2W4.js → chunk-D7I3TUGV.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-PQXEB7W4.js → chunk-DQGJ2RWT.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-OB7TRZ2G.js → chunk-DWOAOZHZ.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-SVDH5CHC.js → chunk-E7BFPLUZ.js} +4 -4
- package/dist/cli-bundle/chunks/{chunk-CGY5I2GO.js → chunk-F4NREH3G.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-DCJ6CM6F.js → chunk-FDBRXV25.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-NM5G7RLU.js → chunk-GU3TSEHD.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-HS7OVAUN.js +164 -0
- package/dist/cli-bundle/chunks/{chunk-CU5ENFNP.js → chunk-JFZIS6JF.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-GOICHIX4.js → chunk-JQ4NZ5EB.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-HE3DZFN4.js → chunk-L6LGKJX3.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-BLINBTMX.js → chunk-M357ZCOR.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-NRDKVOUE.js → chunk-M4VEE3FK.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-HTYC76A4.js → chunk-MCS73BKB.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-BKMZTZYL.js → chunk-MDTH7SAE.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-H6FITE7D.js → chunk-NE5VRDAI.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-NPUJ7OLK.js → chunk-OXY3SJ3N.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-PECV7L5T.js +2 -0
- package/dist/cli-bundle/chunks/{chunk-KH4GVBMC.js → chunk-Q3VOP62W.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-FQ4EFFDU.js → chunk-QJ7C3JIW.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-VH2EAGUG.js → chunk-R6LSRGPS.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-HQ2WU7OY.js → chunk-R6RKEYYW.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-PA5XRN2A.js → chunk-RAFUN7IX.js} +4 -4
- package/dist/cli-bundle/chunks/chunk-RUAU5OSH.js +2 -0
- package/dist/cli-bundle/chunks/chunk-SD3YXU2U.js +56 -0
- package/dist/cli-bundle/chunks/{chunk-JMKVCQSE.js → chunk-SV3YQ4RY.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-B5ZLCD5Z.js → chunk-SVV3DQES.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-J4EPW4GT.js → chunk-SXECIECW.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-T45OHXDW.js +8 -0
- package/dist/cli-bundle/chunks/{chunk-NH35JNK5.js → chunk-TPKDJ5WL.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-ZSH4GNBH.js → chunk-UVVXHGYS.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-V663653R.js +20 -0
- package/dist/cli-bundle/chunks/{chunk-5PZGZMPG.js → chunk-WSI5KZQJ.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-3XZYTRPC.js → chunk-XPOQQJTX.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-UJHUR3LZ.js → chunk-YCCDXBNU.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-ZFBWCKYF.js +2 -0
- package/dist/cli-bundle/chunks/close-DI66JMSB.js +2 -0
- package/dist/cli-bundle/chunks/close-many-VFKT6U3O.js +2 -0
- package/dist/cli-bundle/chunks/comments-E73HIEZJ.js +2 -0
- package/dist/cli-bundle/chunks/copy-WBYVLCPC.js +2 -0
- package/dist/cli-bundle/chunks/{create-RLLCBCXM.js → create-TG67IVFI.js} +2 -2
- package/dist/cli-bundle/chunks/delete-6XKUVVQN.js +2 -0
- package/dist/cli-bundle/chunks/{deps-J4ONTF4X.js → deps-AJUGP2OH.js} +2 -2
- package/dist/cli-bundle/chunks/{docs-YL2DJ4WD.js → docs-QOCLDRKJ.js} +2 -2
- package/dist/cli-bundle/chunks/{files-NGNAIYKK.js → files-3ST5TY64.js} +2 -2
- package/dist/cli-bundle/chunks/focus-R2AWS63Y.js +2 -0
- package/dist/cli-bundle/chunks/{history-compact-HW62K5XP.js → history-compact-7BJFML4S.js} +2 -2
- package/dist/cli-bundle/chunks/{history-redact-HVBKUJOW.js → history-redact-WJN3HYXR.js} +2 -2
- package/dist/cli-bundle/chunks/{history-repair-OPNBH5JJ.js → history-repair-XDNZUB7F.js} +2 -2
- package/dist/cli-bundle/chunks/{learnings-F36PZXT6.js → learnings-SZMUPVNA.js} +2 -2
- package/dist/cli-bundle/chunks/{profile-N3XMWBM7.js → profile-KL53JIAC.js} +2 -2
- package/dist/cli-bundle/chunks/{register-list-query-BKGHBI7Y.js → register-list-query-QH7ONPKU.js} +2 -2
- package/dist/cli-bundle/chunks/register-mutation-OCRQNZK4.js +20 -0
- package/dist/cli-bundle/chunks/register-operations-HIBJXRGB.js +2 -0
- package/dist/cli-bundle/chunks/register-setup-QWBQ6HBS.js +2 -0
- package/dist/cli-bundle/chunks/restore-FXA5QPM7.js +2 -0
- package/dist/cli-bundle/chunks/{schema-4PJQMGUY.js → schema-QJ27OTRV.js} +2 -2
- package/dist/cli-bundle/chunks/update-7GBO4SXG.js +2 -0
- package/dist/cli-bundle/chunks/update-many-VF6ZKQUO.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-27DK4A5O.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-4JC3AOGS.js +8 -0
- package/dist/cli-bundle/focused-chunks/chunk-6KDHXURF.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-74CWP3A4.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-7AZDTGPA.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-7YTZ7A4E.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-ONIX2KKW.js → chunk-CL3NB7VW.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-EAXXF2HW.js +4 -0
- package/dist/cli-bundle/focused-chunks/{chunk-L6A4NVQC.js → chunk-EYO4F74Z.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-GEYU23YS.js +153 -0
- package/dist/cli-bundle/focused-chunks/{chunk-DQ4PLKEE.js → chunk-GIERI4YU.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-GSW2Y7VZ.js +5 -0
- package/dist/cli-bundle/focused-chunks/{chunk-WVGJAD7L.js → chunk-LKQXXO62.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-UFCPSWIL.js → chunk-MBBMTDD3.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-OYTK4VNH.js → chunk-ME4XVOBJ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-RO5BQFG3.js → chunk-N7QN3NE6.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-QL5H3AQH.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-TQMFQYXR.js +29 -0
- package/dist/cli-bundle/focused-chunks/{chunk-SQXVGHMH.js → chunk-XNVXIQ4B.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-BOGRY7M6.js → chunk-YHDCGUIW.js} +10 -10
- package/dist/cli-bundle/focused-chunks/{chunk-K37BB4JL.js → chunk-YLPZHWX7.js} +2 -2
- package/dist/cli-bundle/main.js +13 -13
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +1 -1
- package/dist/cli-bundle/sdk-core.js +28 -28
- 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 +1 -1
- 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 +1 -1
- package/dist/core/extensions/activation-summary.d.ts +4 -0
- package/dist/core/extensions/activation-summary.js +5 -2
- package/dist/core/extensions/contribution-inventory.d.ts +1 -0
- package/dist/core/extensions/contribution-inventory.js +35 -2
- package/dist/core/extensions/extension-hook-runtime.js +5 -3
- package/dist/core/extensions/extension-types.d.ts +18 -1
- package/dist/core/extensions/extension-types.js +2 -2
- package/dist/core/extensions/loader.js +8 -13
- package/dist/core/extensions/preflight-ownership.d.ts +5 -0
- package/dist/core/extensions/preflight-ownership.js +42 -0
- package/dist/core/item/item-format.js +14 -20
- package/dist/core/shared/constants.js +3 -2
- package/dist/core/store/item-store.js +2 -2
- package/dist/core/store/settings.js +2 -2
- package/dist/mcp/server.js +12 -6
- package/dist/mcp/tool-definitions.js +14 -7
- package/dist/sdk/cli-contracts/agent-output-contracts.d.ts +18 -18
- package/dist/sdk/cli-contracts/agent-output-contracts.js +51 -23
- package/dist/sdk/cli-contracts/commander-mutation-options.js +26 -2
- package/dist/sdk/cli-contracts/flag-contracts.d.ts +4 -0
- package/dist/sdk/cli-contracts/flag-contracts.js +34 -2
- package/dist/sdk/cli-contracts/registration-helpers.js +4 -2
- package/dist/sdk/cli-contracts/runtime-contracts.d.ts +10 -2
- package/dist/sdk/cli-contracts/runtime-contracts.js +21 -5
- package/dist/sdk/cli-contracts/tool-schema.js +4 -2
- package/dist/sdk/cli-contracts.d.ts +2 -2
- package/dist/sdk/cli-contracts.js +4 -4
- package/dist/sdk/cli-program.js +3 -3
- package/dist/sdk/compose.d.ts +2 -2
- package/dist/sdk/compose.js +7 -2
- package/dist/sdk/contracts.d.ts +1 -0
- package/dist/sdk/contracts.js +3 -2
- package/dist/sdk/define.d.ts +3 -1
- package/dist/sdk/define.js +3 -8
- package/dist/sdk/dependency-provenance.d.ts +2 -0
- package/dist/sdk/dependency-provenance.js +7 -3
- package/dist/sdk/error-code-catalog.d.ts +23 -0
- package/dist/sdk/error-code-catalog.js +25 -5
- package/dist/sdk/extension/install-sources.d.ts +43 -5
- package/dist/sdk/extension/install-sources.js +36 -2
- package/dist/sdk/extension/migrations.d.ts +114 -0
- package/dist/sdk/extension/migrations.js +175 -0
- package/dist/sdk/extension/scaffold.js +14 -9
- package/dist/sdk/extension/source-resolution.d.ts +50 -0
- package/dist/sdk/extension/source-resolution.js +66 -0
- package/dist/sdk/extension.d.ts +5 -1
- package/dist/sdk/extension.js +24 -26
- package/dist/sdk/generated-error-code-catalog.js +499 -3
- package/dist/sdk/governance/health.js +11 -2
- package/dist/sdk/graph/governance.js +3 -3
- package/dist/sdk/graph/remediation.js +3 -3
- package/dist/sdk/graph/run.js +4 -8
- package/dist/sdk/index.d.ts +7 -2
- package/dist/sdk/index.js +9 -4
- package/dist/sdk/item-transaction.d.ts +51 -6
- package/dist/sdk/item-transaction.js +132 -22
- package/dist/sdk/lifecycle/create.d.ts +4 -0
- package/dist/sdk/lifecycle/create.js +26 -10
- package/dist/sdk/lifecycle-policy.d.ts +9 -0
- package/dist/sdk/lifecycle-policy.js +22 -9
- package/dist/sdk/merge/driver.js +6 -3
- package/dist/sdk/output-contracts.d.ts +67 -0
- package/dist/sdk/output-contracts.js +173 -0
- package/dist/sdk/package-import-adapters.js +2 -2
- package/dist/sdk/package-migrations.d.ts +10 -0
- package/dist/sdk/package-migrations.js +21 -0
- package/dist/sdk/runtime.d.ts +2 -0
- package/dist/sdk/runtime.js +6 -2
- package/dist/sdk/structured-mutations.d.ts +23 -0
- package/dist/sdk/structured-mutations.js +219 -10
- package/dist/sdk/workspace-snapshot.js +58 -12
- package/dist/sdk/workspace.js +30 -4
- package/docs/COMMANDS.md +49 -10
- package/docs/EXTENSIONS.md +3 -3
- package/docs/EXTENSION_LIFECYCLE.md +46 -0
- package/docs/MERGE_SAFETY.md +2 -0
- package/docs/PR_REVIEW_LOOP.md +10 -4
- package/docs/README.md +23 -22
- package/docs/RELEASING.md +36 -23
- package/docs/SCRIPTING.md +32 -9
- package/docs/SDK.md +122 -14
- package/docs/SELF_DESCRIBING_CONTEXT_CONTRACTS.md +13 -0
- package/docs/TESTING.md +25 -0
- package/docs/examples/sdk-contract-consumer/README.md +11 -1
- package/docs/examples/sdk-contract-consumer/package.json +2 -1
- package/docs/examples/sdk-contract-consumer/parse-receipt.mjs +22 -0
- package/marketplace.json +2 -2
- package/package.json +5 -4
- 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/sdk/public-surface.json +508 -32
- package/dist/cli-bundle/chunks/append-DHBRLHHS.js +0 -2
- package/dist/cli-bundle/chunks/chunk-2INN52SU.js +0 -8
- package/dist/cli-bundle/chunks/chunk-377OOXUF.js +0 -55
- package/dist/cli-bundle/chunks/chunk-6RSK4IFN.js +0 -20
- package/dist/cli-bundle/chunks/chunk-ASJJKA57.js +0 -2
- package/dist/cli-bundle/chunks/chunk-IYAVULRN.js +0 -2
- package/dist/cli-bundle/chunks/chunk-JDPKBV5P.js +0 -2
- package/dist/cli-bundle/chunks/chunk-RRQBHQOV.js +0 -2
- package/dist/cli-bundle/chunks/chunk-SULWTPQO.js +0 -8
- package/dist/cli-bundle/chunks/chunk-V5K52PYW.js +0 -164
- package/dist/cli-bundle/chunks/chunk-XBLOD5TZ.js +0 -2
- package/dist/cli-bundle/chunks/close-VLHNN6YB.js +0 -2
- package/dist/cli-bundle/chunks/close-many-LSJNZKU6.js +0 -2
- package/dist/cli-bundle/chunks/comments-AAIA6A6X.js +0 -2
- package/dist/cli-bundle/chunks/copy-Z7YMLEHN.js +0 -2
- package/dist/cli-bundle/chunks/delete-KNOHHQJM.js +0 -2
- package/dist/cli-bundle/chunks/focus-LFRCGALV.js +0 -2
- package/dist/cli-bundle/chunks/register-mutation-GU3DCECN.js +0 -20
- package/dist/cli-bundle/chunks/register-operations-PTWH727R.js +0 -2
- package/dist/cli-bundle/chunks/register-setup-KWV6UD77.js +0 -2
- package/dist/cli-bundle/chunks/restore-4HZSLILR.js +0 -2
- package/dist/cli-bundle/chunks/update-E6LPS5IV.js +0 -2
- package/dist/cli-bundle/chunks/update-many-OSIC6KRO.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-2VIIXOAD.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-5MTKO7TV.js +0 -153
- package/dist/cli-bundle/focused-chunks/chunk-6GIZK7VQ.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-AOP2WIZZ.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-C2QSL62X.js +0 -28
- package/dist/cli-bundle/focused-chunks/chunk-CL75YW32.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-E7OFMWGT.js +0 -8
- package/dist/cli-bundle/focused-chunks/chunk-I5F5UK3Q.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-QM2BIVK7.js +0 -4
- package/dist/cli-bundle/focused-chunks/chunk-T5TSY36R.js +0 -5
- package/dist/cli-bundle/focused-chunks/chunk-VT6BF2IX.js +0 -2
|
@@ -5,13 +5,26 @@
|
|
|
5
5
|
* transaction contract and normalizes full item documents for CLI round trips.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
!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]="
|
|
8
|
+
!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]="91be2c5d-c495-54bc-ac79-b4c645ad2c6e")}catch(e){}}();
|
|
9
|
+
import crypto from "node:crypto";
|
|
10
|
+
import { normalizeItemId, normalizePrefix } from "../core/item/id.js";
|
|
9
11
|
import { EXIT_CODE, ITEM_PROJECT_CONTEXT_KEYS, } from "../core/shared/constants.js";
|
|
10
12
|
import { PmCliError } from "../core/shared/errors.js";
|
|
11
13
|
import { levenshteinDistanceWithinLimit } from "../core/shared/levenshtein.js";
|
|
12
|
-
import { CLOSE_FLAG_CONTRACTS } from "./cli-contracts/flag-contracts.js";
|
|
14
|
+
import { CLOSE_FLAG_CONTRACTS, RELEASE_FLAG_CONTRACTS, } from "./cli-contracts/flag-contracts.js";
|
|
13
15
|
import { CREATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS, UPDATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS, } from "./cli-contracts/commander-mutation-options.js";
|
|
14
16
|
const MUTATION_ROW_KEYS = ["op", "id", "reason", "options"];
|
|
17
|
+
const REFERENCED_MUTATION_ROW_KEYS = [
|
|
18
|
+
...MUTATION_ROW_KEYS,
|
|
19
|
+
"ref",
|
|
20
|
+
];
|
|
21
|
+
const MUTATION_DOCUMENT_KEYS = ["schema_version", "mutations"];
|
|
22
|
+
const MUTATION_REFERENCE_SOURCE = "[a-z][a-z0-9._-]{0,63}";
|
|
23
|
+
const MUTATION_REFERENCE_PATTERN = new RegExp(`^${MUTATION_REFERENCE_SOURCE}$`, "u");
|
|
24
|
+
const MUTATION_DEPENDENCY_REFERENCE_PATTERN = new RegExp(`(^|,)id=@(${MUTATION_REFERENCE_SOURCE})(?=,|$)`, "gu");
|
|
25
|
+
const flagToOptionKey = (flag) => flag
|
|
26
|
+
.slice(2)
|
|
27
|
+
.replaceAll(/-([a-z])/gu, (_match, letter) => letter.toUpperCase());
|
|
15
28
|
const ITEM_ENVELOPE_KEYS = [
|
|
16
29
|
"item",
|
|
17
30
|
"linked",
|
|
@@ -26,13 +39,16 @@ const READ_ONLY_ITEM_KEYS = new Set([
|
|
|
26
39
|
"created_at",
|
|
27
40
|
"updated_at",
|
|
28
41
|
"closed_at",
|
|
29
|
-
"completed_at",
|
|
30
42
|
"version",
|
|
31
43
|
"format_version",
|
|
32
44
|
"path",
|
|
33
45
|
"author",
|
|
34
46
|
]);
|
|
35
|
-
const UPDATE_READ_ONLY_ITEM_KEYS = new Set([
|
|
47
|
+
const UPDATE_READ_ONLY_ITEM_KEYS = new Set([
|
|
48
|
+
...READ_ONLY_ITEM_KEYS,
|
|
49
|
+
"completed_at",
|
|
50
|
+
"id",
|
|
51
|
+
]);
|
|
36
52
|
const ITEM_FIELD_KEYS = new Set([
|
|
37
53
|
"id",
|
|
38
54
|
"title",
|
|
@@ -56,6 +72,7 @@ const ITEM_FIELD_KEYS = new Set([
|
|
|
56
72
|
"regression",
|
|
57
73
|
"customer_impact",
|
|
58
74
|
"close_reason",
|
|
75
|
+
"completed_at",
|
|
59
76
|
"dependencies",
|
|
60
77
|
"comments",
|
|
61
78
|
"notes",
|
|
@@ -70,9 +87,8 @@ const ITEM_FIELD_KEYS = new Set([
|
|
|
70
87
|
const MUTATION_OPTION_KEYS = {
|
|
71
88
|
create: CREATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS.map((contract) => contract.target),
|
|
72
89
|
update: UPDATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS.map((contract) => contract.target),
|
|
73
|
-
close: CLOSE_FLAG_CONTRACTS.map((contract) => contract.flag
|
|
74
|
-
|
|
75
|
-
.replaceAll(/-([a-z])/gu, (_match, letter) => letter.toUpperCase())).filter((key) => key !== "reason"),
|
|
90
|
+
close: CLOSE_FLAG_CONTRACTS.map((contract) => flagToOptionKey(contract.flag)).filter((key) => key !== "reason"),
|
|
91
|
+
release: RELEASE_FLAG_CONTRACTS.map((contract) => flagToOptionKey(contract.flag)),
|
|
76
92
|
};
|
|
77
93
|
/** Validate and normalize atomic transaction controls at every transport boundary. */
|
|
78
94
|
export function parseAtomicMutationControls(value) {
|
|
@@ -172,8 +188,11 @@ function validateMutationOptions(options, op, index) {
|
|
|
172
188
|
function validateMutationRow(value, index) {
|
|
173
189
|
const mutation = validateMutationObject(value, index);
|
|
174
190
|
const { op, id, reason } = mutation;
|
|
175
|
-
if (op !== "create" &&
|
|
176
|
-
|
|
191
|
+
if (op !== "create" &&
|
|
192
|
+
op !== "update" &&
|
|
193
|
+
op !== "close" &&
|
|
194
|
+
op !== "release") {
|
|
195
|
+
throw new PmCliError(`Mutation ${index + 1} op must be create, update, close, or release.`, EXIT_CODE.USAGE);
|
|
177
196
|
}
|
|
178
197
|
const options = validateMutationOptions(mutation.options, op, index);
|
|
179
198
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
@@ -193,6 +212,15 @@ function validateMutationRow(value, index) {
|
|
|
193
212
|
: { options: options }),
|
|
194
213
|
};
|
|
195
214
|
}
|
|
215
|
+
if (op === "release") {
|
|
216
|
+
return {
|
|
217
|
+
op,
|
|
218
|
+
id: normalizedId,
|
|
219
|
+
...(options === undefined
|
|
220
|
+
? {}
|
|
221
|
+
: { options: options }),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
196
224
|
if (!isPlainObject(options)) {
|
|
197
225
|
throw new PmCliError(`Mutation ${index + 1} ${op} requires an options object.`, EXIT_CODE.USAGE);
|
|
198
226
|
}
|
|
@@ -215,6 +243,187 @@ export function parseItemMutationBatch(input) {
|
|
|
215
243
|
}
|
|
216
244
|
return rows.map((row, index) => validateMutationRow(row, index));
|
|
217
245
|
}
|
|
246
|
+
function deriveReferencedItemId(transactionId, reference, idPrefix) {
|
|
247
|
+
const digest = crypto
|
|
248
|
+
.createHash("sha256")
|
|
249
|
+
.update(transactionId)
|
|
250
|
+
.update("\0")
|
|
251
|
+
.update(reference)
|
|
252
|
+
.digest("hex");
|
|
253
|
+
const token = BigInt(`0x${digest.slice(0, 16)}`)
|
|
254
|
+
.toString(36)
|
|
255
|
+
.padStart(13, "0")
|
|
256
|
+
.slice(-12);
|
|
257
|
+
return `${normalizePrefix(idPrefix)}${token}`;
|
|
258
|
+
}
|
|
259
|
+
function replaceExactMutationReference(value, references) {
|
|
260
|
+
if (typeof value !== "string" || !value.startsWith("@")) {
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
const reference = value.slice(1);
|
|
264
|
+
const resolved = references[reference];
|
|
265
|
+
if (resolved === undefined) {
|
|
266
|
+
throw new PmCliError(`Unknown mutation reference "${value}".`, EXIT_CODE.USAGE);
|
|
267
|
+
}
|
|
268
|
+
return resolved;
|
|
269
|
+
}
|
|
270
|
+
function replaceDependencyReference(value, references) {
|
|
271
|
+
if (typeof value !== "string")
|
|
272
|
+
return value;
|
|
273
|
+
const resolvedValue = value.replace(MUTATION_DEPENDENCY_REFERENCE_PATTERN, (_match, prefix, reference) => {
|
|
274
|
+
const resolved = references[reference];
|
|
275
|
+
if (resolved === undefined) {
|
|
276
|
+
throw new PmCliError(`Unknown mutation reference "@${reference}".`, EXIT_CODE.USAGE);
|
|
277
|
+
}
|
|
278
|
+
return `${prefix}id=${resolved}`;
|
|
279
|
+
});
|
|
280
|
+
if (/(^|,)id=@/u.test(resolvedValue)) {
|
|
281
|
+
throw new PmCliError(`Malformed mutation reference in dependency "${value}".`, EXIT_CODE.USAGE);
|
|
282
|
+
}
|
|
283
|
+
return resolvedValue;
|
|
284
|
+
}
|
|
285
|
+
function referencedAliases(value) {
|
|
286
|
+
if (typeof value !== "string")
|
|
287
|
+
return [];
|
|
288
|
+
if (value.startsWith("@"))
|
|
289
|
+
return [value.slice(1)];
|
|
290
|
+
return [...value.matchAll(MUTATION_DEPENDENCY_REFERENCE_PATTERN)]
|
|
291
|
+
.map((match) => match[1])
|
|
292
|
+
.filter((reference) => reference !== undefined);
|
|
293
|
+
}
|
|
294
|
+
function assertAcyclicCreateReferences(rows) {
|
|
295
|
+
const graph = new Map();
|
|
296
|
+
for (const row of rows) {
|
|
297
|
+
if (row.op !== "create" || typeof row.ref !== "string")
|
|
298
|
+
continue;
|
|
299
|
+
const mutationOptions = isPlainObject(row.options) ? row.options : {};
|
|
300
|
+
const dependencies = Array.isArray(mutationOptions.dep)
|
|
301
|
+
? mutationOptions.dep
|
|
302
|
+
: mutationOptions.dep === undefined
|
|
303
|
+
? []
|
|
304
|
+
: [mutationOptions.dep];
|
|
305
|
+
graph.set(row.ref, [mutationOptions.parent, mutationOptions.blockedBy, ...dependencies]
|
|
306
|
+
.flatMap(referencedAliases));
|
|
307
|
+
}
|
|
308
|
+
const visiting = new Set();
|
|
309
|
+
const visited = new Set();
|
|
310
|
+
const visit = (reference) => {
|
|
311
|
+
if (visiting.has(reference)) {
|
|
312
|
+
throw new PmCliError(`Mutation reference cycle includes "@${reference}".`, EXIT_CODE.USAGE);
|
|
313
|
+
}
|
|
314
|
+
if (visited.has(reference))
|
|
315
|
+
return;
|
|
316
|
+
visiting.add(reference);
|
|
317
|
+
for (const dependency of graph.get(reference)) {
|
|
318
|
+
if (graph.has(dependency))
|
|
319
|
+
visit(dependency);
|
|
320
|
+
}
|
|
321
|
+
visiting.delete(reference);
|
|
322
|
+
visited.add(reference);
|
|
323
|
+
};
|
|
324
|
+
for (const reference of graph.keys())
|
|
325
|
+
visit(reference);
|
|
326
|
+
}
|
|
327
|
+
function parseMutationDocumentRows(input) {
|
|
328
|
+
const parsed = parseJsonValue(input, "Mutation document");
|
|
329
|
+
const envelope = Array.isArray(parsed)
|
|
330
|
+
? { schema_version: 1, mutations: parsed }
|
|
331
|
+
: parsed;
|
|
332
|
+
if (!isPlainObject(envelope)) {
|
|
333
|
+
throw new PmCliError("Mutation document must be a JSON array or object.", EXIT_CODE.USAGE);
|
|
334
|
+
}
|
|
335
|
+
for (const key of Object.keys(envelope)) {
|
|
336
|
+
if (!MUTATION_DOCUMENT_KEYS.includes(key)) {
|
|
337
|
+
throw unknownKeyError("mutation document", key, MUTATION_DOCUMENT_KEYS);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (envelope.schema_version !== undefined &&
|
|
341
|
+
envelope.schema_version !== 1) {
|
|
342
|
+
throw new PmCliError("Mutation document schema_version must be 1.", EXIT_CODE.USAGE);
|
|
343
|
+
}
|
|
344
|
+
if (!Array.isArray(envelope.mutations) || envelope.mutations.length === 0) {
|
|
345
|
+
throw new PmCliError("Mutation document requires a non-empty mutations array.", EXIT_CODE.USAGE);
|
|
346
|
+
}
|
|
347
|
+
return envelope.mutations.map((row, index) => {
|
|
348
|
+
if (!isPlainObject(row)) {
|
|
349
|
+
throw new PmCliError(`Mutation ${index + 1} must be an object.`, EXIT_CODE.USAGE);
|
|
350
|
+
}
|
|
351
|
+
for (const key of Object.keys(row)) {
|
|
352
|
+
if (!REFERENCED_MUTATION_ROW_KEYS.includes(key)) {
|
|
353
|
+
throw unknownKeyError(`mutation ${index + 1}`, key, REFERENCED_MUTATION_ROW_KEYS);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return { ...row };
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
function collectMutationReferences(rows, options) {
|
|
360
|
+
const references = Object.create(null);
|
|
361
|
+
for (const [index, row] of rows.entries()) {
|
|
362
|
+
if (row.ref === undefined)
|
|
363
|
+
continue;
|
|
364
|
+
if (row.op !== "create") {
|
|
365
|
+
throw new PmCliError(`Mutation ${index + 1} ref is only valid for create operations.`, EXIT_CODE.USAGE);
|
|
366
|
+
}
|
|
367
|
+
if (typeof row.ref !== "string" ||
|
|
368
|
+
!MUTATION_REFERENCE_PATTERN.test(row.ref)) {
|
|
369
|
+
throw new PmCliError(`Mutation ${index + 1} ref must match ${MUTATION_REFERENCE_PATTERN.source}.`, EXIT_CODE.USAGE);
|
|
370
|
+
}
|
|
371
|
+
if (references[row.ref] !== undefined) {
|
|
372
|
+
throw new PmCliError(`Duplicate mutation ref "${row.ref}".`, EXIT_CODE.USAGE);
|
|
373
|
+
}
|
|
374
|
+
references[row.ref] =
|
|
375
|
+
typeof row.id === "string" && row.id.trim().length > 0
|
|
376
|
+
? normalizeItemId(row.id, options.idPrefix)
|
|
377
|
+
: deriveReferencedItemId(options.transactionId, row.ref, options.idPrefix);
|
|
378
|
+
}
|
|
379
|
+
return references;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Resolve a versioned heterogeneous mutation document. Create rows may declare
|
|
383
|
+
* a unique `ref`; omitted ids are derived deterministically from the stable
|
|
384
|
+
* transaction id, and `@ref` forward references are supported in target ids,
|
|
385
|
+
* parent/blocked-by options, and dependency entries.
|
|
386
|
+
*/
|
|
387
|
+
export function resolveItemMutationDocument(input, options) {
|
|
388
|
+
const rows = parseMutationDocumentRows(input);
|
|
389
|
+
const references = collectMutationReferences(rows, options);
|
|
390
|
+
assertAcyclicCreateReferences(rows);
|
|
391
|
+
const resolvedRows = rows.map((row) => {
|
|
392
|
+
const resolved = { ...row };
|
|
393
|
+
const reference = typeof resolved.ref === "string" ? resolved.ref : undefined;
|
|
394
|
+
delete resolved.ref;
|
|
395
|
+
if (reference !== undefined) {
|
|
396
|
+
resolved.id = references[reference];
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
resolved.id = replaceExactMutationReference(resolved.id, references);
|
|
400
|
+
}
|
|
401
|
+
if (isPlainObject(resolved.options)) {
|
|
402
|
+
const mutationOptions = { ...resolved.options };
|
|
403
|
+
for (const key of ["parent", "blockedBy"]) {
|
|
404
|
+
if (mutationOptions[key] !== undefined) {
|
|
405
|
+
mutationOptions[key] = replaceExactMutationReference(mutationOptions[key], references);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (Array.isArray(mutationOptions.dep)) {
|
|
409
|
+
mutationOptions.dep = mutationOptions.dep.map((entry) => replaceDependencyReference(entry, references));
|
|
410
|
+
}
|
|
411
|
+
else if (mutationOptions.dep !== undefined) {
|
|
412
|
+
mutationOptions.dep = replaceDependencyReference(mutationOptions.dep, references);
|
|
413
|
+
}
|
|
414
|
+
resolved.options = mutationOptions;
|
|
415
|
+
}
|
|
416
|
+
return resolved;
|
|
417
|
+
});
|
|
418
|
+
const mutations = parseItemMutationBatch(JSON.stringify(resolvedRows));
|
|
419
|
+
const createIds = mutations
|
|
420
|
+
.filter((mutation) => mutation.op === "create")
|
|
421
|
+
.map((mutation) => mutation.id);
|
|
422
|
+
if (new Set(createIds).size !== createIds.length) {
|
|
423
|
+
throw new PmCliError("Duplicate resolved create id in mutation document.", EXIT_CODE.USAGE);
|
|
424
|
+
}
|
|
425
|
+
return { schema_version: 1, mutations, references };
|
|
426
|
+
}
|
|
218
427
|
function serializePairs(value, keys) {
|
|
219
428
|
return keys
|
|
220
429
|
.filter((key) => value[key] !== undefined && value[key] !== null)
|
|
@@ -359,4 +568,4 @@ export function validateItemMutationRows(value) {
|
|
|
359
568
|
return parseItemMutationBatch(JSON.stringify(value));
|
|
360
569
|
}
|
|
361
570
|
//# sourceMappingURL=structured-mutations.js.map
|
|
362
|
-
//# debugId=
|
|
571
|
+
//# debugId=91be2c5d-c495-54bc-ac79-b4c645ad2c6e
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* state while excluding clone-local caches, locks, and recovery journals.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
!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]="
|
|
8
|
+
!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]="3e32be7c-f8a4-547d-8c19-456b4567982e")}catch(e){}}();
|
|
9
9
|
import crypto from "node:crypto";
|
|
10
10
|
import { cp, lstat, mkdir, readFile, readdir, rename, rm, writeFile, } from "node:fs/promises";
|
|
11
11
|
import path from "node:path";
|
|
@@ -13,6 +13,8 @@ import { writeFileAtomic } from "../core/fs/fs-utils.js";
|
|
|
13
13
|
import { appendWorkspaceAuditEvent } from "../core/history/workspace-history.js";
|
|
14
14
|
import { acquireLock } from "../core/lock/lock.js";
|
|
15
15
|
import { getLockPath } from "../core/store/paths.js";
|
|
16
|
+
import { EXIT_CODE } from "../core/shared/constants.js";
|
|
17
|
+
import { PmCliError } from "../core/shared/errors.js";
|
|
16
18
|
/** Current content-addressed workspace snapshot manifest schema identifier. */
|
|
17
19
|
export const SNAPSHOT_SCHEMA = "https://schema.unbrained.dev/pm/workspace-snapshot/v1";
|
|
18
20
|
const SNAPSHOT_RUNTIME_PATH = path.join("runtime", "workspace-snapshots");
|
|
@@ -168,7 +170,21 @@ async function snapshotContents(root, files) {
|
|
|
168
170
|
}
|
|
169
171
|
function validateSnapshotTarget(target) {
|
|
170
172
|
if (!SNAPSHOT_TARGET_PATTERN.test(target)) {
|
|
171
|
-
throw new
|
|
173
|
+
throw new PmCliError("Snapshot names and fingerprints must use lowercase letters, digits, dots, underscores, or hyphens", EXIT_CODE.USAGE, {
|
|
174
|
+
code: "invalid_workspace_snapshot_target",
|
|
175
|
+
required: "Provide a non-empty lowercase snapshot name or 64-character hexadecimal fingerprint.",
|
|
176
|
+
why: "Snapshot targets are used as portable reference filenames and content identities.",
|
|
177
|
+
examples: [
|
|
178
|
+
"pm workspace snapshot list --json",
|
|
179
|
+
"pm workspace snapshot inspect baseline --json",
|
|
180
|
+
],
|
|
181
|
+
nextSteps: [
|
|
182
|
+
"List available snapshots, then retry with a returned name or fingerprint.",
|
|
183
|
+
],
|
|
184
|
+
recovery: {
|
|
185
|
+
suggested_retry: "pm workspace snapshot list --json",
|
|
186
|
+
},
|
|
187
|
+
});
|
|
172
188
|
}
|
|
173
189
|
}
|
|
174
190
|
function isErrno(error, code) {
|
|
@@ -180,15 +196,23 @@ function isErrno(error, code) {
|
|
|
180
196
|
function snapshotStore(pmRoot) {
|
|
181
197
|
return path.join(pmRoot, SNAPSHOT_RUNTIME_PATH);
|
|
182
198
|
}
|
|
199
|
+
function workspaceSnapshotNotFound(target) {
|
|
200
|
+
return new PmCliError(`Unknown workspace snapshot: ${target}`, EXIT_CODE.NOT_FOUND, {
|
|
201
|
+
code: "workspace_snapshot_not_found",
|
|
202
|
+
required: "Use a snapshot name or fingerprint returned by snapshot list.",
|
|
203
|
+
why: "The requested reference or immutable snapshot object does not exist.",
|
|
204
|
+
examples: ["pm workspace snapshot list --json"],
|
|
205
|
+
nextSteps: ["List available snapshots and retry with an exact target."],
|
|
206
|
+
recovery: { suggested_retry: "pm workspace snapshot list --json" },
|
|
207
|
+
});
|
|
208
|
+
}
|
|
183
209
|
async function readSnapshotJson(file, target) {
|
|
184
210
|
try {
|
|
185
211
|
return JSON.parse(await readFile(file, "utf8"));
|
|
186
212
|
}
|
|
187
213
|
catch (error) {
|
|
188
214
|
if (isErrno(error, "ENOENT")) {
|
|
189
|
-
throw
|
|
190
|
-
cause: error,
|
|
191
|
-
});
|
|
215
|
+
throw workspaceSnapshotNotFound(target);
|
|
192
216
|
}
|
|
193
217
|
throw error;
|
|
194
218
|
}
|
|
@@ -199,9 +223,7 @@ async function removeSnapshotEntry(entry, target, recursive) {
|
|
|
199
223
|
}
|
|
200
224
|
catch (error) {
|
|
201
225
|
if (isErrno(error, "ENOENT")) {
|
|
202
|
-
throw
|
|
203
|
-
cause: error,
|
|
204
|
-
});
|
|
226
|
+
throw workspaceSnapshotNotFound(target);
|
|
205
227
|
}
|
|
206
228
|
throw error;
|
|
207
229
|
}
|
|
@@ -256,7 +278,13 @@ export async function createWorkspaceSnapshot(pmRoot, options = {}) {
|
|
|
256
278
|
if (options.name !== undefined) {
|
|
257
279
|
validateSnapshotTarget(options.name);
|
|
258
280
|
if (/^[a-f0-9]{64}$/.test(options.name)) {
|
|
259
|
-
throw new
|
|
281
|
+
throw new PmCliError("Snapshot names must not be 64-character lowercase hexadecimal fingerprints", EXIT_CODE.USAGE, {
|
|
282
|
+
code: "workspace_snapshot_name_reserved_fingerprint",
|
|
283
|
+
required: "Choose a human-readable reference name that cannot be mistaken for a content fingerprint.",
|
|
284
|
+
why: "Exact 64-character hexadecimal values address immutable objects.",
|
|
285
|
+
examples: ["pm workspace snapshot create baseline --json"],
|
|
286
|
+
nextSteps: ["Retry with a shorter descriptive snapshot name."],
|
|
287
|
+
});
|
|
260
288
|
}
|
|
261
289
|
}
|
|
262
290
|
const { manifest, contents } = await buildManifest(pmRoot);
|
|
@@ -307,7 +335,13 @@ export async function inspectWorkspaceSnapshot(pmRoot, target) {
|
|
|
307
335
|
const manifest = await readSnapshotJson(path.join(snapshotStore(pmRoot), "objects", fingerprint, "manifest.json"), target);
|
|
308
336
|
if (manifest.schema !== SNAPSHOT_SCHEMA ||
|
|
309
337
|
manifest.fingerprint !== fingerprint) {
|
|
310
|
-
throw new
|
|
338
|
+
throw new PmCliError(`Snapshot manifest identity mismatch: ${target}`, EXIT_CODE.CONFLICT, {
|
|
339
|
+
code: "workspace_snapshot_manifest_mismatch",
|
|
340
|
+
required: "Use an intact snapshot whose manifest fingerprint matches its object path.",
|
|
341
|
+
why: "Content identity must be verified before snapshot data is trusted.",
|
|
342
|
+
examples: ["pm workspace snapshot list --json"],
|
|
343
|
+
nextSteps: ["Inspect or recreate the snapshot before restoring it."],
|
|
344
|
+
});
|
|
311
345
|
}
|
|
312
346
|
return manifest;
|
|
313
347
|
}
|
|
@@ -399,7 +433,19 @@ export async function planWorkspaceSnapshotRestore(pmRoot, target) {
|
|
|
399
433
|
*/
|
|
400
434
|
export async function restoreWorkspaceSnapshotWithRecovery(pmRoot, target, options = {}) {
|
|
401
435
|
if (options.force !== true) {
|
|
402
|
-
throw new
|
|
436
|
+
throw new PmCliError("Workspace snapshot restore requires explicit force confirmation; inspect the impact with planWorkspaceSnapshotRestore or pm workspace snapshot restore <target> --dry-run, then retry with force", EXIT_CODE.USAGE, {
|
|
437
|
+
code: "workspace_snapshot_force_required",
|
|
438
|
+
required: "Preview the destructive impact, then explicitly confirm the restore.",
|
|
439
|
+
why: "A restore replaces the complete authoritative tracker state.",
|
|
440
|
+
examples: [
|
|
441
|
+
`pm workspace snapshot restore ${target} --dry-run --json`,
|
|
442
|
+
`pm workspace snapshot restore ${target} --force --json`,
|
|
443
|
+
],
|
|
444
|
+
nextSteps: ["Review the dry-run counts before retrying with --force."],
|
|
445
|
+
recovery: {
|
|
446
|
+
suggested_retry: `pm workspace snapshot restore ${target} --dry-run --json`,
|
|
447
|
+
},
|
|
448
|
+
});
|
|
403
449
|
}
|
|
404
450
|
const author = options.author?.trim() || "pm-sdk";
|
|
405
451
|
const lockTtlSeconds = options.lockTtlSeconds ?? 60;
|
|
@@ -518,4 +564,4 @@ export async function deleteWorkspaceSnapshot(pmRoot, target) {
|
|
|
518
564
|
return { deleted: "object", target };
|
|
519
565
|
}
|
|
520
566
|
//# sourceMappingURL=workspace-snapshot.js.map
|
|
521
|
-
//# debugId=
|
|
567
|
+
//# debugId=3e32be7c-f8a4-547d-8c19-456b4567982e
|
package/dist/sdk/workspace.js
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
* Maintains repository-scaffold contracts shared by the public SDK and CLI.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
!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]="
|
|
7
|
+
!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]="bbfa5b19-d801-582a-98e8-0c4925dd57c0")}catch(e){}}();
|
|
8
8
|
import { readFile, writeFile } from "node:fs/promises";
|
|
9
9
|
import path from "node:path";
|
|
10
|
+
import { EXIT_CODE } from "../core/shared/constants.js";
|
|
11
|
+
import { PmCliError } from "../core/shared/errors.js";
|
|
10
12
|
/** Opening marker for the init-owned ignore block. */
|
|
11
13
|
export const PM_GITIGNORE_START = "# pm-cli:runtime-cache:start";
|
|
12
14
|
/** Closing marker for the init-owned ignore block. */
|
|
@@ -31,6 +33,30 @@ export const PM_GITIGNORE_RUNTIME_DIRECTORIES = [
|
|
|
31
33
|
];
|
|
32
34
|
/** Tracker-relative curated search evidence that remains version controlled. */
|
|
33
35
|
export const PM_GITIGNORE_TRACKED_FILES = ["search/eval-queries.json"];
|
|
36
|
+
/** Convert expected workspace permission failures into stable, path-safe recovery. */
|
|
37
|
+
async function withGitignorePermissionRecovery(operation) {
|
|
38
|
+
try {
|
|
39
|
+
return await operation();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error instanceof Error &&
|
|
43
|
+
"code" in error &&
|
|
44
|
+
typeof error.code === "string" &&
|
|
45
|
+
["EACCES", "EPERM", "EROFS"].includes(error.code)) {
|
|
46
|
+
throw new PmCliError("The workspace .gitignore is not writable.", EXIT_CODE.GENERIC_FAILURE, {
|
|
47
|
+
code: "init_gitignore_unwritable",
|
|
48
|
+
reason: error.code.toLowerCase(),
|
|
49
|
+
required: "Grant the current user read and write access to the workspace .gitignore before initialization.",
|
|
50
|
+
why: "pm init must publish its managed runtime-cache ignore fence without replacing unrelated entries.",
|
|
51
|
+
nextSteps: [
|
|
52
|
+
"Grant read and write access to the workspace .gitignore and rerun pm init.",
|
|
53
|
+
"If the workspace is intentionally read-only, initialize pm in a writable workspace or clone.",
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
34
60
|
function normalizeTrackerRelativeRoot(trackerRelativeRoot) {
|
|
35
61
|
return trackerRelativeRoot
|
|
36
62
|
.replaceAll("\\", "/")
|
|
@@ -78,7 +104,7 @@ export async function ensurePmGitignore(workspaceRoot, options = {}) {
|
|
|
78
104
|
}
|
|
79
105
|
let current = "";
|
|
80
106
|
try {
|
|
81
|
-
current = await readFile(gitignorePath, "utf8");
|
|
107
|
+
current = await withGitignorePermissionRecovery(() => readFile(gitignorePath, "utf8"));
|
|
82
108
|
}
|
|
83
109
|
catch (error) {
|
|
84
110
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
|
|
@@ -95,8 +121,8 @@ export async function ensurePmGitignore(workspaceRoot, options = {}) {
|
|
|
95
121
|
if (next === current) {
|
|
96
122
|
return { path: gitignorePath, changed: false };
|
|
97
123
|
}
|
|
98
|
-
await writeFile(gitignorePath, next, "utf8");
|
|
124
|
+
await withGitignorePermissionRecovery(() => writeFile(gitignorePath, next, "utf8"));
|
|
99
125
|
return { path: gitignorePath, changed: true };
|
|
100
126
|
}
|
|
101
127
|
//# sourceMappingURL=workspace.js.map
|
|
102
|
-
//# debugId=
|
|
128
|
+
//# debugId=bbfa5b19-d801-582a-98e8-0c4925dd57c0
|
package/docs/COMMANDS.md
CHANGED
|
@@ -27,7 +27,7 @@ Tracked documentation work: [pm-u9d0](../.agents/pm/epics/pm-u9d0.toon).
|
|
|
27
27
|
| ------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
28
28
|
| Bootstrap | `init`, `config`, `health`, `telemetry` | create and inspect tracker setup |
|
|
29
29
|
| Lifecycle | `create`, `copy`, `focus`, `claim`, `update`, `append`, `close`, `release`, `delete`, `start-task`, `pause-task`, `close-task` | mutate item state |
|
|
30
|
-
| Bulk | `item mutate`, `update-many`, `close-many`
|
|
30
|
+
| Bulk | `item mutate`, `item complete`, `update-many`, `close-many` | atomically commit heterogeneous SDK mutation batches or evidence-backed completion, or apply one change across a matched, dry-run-previewed set with a rollback checkpoint |
|
|
31
31
|
| Scheduling | `meet`, `event`, `remind` | low-friction Meeting/Event/Reminder creation |
|
|
32
32
|
| Planning | `plan create`, `plan add-step`, `plan update-step`, `plan complete-step`, `plan link`, `plan approve`, `plan materialize` | agent-optimized living plans with ordered steps, evidence, decisions, validation, and materialization |
|
|
33
33
|
| Links | `files`, `docs`, `test`, `deps` | connect items to artifacts, tests, and relationships |
|
|
@@ -82,6 +82,8 @@ pm install npm:@scope/pm-package --project
|
|
|
82
82
|
pm package describe --project # by-name surface map of every loaded package
|
|
83
83
|
pm package describe my-package --markdown --output docs/my-package-reference.md
|
|
84
84
|
pm package doctor --project --detail summary
|
|
85
|
+
pm package migrate --project --dry-run --json
|
|
86
|
+
pm package migrate --project --json
|
|
85
87
|
pm upgrade --dry-run
|
|
86
88
|
pm upgrade --packages-only
|
|
87
89
|
pm upgrade --cli-only --repair
|
|
@@ -89,6 +91,13 @@ pm upgrade --cli-only --repair
|
|
|
89
91
|
|
|
90
92
|
`pm install` and `pm package` are the preferred package-first workflow. `pm package` and `pm extension` bare invocations default to `--explore` so agents can list installed packages without remembering an action flag. `pm install '*'`, shell-expanded `pm install *`, and `pm install all` install bundled first-party packages. `pm extension` remains as a compatibility command for direct extension lifecycle operations.
|
|
91
93
|
Install output includes a light `verification` summary with target tracker root, activation state, registered commands/actions/item types, and an `ok|degraded` health verdict. Runtime activation failure sets the command result and process exit status to failure; inspect `activation_diagnostics` and `command_discovery.next_steps` for the exact recovery path.
|
|
94
|
+
Bare install names use bundled aliases before installed npm packages. Every
|
|
95
|
+
install result reports `source_resolution`; when both candidates exist it marks
|
|
96
|
+
the choice ambiguous and provides explicit bare and `npm:` retry commands.
|
|
97
|
+
`package migrate` plans or applies active migration registrations and writes
|
|
98
|
+
durable workspace-history receipts; a successful migration is skipped on later
|
|
99
|
+
processes, while a failed migration remains retryable. `extension migrate` is
|
|
100
|
+
the compatibility spelling.
|
|
92
101
|
When package-owned commands are unavailable, usage guidance includes an install-ready retry (for example `pm install calendar`, `pm install search-advanced`, `pm install governance-audit`, or `pm install guide-shell`).
|
|
93
102
|
|
|
94
103
|
## Triage
|
|
@@ -516,20 +525,30 @@ row summaries. See tracker item [pm-awe3t6](../.agents/pm/issues/pm-awe3t6.toon)
|
|
|
516
525
|
|
|
517
526
|
### Atomic heterogeneous mutation batches
|
|
518
527
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
528
|
+
Tracked by [pm-o8z748](../.agents/pm/issues/pm-o8z748.toon) and
|
|
529
|
+
[pm-cyn0y6](../.agents/pm/issues/pm-cyn0y6.toon).
|
|
530
|
+
|
|
531
|
+
`pm item mutate` is the noun-first CLI adapter over the public SDK resolver and
|
|
532
|
+
`commitItemMutations` primitive. Pipe either the legacy non-empty JSON array or
|
|
533
|
+
a versioned `{ "schema_version": 1, "mutations": [...] }` document, provide one
|
|
534
|
+
stable transaction id, and mix create/update/close/release operations in order.
|
|
535
|
+
Create rows may declare a unique `ref` and omit `id`; exact `@ref` values work
|
|
536
|
+
in target ids, `parent`, `blockedBy`, and dependency `id` fields. The resolver
|
|
537
|
+
derives replay-stable ids before the writer lock and returns a `references`
|
|
538
|
+
receipt:
|
|
523
539
|
|
|
524
540
|
```bash
|
|
525
541
|
pm item mutate \
|
|
526
542
|
--transaction-id sync-2026-07-20-001 \
|
|
527
543
|
--stdin-json <<'JSON'
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
]
|
|
544
|
+
{
|
|
545
|
+
"schema_version": 1,
|
|
546
|
+
"mutations": [
|
|
547
|
+
{"op":"create","ref":"initiative","options":{"title":"Imported initiative","type":"Epic"}},
|
|
548
|
+
{"op":"create","ref":"delivery","options":{"title":"Deliver it","type":"Feature","parent":"@initiative","dep":["id=@initiative,kind=implements"]}},
|
|
549
|
+
{"op":"update","id":"@initiative","options":{"addTags":["synced"]}}
|
|
550
|
+
]
|
|
551
|
+
}
|
|
533
552
|
JSON
|
|
534
553
|
```
|
|
535
554
|
|
|
@@ -540,6 +559,26 @@ step. `--create-compensation close|delete`, `--lock-ttl-seconds`, and
|
|
|
540
559
|
`--lock-wait-ms` expose the transaction safety controls. The equivalent MCP
|
|
541
560
|
surface is `pm_mutate`.
|
|
542
561
|
|
|
562
|
+
`pm item complete` composes evidence, governed closure, and claim release into
|
|
563
|
+
one compensating SDK transaction. It accepts the normal repeatable evidence
|
|
564
|
+
flags and can preview the exact ordered mutations before writing:
|
|
565
|
+
|
|
566
|
+
```bash
|
|
567
|
+
pm item complete pm-a1b2 "Implemented and verified" \
|
|
568
|
+
--transaction-id complete-pm-a1b2-v1 \
|
|
569
|
+
--file path=src/index.ts,scope=project,note=implementation \
|
|
570
|
+
--doc path=docs/SDK.md,scope=project,note=contract \
|
|
571
|
+
--test command="pnpm test",scope=project,timeout_seconds=240 \
|
|
572
|
+
--comment "Evidence: full verification passed" \
|
|
573
|
+
--validate-close warn
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
If any phase fails, the SDK restores the evidence, lifecycle, and prior claim.
|
|
577
|
+
Reusing the exact transaction id and payload returns the committed result;
|
|
578
|
+
changing a replayed payload fails against the journal plan fingerprint.
|
|
579
|
+
`--lock-ttl-seconds` and `--lock-wait-ms` tune the same workspace transaction
|
|
580
|
+
controls exposed by `pm item mutate` for slow or contended trackers.
|
|
581
|
+
|
|
543
582
|
## Focus (session default parent)
|
|
544
583
|
|
|
545
584
|
`pm focus` sets a session "focused" item so subsequent `pm create` calls default their `--parent` to it — project management is context management, and focus keeps new work attached to the active parent without restating `--parent` every time.
|
package/docs/EXTENSIONS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Packages and Extensions
|
|
2
2
|
|
|
3
|
-
Extension flags declared with `list: true` accumulate repeated long/short alias occurrences and comma-separated values into one array. Dynamic commands preserve flag-like variadic content after `--`, and package handlers can use the public `suppressHostOutput()` protocol when they already emitted streaming, binary, or pre-rendered output. Declarative blueprints are also checked for reserved item-field collisions during SDK lint/preflight and harness activation, so a package cannot pass author-time validation and then fail only when users create or update items. Local archive installation, command ownership, and MCP custom-field diagnostics are tracked by [pm-lw6acw](../.agents/pm/issues/pm-lw6acw.toon), [pm-6z0wzf](../.agents/pm/issues/pm-6z0wzf.toon), and [pm-yfdav2](../.agents/pm/issues/pm-yfdav2.toon). Transactional mutation guards, host-bound command test SDKs, and installed custom-type lifecycle parity are tracked by [pm-hx23u5](../.agents/pm/issues/pm-hx23u5.toon), [pm-wx2lr5](../.agents/pm/issues/pm-wx2lr5.toon), and [pm-scga6k](../.agents/pm/issues/pm-scga6k.toon).
|
|
3
|
+
Extension flags declared with `list: true` accumulate repeated long/short alias occurrences and comma-separated values into one array. Dynamic commands preserve flag-like variadic content after `--`, and package handlers can use the public `suppressHostOutput()` protocol when they already emitted streaming, binary, or pre-rendered output. Declarative blueprints are also checked for reserved item-field collisions during SDK lint/preflight and harness activation, so a package cannot pass author-time validation and then fail only when users create or update items. Local archive installation, command ownership, and MCP custom-field diagnostics are tracked by [pm-lw6acw](../.agents/pm/issues/pm-lw6acw.toon), [pm-6z0wzf](../.agents/pm/issues/pm-6z0wzf.toon), and [pm-yfdav2](../.agents/pm/issues/pm-yfdav2.toon). Transactional mutation guards, host-bound command test SDKs, and installed custom-type lifecycle parity are tracked by [pm-hx23u5](../.agents/pm/issues/pm-hx23u5.toon), [pm-wx2lr5](../.agents/pm/issues/pm-wx2lr5.toon), and [pm-scga6k](../.agents/pm/issues/pm-scga6k.toon). Durable migration application, explicit source resolution, and composable preflight ownership are covered in [Extension Lifecycle Contracts](EXTENSION_LIFECYCLE.md).
|
|
4
4
|
|
|
5
5
|
Packages add optional `pm` workflows without changing the core CLI. A package can ship one or more runtime extensions plus metadata such as docs and examples. Prefer the package-first commands in new docs and automation:
|
|
6
6
|
|
|
@@ -38,7 +38,7 @@ pm install calendar --project
|
|
|
38
38
|
pm install search-advanced --project
|
|
39
39
|
pm install kanban --project
|
|
40
40
|
```
|
|
41
|
-
`pm install '*'`, `pm install all`, and shell-expanded `pm install *` are normalized to the same bundled install-all request. First-party package aliases come from each package manifest, with a fallback derived from the `packages/pm-*` directory name.
|
|
41
|
+
`pm install '*'`, `pm install all`, and shell-expanded `pm install *` are normalized to the same bundled install-all request. First-party package aliases come from each package manifest, with a fallback derived from the `packages/pm-*` directory name. A bare bundled alias that also names an installed npm package reports both explicit choices in `source_resolution`; see [Extension Lifecycle Contracts](EXTENSION_LIFECYCLE.md).
|
|
42
42
|
|
|
43
43
|
External registry packages are installed by exact package name. If `npm:<name>` returns a registry 404, JSON error output includes `fallback_candidates` and `next_best_command`; unpublished first-party packages fall back to `pm install --project github.com/unbraind/<name>`. Install results include package-owned `command_paths`, `action_paths`, `contributions`, `command_discovery`, and a light `verification` block covering the target tracker, activation status, registered commands/actions/item types, and health verdict. Agents should consume those fields instead of guessing from the package name or immediately spending another invocation on doctor. A successful activation persists the versioned contribution inventory in `.managed-extensions.json`; subsequent discovery can enumerate command handlers, hooks, parser/renderer targets, schema names, and the other registered surfaces without importing the package module. A failed runtime activation returns `ok: false`, `activated: false`, a non-zero CLI exit, and actionable diagnostics; missing SDK resolution adds an explicit dependency recovery step. Local installs are containment-safe when the extension destination is nested inside the source checkout: pm stages the package outside the source and prunes the destination, `.agents`, `node_modules`, and install-backup directories before copying, so reinstalling cannot recursively copy tracker history, host dependencies, or prior backups.
|
|
44
44
|
Local `.tgz` and `.tar.gz` npm archives are inspected and extracted in an isolated temporary directory without invoking a shell. Archives must contain one `package/package.json` root, regular files/directories only, and bounded entry and expanded-byte totals. Absolute paths, traversal, alternate roots, links, device entries, oversized entries, and decompression-ratio abuse fail before installation. The managed source remains the original archive path, so reload and upgrade provenance do not point at a temporary extraction directory.
|
|
@@ -270,7 +270,7 @@ Surface tokens include command handlers/overrides, parser/preflight/services/ren
|
|
|
270
270
|
|
|
271
271
|
## Registration Collisions
|
|
272
272
|
|
|
273
|
-
Some extension surfaces are intentionally single-winner: command handlers and overrides, parser overrides, preflight overrides, and format renderers. If multiple packages register the same single-winner surface,
|
|
273
|
+
Some extension surfaces are intentionally single-winner: command handlers and overrides, parser overrides, preflight overrides, and format renderers. Activation is deterministic: lower manifest `priority` values load first, omitted priority defaults to `100`, equal priorities sort by package identity/path, and the last registration wins. If multiple packages register the same single-winner surface, `pm package doctor` / `pm health` report deterministic `extension_*_collision` warnings whose suffix names the winning layer/package before the displaced layer/package. `pm package describe --json` also exposes `command_ownership`: every claimant in activation order, the effective winner, collision state, and the explicit `last_activated_wins` policy. SDK hosts can build the identical table with `buildExtensionDescribeResult` and the exported `ExtensionCommandOwnership` contracts. Renderer ownership is evaluated per command: same-format renderers with disjoint `commands` lists safely coexist, while an unscoped or overlapping claim still warns; runtime `resultDiscriminator` predicates alone cannot prove static disjointness. Tracked by [pm-6mjxgq](../.agents/pm/issues/pm-6mjxgq.toon).
|
|
274
274
|
|
|
275
275
|
For definition-based commands, validation is isolated per command: a malformed definition is recorded as `extension_command_quarantined:*` with a registration trace while valid siblings continue to activate. Unknown-command recovery reports that failure without recommending reinstallation.
|
|
276
276
|
Use the warning details to resolve the overlap:
|