@unbrained/pm-cli 2026.3.12 → 2026.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/pm/extensions/.managed-extensions.json +42 -0
- package/.agents/pm/extensions/beads/index.js +109 -0
- package/.agents/pm/extensions/beads/manifest.json +7 -0
- package/{dist/cli/commands/beads.js → .agents/pm/extensions/beads/runtime.js} +31 -21
- package/.agents/pm/extensions/beads/runtime.ts +702 -0
- package/.agents/pm/extensions/todos/index.js +126 -0
- package/.agents/pm/extensions/todos/manifest.json +7 -0
- package/{dist/extensions/builtins/todos/import-export.js → .agents/pm/extensions/todos/runtime.js} +39 -29
- package/.agents/pm/extensions/todos/runtime.ts +568 -0
- package/AGENTS.md +196 -92
- package/CHANGELOG.md +399 -0
- package/CODE_OF_CONDUCT.md +42 -0
- package/CONTRIBUTING.md +144 -0
- package/PRD.md +512 -164
- package/README.md +1053 -2
- package/SECURITY.md +51 -0
- package/dist/cli/commands/activity.d.ts +5 -0
- package/dist/cli/commands/activity.js +66 -3
- package/dist/cli/commands/activity.js.map +1 -1
- package/dist/cli/commands/aggregate.d.ts +54 -0
- package/dist/cli/commands/aggregate.js +181 -0
- package/dist/cli/commands/aggregate.js.map +1 -0
- package/dist/cli/commands/append.js +4 -1
- package/dist/cli/commands/append.js.map +1 -1
- package/dist/cli/commands/calendar.d.ts +109 -0
- package/dist/cli/commands/calendar.js +797 -0
- package/dist/cli/commands/calendar.js.map +1 -0
- package/dist/cli/commands/claim.d.ts +5 -1
- package/dist/cli/commands/claim.js +42 -21
- package/dist/cli/commands/claim.js.map +1 -1
- package/dist/cli/commands/close.d.ts +1 -0
- package/dist/cli/commands/close.js +54 -5
- package/dist/cli/commands/close.js.map +1 -1
- package/dist/cli/commands/comments-audit.d.ts +91 -0
- package/dist/cli/commands/comments-audit.js +195 -0
- package/dist/cli/commands/comments-audit.js.map +1 -0
- package/dist/cli/commands/comments.d.ts +1 -0
- package/dist/cli/commands/comments.js +70 -21
- package/dist/cli/commands/comments.js.map +1 -1
- package/dist/cli/commands/completion.d.ts +10 -4
- package/dist/cli/commands/completion.js +1184 -137
- package/dist/cli/commands/completion.js.map +1 -1
- package/dist/cli/commands/config.d.ts +35 -3
- package/dist/cli/commands/config.js +968 -13
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/context.d.ts +86 -0
- package/dist/cli/commands/context.js +299 -0
- package/dist/cli/commands/context.js.map +1 -0
- package/dist/cli/commands/contracts.d.ts +78 -0
- package/dist/cli/commands/contracts.js +920 -0
- package/dist/cli/commands/contracts.js.map +1 -0
- package/dist/cli/commands/create.d.ts +48 -14
- package/dist/cli/commands/create.js +1331 -160
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/dedupe-audit.d.ts +81 -0
- package/dist/cli/commands/dedupe-audit.js +330 -0
- package/dist/cli/commands/dedupe-audit.js.map +1 -0
- package/dist/cli/commands/deps.d.ts +52 -0
- package/dist/cli/commands/deps.js +204 -0
- package/dist/cli/commands/deps.js.map +1 -0
- package/dist/cli/commands/docs.d.ts +19 -0
- package/dist/cli/commands/docs.js +212 -13
- package/dist/cli/commands/docs.js.map +1 -1
- package/dist/cli/commands/extension.d.ts +122 -0
- package/dist/cli/commands/extension.js +1850 -0
- package/dist/cli/commands/extension.js.map +1 -0
- package/dist/cli/commands/files.d.ts +52 -1
- package/dist/cli/commands/files.js +443 -13
- package/dist/cli/commands/files.js.map +1 -1
- package/dist/cli/commands/gc.d.ts +11 -1
- package/dist/cli/commands/gc.js +89 -11
- package/dist/cli/commands/gc.js.map +1 -1
- package/dist/cli/commands/get.d.ts +13 -0
- package/dist/cli/commands/get.js +35 -3
- package/dist/cli/commands/get.js.map +1 -1
- package/dist/cli/commands/health.d.ts +10 -2
- package/dist/cli/commands/health.js +774 -23
- package/dist/cli/commands/health.js.map +1 -1
- package/dist/cli/commands/history.d.ts +20 -0
- package/dist/cli/commands/history.js +152 -6
- package/dist/cli/commands/history.js.map +1 -1
- package/dist/cli/commands/index.d.ts +16 -3
- package/dist/cli/commands/index.js +16 -3
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +7 -2
- package/dist/cli/commands/init.js +137 -5
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/learnings.d.ts +17 -0
- package/dist/cli/commands/learnings.js +129 -0
- package/dist/cli/commands/learnings.js.map +1 -0
- package/dist/cli/commands/list.d.ts +29 -1
- package/dist/cli/commands/list.js +289 -53
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/normalize.d.ts +51 -0
- package/dist/cli/commands/normalize.js +298 -0
- package/dist/cli/commands/normalize.js.map +1 -0
- package/dist/cli/commands/notes.d.ts +17 -0
- package/dist/cli/commands/notes.js +129 -0
- package/dist/cli/commands/notes.js.map +1 -0
- package/dist/cli/commands/reindex.d.ts +1 -0
- package/dist/cli/commands/reindex.js +208 -32
- package/dist/cli/commands/reindex.js.map +1 -1
- package/dist/cli/commands/restore.js +164 -30
- package/dist/cli/commands/restore.js.map +1 -1
- package/dist/cli/commands/search.d.ts +14 -1
- package/dist/cli/commands/search.js +475 -81
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/stats.js +26 -10
- package/dist/cli/commands/stats.js.map +1 -1
- package/dist/cli/commands/templates.d.ts +26 -0
- package/dist/cli/commands/templates.js +179 -0
- package/dist/cli/commands/templates.js.map +1 -0
- package/dist/cli/commands/test-all.d.ts +19 -1
- package/dist/cli/commands/test-all.js +161 -13
- package/dist/cli/commands/test-all.js.map +1 -1
- package/dist/cli/commands/test-runs.d.ts +63 -0
- package/dist/cli/commands/test-runs.js +179 -0
- package/dist/cli/commands/test-runs.js.map +1 -0
- package/dist/cli/commands/test.d.ts +75 -1
- package/dist/cli/commands/test.js +1360 -41
- package/dist/cli/commands/test.js.map +1 -1
- package/dist/cli/commands/update-many.d.ts +57 -0
- package/dist/cli/commands/update-many.js +631 -0
- package/dist/cli/commands/update-many.js.map +1 -0
- package/dist/cli/commands/update.d.ts +30 -0
- package/dist/cli/commands/update.js +1393 -84
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/commands/validate.d.ts +30 -0
- package/dist/cli/commands/validate.js +1140 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/error-guidance.d.ts +33 -0
- package/dist/cli/error-guidance.js +337 -0
- package/dist/cli/error-guidance.js.map +1 -0
- package/dist/cli/extension-command-options.d.ts +1 -0
- package/dist/cli/extension-command-options.js +92 -0
- package/dist/cli/extension-command-options.js.map +1 -1
- package/dist/cli/help-content.d.ts +20 -0
- package/dist/cli/help-content.js +543 -0
- package/dist/cli/help-content.js.map +1 -0
- package/dist/cli/main.js +3625 -445
- package/dist/cli/main.js.map +1 -1
- package/dist/core/extensions/index.d.ts +13 -1
- package/dist/core/extensions/index.js +108 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/item-fields.d.ts +2 -0
- package/dist/core/extensions/item-fields.js +79 -0
- package/dist/core/extensions/item-fields.js.map +1 -0
- package/dist/core/extensions/loader.d.ts +322 -9
- package/dist/core/extensions/loader.js +911 -20
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runtime-registrations.d.ts +5 -0
- package/dist/core/extensions/runtime-registrations.js +51 -0
- package/dist/core/extensions/runtime-registrations.js.map +1 -0
- package/dist/core/history/history-stream-policy.d.ts +20 -0
- package/dist/core/history/history-stream-policy.js +53 -0
- package/dist/core/history/history-stream-policy.js.map +1 -0
- package/dist/core/history/history.js +90 -1
- package/dist/core/history/history.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.d.ts +1 -0
- package/dist/core/item/index.js +1 -0
- package/dist/core/item/index.js.map +1 -1
- package/dist/core/item/item-format.d.ts +11 -5
- package/dist/core/item/item-format.js +507 -24
- package/dist/core/item/item-format.js.map +1 -1
- package/dist/core/item/parent-reference-policy.d.ts +6 -0
- package/dist/core/item/parent-reference-policy.js +32 -0
- package/dist/core/item/parent-reference-policy.js.map +1 -0
- package/dist/core/item/parse.d.ts +5 -0
- package/dist/core/item/parse.js +216 -19
- package/dist/core/item/parse.js.map +1 -1
- package/dist/core/item/sprint-release-format.d.ts +6 -0
- package/dist/core/item/sprint-release-format.js +33 -0
- package/dist/core/item/sprint-release-format.js.map +1 -0
- package/dist/core/item/status.d.ts +3 -0
- package/dist/core/item/status.js +24 -0
- package/dist/core/item/status.js.map +1 -0
- package/dist/core/item/type-registry.d.ts +37 -0
- package/dist/core/item/type-registry.js +706 -0
- package/dist/core/item/type-registry.js.map +1 -0
- package/dist/core/lock/lock.d.ts +1 -1
- package/dist/core/lock/lock.js +101 -12
- package/dist/core/lock/lock.js.map +1 -1
- package/dist/core/output/command-aware.d.ts +1 -0
- package/dist/core/output/command-aware.js +394 -0
- package/dist/core/output/command-aware.js.map +1 -0
- package/dist/core/output/output.d.ts +3 -0
- package/dist/core/output/output.js +124 -6
- package/dist/core/output/output.js.map +1 -1
- package/dist/core/schema/runtime-field-filters.d.ts +3 -0
- package/dist/core/schema/runtime-field-filters.js +39 -0
- package/dist/core/schema/runtime-field-filters.js.map +1 -0
- package/dist/core/schema/runtime-field-values.d.ts +8 -0
- package/dist/core/schema/runtime-field-values.js +154 -0
- package/dist/core/schema/runtime-field-values.js.map +1 -0
- package/dist/core/schema/runtime-schema.d.ts +68 -0
- package/dist/core/schema/runtime-schema.js +554 -0
- package/dist/core/schema/runtime-schema.js.map +1 -0
- package/dist/core/search/cache.d.ts +13 -1
- package/dist/core/search/cache.js +123 -14
- package/dist/core/search/cache.js.map +1 -1
- package/dist/core/search/semantic-defaults.d.ts +6 -0
- package/dist/core/search/semantic-defaults.js +120 -0
- package/dist/core/search/semantic-defaults.js.map +1 -0
- package/dist/core/search/vector-stores.js +3 -1
- package/dist/core/search/vector-stores.js.map +1 -1
- package/dist/core/shared/command-types.d.ts +2 -0
- package/dist/core/shared/conflict-markers.d.ts +7 -0
- package/dist/core/shared/conflict-markers.js +27 -0
- package/dist/core/shared/conflict-markers.js.map +1 -0
- package/dist/core/shared/constants.d.ts +15 -4
- package/dist/core/shared/constants.js +141 -1
- package/dist/core/shared/constants.js.map +1 -1
- package/dist/core/shared/errors.d.ts +10 -1
- package/dist/core/shared/errors.js +3 -1
- package/dist/core/shared/errors.js.map +1 -1
- package/dist/core/shared/text-normalization.d.ts +4 -0
- package/dist/core/shared/text-normalization.js +33 -0
- package/dist/core/shared/text-normalization.js.map +1 -0
- package/dist/core/shared/time.d.ts +1 -2
- package/dist/core/shared/time.js +98 -11
- package/dist/core/shared/time.js.map +1 -1
- package/dist/core/store/index.d.ts +1 -0
- package/dist/core/store/index.js +1 -0
- package/dist/core/store/index.js.map +1 -1
- package/dist/core/store/item-format-migration.d.ts +9 -0
- package/dist/core/store/item-format-migration.js +87 -0
- package/dist/core/store/item-format-migration.js.map +1 -0
- package/dist/core/store/item-store.d.ts +13 -4
- package/dist/core/store/item-store.js +238 -51
- package/dist/core/store/item-store.js.map +1 -1
- package/dist/core/store/paths.d.ts +21 -3
- package/dist/core/store/paths.js +59 -4
- package/dist/core/store/paths.js.map +1 -1
- package/dist/core/store/settings.d.ts +14 -1
- package/dist/core/store/settings.js +463 -7
- package/dist/core/store/settings.js.map +1 -1
- package/dist/core/telemetry/consent.d.ts +2 -0
- package/dist/core/telemetry/consent.js +79 -0
- package/dist/core/telemetry/consent.js.map +1 -0
- package/dist/core/telemetry/runtime.d.ts +38 -0
- package/dist/core/telemetry/runtime.js +733 -0
- package/dist/core/telemetry/runtime.js.map +1 -0
- package/dist/core/test/background-runs.d.ts +117 -0
- package/dist/core/test/background-runs.js +760 -0
- package/dist/core/test/background-runs.js.map +1 -0
- package/dist/core/test/item-test-run-tracking.d.ts +9 -0
- package/dist/core/test/item-test-run-tracking.js +50 -0
- package/dist/core/test/item-test-run-tracking.js.map +1 -0
- package/dist/sdk/cli-contracts.d.ts +92 -0
- package/dist/sdk/cli-contracts.js +2357 -0
- package/dist/sdk/cli-contracts.js.map +1 -0
- package/dist/sdk/index.d.ts +34 -0
- package/dist/sdk/index.js +23 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/types.d.ts +197 -3
- package/dist/types.js +48 -1
- package/dist/types.js.map +1 -1
- package/docs/ARCHITECTURE.md +368 -39
- package/docs/EXTENSIONS.md +454 -49
- package/docs/RELEASING.md +68 -19
- package/docs/SDK.md +123 -0
- package/docs/examples/starter-extension/README.md +48 -0
- package/docs/examples/starter-extension/index.js +191 -0
- package/docs/examples/starter-extension/manifest.json +17 -0
- package/docs/examples/starter-extension/package.json +10 -0
- package/package.json +33 -6
- package/.pi/extensions/pm-cli/index.ts +0 -778
- package/dist/cli/commands/beads.d.ts +0 -16
- package/dist/cli/commands/beads.js.map +0 -1
- package/dist/cli/commands/install.d.ts +0 -18
- package/dist/cli/commands/install.js +0 -87
- package/dist/cli/commands/install.js.map +0 -1
- package/dist/core/extensions/builtins.d.ts +0 -3
- package/dist/core/extensions/builtins.js +0 -47
- package/dist/core/extensions/builtins.js.map +0 -1
- package/dist/extensions/builtins/beads/index.d.ts +0 -8
- package/dist/extensions/builtins/beads/index.js +0 -33
- package/dist/extensions/builtins/beads/index.js.map +0 -1
- package/dist/extensions/builtins/todos/import-export.d.ts +0 -26
- package/dist/extensions/builtins/todos/import-export.js.map +0 -1
- package/dist/extensions/builtins/todos/index.d.ts +0 -8
- package/dist/extensions/builtins/todos/index.js +0 -38
- package/dist/extensions/builtins/todos/index.js.map +0 -1
|
@@ -2,21 +2,175 @@ import { pathExists, removeFileIfExists, writeFileAtomic } from "../../core/fs/f
|
|
|
2
2
|
import { appendHistoryEntry, createHistoryEntry } from "../../core/history/history.js";
|
|
3
3
|
import { generateItemId, normalizeItemId } from "../../core/item/id.js";
|
|
4
4
|
import { canonicalDocument, normalizeFrontMatter, serializeItemDocument } from "../../core/item/item-format.js";
|
|
5
|
-
import {
|
|
5
|
+
import { normalizeParentReferenceValue, validateMissingParentReference, } from "../../core/item/parent-reference-policy.js";
|
|
6
|
+
import { validateSprintOrReleaseValue } from "../../core/item/sprint-release-format.js";
|
|
7
|
+
import { createStdinTokenResolver, parseCsvKv, parseOptionalNumber, parseTags } from "../../core/item/parse.js";
|
|
8
|
+
import { normalizeStatusInput } from "../../core/item/status.js";
|
|
9
|
+
import { canonicalizeCommandOptionKey, commandOptionFlagLabel, resolveItemTypeRegistry, resolveCommandOptionPolicyState, resolveTypeDefinition, resolveTypeName, validateTypeOptions, } from "../../core/item/type-registry.js";
|
|
6
10
|
import { acquireLock } from "../../core/lock/lock.js";
|
|
11
|
+
import { collectRuntimeCreateFieldValues } from "../../core/schema/runtime-field-values.js";
|
|
12
|
+
import { resolveRuntimeFieldRegistry, resolveRuntimeStatusRegistry, } from "../../core/schema/runtime-schema.js";
|
|
7
13
|
import { EXIT_CODE, FRONT_MATTER_KEY_ORDER } from "../../core/shared/constants.js";
|
|
8
14
|
import { PmCliError } from "../../core/shared/errors.js";
|
|
9
|
-
import {
|
|
10
|
-
import { runActiveOnWriteHooks } from "../../core/extensions/index.js";
|
|
15
|
+
import { nowIso, resolveIsoOrRelative } from "../../core/shared/time.js";
|
|
16
|
+
import { getActiveExtensionRegistrations, runActiveOnWriteHooks } from "../../core/extensions/index.js";
|
|
17
|
+
import { applyRegisteredItemFieldDefaultsAndValidation } from "../../core/extensions/item-fields.js";
|
|
18
|
+
import { locateItem } from "../../core/store/item-store.js";
|
|
11
19
|
import { getHistoryPath, getItemPath, getSettingsPath, resolvePmRoot } from "../../core/store/paths.js";
|
|
12
20
|
import { readSettings } from "../../core/store/settings.js";
|
|
13
|
-
import {
|
|
21
|
+
import { loadCreateTemplateOptions } from "./templates.js";
|
|
22
|
+
import { CONFIDENCE_TEXT_VALUES, DEPENDENCY_KIND_VALUES, ISSUE_SEVERITY_VALUES, RECURRENCE_FREQUENCY_VALUES, RECURRENCE_WEEKDAY_VALUES, RISK_VALUES, SCOPE_VALUES, } from "../../types/index.js";
|
|
23
|
+
const CREATE_MODE_VALUES = ["strict", "progressive"];
|
|
24
|
+
const SCHEDULE_CREATE_PRESET_VALUES = ["lightweight"];
|
|
25
|
+
const SCHEDULE_CREATE_PRESET_TYPES = new Set(["Reminder", "Meeting", "Event"]);
|
|
26
|
+
const LOG_SEED_ALLOWED_KEYS = new Set(["author", "created_at", "text"]);
|
|
27
|
+
const LEGACY_NONE_TOKENS = new Set(["none", "null"]);
|
|
28
|
+
const CREATE_UNSET_FIELD_DEFINITIONS = [
|
|
29
|
+
{ canonical: "tags", aliases: ["tags"], optionKey: "tags", frontMatterKey: "tags" },
|
|
30
|
+
{ canonical: "deadline", aliases: ["deadline"], optionKey: "deadline", frontMatterKey: "deadline" },
|
|
31
|
+
{
|
|
32
|
+
canonical: "estimate",
|
|
33
|
+
aliases: ["estimate", "estimated_minutes", "estimated-minutes"],
|
|
34
|
+
optionKey: "estimatedMinutes",
|
|
35
|
+
frontMatterKey: "estimated_minutes",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
canonical: "acceptance-criteria",
|
|
39
|
+
aliases: ["acceptance_criteria", "acceptance-criteria", "ac"],
|
|
40
|
+
optionKey: "acceptanceCriteria",
|
|
41
|
+
frontMatterKey: "acceptance_criteria",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
canonical: "definition-of-ready",
|
|
45
|
+
aliases: ["definition_of_ready", "definition-of-ready"],
|
|
46
|
+
optionKey: "definitionOfReady",
|
|
47
|
+
frontMatterKey: "definition_of_ready",
|
|
48
|
+
},
|
|
49
|
+
{ canonical: "order", aliases: ["order", "rank"], optionKey: "order", frontMatterKey: "order" },
|
|
50
|
+
{ canonical: "goal", aliases: ["goal"], optionKey: "goal", frontMatterKey: "goal" },
|
|
51
|
+
{ canonical: "objective", aliases: ["objective"], optionKey: "objective", frontMatterKey: "objective" },
|
|
52
|
+
{ canonical: "value", aliases: ["value"], optionKey: "value", frontMatterKey: "value" },
|
|
53
|
+
{ canonical: "impact", aliases: ["impact"], optionKey: "impact", frontMatterKey: "impact" },
|
|
54
|
+
{ canonical: "outcome", aliases: ["outcome"], optionKey: "outcome", frontMatterKey: "outcome" },
|
|
55
|
+
{ canonical: "why-now", aliases: ["why_now", "why-now"], optionKey: "whyNow", frontMatterKey: "why_now" },
|
|
56
|
+
{ canonical: "author", aliases: ["author"], optionKey: "author", frontMatterKey: "author" },
|
|
57
|
+
{ canonical: "assignee", aliases: ["assignee"], optionKey: "assignee", frontMatterKey: "assignee" },
|
|
58
|
+
{ canonical: "parent", aliases: ["parent"], optionKey: "parent", frontMatterKey: "parent" },
|
|
59
|
+
{ canonical: "reviewer", aliases: ["reviewer"], optionKey: "reviewer", frontMatterKey: "reviewer" },
|
|
60
|
+
{ canonical: "risk", aliases: ["risk"], optionKey: "risk", frontMatterKey: "risk" },
|
|
61
|
+
{ canonical: "confidence", aliases: ["confidence"], optionKey: "confidence", frontMatterKey: "confidence" },
|
|
62
|
+
{ canonical: "sprint", aliases: ["sprint"], optionKey: "sprint", frontMatterKey: "sprint" },
|
|
63
|
+
{ canonical: "release", aliases: ["release"], optionKey: "release", frontMatterKey: "release" },
|
|
64
|
+
{
|
|
65
|
+
canonical: "blocked-by",
|
|
66
|
+
aliases: ["blocked_by", "blocked-by"],
|
|
67
|
+
optionKey: "blockedBy",
|
|
68
|
+
frontMatterKey: "blocked_by",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
canonical: "blocked-reason",
|
|
72
|
+
aliases: ["blocked_reason", "blocked-reason"],
|
|
73
|
+
optionKey: "blockedReason",
|
|
74
|
+
frontMatterKey: "blocked_reason",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
canonical: "unblock-note",
|
|
78
|
+
aliases: ["unblock_note", "unblock-note"],
|
|
79
|
+
optionKey: "unblockNote",
|
|
80
|
+
frontMatterKey: "unblock_note",
|
|
81
|
+
},
|
|
82
|
+
{ canonical: "reporter", aliases: ["reporter"], optionKey: "reporter", frontMatterKey: "reporter" },
|
|
83
|
+
{ canonical: "severity", aliases: ["severity"], optionKey: "severity", frontMatterKey: "severity" },
|
|
84
|
+
{
|
|
85
|
+
canonical: "environment",
|
|
86
|
+
aliases: ["environment"],
|
|
87
|
+
optionKey: "environment",
|
|
88
|
+
frontMatterKey: "environment",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
canonical: "repro-steps",
|
|
92
|
+
aliases: ["repro_steps", "repro-steps"],
|
|
93
|
+
optionKey: "reproSteps",
|
|
94
|
+
frontMatterKey: "repro_steps",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
canonical: "resolution",
|
|
98
|
+
aliases: ["resolution"],
|
|
99
|
+
optionKey: "resolution",
|
|
100
|
+
frontMatterKey: "resolution",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
canonical: "expected-result",
|
|
104
|
+
aliases: ["expected_result", "expected-result"],
|
|
105
|
+
optionKey: "expectedResult",
|
|
106
|
+
frontMatterKey: "expected_result",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
canonical: "actual-result",
|
|
110
|
+
aliases: ["actual_result", "actual-result"],
|
|
111
|
+
optionKey: "actualResult",
|
|
112
|
+
frontMatterKey: "actual_result",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
canonical: "affected-version",
|
|
116
|
+
aliases: ["affected_version", "affected-version"],
|
|
117
|
+
optionKey: "affectedVersion",
|
|
118
|
+
frontMatterKey: "affected_version",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
canonical: "fixed-version",
|
|
122
|
+
aliases: ["fixed_version", "fixed-version"],
|
|
123
|
+
optionKey: "fixedVersion",
|
|
124
|
+
frontMatterKey: "fixed_version",
|
|
125
|
+
},
|
|
126
|
+
{ canonical: "component", aliases: ["component"], optionKey: "component", frontMatterKey: "component" },
|
|
127
|
+
{ canonical: "regression", aliases: ["regression"], optionKey: "regression", frontMatterKey: "regression" },
|
|
128
|
+
{
|
|
129
|
+
canonical: "customer-impact",
|
|
130
|
+
aliases: ["customer_impact", "customer-impact"],
|
|
131
|
+
optionKey: "customerImpact",
|
|
132
|
+
frontMatterKey: "customer_impact",
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
const CREATE_UNSET_ALIAS_MAP = (() => {
|
|
136
|
+
const map = new Map();
|
|
137
|
+
for (const definition of CREATE_UNSET_FIELD_DEFINITIONS) {
|
|
138
|
+
for (const alias of definition.aliases) {
|
|
139
|
+
map.set(alias, {
|
|
140
|
+
optionKey: definition.optionKey,
|
|
141
|
+
frontMatterKey: definition.frontMatterKey,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return map;
|
|
146
|
+
})();
|
|
147
|
+
const CREATE_OPTION_KEY_TO_UNSET_CANONICAL = new Map(CREATE_UNSET_FIELD_DEFINITIONS.map((definition) => [definition.optionKey, definition.canonical]));
|
|
148
|
+
const CREATE_UNSET_SUPPORTED_CANONICAL_FIELDS = CREATE_UNSET_FIELD_DEFINITIONS.map((definition) => definition.canonical)
|
|
149
|
+
.sort((left, right) => left.localeCompare(right))
|
|
150
|
+
.join(", ");
|
|
151
|
+
function buildInvalidLogSeedKeysMessage(optionName, unsupportedKeys) {
|
|
152
|
+
const sortedUnsupported = [...unsupportedKeys].sort((left, right) => left.localeCompare(right));
|
|
153
|
+
const keyLabel = sortedUnsupported.length === 1 ? "key" : "keys";
|
|
154
|
+
return (`${optionName} supports only author, created_at, and text seed fields. ` +
|
|
155
|
+
`Found unsupported ${keyLabel}: ${sortedUnsupported.join(", ")}. ` +
|
|
156
|
+
`If text contains comma-separated key:value-like fragments, wrap text in quotes ` +
|
|
157
|
+
'(for example text="first,scope:project"), use markdown-style key/value input, ' +
|
|
158
|
+
`or pass ${optionName} - with piped stdin.`);
|
|
159
|
+
}
|
|
14
160
|
function ensureEnumValue(value, allowed, label) {
|
|
15
161
|
if (!allowed.includes(value)) {
|
|
16
162
|
throw new PmCliError(`Invalid ${label} value "${value}". Allowed: ${allowed.join(", ")}`, EXIT_CODE.USAGE);
|
|
17
163
|
}
|
|
18
164
|
return value;
|
|
19
165
|
}
|
|
166
|
+
function parseStatusValue(value, statusRegistry) {
|
|
167
|
+
const normalized = normalizeStatusInput(value, statusRegistry);
|
|
168
|
+
if (!normalized) {
|
|
169
|
+
const allowedStatuses = statusRegistry.definitions.map((definition) => definition.id);
|
|
170
|
+
throw new PmCliError(`Invalid status value "${value}". Allowed: ${allowedStatuses.join(", ")}`, EXIT_CODE.USAGE);
|
|
171
|
+
}
|
|
172
|
+
return normalized;
|
|
173
|
+
}
|
|
20
174
|
function normalizeRiskInput(value) {
|
|
21
175
|
const trimmed = value.trim();
|
|
22
176
|
return trimmed.toLowerCase() === "med" ? "medium" : trimmed;
|
|
@@ -59,29 +213,101 @@ function parseCreatedAt(value, currentIso) {
|
|
|
59
213
|
}
|
|
60
214
|
return new Date(parsed).toISOString();
|
|
61
215
|
}
|
|
216
|
+
function isLegacyNoneToken(value) {
|
|
217
|
+
if (value === undefined) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
return LEGACY_NONE_TOKENS.has(value.trim().toLowerCase());
|
|
221
|
+
}
|
|
222
|
+
function assertNoLegacyNoneToken(value, flag, replacementHint) {
|
|
223
|
+
if (!isLegacyNoneToken(value)) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const suffix = replacementHint ? ` ${replacementHint}` : "";
|
|
227
|
+
throw new PmCliError(`${flag} no longer accepts "none" or "null".${suffix}`.trim(), EXIT_CODE.USAGE);
|
|
228
|
+
}
|
|
229
|
+
function assertNoLegacyNoneTokens(values, flag, replacementHint) {
|
|
230
|
+
if (!values || values.length === 0) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const hasLegacyToken = values.some((value) => isLegacyNoneToken(value));
|
|
234
|
+
if (!hasLegacyToken) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const suffix = replacementHint ? ` ${replacementHint}` : "";
|
|
238
|
+
throw new PmCliError(`${flag} no longer accepts "none" or "null".${suffix}`.trim(), EXIT_CODE.USAGE);
|
|
239
|
+
}
|
|
62
240
|
function parseOptionalString(value) {
|
|
63
241
|
if (value === undefined)
|
|
64
242
|
return undefined;
|
|
65
|
-
if (isNoneToken(value))
|
|
66
|
-
return undefined;
|
|
67
243
|
return value;
|
|
68
244
|
}
|
|
245
|
+
function resolveRuntimeCreateUnsetDefinition(token, runtimeFieldRegistry) {
|
|
246
|
+
if (!runtimeFieldRegistry) {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
for (const definition of runtimeFieldRegistry.definitions) {
|
|
250
|
+
if (definition.allow_unset === false) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const candidates = new Set([
|
|
254
|
+
definition.key,
|
|
255
|
+
definition.front_matter_key,
|
|
256
|
+
definition.cli_flag.replaceAll("-", "_"),
|
|
257
|
+
definition.cli_flag,
|
|
258
|
+
...definition.cli_aliases.map((alias) => alias.replaceAll("-", "_")),
|
|
259
|
+
...definition.cli_aliases,
|
|
260
|
+
]);
|
|
261
|
+
if (!candidates.has(token)) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
optionKey: definition.key,
|
|
266
|
+
frontMatterKey: definition.front_matter_key,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
function parseCreateUnsetTargets(raw, runtimeFieldRegistry) {
|
|
272
|
+
const frontMatterKeys = new Set();
|
|
273
|
+
const optionKeys = new Set();
|
|
274
|
+
if (!raw || raw.length === 0) {
|
|
275
|
+
return { frontMatterKeys, optionKeys };
|
|
276
|
+
}
|
|
277
|
+
for (const entry of raw) {
|
|
278
|
+
const trimmed = entry.trim().toLowerCase();
|
|
279
|
+
if (!trimmed) {
|
|
280
|
+
throw new PmCliError("--unset values must not be empty", EXIT_CODE.USAGE);
|
|
281
|
+
}
|
|
282
|
+
if (isLegacyNoneToken(trimmed)) {
|
|
283
|
+
throw new PmCliError('--unset no longer accepts "none" or "null". Specify concrete field names such as --unset deadline', EXIT_CODE.USAGE);
|
|
284
|
+
}
|
|
285
|
+
const definition = CREATE_UNSET_ALIAS_MAP.get(trimmed) ?? resolveRuntimeCreateUnsetDefinition(trimmed, runtimeFieldRegistry);
|
|
286
|
+
if (!definition) {
|
|
287
|
+
throw new PmCliError(`Unsupported --unset field "${entry}". Supported fields: ${CREATE_UNSET_SUPPORTED_CANONICAL_FIELDS}`, EXIT_CODE.USAGE);
|
|
288
|
+
}
|
|
289
|
+
frontMatterKeys.add(definition.frontMatterKey);
|
|
290
|
+
optionKeys.add(definition.optionKey);
|
|
291
|
+
}
|
|
292
|
+
return { frontMatterKeys, optionKeys };
|
|
293
|
+
}
|
|
294
|
+
function weekdayOrderIndex(value) {
|
|
295
|
+
return RECURRENCE_WEEKDAY_VALUES.indexOf(value);
|
|
296
|
+
}
|
|
69
297
|
function parseDependencies(raw, nowValue, prefix) {
|
|
70
298
|
if (!raw || raw.length === 0)
|
|
71
299
|
return { values: undefined, explicitEmpty: false };
|
|
72
|
-
|
|
73
|
-
if (raw.length > 1) {
|
|
74
|
-
throw new PmCliError("--dep cannot mix 'none' with dependency values", EXIT_CODE.USAGE);
|
|
75
|
-
}
|
|
76
|
-
return { values: undefined, explicitEmpty: true };
|
|
77
|
-
}
|
|
300
|
+
assertNoLegacyNoneTokens(raw, "--dep", "Use --clear-deps to clear dependencies.");
|
|
78
301
|
const values = raw.map((entry) => {
|
|
79
302
|
const kv = parseCsvKv(entry, "--dep");
|
|
80
|
-
const id = kv.id;
|
|
81
|
-
const kind = kv.kind;
|
|
303
|
+
const id = parseOptionalString(kv.id);
|
|
304
|
+
const kind = parseOptionalString(kv.kind);
|
|
82
305
|
if (!id || !kind) {
|
|
83
306
|
throw new PmCliError("--dep requires id and kind", EXIT_CODE.USAGE);
|
|
84
307
|
}
|
|
308
|
+
if (id.trim().toLowerCase() === "undefined") {
|
|
309
|
+
throw new PmCliError(`--dep id must not use placeholder token "${id}". Use --clear-deps to clear dependencies.`, EXIT_CODE.USAGE);
|
|
310
|
+
}
|
|
85
311
|
return {
|
|
86
312
|
id: normalizeItemId(id, prefix),
|
|
87
313
|
kind: ensureEnumValue(kind, DEPENDENCY_KIND_VALUES, "dependency kind"),
|
|
@@ -91,17 +317,44 @@ function parseDependencies(raw, nowValue, prefix) {
|
|
|
91
317
|
});
|
|
92
318
|
return { values, explicitEmpty: false };
|
|
93
319
|
}
|
|
94
|
-
function parseLogSeed(optionName, raw, nowValue, fallbackAuthor) {
|
|
320
|
+
export function parseLogSeed(optionName, raw, nowValue, fallbackAuthor) {
|
|
95
321
|
if (!raw || raw.length === 0)
|
|
96
322
|
return { values: undefined, explicitEmpty: false };
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
323
|
+
const clearHint = optionName === "--comment"
|
|
324
|
+
? "Use --clear-comments to clear comments."
|
|
325
|
+
: optionName === "--note"
|
|
326
|
+
? "Use --clear-notes to clear notes."
|
|
327
|
+
: "Use --clear-learnings to clear learnings.";
|
|
328
|
+
assertNoLegacyNoneTokens(raw, optionName, clearHint);
|
|
103
329
|
const values = raw.map((entry) => {
|
|
104
|
-
const
|
|
330
|
+
const trimmedEntry = entry.trim();
|
|
331
|
+
const buildPlainTextCommentSeed = () => {
|
|
332
|
+
if (trimmedEntry.length === 0) {
|
|
333
|
+
throw new PmCliError(`${optionName} requires text=<value>`, EXIT_CODE.USAGE);
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
created_at: nowValue,
|
|
337
|
+
author: fallbackAuthor,
|
|
338
|
+
text: trimmedEntry,
|
|
339
|
+
};
|
|
340
|
+
};
|
|
341
|
+
let kv;
|
|
342
|
+
try {
|
|
343
|
+
kv = parseCsvKv(entry, optionName);
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
if (optionName === "--comment") {
|
|
347
|
+
return buildPlainTextCommentSeed();
|
|
348
|
+
}
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
const unsupportedKeys = Object.keys(kv).filter((key) => !LOG_SEED_ALLOWED_KEYS.has(key));
|
|
352
|
+
if (unsupportedKeys.length > 0) {
|
|
353
|
+
if (optionName === "--comment" && !trimmedEntry.includes(",") && !trimmedEntry.includes("\n")) {
|
|
354
|
+
return buildPlainTextCommentSeed();
|
|
355
|
+
}
|
|
356
|
+
throw new PmCliError(buildInvalidLogSeedKeysMessage(optionName, unsupportedKeys), EXIT_CODE.USAGE);
|
|
357
|
+
}
|
|
105
358
|
const text = kv.text ?? "";
|
|
106
359
|
if (text === "") {
|
|
107
360
|
throw new PmCliError(`${optionName} requires text=<value>`, EXIT_CODE.USAGE);
|
|
@@ -114,15 +367,10 @@ function parseLogSeed(optionName, raw, nowValue, fallbackAuthor) {
|
|
|
114
367
|
});
|
|
115
368
|
return { values, explicitEmpty: false };
|
|
116
369
|
}
|
|
117
|
-
function parseFiles(raw) {
|
|
370
|
+
export function parseFiles(raw) {
|
|
118
371
|
if (!raw || raw.length === 0)
|
|
119
372
|
return { values: undefined, explicitEmpty: false };
|
|
120
|
-
|
|
121
|
-
if (raw.length > 1) {
|
|
122
|
-
throw new PmCliError("--file cannot mix 'none' with file values", EXIT_CODE.USAGE);
|
|
123
|
-
}
|
|
124
|
-
return { values: undefined, explicitEmpty: true };
|
|
125
|
-
}
|
|
373
|
+
assertNoLegacyNoneTokens(raw, "--file", "Use --clear-files to clear linked files.");
|
|
126
374
|
const values = raw.map((entry) => {
|
|
127
375
|
const kv = parseCsvKv(entry, "--file");
|
|
128
376
|
if (!kv.path) {
|
|
@@ -136,21 +384,186 @@ function parseFiles(raw) {
|
|
|
136
384
|
});
|
|
137
385
|
return { values, explicitEmpty: false };
|
|
138
386
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
387
|
+
const LINKED_TEST_PROTECTED_ENV_KEYS = new Set(["PM_PATH", "PM_GLOBAL_PATH", "FORCE_COLOR"]);
|
|
388
|
+
const LINKED_TEST_ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
389
|
+
const LINKED_TEST_PM_CONTEXT_MODE_VALUES = ["schema", "tracker", "auto"];
|
|
390
|
+
function parseLinkedTestEnvSet(raw, optionName) {
|
|
391
|
+
const normalized = parseOptionalString(raw);
|
|
392
|
+
if (!normalized) {
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
const assignments = normalized
|
|
396
|
+
.split(/[;\n]/)
|
|
397
|
+
.map((entry) => entry.trim())
|
|
398
|
+
.filter((entry) => entry.length > 0);
|
|
399
|
+
if (assignments.length === 0) {
|
|
400
|
+
throw new PmCliError(`${optionName} env_set must include at least one KEY=VALUE assignment`, EXIT_CODE.USAGE);
|
|
401
|
+
}
|
|
402
|
+
const envSet = {};
|
|
403
|
+
for (const assignment of assignments) {
|
|
404
|
+
const separatorIndex = assignment.indexOf("=");
|
|
405
|
+
if (separatorIndex <= 0) {
|
|
406
|
+
throw new PmCliError(`${optionName} env_set entries must use KEY=VALUE and be separated by semicolons. Example: env_set=PORT=0;PLAYWRIGHT_BASE_URL=http://127.0.0.1:4173`, EXIT_CODE.USAGE);
|
|
407
|
+
}
|
|
408
|
+
const key = assignment.slice(0, separatorIndex).trim();
|
|
409
|
+
const value = assignment.slice(separatorIndex + 1);
|
|
410
|
+
if (!LINKED_TEST_ENV_NAME_PATTERN.test(key)) {
|
|
411
|
+
throw new PmCliError(`${optionName} env_set key "${key}" is invalid`, EXIT_CODE.USAGE);
|
|
412
|
+
}
|
|
413
|
+
if (LINKED_TEST_PROTECTED_ENV_KEYS.has(key.toUpperCase())) {
|
|
414
|
+
throw new PmCliError(`${optionName} env_set key "${key}" is reserved for sandbox safety`, EXIT_CODE.USAGE);
|
|
415
|
+
}
|
|
416
|
+
envSet[key] = value;
|
|
417
|
+
}
|
|
418
|
+
return Object.keys(envSet).length > 0 ? envSet : undefined;
|
|
419
|
+
}
|
|
420
|
+
function parseLinkedTestEnvClear(raw, optionName) {
|
|
421
|
+
const normalized = parseOptionalString(raw);
|
|
422
|
+
if (!normalized) {
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
const keys = [...new Set(normalized.split(/[;,\n]/).map((entry) => entry.trim()).filter((entry) => entry.length > 0))];
|
|
426
|
+
if (keys.length === 0) {
|
|
427
|
+
throw new PmCliError(`${optionName} env_clear must include at least one environment variable name`, EXIT_CODE.USAGE);
|
|
428
|
+
}
|
|
429
|
+
for (const key of keys) {
|
|
430
|
+
if (!LINKED_TEST_ENV_NAME_PATTERN.test(key)) {
|
|
431
|
+
throw new PmCliError(`${optionName} env_clear key "${key}" is invalid`, EXIT_CODE.USAGE);
|
|
432
|
+
}
|
|
433
|
+
if (LINKED_TEST_PROTECTED_ENV_KEYS.has(key.toUpperCase())) {
|
|
434
|
+
throw new PmCliError(`${optionName} env_clear key "${key}" is reserved for sandbox safety`, EXIT_CODE.USAGE);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return keys;
|
|
438
|
+
}
|
|
439
|
+
function parseLinkedTestBoolean(raw, optionName, fieldLabel) {
|
|
440
|
+
const normalized = parseOptionalString(raw);
|
|
441
|
+
if (!normalized) {
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
const value = normalized.trim().toLowerCase();
|
|
445
|
+
if (value === "true" || value === "1" || value === "yes") {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
if (value === "false" || value === "0" || value === "no") {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
throw new PmCliError(`${optionName} ${fieldLabel} must be one of true|false|1|0|yes|no`, EXIT_CODE.USAGE);
|
|
452
|
+
}
|
|
453
|
+
function parseLinkedTestContextMode(raw, optionName) {
|
|
454
|
+
const normalized = parseOptionalString(raw);
|
|
455
|
+
if (!normalized) {
|
|
456
|
+
return undefined;
|
|
457
|
+
}
|
|
458
|
+
const value = normalized.trim().toLowerCase();
|
|
459
|
+
if (LINKED_TEST_PM_CONTEXT_MODE_VALUES.includes(value)) {
|
|
460
|
+
return value;
|
|
461
|
+
}
|
|
462
|
+
throw new PmCliError(`${optionName} pm_context_mode must be one of: ${LINKED_TEST_PM_CONTEXT_MODE_VALUES.join(", ")}`, EXIT_CODE.USAGE);
|
|
463
|
+
}
|
|
464
|
+
function parseLinkedTestStringList(raw) {
|
|
465
|
+
const normalized = parseOptionalString(raw);
|
|
466
|
+
if (!normalized) {
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
469
|
+
const values = [...new Set(normalized.split(/[;\n]/).map((entry) => entry.trim()).filter((entry) => entry.length > 0))];
|
|
470
|
+
return values.length > 0 ? values : undefined;
|
|
471
|
+
}
|
|
472
|
+
function parseLinkedTestRegexList(raw, optionName, fieldLabel) {
|
|
473
|
+
const values = parseLinkedTestStringList(raw);
|
|
474
|
+
if (!values || values.length === 0) {
|
|
475
|
+
return undefined;
|
|
476
|
+
}
|
|
477
|
+
for (const pattern of values) {
|
|
478
|
+
try {
|
|
479
|
+
// Validate syntax early so malformed assertions fail at mutation time.
|
|
480
|
+
new RegExp(pattern, "m");
|
|
145
481
|
}
|
|
146
|
-
|
|
482
|
+
catch (error) {
|
|
483
|
+
throw new PmCliError(`${optionName} ${fieldLabel} includes invalid regex "${pattern}": ${error instanceof Error ? error.message : String(error)}`, EXIT_CODE.USAGE);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return values;
|
|
487
|
+
}
|
|
488
|
+
function parseLinkedTestMinLines(raw, optionName) {
|
|
489
|
+
const normalized = parseOptionalString(raw);
|
|
490
|
+
if (!normalized) {
|
|
491
|
+
return undefined;
|
|
147
492
|
}
|
|
493
|
+
const parsed = parseOptionalNumber(normalized, "assert_stdout_min_lines");
|
|
494
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
495
|
+
throw new PmCliError(`${optionName} assert_stdout_min_lines must be an integer >= 0`, EXIT_CODE.USAGE);
|
|
496
|
+
}
|
|
497
|
+
return parsed;
|
|
498
|
+
}
|
|
499
|
+
function parseLinkedTestAssertionEqualsMap(raw, optionName) {
|
|
500
|
+
const normalized = parseOptionalString(raw);
|
|
501
|
+
if (!normalized) {
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
const assignments = normalized
|
|
505
|
+
.split(/[;\n]/)
|
|
506
|
+
.map((entry) => entry.trim())
|
|
507
|
+
.filter((entry) => entry.length > 0);
|
|
508
|
+
if (assignments.length === 0) {
|
|
509
|
+
throw new PmCliError(`${optionName} assert_json_field_equals must include at least one path=value assignment`, EXIT_CODE.USAGE);
|
|
510
|
+
}
|
|
511
|
+
const values = {};
|
|
512
|
+
for (const assignment of assignments) {
|
|
513
|
+
const separatorIndex = assignment.indexOf("=");
|
|
514
|
+
if (separatorIndex <= 0) {
|
|
515
|
+
throw new PmCliError(`${optionName} assert_json_field_equals entries must use path=value and be separated by semicolons`, EXIT_CODE.USAGE);
|
|
516
|
+
}
|
|
517
|
+
const key = assignment.slice(0, separatorIndex).trim();
|
|
518
|
+
const value = assignment.slice(separatorIndex + 1).trim();
|
|
519
|
+
if (key.length === 0 || value.length === 0) {
|
|
520
|
+
throw new PmCliError(`${optionName} assert_json_field_equals entries must include non-empty path and value`, EXIT_CODE.USAGE);
|
|
521
|
+
}
|
|
522
|
+
values[key] = value;
|
|
523
|
+
}
|
|
524
|
+
return Object.keys(values).length > 0 ? values : undefined;
|
|
525
|
+
}
|
|
526
|
+
function parseLinkedTestAssertionGteMap(raw, optionName) {
|
|
527
|
+
const normalized = parseOptionalString(raw);
|
|
528
|
+
if (!normalized) {
|
|
529
|
+
return undefined;
|
|
530
|
+
}
|
|
531
|
+
const assignments = normalized
|
|
532
|
+
.split(/[;\n]/)
|
|
533
|
+
.map((entry) => entry.trim())
|
|
534
|
+
.filter((entry) => entry.length > 0);
|
|
535
|
+
if (assignments.length === 0) {
|
|
536
|
+
throw new PmCliError(`${optionName} assert_json_field_gte must include at least one path=value assignment`, EXIT_CODE.USAGE);
|
|
537
|
+
}
|
|
538
|
+
const values = {};
|
|
539
|
+
for (const assignment of assignments) {
|
|
540
|
+
const separatorIndex = assignment.indexOf("=");
|
|
541
|
+
if (separatorIndex <= 0) {
|
|
542
|
+
throw new PmCliError(`${optionName} assert_json_field_gte entries must use path=value and be separated by semicolons`, EXIT_CODE.USAGE);
|
|
543
|
+
}
|
|
544
|
+
const key = assignment.slice(0, separatorIndex).trim();
|
|
545
|
+
const valueRaw = assignment.slice(separatorIndex + 1).trim();
|
|
546
|
+
if (key.length === 0 || valueRaw.length === 0) {
|
|
547
|
+
throw new PmCliError(`${optionName} assert_json_field_gte entries must include non-empty path and value`, EXIT_CODE.USAGE);
|
|
548
|
+
}
|
|
549
|
+
const value = Number.parseFloat(valueRaw);
|
|
550
|
+
if (!Number.isFinite(value)) {
|
|
551
|
+
throw new PmCliError(`${optionName} assert_json_field_gte value for "${key}" must be numeric`, EXIT_CODE.USAGE);
|
|
552
|
+
}
|
|
553
|
+
values[key] = value;
|
|
554
|
+
}
|
|
555
|
+
return Object.keys(values).length > 0 ? values : undefined;
|
|
556
|
+
}
|
|
557
|
+
export function parseTests(raw) {
|
|
558
|
+
if (!raw || raw.length === 0)
|
|
559
|
+
return { values: undefined, explicitEmpty: false };
|
|
560
|
+
assertNoLegacyNoneTokens(raw, "--test", "Use --clear-tests to clear linked tests.");
|
|
148
561
|
const values = raw.map((entry) => {
|
|
149
562
|
const kv = parseCsvKv(entry, "--test");
|
|
150
563
|
const command = parseOptionalString(kv.command);
|
|
151
564
|
const filePath = parseOptionalString(kv.path);
|
|
152
|
-
if (!command
|
|
153
|
-
throw new PmCliError("--test requires command=<value>
|
|
565
|
+
if (!command) {
|
|
566
|
+
throw new PmCliError("--test requires command=<value> (path=<value> is optional metadata)", EXIT_CODE.USAGE);
|
|
154
567
|
}
|
|
155
568
|
const timeoutSecondsRaw = parseOptionalString(kv.timeout_seconds);
|
|
156
569
|
const timeoutAliasRaw = parseOptionalString(kv.timeout);
|
|
@@ -163,20 +576,26 @@ function parseTests(raw) {
|
|
|
163
576
|
path: filePath,
|
|
164
577
|
scope: ensureEnumValue(kv.scope ?? "project", SCOPE_VALUES, "test scope"),
|
|
165
578
|
timeout_seconds: timeoutRaw ? parseOptionalNumber(timeoutRaw, "timeout_seconds") : undefined,
|
|
579
|
+
pm_context_mode: parseLinkedTestContextMode(kv.pm_context_mode, "--test"),
|
|
580
|
+
env_set: parseLinkedTestEnvSet(kv.env_set, "--test"),
|
|
581
|
+
env_clear: parseLinkedTestEnvClear(kv.env_clear, "--test"),
|
|
582
|
+
shared_host_safe: parseLinkedTestBoolean(kv.shared_host_safe, "--test", "shared_host_safe"),
|
|
583
|
+
assert_stdout_contains: parseLinkedTestStringList(kv.assert_stdout_contains),
|
|
584
|
+
assert_stdout_regex: parseLinkedTestRegexList(kv.assert_stdout_regex, "--test", "assert_stdout_regex"),
|
|
585
|
+
assert_stderr_contains: parseLinkedTestStringList(kv.assert_stderr_contains),
|
|
586
|
+
assert_stderr_regex: parseLinkedTestRegexList(kv.assert_stderr_regex, "--test", "assert_stderr_regex"),
|
|
587
|
+
assert_stdout_min_lines: parseLinkedTestMinLines(kv.assert_stdout_min_lines, "--test"),
|
|
588
|
+
assert_json_field_equals: parseLinkedTestAssertionEqualsMap(kv.assert_json_field_equals, "--test"),
|
|
589
|
+
assert_json_field_gte: parseLinkedTestAssertionGteMap(kv.assert_json_field_gte, "--test"),
|
|
166
590
|
note: parseOptionalString(kv.note),
|
|
167
591
|
};
|
|
168
592
|
});
|
|
169
593
|
return { values, explicitEmpty: false };
|
|
170
594
|
}
|
|
171
|
-
function parseDocs(raw) {
|
|
595
|
+
export function parseDocs(raw) {
|
|
172
596
|
if (!raw || raw.length === 0)
|
|
173
597
|
return { values: undefined, explicitEmpty: false };
|
|
174
|
-
|
|
175
|
-
if (raw.length > 1) {
|
|
176
|
-
throw new PmCliError("--doc cannot mix 'none' with doc values", EXIT_CODE.USAGE);
|
|
177
|
-
}
|
|
178
|
-
return { values: undefined, explicitEmpty: true };
|
|
179
|
-
}
|
|
598
|
+
assertNoLegacyNoneTokens(raw, "--doc", "Use --clear-docs to clear linked docs.");
|
|
180
599
|
const values = raw.map((entry) => {
|
|
181
600
|
const kv = parseCsvKv(entry, "--doc");
|
|
182
601
|
if (!kv.path) {
|
|
@@ -190,6 +609,147 @@ function parseDocs(raw) {
|
|
|
190
609
|
});
|
|
191
610
|
return { values, explicitEmpty: false };
|
|
192
611
|
}
|
|
612
|
+
function parseReminders(raw, nowValue) {
|
|
613
|
+
if (!raw || raw.length === 0)
|
|
614
|
+
return { values: undefined, explicitEmpty: false };
|
|
615
|
+
assertNoLegacyNoneTokens(raw, "--reminder", "Use --clear-reminders to clear reminders.");
|
|
616
|
+
const values = raw.map((entry) => {
|
|
617
|
+
const kv = parseCsvKv(entry, "--reminder");
|
|
618
|
+
const atRaw = parseOptionalString(kv.at);
|
|
619
|
+
const textRaw = parseOptionalString(kv.text);
|
|
620
|
+
if (!atRaw || !textRaw) {
|
|
621
|
+
throw new PmCliError("--reminder requires at=<iso|relative> and text=<value>", EXIT_CODE.USAGE);
|
|
622
|
+
}
|
|
623
|
+
const text = textRaw.trim();
|
|
624
|
+
if (!text) {
|
|
625
|
+
throw new PmCliError("--reminder text must not be empty", EXIT_CODE.USAGE);
|
|
626
|
+
}
|
|
627
|
+
return {
|
|
628
|
+
at: resolveIsoOrRelative(atRaw, new Date(nowValue), "reminder.at"),
|
|
629
|
+
text,
|
|
630
|
+
};
|
|
631
|
+
});
|
|
632
|
+
return { values, explicitEmpty: false };
|
|
633
|
+
}
|
|
634
|
+
function parseEventBoolean(value, flag) {
|
|
635
|
+
const normalized = value.trim().toLowerCase();
|
|
636
|
+
if (normalized === "true" || normalized === "1" || normalized === "yes") {
|
|
637
|
+
return true;
|
|
638
|
+
}
|
|
639
|
+
if (normalized === "false" || normalized === "0" || normalized === "no") {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
throw new PmCliError(`${flag} must be one of true|false|1|0|yes|no`, EXIT_CODE.USAGE);
|
|
643
|
+
}
|
|
644
|
+
function parseDelimitedList(raw) {
|
|
645
|
+
if (!raw) {
|
|
646
|
+
return [];
|
|
647
|
+
}
|
|
648
|
+
return raw
|
|
649
|
+
.split("|")
|
|
650
|
+
.map((value) => value.trim())
|
|
651
|
+
.filter((value) => value.length > 0);
|
|
652
|
+
}
|
|
653
|
+
function parseRecurrenceRule(kv, startAt, nowValue) {
|
|
654
|
+
const freqRaw = parseOptionalString(kv.recur_freq)?.trim();
|
|
655
|
+
const intervalRaw = parseOptionalString(kv.recur_interval)?.trim();
|
|
656
|
+
const countRaw = parseOptionalString(kv.recur_count)?.trim();
|
|
657
|
+
const untilRaw = parseOptionalString(kv.recur_until)?.trim();
|
|
658
|
+
const byWeekdayRaw = parseOptionalString(kv.recur_by_weekday)?.trim();
|
|
659
|
+
const byMonthDayRaw = parseOptionalString(kv.recur_by_month_day)?.trim();
|
|
660
|
+
const exdatesRaw = parseOptionalString(kv.recur_exdates)?.trim();
|
|
661
|
+
const recurrenceInputsProvided = [freqRaw, intervalRaw, countRaw, untilRaw, byWeekdayRaw, byMonthDayRaw, exdatesRaw].some((value) => value !== undefined);
|
|
662
|
+
if (!recurrenceInputsProvided) {
|
|
663
|
+
return undefined;
|
|
664
|
+
}
|
|
665
|
+
if (!freqRaw) {
|
|
666
|
+
throw new PmCliError("--event recurrence fields require recur_freq=<daily|weekly|monthly|yearly>", EXIT_CODE.USAGE);
|
|
667
|
+
}
|
|
668
|
+
const freq = ensureEnumValue(freqRaw.toLowerCase(), RECURRENCE_FREQUENCY_VALUES, "event recurrence frequency");
|
|
669
|
+
const interval = intervalRaw !== undefined ? parseOptionalNumber(intervalRaw, "event recur_interval") : undefined;
|
|
670
|
+
if (interval !== undefined && (!Number.isInteger(interval) || interval < 1)) {
|
|
671
|
+
throw new PmCliError("--event recur_interval must be an integer >= 1", EXIT_CODE.USAGE);
|
|
672
|
+
}
|
|
673
|
+
const count = countRaw !== undefined ? parseOptionalNumber(countRaw, "event recur_count") : undefined;
|
|
674
|
+
if (count !== undefined && (!Number.isInteger(count) || count < 1)) {
|
|
675
|
+
throw new PmCliError("--event recur_count must be an integer >= 1", EXIT_CODE.USAGE);
|
|
676
|
+
}
|
|
677
|
+
const until = untilRaw ? resolveIsoOrRelative(untilRaw, nowValue, "event.recur_until") : undefined;
|
|
678
|
+
if (until && until < startAt) {
|
|
679
|
+
throw new PmCliError("--event recur_until must be at or after start", EXIT_CODE.USAGE);
|
|
680
|
+
}
|
|
681
|
+
const byWeekday = Array.from(new Set(parseDelimitedList(byWeekdayRaw).map((value) => ensureEnumValue(value.toLowerCase(), RECURRENCE_WEEKDAY_VALUES, "event weekday")))).sort((left, right) => weekdayOrderIndex(left) -
|
|
682
|
+
weekdayOrderIndex(right));
|
|
683
|
+
const byMonthDay = Array.from(new Set(parseDelimitedList(byMonthDayRaw).map((value) => {
|
|
684
|
+
const day = parseOptionalNumber(value, "event recur_by_month_day");
|
|
685
|
+
if (!Number.isInteger(day) || day < 1 || day > 31) {
|
|
686
|
+
throw new PmCliError("--event recur_by_month_day values must be integers 1..31", EXIT_CODE.USAGE);
|
|
687
|
+
}
|
|
688
|
+
return day;
|
|
689
|
+
}))).sort((left, right) => left - right);
|
|
690
|
+
const exdates = Array.from(new Set(parseDelimitedList(exdatesRaw).map((value) => resolveIsoOrRelative(value, nowValue, "event.recur_exdates")))).sort((left, right) => left.localeCompare(right));
|
|
691
|
+
return {
|
|
692
|
+
freq,
|
|
693
|
+
interval,
|
|
694
|
+
count,
|
|
695
|
+
until,
|
|
696
|
+
by_weekday: byWeekday.length > 0 ? byWeekday : undefined,
|
|
697
|
+
by_month_day: byMonthDay.length > 0 ? byMonthDay : undefined,
|
|
698
|
+
exdates: exdates.length > 0 ? exdates : undefined,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
function parseEvents(raw, nowValue) {
|
|
702
|
+
if (!raw || raw.length === 0)
|
|
703
|
+
return { values: undefined, explicitEmpty: false };
|
|
704
|
+
assertNoLegacyNoneTokens(raw, "--event", "Use --clear-events to clear linked events.");
|
|
705
|
+
const referenceDate = new Date(nowValue);
|
|
706
|
+
const values = raw.map((entry) => {
|
|
707
|
+
const kv = parseCsvKv(entry, "--event");
|
|
708
|
+
const startRaw = parseOptionalString(kv.start)?.trim();
|
|
709
|
+
if (!startRaw) {
|
|
710
|
+
throw new PmCliError("--event requires start=<iso|relative>", EXIT_CODE.USAGE);
|
|
711
|
+
}
|
|
712
|
+
const startAt = resolveIsoOrRelative(startRaw, referenceDate, "event.start");
|
|
713
|
+
const endRaw = parseOptionalString(kv.end)?.trim();
|
|
714
|
+
const endAt = endRaw ? resolveIsoOrRelative(endRaw, referenceDate, "event.end") : undefined;
|
|
715
|
+
if (endAt && endAt <= startAt) {
|
|
716
|
+
throw new PmCliError("--event end must be after start", EXIT_CODE.USAGE);
|
|
717
|
+
}
|
|
718
|
+
const titleRaw = parseOptionalString(kv.title);
|
|
719
|
+
const descriptionRaw = parseOptionalString(kv.description);
|
|
720
|
+
const locationRaw = parseOptionalString(kv.location);
|
|
721
|
+
const timezoneRaw = parseOptionalString(kv.timezone);
|
|
722
|
+
const title = titleRaw?.trim();
|
|
723
|
+
const description = descriptionRaw?.trim();
|
|
724
|
+
const location = locationRaw?.trim();
|
|
725
|
+
const timezone = timezoneRaw?.trim();
|
|
726
|
+
if (titleRaw !== undefined && !title) {
|
|
727
|
+
throw new PmCliError("--event title must not be empty", EXIT_CODE.USAGE);
|
|
728
|
+
}
|
|
729
|
+
if (descriptionRaw !== undefined && !description) {
|
|
730
|
+
throw new PmCliError("--event description must not be empty", EXIT_CODE.USAGE);
|
|
731
|
+
}
|
|
732
|
+
if (locationRaw !== undefined && !location) {
|
|
733
|
+
throw new PmCliError("--event location must not be empty", EXIT_CODE.USAGE);
|
|
734
|
+
}
|
|
735
|
+
if (timezoneRaw !== undefined && !timezone) {
|
|
736
|
+
throw new PmCliError("--event timezone must not be empty", EXIT_CODE.USAGE);
|
|
737
|
+
}
|
|
738
|
+
const allDayRaw = parseOptionalString(kv.all_day)?.trim();
|
|
739
|
+
const recurrence = parseRecurrenceRule(kv, startAt, referenceDate);
|
|
740
|
+
return {
|
|
741
|
+
start_at: startAt,
|
|
742
|
+
end_at: endAt,
|
|
743
|
+
title,
|
|
744
|
+
description,
|
|
745
|
+
location,
|
|
746
|
+
all_day: allDayRaw !== undefined ? parseEventBoolean(allDayRaw, "--event all_day") : undefined,
|
|
747
|
+
timezone,
|
|
748
|
+
recurrence,
|
|
749
|
+
};
|
|
750
|
+
});
|
|
751
|
+
return { values, explicitEmpty: false };
|
|
752
|
+
}
|
|
193
753
|
function buildChangedFields(frontMatter, explicitUnsets) {
|
|
194
754
|
const changed = [
|
|
195
755
|
...FRONT_MATTER_KEY_ORDER.filter((key) => frontMatter[key] !== undefined),
|
|
@@ -206,6 +766,295 @@ function buildHistoryMessage(baseMessage, explicitUnsets) {
|
|
|
206
766
|
const suffix = `explicit_unset=${explicitUnsets.join(",")}`;
|
|
207
767
|
return trimmed ? `${trimmed} | ${suffix}` : suffix;
|
|
208
768
|
}
|
|
769
|
+
function normalizeCreatePolicyOptionKey(raw, typeName, sourceLabel) {
|
|
770
|
+
const canonical = canonicalizeCommandOptionKey("create", raw);
|
|
771
|
+
if (!canonical) {
|
|
772
|
+
throw new PmCliError(`Unsupported ${sourceLabel} entry "${raw}" for type "${typeName}"`, EXIT_CODE.CONFLICT);
|
|
773
|
+
}
|
|
774
|
+
return canonical;
|
|
775
|
+
}
|
|
776
|
+
function parseTypeOptions(raw) {
|
|
777
|
+
if (!raw || raw.length === 0) {
|
|
778
|
+
return { values: undefined, explicitEmpty: false };
|
|
779
|
+
}
|
|
780
|
+
assertNoLegacyNoneTokens(raw, "--type-option", "Use --clear-type-options to clear existing type options.");
|
|
781
|
+
const values = {};
|
|
782
|
+
for (const entry of raw) {
|
|
783
|
+
const trimmedEntry = entry.trim();
|
|
784
|
+
if (trimmedEntry.length === 0) {
|
|
785
|
+
throw new PmCliError("--type-option values must not be empty", EXIT_CODE.USAGE);
|
|
786
|
+
}
|
|
787
|
+
let key;
|
|
788
|
+
let value;
|
|
789
|
+
const prefersStructuredKv = trimmedEntry.includes(",") ||
|
|
790
|
+
trimmedEntry.includes("\n") ||
|
|
791
|
+
trimmedEntry.startsWith("```") ||
|
|
792
|
+
/^(?:[-*+]\s+)?(?:key|value)\s*[:=]/i.test(trimmedEntry);
|
|
793
|
+
if (prefersStructuredKv) {
|
|
794
|
+
const kv = parseCsvKv(trimmedEntry, "--type-option");
|
|
795
|
+
key = parseOptionalString(kv.key)?.trim();
|
|
796
|
+
value = parseOptionalString(kv.value)?.trim();
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
const equalsIndex = trimmedEntry.indexOf("=");
|
|
800
|
+
const colonIndex = trimmedEntry.indexOf(":");
|
|
801
|
+
let separatorIndex = equalsIndex;
|
|
802
|
+
if (equalsIndex <= 0 && colonIndex > 0) {
|
|
803
|
+
separatorIndex = colonIndex;
|
|
804
|
+
}
|
|
805
|
+
if (separatorIndex <= 0 || separatorIndex === trimmedEntry.length - 1) {
|
|
806
|
+
throw new PmCliError("--type-option requires key=value or key=<name>,value=<value> entries", EXIT_CODE.USAGE);
|
|
807
|
+
}
|
|
808
|
+
key = trimmedEntry.slice(0, separatorIndex).trim();
|
|
809
|
+
value = trimmedEntry.slice(separatorIndex + 1).trim();
|
|
810
|
+
}
|
|
811
|
+
if (!key || !value) {
|
|
812
|
+
throw new PmCliError("--type-option requires key and value", EXIT_CODE.USAGE);
|
|
813
|
+
}
|
|
814
|
+
values[key] = value;
|
|
815
|
+
}
|
|
816
|
+
const sortedEntries = Object.entries(values).sort((left, right) => left[0].localeCompare(right[0]));
|
|
817
|
+
return {
|
|
818
|
+
values: Object.fromEntries(sortedEntries),
|
|
819
|
+
explicitEmpty: false,
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
async function resolveCreateStdinInputs(options) {
|
|
823
|
+
const stdinResolver = createStdinTokenResolver();
|
|
824
|
+
return {
|
|
825
|
+
...options,
|
|
826
|
+
body: await stdinResolver.resolveValue(options.body, "--body"),
|
|
827
|
+
dep: await stdinResolver.resolveList(options.dep, "--dep"),
|
|
828
|
+
comment: await stdinResolver.resolveList(options.comment, "--comment"),
|
|
829
|
+
note: await stdinResolver.resolveList(options.note, "--note"),
|
|
830
|
+
learning: await stdinResolver.resolveList(options.learning, "--learning"),
|
|
831
|
+
file: await stdinResolver.resolveList(options.file, "--file"),
|
|
832
|
+
test: await stdinResolver.resolveList(options.test, "--test"),
|
|
833
|
+
doc: await stdinResolver.resolveList(options.doc, "--doc"),
|
|
834
|
+
reminder: await stdinResolver.resolveList(options.reminder, "--reminder"),
|
|
835
|
+
event: await stdinResolver.resolveList(options.event, "--event"),
|
|
836
|
+
typeOption: await stdinResolver.resolveList(options.typeOption, "--type-option"),
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
function resolveCreateMode(createMode, defaultMode) {
|
|
840
|
+
if (createMode === undefined) {
|
|
841
|
+
return defaultMode;
|
|
842
|
+
}
|
|
843
|
+
const normalized = createMode.trim().toLowerCase();
|
|
844
|
+
if (normalized.length === 0) {
|
|
845
|
+
return defaultMode;
|
|
846
|
+
}
|
|
847
|
+
if (normalized === "strict" || normalized === "progressive") {
|
|
848
|
+
return normalized;
|
|
849
|
+
}
|
|
850
|
+
throw new PmCliError(`Invalid --create-mode value "${createMode}". Allowed: ${CREATE_MODE_VALUES.join(", ")}`, EXIT_CODE.USAGE);
|
|
851
|
+
}
|
|
852
|
+
function resolveScheduleCreatePreset(raw) {
|
|
853
|
+
if (raw === undefined) {
|
|
854
|
+
return undefined;
|
|
855
|
+
}
|
|
856
|
+
const normalized = raw.trim().toLowerCase();
|
|
857
|
+
if (normalized.length === 0) {
|
|
858
|
+
throw new PmCliError("--schedule-preset must not be empty", EXIT_CODE.USAGE);
|
|
859
|
+
}
|
|
860
|
+
if (normalized === "lightweight" || normalized === "lite" || normalized === "schedule-lite") {
|
|
861
|
+
return "lightweight";
|
|
862
|
+
}
|
|
863
|
+
throw new PmCliError(`Invalid --schedule-preset value "${raw}". Allowed: ${SCHEDULE_CREATE_PRESET_VALUES.join(", ")}`, EXIT_CODE.USAGE);
|
|
864
|
+
}
|
|
865
|
+
function resolveEffectiveCreateMode(createMode, schedulePreset, defaultMode) {
|
|
866
|
+
const resolvedMode = resolveCreateMode(createMode, defaultMode);
|
|
867
|
+
if (schedulePreset === undefined) {
|
|
868
|
+
return resolvedMode;
|
|
869
|
+
}
|
|
870
|
+
const createModeWasExplicit = typeof createMode === "string" && createMode.trim().length > 0;
|
|
871
|
+
if (createModeWasExplicit && resolvedMode === "strict") {
|
|
872
|
+
throw new PmCliError("--schedule-preset lightweight cannot be combined with --create-mode strict. Use --create-mode progressive or omit --create-mode.", EXIT_CODE.USAGE);
|
|
873
|
+
}
|
|
874
|
+
return "progressive";
|
|
875
|
+
}
|
|
876
|
+
function requireCreateOptionByType(typeDefinition, options, createMode, clearOptionKeys) {
|
|
877
|
+
const typeName = typeDefinition.name;
|
|
878
|
+
const scalarValues = {
|
|
879
|
+
title: options.title,
|
|
880
|
+
description: options.description,
|
|
881
|
+
type: options.type,
|
|
882
|
+
status: options.status,
|
|
883
|
+
priority: options.priority,
|
|
884
|
+
tags: options.tags,
|
|
885
|
+
body: options.body,
|
|
886
|
+
deadline: options.deadline,
|
|
887
|
+
estimatedMinutes: options.estimatedMinutes,
|
|
888
|
+
acceptanceCriteria: options.acceptanceCriteria,
|
|
889
|
+
definitionOfReady: options.definitionOfReady,
|
|
890
|
+
order: options.order ?? options.rank,
|
|
891
|
+
goal: options.goal,
|
|
892
|
+
objective: options.objective,
|
|
893
|
+
value: options.value,
|
|
894
|
+
impact: options.impact,
|
|
895
|
+
outcome: options.outcome,
|
|
896
|
+
whyNow: options.whyNow,
|
|
897
|
+
author: options.author,
|
|
898
|
+
message: options.message,
|
|
899
|
+
assignee: options.assignee,
|
|
900
|
+
parent: options.parent,
|
|
901
|
+
reviewer: options.reviewer,
|
|
902
|
+
risk: options.risk,
|
|
903
|
+
confidence: options.confidence,
|
|
904
|
+
sprint: options.sprint,
|
|
905
|
+
release: options.release,
|
|
906
|
+
blockedBy: options.blockedBy,
|
|
907
|
+
blockedReason: options.blockedReason,
|
|
908
|
+
unblockNote: options.unblockNote,
|
|
909
|
+
reporter: options.reporter,
|
|
910
|
+
severity: options.severity,
|
|
911
|
+
environment: options.environment,
|
|
912
|
+
reproSteps: options.reproSteps,
|
|
913
|
+
resolution: options.resolution,
|
|
914
|
+
expectedResult: options.expectedResult,
|
|
915
|
+
actualResult: options.actualResult,
|
|
916
|
+
affectedVersion: options.affectedVersion,
|
|
917
|
+
fixedVersion: options.fixedVersion,
|
|
918
|
+
component: options.component,
|
|
919
|
+
regression: options.regression,
|
|
920
|
+
customerImpact: options.customerImpact,
|
|
921
|
+
};
|
|
922
|
+
const repeatableValues = {
|
|
923
|
+
dep: options.dep,
|
|
924
|
+
comment: options.comment,
|
|
925
|
+
note: options.note,
|
|
926
|
+
learning: options.learning,
|
|
927
|
+
file: options.file,
|
|
928
|
+
test: options.test,
|
|
929
|
+
doc: options.doc,
|
|
930
|
+
reminder: options.reminder,
|
|
931
|
+
event: options.event,
|
|
932
|
+
typeOption: options.typeOption,
|
|
933
|
+
};
|
|
934
|
+
const hasOptionValue = (optionKey) => {
|
|
935
|
+
if (optionKey in scalarValues) {
|
|
936
|
+
return scalarValues[optionKey] !== undefined;
|
|
937
|
+
}
|
|
938
|
+
if (optionKey in repeatableValues) {
|
|
939
|
+
const value = repeatableValues[optionKey];
|
|
940
|
+
return Array.isArray(value) && value.length > 0;
|
|
941
|
+
}
|
|
942
|
+
return false;
|
|
943
|
+
};
|
|
944
|
+
const hasOptionMutation = (optionKey) => hasOptionValue(optionKey) || clearOptionKeys.has(optionKey);
|
|
945
|
+
const baseRequiredOptions = new Set(["title", "description", "type"]);
|
|
946
|
+
if (createMode === "strict") {
|
|
947
|
+
for (const field of typeDefinition.required_create_fields) {
|
|
948
|
+
baseRequiredOptions.add(normalizeCreatePolicyOptionKey(field, typeName, "required_create_fields"));
|
|
949
|
+
}
|
|
950
|
+
for (const field of typeDefinition.required_create_repeatables) {
|
|
951
|
+
baseRequiredOptions.add(normalizeCreatePolicyOptionKey(field, typeName, "required_create_repeatables"));
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
const policyState = resolveCommandOptionPolicyState(typeDefinition, "create", baseRequiredOptions);
|
|
955
|
+
if (policyState.errors.length > 0) {
|
|
956
|
+
throw new PmCliError(policyState.errors.join("; "), EXIT_CODE.CONFLICT);
|
|
957
|
+
}
|
|
958
|
+
for (const option of policyState.disabled) {
|
|
959
|
+
if (hasOptionMutation(option)) {
|
|
960
|
+
throw new PmCliError(`Option ${commandOptionFlagLabel("create", option)} is disabled for type "${typeName}" by command_option_policies`, EXIT_CODE.USAGE);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
if (createMode === "strict") {
|
|
964
|
+
const strictRequiredClears = policyState.required.filter((required) => clearOptionKeys.has(required));
|
|
965
|
+
if (strictRequiredClears.length > 0) {
|
|
966
|
+
const requiredFlags = [...new Set(strictRequiredClears.map((required) => commandOptionFlagLabel("create", required)))].sort((left, right) => left.localeCompare(right));
|
|
967
|
+
throw new PmCliError(`Strict create mode requires concrete values for ${requiredFlags.join(", ")}; --unset/--clear-* directives cannot satisfy required options`, EXIT_CODE.USAGE);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
const missingRequiredOptions = policyState.required.filter((required) => !hasOptionValue(required));
|
|
971
|
+
return [...new Set(missingRequiredOptions.map((required) => commandOptionFlagLabel("create", required)))].sort((left, right) => left.localeCompare(right));
|
|
972
|
+
}
|
|
973
|
+
const MISSING_REQUIRED_TYPE_OPTION_PATTERN = /^Missing required type option "([^"]+)" for type "([^"]+)"$/;
|
|
974
|
+
function collectMissingRequiredTypeOptionKeys(errors, typeName) {
|
|
975
|
+
const missingKeys = [];
|
|
976
|
+
for (const error of errors) {
|
|
977
|
+
const match = error.match(MISSING_REQUIRED_TYPE_OPTION_PATTERN);
|
|
978
|
+
if (!match) {
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
if (match[2] !== typeName) {
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
missingKeys.push(match[1]);
|
|
985
|
+
}
|
|
986
|
+
return [...new Set(missingKeys)].sort((left, right) => left.localeCompare(right));
|
|
987
|
+
}
|
|
988
|
+
function filterNonMissingTypeOptionErrors(errors, typeName) {
|
|
989
|
+
return errors.filter((error) => {
|
|
990
|
+
const match = error.match(MISSING_REQUIRED_TYPE_OPTION_PATTERN);
|
|
991
|
+
return !match || match[2] !== typeName;
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
function typeOptionExampleValue(typeDefinition, key) {
|
|
995
|
+
const optionDefinition = typeDefinition.options.find((option) => option.key === key);
|
|
996
|
+
const firstAllowed = optionDefinition?.values[0];
|
|
997
|
+
if (typeof firstAllowed === "string" && firstAllowed.trim().length > 0) {
|
|
998
|
+
return firstAllowed;
|
|
999
|
+
}
|
|
1000
|
+
return "<value>";
|
|
1001
|
+
}
|
|
1002
|
+
function createExampleTokensForFlag(flag, typeName, openStatus) {
|
|
1003
|
+
switch (flag) {
|
|
1004
|
+
case "--title":
|
|
1005
|
+
return ["--title", `"${typeName} example title"`];
|
|
1006
|
+
case "--description":
|
|
1007
|
+
return ["--description", `"${typeName} example description"`];
|
|
1008
|
+
case "--type":
|
|
1009
|
+
return ["--type", typeName];
|
|
1010
|
+
case "--status":
|
|
1011
|
+
return ["--status", openStatus];
|
|
1012
|
+
case "--priority":
|
|
1013
|
+
return ["--priority", "1"];
|
|
1014
|
+
case "--message":
|
|
1015
|
+
return ["--message", `"Create ${typeName} item"`];
|
|
1016
|
+
case "--dep":
|
|
1017
|
+
return ["--dep", "\"id=pm-xxxx,kind=related,author=maintainer,created_at=now\""];
|
|
1018
|
+
case "--comment":
|
|
1019
|
+
return ["--comment", "\"author=maintainer,created_at=now,text=Implementation context\""];
|
|
1020
|
+
case "--note":
|
|
1021
|
+
return ["--note", "\"author=maintainer,created_at=now,text=Design note\""];
|
|
1022
|
+
case "--learning":
|
|
1023
|
+
return ["--learning", "\"author=maintainer,created_at=now,text=Durable lesson\""];
|
|
1024
|
+
case "--file":
|
|
1025
|
+
return ["--file", "\"path=src/example.ts,scope=project,note=implementation file\""];
|
|
1026
|
+
case "--test":
|
|
1027
|
+
return ["--test", "\"command=node scripts/run-tests.mjs test,scope=project,timeout_seconds=240\""];
|
|
1028
|
+
case "--doc":
|
|
1029
|
+
return ["--doc", "\"path=README.md,scope=project,note=reference doc\""];
|
|
1030
|
+
default:
|
|
1031
|
+
return [flag, "\"<value>\""];
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
function buildTypeSpecificCreateExample(typeDefinition, missingCreateFlags, missingTypeOptionKeys, openStatus) {
|
|
1035
|
+
const tokens = ["pm", "create", "--title", `"${typeDefinition.name} example title"`, "--description", `"${typeDefinition.name} example description"`, "--type", typeDefinition.name];
|
|
1036
|
+
const optionalRecommendationFlags = ["--status", "--priority", "--message"];
|
|
1037
|
+
const orderedFlags = [...new Set([...optionalRecommendationFlags, ...missingCreateFlags])];
|
|
1038
|
+
const includedFlags = new Set(["--title", "--description", "--type"]);
|
|
1039
|
+
for (const flag of orderedFlags) {
|
|
1040
|
+
if (includedFlags.has(flag)) {
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
tokens.push(...createExampleTokensForFlag(flag, typeDefinition.name, openStatus));
|
|
1044
|
+
includedFlags.add(flag);
|
|
1045
|
+
}
|
|
1046
|
+
for (const key of missingTypeOptionKeys) {
|
|
1047
|
+
const value = typeOptionExampleValue(typeDefinition, key);
|
|
1048
|
+
tokens.push("--type-option", `${key}=${value}`);
|
|
1049
|
+
}
|
|
1050
|
+
return tokens.join(" ");
|
|
1051
|
+
}
|
|
1052
|
+
function requireStringOption(value, flag) {
|
|
1053
|
+
if (value === undefined) {
|
|
1054
|
+
throw new PmCliError(`Missing required option ${flag}`, EXIT_CODE.USAGE);
|
|
1055
|
+
}
|
|
1056
|
+
return value;
|
|
1057
|
+
}
|
|
209
1058
|
function selectAuthor(explicitAuthor, settingsAuthor) {
|
|
210
1059
|
const candidate = parseOptionalString(explicitAuthor) ?? process.env.PM_AUTHOR ?? settingsAuthor;
|
|
211
1060
|
const trimmed = candidate.trim();
|
|
@@ -218,6 +1067,18 @@ function ensurePriority(rawPriority) {
|
|
|
218
1067
|
}
|
|
219
1068
|
return parsed;
|
|
220
1069
|
}
|
|
1070
|
+
function mergeCreateOptionsWithTemplate(templateOptions, explicitOptions) {
|
|
1071
|
+
const merged = {};
|
|
1072
|
+
for (const [key, value] of Object.entries(templateOptions)) {
|
|
1073
|
+
merged[key] = Array.isArray(value) ? [...value] : value;
|
|
1074
|
+
}
|
|
1075
|
+
for (const [key, value] of Object.entries(explicitOptions)) {
|
|
1076
|
+
if (value !== undefined) {
|
|
1077
|
+
merged[key] = Array.isArray(value) ? [...value] : value;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
return merged;
|
|
1081
|
+
}
|
|
221
1082
|
function ensureInitHasRun(pmRoot) {
|
|
222
1083
|
return pathExists(getSettingsPath(pmRoot)).then((exists) => {
|
|
223
1084
|
if (!exists) {
|
|
@@ -226,132 +1087,432 @@ function ensureInitHasRun(pmRoot) {
|
|
|
226
1087
|
});
|
|
227
1088
|
}
|
|
228
1089
|
export async function runCreate(options, global) {
|
|
1090
|
+
let resolvedOptions = await resolveCreateStdinInputs(options);
|
|
229
1091
|
const pmRoot = resolvePmRoot(process.cwd(), global.path);
|
|
230
1092
|
await ensureInitHasRun(pmRoot);
|
|
231
1093
|
const settings = await readSettings(pmRoot);
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
1094
|
+
const statusRegistry = resolveRuntimeStatusRegistry(settings.schema);
|
|
1095
|
+
const runtimeFieldRegistry = resolveRuntimeFieldRegistry(settings.schema);
|
|
1096
|
+
const typeRegistry = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations());
|
|
1097
|
+
assertNoLegacyNoneToken(resolvedOptions.template, "--template", "Omit --template to skip template usage.");
|
|
1098
|
+
if (resolvedOptions.template !== undefined) {
|
|
1099
|
+
const templateName = resolvedOptions.template.trim();
|
|
1100
|
+
if (templateName.length === 0) {
|
|
1101
|
+
throw new PmCliError("--template must not be empty. Omit --template to disable template usage.", EXIT_CODE.USAGE);
|
|
1102
|
+
}
|
|
1103
|
+
const templateOptions = await loadCreateTemplateOptions(pmRoot, templateName);
|
|
1104
|
+
resolvedOptions = mergeCreateOptionsWithTemplate(templateOptions, resolvedOptions);
|
|
1105
|
+
}
|
|
1106
|
+
if (resolvedOptions.type === undefined) {
|
|
1107
|
+
throw new PmCliError("Missing required option --type <value>", EXIT_CODE.USAGE);
|
|
1108
|
+
}
|
|
1109
|
+
const resolvedTypeName = resolveTypeName(resolvedOptions.type, typeRegistry);
|
|
1110
|
+
if (!resolvedTypeName) {
|
|
1111
|
+
throw new PmCliError(`Invalid type value "${resolvedOptions.type}". Allowed: ${typeRegistry.types.join(", ")}`, EXIT_CODE.USAGE);
|
|
1112
|
+
}
|
|
1113
|
+
const typeDefinition = resolveTypeDefinition(resolvedTypeName, typeRegistry);
|
|
1114
|
+
if (!typeDefinition) {
|
|
1115
|
+
throw new PmCliError(`Invalid type value "${resolvedOptions.type}"`, EXIT_CODE.USAGE);
|
|
1116
|
+
}
|
|
1117
|
+
const type = typeDefinition.name;
|
|
1118
|
+
const schedulePreset = resolveScheduleCreatePreset(resolvedOptions.schedulePreset);
|
|
1119
|
+
if (schedulePreset !== undefined && !SCHEDULE_CREATE_PRESET_TYPES.has(type)) {
|
|
1120
|
+
throw new PmCliError(`--schedule-preset ${schedulePreset} is only supported for Reminder, Meeting, or Event types`, EXIT_CODE.USAGE);
|
|
1121
|
+
}
|
|
1122
|
+
const createMode = resolveEffectiveCreateMode(resolvedOptions.createMode, schedulePreset, settings.governance.create_mode_default);
|
|
1123
|
+
const unsetTargets = parseCreateUnsetTargets(resolvedOptions.unset, runtimeFieldRegistry);
|
|
1124
|
+
const explicitUnsets = new Set(unsetTargets.frontMatterKeys);
|
|
1125
|
+
const clearOptionKeys = new Set(unsetTargets.optionKeys);
|
|
1126
|
+
const clearCollectionDefinitions = [
|
|
1127
|
+
{
|
|
1128
|
+
enabled: resolvedOptions.clearDeps,
|
|
1129
|
+
optionKey: "dep",
|
|
1130
|
+
clearFlag: "--clear-deps",
|
|
1131
|
+
valueFlag: "--dep",
|
|
1132
|
+
values: resolvedOptions.dep,
|
|
1133
|
+
frontMatterKey: "dependencies",
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
enabled: resolvedOptions.clearComments,
|
|
1137
|
+
optionKey: "comment",
|
|
1138
|
+
clearFlag: "--clear-comments",
|
|
1139
|
+
valueFlag: "--comment",
|
|
1140
|
+
values: resolvedOptions.comment,
|
|
1141
|
+
frontMatterKey: "comments",
|
|
1142
|
+
},
|
|
1143
|
+
{
|
|
1144
|
+
enabled: resolvedOptions.clearNotes,
|
|
1145
|
+
optionKey: "note",
|
|
1146
|
+
clearFlag: "--clear-notes",
|
|
1147
|
+
valueFlag: "--note",
|
|
1148
|
+
values: resolvedOptions.note,
|
|
1149
|
+
frontMatterKey: "notes",
|
|
1150
|
+
},
|
|
1151
|
+
{
|
|
1152
|
+
enabled: resolvedOptions.clearLearnings,
|
|
1153
|
+
optionKey: "learning",
|
|
1154
|
+
clearFlag: "--clear-learnings",
|
|
1155
|
+
valueFlag: "--learning",
|
|
1156
|
+
values: resolvedOptions.learning,
|
|
1157
|
+
frontMatterKey: "learnings",
|
|
1158
|
+
},
|
|
1159
|
+
{
|
|
1160
|
+
enabled: resolvedOptions.clearFiles,
|
|
1161
|
+
optionKey: "file",
|
|
1162
|
+
clearFlag: "--clear-files",
|
|
1163
|
+
valueFlag: "--file",
|
|
1164
|
+
values: resolvedOptions.file,
|
|
1165
|
+
frontMatterKey: "files",
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
enabled: resolvedOptions.clearTests,
|
|
1169
|
+
optionKey: "test",
|
|
1170
|
+
clearFlag: "--clear-tests",
|
|
1171
|
+
valueFlag: "--test",
|
|
1172
|
+
values: resolvedOptions.test,
|
|
1173
|
+
frontMatterKey: "tests",
|
|
1174
|
+
},
|
|
1175
|
+
{
|
|
1176
|
+
enabled: resolvedOptions.clearDocs,
|
|
1177
|
+
optionKey: "doc",
|
|
1178
|
+
clearFlag: "--clear-docs",
|
|
1179
|
+
valueFlag: "--doc",
|
|
1180
|
+
values: resolvedOptions.doc,
|
|
1181
|
+
frontMatterKey: "docs",
|
|
1182
|
+
},
|
|
1183
|
+
{
|
|
1184
|
+
enabled: resolvedOptions.clearReminders,
|
|
1185
|
+
optionKey: "reminder",
|
|
1186
|
+
clearFlag: "--clear-reminders",
|
|
1187
|
+
valueFlag: "--reminder",
|
|
1188
|
+
values: resolvedOptions.reminder,
|
|
1189
|
+
frontMatterKey: "reminders",
|
|
1190
|
+
},
|
|
1191
|
+
{
|
|
1192
|
+
enabled: resolvedOptions.clearEvents,
|
|
1193
|
+
optionKey: "event",
|
|
1194
|
+
clearFlag: "--clear-events",
|
|
1195
|
+
valueFlag: "--event",
|
|
1196
|
+
values: resolvedOptions.event,
|
|
1197
|
+
frontMatterKey: "events",
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
enabled: resolvedOptions.clearTypeOptions,
|
|
1201
|
+
optionKey: "typeOption",
|
|
1202
|
+
clearFlag: "--clear-type-options",
|
|
1203
|
+
valueFlag: "--type-option",
|
|
1204
|
+
values: resolvedOptions.typeOption,
|
|
1205
|
+
frontMatterKey: "type_options",
|
|
1206
|
+
},
|
|
292
1207
|
];
|
|
293
|
-
for (const
|
|
294
|
-
if (
|
|
295
|
-
|
|
1208
|
+
for (const definition of clearCollectionDefinitions) {
|
|
1209
|
+
if (!definition.enabled) {
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
if (definition.values && definition.values.length > 0) {
|
|
1213
|
+
throw new PmCliError(`Cannot combine ${definition.clearFlag} with ${definition.valueFlag}`, EXIT_CODE.USAGE);
|
|
1214
|
+
}
|
|
1215
|
+
explicitUnsets.add(definition.frontMatterKey);
|
|
1216
|
+
clearOptionKeys.add(definition.optionKey);
|
|
1217
|
+
}
|
|
1218
|
+
const scalarOptionPresence = {
|
|
1219
|
+
tags: resolvedOptions.tags !== undefined,
|
|
1220
|
+
deadline: resolvedOptions.deadline !== undefined,
|
|
1221
|
+
estimatedMinutes: resolvedOptions.estimatedMinutes !== undefined,
|
|
1222
|
+
acceptanceCriteria: resolvedOptions.acceptanceCriteria !== undefined,
|
|
1223
|
+
definitionOfReady: resolvedOptions.definitionOfReady !== undefined,
|
|
1224
|
+
order: resolvedOptions.order !== undefined || resolvedOptions.rank !== undefined,
|
|
1225
|
+
goal: resolvedOptions.goal !== undefined,
|
|
1226
|
+
objective: resolvedOptions.objective !== undefined,
|
|
1227
|
+
value: resolvedOptions.value !== undefined,
|
|
1228
|
+
impact: resolvedOptions.impact !== undefined,
|
|
1229
|
+
outcome: resolvedOptions.outcome !== undefined,
|
|
1230
|
+
whyNow: resolvedOptions.whyNow !== undefined,
|
|
1231
|
+
author: resolvedOptions.author !== undefined,
|
|
1232
|
+
assignee: resolvedOptions.assignee !== undefined,
|
|
1233
|
+
parent: resolvedOptions.parent !== undefined,
|
|
1234
|
+
reviewer: resolvedOptions.reviewer !== undefined,
|
|
1235
|
+
risk: resolvedOptions.risk !== undefined,
|
|
1236
|
+
confidence: resolvedOptions.confidence !== undefined,
|
|
1237
|
+
sprint: resolvedOptions.sprint !== undefined,
|
|
1238
|
+
release: resolvedOptions.release !== undefined,
|
|
1239
|
+
blockedBy: resolvedOptions.blockedBy !== undefined,
|
|
1240
|
+
blockedReason: resolvedOptions.blockedReason !== undefined,
|
|
1241
|
+
unblockNote: resolvedOptions.unblockNote !== undefined,
|
|
1242
|
+
reporter: resolvedOptions.reporter !== undefined,
|
|
1243
|
+
severity: resolvedOptions.severity !== undefined,
|
|
1244
|
+
environment: resolvedOptions.environment !== undefined,
|
|
1245
|
+
reproSteps: resolvedOptions.reproSteps !== undefined,
|
|
1246
|
+
resolution: resolvedOptions.resolution !== undefined,
|
|
1247
|
+
expectedResult: resolvedOptions.expectedResult !== undefined,
|
|
1248
|
+
actualResult: resolvedOptions.actualResult !== undefined,
|
|
1249
|
+
affectedVersion: resolvedOptions.affectedVersion !== undefined,
|
|
1250
|
+
fixedVersion: resolvedOptions.fixedVersion !== undefined,
|
|
1251
|
+
component: resolvedOptions.component !== undefined,
|
|
1252
|
+
regression: resolvedOptions.regression !== undefined,
|
|
1253
|
+
customerImpact: resolvedOptions.customerImpact !== undefined,
|
|
1254
|
+
};
|
|
1255
|
+
for (const [optionKey, hasValue] of Object.entries(scalarOptionPresence)) {
|
|
1256
|
+
if (!hasValue || !unsetTargets.optionKeys.has(optionKey)) {
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1259
|
+
const unsetField = CREATE_OPTION_KEY_TO_UNSET_CANONICAL.get(optionKey) ?? optionKey;
|
|
1260
|
+
throw new PmCliError(`Cannot combine --unset ${unsetField} with ${commandOptionFlagLabel("create", optionKey)}`, EXIT_CODE.USAGE);
|
|
1261
|
+
}
|
|
1262
|
+
const assertNoLegacyScalarToken = (value, optionKey) => {
|
|
1263
|
+
const unsetField = CREATE_OPTION_KEY_TO_UNSET_CANONICAL.get(optionKey);
|
|
1264
|
+
const hint = unsetField ? `Use --unset ${unsetField} to clear this field.` : undefined;
|
|
1265
|
+
assertNoLegacyNoneToken(value, commandOptionFlagLabel("create", optionKey), hint);
|
|
1266
|
+
};
|
|
1267
|
+
assertNoLegacyScalarToken(resolvedOptions.tags, "tags");
|
|
1268
|
+
assertNoLegacyScalarToken(resolvedOptions.deadline, "deadline");
|
|
1269
|
+
assertNoLegacyScalarToken(resolvedOptions.estimatedMinutes, "estimatedMinutes");
|
|
1270
|
+
assertNoLegacyScalarToken(resolvedOptions.acceptanceCriteria, "acceptanceCriteria");
|
|
1271
|
+
assertNoLegacyScalarToken(resolvedOptions.definitionOfReady, "definitionOfReady");
|
|
1272
|
+
assertNoLegacyScalarToken(resolvedOptions.order ?? resolvedOptions.rank, "order");
|
|
1273
|
+
assertNoLegacyScalarToken(resolvedOptions.goal, "goal");
|
|
1274
|
+
assertNoLegacyScalarToken(resolvedOptions.objective, "objective");
|
|
1275
|
+
assertNoLegacyScalarToken(resolvedOptions.value, "value");
|
|
1276
|
+
assertNoLegacyScalarToken(resolvedOptions.impact, "impact");
|
|
1277
|
+
assertNoLegacyScalarToken(resolvedOptions.outcome, "outcome");
|
|
1278
|
+
assertNoLegacyScalarToken(resolvedOptions.whyNow, "whyNow");
|
|
1279
|
+
assertNoLegacyScalarToken(resolvedOptions.author, "author");
|
|
1280
|
+
assertNoLegacyScalarToken(resolvedOptions.assignee, "assignee");
|
|
1281
|
+
assertNoLegacyScalarToken(resolvedOptions.parent, "parent");
|
|
1282
|
+
assertNoLegacyScalarToken(resolvedOptions.reviewer, "reviewer");
|
|
1283
|
+
assertNoLegacyScalarToken(resolvedOptions.risk, "risk");
|
|
1284
|
+
assertNoLegacyScalarToken(resolvedOptions.confidence, "confidence");
|
|
1285
|
+
assertNoLegacyScalarToken(resolvedOptions.sprint, "sprint");
|
|
1286
|
+
assertNoLegacyScalarToken(resolvedOptions.release, "release");
|
|
1287
|
+
assertNoLegacyScalarToken(resolvedOptions.blockedBy, "blockedBy");
|
|
1288
|
+
assertNoLegacyScalarToken(resolvedOptions.blockedReason, "blockedReason");
|
|
1289
|
+
assertNoLegacyScalarToken(resolvedOptions.unblockNote, "unblockNote");
|
|
1290
|
+
assertNoLegacyScalarToken(resolvedOptions.reporter, "reporter");
|
|
1291
|
+
assertNoLegacyScalarToken(resolvedOptions.severity, "severity");
|
|
1292
|
+
assertNoLegacyScalarToken(resolvedOptions.environment, "environment");
|
|
1293
|
+
assertNoLegacyScalarToken(resolvedOptions.reproSteps, "reproSteps");
|
|
1294
|
+
assertNoLegacyScalarToken(resolvedOptions.resolution, "resolution");
|
|
1295
|
+
assertNoLegacyScalarToken(resolvedOptions.expectedResult, "expectedResult");
|
|
1296
|
+
assertNoLegacyScalarToken(resolvedOptions.actualResult, "actualResult");
|
|
1297
|
+
assertNoLegacyScalarToken(resolvedOptions.affectedVersion, "affectedVersion");
|
|
1298
|
+
assertNoLegacyScalarToken(resolvedOptions.fixedVersion, "fixedVersion");
|
|
1299
|
+
assertNoLegacyScalarToken(resolvedOptions.component, "component");
|
|
1300
|
+
assertNoLegacyScalarToken(resolvedOptions.regression, "regression");
|
|
1301
|
+
assertNoLegacyScalarToken(resolvedOptions.customerImpact, "customerImpact");
|
|
1302
|
+
const missingRequiredCreateFlags = requireCreateOptionByType(typeDefinition, resolvedOptions, createMode, clearOptionKeys);
|
|
1303
|
+
const nowValue = nowIso();
|
|
1304
|
+
const author = selectAuthor(resolvedOptions.author, settings.author_default);
|
|
1305
|
+
const dependencies = parseDependencies(resolvedOptions.dep, nowValue, settings.id_prefix);
|
|
1306
|
+
const comments = parseLogSeed("--comment", resolvedOptions.comment, nowValue, author);
|
|
1307
|
+
const notes = parseLogSeed("--note", resolvedOptions.note, nowValue, author);
|
|
1308
|
+
const learnings = parseLogSeed("--learning", resolvedOptions.learning, nowValue, author);
|
|
1309
|
+
const files = parseFiles(resolvedOptions.file);
|
|
1310
|
+
const tests = parseTests(resolvedOptions.test);
|
|
1311
|
+
const docs = parseDocs(resolvedOptions.doc);
|
|
1312
|
+
const reminders = parseReminders(resolvedOptions.reminder, nowValue);
|
|
1313
|
+
const events = parseEvents(resolvedOptions.event, nowValue);
|
|
1314
|
+
const typeOptions = parseTypeOptions(resolvedOptions.typeOption);
|
|
1315
|
+
const validatedTypeOptions = validateTypeOptions(type, typeOptions.values, typeRegistry);
|
|
1316
|
+
const runtimeCreateFieldValues = collectRuntimeCreateFieldValues(resolvedOptions, runtimeFieldRegistry, type);
|
|
1317
|
+
for (const fieldKey of Object.keys(runtimeCreateFieldValues.values)) {
|
|
1318
|
+
if (!unsetTargets.frontMatterKeys.has(fieldKey)) {
|
|
1319
|
+
continue;
|
|
296
1320
|
}
|
|
1321
|
+
throw new PmCliError(`Cannot combine --unset ${fieldKey.replaceAll("_", "-")} with its value flag`, EXIT_CODE.USAGE);
|
|
1322
|
+
}
|
|
1323
|
+
const missingRequiredTypeOptionKeys = collectMissingRequiredTypeOptionKeys(validatedTypeOptions.errors, type);
|
|
1324
|
+
const missingRequiredTypeOptionFlags = missingRequiredTypeOptionKeys.map((key) => `--type-option ${key}=<value>`);
|
|
1325
|
+
const combinedMissingFlags = [
|
|
1326
|
+
...new Set([
|
|
1327
|
+
...missingRequiredCreateFlags,
|
|
1328
|
+
...missingRequiredTypeOptionFlags,
|
|
1329
|
+
...runtimeCreateFieldValues.missing_required_flags,
|
|
1330
|
+
]),
|
|
1331
|
+
].sort((left, right) => left.localeCompare(right));
|
|
1332
|
+
if (combinedMissingFlags.length > 0) {
|
|
1333
|
+
const nextValidExample = buildTypeSpecificCreateExample(typeDefinition, missingRequiredCreateFlags, missingRequiredTypeOptionKeys, statusRegistry.open_status);
|
|
1334
|
+
const nextSteps = [`Run "pm create --help --type ${type}" for type-aware required option guidance.`];
|
|
1335
|
+
if (createMode === "strict") {
|
|
1336
|
+
nextSteps.push('For staged onboarding, retry with "--create-mode progressive".');
|
|
1337
|
+
if (SCHEDULE_CREATE_PRESET_TYPES.has(type)) {
|
|
1338
|
+
nextSteps.push('For minimal scheduling inputs, try "--schedule-preset lightweight".');
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
const errorMessage = combinedMissingFlags.length === 1
|
|
1342
|
+
? `Missing required option ${combinedMissingFlags[0]} for type "${type}"`
|
|
1343
|
+
: `Missing required options ${combinedMissingFlags.join(", ")} for type "${type}"`;
|
|
1344
|
+
throw new PmCliError(errorMessage, EXIT_CODE.USAGE, {
|
|
1345
|
+
code: "missing_required_option",
|
|
1346
|
+
required: `Provide all required create options and type options for type "${type}" in one invocation.`,
|
|
1347
|
+
examples: [nextValidExample],
|
|
1348
|
+
nextSteps,
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
const nonMissingTypeOptionErrors = filterNonMissingTypeOptionErrors(validatedTypeOptions.errors, type);
|
|
1352
|
+
if (nonMissingTypeOptionErrors.length > 0) {
|
|
1353
|
+
const nextValidExample = buildTypeSpecificCreateExample(typeDefinition, [], [], statusRegistry.open_status);
|
|
1354
|
+
throw new PmCliError(nonMissingTypeOptionErrors.join("; "), EXIT_CODE.USAGE, {
|
|
1355
|
+
code: "invalid_argument_value",
|
|
1356
|
+
required: `Provide valid --type-option key/value pairs for type "${type}".`,
|
|
1357
|
+
examples: [nextValidExample],
|
|
1358
|
+
nextSteps: [`Run "pm create --help --type ${type}" to review allowed type-option keys and values.`],
|
|
1359
|
+
});
|
|
297
1360
|
}
|
|
298
1361
|
const id = await generateItemId(pmRoot, settings.id_prefix);
|
|
299
|
-
const
|
|
300
|
-
const
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
1362
|
+
const status = resolvedOptions.status !== undefined ? parseStatusValue(resolvedOptions.status, statusRegistry) : statusRegistry.open_status;
|
|
1363
|
+
const priority = resolvedOptions.priority !== undefined ? ensurePriority(resolvedOptions.priority) : 2;
|
|
1364
|
+
const tags = unsetTargets.frontMatterKeys.has("tags")
|
|
1365
|
+
? []
|
|
1366
|
+
: resolvedOptions.tags !== undefined
|
|
1367
|
+
? parseTags(resolvedOptions.tags)
|
|
1368
|
+
: [];
|
|
1369
|
+
const deadline = unsetTargets.frontMatterKeys.has("deadline")
|
|
1370
|
+
? undefined
|
|
1371
|
+
: resolvedOptions.deadline === undefined
|
|
1372
|
+
? undefined
|
|
1373
|
+
: resolveIsoOrRelative(resolvedOptions.deadline, new Date(nowValue), "deadline");
|
|
1374
|
+
const estimatedMinutes = unsetTargets.frontMatterKeys.has("estimated_minutes")
|
|
1375
|
+
? undefined
|
|
1376
|
+
: resolvedOptions.estimatedMinutes === undefined
|
|
1377
|
+
? undefined
|
|
1378
|
+
: parseOptionalNumber(resolvedOptions.estimatedMinutes, "estimated-minutes");
|
|
1379
|
+
const acceptanceCriteria = unsetTargets.frontMatterKeys.has("acceptance_criteria")
|
|
1380
|
+
? undefined
|
|
1381
|
+
: resolvedOptions.acceptanceCriteria === undefined
|
|
1382
|
+
? undefined
|
|
1383
|
+
: resolvedOptions.acceptanceCriteria;
|
|
1384
|
+
const definitionOfReady = unsetTargets.frontMatterKeys.has("definition_of_ready") || resolvedOptions.definitionOfReady === undefined
|
|
1385
|
+
? undefined
|
|
1386
|
+
: parseOptionalString(resolvedOptions.definitionOfReady);
|
|
1387
|
+
if (resolvedOptions.order !== undefined &&
|
|
1388
|
+
resolvedOptions.rank !== undefined &&
|
|
1389
|
+
resolvedOptions.order !== resolvedOptions.rank) {
|
|
310
1390
|
throw new PmCliError("--order and --rank must match when both are provided", EXIT_CODE.USAGE);
|
|
311
1391
|
}
|
|
312
|
-
const orderRaw =
|
|
313
|
-
const order = orderRaw === undefined
|
|
1392
|
+
const orderRaw = resolvedOptions.order ?? resolvedOptions.rank;
|
|
1393
|
+
const order = unsetTargets.frontMatterKeys.has("order") || orderRaw === undefined ? undefined : parseOptionalNumber(orderRaw, "order");
|
|
314
1394
|
if (order !== undefined && !Number.isInteger(order)) {
|
|
315
1395
|
throw new PmCliError("Order must be an integer", EXIT_CODE.USAGE);
|
|
316
1396
|
}
|
|
317
|
-
const goal =
|
|
318
|
-
const objective =
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const
|
|
1397
|
+
const goal = unsetTargets.frontMatterKeys.has("goal") || resolvedOptions.goal === undefined ? undefined : parseOptionalString(resolvedOptions.goal);
|
|
1398
|
+
const objective = unsetTargets.frontMatterKeys.has("objective") || resolvedOptions.objective === undefined
|
|
1399
|
+
? undefined
|
|
1400
|
+
: parseOptionalString(resolvedOptions.objective);
|
|
1401
|
+
const value = unsetTargets.frontMatterKeys.has("value") || resolvedOptions.value === undefined
|
|
1402
|
+
? undefined
|
|
1403
|
+
: parseOptionalString(resolvedOptions.value);
|
|
1404
|
+
const impact = unsetTargets.frontMatterKeys.has("impact") || resolvedOptions.impact === undefined
|
|
1405
|
+
? undefined
|
|
1406
|
+
: parseOptionalString(resolvedOptions.impact);
|
|
1407
|
+
const outcome = unsetTargets.frontMatterKeys.has("outcome") || resolvedOptions.outcome === undefined
|
|
1408
|
+
? undefined
|
|
1409
|
+
: parseOptionalString(resolvedOptions.outcome);
|
|
1410
|
+
const whyNow = unsetTargets.frontMatterKeys.has("why_now") || resolvedOptions.whyNow === undefined
|
|
1411
|
+
? undefined
|
|
1412
|
+
: parseOptionalString(resolvedOptions.whyNow);
|
|
1413
|
+
const assignee = unsetTargets.frontMatterKeys.has("assignee") || resolvedOptions.assignee === undefined
|
|
1414
|
+
? undefined
|
|
1415
|
+
: parseOptionalString(resolvedOptions.assignee);
|
|
1416
|
+
const authorValue = unsetTargets.frontMatterKeys.has("author")
|
|
1417
|
+
? undefined
|
|
1418
|
+
: parseOptionalString(resolvedOptions.author) ?? author;
|
|
1419
|
+
let parent = unsetTargets.frontMatterKeys.has("parent") || resolvedOptions.parent === undefined
|
|
1420
|
+
? undefined
|
|
1421
|
+
: parseOptionalString(resolvedOptions.parent);
|
|
1422
|
+
const reviewer = unsetTargets.frontMatterKeys.has("reviewer") || resolvedOptions.reviewer === undefined
|
|
1423
|
+
? undefined
|
|
1424
|
+
: parseOptionalString(resolvedOptions.reviewer);
|
|
1425
|
+
const riskRaw = unsetTargets.frontMatterKeys.has("risk") || resolvedOptions.risk === undefined
|
|
1426
|
+
? undefined
|
|
1427
|
+
: parseOptionalString(resolvedOptions.risk);
|
|
328
1428
|
const risk = riskRaw !== undefined ? ensureEnumValue(normalizeRiskInput(riskRaw), RISK_VALUES, "risk") : undefined;
|
|
329
|
-
const confidenceRaw =
|
|
1429
|
+
const confidenceRaw = unsetTargets.frontMatterKeys.has("confidence") || resolvedOptions.confidence === undefined
|
|
1430
|
+
? undefined
|
|
1431
|
+
: parseOptionalString(resolvedOptions.confidence);
|
|
330
1432
|
const confidence = confidenceRaw !== undefined ? parseConfidenceInput(confidenceRaw) : undefined;
|
|
331
|
-
const
|
|
332
|
-
const
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
1433
|
+
const parentReferencePolicy = settings.validation.parent_reference;
|
|
1434
|
+
const sprintReleasePolicy = settings.validation.sprint_release_format;
|
|
1435
|
+
const validationWarnings = [];
|
|
1436
|
+
if (parent !== undefined) {
|
|
1437
|
+
parent = normalizeParentReferenceValue(parent);
|
|
1438
|
+
const parentLocated = await locateItem(pmRoot, parent, settings.id_prefix, settings.item_format, typeRegistry.type_to_folder);
|
|
1439
|
+
if (!parentLocated) {
|
|
1440
|
+
const normalizedParentId = normalizeItemId(parent, settings.id_prefix);
|
|
1441
|
+
validationWarnings.push(...validateMissingParentReference(normalizedParentId, parentReferencePolicy).warnings);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
let sprint = unsetTargets.frontMatterKeys.has("sprint") || resolvedOptions.sprint === undefined
|
|
1445
|
+
? undefined
|
|
1446
|
+
: parseOptionalString(resolvedOptions.sprint);
|
|
1447
|
+
if (sprint !== undefined) {
|
|
1448
|
+
const sprintValidation = validateSprintOrReleaseValue("sprint", sprint, sprintReleasePolicy);
|
|
1449
|
+
sprint = sprintValidation.value;
|
|
1450
|
+
validationWarnings.push(...sprintValidation.warnings);
|
|
1451
|
+
}
|
|
1452
|
+
let release = unsetTargets.frontMatterKeys.has("release") || resolvedOptions.release === undefined
|
|
1453
|
+
? undefined
|
|
1454
|
+
: parseOptionalString(resolvedOptions.release);
|
|
1455
|
+
if (release !== undefined) {
|
|
1456
|
+
const releaseValidation = validateSprintOrReleaseValue("release", release, sprintReleasePolicy);
|
|
1457
|
+
release = releaseValidation.value;
|
|
1458
|
+
validationWarnings.push(...releaseValidation.warnings);
|
|
1459
|
+
}
|
|
1460
|
+
const blockedBy = unsetTargets.frontMatterKeys.has("blocked_by") || resolvedOptions.blockedBy === undefined
|
|
1461
|
+
? undefined
|
|
1462
|
+
: parseOptionalString(resolvedOptions.blockedBy);
|
|
1463
|
+
const blockedReason = unsetTargets.frontMatterKeys.has("blocked_reason") || resolvedOptions.blockedReason === undefined
|
|
1464
|
+
? undefined
|
|
1465
|
+
: parseOptionalString(resolvedOptions.blockedReason);
|
|
1466
|
+
const unblockNote = unsetTargets.frontMatterKeys.has("unblock_note") || resolvedOptions.unblockNote === undefined
|
|
1467
|
+
? undefined
|
|
1468
|
+
: parseOptionalString(resolvedOptions.unblockNote);
|
|
1469
|
+
const reporter = unsetTargets.frontMatterKeys.has("reporter") || resolvedOptions.reporter === undefined
|
|
1470
|
+
? undefined
|
|
1471
|
+
: parseOptionalString(resolvedOptions.reporter);
|
|
1472
|
+
const severityRaw = unsetTargets.frontMatterKeys.has("severity") || resolvedOptions.severity === undefined
|
|
1473
|
+
? undefined
|
|
1474
|
+
: parseOptionalString(resolvedOptions.severity);
|
|
338
1475
|
const severity = severityRaw !== undefined ? ensureEnumValue(normalizeSeverityInput(severityRaw), ISSUE_SEVERITY_VALUES, "severity") : undefined;
|
|
339
|
-
const environment =
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
1476
|
+
const environment = unsetTargets.frontMatterKeys.has("environment") || resolvedOptions.environment === undefined
|
|
1477
|
+
? undefined
|
|
1478
|
+
: parseOptionalString(resolvedOptions.environment);
|
|
1479
|
+
const reproSteps = unsetTargets.frontMatterKeys.has("repro_steps") || resolvedOptions.reproSteps === undefined
|
|
1480
|
+
? undefined
|
|
1481
|
+
: parseOptionalString(resolvedOptions.reproSteps);
|
|
1482
|
+
const resolution = unsetTargets.frontMatterKeys.has("resolution") || resolvedOptions.resolution === undefined
|
|
1483
|
+
? undefined
|
|
1484
|
+
: parseOptionalString(resolvedOptions.resolution);
|
|
1485
|
+
const expectedResult = unsetTargets.frontMatterKeys.has("expected_result") || resolvedOptions.expectedResult === undefined
|
|
1486
|
+
? undefined
|
|
1487
|
+
: parseOptionalString(resolvedOptions.expectedResult);
|
|
1488
|
+
const actualResult = unsetTargets.frontMatterKeys.has("actual_result") || resolvedOptions.actualResult === undefined
|
|
1489
|
+
? undefined
|
|
1490
|
+
: parseOptionalString(resolvedOptions.actualResult);
|
|
1491
|
+
const affectedVersion = unsetTargets.frontMatterKeys.has("affected_version") || resolvedOptions.affectedVersion === undefined
|
|
1492
|
+
? undefined
|
|
1493
|
+
: parseOptionalString(resolvedOptions.affectedVersion);
|
|
1494
|
+
const fixedVersion = unsetTargets.frontMatterKeys.has("fixed_version") || resolvedOptions.fixedVersion === undefined
|
|
1495
|
+
? undefined
|
|
1496
|
+
: parseOptionalString(resolvedOptions.fixedVersion);
|
|
1497
|
+
const component = unsetTargets.frontMatterKeys.has("component") || resolvedOptions.component === undefined
|
|
1498
|
+
? undefined
|
|
1499
|
+
: parseOptionalString(resolvedOptions.component);
|
|
1500
|
+
const regressionRaw = unsetTargets.frontMatterKeys.has("regression") || resolvedOptions.regression === undefined
|
|
1501
|
+
? undefined
|
|
1502
|
+
: parseOptionalString(resolvedOptions.regression);
|
|
348
1503
|
const regression = regressionRaw !== undefined ? parseRegressionInput(regressionRaw) : undefined;
|
|
349
|
-
const customerImpact =
|
|
1504
|
+
const customerImpact = unsetTargets.frontMatterKeys.has("customer_impact") || resolvedOptions.customerImpact === undefined
|
|
1505
|
+
? undefined
|
|
1506
|
+
: parseOptionalString(resolvedOptions.customerImpact);
|
|
1507
|
+
const title = requireStringOption(resolvedOptions.title, "--title");
|
|
1508
|
+
const description = requireStringOption(resolvedOptions.description, "--description");
|
|
1509
|
+
const body = resolvedOptions.body ?? "";
|
|
350
1510
|
const frontMatter = normalizeFrontMatter({
|
|
351
1511
|
id,
|
|
352
|
-
title
|
|
353
|
-
description
|
|
1512
|
+
title,
|
|
1513
|
+
description,
|
|
354
1514
|
type,
|
|
1515
|
+
type_options: validatedTypeOptions.normalized,
|
|
355
1516
|
status,
|
|
356
1517
|
priority,
|
|
357
1518
|
tags,
|
|
@@ -398,22 +1559,32 @@ export async function runCreate(options, global) {
|
|
|
398
1559
|
files: files.values,
|
|
399
1560
|
tests: tests.values,
|
|
400
1561
|
docs: docs.values,
|
|
1562
|
+
reminders: reminders.values,
|
|
1563
|
+
events: events.values,
|
|
1564
|
+
...runtimeCreateFieldValues.values,
|
|
401
1565
|
});
|
|
1566
|
+
try {
|
|
1567
|
+
applyRegisteredItemFieldDefaultsAndValidation(frontMatter, getActiveExtensionRegistrations());
|
|
1568
|
+
}
|
|
1569
|
+
catch (error) {
|
|
1570
|
+
throw new PmCliError(error instanceof Error ? error.message : "Invalid extension item field values", EXIT_CODE.USAGE);
|
|
1571
|
+
}
|
|
402
1572
|
const afterDocument = canonicalDocument({
|
|
403
1573
|
front_matter: frontMatter,
|
|
404
|
-
body
|
|
405
|
-
});
|
|
1574
|
+
body,
|
|
1575
|
+
}, { schema: settings.schema });
|
|
406
1576
|
const beforeDocument = {
|
|
407
1577
|
front_matter: {},
|
|
408
1578
|
body: "",
|
|
409
1579
|
};
|
|
410
|
-
const itemPath = getItemPath(pmRoot, type, id);
|
|
1580
|
+
const itemPath = getItemPath(pmRoot, type, id, settings.item_format, typeRegistry.type_to_folder);
|
|
411
1581
|
const historyPath = getHistoryPath(pmRoot, id);
|
|
412
|
-
const lockRelease = await acquireLock(pmRoot, id, settings.locks.ttl_seconds, author);
|
|
413
|
-
const
|
|
1582
|
+
const lockRelease = await acquireLock(pmRoot, id, settings.locks.ttl_seconds, author, false, settings.governance.force_required_for_stale_lock);
|
|
1583
|
+
const explicitUnsetKeys = [...explicitUnsets].sort((left, right) => left.localeCompare(right));
|
|
1584
|
+
const historyMessage = buildHistoryMessage(resolvedOptions.message, explicitUnsetKeys);
|
|
414
1585
|
let hookWarnings = [];
|
|
415
1586
|
try {
|
|
416
|
-
await writeFileAtomic(itemPath, serializeItemDocument(afterDocument));
|
|
1587
|
+
await writeFileAtomic(itemPath, serializeItemDocument(afterDocument, { format: settings.item_format, schema: settings.schema }));
|
|
417
1588
|
try {
|
|
418
1589
|
const entry = createHistoryEntry({
|
|
419
1590
|
nowIso: nowValue,
|
|
@@ -445,12 +1616,12 @@ export async function runCreate(options, global) {
|
|
|
445
1616
|
finally {
|
|
446
1617
|
await lockRelease();
|
|
447
1618
|
}
|
|
448
|
-
const changedFields = buildChangedFields(frontMatter,
|
|
1619
|
+
const changedFields = buildChangedFields(frontMatter, explicitUnsetKeys);
|
|
449
1620
|
const outputItem = structuredClone(frontMatter);
|
|
450
1621
|
return {
|
|
451
1622
|
item: outputItem,
|
|
452
1623
|
changed_fields: changedFields,
|
|
453
|
-
warnings: hookWarnings,
|
|
1624
|
+
warnings: [...validationWarnings, ...hookWarnings],
|
|
454
1625
|
};
|
|
455
1626
|
}
|
|
456
1627
|
//# sourceMappingURL=create.js.map
|