@unbrained/pm-cli 2026.5.12 → 2026.5.18
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 +4 -4
- package/AGENTS.md +78 -457
- package/CHANGELOG.md +22 -0
- package/CONTRIBUTING.md +1 -0
- package/PRD.md +7 -28
- package/README.md +8 -14
- package/dist/cli/argv-utils.js +4 -1
- package/dist/cli/argv-utils.js.map +1 -1
- package/dist/cli/bootstrap-args.js +4 -1
- package/dist/cli/bootstrap-args.js.map +1 -1
- package/dist/cli/commander-usage.js +32 -1
- package/dist/cli/commander-usage.js.map +1 -1
- package/dist/cli/commands/activity.js +23 -5
- package/dist/cli/commands/activity.js.map +1 -1
- package/dist/cli/commands/aggregate.js +5 -2
- package/dist/cli/commands/aggregate.js.map +1 -1
- package/dist/cli/commands/append.js +4 -1
- package/dist/cli/commands/append.js.map +1 -1
- package/dist/cli/commands/calendar.js +9 -3
- package/dist/cli/commands/calendar.js.map +1 -1
- package/dist/cli/commands/claim.d.ts +3 -0
- package/dist/cli/commands/claim.js +19 -3
- package/dist/cli/commands/claim.js.map +1 -1
- package/dist/cli/commands/close.js +4 -1
- package/dist/cli/commands/close.js.map +1 -1
- package/dist/cli/commands/comments-audit.js +4 -1
- package/dist/cli/commands/comments-audit.js.map +1 -1
- package/dist/cli/commands/comments.js +4 -1
- package/dist/cli/commands/comments.js.map +1 -1
- package/dist/cli/commands/completion.js +98 -2
- package/dist/cli/commands/completion.js.map +1 -1
- package/dist/cli/commands/config.js +4 -1
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/context.js +19 -5
- package/dist/cli/commands/context.js.map +1 -1
- package/dist/cli/commands/contracts.d.ts +9 -0
- package/dist/cli/commands/contracts.js +205 -49
- package/dist/cli/commands/contracts.js.map +1 -1
- package/dist/cli/commands/create.js +88 -9
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/dedupe-audit.js +4 -1
- package/dist/cli/commands/dedupe-audit.js.map +1 -1
- package/dist/cli/commands/delete.js +4 -1
- package/dist/cli/commands/delete.js.map +1 -1
- package/dist/cli/commands/deps.js +4 -1
- package/dist/cli/commands/deps.js.map +1 -1
- package/dist/cli/commands/docs.js +4 -1
- package/dist/cli/commands/docs.js.map +1 -1
- package/dist/cli/commands/extension.d.ts +7 -2
- package/dist/cli/commands/extension.js +360 -64
- package/dist/cli/commands/extension.js.map +1 -1
- package/dist/cli/commands/files.js +4 -1
- package/dist/cli/commands/files.js.map +1 -1
- package/dist/cli/commands/gc.js +4 -1
- package/dist/cli/commands/gc.js.map +1 -1
- package/dist/cli/commands/get.d.ts +7 -3
- package/dist/cli/commands/get.js +91 -18
- package/dist/cli/commands/get.js.map +1 -1
- package/dist/cli/commands/guide.js +6 -8
- package/dist/cli/commands/guide.js.map +1 -1
- package/dist/cli/commands/health.d.ts +4 -0
- package/dist/cli/commands/health.js +31 -8
- package/dist/cli/commands/health.js.map +1 -1
- package/dist/cli/commands/history-redact.d.ts +42 -0
- package/dist/cli/commands/history-redact.js +559 -0
- package/dist/cli/commands/history-redact.js.map +1 -0
- package/dist/cli/commands/history.d.ts +4 -0
- package/dist/cli/commands/history.js +14 -3
- package/dist/cli/commands/history.js.map +1 -1
- package/dist/cli/commands/index.d.ts +2 -8
- package/dist/cli/commands/index.js +6 -9
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init-agent-guidance.d.ts +31 -0
- package/dist/cli/commands/init-agent-guidance.js +336 -0
- package/dist/cli/commands/init-agent-guidance.js.map +1 -0
- package/dist/cli/commands/init.d.ts +14 -0
- package/dist/cli/commands/init.js +75 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/learnings.js +4 -1
- package/dist/cli/commands/learnings.js.map +1 -1
- package/dist/cli/commands/list.d.ts +1 -0
- package/dist/cli/commands/list.js +42 -18
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/metadata-normalizers.js +4 -1
- package/dist/cli/commands/metadata-normalizers.js.map +1 -1
- package/dist/cli/commands/normalize.js +4 -1
- package/dist/cli/commands/normalize.js.map +1 -1
- package/dist/cli/commands/notes.js +4 -1
- package/dist/cli/commands/notes.js.map +1 -1
- package/dist/cli/commands/plan.d.ts +118 -0
- package/dist/cli/commands/plan.js +975 -0
- package/dist/cli/commands/plan.js.map +1 -0
- package/dist/cli/commands/reindex.d.ts +8 -0
- package/dist/cli/commands/reindex.js +100 -24
- package/dist/cli/commands/reindex.js.map +1 -1
- package/dist/cli/commands/restore.js +4 -1
- package/dist/cli/commands/restore.js.map +1 -1
- package/dist/cli/commands/search.js +58 -27
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/stats.js +4 -1
- package/dist/cli/commands/stats.js.map +1 -1
- package/dist/cli/commands/templates.js +4 -1
- package/dist/cli/commands/templates.js.map +1 -1
- package/dist/cli/commands/test-all.js +4 -1
- package/dist/cli/commands/test-all.js.map +1 -1
- package/dist/cli/commands/test-runs.js +4 -1
- package/dist/cli/commands/test-runs.js.map +1 -1
- package/dist/cli/commands/test.js +4 -1
- package/dist/cli/commands/test.js.map +1 -1
- package/dist/cli/commands/update-many.js +4 -1
- package/dist/cli/commands/update-many.js.map +1 -1
- package/dist/cli/commands/update.js +114 -71
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/commands/upgrade.js +6 -3
- package/dist/cli/commands/upgrade.js.map +1 -1
- package/dist/cli/commands/validate.js +32 -4
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/error-guidance.js +5 -2
- package/dist/cli/error-guidance.js.map +1 -1
- package/dist/cli/extension-command-help.js +4 -1
- package/dist/cli/extension-command-help.js.map +1 -1
- package/dist/cli/extension-command-options.js +4 -1
- package/dist/cli/extension-command-options.js.map +1 -1
- package/dist/cli/guide-topics.js +4 -1
- package/dist/cli/guide-topics.js.map +1 -1
- package/dist/cli/help-content.js +52 -33
- package/dist/cli/help-content.js.map +1 -1
- package/dist/cli/help-json-payload.js +4 -1
- package/dist/cli/help-json-payload.js.map +1 -1
- package/dist/cli/main.js +276 -32
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/migration-gates.js +4 -1
- package/dist/cli/migration-gates.js.map +1 -1
- package/dist/cli/register-list-query.js +55 -150
- package/dist/cli/register-list-query.js.map +1 -1
- package/dist/cli/register-mutation.js +277 -261
- package/dist/cli/register-mutation.js.map +1 -1
- package/dist/cli/register-operations.js +62 -199
- package/dist/cli/register-operations.js.map +1 -1
- package/dist/cli/register-setup.js +55 -146
- package/dist/cli/register-setup.js.map +1 -1
- package/dist/cli/registration-helpers.d.ts +2 -2
- package/dist/cli/registration-helpers.js +11 -21
- package/dist/cli/registration-helpers.js.map +1 -1
- package/dist/cli/shared-parsers.js +4 -1
- package/dist/cli/shared-parsers.js.map +1 -1
- package/dist/cli/telemetry-flush.js +4 -1
- package/dist/cli/telemetry-flush.js.map +1 -1
- package/dist/cli.js +45 -3
- package/dist/cli.js.map +1 -1
- package/dist/core/extensions/extension-types.js +4 -1
- package/dist/core/extensions/extension-types.js.map +1 -1
- package/dist/core/extensions/index.js +4 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/item-fields.js +4 -1
- package/dist/core/extensions/item-fields.js.map +1 -1
- package/dist/core/extensions/loader.js +84 -54
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runtime-registrations.js +4 -1
- package/dist/core/extensions/runtime-registrations.js.map +1 -1
- package/dist/core/fs/fs-utils.js +4 -1
- package/dist/core/fs/fs-utils.js.map +1 -1
- package/dist/core/fs/index.js +4 -1
- package/dist/core/fs/index.js.map +1 -1
- package/dist/core/history/history-stream-policy.js +4 -1
- package/dist/core/history/history-stream-policy.js.map +1 -1
- package/dist/core/history/history.js +4 -1
- package/dist/core/history/history.js.map +1 -1
- package/dist/core/history/index.js +4 -1
- package/dist/core/history/index.js.map +1 -1
- package/dist/core/item/id.js +4 -1
- package/dist/core/item/id.js.map +1 -1
- package/dist/core/item/index.js +4 -1
- package/dist/core/item/index.js.map +1 -1
- package/dist/core/item/item-format.js +241 -2
- package/dist/core/item/item-format.js.map +1 -1
- package/dist/core/item/parent-reference-policy.js +4 -1
- package/dist/core/item/parent-reference-policy.js.map +1 -1
- package/dist/core/item/parse.js +33 -3
- package/dist/core/item/parse.js.map +1 -1
- package/dist/core/item/sprint-release-format.js +4 -1
- package/dist/core/item/sprint-release-format.js.map +1 -1
- package/dist/core/item/status.js +4 -1
- package/dist/core/item/status.js.map +1 -1
- package/dist/core/item/type-registry.js +4 -1
- package/dist/core/item/type-registry.js.map +1 -1
- package/dist/core/lock/index.js +4 -1
- package/dist/core/lock/index.js.map +1 -1
- package/dist/core/lock/lock.js +4 -1
- package/dist/core/lock/lock.js.map +1 -1
- package/dist/core/output/command-aware.js +4 -1
- package/dist/core/output/command-aware.js.map +1 -1
- package/dist/core/output/output.d.ts +4 -0
- package/dist/core/output/output.js +47 -6
- package/dist/core/output/output.js.map +1 -1
- package/dist/core/packages/manifest.d.ts +27 -1
- package/dist/core/packages/manifest.js +87 -1
- package/dist/core/packages/manifest.js.map +1 -1
- package/dist/core/packages/root.d.ts +3 -0
- package/dist/core/packages/root.js +51 -0
- package/dist/core/packages/root.js.map +1 -0
- package/dist/core/schema/runtime-field-filters.js +4 -1
- package/dist/core/schema/runtime-field-filters.js.map +1 -1
- package/dist/core/schema/runtime-field-values.js +4 -1
- package/dist/core/schema/runtime-field-values.js.map +1 -1
- package/dist/core/schema/runtime-schema.js +4 -1
- package/dist/core/schema/runtime-schema.js.map +1 -1
- package/dist/core/search/cache.js +7 -2
- package/dist/core/search/cache.js.map +1 -1
- package/dist/core/search/corpus.d.ts +2 -0
- package/dist/core/search/corpus.js +77 -2
- package/dist/core/search/corpus.js.map +1 -1
- package/dist/core/search/embedding-batches.d.ts +13 -1
- package/dist/core/search/embedding-batches.js +40 -8
- package/dist/core/search/embedding-batches.js.map +1 -1
- package/dist/core/search/http-client.js +4 -1
- package/dist/core/search/http-client.js.map +1 -1
- package/dist/core/search/providers.js +4 -1
- package/dist/core/search/providers.js.map +1 -1
- package/dist/core/search/semantic-defaults.js +11 -2
- package/dist/core/search/semantic-defaults.js.map +1 -1
- package/dist/core/search/vector-stores.js +4 -1
- package/dist/core/search/vector-stores.js.map +1 -1
- package/dist/core/sentry/helpers.js +4 -1
- package/dist/core/sentry/helpers.js.map +1 -1
- package/dist/core/sentry/instrument.js +10 -13
- package/dist/core/sentry/instrument.js.map +1 -1
- package/dist/core/shared/command-types.js +4 -1
- package/dist/core/shared/command-types.js.map +1 -1
- package/dist/core/shared/conflict-markers.js +4 -1
- package/dist/core/shared/conflict-markers.js.map +1 -1
- package/dist/core/shared/constants.d.ts +2 -2
- package/dist/core/shared/constants.js +24 -1
- package/dist/core/shared/constants.js.map +1 -1
- package/dist/core/shared/errors.js +4 -1
- package/dist/core/shared/errors.js.map +1 -1
- package/dist/core/shared/index.js +4 -1
- package/dist/core/shared/index.js.map +1 -1
- package/dist/core/shared/levenshtein.js +4 -1
- package/dist/core/shared/levenshtein.js.map +1 -1
- package/dist/core/shared/primitives.js +4 -1
- package/dist/core/shared/primitives.js.map +1 -1
- package/dist/core/shared/serialization.js +4 -1
- package/dist/core/shared/serialization.js.map +1 -1
- package/dist/core/shared/text-normalization.js +4 -1
- package/dist/core/shared/text-normalization.js.map +1 -1
- package/dist/core/shared/time.js +4 -1
- package/dist/core/shared/time.js.map +1 -1
- package/dist/core/store/front-matter-cache.d.ts +8 -1
- package/dist/core/store/front-matter-cache.js +28 -14
- package/dist/core/store/front-matter-cache.js.map +1 -1
- package/dist/core/store/index.js +4 -1
- package/dist/core/store/index.js.map +1 -1
- package/dist/core/store/item-format-migration.js +4 -1
- package/dist/core/store/item-format-migration.js.map +1 -1
- package/dist/core/store/item-store.d.ts +2 -0
- package/dist/core/store/item-store.js +66 -3
- package/dist/core/store/item-store.js.map +1 -1
- package/dist/core/store/paths.js +4 -1
- package/dist/core/store/paths.js.map +1 -1
- package/dist/core/store/settings.js +39 -1
- package/dist/core/store/settings.js.map +1 -1
- package/dist/core/telemetry/consent.js +4 -1
- package/dist/core/telemetry/consent.js.map +1 -1
- package/dist/core/telemetry/observability.d.ts +1 -1
- package/dist/core/telemetry/observability.js +11 -2
- package/dist/core/telemetry/observability.js.map +1 -1
- package/dist/core/telemetry/runtime.js +31 -5
- package/dist/core/telemetry/runtime.js.map +1 -1
- package/dist/core/test/background-runs.js +4 -1
- package/dist/core/test/background-runs.js.map +1 -1
- package/dist/core/test/item-test-run-tracking.js +4 -1
- package/dist/core/test/item-test-run-tracking.js.map +1 -1
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.js +212 -53
- package/dist/mcp/server.js.map +1 -1
- package/dist/sdk/cli-contracts/commander-mutation-options.d.ts +7 -0
- package/dist/sdk/cli-contracts/commander-mutation-options.js +484 -0
- package/dist/sdk/cli-contracts/commander-mutation-options.js.map +1 -0
- package/dist/sdk/cli-contracts/commander-types.d.ts +21 -0
- package/dist/sdk/cli-contracts/commander-types.js +95 -0
- package/dist/sdk/cli-contracts/commander-types.js.map +1 -0
- package/dist/sdk/cli-contracts.d.ts +10 -17
- package/dist/sdk/cli-contracts.js +232 -282
- package/dist/sdk/cli-contracts.js.map +1 -1
- package/dist/sdk/index.d.ts +2 -1
- package/dist/sdk/index.js +5 -1
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/runtime.d.ts +29 -0
- package/dist/sdk/runtime.js +31 -0
- package/dist/sdk/runtime.js.map +1 -0
- package/dist/types/index.js +4 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types.d.ts +86 -2
- package/dist/types.js +34 -1
- package/dist/types.js.map +1 -1
- package/docs/AGENT_GUIDE.md +16 -6
- package/docs/CLAUDE_CODE_PLUGIN.md +10 -10
- package/docs/CODEX_PLUGIN.md +2 -2
- package/docs/COMMANDS.md +83 -8
- package/docs/CONFIGURATION.md +4 -1
- package/docs/EXTENSIONS.md +176 -807
- package/docs/QUICKSTART.md +12 -5
- package/docs/README.md +7 -6
- package/docs/RELEASING.md +6 -4
- package/docs/SDK.md +78 -441
- package/docs/TESTING.md +2 -2
- package/marketplace.json +3 -3
- package/package.json +7 -4
- package/packages/pm-beads/extensions/beads/index.js +90 -101
- package/packages/pm-beads/extensions/beads/index.ts +2 -2
- package/packages/pm-beads/extensions/beads/runtime.js +2 -17
- package/packages/pm-beads/extensions/beads/runtime.ts +41 -18
- package/packages/pm-beads/package.json +34 -1
- package/packages/pm-calendar/README.md +13 -0
- package/packages/pm-calendar/extensions/calendar/index.js +56 -0
- package/packages/pm-calendar/extensions/calendar/index.ts +62 -0
- package/packages/pm-calendar/extensions/calendar/manifest.json +7 -0
- package/packages/pm-calendar/extensions/calendar/runtime.js +114 -0
- package/packages/pm-calendar/extensions/calendar/runtime.ts +123 -0
- package/packages/pm-calendar/package.json +51 -0
- package/packages/pm-governance-audit/README.md +23 -0
- package/packages/pm-governance-audit/extensions/governance-audit/index.js +117 -0
- package/packages/pm-governance-audit/extensions/governance-audit/index.ts +118 -0
- package/packages/pm-governance-audit/extensions/governance-audit/manifest.json +7 -0
- package/packages/pm-governance-audit/extensions/governance-audit/runtime.js +159 -0
- package/packages/pm-governance-audit/extensions/governance-audit/runtime.ts +176 -0
- package/packages/pm-governance-audit/package.json +52 -0
- package/packages/pm-guide-shell/README.md +23 -0
- package/packages/pm-guide-shell/extensions/guide-shell/index.js +76 -0
- package/packages/pm-guide-shell/extensions/guide-shell/index.ts +81 -0
- package/packages/pm-guide-shell/extensions/guide-shell/manifest.json +7 -0
- package/packages/pm-guide-shell/extensions/guide-shell/runtime.js +263 -0
- package/packages/pm-guide-shell/extensions/guide-shell/runtime.ts +327 -0
- package/packages/pm-guide-shell/package.json +52 -0
- package/packages/pm-linked-test-adapters/README.md +24 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/index.js +101 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/index.ts +102 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/manifest.json +7 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/runtime.js +142 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/runtime.ts +173 -0
- package/packages/pm-linked-test-adapters/package.json +53 -0
- package/packages/pm-search-advanced/README.md +27 -0
- package/packages/pm-search-advanced/extensions/search-advanced/index.js +93 -0
- package/packages/pm-search-advanced/extensions/search-advanced/index.ts +94 -0
- package/packages/pm-search-advanced/extensions/search-advanced/manifest.json +7 -0
- package/packages/pm-search-advanced/extensions/search-advanced/runtime.js +120 -0
- package/packages/pm-search-advanced/extensions/search-advanced/runtime.ts +144 -0
- package/packages/pm-search-advanced/package.json +54 -0
- package/packages/pm-templates/README.md +20 -0
- package/packages/pm-templates/extensions/templates/index.js +101 -0
- package/packages/pm-templates/extensions/templates/index.ts +109 -0
- package/packages/pm-templates/extensions/templates/manifest.json +7 -0
- package/packages/pm-templates/extensions/templates/runtime.js +226 -0
- package/packages/pm-templates/extensions/templates/runtime.ts +283 -0
- package/packages/pm-templates/package.json +50 -0
- package/packages/pm-todos/extensions/todos/index.js +105 -116
- package/packages/pm-todos/extensions/todos/index.ts +3 -2
- package/packages/pm-todos/extensions/todos/runtime.js +2 -17
- package/packages/pm-todos/extensions/todos/runtime.ts +40 -18
- package/packages/pm-todos/package.json +35 -1
- package/plugins/{pm-cli-claude → pm-claude}/.claude-plugin/plugin.json +2 -2
- package/plugins/{pm-cli-claude → pm-claude}/.mcp.json +1 -1
- package/plugins/{pm-cli-claude → pm-claude}/README.md +4 -4
- package/plugins/{pm-cli-claude → pm-claude}/agents/pm-coordinator.md +1 -1
- package/plugins/{pm-cli-claude → pm-claude}/commands/pm-init.md +10 -1
- package/plugins/{pm-cli-claude → pm-claude}/commands/pm-planner.md +18 -0
- package/plugins/{pm-cli-claude → pm-claude}/scripts/pm-mcp-server.mjs +4 -2
- package/plugins/{pm-cli-claude → pm-claude}/skills/pm-planner/SKILL.md +46 -1
- package/plugins/{pm-cli-codex → pm-codex}/.codex-plugin/plugin.json +3 -3
- package/plugins/{pm-cli-codex → pm-codex}/.mcp.json +1 -1
- package/plugins/{pm-cli-codex → pm-codex}/README.md +7 -4
- package/plugins/{pm-cli-codex → pm-codex}/scripts/pm-mcp-server.mjs +4 -2
- package/plugins/pm-codex/skills/pm-native/SKILL.md +81 -0
- package/scripts/finalize-build.mjs +28 -0
- package/scripts/prepare-build-cache.mjs +37 -0
- package/plugins/pm-cli-codex/skills/pm-native/SKILL.md +0 -57
- /package/plugins/{pm-cli-claude → pm-claude}/agents/pm-delivery-chain.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/agents/pm-triage-agent.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/agents/pm-verification-agent.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/commands/pm-audit.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/commands/pm-calendar.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/commands/pm-close-task.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/commands/pm-developer.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/commands/pm-list.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/commands/pm-new.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/commands/pm-release.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/commands/pm-search.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/commands/pm-start-task.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/commands/pm-status.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/commands/pm-triage.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/commands/pm-workflow.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/hooks/hooks.json +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/hooks/session-start.mjs +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/skills/pm-audit/SKILL.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/skills/pm-developer/SKILL.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/skills/pm-release/SKILL.md +0 -0
- /package/plugins/{pm-cli-claude → pm-claude}/skills/pm-workflow/SKILL.md +0 -0
- /package/plugins/{pm-cli-codex → pm-codex}/assets/pm-cli-small.svg +0 -0
- /package/plugins/{pm-cli-codex → pm-codex}/commands/pm-audit.md +0 -0
- /package/plugins/{pm-cli-codex → pm-codex}/commands/pm-close-task.md +0 -0
- /package/plugins/{pm-cli-codex → pm-codex}/commands/pm-start-task.md +0 -0
- /package/plugins/{pm-cli-codex → pm-codex}/skills/pm-auditor/SKILL.md +0 -0
- /package/plugins/{pm-cli-codex → pm-codex}/skills/pm-auditor/agents/openai.yaml +0 -0
- /package/plugins/{pm-cli-codex → pm-codex}/skills/pm-native/agents/openai.yaml +0 -0
- /package/plugins/{pm-cli-codex → pm-codex}/skills/pm-release/SKILL.md +0 -0
- /package/plugins/{pm-cli-codex → pm-codex}/skills/pm-release/agents/openai.yaml +0 -0
|
@@ -0,0 +1,975 @@
|
|
|
1
|
+
|
|
2
|
+
!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]="588c239a-66d0-5d60-b675-5a5a4e79d70b")}catch(e){}}();
|
|
3
|
+
import { pathExists } from "../../core/fs/fs-utils.js";
|
|
4
|
+
import { getActiveExtensionRegistrations } from "../../core/extensions/index.js";
|
|
5
|
+
import { resolveItemTypeRegistry, resolveTypeName } from "../../core/item/type-registry.js";
|
|
6
|
+
import { EXIT_CODE } from "../../core/shared/constants.js";
|
|
7
|
+
import { PmCliError } from "../../core/shared/errors.js";
|
|
8
|
+
import { nowIso } from "../../core/shared/time.js";
|
|
9
|
+
import { locateItem, mutateItem, readLocatedItem } from "../../core/store/item-store.js";
|
|
10
|
+
import { getSettingsPath, resolvePmRoot } from "../../core/store/paths.js";
|
|
11
|
+
import { readSettings } from "../../core/store/settings.js";
|
|
12
|
+
import { PLAN_HARNESS_VALUES, PLAN_MODE_VALUES, PLAN_STEP_LINK_KIND_VALUES, PLAN_STEP_STATUS_VALUES, } from "../../types/index.js";
|
|
13
|
+
import { runCreate } from "./create.js";
|
|
14
|
+
export const PLAN_SUBCOMMANDS = [
|
|
15
|
+
"create",
|
|
16
|
+
"show",
|
|
17
|
+
"add-step",
|
|
18
|
+
"update-step",
|
|
19
|
+
"complete-step",
|
|
20
|
+
"block-step",
|
|
21
|
+
"reorder-step",
|
|
22
|
+
"remove-step",
|
|
23
|
+
"link",
|
|
24
|
+
"unlink",
|
|
25
|
+
"decision",
|
|
26
|
+
"discovery",
|
|
27
|
+
"validation",
|
|
28
|
+
"resume",
|
|
29
|
+
"approve",
|
|
30
|
+
"materialize",
|
|
31
|
+
];
|
|
32
|
+
export const PLAN_SHOW_DEPTH_VALUES = ["brief", "standard", "deep"];
|
|
33
|
+
const STEP_ID_PREFIX = "plan-step-";
|
|
34
|
+
const DEFAULT_PLAN_MODE = "draft";
|
|
35
|
+
function resolveAuthor(candidate, fallback) {
|
|
36
|
+
const resolved = candidate ?? process.env.PM_AUTHOR ?? fallback;
|
|
37
|
+
const trimmed = resolved.trim();
|
|
38
|
+
return trimmed.length > 0 ? trimmed : "unknown";
|
|
39
|
+
}
|
|
40
|
+
function toArray(value) {
|
|
41
|
+
if (Array.isArray(value))
|
|
42
|
+
return value.flatMap((entry) => entry.split(",")).map((entry) => entry.trim()).filter(Boolean);
|
|
43
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
44
|
+
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
45
|
+
}
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
function toSpecArray(value) {
|
|
49
|
+
if (Array.isArray(value))
|
|
50
|
+
return value.map((entry) => entry.trim()).filter(Boolean);
|
|
51
|
+
if (typeof value === "string" && value.trim().length > 0)
|
|
52
|
+
return [value.trim()];
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
function asPlanMode(value, fallback = DEFAULT_PLAN_MODE) {
|
|
56
|
+
if (value === undefined)
|
|
57
|
+
return fallback;
|
|
58
|
+
const normalized = value.trim().toLowerCase();
|
|
59
|
+
const found = PLAN_MODE_VALUES.find((entry) => entry === normalized);
|
|
60
|
+
if (!found) {
|
|
61
|
+
throw new PmCliError(`Invalid plan mode "${value}". Allowed: ${PLAN_MODE_VALUES.join(", ")}`, EXIT_CODE.USAGE);
|
|
62
|
+
}
|
|
63
|
+
return found;
|
|
64
|
+
}
|
|
65
|
+
function asStepStatus(value, fallback = "pending") {
|
|
66
|
+
if (value === undefined)
|
|
67
|
+
return fallback;
|
|
68
|
+
const normalized = value.trim().toLowerCase();
|
|
69
|
+
const found = PLAN_STEP_STATUS_VALUES.find((entry) => entry === normalized);
|
|
70
|
+
if (!found) {
|
|
71
|
+
throw new PmCliError(`Invalid step status "${value}". Allowed: ${PLAN_STEP_STATUS_VALUES.join(", ")}`, EXIT_CODE.USAGE);
|
|
72
|
+
}
|
|
73
|
+
return found;
|
|
74
|
+
}
|
|
75
|
+
function asLinkKind(value, fallback = "related") {
|
|
76
|
+
if (value === undefined)
|
|
77
|
+
return fallback;
|
|
78
|
+
const normalized = value.trim().toLowerCase();
|
|
79
|
+
const found = PLAN_STEP_LINK_KIND_VALUES.find((entry) => entry === normalized);
|
|
80
|
+
if (!found) {
|
|
81
|
+
throw new PmCliError(`Invalid step link kind "${value}". Allowed: ${PLAN_STEP_LINK_KIND_VALUES.join(", ")}`, EXIT_CODE.USAGE);
|
|
82
|
+
}
|
|
83
|
+
return found;
|
|
84
|
+
}
|
|
85
|
+
function asHarness(value) {
|
|
86
|
+
if (value === undefined)
|
|
87
|
+
return undefined;
|
|
88
|
+
const normalized = value.trim().toLowerCase();
|
|
89
|
+
if (normalized.length === 0)
|
|
90
|
+
return undefined;
|
|
91
|
+
const found = PLAN_HARNESS_VALUES.find((entry) => entry === normalized);
|
|
92
|
+
if (!found) {
|
|
93
|
+
throw new PmCliError(`Invalid plan harness "${value}". Allowed: ${PLAN_HARNESS_VALUES.join(", ")}`, EXIT_CODE.USAGE);
|
|
94
|
+
}
|
|
95
|
+
return found;
|
|
96
|
+
}
|
|
97
|
+
function asDepth(value) {
|
|
98
|
+
if (value === undefined)
|
|
99
|
+
return "brief";
|
|
100
|
+
const normalized = value.trim().toLowerCase();
|
|
101
|
+
const found = PLAN_SHOW_DEPTH_VALUES.find((entry) => entry === normalized);
|
|
102
|
+
if (!found) {
|
|
103
|
+
throw new PmCliError(`Invalid --depth "${value}". Allowed: ${PLAN_SHOW_DEPTH_VALUES.join(", ")}`, EXIT_CODE.USAGE);
|
|
104
|
+
}
|
|
105
|
+
return found;
|
|
106
|
+
}
|
|
107
|
+
function parsePlanFields(raw) {
|
|
108
|
+
if (raw === undefined) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const fields = raw
|
|
112
|
+
.split(",")
|
|
113
|
+
.map((entry) => entry.trim())
|
|
114
|
+
.filter((entry) => entry.length > 0);
|
|
115
|
+
if (fields.length === 0) {
|
|
116
|
+
throw new PmCliError("Plan --fields requires a comma-separated list of plan field names", EXIT_CODE.USAGE);
|
|
117
|
+
}
|
|
118
|
+
return fields;
|
|
119
|
+
}
|
|
120
|
+
function projectPlanForFields(plan, fields) {
|
|
121
|
+
const source = plan;
|
|
122
|
+
const projected = {};
|
|
123
|
+
for (const field of fields) {
|
|
124
|
+
const normalized = field.startsWith("plan.") ? field.slice("plan.".length) : field;
|
|
125
|
+
if (normalized.length === 0) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (Object.prototype.hasOwnProperty.call(source, normalized)) {
|
|
129
|
+
projected[normalized] = source[normalized];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return projected;
|
|
133
|
+
}
|
|
134
|
+
function parsePairList(raw, label) {
|
|
135
|
+
const out = {};
|
|
136
|
+
for (const entry of raw.split(",")) {
|
|
137
|
+
const trimmed = entry.trim();
|
|
138
|
+
if (trimmed.length === 0)
|
|
139
|
+
continue;
|
|
140
|
+
const equalsIndex = trimmed.indexOf("=");
|
|
141
|
+
if (equalsIndex <= 0) {
|
|
142
|
+
throw new PmCliError(`Invalid ${label} entry "${trimmed}"; expected key=value`, EXIT_CODE.USAGE);
|
|
143
|
+
}
|
|
144
|
+
out[trimmed.slice(0, equalsIndex).trim().toLowerCase()] = trimmed.slice(equalsIndex + 1).trim();
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
function parseStepFile(spec) {
|
|
149
|
+
const fields = parsePairList(spec, "--file");
|
|
150
|
+
if (!fields.path) {
|
|
151
|
+
throw new PmCliError("--file requires path=<value>", EXIT_CODE.USAGE);
|
|
152
|
+
}
|
|
153
|
+
const file = { path: fields.path };
|
|
154
|
+
if (fields.scope === "global" || fields.scope === "project")
|
|
155
|
+
file.scope = fields.scope;
|
|
156
|
+
if (fields.note)
|
|
157
|
+
file.note = fields.note;
|
|
158
|
+
return file;
|
|
159
|
+
}
|
|
160
|
+
function parseStepTest(spec) {
|
|
161
|
+
const fields = parsePairList(spec, "--test");
|
|
162
|
+
if (!fields.command && !fields.path) {
|
|
163
|
+
throw new PmCliError("--test requires at least command=<value> or path=<value>", EXIT_CODE.USAGE);
|
|
164
|
+
}
|
|
165
|
+
const test = {};
|
|
166
|
+
if (fields.command)
|
|
167
|
+
test.command = fields.command;
|
|
168
|
+
if (fields.path)
|
|
169
|
+
test.path = fields.path;
|
|
170
|
+
if (fields.note)
|
|
171
|
+
test.note = fields.note;
|
|
172
|
+
return test;
|
|
173
|
+
}
|
|
174
|
+
function parseStepDoc(spec) {
|
|
175
|
+
const fields = parsePairList(spec, "--doc");
|
|
176
|
+
if (!fields.path) {
|
|
177
|
+
throw new PmCliError("--doc requires path=<value>", EXIT_CODE.USAGE);
|
|
178
|
+
}
|
|
179
|
+
const doc = { path: fields.path };
|
|
180
|
+
if (fields.scope === "global" || fields.scope === "project")
|
|
181
|
+
doc.scope = fields.scope;
|
|
182
|
+
if (fields.note)
|
|
183
|
+
doc.note = fields.note;
|
|
184
|
+
return doc;
|
|
185
|
+
}
|
|
186
|
+
function summarizeSteps(steps) {
|
|
187
|
+
const summary = {
|
|
188
|
+
total: steps.length,
|
|
189
|
+
pending: 0,
|
|
190
|
+
in_progress: 0,
|
|
191
|
+
blocked: 0,
|
|
192
|
+
completed: 0,
|
|
193
|
+
skipped: 0,
|
|
194
|
+
superseded: 0,
|
|
195
|
+
};
|
|
196
|
+
for (const step of steps) {
|
|
197
|
+
summary[step.status] += 1;
|
|
198
|
+
}
|
|
199
|
+
return summary;
|
|
200
|
+
}
|
|
201
|
+
function newStepId(existing) {
|
|
202
|
+
const used = new Set(existing.map((step) => step.id));
|
|
203
|
+
for (let cursor = existing.length + 1; cursor < existing.length + 1024; cursor += 1) {
|
|
204
|
+
const candidate = `${STEP_ID_PREFIX}${String(cursor).padStart(3, "0")}`;
|
|
205
|
+
if (!used.has(candidate))
|
|
206
|
+
return candidate;
|
|
207
|
+
}
|
|
208
|
+
/* c8 ignore next -- step id allocation only fails if 1024 consecutive ids are taken. */
|
|
209
|
+
throw new PmCliError("Could not allocate step id (limit reached)", EXIT_CODE.GENERIC_FAILURE);
|
|
210
|
+
}
|
|
211
|
+
function resolveStepRef(steps, ref) {
|
|
212
|
+
const trimmed = ref.trim();
|
|
213
|
+
if (trimmed.length === 0) {
|
|
214
|
+
throw new PmCliError("Step reference cannot be empty", EXIT_CODE.USAGE);
|
|
215
|
+
}
|
|
216
|
+
const direct = steps.find((step) => step.id === trimmed);
|
|
217
|
+
if (direct)
|
|
218
|
+
return direct;
|
|
219
|
+
if (/^\d+$/.test(trimmed)) {
|
|
220
|
+
const order = Number.parseInt(trimmed, 10);
|
|
221
|
+
const byOrder = steps.find((step) => step.order === order);
|
|
222
|
+
if (byOrder)
|
|
223
|
+
return byOrder;
|
|
224
|
+
}
|
|
225
|
+
throw new PmCliError(`Step "${ref}" not found in plan`, EXIT_CODE.NOT_FOUND);
|
|
226
|
+
}
|
|
227
|
+
function findCurrentStep(steps) {
|
|
228
|
+
return steps.find((step) => step.status === "in_progress")
|
|
229
|
+
?? steps.find((step) => step.status === "pending");
|
|
230
|
+
}
|
|
231
|
+
function blockedSteps(steps) {
|
|
232
|
+
return steps
|
|
233
|
+
.filter((step) => step.status === "blocked")
|
|
234
|
+
.map((step) => ({ id: step.id, order: step.order, title: step.title, blocked_reason: step.blocked_reason }));
|
|
235
|
+
}
|
|
236
|
+
function projectPlan(item, depth = "brief") {
|
|
237
|
+
const steps = (item.plan_steps ?? []).slice().sort((left, right) => left.order - right.order);
|
|
238
|
+
const summary = summarizeSteps(steps);
|
|
239
|
+
const current = findCurrentStep(steps);
|
|
240
|
+
const projection = {
|
|
241
|
+
id: item.id,
|
|
242
|
+
title: item.title,
|
|
243
|
+
status: item.status,
|
|
244
|
+
mode: (item.plan_mode ?? DEFAULT_PLAN_MODE),
|
|
245
|
+
scope: item.plan_scope,
|
|
246
|
+
harness: item.plan_harness,
|
|
247
|
+
parent: item.parent,
|
|
248
|
+
resume_context: item.plan_resume_context,
|
|
249
|
+
steps_summary: summary,
|
|
250
|
+
current_step: current
|
|
251
|
+
? { id: current.id, order: current.order, title: current.title, status: current.status }
|
|
252
|
+
: undefined,
|
|
253
|
+
blocked_steps: blockedSteps(steps),
|
|
254
|
+
linked_items: (item.dependencies ?? []).map((dep) => ({ id: dep.id, kind: dep.kind })),
|
|
255
|
+
};
|
|
256
|
+
if (depth === "standard" || depth === "deep") {
|
|
257
|
+
projection.steps = steps;
|
|
258
|
+
}
|
|
259
|
+
if (depth === "deep") {
|
|
260
|
+
projection.decisions = item.plan_decisions ?? [];
|
|
261
|
+
projection.discoveries = item.plan_discoveries ?? [];
|
|
262
|
+
projection.validation = item.plan_validation ?? [];
|
|
263
|
+
}
|
|
264
|
+
return projection;
|
|
265
|
+
}
|
|
266
|
+
function nextActionsFor(planId, plan) {
|
|
267
|
+
const tips = [];
|
|
268
|
+
if (plan.steps_summary.total === 0) {
|
|
269
|
+
tips.push(`pm plan add-step ${planId} --step-title "First step"`);
|
|
270
|
+
}
|
|
271
|
+
if (plan.current_step) {
|
|
272
|
+
tips.push(`pm plan update-step ${planId} ${plan.current_step.id} --step-status in_progress`, `pm plan complete-step ${planId} ${plan.current_step.id} --step-evidence "..."`);
|
|
273
|
+
}
|
|
274
|
+
if (plan.steps_summary.blocked > 0) {
|
|
275
|
+
tips.push(`pm plan show ${planId} --depth standard`);
|
|
276
|
+
}
|
|
277
|
+
if (plan.mode === "draft" || plan.mode === "research") {
|
|
278
|
+
tips.push(`pm plan approve ${planId} --message "ready to execute"`);
|
|
279
|
+
}
|
|
280
|
+
if (plan.steps_summary.completed === plan.steps_summary.total && plan.steps_summary.total > 0) {
|
|
281
|
+
tips.push(`pm close ${planId} "plan complete"`);
|
|
282
|
+
}
|
|
283
|
+
return tips;
|
|
284
|
+
}
|
|
285
|
+
function ensurePlanItem(item) {
|
|
286
|
+
const normalizedType = (item.type ?? "").trim().toLowerCase();
|
|
287
|
+
if (normalizedType !== "plan") {
|
|
288
|
+
throw new PmCliError(`Item ${item.id} is type ${item.type}; pm plan commands require type=Plan. Use pm plan create or pm create --type Plan first.`, EXIT_CODE.USAGE);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async function loadContext(global) {
|
|
292
|
+
const pmRoot = resolvePmRoot(process.cwd(), global.path);
|
|
293
|
+
if (!(await pathExists(getSettingsPath(pmRoot)))) {
|
|
294
|
+
throw new PmCliError(`Tracker is not initialized at ${pmRoot}. Run pm init first.`, EXIT_CODE.NOT_FOUND);
|
|
295
|
+
}
|
|
296
|
+
const settings = await readSettings(pmRoot);
|
|
297
|
+
return { pmRoot, settings };
|
|
298
|
+
}
|
|
299
|
+
async function readPlanItem(ctx, id) {
|
|
300
|
+
const typeRegistry = resolveItemTypeRegistry(ctx.settings, getActiveExtensionRegistrations());
|
|
301
|
+
const located = await locateItem(ctx.pmRoot, id, ctx.settings.id_prefix, ctx.settings.item_format, typeRegistry.type_to_folder);
|
|
302
|
+
if (!located) {
|
|
303
|
+
throw new PmCliError(`Plan ${id} not found`, EXIT_CODE.NOT_FOUND);
|
|
304
|
+
}
|
|
305
|
+
const loaded = await readLocatedItem(located, { schema: ctx.settings.schema });
|
|
306
|
+
ensurePlanItem(loaded.document.metadata);
|
|
307
|
+
return { document: loaded.document, itemId: located.id };
|
|
308
|
+
}
|
|
309
|
+
async function planCreate(options, global, ctx) {
|
|
310
|
+
const title = options.title?.trim();
|
|
311
|
+
if (!title) {
|
|
312
|
+
throw new PmCliError("pm plan create requires --title", EXIT_CODE.USAGE, {
|
|
313
|
+
code: "missing_required_option",
|
|
314
|
+
examples: ['pm plan create --title "Refactor lock retry" --scope pm-a1b2'],
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
const mode = asPlanMode(options.mode, DEFAULT_PLAN_MODE);
|
|
318
|
+
const harness = asHarness(options.harness);
|
|
319
|
+
const fromSearch = options.fromSearch?.trim();
|
|
320
|
+
const related = toArray(options.related);
|
|
321
|
+
const blocks = toArray(options.blocks);
|
|
322
|
+
const blockedBy = toArray(options.blockedBy);
|
|
323
|
+
const deps = [];
|
|
324
|
+
if (options.parent) {
|
|
325
|
+
deps.push(`id=${options.parent.trim()},kind=parent`);
|
|
326
|
+
}
|
|
327
|
+
for (const ref of related)
|
|
328
|
+
deps.push(`id=${ref},kind=related`);
|
|
329
|
+
for (const ref of blocks)
|
|
330
|
+
deps.push(`id=${ref},kind=blocks`);
|
|
331
|
+
for (const ref of blockedBy)
|
|
332
|
+
deps.push(`id=${ref},kind=blocked_by`);
|
|
333
|
+
const description = options.description?.trim() ?? options.scope?.trim() ?? title;
|
|
334
|
+
const createOptions = {
|
|
335
|
+
title,
|
|
336
|
+
description,
|
|
337
|
+
type: "Plan",
|
|
338
|
+
body: options.body,
|
|
339
|
+
tags: options.tags,
|
|
340
|
+
priority: options.priority,
|
|
341
|
+
parent: options.parent,
|
|
342
|
+
dep: deps.length > 0 ? deps : undefined,
|
|
343
|
+
author: options.author,
|
|
344
|
+
message: options.message ?? (fromSearch ? `plan create (search: ${fromSearch})` : `plan create`),
|
|
345
|
+
};
|
|
346
|
+
const createResult = await runCreate(createOptions, global);
|
|
347
|
+
// The create command stores type_options. To use real metadata keys, run a follow-up mutate.
|
|
348
|
+
const seedResult = await mutateItem({
|
|
349
|
+
pmRoot: ctx.pmRoot,
|
|
350
|
+
settings: ctx.settings,
|
|
351
|
+
id: createResult.item.id,
|
|
352
|
+
op: "plan_create_metadata",
|
|
353
|
+
author: resolveAuthor(options.author, ctx.settings.author_default),
|
|
354
|
+
message: "seed plan metadata",
|
|
355
|
+
mutate(doc) {
|
|
356
|
+
const changed = [];
|
|
357
|
+
doc.metadata.plan_mode = mode;
|
|
358
|
+
changed.push("plan_mode");
|
|
359
|
+
if (harness) {
|
|
360
|
+
doc.metadata.plan_harness = harness;
|
|
361
|
+
changed.push("plan_harness");
|
|
362
|
+
}
|
|
363
|
+
if (options.scope?.trim()) {
|
|
364
|
+
doc.metadata.plan_scope = options.scope.trim();
|
|
365
|
+
changed.push("plan_scope");
|
|
366
|
+
}
|
|
367
|
+
if (options.resumeContext?.trim()) {
|
|
368
|
+
doc.metadata.plan_resume_context = options.resumeContext.trim();
|
|
369
|
+
changed.push("plan_resume_context");
|
|
370
|
+
}
|
|
371
|
+
doc.metadata.plan_steps = doc.metadata.plan_steps ?? [];
|
|
372
|
+
return { changedFields: changed };
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
let finalMetadata = seedResult.item;
|
|
376
|
+
if (options.claim) {
|
|
377
|
+
const claimed = await mutateItem({
|
|
378
|
+
pmRoot: ctx.pmRoot,
|
|
379
|
+
settings: ctx.settings,
|
|
380
|
+
id: createResult.item.id,
|
|
381
|
+
op: "claim",
|
|
382
|
+
author: resolveAuthor(options.author, ctx.settings.author_default),
|
|
383
|
+
message: "plan claim by author",
|
|
384
|
+
mutate(doc) {
|
|
385
|
+
doc.metadata.assignee = resolveAuthor(options.author, ctx.settings.author_default);
|
|
386
|
+
return { changedFields: ["assignee"] };
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
finalMetadata = claimed.item;
|
|
390
|
+
}
|
|
391
|
+
const plan = projectPlan(finalMetadata, "brief");
|
|
392
|
+
return {
|
|
393
|
+
action: "create",
|
|
394
|
+
plan,
|
|
395
|
+
next_actions: nextActionsFor(createResult.item.id, plan),
|
|
396
|
+
warnings: [...createResult.warnings],
|
|
397
|
+
generated_at: nowIso(),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
async function planShow(id, options, ctx) {
|
|
401
|
+
const depth = asDepth(options.depth);
|
|
402
|
+
const fields = parsePlanFields(options.fields);
|
|
403
|
+
const { document, itemId } = await readPlanItem(ctx, id);
|
|
404
|
+
const fullPlan = projectPlan(document.metadata, depth);
|
|
405
|
+
const plan = fields === null ? fullPlan : projectPlanForFields(fullPlan, fields);
|
|
406
|
+
return {
|
|
407
|
+
action: "show",
|
|
408
|
+
plan,
|
|
409
|
+
next_actions: nextActionsFor(itemId, fullPlan),
|
|
410
|
+
warnings: [],
|
|
411
|
+
generated_at: nowIso(),
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
async function mutatePlanSteps(args) {
|
|
415
|
+
const author = resolveAuthor(args.options.author, args.ctx.settings.author_default);
|
|
416
|
+
let resultStep;
|
|
417
|
+
const result = await mutateItem({
|
|
418
|
+
pmRoot: args.ctx.pmRoot,
|
|
419
|
+
settings: args.ctx.settings,
|
|
420
|
+
id: args.id,
|
|
421
|
+
op: args.op,
|
|
422
|
+
author,
|
|
423
|
+
message: args.options.message ?? args.message,
|
|
424
|
+
force: args.options.force,
|
|
425
|
+
mutate(doc) {
|
|
426
|
+
ensurePlanItem(doc.metadata);
|
|
427
|
+
const steps = (doc.metadata.plan_steps ?? []).slice();
|
|
428
|
+
const before = JSON.stringify(steps);
|
|
429
|
+
const outcome = args.mutator(steps, doc);
|
|
430
|
+
resultStep = outcome.resultStep;
|
|
431
|
+
const sorted = steps
|
|
432
|
+
.slice()
|
|
433
|
+
.sort((left, right) => left.order - right.order)
|
|
434
|
+
.map((step, index) => ({ ...step, order: index + 1 }));
|
|
435
|
+
doc.metadata.plan_steps = sorted;
|
|
436
|
+
const after = JSON.stringify(sorted);
|
|
437
|
+
const changedFields = before === after ? [] : ["plan_steps"];
|
|
438
|
+
if (changedFields.length === 0 && outcome.changedSteps.length === 0) {
|
|
439
|
+
return { changedFields: [] };
|
|
440
|
+
}
|
|
441
|
+
return { changedFields: changedFields.length > 0 ? changedFields : ["plan_steps"] };
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
return {
|
|
445
|
+
document: { metadata: result.item, body: result.body },
|
|
446
|
+
resultStep,
|
|
447
|
+
itemId: result.item.id,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
async function planAddStep(id, options, ctx) {
|
|
451
|
+
const title = options.stepTitle?.trim();
|
|
452
|
+
if (!title) {
|
|
453
|
+
throw new PmCliError("pm plan add-step requires --step-title", EXIT_CODE.USAGE);
|
|
454
|
+
}
|
|
455
|
+
const status = asStepStatus(options.stepStatus, "pending");
|
|
456
|
+
const allowMultipleActive = options.allowMultipleActive === true;
|
|
457
|
+
const linkedItems = buildLinkInputs(options, "depends_on");
|
|
458
|
+
const files = toSpecArray(options.file).map(parseStepFile);
|
|
459
|
+
const tests = toSpecArray(options.test).map(parseStepTest);
|
|
460
|
+
const docs = toSpecArray(options.doc).map(parseStepDoc);
|
|
461
|
+
const { document, resultStep, itemId } = await mutatePlanSteps({
|
|
462
|
+
id,
|
|
463
|
+
options,
|
|
464
|
+
ctx,
|
|
465
|
+
op: "plan_add_step",
|
|
466
|
+
message: `plan add-step "${title}"`,
|
|
467
|
+
mutator(steps) {
|
|
468
|
+
const order = steps.length + 1;
|
|
469
|
+
if (status === "in_progress" && !allowMultipleActive) {
|
|
470
|
+
for (const step of steps) {
|
|
471
|
+
if (step.status === "in_progress") {
|
|
472
|
+
throw new PmCliError(`Plan already has step ${step.id} in_progress. Pass --allow-multiple-active or update that step first.`, EXIT_CODE.CONFLICT);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const now = nowIso();
|
|
477
|
+
const step = {
|
|
478
|
+
id: newStepId(steps),
|
|
479
|
+
order,
|
|
480
|
+
title,
|
|
481
|
+
body: options.stepBody?.trim() || undefined,
|
|
482
|
+
status,
|
|
483
|
+
owner: options.stepOwner?.trim() || undefined,
|
|
484
|
+
evidence: options.stepEvidence?.trim() || undefined,
|
|
485
|
+
blocked_reason: status === "blocked" ? options.stepBlockedReason?.trim() || "" : undefined,
|
|
486
|
+
linked_items: linkedItems.length > 0 ? linkedItems : undefined,
|
|
487
|
+
files: files.length > 0 ? files : undefined,
|
|
488
|
+
tests: tests.length > 0 ? tests : undefined,
|
|
489
|
+
docs: docs.length > 0 ? docs : undefined,
|
|
490
|
+
created_at: now,
|
|
491
|
+
updated_at: now,
|
|
492
|
+
completed_at: status === "completed" ? now : undefined,
|
|
493
|
+
};
|
|
494
|
+
steps.push(step);
|
|
495
|
+
return { changedSteps: [step.id], resultStep: step };
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
const plan = projectPlan(document.metadata, "standard");
|
|
499
|
+
return {
|
|
500
|
+
action: "add-step",
|
|
501
|
+
plan,
|
|
502
|
+
step: resultStep,
|
|
503
|
+
next_actions: nextActionsFor(itemId, plan),
|
|
504
|
+
warnings: [],
|
|
505
|
+
generated_at: nowIso(),
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function buildLinkInputs(options, fallbackKind) {
|
|
509
|
+
const dependsOn = toArray(options.dependsOn).map((id) => ({ id, kind: fallbackKind }));
|
|
510
|
+
const related = toArray(options.related).map((id) => ({ id, kind: "related" }));
|
|
511
|
+
const blocks = toArray(options.blocks).map((id) => ({ id, kind: "blocks" }));
|
|
512
|
+
const blockedBy = toArray(options.blockedBy).map((id) => ({ id, kind: "blocked_by" }));
|
|
513
|
+
const explicit = toArray(options.link);
|
|
514
|
+
const explicitKind = asLinkKind(options.linkKind, fallbackKind);
|
|
515
|
+
const note = options.linkNote?.trim();
|
|
516
|
+
const explicitLinks = explicit.map((id) => {
|
|
517
|
+
const link = { id, kind: explicitKind };
|
|
518
|
+
if (note)
|
|
519
|
+
link.note = note;
|
|
520
|
+
return link;
|
|
521
|
+
});
|
|
522
|
+
return [...dependsOn, ...related, ...blocks, ...blockedBy, ...explicitLinks];
|
|
523
|
+
}
|
|
524
|
+
async function planUpdateStep(id, options, ctx, args) {
|
|
525
|
+
const { document, resultStep, itemId } = await mutatePlanSteps({
|
|
526
|
+
id,
|
|
527
|
+
options,
|
|
528
|
+
ctx,
|
|
529
|
+
op: args.op,
|
|
530
|
+
message: options.message ?? `${args.op} ${args.stepRef}`,
|
|
531
|
+
mutator(steps, doc) {
|
|
532
|
+
const step = resolveStepRef(steps, args.stepRef);
|
|
533
|
+
const now = nowIso();
|
|
534
|
+
const desiredStatus = args.finalStatus ?? asStepStatus(options.stepStatus, step.status);
|
|
535
|
+
if (desiredStatus === "in_progress" && step.status !== "in_progress" && !options.allowMultipleActive && !args.allowMultipleActive) {
|
|
536
|
+
for (const other of steps) {
|
|
537
|
+
if (other.id !== step.id && other.status === "in_progress") {
|
|
538
|
+
throw new PmCliError(`Plan already has step ${other.id} in_progress. Pass --allow-multiple-active or update that step first.`, EXIT_CODE.CONFLICT);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (desiredStatus === "blocked" && !options.stepBlockedReason?.trim() && !step.blocked_reason) {
|
|
543
|
+
throw new PmCliError("Blocking a step requires --step-blocked-reason or an already-recorded blocked_reason.", EXIT_CODE.USAGE);
|
|
544
|
+
}
|
|
545
|
+
if (options.stepTitle?.trim())
|
|
546
|
+
step.title = options.stepTitle.trim();
|
|
547
|
+
if (options.stepBody !== undefined)
|
|
548
|
+
step.body = options.stepBody.trim() || undefined;
|
|
549
|
+
if (options.stepOwner !== undefined)
|
|
550
|
+
step.owner = options.stepOwner.trim() || undefined;
|
|
551
|
+
if (options.stepEvidence !== undefined)
|
|
552
|
+
step.evidence = options.stepEvidence.trim() || undefined;
|
|
553
|
+
if (options.stepBlockedReason !== undefined)
|
|
554
|
+
step.blocked_reason = options.stepBlockedReason.trim() || undefined;
|
|
555
|
+
if (options.stepReplacement !== undefined)
|
|
556
|
+
step.superseded_by = options.stepReplacement.trim() || undefined;
|
|
557
|
+
step.status = desiredStatus;
|
|
558
|
+
step.updated_at = now;
|
|
559
|
+
if (desiredStatus === "completed" && !step.completed_at) {
|
|
560
|
+
step.completed_at = now;
|
|
561
|
+
}
|
|
562
|
+
else if (desiredStatus !== "completed") {
|
|
563
|
+
step.completed_at = undefined;
|
|
564
|
+
}
|
|
565
|
+
doc.metadata.updated_at = now;
|
|
566
|
+
return { changedSteps: [step.id], resultStep: step };
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
const plan = projectPlan(document.metadata, "standard");
|
|
570
|
+
return {
|
|
571
|
+
action: args.op === "plan_complete_step" ? "complete-step" : args.op === "plan_block_step" ? "block-step" : "update-step",
|
|
572
|
+
plan,
|
|
573
|
+
step: resultStep,
|
|
574
|
+
next_actions: nextActionsFor(itemId, plan),
|
|
575
|
+
warnings: [],
|
|
576
|
+
generated_at: nowIso(),
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
async function planReorderStep(id, options, ctx, stepRef, newOrder) {
|
|
580
|
+
const { document, itemId } = await mutatePlanSteps({
|
|
581
|
+
id,
|
|
582
|
+
options,
|
|
583
|
+
ctx,
|
|
584
|
+
op: "plan_reorder_step",
|
|
585
|
+
message: options.message ?? `plan reorder-step ${stepRef} -> ${newOrder}`,
|
|
586
|
+
mutator(steps) {
|
|
587
|
+
const step = resolveStepRef(steps, stepRef);
|
|
588
|
+
const filtered = steps.filter((entry) => entry.id !== step.id);
|
|
589
|
+
const clamped = Math.max(1, Math.min(newOrder, filtered.length + 1));
|
|
590
|
+
filtered.splice(clamped - 1, 0, step);
|
|
591
|
+
filtered.forEach((entry, index) => {
|
|
592
|
+
entry.order = index + 1;
|
|
593
|
+
entry.updated_at = nowIso();
|
|
594
|
+
});
|
|
595
|
+
steps.length = 0;
|
|
596
|
+
steps.push(...filtered);
|
|
597
|
+
return { changedSteps: [step.id], resultStep: step };
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
const plan = projectPlan(document.metadata, "standard");
|
|
601
|
+
return {
|
|
602
|
+
action: "reorder-step",
|
|
603
|
+
plan,
|
|
604
|
+
next_actions: nextActionsFor(itemId, plan),
|
|
605
|
+
warnings: [],
|
|
606
|
+
generated_at: nowIso(),
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
async function planRemoveStep(id, options, ctx, stepRef) {
|
|
610
|
+
const { document, itemId } = await mutatePlanSteps({
|
|
611
|
+
id,
|
|
612
|
+
options,
|
|
613
|
+
ctx,
|
|
614
|
+
op: "plan_remove_step",
|
|
615
|
+
message: options.message ?? `plan remove-step ${stepRef}`,
|
|
616
|
+
mutator(steps) {
|
|
617
|
+
const step = resolveStepRef(steps, stepRef);
|
|
618
|
+
const remaining = steps.filter((entry) => entry.id !== step.id);
|
|
619
|
+
remaining.forEach((entry, index) => {
|
|
620
|
+
entry.order = index + 1;
|
|
621
|
+
});
|
|
622
|
+
steps.length = 0;
|
|
623
|
+
steps.push(...remaining);
|
|
624
|
+
return { changedSteps: [step.id] };
|
|
625
|
+
},
|
|
626
|
+
});
|
|
627
|
+
const plan = projectPlan(document.metadata, "standard");
|
|
628
|
+
return {
|
|
629
|
+
action: "remove-step",
|
|
630
|
+
plan,
|
|
631
|
+
next_actions: nextActionsFor(itemId, plan),
|
|
632
|
+
warnings: [],
|
|
633
|
+
generated_at: nowIso(),
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
async function planLink(id, options, ctx, stepRef) {
|
|
637
|
+
const newLinks = buildLinkInputs(options, asLinkKind(options.linkKind, "related"));
|
|
638
|
+
if (newLinks.length === 0) {
|
|
639
|
+
throw new PmCliError("pm plan link requires at least one --link/--related/--blocks/--blocked-by/--depends-on id", EXIT_CODE.USAGE);
|
|
640
|
+
}
|
|
641
|
+
const promoteToItemDep = options.promoteToItemDep === true;
|
|
642
|
+
const { document, itemId, resultStep } = await mutatePlanSteps({
|
|
643
|
+
id,
|
|
644
|
+
options,
|
|
645
|
+
ctx,
|
|
646
|
+
op: "plan_link_step",
|
|
647
|
+
message: options.message ?? `plan link ${stepRef}`,
|
|
648
|
+
mutator(steps, doc) {
|
|
649
|
+
const step = resolveStepRef(steps, stepRef);
|
|
650
|
+
const existing = step.linked_items ?? [];
|
|
651
|
+
const dedupKey = (link) => `${link.kind}:${link.id}`;
|
|
652
|
+
const seen = new Set(existing.map(dedupKey));
|
|
653
|
+
for (const link of newLinks) {
|
|
654
|
+
const key = dedupKey(link);
|
|
655
|
+
if (seen.has(key))
|
|
656
|
+
continue;
|
|
657
|
+
existing.push(link);
|
|
658
|
+
seen.add(key);
|
|
659
|
+
}
|
|
660
|
+
step.linked_items = existing;
|
|
661
|
+
step.updated_at = nowIso();
|
|
662
|
+
if (promoteToItemDep) {
|
|
663
|
+
const deps = doc.metadata.dependencies ?? [];
|
|
664
|
+
const depKey = (dep) => `${dep.kind}:${dep.id}`;
|
|
665
|
+
const seenDeps = new Set(deps.map(depKey));
|
|
666
|
+
for (const link of newLinks) {
|
|
667
|
+
const candidate = {
|
|
668
|
+
id: link.id,
|
|
669
|
+
kind: link.kind,
|
|
670
|
+
created_at: nowIso(),
|
|
671
|
+
author: resolveAuthor(options.author, ctx.settings.author_default),
|
|
672
|
+
};
|
|
673
|
+
if (seenDeps.has(depKey(candidate)))
|
|
674
|
+
continue;
|
|
675
|
+
deps.push(candidate);
|
|
676
|
+
seenDeps.add(depKey(candidate));
|
|
677
|
+
}
|
|
678
|
+
doc.metadata.dependencies = deps;
|
|
679
|
+
}
|
|
680
|
+
return { changedSteps: [step.id], resultStep: step };
|
|
681
|
+
},
|
|
682
|
+
});
|
|
683
|
+
const plan = projectPlan(document.metadata, "standard");
|
|
684
|
+
return {
|
|
685
|
+
action: "link",
|
|
686
|
+
plan,
|
|
687
|
+
step: resultStep,
|
|
688
|
+
next_actions: nextActionsFor(itemId, plan),
|
|
689
|
+
warnings: [],
|
|
690
|
+
generated_at: nowIso(),
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
async function planUnlink(id, options, ctx, stepRef) {
|
|
694
|
+
const removeIds = toArray(options.link);
|
|
695
|
+
if (removeIds.length === 0) {
|
|
696
|
+
throw new PmCliError("pm plan unlink requires --link <id> to remove", EXIT_CODE.USAGE);
|
|
697
|
+
}
|
|
698
|
+
const { document, itemId, resultStep } = await mutatePlanSteps({
|
|
699
|
+
id,
|
|
700
|
+
options,
|
|
701
|
+
ctx,
|
|
702
|
+
op: "plan_unlink_step",
|
|
703
|
+
message: options.message ?? `plan unlink ${stepRef}`,
|
|
704
|
+
mutator(steps) {
|
|
705
|
+
const step = resolveStepRef(steps, stepRef);
|
|
706
|
+
const filtered = (step.linked_items ?? []).filter((link) => !removeIds.includes(link.id));
|
|
707
|
+
step.linked_items = filtered.length > 0 ? filtered : undefined;
|
|
708
|
+
step.updated_at = nowIso();
|
|
709
|
+
return { changedSteps: [step.id], resultStep: step };
|
|
710
|
+
},
|
|
711
|
+
});
|
|
712
|
+
const plan = projectPlan(document.metadata, "standard");
|
|
713
|
+
return {
|
|
714
|
+
action: "unlink",
|
|
715
|
+
plan,
|
|
716
|
+
step: resultStep,
|
|
717
|
+
next_actions: nextActionsFor(itemId, plan),
|
|
718
|
+
warnings: [],
|
|
719
|
+
generated_at: nowIso(),
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
async function planAppendLog(id, options, ctx, kind) {
|
|
723
|
+
if (kind === "decision" && !options.decisionText?.trim()) {
|
|
724
|
+
throw new PmCliError("pm plan decision requires --decision-text", EXIT_CODE.USAGE);
|
|
725
|
+
}
|
|
726
|
+
if (kind === "discovery" && !options.discoveryText?.trim()) {
|
|
727
|
+
throw new PmCliError("pm plan discovery requires --discovery-text", EXIT_CODE.USAGE);
|
|
728
|
+
}
|
|
729
|
+
if (kind === "validation" && !options.validationText?.trim()) {
|
|
730
|
+
throw new PmCliError("pm plan validation requires --validation-text", EXIT_CODE.USAGE);
|
|
731
|
+
}
|
|
732
|
+
const author = resolveAuthor(options.author, ctx.settings.author_default);
|
|
733
|
+
const { document, itemId } = await mutatePlanSteps({
|
|
734
|
+
id,
|
|
735
|
+
options,
|
|
736
|
+
ctx,
|
|
737
|
+
op: `plan_append_${kind}`,
|
|
738
|
+
message: options.message ?? `plan ${kind} append`,
|
|
739
|
+
mutator(_steps, doc) {
|
|
740
|
+
const now = nowIso();
|
|
741
|
+
if (kind === "decision") {
|
|
742
|
+
const list = doc.metadata.plan_decisions ?? [];
|
|
743
|
+
list.push({
|
|
744
|
+
ts: now,
|
|
745
|
+
author,
|
|
746
|
+
decision: options.decisionText.trim(),
|
|
747
|
+
rationale: options.decisionRationale?.trim() || undefined,
|
|
748
|
+
evidence: options.decisionEvidence?.trim() || undefined,
|
|
749
|
+
});
|
|
750
|
+
doc.metadata.plan_decisions = list;
|
|
751
|
+
return { changedSteps: [] };
|
|
752
|
+
}
|
|
753
|
+
if (kind === "discovery") {
|
|
754
|
+
const list = doc.metadata.plan_discoveries ?? [];
|
|
755
|
+
list.push({ ts: now, author, text: options.discoveryText.trim() });
|
|
756
|
+
doc.metadata.plan_discoveries = list;
|
|
757
|
+
return { changedSteps: [] };
|
|
758
|
+
}
|
|
759
|
+
const list = doc.metadata.plan_validation ?? [];
|
|
760
|
+
list.push({
|
|
761
|
+
text: options.validationText.trim(),
|
|
762
|
+
command: options.validationCommand?.trim() || undefined,
|
|
763
|
+
expected: options.validationExpected?.trim() || undefined,
|
|
764
|
+
});
|
|
765
|
+
doc.metadata.plan_validation = list;
|
|
766
|
+
return { changedSteps: [] };
|
|
767
|
+
},
|
|
768
|
+
});
|
|
769
|
+
const plan = projectPlan(document.metadata, "deep");
|
|
770
|
+
return {
|
|
771
|
+
action: kind,
|
|
772
|
+
plan,
|
|
773
|
+
next_actions: nextActionsFor(itemId, plan),
|
|
774
|
+
warnings: [],
|
|
775
|
+
generated_at: nowIso(),
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
async function planResume(id, options, ctx) {
|
|
779
|
+
const text = options.resumeContext?.trim();
|
|
780
|
+
if (!text) {
|
|
781
|
+
throw new PmCliError("pm plan resume requires --resume-context <text>", EXIT_CODE.USAGE);
|
|
782
|
+
}
|
|
783
|
+
const { document, itemId } = await mutatePlanSteps({
|
|
784
|
+
id,
|
|
785
|
+
options,
|
|
786
|
+
ctx,
|
|
787
|
+
op: "plan_resume",
|
|
788
|
+
message: options.message ?? "plan resume context update",
|
|
789
|
+
mutator(_steps, doc) {
|
|
790
|
+
doc.metadata.plan_resume_context = text;
|
|
791
|
+
return { changedSteps: [] };
|
|
792
|
+
},
|
|
793
|
+
});
|
|
794
|
+
const plan = projectPlan(document.metadata, "brief");
|
|
795
|
+
return {
|
|
796
|
+
action: "resume",
|
|
797
|
+
plan,
|
|
798
|
+
next_actions: nextActionsFor(itemId, plan),
|
|
799
|
+
warnings: [],
|
|
800
|
+
generated_at: nowIso(),
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
async function planApprove(id, options, ctx) {
|
|
804
|
+
const mode = asPlanMode(options.mode ?? "approved", "approved");
|
|
805
|
+
const { document, itemId } = await mutatePlanSteps({
|
|
806
|
+
id,
|
|
807
|
+
options,
|
|
808
|
+
ctx,
|
|
809
|
+
op: "plan_approve",
|
|
810
|
+
message: options.message ?? `plan approve mode=${mode}`,
|
|
811
|
+
mutator(_steps, doc) {
|
|
812
|
+
doc.metadata.plan_mode = mode;
|
|
813
|
+
return { changedSteps: [] };
|
|
814
|
+
},
|
|
815
|
+
});
|
|
816
|
+
const plan = projectPlan(document.metadata, "brief");
|
|
817
|
+
return {
|
|
818
|
+
action: "approve",
|
|
819
|
+
plan,
|
|
820
|
+
next_actions: nextActionsFor(itemId, plan),
|
|
821
|
+
warnings: [],
|
|
822
|
+
generated_at: nowIso(),
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
async function planMaterialize(id, options, ctx) {
|
|
826
|
+
const stepRefs = toArray(options.steps);
|
|
827
|
+
if (stepRefs.length === 0) {
|
|
828
|
+
throw new PmCliError("pm plan materialize requires --steps <ids|orders>", EXIT_CODE.USAGE);
|
|
829
|
+
}
|
|
830
|
+
const targetType = options.materializeType?.trim() || "Task";
|
|
831
|
+
const typeRegistry = resolveItemTypeRegistry(ctx.settings, getActiveExtensionRegistrations());
|
|
832
|
+
const resolvedTypeName = resolveTypeName(targetType, typeRegistry);
|
|
833
|
+
if (!resolvedTypeName) {
|
|
834
|
+
throw new PmCliError(`Invalid --materialize-type "${targetType}"`, EXIT_CODE.USAGE);
|
|
835
|
+
}
|
|
836
|
+
const parent = options.materializeParent?.trim() || id;
|
|
837
|
+
const tags = options.materializeTags;
|
|
838
|
+
const planRead = await readPlanItem(ctx, id);
|
|
839
|
+
const steps = (planRead.document.metadata.plan_steps ?? []).slice();
|
|
840
|
+
const targets = stepRefs.map((ref) => resolveStepRef(steps, ref));
|
|
841
|
+
if (targets.length === 0) {
|
|
842
|
+
throw new PmCliError("No matching plan steps found for --steps", EXIT_CODE.NOT_FOUND);
|
|
843
|
+
}
|
|
844
|
+
const materialized = [];
|
|
845
|
+
for (const step of targets) {
|
|
846
|
+
const deps = [
|
|
847
|
+
`id=${parent},kind=parent`,
|
|
848
|
+
`id=${planRead.itemId},kind=discovered_from`,
|
|
849
|
+
];
|
|
850
|
+
for (const link of step.linked_items ?? []) {
|
|
851
|
+
const realKind = link.kind === "blocked_by" || link.kind === "blocks" || link.kind === "related" || link.kind === "discovered_from"
|
|
852
|
+
? link.kind
|
|
853
|
+
: "related";
|
|
854
|
+
deps.push(`id=${link.id},kind=${realKind}`);
|
|
855
|
+
}
|
|
856
|
+
const created = await runCreate({
|
|
857
|
+
title: step.title,
|
|
858
|
+
description: step.body?.trim() || step.title,
|
|
859
|
+
type: resolvedTypeName,
|
|
860
|
+
parent,
|
|
861
|
+
tags,
|
|
862
|
+
author: options.author,
|
|
863
|
+
message: options.message ?? `materialized from plan ${planRead.itemId} step ${step.id}`,
|
|
864
|
+
dep: deps,
|
|
865
|
+
}, ctx.settings ? { ...{}, path: ctx.pmRoot } : {});
|
|
866
|
+
materialized.push({ id: created.item.id, type: resolvedTypeName, from_step: step.id });
|
|
867
|
+
}
|
|
868
|
+
const { document, itemId } = await mutatePlanSteps({
|
|
869
|
+
id,
|
|
870
|
+
options,
|
|
871
|
+
ctx,
|
|
872
|
+
op: "plan_materialize",
|
|
873
|
+
message: options.message ?? `plan materialize ${stepRefs.join(",")}`,
|
|
874
|
+
mutator(currentSteps, doc) {
|
|
875
|
+
for (const target of targets) {
|
|
876
|
+
const matched = materialized.find((entry) => entry.from_step === target.id);
|
|
877
|
+
if (!matched)
|
|
878
|
+
continue;
|
|
879
|
+
const step = currentSteps.find((entry) => entry.id === target.id);
|
|
880
|
+
if (!step)
|
|
881
|
+
continue;
|
|
882
|
+
const links = step.linked_items ?? [];
|
|
883
|
+
links.push({ id: matched.id, kind: "implements", note: `materialized as ${matched.type}` });
|
|
884
|
+
step.linked_items = links;
|
|
885
|
+
step.updated_at = nowIso();
|
|
886
|
+
}
|
|
887
|
+
const deps = doc.metadata.dependencies ?? [];
|
|
888
|
+
for (const m of materialized) {
|
|
889
|
+
deps.push({ id: m.id, kind: "child", created_at: nowIso(), author: resolveAuthor(options.author, ctx.settings.author_default) });
|
|
890
|
+
}
|
|
891
|
+
doc.metadata.dependencies = deps;
|
|
892
|
+
return { changedSteps: targets.map((entry) => entry.id) };
|
|
893
|
+
},
|
|
894
|
+
});
|
|
895
|
+
const plan = projectPlan(document.metadata, "standard");
|
|
896
|
+
return {
|
|
897
|
+
action: "materialize",
|
|
898
|
+
plan,
|
|
899
|
+
materialized,
|
|
900
|
+
next_actions: nextActionsFor(itemId, plan),
|
|
901
|
+
warnings: [],
|
|
902
|
+
generated_at: nowIso(),
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
export async function runPlan(input) {
|
|
906
|
+
const ctx = await loadContext(input.global);
|
|
907
|
+
switch (input.subcommand) {
|
|
908
|
+
case "create":
|
|
909
|
+
return planCreate(input.options, input.global, ctx);
|
|
910
|
+
case "show":
|
|
911
|
+
if (!input.id)
|
|
912
|
+
throw new PmCliError("pm plan show requires a plan id", EXIT_CODE.USAGE);
|
|
913
|
+
return planShow(input.id, input.options, ctx);
|
|
914
|
+
case "add-step":
|
|
915
|
+
if (!input.id)
|
|
916
|
+
throw new PmCliError("pm plan add-step requires a plan id", EXIT_CODE.USAGE);
|
|
917
|
+
return planAddStep(input.id, input.options, ctx);
|
|
918
|
+
case "update-step":
|
|
919
|
+
if (!input.id || !input.stepRef)
|
|
920
|
+
throw new PmCliError("pm plan update-step requires <plan-id> <step>", EXIT_CODE.USAGE);
|
|
921
|
+
return planUpdateStep(input.id, input.options, ctx, { stepRef: input.stepRef, op: "plan_update_step" });
|
|
922
|
+
case "complete-step":
|
|
923
|
+
if (!input.id || !input.stepRef)
|
|
924
|
+
throw new PmCliError("pm plan complete-step requires <plan-id> <step>", EXIT_CODE.USAGE);
|
|
925
|
+
return planUpdateStep(input.id, input.options, ctx, { stepRef: input.stepRef, finalStatus: "completed", op: "plan_complete_step" });
|
|
926
|
+
case "block-step":
|
|
927
|
+
if (!input.id || !input.stepRef)
|
|
928
|
+
throw new PmCliError("pm plan block-step requires <plan-id> <step>", EXIT_CODE.USAGE);
|
|
929
|
+
return planUpdateStep(input.id, input.options, ctx, { stepRef: input.stepRef, finalStatus: "blocked", op: "plan_block_step" });
|
|
930
|
+
case "reorder-step":
|
|
931
|
+
if (!input.id || !input.stepRef || input.reorderTo === undefined)
|
|
932
|
+
throw new PmCliError("pm plan reorder-step requires <plan-id> <step> <new-order>", EXIT_CODE.USAGE);
|
|
933
|
+
return planReorderStep(input.id, input.options, ctx, input.stepRef, input.reorderTo);
|
|
934
|
+
case "remove-step":
|
|
935
|
+
if (!input.id || !input.stepRef)
|
|
936
|
+
throw new PmCliError("pm plan remove-step requires <plan-id> <step>", EXIT_CODE.USAGE);
|
|
937
|
+
return planRemoveStep(input.id, input.options, ctx, input.stepRef);
|
|
938
|
+
case "link":
|
|
939
|
+
if (!input.id || !input.stepRef)
|
|
940
|
+
throw new PmCliError("pm plan link requires <plan-id> <step>", EXIT_CODE.USAGE);
|
|
941
|
+
return planLink(input.id, input.options, ctx, input.stepRef);
|
|
942
|
+
case "unlink":
|
|
943
|
+
if (!input.id || !input.stepRef)
|
|
944
|
+
throw new PmCliError("pm plan unlink requires <plan-id> <step>", EXIT_CODE.USAGE);
|
|
945
|
+
return planUnlink(input.id, input.options, ctx, input.stepRef);
|
|
946
|
+
case "decision":
|
|
947
|
+
if (!input.id)
|
|
948
|
+
throw new PmCliError("pm plan decision requires a plan id", EXIT_CODE.USAGE);
|
|
949
|
+
return planAppendLog(input.id, input.options, ctx, "decision");
|
|
950
|
+
case "discovery":
|
|
951
|
+
if (!input.id)
|
|
952
|
+
throw new PmCliError("pm plan discovery requires a plan id", EXIT_CODE.USAGE);
|
|
953
|
+
return planAppendLog(input.id, input.options, ctx, "discovery");
|
|
954
|
+
case "validation":
|
|
955
|
+
if (!input.id)
|
|
956
|
+
throw new PmCliError("pm plan validation requires a plan id", EXIT_CODE.USAGE);
|
|
957
|
+
return planAppendLog(input.id, input.options, ctx, "validation");
|
|
958
|
+
case "resume":
|
|
959
|
+
if (!input.id)
|
|
960
|
+
throw new PmCliError("pm plan resume requires a plan id", EXIT_CODE.USAGE);
|
|
961
|
+
return planResume(input.id, input.options, ctx);
|
|
962
|
+
case "approve":
|
|
963
|
+
if (!input.id)
|
|
964
|
+
throw new PmCliError("pm plan approve requires a plan id", EXIT_CODE.USAGE);
|
|
965
|
+
return planApprove(input.id, input.options, ctx);
|
|
966
|
+
case "materialize":
|
|
967
|
+
if (!input.id)
|
|
968
|
+
throw new PmCliError("pm plan materialize requires a plan id", EXIT_CODE.USAGE);
|
|
969
|
+
return planMaterialize(input.id, input.options, ctx);
|
|
970
|
+
default:
|
|
971
|
+
throw new PmCliError(`Unknown pm plan subcommand "${input.subcommand}"`, EXIT_CODE.USAGE);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
//# sourceMappingURL=plan.js.map
|
|
975
|
+
//# debugId=588c239a-66d0-5d60-b675-5a5a4e79d70b
|