@unbrained/pm-cli 2026.3.12 → 2026.5.1-2
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 +404 -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 +455 -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 +1151 -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 +70 -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 +41 -14
- 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
package/dist/cli/main.js
CHANGED
|
@@ -3,29 +3,143 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import { runAppend, runActivity, runClaim, runClose, runComments, runCompletion, runConfig, runCreate, runDelete, runDocs, runFiles, runGet, runGc, runHealth, runHistory, runInit,
|
|
7
|
-
import { activateExtensions, clearActiveExtensionHooks,
|
|
6
|
+
import { runAggregate, runAppend, runActivity, runCalendar, runClaim, runClose, runComments, runCommentsAudit, runCompletion, runConfig, runContracts, runCreate, runDelete, runDedupeAudit, runDeps, runDocs, runExtension, runFiles, runFilesDiscover, runGet, runGc, runHealth, runHistory, runInit, runLearnings, runList, runNormalize, runNotes, runSearch, runReindex, runRestore, renderCalendarMarkdown, renderContextMarkdown, runRelease, resolveCalendarOutputFormat, resolveContextOutputFormat, runStats, runStartBackgroundRun, runTest, runTestAll, runTestRunsList, runTestRunsLogs, runTestRunsResume, runTestRunsStatus, runTestRunsStop, runTestRunsWorker, runTemplatesList, runTemplatesSave, runTemplatesShow, runUpdate, runUpdateMany, runValidate, runContext, } from "./commands/index.js";
|
|
7
|
+
import { activateExtensions, clearActiveExtensionHooks, getActiveCommandResult, getActiveExtensionRegistrations, loadExtensions, runActiveCommandHandler, runActiveParserOverride, runActivePreflightOverride, runActiveServiceOverride, runAfterCommandHooks, runBeforeCommandHooks, setActiveCommandResult, setActiveCommandContext, setActiveExtensionCommands, setActiveExtensionHooks, setActiveExtensionParsers, setActiveExtensionPreflight, setActiveExtensionRegistrations, setActiveExtensionRenderers, setActiveExtensionServices, } from "../core/extensions/index.js";
|
|
8
8
|
import { pathExists } from "../core/fs/fs-utils.js";
|
|
9
|
+
import { commandOptionFlagLabel, resolveCommandOptionPolicyState, resolveItemTypeRegistry, resolveTypeDefinition, } from "../core/item/type-registry.js";
|
|
10
|
+
import { normalizeStatusInput } from "../core/item/status.js";
|
|
11
|
+
import { resolveRuntimeFieldRegistry, resolveRuntimeStatusRegistry, } from "../core/schema/runtime-schema.js";
|
|
9
12
|
import { refreshSearchArtifactsForMutation } from "../core/search/cache.js";
|
|
10
13
|
import { EXIT_CODE } from "../core/shared/constants.js";
|
|
11
14
|
import { PmCliError } from "../core/shared/errors.js";
|
|
12
|
-
import { printError, printResult } from "../core/output/output.js";
|
|
15
|
+
import { printError, printResult, writeStdout } from "../core/output/output.js";
|
|
16
|
+
import { maybeRunFirstUseTelemetryPrompt } from "../core/telemetry/consent.js";
|
|
17
|
+
import { finishTelemetryCommand, startTelemetryCommand, } from "../core/telemetry/runtime.js";
|
|
18
|
+
import { migrateItemFilesToFormat } from "../core/store/item-format-migration.js";
|
|
19
|
+
import { listAllFrontMatter } from "../core/store/item-store.js";
|
|
13
20
|
import { getSettingsPath, resolvePmRoot } from "../core/store/paths.js";
|
|
14
|
-
import { readSettings } from "../core/store/settings.js";
|
|
15
|
-
import {
|
|
16
|
-
import { parseLooseCommandOptions } from "./extension-command-options.js";
|
|
21
|
+
import { readSettings, readSettingsWithMetadata, writeSettings } from "../core/store/settings.js";
|
|
22
|
+
import { BUILTIN_ITEM_TYPE_VALUES } from "../types/index.js";
|
|
23
|
+
import { coerceLooseCommandOptionsWithFlagDefinitions, parseLooseCommandOptions } from "./extension-command-options.js";
|
|
24
|
+
import { attachRichHelpText, normalizeHelpCommandPath, resolveHelpDetailMode, resolveHelpNarrative } from "./help-content.js";
|
|
25
|
+
import { formatCommanderErrorForDisplay, formatCommanderErrorForJson, formatPmCliErrorForDisplay, formatPmCliErrorForJson, formatUnknownErrorForJson, } from "./error-guidance.js";
|
|
26
|
+
import { CALENDAR_COMMANDER_STRING_OPTION_CONTRACTS, ACTIVITY_COMMANDER_STRING_OPTION_CONTRACTS, CONTEXT_COMMANDER_STRING_OPTION_CONTRACTS, CREATE_COMMANDER_REPEATABLE_OPTION_CONTRACTS, CREATE_COMMANDER_STRING_OPTION_CONTRACTS, LIST_COMMANDER_STRING_OPTION_CONTRACTS, SEARCH_COMMANDER_STRING_OPTION_CONTRACTS, UPDATE_COMMANDER_REPEATABLE_OPTION_CONTRACTS, UPDATE_COMMANDER_STRING_OPTION_CONTRACTS, readFirstStringFromCommanderOptions, readStringArrayFromCommanderOptions, } from "../sdk/cli-contracts.js";
|
|
27
|
+
const PM_PACKAGE_ROOT_ENV = "PM_CLI_PACKAGE_ROOT";
|
|
28
|
+
function resolvePmPackageRoot() {
|
|
29
|
+
const mainPath = fileURLToPath(import.meta.url);
|
|
30
|
+
return path.resolve(path.dirname(mainPath), "../..");
|
|
31
|
+
}
|
|
32
|
+
if (typeof process.env[PM_PACKAGE_ROOT_ENV] !== "string" || process.env[PM_PACKAGE_ROOT_ENV]?.trim().length === 0) {
|
|
33
|
+
process.env[PM_PACKAGE_ROOT_ENV] = resolvePmPackageRoot();
|
|
34
|
+
}
|
|
17
35
|
function collect(value, previous) {
|
|
18
36
|
const next = previous ?? [];
|
|
19
37
|
next.push(value);
|
|
20
38
|
return next;
|
|
21
39
|
}
|
|
40
|
+
function pushOptionalValueFlag(args, flag, value) {
|
|
41
|
+
if (typeof value !== "string") {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const trimmed = value.trim();
|
|
45
|
+
if (trimmed.length === 0) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
args.push(flag, trimmed);
|
|
49
|
+
}
|
|
50
|
+
function pushOptionalBooleanFlag(args, flag, value) {
|
|
51
|
+
if (value === true) {
|
|
52
|
+
args.push(flag);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function pushRepeatableValueFlag(args, flag, values) {
|
|
56
|
+
if (!Array.isArray(values)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
for (const value of values) {
|
|
60
|
+
if (typeof value !== "string") {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const trimmed = value.trim();
|
|
64
|
+
if (trimmed.length === 0) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
args.push(flag, trimmed);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function buildBackgroundTestCommandArgs(id, options) {
|
|
71
|
+
const args = ["test", id, "--run", "--json", "--progress"];
|
|
72
|
+
pushRepeatableValueFlag(args, "--add", options.add);
|
|
73
|
+
pushRepeatableValueFlag(args, "--remove", options.remove);
|
|
74
|
+
pushOptionalValueFlag(args, "--timeout", options.timeout);
|
|
75
|
+
pushRepeatableValueFlag(args, "--env-set", options.envSet);
|
|
76
|
+
pushRepeatableValueFlag(args, "--env-clear", options.envClear);
|
|
77
|
+
pushOptionalBooleanFlag(args, "--shared-host-safe", options.sharedHostSafe);
|
|
78
|
+
pushOptionalValueFlag(args, "--pm-context", options.pmContext);
|
|
79
|
+
pushOptionalBooleanFlag(args, "--override-linked-pm-context", options.overrideLinkedPmContext);
|
|
80
|
+
pushOptionalBooleanFlag(args, "--fail-on-context-mismatch", options.failOnContextMismatch);
|
|
81
|
+
pushOptionalBooleanFlag(args, "--fail-on-skipped", options.failOnSkipped);
|
|
82
|
+
pushOptionalBooleanFlag(args, "--fail-on-empty-test-run", options.failOnEmptyTestRun);
|
|
83
|
+
pushOptionalBooleanFlag(args, "--require-assertions-for-pm", options.requireAssertionsForPm);
|
|
84
|
+
pushOptionalBooleanFlag(args, "--check-context", options.checkContext);
|
|
85
|
+
pushOptionalBooleanFlag(args, "--auto-pm-context", options.autoPmContext);
|
|
86
|
+
pushOptionalValueFlag(args, "--author", options.author);
|
|
87
|
+
pushOptionalValueFlag(args, "--message", options.message);
|
|
88
|
+
pushOptionalBooleanFlag(args, "--force", options.force);
|
|
89
|
+
return args;
|
|
90
|
+
}
|
|
91
|
+
function buildBackgroundTestAllCommandArgs(options) {
|
|
92
|
+
const args = ["test-all", "--json", "--progress"];
|
|
93
|
+
pushOptionalValueFlag(args, "--status", options.status);
|
|
94
|
+
pushOptionalValueFlag(args, "--limit", options.limit);
|
|
95
|
+
pushOptionalValueFlag(args, "--offset", options.offset);
|
|
96
|
+
pushOptionalValueFlag(args, "--timeout", options.timeout);
|
|
97
|
+
pushRepeatableValueFlag(args, "--env-set", options.envSet);
|
|
98
|
+
pushRepeatableValueFlag(args, "--env-clear", options.envClear);
|
|
99
|
+
pushOptionalBooleanFlag(args, "--shared-host-safe", options.sharedHostSafe);
|
|
100
|
+
pushOptionalValueFlag(args, "--pm-context", options.pmContext);
|
|
101
|
+
pushOptionalBooleanFlag(args, "--override-linked-pm-context", options.overrideLinkedPmContext);
|
|
102
|
+
pushOptionalBooleanFlag(args, "--fail-on-context-mismatch", options.failOnContextMismatch);
|
|
103
|
+
pushOptionalBooleanFlag(args, "--fail-on-skipped", options.failOnSkipped);
|
|
104
|
+
pushOptionalBooleanFlag(args, "--fail-on-empty-test-run", options.failOnEmptyTestRun);
|
|
105
|
+
pushOptionalBooleanFlag(args, "--require-assertions-for-pm", options.requireAssertionsForPm);
|
|
106
|
+
pushOptionalBooleanFlag(args, "--check-context", options.checkContext);
|
|
107
|
+
pushOptionalBooleanFlag(args, "--auto-pm-context", options.autoPmContext);
|
|
108
|
+
return args;
|
|
109
|
+
}
|
|
110
|
+
const RESOLVED_GLOBAL_OPTIONS = Symbol("pm.resolvedGlobalOptions");
|
|
111
|
+
function setResolvedGlobalOptions(command, globalOptions) {
|
|
112
|
+
command[RESOLVED_GLOBAL_OPTIONS] = { ...globalOptions };
|
|
113
|
+
}
|
|
114
|
+
function clearResolvedGlobalOptions(command) {
|
|
115
|
+
delete command[RESOLVED_GLOBAL_OPTIONS];
|
|
116
|
+
}
|
|
117
|
+
async function applyDefaultOutputFormat(globalOptions) {
|
|
118
|
+
if (globalOptions.json === true) {
|
|
119
|
+
return globalOptions;
|
|
120
|
+
}
|
|
121
|
+
const pmRoot = resolvePmRoot(process.cwd(), globalOptions.path);
|
|
122
|
+
if (!(await pathExists(getSettingsPath(pmRoot)))) {
|
|
123
|
+
return globalOptions;
|
|
124
|
+
}
|
|
125
|
+
const settings = await readSettings(pmRoot);
|
|
126
|
+
return {
|
|
127
|
+
...globalOptions,
|
|
128
|
+
defaultOutputFormat: settings.output.default_format,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
22
131
|
function getGlobalOptions(command) {
|
|
132
|
+
const resolved = command[RESOLVED_GLOBAL_OPTIONS];
|
|
133
|
+
if (resolved) {
|
|
134
|
+
return { ...resolved };
|
|
135
|
+
}
|
|
23
136
|
const opts = command.optsWithGlobals();
|
|
24
137
|
return {
|
|
25
|
-
json:
|
|
138
|
+
json: opts.json === true ? true : undefined,
|
|
26
139
|
quiet: Boolean(opts.quiet),
|
|
27
140
|
path: typeof opts.path === "string" ? opts.path : undefined,
|
|
28
141
|
noExtensions: opts.extensions === false,
|
|
142
|
+
noPager: Boolean(opts.noPager),
|
|
29
143
|
profile: Boolean(opts.profile),
|
|
30
144
|
};
|
|
31
145
|
}
|
|
@@ -53,7 +167,14 @@ function toNonEmptyFlagString(value) {
|
|
|
53
167
|
const trimmed = value.trim();
|
|
54
168
|
return trimmed.length > 0 ? trimmed : null;
|
|
55
169
|
}
|
|
170
|
+
function toOptionalBoolean(value) {
|
|
171
|
+
return typeof value === "boolean" ? value : undefined;
|
|
172
|
+
}
|
|
56
173
|
function formatDynamicExtensionFlagHelpLine(definition) {
|
|
174
|
+
const visible = toOptionalBoolean(definition.visible);
|
|
175
|
+
if (visible === false) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
57
178
|
const longName = toNonEmptyFlagString(definition.long);
|
|
58
179
|
if (!longName || !longName.startsWith("--") || longName.length < 3) {
|
|
59
180
|
return null;
|
|
@@ -63,7 +184,15 @@ function formatDynamicExtensionFlagHelpLine(definition) {
|
|
|
63
184
|
const valueName = toNonEmptyFlagString(definition.value_name);
|
|
64
185
|
const valueSuffix = valueName ? ` <${valueName}>` : "";
|
|
65
186
|
const description = toNonEmptyFlagString(definition.description) ?? "Extension-provided option.";
|
|
66
|
-
|
|
187
|
+
const markers = [];
|
|
188
|
+
if (toOptionalBoolean(definition.required) === true) {
|
|
189
|
+
markers.push("required");
|
|
190
|
+
}
|
|
191
|
+
if (toOptionalBoolean(definition.enabled) === false) {
|
|
192
|
+
markers.push("disabled");
|
|
193
|
+
}
|
|
194
|
+
const markerSuffix = markers.length > 0 ? ` [${markers.join(", ")}]` : "";
|
|
195
|
+
return `${shortPrefix}${longName}${valueSuffix} ${description}${markerSuffix}`;
|
|
67
196
|
}
|
|
68
197
|
function buildDynamicExtensionFlagHelp(definitions) {
|
|
69
198
|
const lines = [
|
|
@@ -98,8 +227,284 @@ function collectDynamicExtensionFlagHelpByCommand(registrations) {
|
|
|
98
227
|
}
|
|
99
228
|
return helpByCommand;
|
|
100
229
|
}
|
|
230
|
+
function normalizeExtensionCommandAction(commandPath, action) {
|
|
231
|
+
if (typeof action !== "string" || action.trim().length === 0) {
|
|
232
|
+
return commandPath.replace(/\s+/g, "-");
|
|
233
|
+
}
|
|
234
|
+
return action.trim().toLowerCase();
|
|
235
|
+
}
|
|
236
|
+
function normalizeExtensionCommandStringList(values) {
|
|
237
|
+
if (!Array.isArray(values)) {
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
const normalized = [];
|
|
241
|
+
const seen = new Set();
|
|
242
|
+
for (const value of values) {
|
|
243
|
+
if (typeof value !== "string") {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const trimmed = value.trim();
|
|
247
|
+
if (trimmed.length === 0 || seen.has(trimmed)) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
seen.add(trimmed);
|
|
251
|
+
normalized.push(trimmed);
|
|
252
|
+
}
|
|
253
|
+
return normalized;
|
|
254
|
+
}
|
|
255
|
+
function normalizeExtensionCommandArguments(values) {
|
|
256
|
+
if (!Array.isArray(values)) {
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
return values
|
|
260
|
+
.map((value) => {
|
|
261
|
+
const name = typeof value.name === "string" ? value.name.trim() : "";
|
|
262
|
+
if (name.length === 0) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
const normalized = {
|
|
266
|
+
name,
|
|
267
|
+
required: value.required === true,
|
|
268
|
+
variadic: value.variadic === true,
|
|
269
|
+
};
|
|
270
|
+
if (typeof value.description === "string" && value.description.trim().length > 0) {
|
|
271
|
+
normalized.description = value.description.trim();
|
|
272
|
+
}
|
|
273
|
+
return normalized;
|
|
274
|
+
})
|
|
275
|
+
.filter((entry) => entry !== null);
|
|
276
|
+
}
|
|
277
|
+
function collectExtensionCommandHelpDescriptors(commandHandlers, commandDefinitions, flagRegistrations) {
|
|
278
|
+
const definitionsByCommand = new Map();
|
|
279
|
+
for (const definition of commandDefinitions) {
|
|
280
|
+
const commandPath = normalizeExtensionCommandPath(definition.command);
|
|
281
|
+
if (commandPath.length === 0) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const description = typeof definition.description === "string" && definition.description.trim().length > 0
|
|
285
|
+
? definition.description.trim()
|
|
286
|
+
: undefined;
|
|
287
|
+
const intent = typeof definition.intent === "string" && definition.intent.trim().length > 0
|
|
288
|
+
? definition.intent.trim()
|
|
289
|
+
: undefined;
|
|
290
|
+
definitionsByCommand.set(commandPath, {
|
|
291
|
+
command: commandPath,
|
|
292
|
+
action: normalizeExtensionCommandAction(commandPath, definition.action),
|
|
293
|
+
description,
|
|
294
|
+
intent,
|
|
295
|
+
examples: normalizeExtensionCommandStringList(definition.examples),
|
|
296
|
+
failure_hints: normalizeExtensionCommandStringList(definition.failure_hints),
|
|
297
|
+
arguments: normalizeExtensionCommandArguments(definition.arguments),
|
|
298
|
+
flags: [],
|
|
299
|
+
source: {
|
|
300
|
+
layer: definition.layer,
|
|
301
|
+
name: definition.name,
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
const flagsByCommand = new Map();
|
|
306
|
+
for (const registration of flagRegistrations) {
|
|
307
|
+
const commandPath = normalizeExtensionCommandPath(registration.target_command);
|
|
308
|
+
if (commandPath.length === 0) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
const existing = flagsByCommand.get(commandPath) ?? [];
|
|
312
|
+
existing.push(...registration.flags);
|
|
313
|
+
flagsByCommand.set(commandPath, existing);
|
|
314
|
+
}
|
|
315
|
+
const commandSet = new Set();
|
|
316
|
+
for (const commandPath of commandHandlers) {
|
|
317
|
+
const normalized = normalizeExtensionCommandPath(commandPath);
|
|
318
|
+
if (normalized.length > 0) {
|
|
319
|
+
commandSet.add(normalized);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
for (const commandPath of definitionsByCommand.keys()) {
|
|
323
|
+
commandSet.add(commandPath);
|
|
324
|
+
}
|
|
325
|
+
for (const commandPath of flagsByCommand.keys()) {
|
|
326
|
+
commandSet.add(commandPath);
|
|
327
|
+
}
|
|
328
|
+
const descriptors = new Map();
|
|
329
|
+
const sortedCommands = [...commandSet].sort((left, right) => left.localeCompare(right));
|
|
330
|
+
for (const commandPath of sortedCommands) {
|
|
331
|
+
const definition = definitionsByCommand.get(commandPath);
|
|
332
|
+
const flags = flagsByCommand.get(commandPath) ?? [];
|
|
333
|
+
if (definition) {
|
|
334
|
+
descriptors.set(commandPath, {
|
|
335
|
+
...definition,
|
|
336
|
+
flags,
|
|
337
|
+
});
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
descriptors.set(commandPath, {
|
|
341
|
+
command: commandPath,
|
|
342
|
+
action: normalizeExtensionCommandAction(commandPath, undefined),
|
|
343
|
+
examples: [],
|
|
344
|
+
failure_hints: [],
|
|
345
|
+
arguments: [],
|
|
346
|
+
flags,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
return descriptors;
|
|
350
|
+
}
|
|
351
|
+
function buildExtensionArgumentToken(argument) {
|
|
352
|
+
const variadicSuffix = argument.variadic ? "..." : "";
|
|
353
|
+
if (argument.required) {
|
|
354
|
+
return `<${argument.name}${variadicSuffix}>`;
|
|
355
|
+
}
|
|
356
|
+
return `[${argument.name}${variadicSuffix}]`;
|
|
357
|
+
}
|
|
358
|
+
function applyDynamicExtensionArguments(command, descriptor) {
|
|
359
|
+
for (const argument of descriptor.arguments) {
|
|
360
|
+
command.argument(buildExtensionArgumentToken(argument), argument.description ?? "Extension argument.");
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function formatDynamicExtensionOptionFlags(definition) {
|
|
364
|
+
const visible = toOptionalBoolean(definition.visible);
|
|
365
|
+
if (visible === false) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
const longName = toNonEmptyFlagString(definition.long);
|
|
369
|
+
const shortName = toNonEmptyFlagString(definition.short);
|
|
370
|
+
const normalizedShort = shortName && shortName.startsWith("-") && !shortName.startsWith("--") ? shortName : null;
|
|
371
|
+
const normalizedLong = longName && longName.startsWith("--") && longName.length > 2 ? longName : null;
|
|
372
|
+
if (!normalizedLong && !normalizedShort) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
const optionValueName = toNonEmptyFlagString(definition.value_name);
|
|
376
|
+
const optionValueSuffix = optionValueName ? ` <${optionValueName}>` : "";
|
|
377
|
+
const optionNames = [normalizedShort, normalizedLong].filter((entry) => entry !== null);
|
|
378
|
+
return `${optionNames.join(", ")}${optionValueSuffix}`;
|
|
379
|
+
}
|
|
380
|
+
function formatDynamicExtensionOptionDescription(definition) {
|
|
381
|
+
const description = toNonEmptyFlagString(definition.description) ?? "Extension-provided option.";
|
|
382
|
+
const markers = [];
|
|
383
|
+
if (toOptionalBoolean(definition.required) === true) {
|
|
384
|
+
markers.push("required");
|
|
385
|
+
}
|
|
386
|
+
if (toOptionalBoolean(definition.enabled) === false) {
|
|
387
|
+
markers.push("disabled");
|
|
388
|
+
}
|
|
389
|
+
const markerSuffix = markers.length > 0 ? ` [${markers.join(", ")}]` : "";
|
|
390
|
+
return `${description}${markerSuffix}`;
|
|
391
|
+
}
|
|
392
|
+
function applyDynamicExtensionOptions(command, descriptor) {
|
|
393
|
+
const seen = new Set();
|
|
394
|
+
for (const definition of descriptor.flags) {
|
|
395
|
+
const optionFlags = formatDynamicExtensionOptionFlags(definition);
|
|
396
|
+
if (!optionFlags || seen.has(optionFlags)) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
seen.add(optionFlags);
|
|
400
|
+
command.option(optionFlags, formatDynamicExtensionOptionDescription(definition));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function buildDynamicExtensionHelpOptionSummary(definition) {
|
|
404
|
+
const flags = formatDynamicExtensionOptionFlags(definition);
|
|
405
|
+
if (!flags) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
const longName = toNonEmptyFlagString(definition.long);
|
|
409
|
+
const shortName = toNonEmptyFlagString(definition.short);
|
|
410
|
+
const normalizedLong = longName && longName.startsWith("--") && longName.length > 2 ? longName : null;
|
|
411
|
+
const normalizedShort = shortName && shortName.startsWith("-") && !shortName.startsWith("--") ? shortName : null;
|
|
412
|
+
const valueName = toNonEmptyFlagString(definition.value_name);
|
|
413
|
+
const required = toOptionalBoolean(definition.required) === true;
|
|
414
|
+
return {
|
|
415
|
+
flags,
|
|
416
|
+
long: normalizedLong,
|
|
417
|
+
short: normalizedShort,
|
|
418
|
+
description: formatDynamicExtensionOptionDescription(definition),
|
|
419
|
+
takes_value: valueName !== null,
|
|
420
|
+
value_required: valueName !== null,
|
|
421
|
+
value_name: valueName,
|
|
422
|
+
variadic: false,
|
|
423
|
+
required,
|
|
424
|
+
aliases: [],
|
|
425
|
+
alias_for: null,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function buildDynamicExtensionHelpOptionSummaries(descriptor) {
|
|
429
|
+
if (!descriptor) {
|
|
430
|
+
return [];
|
|
431
|
+
}
|
|
432
|
+
const summaries = [];
|
|
433
|
+
const seen = new Set();
|
|
434
|
+
for (const definition of descriptor.flags) {
|
|
435
|
+
const summary = buildDynamicExtensionHelpOptionSummary(definition);
|
|
436
|
+
if (!summary || seen.has(summary.flags)) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
seen.add(summary.flags);
|
|
440
|
+
summaries.push(summary);
|
|
441
|
+
}
|
|
442
|
+
return summaries;
|
|
443
|
+
}
|
|
444
|
+
function mergeHelpOptionSummaries(base, extension) {
|
|
445
|
+
if (extension.length === 0) {
|
|
446
|
+
return base;
|
|
447
|
+
}
|
|
448
|
+
const merged = [...base];
|
|
449
|
+
const seen = new Set(base.map((entry) => entry.flags));
|
|
450
|
+
for (const entry of extension) {
|
|
451
|
+
if (seen.has(entry.flags)) {
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
seen.add(entry.flags);
|
|
455
|
+
merged.push(entry);
|
|
456
|
+
}
|
|
457
|
+
return merged;
|
|
458
|
+
}
|
|
459
|
+
function buildDynamicExtensionCommandMetadataHelp(descriptor) {
|
|
460
|
+
const lines = [];
|
|
461
|
+
if (descriptor.intent) {
|
|
462
|
+
lines.push(`Intent: ${descriptor.intent}`);
|
|
463
|
+
}
|
|
464
|
+
if (descriptor.action) {
|
|
465
|
+
lines.push(`Action contract: ${descriptor.action}`);
|
|
466
|
+
}
|
|
467
|
+
if (descriptor.examples.length > 0) {
|
|
468
|
+
lines.push("Examples:");
|
|
469
|
+
for (const example of descriptor.examples) {
|
|
470
|
+
lines.push(` - ${example}`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (descriptor.failure_hints.length > 0) {
|
|
474
|
+
lines.push("Common failure hints:");
|
|
475
|
+
for (const hint of descriptor.failure_hints) {
|
|
476
|
+
lines.push(` - ${hint}`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (lines.length === 0) {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
return `\nExtension command metadata:\n ${lines.join("\n ")}`;
|
|
483
|
+
}
|
|
484
|
+
function commandAliases(command) {
|
|
485
|
+
const commandRecord = command;
|
|
486
|
+
if (typeof commandRecord.aliases === "function") {
|
|
487
|
+
return commandRecord.aliases().map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
|
|
488
|
+
}
|
|
489
|
+
if (typeof commandRecord.alias === "function") {
|
|
490
|
+
const alias = commandRecord.alias();
|
|
491
|
+
if (typeof alias === "string" && alias.trim().length > 0) {
|
|
492
|
+
return [alias.trim().toLowerCase()];
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (Array.isArray(commandRecord._aliases)) {
|
|
496
|
+
return commandRecord._aliases.map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
|
|
497
|
+
}
|
|
498
|
+
return [];
|
|
499
|
+
}
|
|
101
500
|
function findDirectChildCommand(parent, name) {
|
|
102
|
-
|
|
501
|
+
const normalizedTarget = name.trim().toLowerCase();
|
|
502
|
+
return (parent.commands.find((entry) => {
|
|
503
|
+
if (entry.name().trim().toLowerCase() === normalizedTarget) {
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
return commandAliases(entry).includes(normalizedTarget);
|
|
507
|
+
}) ?? null);
|
|
103
508
|
}
|
|
104
509
|
function findCommandByPath(root, pathParts) {
|
|
105
510
|
let current = root;
|
|
@@ -164,6 +569,9 @@ function parseBootstrapPathToken(token, next) {
|
|
|
164
569
|
function parseBootstrapGlobalOptions(argv) {
|
|
165
570
|
let pathValue;
|
|
166
571
|
let noExtensions = false;
|
|
572
|
+
let noPager = false;
|
|
573
|
+
let json = false;
|
|
574
|
+
let quiet = false;
|
|
167
575
|
let index = 0;
|
|
168
576
|
while (index < argv.length) {
|
|
169
577
|
const token = argv[index];
|
|
@@ -175,6 +583,21 @@ function parseBootstrapGlobalOptions(argv) {
|
|
|
175
583
|
index += 1;
|
|
176
584
|
continue;
|
|
177
585
|
}
|
|
586
|
+
if (token === "--no-pager") {
|
|
587
|
+
noPager = true;
|
|
588
|
+
index += 1;
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
if (token === "--json") {
|
|
592
|
+
json = true;
|
|
593
|
+
index += 1;
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
if (token === "--quiet") {
|
|
597
|
+
quiet = true;
|
|
598
|
+
index += 1;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
178
601
|
const parsedPath = parseBootstrapPathToken(token, argv[index + 1]);
|
|
179
602
|
if (parsedPath) {
|
|
180
603
|
if (parsedPath.pathValue !== undefined) {
|
|
@@ -188,9 +611,436 @@ function parseBootstrapGlobalOptions(argv) {
|
|
|
188
611
|
return {
|
|
189
612
|
path: pathValue,
|
|
190
613
|
noExtensions,
|
|
614
|
+
noPager,
|
|
615
|
+
json,
|
|
616
|
+
quiet,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function stripGlobalBootstrapTokens(argv) {
|
|
620
|
+
const remaining = [];
|
|
621
|
+
let index = 0;
|
|
622
|
+
while (index < argv.length) {
|
|
623
|
+
const token = argv[index];
|
|
624
|
+
if (token === "--") {
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
if (token === "--json" ||
|
|
628
|
+
token === "--quiet" ||
|
|
629
|
+
token === "--no-extensions" ||
|
|
630
|
+
token === "--no-pager" ||
|
|
631
|
+
token === "--profile" ||
|
|
632
|
+
token === "--explain") {
|
|
633
|
+
index += 1;
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (token === "--path") {
|
|
637
|
+
index += 2;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
if (token.startsWith("--path=")) {
|
|
641
|
+
index += 1;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
remaining.push(token);
|
|
645
|
+
index += 1;
|
|
646
|
+
}
|
|
647
|
+
return remaining;
|
|
648
|
+
}
|
|
649
|
+
function shouldDisablePagerForInvocation(argv, bootstrapGlobal) {
|
|
650
|
+
if (bootstrapGlobal.noPager) {
|
|
651
|
+
return true;
|
|
652
|
+
}
|
|
653
|
+
if (process.stdout.isTTY === true) {
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
const helpRequest = parseBootstrapHelpRequest(argv);
|
|
657
|
+
return helpRequest.requested;
|
|
658
|
+
}
|
|
659
|
+
function applyBootstrapPagerPolicy(argv) {
|
|
660
|
+
const bootstrapGlobal = parseBootstrapGlobalOptions(argv);
|
|
661
|
+
if (!shouldDisablePagerForInvocation(argv, bootstrapGlobal)) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
process.env.PAGER = "cat";
|
|
665
|
+
process.env.MANPAGER = "cat";
|
|
666
|
+
process.env.GIT_PAGER = "cat";
|
|
667
|
+
if (typeof process.env.LESS !== "string" || process.env.LESS.trim().length === 0) {
|
|
668
|
+
process.env.LESS = "FRX";
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
function parseBootstrapHelpRequest(argv) {
|
|
672
|
+
const stripped = stripGlobalBootstrapTokens(argv);
|
|
673
|
+
const first = stripped[0]?.trim().toLowerCase();
|
|
674
|
+
if (first === "help") {
|
|
675
|
+
const commandPathTokens = [];
|
|
676
|
+
for (let index = 1; index < stripped.length; index += 1) {
|
|
677
|
+
const token = stripped[index];
|
|
678
|
+
if (token.startsWith("-")) {
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
commandPathTokens.push(token.trim().toLowerCase());
|
|
682
|
+
}
|
|
683
|
+
return {
|
|
684
|
+
requested: true,
|
|
685
|
+
commandPathTokens,
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
const helpFlagIndex = stripped.findIndex((token) => token === "--help" || token === "-h");
|
|
689
|
+
if (helpFlagIndex < 0) {
|
|
690
|
+
return {
|
|
691
|
+
requested: false,
|
|
692
|
+
commandPathTokens: [],
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
const commandPathTokens = [];
|
|
696
|
+
for (const token of stripped) {
|
|
697
|
+
if (token.startsWith("-")) {
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
commandPathTokens.push(token.trim().toLowerCase());
|
|
701
|
+
}
|
|
702
|
+
return {
|
|
703
|
+
requested: true,
|
|
704
|
+
commandPathTokens,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function parseBootstrapCommandName(argv) {
|
|
708
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
709
|
+
const token = argv[index];
|
|
710
|
+
if (token === "--") {
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
if (token === "--path") {
|
|
714
|
+
index += 1;
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
if (token.startsWith("--path=") ||
|
|
718
|
+
token === "--json" ||
|
|
719
|
+
token === "--quiet" ||
|
|
720
|
+
token === "--no-extensions" ||
|
|
721
|
+
token === "--no-pager" ||
|
|
722
|
+
token === "--profile" ||
|
|
723
|
+
token === "--explain") {
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
if (token.startsWith("-")) {
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
return token.trim().toLowerCase();
|
|
730
|
+
}
|
|
731
|
+
return undefined;
|
|
732
|
+
}
|
|
733
|
+
const EXTENSION_ACTION_SYNTAX_TOKENS = new Set([
|
|
734
|
+
"install",
|
|
735
|
+
"uninstall",
|
|
736
|
+
"explore",
|
|
737
|
+
"manage",
|
|
738
|
+
"doctor",
|
|
739
|
+
"adopt",
|
|
740
|
+
"adopt-all",
|
|
741
|
+
"activate",
|
|
742
|
+
"deactivate",
|
|
743
|
+
]);
|
|
744
|
+
function normalizeLegacyExtensionActionSyntax(argv) {
|
|
745
|
+
const extensionIndex = argv.findIndex((token) => token === "extension");
|
|
746
|
+
if (extensionIndex < 0) {
|
|
747
|
+
return [...argv];
|
|
748
|
+
}
|
|
749
|
+
const actionToken = argv[extensionIndex + 1];
|
|
750
|
+
if (!actionToken || actionToken.startsWith("-")) {
|
|
751
|
+
return [...argv];
|
|
752
|
+
}
|
|
753
|
+
if (!EXTENSION_ACTION_SYNTAX_TOKENS.has(actionToken)) {
|
|
754
|
+
return [...argv];
|
|
755
|
+
}
|
|
756
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
757
|
+
return [...argv];
|
|
758
|
+
}
|
|
759
|
+
const forcedActionFlag = `--${actionToken}`;
|
|
760
|
+
if (argv.includes(forcedActionFlag)) {
|
|
761
|
+
return [...argv];
|
|
762
|
+
}
|
|
763
|
+
return [...argv.slice(0, extensionIndex + 1), forcedActionFlag, ...argv.slice(extensionIndex + 2)];
|
|
764
|
+
}
|
|
765
|
+
const BUILTIN_TYPE_HELP_VALUES = BUILTIN_ITEM_TYPE_VALUES.join("|");
|
|
766
|
+
function resolveCommandFromPathTokens(root, pathTokens) {
|
|
767
|
+
if (pathTokens.length === 0) {
|
|
768
|
+
return root;
|
|
769
|
+
}
|
|
770
|
+
return findCommandByPath(root, pathTokens);
|
|
771
|
+
}
|
|
772
|
+
function extractOptionValueName(flags) {
|
|
773
|
+
const match = flags.match(/[<[]([^>\]]+)[>\]]/);
|
|
774
|
+
if (!match) {
|
|
775
|
+
return null;
|
|
776
|
+
}
|
|
777
|
+
const value = match[1]?.trim();
|
|
778
|
+
return value && value.length > 0 ? value : null;
|
|
779
|
+
}
|
|
780
|
+
function readOptionAttributeName(option) {
|
|
781
|
+
const optionRecord = option;
|
|
782
|
+
if (typeof optionRecord.attributeName === "function") {
|
|
783
|
+
const value = optionRecord.attributeName();
|
|
784
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
785
|
+
}
|
|
786
|
+
if (typeof optionRecord.attributeName === "string" && optionRecord.attributeName.trim().length > 0) {
|
|
787
|
+
return optionRecord.attributeName.trim();
|
|
788
|
+
}
|
|
789
|
+
return null;
|
|
790
|
+
}
|
|
791
|
+
function buildOptionAliasMap(options) {
|
|
792
|
+
const aliasMap = new Map();
|
|
793
|
+
for (const option of options) {
|
|
794
|
+
const optionRecord = option;
|
|
795
|
+
const attributeName = readOptionAttributeName(option);
|
|
796
|
+
if (!attributeName || typeof optionRecord.long !== "string" || optionRecord.long.trim().length === 0) {
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
const existing = aliasMap.get(attributeName) ?? [];
|
|
800
|
+
existing.push(optionRecord.long.trim());
|
|
801
|
+
aliasMap.set(attributeName, existing);
|
|
802
|
+
}
|
|
803
|
+
for (const [attributeName, values] of aliasMap.entries()) {
|
|
804
|
+
aliasMap.set(attributeName, [...new Set(values.map((value) => value.trim()).filter((value) => value.length > 0))].sort((left, right) => left.localeCompare(right)));
|
|
805
|
+
}
|
|
806
|
+
return aliasMap;
|
|
807
|
+
}
|
|
808
|
+
function buildHelpOptionSummaries(command) {
|
|
809
|
+
const options = (command.options ?? []);
|
|
810
|
+
const optionAliasMap = buildOptionAliasMap(options);
|
|
811
|
+
return options.map((option) => {
|
|
812
|
+
const optionRecord = option;
|
|
813
|
+
const flags = typeof optionRecord.flags === "string" ? optionRecord.flags.trim() : "";
|
|
814
|
+
const description = typeof optionRecord.description === "string" ? optionRecord.description.trim() : "";
|
|
815
|
+
const attributeName = readOptionAttributeName(option);
|
|
816
|
+
const aliasCandidates = attributeName ? optionAliasMap.get(attributeName) ?? [] : [];
|
|
817
|
+
const aliases = aliasCandidates
|
|
818
|
+
.filter((entry) => entry !== optionRecord.long)
|
|
819
|
+
.map((entry) => entry.trim())
|
|
820
|
+
.filter((entry) => entry.length > 0);
|
|
821
|
+
const aliasForMatch = description.match(/^Alias for ([^ ]+)/i);
|
|
822
|
+
const aliasFor = aliasForMatch && aliasForMatch[1] ? aliasForMatch[1].trim() : null;
|
|
823
|
+
const required = optionRecord.mandatory === true || description.includes("[required]") || description.toLowerCase().includes("required;");
|
|
824
|
+
const valueRequired = flags.includes("<");
|
|
825
|
+
const takesValue = valueRequired || flags.includes("[");
|
|
826
|
+
const summary = {
|
|
827
|
+
flags,
|
|
828
|
+
long: typeof optionRecord.long === "string" ? optionRecord.long : null,
|
|
829
|
+
short: typeof optionRecord.short === "string" ? optionRecord.short : null,
|
|
830
|
+
description,
|
|
831
|
+
takes_value: takesValue,
|
|
832
|
+
value_required: valueRequired,
|
|
833
|
+
value_name: extractOptionValueName(flags),
|
|
834
|
+
variadic: optionRecord.variadic === true,
|
|
835
|
+
required,
|
|
836
|
+
aliases,
|
|
837
|
+
alias_for: aliasFor,
|
|
838
|
+
};
|
|
839
|
+
if (optionRecord.defaultValue !== undefined) {
|
|
840
|
+
summary.default_value = optionRecord.defaultValue;
|
|
841
|
+
}
|
|
842
|
+
return summary;
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
function buildHelpArgumentSummaries(command) {
|
|
846
|
+
const commandRecord = command;
|
|
847
|
+
const argumentsList = Array.isArray(commandRecord.registeredArguments)
|
|
848
|
+
? commandRecord.registeredArguments
|
|
849
|
+
: Array.isArray(commandRecord._args)
|
|
850
|
+
? commandRecord._args
|
|
851
|
+
: [];
|
|
852
|
+
return argumentsList.map((argument) => {
|
|
853
|
+
const rawName = typeof argument.name === "function"
|
|
854
|
+
? argument.name()
|
|
855
|
+
: typeof argument.name === "string"
|
|
856
|
+
? argument.name
|
|
857
|
+
: "argument";
|
|
858
|
+
const description = typeof argument.description === "string" && argument.description.trim().length > 0
|
|
859
|
+
? argument.description.trim()
|
|
860
|
+
: null;
|
|
861
|
+
return {
|
|
862
|
+
name: rawName.trim(),
|
|
863
|
+
required: argument.required === true,
|
|
864
|
+
variadic: argument.variadic === true,
|
|
865
|
+
description,
|
|
866
|
+
};
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
function buildHelpSubcommandSummaries(command) {
|
|
870
|
+
return command.commands
|
|
871
|
+
.map((entry) => ({
|
|
872
|
+
name: entry.name().trim(),
|
|
873
|
+
aliases: commandAliases(entry),
|
|
874
|
+
description: entry.description().trim(),
|
|
875
|
+
}))
|
|
876
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
877
|
+
}
|
|
878
|
+
function buildJsonHelpPayload(rootProgram, targetCommand, argv, requestedPath) {
|
|
879
|
+
const detailMode = resolveHelpDetailMode(argv);
|
|
880
|
+
const resolvedPath = normalizeHelpCommandPath(getCommandPath(targetCommand));
|
|
881
|
+
const commandPath = resolvedPath.length > 0 ? resolvedPath : undefined;
|
|
882
|
+
const fallbackNarrative = resolveHelpNarrative(commandPath, detailMode);
|
|
883
|
+
const extensionDescriptor = commandPath ? activeRuntimeExtensionCommandDescriptors.get(commandPath) : undefined;
|
|
884
|
+
const extensionExamples = extensionDescriptor?.examples ?? [];
|
|
885
|
+
const extensionFailureHints = extensionDescriptor?.failure_hints ?? [];
|
|
886
|
+
const narrative = extensionDescriptor
|
|
887
|
+
? {
|
|
888
|
+
intent: extensionDescriptor.intent ?? extensionDescriptor.description ?? fallbackNarrative.intent,
|
|
889
|
+
examples: detailMode === "detailed"
|
|
890
|
+
? extensionExamples.length > 0
|
|
891
|
+
? [...extensionExamples]
|
|
892
|
+
: [...fallbackNarrative.examples]
|
|
893
|
+
: extensionExamples.length > 0
|
|
894
|
+
? [extensionExamples[0]]
|
|
895
|
+
: [...fallbackNarrative.examples],
|
|
896
|
+
tips: detailMode === "detailed"
|
|
897
|
+
? extensionFailureHints.length > 0
|
|
898
|
+
? [...extensionFailureHints]
|
|
899
|
+
: [...fallbackNarrative.tips]
|
|
900
|
+
: [],
|
|
901
|
+
detail_mode: detailMode,
|
|
902
|
+
}
|
|
903
|
+
: fallbackNarrative;
|
|
904
|
+
const optionSummaries = mergeHelpOptionSummaries(buildHelpOptionSummaries(targetCommand), buildDynamicExtensionHelpOptionSummaries(extensionDescriptor));
|
|
905
|
+
const subcommands = buildHelpSubcommandSummaries(targetCommand);
|
|
906
|
+
return {
|
|
907
|
+
format: "pm_help_v1",
|
|
908
|
+
detail_mode: detailMode,
|
|
909
|
+
root_command: rootProgram.name(),
|
|
910
|
+
requested_path: requestedPath,
|
|
911
|
+
resolved_path: resolvedPath.length > 0 ? resolvedPath : rootProgram.name(),
|
|
912
|
+
description: targetCommand.description(),
|
|
913
|
+
usage: targetCommand.usage(),
|
|
914
|
+
intent: narrative.intent,
|
|
915
|
+
examples: narrative.examples,
|
|
916
|
+
tips: narrative.tips,
|
|
917
|
+
arguments: buildHelpArgumentSummaries(targetCommand),
|
|
918
|
+
options: optionSummaries,
|
|
919
|
+
subcommands,
|
|
920
|
+
has_subcommands: subcommands.length > 0,
|
|
191
921
|
};
|
|
192
922
|
}
|
|
923
|
+
async function maybeRenderBootstrapJsonHelp(rootProgram, argv) {
|
|
924
|
+
const bootstrapGlobal = parseBootstrapGlobalOptions(argv);
|
|
925
|
+
if (!bootstrapGlobal.json) {
|
|
926
|
+
return false;
|
|
927
|
+
}
|
|
928
|
+
const helpRequest = parseBootstrapHelpRequest(argv);
|
|
929
|
+
if (!helpRequest.requested) {
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
const targetCommand = resolveCommandFromPathTokens(rootProgram, helpRequest.commandPathTokens);
|
|
933
|
+
if (!targetCommand) {
|
|
934
|
+
if (!bootstrapGlobal.quiet) {
|
|
935
|
+
const unknownMessage = `unknown command '${helpRequest.commandPathTokens.join(" ")}'`;
|
|
936
|
+
const envelope = formatCommanderErrorForJson(unknownMessage, "help", BUILTIN_TYPE_HELP_VALUES, EXIT_CODE.USAGE, buildUnknownCommandGuidanceFromRuntime(unknownMessage, rootProgram));
|
|
937
|
+
printError(JSON.stringify(envelope, null, 2));
|
|
938
|
+
}
|
|
939
|
+
process.exitCode = EXIT_CODE.USAGE;
|
|
940
|
+
return true;
|
|
941
|
+
}
|
|
942
|
+
if (!bootstrapGlobal.quiet) {
|
|
943
|
+
const payload = buildJsonHelpPayload(rootProgram, targetCommand, argv, helpRequest.commandPathTokens);
|
|
944
|
+
writeStdout(`${JSON.stringify(payload, null, 2)}\n`);
|
|
945
|
+
}
|
|
946
|
+
process.exitCode = EXIT_CODE.SUCCESS;
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
function parseBootstrapTypeValue(argv) {
|
|
950
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
951
|
+
const token = argv[index];
|
|
952
|
+
if (token === "--type") {
|
|
953
|
+
const candidate = argv[index + 1];
|
|
954
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
955
|
+
return candidate.trim();
|
|
956
|
+
}
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
if (token.startsWith("--type=")) {
|
|
960
|
+
const candidate = token.slice("--type=".length).trim();
|
|
961
|
+
if (candidate.length > 0) {
|
|
962
|
+
return candidate;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return undefined;
|
|
967
|
+
}
|
|
968
|
+
function buildCreateUpdatePolicyHelpText(commandName, typeRegistry, argv) {
|
|
969
|
+
const selectedTypeRaw = parseBootstrapTypeValue(argv);
|
|
970
|
+
if (!selectedTypeRaw) {
|
|
971
|
+
const allowed = typeRegistry.types.join("|");
|
|
972
|
+
const lines = [
|
|
973
|
+
"",
|
|
974
|
+
"Type-aware option policies:",
|
|
975
|
+
" pass --type <value> with --help to render required/disabled/hidden option policy details for that type.",
|
|
976
|
+
` active type values: ${allowed}`,
|
|
977
|
+
];
|
|
978
|
+
if (commandName === "create") {
|
|
979
|
+
lines.push(" scheduling shortcut: use --schedule-preset lightweight for Reminder/Meeting/Event minimal create flows.");
|
|
980
|
+
}
|
|
981
|
+
return lines.join("\n");
|
|
982
|
+
}
|
|
983
|
+
const typeDefinition = resolveTypeDefinition(selectedTypeRaw, typeRegistry);
|
|
984
|
+
if (!typeDefinition) {
|
|
985
|
+
const allowed = typeRegistry.types.join("|");
|
|
986
|
+
return [
|
|
987
|
+
"",
|
|
988
|
+
`Type-aware option policies: type "${selectedTypeRaw}" is not in the active registry.`,
|
|
989
|
+
` active type values: ${allowed}`,
|
|
990
|
+
].join("\n");
|
|
991
|
+
}
|
|
992
|
+
const baseRequired = commandName === "create"
|
|
993
|
+
? new Set(["title", "description", "type", ...typeDefinition.required_create_fields, ...typeDefinition.required_create_repeatables])
|
|
994
|
+
: new Set();
|
|
995
|
+
const policyState = resolveCommandOptionPolicyState(typeDefinition, commandName, baseRequired);
|
|
996
|
+
const toFlags = (options) => options.length > 0 ? options.map((option) => commandOptionFlagLabel(commandName, option)).join(", ") : "none";
|
|
997
|
+
const lines = [
|
|
998
|
+
"",
|
|
999
|
+
`Type-aware option policies for ${typeDefinition.name}:`,
|
|
1000
|
+
` required: ${toFlags(policyState.required)}`,
|
|
1001
|
+
` disabled: ${toFlags(policyState.disabled)}`,
|
|
1002
|
+
` hidden: ${toFlags(policyState.hidden)}`,
|
|
1003
|
+
];
|
|
1004
|
+
if (commandName === "create" && ["Reminder", "Meeting", "Event"].includes(typeDefinition.name)) {
|
|
1005
|
+
lines.push(" schedule preset: --schedule-preset lightweight switches schedule artifacts to progressive required-option policy.");
|
|
1006
|
+
lines.push(" strict parity remains available via --create-mode strict.");
|
|
1007
|
+
}
|
|
1008
|
+
if (typeDefinition.options.length === 0) {
|
|
1009
|
+
lines.push(" type options: none");
|
|
1010
|
+
}
|
|
1011
|
+
else {
|
|
1012
|
+
lines.push(" type options:");
|
|
1013
|
+
for (const option of typeDefinition.options) {
|
|
1014
|
+
const requiredLabel = option.required ? " (required)" : "";
|
|
1015
|
+
const aliases = option.aliases ?? [];
|
|
1016
|
+
lines.push(` - ${option.key}${requiredLabel}`);
|
|
1017
|
+
lines.push(` values: ${option.values.length > 0 ? option.values.join("|") : "any non-empty string"}`);
|
|
1018
|
+
lines.push(` aliases: ${aliases.length > 0 ? aliases.join("|") : "none"}`);
|
|
1019
|
+
if (option.description && option.description.trim().length > 0) {
|
|
1020
|
+
lines.push(` description: ${option.description.trim()}`);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (policyState.errors.length > 0) {
|
|
1025
|
+
lines.push(` config errors: ${policyState.errors.join("; ")}`);
|
|
1026
|
+
}
|
|
1027
|
+
return lines.join("\n");
|
|
1028
|
+
}
|
|
1029
|
+
function attachCreateUpdatePolicyHelpText(rootProgram, typeRegistry, argv) {
|
|
1030
|
+
const bootstrapCommand = parseBootstrapCommandName(argv);
|
|
1031
|
+
if (bootstrapCommand !== "create" && bootstrapCommand !== "update") {
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
const command = findDirectChildCommand(rootProgram, bootstrapCommand);
|
|
1035
|
+
if (!command) {
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
command.addHelpText("after", buildCreateUpdatePolicyHelpText(bootstrapCommand, typeRegistry, argv));
|
|
1039
|
+
}
|
|
193
1040
|
let activeExtensionHookContext = null;
|
|
1041
|
+
let activeTelemetryCommandContext = null;
|
|
1042
|
+
let runtimeExtensionSnapshotCache = null;
|
|
1043
|
+
let activeRuntimeExtensionCommandDescriptors = new Map();
|
|
194
1044
|
function formatHookWarnings(warnings) {
|
|
195
1045
|
return warnings.join(",");
|
|
196
1046
|
}
|
|
@@ -273,6 +1123,8 @@ function decideWriteGate(commandPath, options) {
|
|
|
273
1123
|
forceRequested,
|
|
274
1124
|
};
|
|
275
1125
|
case "comments":
|
|
1126
|
+
case "notes":
|
|
1127
|
+
case "learnings":
|
|
276
1128
|
return {
|
|
277
1129
|
isMutation: typeof options.add === "string",
|
|
278
1130
|
forceCapable: true,
|
|
@@ -311,11 +1163,34 @@ function enforceMandatoryMigrationWriteGate(commandPath, options, blockers) {
|
|
|
311
1163
|
: "This command path does not support --force bypass.";
|
|
312
1164
|
throw new PmCliError(`Write command "${commandPath}" blocked by unresolved mandatory extension migrations (${codes.join(",")}). ${forceGuidance}`, EXIT_CODE.CONFLICT);
|
|
313
1165
|
}
|
|
314
|
-
function
|
|
315
|
-
|
|
316
|
-
|
|
1166
|
+
async function enforceItemFormatWriteGateAndPreflightMigration(commandPath, options, pmRoot, decision) {
|
|
1167
|
+
const writeGate = decideWriteGate(commandPath, options);
|
|
1168
|
+
if (!writeGate.isMutation) {
|
|
1169
|
+
return;
|
|
317
1170
|
}
|
|
318
|
-
if (
|
|
1171
|
+
if (!decision.enforce_item_format_gate && !decision.run_preflight_item_format_sync) {
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
if (!(await pathExists(getSettingsPath(pmRoot)))) {
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
const { settings, metadata, warnings } = await readSettingsWithMetadata(pmRoot);
|
|
1178
|
+
for (const warning of warnings) {
|
|
1179
|
+
printError(`warning:${warning}`);
|
|
1180
|
+
}
|
|
1181
|
+
if (decision.enforce_item_format_gate && !metadata.has_explicit_item_format) {
|
|
1182
|
+
await writeSettings(pmRoot, settings, "item_format:auto_select_default");
|
|
1183
|
+
}
|
|
1184
|
+
if (decision.run_preflight_item_format_sync) {
|
|
1185
|
+
const typeRegistry = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations());
|
|
1186
|
+
await migrateItemFilesToFormat(pmRoot, settings.item_format, "item_format:pre_mutation_sync", typeRegistry.type_to_folder, settings.schema);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
function describeUnknownError(error) {
|
|
1190
|
+
if (error instanceof PmCliError) {
|
|
1191
|
+
return error.message;
|
|
1192
|
+
}
|
|
1193
|
+
if (error instanceof Error) {
|
|
319
1194
|
return error.message;
|
|
320
1195
|
}
|
|
321
1196
|
return "Unknown failure";
|
|
@@ -366,31 +1241,221 @@ async function invalidateSearchCachesForMutation(globalOptions, result) {
|
|
|
366
1241
|
}
|
|
367
1242
|
}
|
|
368
1243
|
async function runAndClearAfterCommandHooks(outcome) {
|
|
1244
|
+
const telemetryRuntime = activeTelemetryCommandContext;
|
|
1245
|
+
activeTelemetryCommandContext = null;
|
|
1246
|
+
await finishTelemetryCommand(telemetryRuntime, {
|
|
1247
|
+
ok: outcome.ok,
|
|
1248
|
+
error: outcome.error,
|
|
1249
|
+
result: getActiveCommandResult(),
|
|
1250
|
+
});
|
|
369
1251
|
const runtime = activeExtensionHookContext;
|
|
370
1252
|
activeExtensionHookContext = null;
|
|
371
|
-
clearActiveExtensionHooks();
|
|
372
1253
|
if (!runtime) {
|
|
1254
|
+
clearActiveExtensionHooks();
|
|
373
1255
|
return;
|
|
374
1256
|
}
|
|
375
1257
|
const hookWarnings = await runAfterCommandHooks(runtime.hooks, {
|
|
376
1258
|
command: runtime.commandName,
|
|
377
1259
|
args: runtime.commandArgs,
|
|
1260
|
+
options: { ...runtime.commandOptions },
|
|
1261
|
+
global: { ...runtime.globalOptions },
|
|
378
1262
|
pm_root: runtime.pmRoot,
|
|
379
1263
|
ok: outcome.ok,
|
|
380
1264
|
error: outcome.error,
|
|
1265
|
+
result: getActiveCommandResult(),
|
|
381
1266
|
});
|
|
1267
|
+
clearActiveExtensionHooks();
|
|
382
1268
|
if (runtime.profileEnabled && hookWarnings.length > 0) {
|
|
383
1269
|
printError(`profile:extensions hook_warnings=${formatHookWarnings(hookWarnings)}`);
|
|
384
1270
|
}
|
|
385
1271
|
}
|
|
386
|
-
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
1272
|
+
function extractCommandScopedOptions(command, commandArgs, extensionFlagDefinitions = []) {
|
|
1273
|
+
const allOptions = command.optsWithGlobals();
|
|
1274
|
+
const scoped = { ...allOptions };
|
|
1275
|
+
delete scoped.json;
|
|
1276
|
+
delete scoped.quiet;
|
|
1277
|
+
delete scoped.path;
|
|
1278
|
+
delete scoped.noExtensions;
|
|
1279
|
+
delete scoped.extensions;
|
|
1280
|
+
delete scoped.profile;
|
|
1281
|
+
delete scoped.pager;
|
|
1282
|
+
const looseOptions = parseLooseCommandOptions(commandArgs);
|
|
1283
|
+
for (const [key, value] of Object.entries(looseOptions)) {
|
|
1284
|
+
if (scoped[key] === undefined) {
|
|
1285
|
+
scoped[key] = value;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
if (extensionFlagDefinitions.length > 0) {
|
|
1289
|
+
return coerceLooseCommandOptionsWithFlagDefinitions(scoped, extensionFlagDefinitions);
|
|
1290
|
+
}
|
|
1291
|
+
return scoped;
|
|
1292
|
+
}
|
|
1293
|
+
function collectExtensionFlagDefinitionsForCommand(registrations, commandPath) {
|
|
1294
|
+
const normalizedCommandPath = normalizeExtensionCommandPath(commandPath);
|
|
1295
|
+
if (normalizedCommandPath.length === 0) {
|
|
1296
|
+
return [];
|
|
1297
|
+
}
|
|
1298
|
+
return registrations.flags
|
|
1299
|
+
.filter((entry) => normalizeExtensionCommandPath(entry.target_command) === normalizedCommandPath)
|
|
1300
|
+
.flatMap((entry) => entry.flags);
|
|
1301
|
+
}
|
|
1302
|
+
const RUNTIME_FIELD_COMMAND_BY_COMMAND_PATH = {
|
|
1303
|
+
create: "create",
|
|
1304
|
+
update: "update",
|
|
1305
|
+
"update-many": "update_many",
|
|
1306
|
+
list: "list",
|
|
1307
|
+
"list-all": "list",
|
|
1308
|
+
"list-draft": "list",
|
|
1309
|
+
"list-open": "list",
|
|
1310
|
+
"list-in-progress": "list",
|
|
1311
|
+
"list-blocked": "list",
|
|
1312
|
+
"list-closed": "list",
|
|
1313
|
+
"list-canceled": "list",
|
|
1314
|
+
search: "search",
|
|
1315
|
+
calendar: "calendar",
|
|
1316
|
+
context: "context",
|
|
1317
|
+
"templates save": "create",
|
|
1318
|
+
};
|
|
1319
|
+
const runtimeFieldLooseFlagDefinitionCache = new Map();
|
|
1320
|
+
function toLooseFieldDefinitionType(fieldType) {
|
|
1321
|
+
if (fieldType === "number") {
|
|
1322
|
+
return "number";
|
|
1323
|
+
}
|
|
1324
|
+
if (fieldType === "boolean") {
|
|
1325
|
+
return "boolean";
|
|
1326
|
+
}
|
|
1327
|
+
return "string";
|
|
1328
|
+
}
|
|
1329
|
+
function commandHasLongOption(command, longFlag) {
|
|
1330
|
+
return command.options.some((option) => option.long === longFlag);
|
|
1331
|
+
}
|
|
1332
|
+
function commandHasShortOption(command, shortFlag) {
|
|
1333
|
+
return command.options.some((option) => option.short === shortFlag);
|
|
1334
|
+
}
|
|
1335
|
+
function addRuntimeFieldOption(command, flagToken, description, repeatable) {
|
|
1336
|
+
const normalizedToken = flagToken.trim();
|
|
1337
|
+
if (!normalizedToken) {
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
const helpText = description.length > 0 ? description : `Runtime schema field (${flagToken})`;
|
|
1341
|
+
if (normalizedToken.startsWith("-") && !normalizedToken.startsWith("--")) {
|
|
1342
|
+
if (commandHasShortOption(command, normalizedToken)) {
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
if (repeatable) {
|
|
1346
|
+
command.option(`${normalizedToken} <value>`, `${helpText} (repeatable)`, collect);
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
command.option(`${normalizedToken} <value>`, helpText);
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
const longFlag = normalizedToken.startsWith("--") ? normalizedToken : `--${normalizedToken}`;
|
|
1353
|
+
if (commandHasLongOption(command, longFlag)) {
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
if (repeatable) {
|
|
1357
|
+
command.option(`${longFlag} <value>`, `${helpText} (repeatable)`, collect);
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
command.option(`${longFlag} <value>`, helpText);
|
|
1361
|
+
}
|
|
1362
|
+
async function collectRuntimeFieldLooseFlagDefinitionsForCommand(commandPath, pmRoot) {
|
|
1363
|
+
const runtimeCommand = RUNTIME_FIELD_COMMAND_BY_COMMAND_PATH[commandPath];
|
|
1364
|
+
if (!runtimeCommand) {
|
|
1365
|
+
return [];
|
|
1366
|
+
}
|
|
1367
|
+
const cacheKey = `${pmRoot}:${runtimeCommand}`;
|
|
1368
|
+
const cached = runtimeFieldLooseFlagDefinitionCache.get(cacheKey);
|
|
1369
|
+
if (cached) {
|
|
1370
|
+
return cached;
|
|
1371
|
+
}
|
|
1372
|
+
if (!(await pathExists(getSettingsPath(pmRoot)))) {
|
|
1373
|
+
runtimeFieldLooseFlagDefinitionCache.set(cacheKey, []);
|
|
1374
|
+
return [];
|
|
1375
|
+
}
|
|
1376
|
+
const settings = await readSettings(pmRoot);
|
|
1377
|
+
const fieldRegistry = resolveRuntimeFieldRegistry(settings.schema);
|
|
1378
|
+
const definitions = (fieldRegistry.command_to_fields.get(runtimeCommand) ?? []).flatMap((field) => {
|
|
1379
|
+
const flagTokens = [field.cli_flag, ...field.cli_aliases];
|
|
1380
|
+
return flagTokens.map((token) => ({
|
|
1381
|
+
long: `--${token}`,
|
|
1382
|
+
type: toLooseFieldDefinitionType(field.type),
|
|
1383
|
+
value_type: toLooseFieldDefinitionType(field.type),
|
|
1384
|
+
}));
|
|
1385
|
+
});
|
|
1386
|
+
runtimeFieldLooseFlagDefinitionCache.set(cacheKey, definitions);
|
|
1387
|
+
return definitions;
|
|
1388
|
+
}
|
|
1389
|
+
async function registerRuntimeSchemaFieldFlags(rootProgram) {
|
|
1390
|
+
const bootstrapGlobalOptions = parseBootstrapGlobalOptions(process.argv.slice(2));
|
|
1391
|
+
const pmRoot = resolvePmRoot(process.cwd(), bootstrapGlobalOptions.path);
|
|
1392
|
+
if (!(await pathExists(getSettingsPath(pmRoot)))) {
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
const settings = await readSettings(pmRoot);
|
|
1396
|
+
const fieldRegistry = resolveRuntimeFieldRegistry(settings.schema);
|
|
1397
|
+
const mappings = [
|
|
1398
|
+
{ path: "create", command: "create" },
|
|
1399
|
+
{ path: "update", command: "update" },
|
|
1400
|
+
{ path: "update-many", command: "update_many" },
|
|
1401
|
+
{ path: "list", command: "list" },
|
|
1402
|
+
{ path: "list-all", command: "list" },
|
|
1403
|
+
{ path: "list-draft", command: "list" },
|
|
1404
|
+
{ path: "list-open", command: "list" },
|
|
1405
|
+
{ path: "list-in-progress", command: "list" },
|
|
1406
|
+
{ path: "list-blocked", command: "list" },
|
|
1407
|
+
{ path: "list-closed", command: "list" },
|
|
1408
|
+
{ path: "list-canceled", command: "list" },
|
|
1409
|
+
{ path: "search", command: "search" },
|
|
1410
|
+
{ path: "calendar", command: "calendar" },
|
|
1411
|
+
{ path: "context", command: "context" },
|
|
1412
|
+
{ path: "templates save", command: "create" },
|
|
1413
|
+
];
|
|
1414
|
+
for (const mapping of mappings) {
|
|
1415
|
+
const command = findCommandByPath(rootProgram, mapping.path.split(" "));
|
|
1416
|
+
if (!command) {
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
for (const field of fieldRegistry.command_to_fields.get(mapping.command) ?? []) {
|
|
1420
|
+
const description = field.description ?? "";
|
|
1421
|
+
addRuntimeFieldOption(command, field.cli_flag, description, field.repeatable);
|
|
1422
|
+
for (const alias of field.cli_aliases) {
|
|
1423
|
+
addRuntimeFieldOption(command, alias, `Alias for --${field.cli_flag}`, field.repeatable);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
function defaultPreflightDecision() {
|
|
1429
|
+
return {
|
|
1430
|
+
enforce_item_format_gate: true,
|
|
1431
|
+
run_preflight_item_format_sync: true,
|
|
1432
|
+
run_extension_migrations: true,
|
|
1433
|
+
enforce_mandatory_migration_gate: true,
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
function buildRuntimeExtensionSnapshotCacheKey(pmRoot) {
|
|
1437
|
+
return `pm-root:${pmRoot}`;
|
|
1438
|
+
}
|
|
1439
|
+
function emitExtensionProfile(globalOptions, snapshot) {
|
|
1440
|
+
if (!globalOptions.profile) {
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
printError(`profile:extensions loaded=${snapshot.loadedCount} failed=${snapshot.loadFailedCount} warnings=${snapshot.loadWarnings.length} activation_failed=${snapshot.activationFailedCount} hook_counts=before:${snapshot.hooks.beforeCommand.length}|after:${snapshot.hooks.afterCommand.length}|write:${snapshot.hooks.onWrite.length}|read:${snapshot.hooks.onRead.length}|index:${snapshot.hooks.onIndex.length} command_overrides=${snapshot.commands.overrides.length} command_handlers=${snapshot.commands.handlers.length} parser_overrides=${snapshot.parsers.overrides.length} preflight_overrides=${snapshot.preflight.overrides.length} service_overrides=${snapshot.services.overrides.length} renderer_overrides=${snapshot.renderers.overrides.length}`);
|
|
1444
|
+
if (snapshot.activationWarnings.length > 0) {
|
|
1445
|
+
printError(`profile:extensions activation_warnings=${formatHookWarnings(snapshot.activationWarnings)}`);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
async function loadRuntimeExtensionSnapshot(pmRoot) {
|
|
1449
|
+
const cacheKey = buildRuntimeExtensionSnapshotCacheKey(pmRoot);
|
|
1450
|
+
if (runtimeExtensionSnapshotCache?.key === cacheKey) {
|
|
1451
|
+
return runtimeExtensionSnapshotCache.snapshot;
|
|
390
1452
|
}
|
|
391
|
-
const pmRoot = resolvePmRoot(process.cwd(), globalOptions.path);
|
|
392
1453
|
const settingsPath = getSettingsPath(pmRoot);
|
|
393
1454
|
if (!(await pathExists(settingsPath))) {
|
|
1455
|
+
runtimeExtensionSnapshotCache = {
|
|
1456
|
+
key: cacheKey,
|
|
1457
|
+
snapshot: null,
|
|
1458
|
+
};
|
|
394
1459
|
return null;
|
|
395
1460
|
}
|
|
396
1461
|
try {
|
|
@@ -399,61 +1464,143 @@ async function maybeLoadRuntimeExtensions(command) {
|
|
|
399
1464
|
pmRoot,
|
|
400
1465
|
settings,
|
|
401
1466
|
cwd: process.cwd(),
|
|
402
|
-
noExtensions:
|
|
1467
|
+
noExtensions: false,
|
|
403
1468
|
});
|
|
404
|
-
const loadedWithBuiltins = [...getEnabledBuiltInExtensions(settings), ...loadResult.loaded];
|
|
405
1469
|
const activationResult = await activateExtensions({
|
|
406
1470
|
...loadResult,
|
|
407
|
-
loaded:
|
|
1471
|
+
loaded: loadResult.loaded,
|
|
408
1472
|
});
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const migrationBlockers = collectMandatoryMigrationBlockers(activationResult.registrations.migrations);
|
|
416
|
-
return {
|
|
1473
|
+
const commandHandlers = [...new Set(activationResult.commands.handlers.map((entry) => normalizeExtensionCommandPath(entry.command)))]
|
|
1474
|
+
.filter((entry) => entry.length > 0)
|
|
1475
|
+
.sort((left, right) => left.localeCompare(right));
|
|
1476
|
+
const commandFlagHelp = collectDynamicExtensionFlagHelpByCommand(activationResult.registrations.flags);
|
|
1477
|
+
const commandDescriptors = collectExtensionCommandHelpDescriptors(commandHandlers, activationResult.registrations.commands, activationResult.registrations.flags);
|
|
1478
|
+
const snapshot = {
|
|
417
1479
|
hooks: activationResult.hooks,
|
|
418
1480
|
commands: activationResult.commands,
|
|
1481
|
+
parsers: activationResult.parsers,
|
|
1482
|
+
preflight: activationResult.preflight,
|
|
1483
|
+
services: activationResult.services,
|
|
419
1484
|
renderers: activationResult.renderers,
|
|
1485
|
+
registrations: activationResult.registrations,
|
|
420
1486
|
pmRoot,
|
|
421
|
-
|
|
1487
|
+
settings,
|
|
1488
|
+
commandHandlers,
|
|
1489
|
+
commandFlagHelp,
|
|
1490
|
+
commandDescriptors,
|
|
1491
|
+
loadWarnings: [...loadResult.warnings],
|
|
1492
|
+
activationWarnings: [...activationResult.warnings],
|
|
1493
|
+
loadedCount: loadResult.loaded.length,
|
|
1494
|
+
loadFailedCount: loadResult.failed.length,
|
|
1495
|
+
activationFailedCount: activationResult.failed.length,
|
|
422
1496
|
};
|
|
1497
|
+
runtimeExtensionSnapshotCache = {
|
|
1498
|
+
key: cacheKey,
|
|
1499
|
+
snapshot,
|
|
1500
|
+
};
|
|
1501
|
+
return snapshot;
|
|
423
1502
|
}
|
|
424
|
-
catch
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
return {
|
|
430
|
-
hooks: createEmptyExtensionHookRegistry(),
|
|
431
|
-
commands: createEmptyExtensionCommandRegistry(),
|
|
432
|
-
renderers: createEmptyExtensionRendererRegistry(),
|
|
433
|
-
pmRoot,
|
|
434
|
-
migrationBlockers: [],
|
|
1503
|
+
catch {
|
|
1504
|
+
runtimeExtensionSnapshotCache = {
|
|
1505
|
+
key: cacheKey,
|
|
1506
|
+
snapshot: null,
|
|
435
1507
|
};
|
|
1508
|
+
return null;
|
|
436
1509
|
}
|
|
437
1510
|
}
|
|
1511
|
+
async function maybeLoadRuntimeExtensions(command) {
|
|
1512
|
+
const globalOptions = getGlobalOptions(command);
|
|
1513
|
+
if (globalOptions.noExtensions) {
|
|
1514
|
+
return null;
|
|
1515
|
+
}
|
|
1516
|
+
const pmRoot = resolvePmRoot(process.cwd(), globalOptions.path);
|
|
1517
|
+
const snapshot = await loadRuntimeExtensionSnapshot(pmRoot);
|
|
1518
|
+
if (!snapshot) {
|
|
1519
|
+
return null;
|
|
1520
|
+
}
|
|
1521
|
+
emitExtensionProfile(globalOptions, snapshot);
|
|
1522
|
+
return {
|
|
1523
|
+
hooks: snapshot.hooks,
|
|
1524
|
+
commands: snapshot.commands,
|
|
1525
|
+
parsers: snapshot.parsers,
|
|
1526
|
+
preflight: snapshot.preflight,
|
|
1527
|
+
services: snapshot.services,
|
|
1528
|
+
renderers: snapshot.renderers,
|
|
1529
|
+
registrations: snapshot.registrations,
|
|
1530
|
+
pmRoot,
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
async function executeRegisteredRuntimeMigrations(migrations, pmRoot) {
|
|
1534
|
+
const warnings = [];
|
|
1535
|
+
for (let index = 0; index < migrations.length; index += 1) {
|
|
1536
|
+
const migration = migrations[index];
|
|
1537
|
+
const status = resolveNormalizedMigrationStatus(migration.definition);
|
|
1538
|
+
if (status === "applied") {
|
|
1539
|
+
continue;
|
|
1540
|
+
}
|
|
1541
|
+
const runtimeDefinition = migration.runtime_definition ?? migration.definition;
|
|
1542
|
+
const run = runtimeDefinition.run;
|
|
1543
|
+
if (typeof run !== "function") {
|
|
1544
|
+
continue;
|
|
1545
|
+
}
|
|
1546
|
+
const migrationId = resolveMigrationId(migration.definition, index);
|
|
1547
|
+
try {
|
|
1548
|
+
await Promise.resolve(run({
|
|
1549
|
+
id: migrationId,
|
|
1550
|
+
command: "migration",
|
|
1551
|
+
layer: migration.layer,
|
|
1552
|
+
extension: migration.name,
|
|
1553
|
+
pm_root: pmRoot,
|
|
1554
|
+
status,
|
|
1555
|
+
}));
|
|
1556
|
+
migration.definition.status = "applied";
|
|
1557
|
+
delete migration.definition.reason;
|
|
1558
|
+
delete migration.definition.error;
|
|
1559
|
+
delete migration.definition.message;
|
|
1560
|
+
}
|
|
1561
|
+
catch (error) {
|
|
1562
|
+
migration.definition.status = "failed";
|
|
1563
|
+
migration.definition.reason = describeUnknownError(error);
|
|
1564
|
+
warnings.push(`extension_migration_failed:${migration.layer}:${migration.name}:${migrationId}`);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
return warnings;
|
|
1568
|
+
}
|
|
438
1569
|
async function runRequiredExtensionCommand(command, options, globalOptions) {
|
|
439
1570
|
const commandPath = getCommandPath(command);
|
|
440
|
-
|
|
1571
|
+
let commandArgs = command.args.map(String);
|
|
1572
|
+
let commandOptions = { ...options };
|
|
1573
|
+
let resolvedGlobalOptions = { ...globalOptions };
|
|
441
1574
|
const pmRoot = resolvePmRoot(process.cwd(), globalOptions.path);
|
|
1575
|
+
const parserOverride = await runActiveParserOverride({
|
|
1576
|
+
command: commandPath,
|
|
1577
|
+
args: commandArgs,
|
|
1578
|
+
options: commandOptions,
|
|
1579
|
+
global: resolvedGlobalOptions,
|
|
1580
|
+
pm_root: pmRoot,
|
|
1581
|
+
});
|
|
1582
|
+
if (globalOptions.profile && parserOverride.warnings.length > 0) {
|
|
1583
|
+
printError(`profile:extensions parser_warnings=${formatHookWarnings(parserOverride.warnings)}`);
|
|
1584
|
+
}
|
|
1585
|
+
commandArgs = parserOverride.context.args;
|
|
1586
|
+
commandOptions = parserOverride.context.options;
|
|
1587
|
+
resolvedGlobalOptions = parserOverride.context.global;
|
|
1588
|
+
setActiveCommandResult(undefined);
|
|
442
1589
|
setActiveCommandContext({
|
|
443
1590
|
command: commandPath,
|
|
444
1591
|
args: commandArgs,
|
|
445
|
-
options: { ...
|
|
446
|
-
global: { ...
|
|
1592
|
+
options: { ...commandOptions },
|
|
1593
|
+
global: { ...resolvedGlobalOptions },
|
|
447
1594
|
pm_root: pmRoot,
|
|
448
1595
|
});
|
|
449
1596
|
const extensionCommandResult = await runActiveCommandHandler({
|
|
450
1597
|
command: commandPath,
|
|
451
1598
|
args: commandArgs,
|
|
452
|
-
options,
|
|
453
|
-
global:
|
|
1599
|
+
options: commandOptions,
|
|
1600
|
+
global: resolvedGlobalOptions,
|
|
454
1601
|
pm_root: pmRoot,
|
|
455
1602
|
});
|
|
456
|
-
if (
|
|
1603
|
+
if (resolvedGlobalOptions.profile && extensionCommandResult.warnings.length > 0) {
|
|
457
1604
|
printError(`profile:extensions command_handler_warnings=${formatHookWarnings(extensionCommandResult.warnings)}`);
|
|
458
1605
|
}
|
|
459
1606
|
if (!extensionCommandResult.handled) {
|
|
@@ -463,71 +1610,159 @@ async function runRequiredExtensionCommand(command, options, globalOptions) {
|
|
|
463
1610
|
}
|
|
464
1611
|
throw new PmCliError(`Command "${commandPath}" is provided by extensions and is not currently available.`, EXIT_CODE.NOT_FOUND);
|
|
465
1612
|
}
|
|
1613
|
+
setActiveCommandResult(extensionCommandResult.result);
|
|
466
1614
|
return extensionCommandResult.result;
|
|
467
1615
|
}
|
|
1616
|
+
const WRAPPED_ACTION_HANDLER = Symbol("pm.wrappedActionHandler");
|
|
1617
|
+
function wrapProgramActionsForExtensionHandlers(rootProgram) {
|
|
1618
|
+
const visit = (entry) => {
|
|
1619
|
+
const actionEntry = entry;
|
|
1620
|
+
if (typeof actionEntry._actionHandler === "function" && actionEntry[WRAPPED_ACTION_HANDLER] !== true) {
|
|
1621
|
+
const originalAction = actionEntry._actionHandler;
|
|
1622
|
+
actionEntry._actionHandler = async function wrappedActionHandler(...actionArgs) {
|
|
1623
|
+
const possibleCommand = actionArgs[actionArgs.length - 1];
|
|
1624
|
+
const actionCommand = possibleCommand instanceof Command ? possibleCommand : entry;
|
|
1625
|
+
const startedAt = Date.now();
|
|
1626
|
+
clearResolvedGlobalOptions(actionCommand);
|
|
1627
|
+
let globalOptions = getGlobalOptions(actionCommand);
|
|
1628
|
+
const commandPath = getCommandPath(actionCommand);
|
|
1629
|
+
const pmRoot = resolvePmRoot(process.cwd(), globalOptions.path);
|
|
1630
|
+
let commandArgs = actionCommand.args.map(String);
|
|
1631
|
+
const activeRegistrations = getActiveExtensionRegistrations();
|
|
1632
|
+
const extensionFlagDefinitions = activeRegistrations
|
|
1633
|
+
? collectExtensionFlagDefinitionsForCommand(activeRegistrations, commandPath)
|
|
1634
|
+
: [];
|
|
1635
|
+
const runtimeFieldFlagDefinitions = await collectRuntimeFieldLooseFlagDefinitionsForCommand(commandPath, pmRoot);
|
|
1636
|
+
let commandOptions = extractCommandScopedOptions(actionCommand, commandArgs, [
|
|
1637
|
+
...extensionFlagDefinitions,
|
|
1638
|
+
...runtimeFieldFlagDefinitions,
|
|
1639
|
+
]);
|
|
1640
|
+
const parserOverride = await runActiveParserOverride({
|
|
1641
|
+
command: commandPath,
|
|
1642
|
+
args: commandArgs,
|
|
1643
|
+
options: commandOptions,
|
|
1644
|
+
global: globalOptions,
|
|
1645
|
+
pm_root: pmRoot,
|
|
1646
|
+
});
|
|
1647
|
+
if (globalOptions.profile && parserOverride.warnings.length > 0) {
|
|
1648
|
+
printError(`profile:extensions parser_warnings=${formatHookWarnings(parserOverride.warnings)}`);
|
|
1649
|
+
}
|
|
1650
|
+
commandArgs = parserOverride.context.args;
|
|
1651
|
+
commandOptions = parserOverride.context.options;
|
|
1652
|
+
globalOptions = parserOverride.context.global;
|
|
1653
|
+
globalOptions = await applyDefaultOutputFormat(globalOptions);
|
|
1654
|
+
setResolvedGlobalOptions(actionCommand, globalOptions);
|
|
1655
|
+
actionCommand.args = [...commandArgs];
|
|
1656
|
+
if ("_processArguments" in actionCommand && typeof actionCommand._processArguments === "function") {
|
|
1657
|
+
actionCommand._processArguments();
|
|
1658
|
+
}
|
|
1659
|
+
if (actionArgs.length > 0 && Array.isArray(actionArgs[0])) {
|
|
1660
|
+
actionArgs[0] = [...actionCommand.processedArgs];
|
|
1661
|
+
}
|
|
1662
|
+
for (const [key, value] of Object.entries(commandOptions)) {
|
|
1663
|
+
actionCommand.setOptionValueWithSource(key, value, "cli");
|
|
1664
|
+
}
|
|
1665
|
+
setActiveCommandResult(undefined);
|
|
1666
|
+
setActiveCommandContext({
|
|
1667
|
+
command: commandPath,
|
|
1668
|
+
args: commandArgs,
|
|
1669
|
+
options: { ...commandOptions },
|
|
1670
|
+
global: { ...globalOptions },
|
|
1671
|
+
pm_root: pmRoot,
|
|
1672
|
+
});
|
|
1673
|
+
const extensionCommandResult = await runActiveCommandHandler({
|
|
1674
|
+
command: commandPath,
|
|
1675
|
+
args: commandArgs,
|
|
1676
|
+
options: commandOptions,
|
|
1677
|
+
global: globalOptions,
|
|
1678
|
+
pm_root: pmRoot,
|
|
1679
|
+
});
|
|
1680
|
+
if (globalOptions.profile && extensionCommandResult.warnings.length > 0) {
|
|
1681
|
+
printError(`profile:extensions command_handler_warnings=${formatHookWarnings(extensionCommandResult.warnings)}`);
|
|
1682
|
+
}
|
|
1683
|
+
if (extensionCommandResult.handled) {
|
|
1684
|
+
setActiveCommandResult(extensionCommandResult.result);
|
|
1685
|
+
printResult(extensionCommandResult.result, globalOptions);
|
|
1686
|
+
if (globalOptions.profile) {
|
|
1687
|
+
printError(`profile:command=${commandPath} took_ms=${Date.now() - startedAt}`);
|
|
1688
|
+
}
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
return await originalAction.apply(this, actionArgs);
|
|
1692
|
+
};
|
|
1693
|
+
actionEntry[WRAPPED_ACTION_HANDLER] = true;
|
|
1694
|
+
}
|
|
1695
|
+
for (const child of entry.commands) {
|
|
1696
|
+
visit(child);
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
visit(rootProgram);
|
|
1700
|
+
}
|
|
468
1701
|
async function registerDynamicExtensionCommandPaths(rootProgram) {
|
|
469
1702
|
const bootstrapGlobalOptions = parseBootstrapGlobalOptions(process.argv.slice(2));
|
|
470
1703
|
if (bootstrapGlobalOptions.noExtensions) {
|
|
1704
|
+
activeRuntimeExtensionCommandDescriptors = new Map();
|
|
1705
|
+
setActiveExtensionServices({ overrides: [] });
|
|
471
1706
|
return;
|
|
472
1707
|
}
|
|
473
1708
|
const pmRoot = resolvePmRoot(process.cwd(), bootstrapGlobalOptions.path);
|
|
474
|
-
const
|
|
475
|
-
if (!
|
|
1709
|
+
const snapshot = await loadRuntimeExtensionSnapshot(pmRoot);
|
|
1710
|
+
if (!snapshot) {
|
|
1711
|
+
activeRuntimeExtensionCommandDescriptors = new Map();
|
|
1712
|
+
setActiveExtensionServices({ overrides: [] });
|
|
476
1713
|
return;
|
|
477
1714
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
let commandFlagHelp;
|
|
487
|
-
try {
|
|
488
|
-
const loadResult = await loadExtensions({
|
|
489
|
-
pmRoot,
|
|
490
|
-
settings,
|
|
491
|
-
cwd: process.cwd(),
|
|
492
|
-
noExtensions: false,
|
|
493
|
-
});
|
|
494
|
-
const loadedWithBuiltins = [...getEnabledBuiltInExtensions(settings), ...loadResult.loaded];
|
|
495
|
-
const activationResult = await activateExtensions({
|
|
496
|
-
...loadResult,
|
|
497
|
-
loaded: loadedWithBuiltins,
|
|
498
|
-
});
|
|
499
|
-
commandHandlers = [...new Set(activationResult.commands.handlers.map((entry) => normalizeExtensionCommandPath(entry.command)))]
|
|
500
|
-
.filter((entry) => entry.length > 0)
|
|
501
|
-
.sort((left, right) => left.localeCompare(right));
|
|
502
|
-
commandFlagHelp = collectDynamicExtensionFlagHelpByCommand(activationResult.registrations.flags);
|
|
503
|
-
}
|
|
504
|
-
catch {
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
for (const commandPath of commandHandlers) {
|
|
1715
|
+
// Ensure usage/help/error formatting overrides are available even when parse
|
|
1716
|
+
// errors occur before preAction hooks initialize full runtime extension state.
|
|
1717
|
+
setActiveExtensionServices(snapshot.services);
|
|
1718
|
+
activeRuntimeExtensionCommandDescriptors = new Map(snapshot.commandDescriptors);
|
|
1719
|
+
const typeRegistry = resolveItemTypeRegistry(snapshot.settings, snapshot.registrations);
|
|
1720
|
+
attachCreateUpdatePolicyHelpText(rootProgram, typeRegistry, process.argv.slice(2));
|
|
1721
|
+
const commandPaths = [...new Set([...snapshot.commandHandlers, ...snapshot.commandDescriptors.keys()])].sort((left, right) => left.localeCompare(right));
|
|
1722
|
+
for (const commandPath of commandPaths) {
|
|
508
1723
|
const pathParts = commandPath.split(" ").filter((part) => part.length > 0);
|
|
509
1724
|
if (pathParts.length === 0) {
|
|
510
1725
|
continue;
|
|
511
1726
|
}
|
|
512
|
-
|
|
1727
|
+
const descriptor = snapshot.commandDescriptors.get(commandPath);
|
|
1728
|
+
const existingCommand = findCommandByPath(rootProgram, pathParts);
|
|
1729
|
+
const flagHelp = snapshot.commandFlagHelp.get(commandPath);
|
|
1730
|
+
const metadataHelp = descriptor ? buildDynamicExtensionCommandMetadataHelp(descriptor) : null;
|
|
1731
|
+
if (existingCommand) {
|
|
1732
|
+
if (flagHelp) {
|
|
1733
|
+
existingCommand.addHelpText("after", flagHelp);
|
|
1734
|
+
}
|
|
1735
|
+
if (metadataHelp) {
|
|
1736
|
+
existingCommand.addHelpText("after", metadataHelp);
|
|
1737
|
+
}
|
|
513
1738
|
continue;
|
|
514
1739
|
}
|
|
515
1740
|
const dynamicCommand = ensureCommandPath(rootProgram, pathParts);
|
|
516
1741
|
if (!dynamicCommand) {
|
|
517
1742
|
continue;
|
|
518
1743
|
}
|
|
519
|
-
|
|
1744
|
+
if (descriptor?.description) {
|
|
1745
|
+
dynamicCommand.description(descriptor.description);
|
|
1746
|
+
}
|
|
1747
|
+
if (descriptor) {
|
|
1748
|
+
applyDynamicExtensionArguments(dynamicCommand, descriptor);
|
|
1749
|
+
}
|
|
520
1750
|
if (flagHelp) {
|
|
521
1751
|
dynamicCommand.addHelpText("after", flagHelp);
|
|
522
1752
|
}
|
|
1753
|
+
if (metadataHelp) {
|
|
1754
|
+
dynamicCommand.addHelpText("after", metadataHelp);
|
|
1755
|
+
}
|
|
523
1756
|
dynamicCommand
|
|
524
1757
|
.allowUnknownOption(true)
|
|
525
1758
|
.allowExcessArguments(true)
|
|
526
1759
|
.action(async (_options, command) => {
|
|
527
1760
|
const globalOptions = getGlobalOptions(command);
|
|
528
1761
|
const startedAt = Date.now();
|
|
529
|
-
const
|
|
530
|
-
const
|
|
1762
|
+
const extensionFlagDefinitions = collectExtensionFlagDefinitionsForCommand(snapshot.registrations, commandPath);
|
|
1763
|
+
const scopedOptions = extractCommandScopedOptions(command, command.args.map(String), extensionFlagDefinitions);
|
|
1764
|
+
const result = await runRequiredExtensionCommand(command, scopedOptions, globalOptions);
|
|
1765
|
+
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
531
1766
|
printResult(result, globalOptions);
|
|
532
1767
|
if (globalOptions.profile) {
|
|
533
1768
|
printError(`profile:command=${commandPath} took_ms=${Date.now() - startedAt}`);
|
|
@@ -535,179 +1770,483 @@ async function registerDynamicExtensionCommandPaths(rootProgram) {
|
|
|
535
1770
|
});
|
|
536
1771
|
}
|
|
537
1772
|
}
|
|
538
|
-
function normalizeCreateOptions(commandOptions) {
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
1773
|
+
function normalizeCreateOptions(commandOptions, options = {}) {
|
|
1774
|
+
const readCreateString = (target) => readFirstStringFromCommanderOptions(commandOptions, CREATE_COMMANDER_STRING_OPTION_CONTRACTS.find((entry) => entry.target === target) ?? {
|
|
1775
|
+
target,
|
|
1776
|
+
keys: [target],
|
|
1777
|
+
});
|
|
1778
|
+
const readCreateList = (target) => readStringArrayFromCommanderOptions(commandOptions, CREATE_COMMANDER_REPEATABLE_OPTION_CONTRACTS.find((entry) => entry.target === target) ?? {
|
|
1779
|
+
target,
|
|
1780
|
+
keys: [target],
|
|
1781
|
+
});
|
|
1782
|
+
const type = readCreateString("type");
|
|
1783
|
+
if (options.requireType !== false && type === undefined) {
|
|
1784
|
+
throw new PmCliError("Missing required option --type <value>", EXIT_CODE.USAGE);
|
|
544
1785
|
}
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
1786
|
+
const normalized = {
|
|
1787
|
+
title: readCreateString("title"),
|
|
1788
|
+
description: readCreateString("description"),
|
|
1789
|
+
type,
|
|
1790
|
+
template: readCreateString("template"),
|
|
1791
|
+
createMode: readCreateString("createMode"),
|
|
1792
|
+
schedulePreset: readCreateString("schedulePreset"),
|
|
1793
|
+
status: readCreateString("status"),
|
|
1794
|
+
priority: readCreateString("priority"),
|
|
1795
|
+
tags: readCreateString("tags"),
|
|
1796
|
+
body: readCreateString("body"),
|
|
1797
|
+
deadline: readCreateString("deadline"),
|
|
1798
|
+
estimatedMinutes: readCreateString("estimatedMinutes"),
|
|
1799
|
+
acceptanceCriteria: readCreateString("acceptanceCriteria"),
|
|
1800
|
+
definitionOfReady: readCreateString("definitionOfReady"),
|
|
1801
|
+
order: readCreateString("order"),
|
|
1802
|
+
rank: readCreateString("rank"),
|
|
1803
|
+
goal: readCreateString("goal"),
|
|
1804
|
+
objective: readCreateString("objective"),
|
|
1805
|
+
value: readCreateString("value"),
|
|
1806
|
+
impact: readCreateString("impact"),
|
|
1807
|
+
outcome: readCreateString("outcome"),
|
|
1808
|
+
whyNow: readCreateString("whyNow"),
|
|
1809
|
+
author: readCreateString("author"),
|
|
1810
|
+
message: readCreateString("message"),
|
|
1811
|
+
assignee: readCreateString("assignee"),
|
|
1812
|
+
parent: readCreateString("parent"),
|
|
1813
|
+
reviewer: readCreateString("reviewer"),
|
|
1814
|
+
risk: readCreateString("risk"),
|
|
1815
|
+
confidence: readCreateString("confidence"),
|
|
1816
|
+
sprint: readCreateString("sprint"),
|
|
1817
|
+
release: readCreateString("release"),
|
|
1818
|
+
blockedBy: readCreateString("blockedBy"),
|
|
1819
|
+
blockedReason: readCreateString("blockedReason"),
|
|
1820
|
+
unblockNote: readCreateString("unblockNote"),
|
|
1821
|
+
reporter: readCreateString("reporter"),
|
|
1822
|
+
severity: readCreateString("severity"),
|
|
1823
|
+
environment: readCreateString("environment"),
|
|
1824
|
+
reproSteps: readCreateString("reproSteps"),
|
|
1825
|
+
resolution: readCreateString("resolution"),
|
|
1826
|
+
expectedResult: readCreateString("expectedResult"),
|
|
1827
|
+
actualResult: readCreateString("actualResult"),
|
|
1828
|
+
affectedVersion: readCreateString("affectedVersion"),
|
|
1829
|
+
fixedVersion: readCreateString("fixedVersion"),
|
|
1830
|
+
component: readCreateString("component"),
|
|
1831
|
+
regression: readCreateString("regression"),
|
|
1832
|
+
customerImpact: readCreateString("customerImpact"),
|
|
1833
|
+
dep: readCreateList("dep"),
|
|
1834
|
+
comment: readCreateList("comment"),
|
|
1835
|
+
note: readCreateList("note"),
|
|
1836
|
+
learning: readCreateList("learning"),
|
|
1837
|
+
file: readCreateList("file"),
|
|
1838
|
+
test: readCreateList("test"),
|
|
1839
|
+
doc: readCreateList("doc"),
|
|
1840
|
+
reminder: readCreateList("reminder"),
|
|
1841
|
+
event: readCreateList("event"),
|
|
1842
|
+
typeOption: readCreateList("typeOption"),
|
|
1843
|
+
unset: readCreateList("unset"),
|
|
1844
|
+
clearDeps: commandOptions.clearDeps === true ? true : undefined,
|
|
1845
|
+
clearComments: commandOptions.clearComments === true ? true : undefined,
|
|
1846
|
+
clearNotes: commandOptions.clearNotes === true ? true : undefined,
|
|
1847
|
+
clearLearnings: commandOptions.clearLearnings === true ? true : undefined,
|
|
1848
|
+
clearFiles: commandOptions.clearFiles === true ? true : undefined,
|
|
1849
|
+
clearTests: commandOptions.clearTests === true ? true : undefined,
|
|
1850
|
+
clearDocs: commandOptions.clearDocs === true ? true : undefined,
|
|
1851
|
+
clearReminders: commandOptions.clearReminders === true ? true : undefined,
|
|
1852
|
+
clearEvents: commandOptions.clearEvents === true ? true : undefined,
|
|
1853
|
+
clearTypeOptions: commandOptions.clearTypeOptions === true ? true : undefined,
|
|
551
1854
|
};
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
throw new PmCliError(`Missing required option ${display} (use 'none' for explicit empty)`, EXIT_CODE.USAGE);
|
|
1855
|
+
for (const [key, value] of Object.entries(commandOptions)) {
|
|
1856
|
+
if (Object.hasOwn(normalized, key)) {
|
|
1857
|
+
continue;
|
|
556
1858
|
}
|
|
557
|
-
|
|
558
|
-
}
|
|
559
|
-
return
|
|
560
|
-
title: requiredString("title", "--title"),
|
|
561
|
-
description: requiredString("description", "--description"),
|
|
562
|
-
type: requiredString("type", "--type"),
|
|
563
|
-
status: requiredString("status", "--status"),
|
|
564
|
-
priority: requiredString("priority", "--priority"),
|
|
565
|
-
tags: requiredString("tags", "--tags"),
|
|
566
|
-
body: requiredString("body", "--body"),
|
|
567
|
-
deadline: requiredString("deadline", "--deadline"),
|
|
568
|
-
estimatedMinutes,
|
|
569
|
-
acceptanceCriteria: (typeof commandOptions.acceptanceCriteria === "string" ? commandOptions.acceptanceCriteria : undefined) ??
|
|
570
|
-
(typeof commandOptions.acceptance_criteria === "string" ? commandOptions.acceptance_criteria : undefined) ??
|
|
571
|
-
requiredString("ac", "--acceptance-criteria/--ac"),
|
|
572
|
-
definitionOfReady: (typeof commandOptions.definitionOfReady === "string" ? commandOptions.definitionOfReady : undefined) ??
|
|
573
|
-
(typeof commandOptions.definition_of_ready === "string" ? commandOptions.definition_of_ready : undefined),
|
|
574
|
-
order: typeof commandOptions.order === "string" ? commandOptions.order : undefined,
|
|
575
|
-
rank: typeof commandOptions.rank === "string" ? commandOptions.rank : undefined,
|
|
576
|
-
goal: typeof commandOptions.goal === "string" ? commandOptions.goal : undefined,
|
|
577
|
-
objective: typeof commandOptions.objective === "string" ? commandOptions.objective : undefined,
|
|
578
|
-
value: typeof commandOptions.value === "string" ? commandOptions.value : undefined,
|
|
579
|
-
impact: typeof commandOptions.impact === "string" ? commandOptions.impact : undefined,
|
|
580
|
-
outcome: typeof commandOptions.outcome === "string" ? commandOptions.outcome : undefined,
|
|
581
|
-
whyNow: (typeof commandOptions.whyNow === "string" ? commandOptions.whyNow : undefined) ??
|
|
582
|
-
(typeof commandOptions.why_now === "string" ? commandOptions.why_now : undefined),
|
|
583
|
-
author: requiredString("author", "--author"),
|
|
584
|
-
message: requiredString("message", "--message"),
|
|
585
|
-
assignee: requiredString("assignee", "--assignee"),
|
|
586
|
-
parent: typeof commandOptions.parent === "string" ? commandOptions.parent : undefined,
|
|
587
|
-
reviewer: typeof commandOptions.reviewer === "string" ? commandOptions.reviewer : undefined,
|
|
588
|
-
risk: typeof commandOptions.risk === "string" ? commandOptions.risk : undefined,
|
|
589
|
-
confidence: typeof commandOptions.confidence === "string" ? commandOptions.confidence : undefined,
|
|
590
|
-
sprint: typeof commandOptions.sprint === "string" ? commandOptions.sprint : undefined,
|
|
591
|
-
release: typeof commandOptions.release === "string" ? commandOptions.release : undefined,
|
|
592
|
-
blockedBy: (typeof commandOptions.blockedBy === "string" ? commandOptions.blockedBy : undefined) ??
|
|
593
|
-
(typeof commandOptions.blocked_by === "string" ? commandOptions.blocked_by : undefined),
|
|
594
|
-
blockedReason: (typeof commandOptions.blockedReason === "string" ? commandOptions.blockedReason : undefined) ??
|
|
595
|
-
(typeof commandOptions.blocked_reason === "string" ? commandOptions.blocked_reason : undefined),
|
|
596
|
-
unblockNote: (typeof commandOptions.unblockNote === "string" ? commandOptions.unblockNote : undefined) ??
|
|
597
|
-
(typeof commandOptions.unblock_note === "string" ? commandOptions.unblock_note : undefined),
|
|
598
|
-
reporter: typeof commandOptions.reporter === "string" ? commandOptions.reporter : undefined,
|
|
599
|
-
severity: typeof commandOptions.severity === "string" ? commandOptions.severity : undefined,
|
|
600
|
-
environment: typeof commandOptions.environment === "string" ? commandOptions.environment : undefined,
|
|
601
|
-
reproSteps: (typeof commandOptions.reproSteps === "string" ? commandOptions.reproSteps : undefined) ??
|
|
602
|
-
(typeof commandOptions.repro_steps === "string" ? commandOptions.repro_steps : undefined),
|
|
603
|
-
resolution: typeof commandOptions.resolution === "string" ? commandOptions.resolution : undefined,
|
|
604
|
-
expectedResult: (typeof commandOptions.expectedResult === "string" ? commandOptions.expectedResult : undefined) ??
|
|
605
|
-
(typeof commandOptions.expected_result === "string" ? commandOptions.expected_result : undefined),
|
|
606
|
-
actualResult: (typeof commandOptions.actualResult === "string" ? commandOptions.actualResult : undefined) ??
|
|
607
|
-
(typeof commandOptions.actual_result === "string" ? commandOptions.actual_result : undefined),
|
|
608
|
-
affectedVersion: (typeof commandOptions.affectedVersion === "string" ? commandOptions.affectedVersion : undefined) ??
|
|
609
|
-
(typeof commandOptions.affected_version === "string" ? commandOptions.affected_version : undefined),
|
|
610
|
-
fixedVersion: (typeof commandOptions.fixedVersion === "string" ? commandOptions.fixedVersion : undefined) ??
|
|
611
|
-
(typeof commandOptions.fixed_version === "string" ? commandOptions.fixed_version : undefined),
|
|
612
|
-
component: typeof commandOptions.component === "string" ? commandOptions.component : undefined,
|
|
613
|
-
regression: typeof commandOptions.regression === "string" ? commandOptions.regression : undefined,
|
|
614
|
-
customerImpact: (typeof commandOptions.customerImpact === "string" ? commandOptions.customerImpact : undefined) ??
|
|
615
|
-
(typeof commandOptions.customer_impact === "string" ? commandOptions.customer_impact : undefined),
|
|
616
|
-
dep: requiredRepeatable("dep", "--dep"),
|
|
617
|
-
comment: requiredRepeatable("comment", "--comment"),
|
|
618
|
-
note: requiredRepeatable("note", "--note"),
|
|
619
|
-
learning: requiredRepeatable("learning", "--learning"),
|
|
620
|
-
file: requiredRepeatable("file", "--file"),
|
|
621
|
-
test: requiredRepeatable("test", "--test"),
|
|
622
|
-
doc: requiredRepeatable("doc", "--doc"),
|
|
623
|
-
};
|
|
1859
|
+
normalized[key] = value;
|
|
1860
|
+
}
|
|
1861
|
+
return normalized;
|
|
624
1862
|
}
|
|
625
1863
|
function normalizeUpdateOptions(commandOptions) {
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
1864
|
+
const readUpdateString = (target) => readFirstStringFromCommanderOptions(commandOptions, UPDATE_COMMANDER_STRING_OPTION_CONTRACTS.find((entry) => entry.target === target) ?? {
|
|
1865
|
+
target,
|
|
1866
|
+
keys: [target],
|
|
1867
|
+
});
|
|
1868
|
+
const readUpdateList = (target) => readStringArrayFromCommanderOptions(commandOptions, UPDATE_COMMANDER_REPEATABLE_OPTION_CONTRACTS.find((entry) => entry.target === target) ?? {
|
|
1869
|
+
target,
|
|
1870
|
+
keys: [target],
|
|
1871
|
+
});
|
|
1872
|
+
const normalized = {
|
|
1873
|
+
title: readUpdateString("title"),
|
|
1874
|
+
description: readUpdateString("description"),
|
|
1875
|
+
body: readUpdateString("body"),
|
|
1876
|
+
status: readUpdateString("status"),
|
|
1877
|
+
closeReason: readUpdateString("closeReason"),
|
|
1878
|
+
priority: readUpdateString("priority"),
|
|
1879
|
+
type: readUpdateString("type"),
|
|
1880
|
+
tags: readUpdateString("tags"),
|
|
1881
|
+
deadline: readUpdateString("deadline"),
|
|
1882
|
+
estimatedMinutes: readUpdateString("estimatedMinutes"),
|
|
1883
|
+
acceptanceCriteria: readUpdateString("acceptanceCriteria"),
|
|
1884
|
+
definitionOfReady: readUpdateString("definitionOfReady"),
|
|
1885
|
+
order: readUpdateString("order"),
|
|
1886
|
+
rank: readUpdateString("rank"),
|
|
1887
|
+
goal: readUpdateString("goal"),
|
|
1888
|
+
objective: readUpdateString("objective"),
|
|
1889
|
+
value: readUpdateString("value"),
|
|
1890
|
+
impact: readUpdateString("impact"),
|
|
1891
|
+
outcome: readUpdateString("outcome"),
|
|
1892
|
+
whyNow: readUpdateString("whyNow"),
|
|
1893
|
+
author: readUpdateString("author"),
|
|
1894
|
+
message: readUpdateString("message"),
|
|
653
1895
|
force: Boolean(commandOptions.force),
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
unblockNote: (
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
1896
|
+
allowAuditUpdate: commandOptions.allowAuditUpdate === true || commandOptions.allow_audit_update === true ? true : undefined,
|
|
1897
|
+
allowAuditDepUpdate: commandOptions.allowAuditDepUpdate === true || commandOptions.allow_audit_dep_update === true ? true : undefined,
|
|
1898
|
+
assignee: readUpdateString("assignee"),
|
|
1899
|
+
parent: readUpdateString("parent"),
|
|
1900
|
+
reviewer: readUpdateString("reviewer"),
|
|
1901
|
+
risk: readUpdateString("risk"),
|
|
1902
|
+
confidence: readUpdateString("confidence"),
|
|
1903
|
+
sprint: readUpdateString("sprint"),
|
|
1904
|
+
release: readUpdateString("release"),
|
|
1905
|
+
blockedBy: readUpdateString("blockedBy"),
|
|
1906
|
+
blockedReason: readUpdateString("blockedReason"),
|
|
1907
|
+
unblockNote: readUpdateString("unblockNote"),
|
|
1908
|
+
reporter: readUpdateString("reporter"),
|
|
1909
|
+
severity: readUpdateString("severity"),
|
|
1910
|
+
environment: readUpdateString("environment"),
|
|
1911
|
+
reproSteps: readUpdateString("reproSteps"),
|
|
1912
|
+
resolution: readUpdateString("resolution"),
|
|
1913
|
+
expectedResult: readUpdateString("expectedResult"),
|
|
1914
|
+
actualResult: readUpdateString("actualResult"),
|
|
1915
|
+
affectedVersion: readUpdateString("affectedVersion"),
|
|
1916
|
+
fixedVersion: readUpdateString("fixedVersion"),
|
|
1917
|
+
component: readUpdateString("component"),
|
|
1918
|
+
regression: readUpdateString("regression"),
|
|
1919
|
+
customerImpact: readUpdateString("customerImpact"),
|
|
1920
|
+
dep: readUpdateList("dep"),
|
|
1921
|
+
depRemove: readUpdateList("depRemove"),
|
|
1922
|
+
replaceDeps: commandOptions.replaceDeps === true ? true : undefined,
|
|
1923
|
+
replaceTests: commandOptions.replaceTests === true ? true : undefined,
|
|
1924
|
+
comment: readUpdateList("comment"),
|
|
1925
|
+
note: readUpdateList("note"),
|
|
1926
|
+
learning: readUpdateList("learning"),
|
|
1927
|
+
file: readUpdateList("file"),
|
|
1928
|
+
test: readUpdateList("test"),
|
|
1929
|
+
doc: readUpdateList("doc"),
|
|
1930
|
+
reminder: readUpdateList("reminder"),
|
|
1931
|
+
event: readUpdateList("event"),
|
|
1932
|
+
typeOption: readUpdateList("typeOption"),
|
|
1933
|
+
unset: readUpdateList("unset"),
|
|
1934
|
+
clearDeps: commandOptions.clearDeps === true ? true : undefined,
|
|
1935
|
+
clearComments: commandOptions.clearComments === true ? true : undefined,
|
|
1936
|
+
clearNotes: commandOptions.clearNotes === true ? true : undefined,
|
|
1937
|
+
clearLearnings: commandOptions.clearLearnings === true ? true : undefined,
|
|
1938
|
+
clearFiles: commandOptions.clearFiles === true ? true : undefined,
|
|
1939
|
+
clearTests: commandOptions.clearTests === true ? true : undefined,
|
|
1940
|
+
clearDocs: commandOptions.clearDocs === true ? true : undefined,
|
|
1941
|
+
clearReminders: commandOptions.clearReminders === true ? true : undefined,
|
|
1942
|
+
clearEvents: commandOptions.clearEvents === true ? true : undefined,
|
|
1943
|
+
clearTypeOptions: commandOptions.clearTypeOptions === true ? true : undefined,
|
|
685
1944
|
};
|
|
1945
|
+
for (const [key, value] of Object.entries(commandOptions)) {
|
|
1946
|
+
if (Object.hasOwn(normalized, key)) {
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
normalized[key] = value;
|
|
1950
|
+
}
|
|
1951
|
+
return normalized;
|
|
1952
|
+
}
|
|
1953
|
+
const UPDATE_MANY_CONTROL_OPTION_KEYS = new Set([
|
|
1954
|
+
"filterStatus",
|
|
1955
|
+
"filterType",
|
|
1956
|
+
"filterTag",
|
|
1957
|
+
"filterPriority",
|
|
1958
|
+
"filterDeadlineBefore",
|
|
1959
|
+
"filterDeadlineAfter",
|
|
1960
|
+
"filterAssignee",
|
|
1961
|
+
"filterAssigneeFilter",
|
|
1962
|
+
"filterAssignee_filter",
|
|
1963
|
+
"filterParent",
|
|
1964
|
+
"filterSprint",
|
|
1965
|
+
"filterRelease",
|
|
1966
|
+
"limit",
|
|
1967
|
+
"offset",
|
|
1968
|
+
"dryRun",
|
|
1969
|
+
"rollback",
|
|
1970
|
+
"checkpoint",
|
|
1971
|
+
]);
|
|
1972
|
+
function extractUpdateManyMutationOptionSource(commandOptions) {
|
|
1973
|
+
const mutationOptions = {};
|
|
1974
|
+
for (const [key, value] of Object.entries(commandOptions)) {
|
|
1975
|
+
if (UPDATE_MANY_CONTROL_OPTION_KEYS.has(key)) {
|
|
1976
|
+
continue;
|
|
1977
|
+
}
|
|
1978
|
+
mutationOptions[key] = value;
|
|
1979
|
+
}
|
|
1980
|
+
return mutationOptions;
|
|
1981
|
+
}
|
|
1982
|
+
function readListOptionString(options, target) {
|
|
1983
|
+
return readFirstStringFromCommanderOptions(options, LIST_COMMANDER_STRING_OPTION_CONTRACTS.find((entry) => entry.target === target) ?? {
|
|
1984
|
+
target,
|
|
1985
|
+
keys: [target],
|
|
1986
|
+
});
|
|
686
1987
|
}
|
|
687
1988
|
function normalizeListOptions(options) {
|
|
1989
|
+
const normalized = {
|
|
1990
|
+
status: readListOptionString(options, "status"),
|
|
1991
|
+
type: readListOptionString(options, "type"),
|
|
1992
|
+
tag: readListOptionString(options, "tag"),
|
|
1993
|
+
priority: readListOptionString(options, "priority"),
|
|
1994
|
+
deadlineBefore: readListOptionString(options, "deadlineBefore"),
|
|
1995
|
+
deadlineAfter: readListOptionString(options, "deadlineAfter"),
|
|
1996
|
+
assignee: readListOptionString(options, "assignee"),
|
|
1997
|
+
assigneeFilter: readListOptionString(options, "assigneeFilter"),
|
|
1998
|
+
parent: readListOptionString(options, "parent"),
|
|
1999
|
+
sprint: readListOptionString(options, "sprint"),
|
|
2000
|
+
release: readListOptionString(options, "release"),
|
|
2001
|
+
limit: readListOptionString(options, "limit"),
|
|
2002
|
+
offset: readListOptionString(options, "offset"),
|
|
2003
|
+
includeBody: options.includeBody === true ? true : undefined,
|
|
2004
|
+
compact: options.compact === true ? true : undefined,
|
|
2005
|
+
fields: readListOptionString(options, "fields"),
|
|
2006
|
+
sort: readListOptionString(options, "sort"),
|
|
2007
|
+
order: readListOptionString(options, "order"),
|
|
2008
|
+
};
|
|
2009
|
+
for (const [key, value] of Object.entries(options)) {
|
|
2010
|
+
if (Object.hasOwn(normalized, key)) {
|
|
2011
|
+
continue;
|
|
2012
|
+
}
|
|
2013
|
+
normalized[key] = value;
|
|
2014
|
+
}
|
|
2015
|
+
return normalized;
|
|
2016
|
+
}
|
|
2017
|
+
function normalizeAggregateOptions(options) {
|
|
688
2018
|
return {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
2019
|
+
groupBy: typeof options.groupBy === "string" ? options.groupBy : undefined,
|
|
2020
|
+
count: options.count === true ? true : undefined,
|
|
2021
|
+
includeUnparented: options.includeUnparented === true || options.include_unparented === true,
|
|
2022
|
+
status: typeof options.status === "string" ? options.status : undefined,
|
|
2023
|
+
type: readListOptionString(options, "type"),
|
|
2024
|
+
tag: readListOptionString(options, "tag"),
|
|
2025
|
+
priority: readListOptionString(options, "priority"),
|
|
2026
|
+
deadlineBefore: readListOptionString(options, "deadlineBefore"),
|
|
2027
|
+
deadlineAfter: readListOptionString(options, "deadlineAfter"),
|
|
2028
|
+
assignee: readListOptionString(options, "assignee"),
|
|
2029
|
+
assigneeFilter: readListOptionString(options, "assigneeFilter"),
|
|
2030
|
+
parent: readListOptionString(options, "parent"),
|
|
2031
|
+
sprint: readListOptionString(options, "sprint"),
|
|
2032
|
+
release: readListOptionString(options, "release"),
|
|
698
2033
|
};
|
|
699
2034
|
}
|
|
700
|
-
function
|
|
2035
|
+
function normalizeDedupeAuditOptions(options) {
|
|
701
2036
|
return {
|
|
702
2037
|
mode: typeof options.mode === "string" ? options.mode : undefined,
|
|
2038
|
+
status: typeof options.status === "string" ? options.status : undefined,
|
|
2039
|
+
type: readListOptionString(options, "type"),
|
|
2040
|
+
tag: readListOptionString(options, "tag"),
|
|
2041
|
+
priority: readListOptionString(options, "priority"),
|
|
2042
|
+
deadlineBefore: readListOptionString(options, "deadlineBefore"),
|
|
2043
|
+
deadlineAfter: readListOptionString(options, "deadlineAfter"),
|
|
2044
|
+
assignee: readListOptionString(options, "assignee"),
|
|
2045
|
+
assigneeFilter: readListOptionString(options, "assigneeFilter"),
|
|
2046
|
+
parent: readListOptionString(options, "parent"),
|
|
2047
|
+
sprint: readListOptionString(options, "sprint"),
|
|
2048
|
+
release: readListOptionString(options, "release"),
|
|
2049
|
+
limit: readListOptionString(options, "limit"),
|
|
2050
|
+
threshold: typeof options.threshold === "string" ? options.threshold : undefined,
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
function printListJsonStream(commandName, result, globalOptions) {
|
|
2054
|
+
setActiveCommandResult(result);
|
|
2055
|
+
if (globalOptions.quiet) {
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
const warnings = Array.isArray(result.warnings) ? result.warnings : [];
|
|
2059
|
+
const metaPayload = {
|
|
2060
|
+
type: "meta",
|
|
2061
|
+
command: commandName,
|
|
2062
|
+
count: result.count,
|
|
2063
|
+
now: result.now,
|
|
2064
|
+
filters: result.filters,
|
|
2065
|
+
};
|
|
2066
|
+
if (warnings.length > 0) {
|
|
2067
|
+
metaPayload.warnings = warnings;
|
|
2068
|
+
}
|
|
2069
|
+
if (!writeStdout(`${JSON.stringify(metaPayload)}\n`)) {
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
for (const item of result.items) {
|
|
2073
|
+
if (!writeStdout(`${JSON.stringify({ type: "item", command: commandName, item })}\n`)) {
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
writeStdout(`${JSON.stringify({ type: "end", command: commandName, count: result.count })}\n`);
|
|
2078
|
+
}
|
|
2079
|
+
function printActivityJsonStream(result, options, globalOptions) {
|
|
2080
|
+
setActiveCommandResult(result);
|
|
2081
|
+
if (globalOptions.quiet) {
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2084
|
+
const metaPayload = {
|
|
2085
|
+
type: "meta",
|
|
2086
|
+
command: "activity",
|
|
2087
|
+
count: result.count,
|
|
2088
|
+
filters: {
|
|
2089
|
+
id: options.id ?? null,
|
|
2090
|
+
op: options.op ?? null,
|
|
2091
|
+
author: options.author ?? null,
|
|
2092
|
+
from: options.from ?? null,
|
|
2093
|
+
to: options.to ?? null,
|
|
2094
|
+
limit: options.limit ?? null,
|
|
2095
|
+
},
|
|
2096
|
+
};
|
|
2097
|
+
if (!writeStdout(`${JSON.stringify(metaPayload)}\n`)) {
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
for (const entry of result.activity) {
|
|
2101
|
+
if (!writeStdout(`${JSON.stringify({ type: "entry", command: "activity", entry })}\n`)) {
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
writeStdout(`${JSON.stringify({ type: "end", command: "activity", count: result.count })}\n`);
|
|
2106
|
+
}
|
|
2107
|
+
function normalizeSearchOptions(options) {
|
|
2108
|
+
const readSearchString = (target) => readFirstStringFromCommanderOptions(options, SEARCH_COMMANDER_STRING_OPTION_CONTRACTS.find((entry) => entry.target === target) ?? {
|
|
2109
|
+
target,
|
|
2110
|
+
keys: [target],
|
|
2111
|
+
});
|
|
2112
|
+
const fields = readSearchString("fields");
|
|
2113
|
+
const compactRequested = options.compact === true;
|
|
2114
|
+
const fullRequested = options.full === true;
|
|
2115
|
+
const defaultCompact = !compactRequested && !fullRequested && fields === undefined;
|
|
2116
|
+
const normalized = {
|
|
2117
|
+
mode: readSearchString("mode"),
|
|
703
2118
|
includeLinked: options.includeLinked === true ? true : undefined,
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
2119
|
+
titleExact: options.titleExact === true ? true : undefined,
|
|
2120
|
+
phraseExact: options.phraseExact === true ? true : undefined,
|
|
2121
|
+
type: readSearchString("type"),
|
|
2122
|
+
tag: readSearchString("tag"),
|
|
2123
|
+
priority: readSearchString("priority"),
|
|
2124
|
+
deadlineBefore: readSearchString("deadlineBefore"),
|
|
2125
|
+
deadlineAfter: readSearchString("deadlineAfter"),
|
|
2126
|
+
limit: readSearchString("limit"),
|
|
2127
|
+
fields,
|
|
2128
|
+
compact: compactRequested || defaultCompact ? true : undefined,
|
|
2129
|
+
full: fullRequested ? true : undefined,
|
|
710
2130
|
};
|
|
2131
|
+
for (const [key, value] of Object.entries(options)) {
|
|
2132
|
+
if (Object.hasOwn(normalized, key)) {
|
|
2133
|
+
continue;
|
|
2134
|
+
}
|
|
2135
|
+
normalized[key] = value;
|
|
2136
|
+
}
|
|
2137
|
+
return normalized;
|
|
2138
|
+
}
|
|
2139
|
+
function normalizeSearchKeywordsInput(keywords) {
|
|
2140
|
+
const query = keywords
|
|
2141
|
+
.map((entry) => entry.trim())
|
|
2142
|
+
.filter((entry) => entry.length > 0)
|
|
2143
|
+
.join(" ");
|
|
2144
|
+
if (query.length === 0) {
|
|
2145
|
+
throw new PmCliError("Search query must not be empty", EXIT_CODE.USAGE);
|
|
2146
|
+
}
|
|
2147
|
+
return query;
|
|
2148
|
+
}
|
|
2149
|
+
function normalizeCalendarOptions(options) {
|
|
2150
|
+
const readCalendarString = (target) => readFirstStringFromCommanderOptions(options, CALENDAR_COMMANDER_STRING_OPTION_CONTRACTS.find((entry) => entry.target === target) ?? {
|
|
2151
|
+
target,
|
|
2152
|
+
keys: [target],
|
|
2153
|
+
});
|
|
2154
|
+
const normalized = {
|
|
2155
|
+
view: readCalendarString("view"),
|
|
2156
|
+
date: readCalendarString("date"),
|
|
2157
|
+
from: readCalendarString("from"),
|
|
2158
|
+
to: readCalendarString("to"),
|
|
2159
|
+
past: options.past === true ? true : undefined,
|
|
2160
|
+
fullPeriod: options.fullPeriod === true || options.full_period === true ? true : undefined,
|
|
2161
|
+
limit: readCalendarString("limit"),
|
|
2162
|
+
type: readCalendarString("type"),
|
|
2163
|
+
tag: readCalendarString("tag"),
|
|
2164
|
+
priority: readCalendarString("priority"),
|
|
2165
|
+
status: readCalendarString("status"),
|
|
2166
|
+
assignee: readCalendarString("assignee"),
|
|
2167
|
+
assigneeFilter: readCalendarString("assigneeFilter"),
|
|
2168
|
+
sprint: readCalendarString("sprint"),
|
|
2169
|
+
release: readCalendarString("release"),
|
|
2170
|
+
include: readCalendarString("include"),
|
|
2171
|
+
recurrenceLookaheadDays: readCalendarString("recurrenceLookaheadDays"),
|
|
2172
|
+
recurrenceLookbackDays: readCalendarString("recurrenceLookbackDays"),
|
|
2173
|
+
occurrenceLimit: readCalendarString("occurrenceLimit"),
|
|
2174
|
+
format: readCalendarString("format"),
|
|
2175
|
+
};
|
|
2176
|
+
for (const [key, value] of Object.entries(options)) {
|
|
2177
|
+
if (Object.hasOwn(normalized, key)) {
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
normalized[key] = value;
|
|
2181
|
+
}
|
|
2182
|
+
return normalized;
|
|
2183
|
+
}
|
|
2184
|
+
function normalizeActivityOptions(options) {
|
|
2185
|
+
const readActivityString = (target) => readFirstStringFromCommanderOptions(options, ACTIVITY_COMMANDER_STRING_OPTION_CONTRACTS.find((entry) => entry.target === target) ?? {
|
|
2186
|
+
target,
|
|
2187
|
+
keys: [target],
|
|
2188
|
+
});
|
|
2189
|
+
return {
|
|
2190
|
+
id: readActivityString("id"),
|
|
2191
|
+
op: readActivityString("op"),
|
|
2192
|
+
author: readActivityString("author"),
|
|
2193
|
+
from: readActivityString("from"),
|
|
2194
|
+
to: readActivityString("to"),
|
|
2195
|
+
limit: readActivityString("limit"),
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
function resolveActivityStreamMode(raw) {
|
|
2199
|
+
if (raw === true) {
|
|
2200
|
+
return true;
|
|
2201
|
+
}
|
|
2202
|
+
if (raw === false || raw === undefined || raw === null) {
|
|
2203
|
+
return false;
|
|
2204
|
+
}
|
|
2205
|
+
if (typeof raw === "string") {
|
|
2206
|
+
const normalized = raw.trim().toLowerCase();
|
|
2207
|
+
if (normalized.length === 0 ||
|
|
2208
|
+
normalized === "rows" ||
|
|
2209
|
+
normalized === "ndjson" ||
|
|
2210
|
+
normalized === "jsonl" ||
|
|
2211
|
+
normalized === "true" ||
|
|
2212
|
+
normalized === "1" ||
|
|
2213
|
+
normalized === "yes" ||
|
|
2214
|
+
normalized === "on") {
|
|
2215
|
+
return true;
|
|
2216
|
+
}
|
|
2217
|
+
if (normalized === "false" || normalized === "off" || normalized === "none" || normalized === "0") {
|
|
2218
|
+
return false;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
throw new PmCliError("Activity --stream accepts rows|ndjson|jsonl (or no value)", EXIT_CODE.USAGE);
|
|
2222
|
+
}
|
|
2223
|
+
function normalizeContextOptions(options) {
|
|
2224
|
+
const readContextString = (target) => readFirstStringFromCommanderOptions(options, CONTEXT_COMMANDER_STRING_OPTION_CONTRACTS.find((entry) => entry.target === target) ?? {
|
|
2225
|
+
target,
|
|
2226
|
+
keys: [target],
|
|
2227
|
+
});
|
|
2228
|
+
const normalized = {
|
|
2229
|
+
date: readContextString("date"),
|
|
2230
|
+
from: readContextString("from"),
|
|
2231
|
+
to: readContextString("to"),
|
|
2232
|
+
past: options.past === true ? true : undefined,
|
|
2233
|
+
type: readContextString("type"),
|
|
2234
|
+
tag: readContextString("tag"),
|
|
2235
|
+
priority: readContextString("priority"),
|
|
2236
|
+
assignee: readContextString("assignee"),
|
|
2237
|
+
assigneeFilter: readContextString("assigneeFilter"),
|
|
2238
|
+
sprint: readContextString("sprint"),
|
|
2239
|
+
release: readContextString("release"),
|
|
2240
|
+
limit: readContextString("limit"),
|
|
2241
|
+
format: readContextString("format"),
|
|
2242
|
+
};
|
|
2243
|
+
for (const [key, value] of Object.entries(options)) {
|
|
2244
|
+
if (Object.hasOwn(normalized, key)) {
|
|
2245
|
+
continue;
|
|
2246
|
+
}
|
|
2247
|
+
normalized[key] = value;
|
|
2248
|
+
}
|
|
2249
|
+
return normalized;
|
|
711
2250
|
}
|
|
712
2251
|
function resolveCliVersion() {
|
|
713
2252
|
try {
|
|
@@ -721,42 +2260,111 @@ function resolveCliVersion() {
|
|
|
721
2260
|
return "0.0.0";
|
|
722
2261
|
}
|
|
723
2262
|
}
|
|
2263
|
+
const CLI_VERSION = resolveCliVersion();
|
|
724
2264
|
const program = new Command();
|
|
725
2265
|
program
|
|
726
2266
|
.name("pm")
|
|
727
2267
|
.description("Universal, flexible, extensible, agent-optimized project management CLI for any project or programming language.")
|
|
728
|
-
.version(
|
|
729
|
-
.showHelpAfterError()
|
|
2268
|
+
.version(CLI_VERSION)
|
|
2269
|
+
.showHelpAfterError(false)
|
|
730
2270
|
.allowExcessArguments(false)
|
|
731
2271
|
.allowUnknownOption(false)
|
|
2272
|
+
.configureOutput({
|
|
2273
|
+
writeOut: (str) => {
|
|
2274
|
+
writeStdout(str);
|
|
2275
|
+
},
|
|
2276
|
+
// Commander errors are rendered in our own catch path.
|
|
2277
|
+
writeErr: () => { },
|
|
2278
|
+
})
|
|
732
2279
|
.option("--json", "Output JSON instead of TOON")
|
|
733
2280
|
.option("--quiet", "Suppress stdout output")
|
|
734
2281
|
.option("--path <dir>", "Override PM path for this command")
|
|
735
2282
|
.option("--no-extensions", "Disable extension loading")
|
|
2283
|
+
.option("--no-pager", "Disable pager integration for help and long output")
|
|
2284
|
+
.option("--explain", "Render extended rationale and examples in help output")
|
|
736
2285
|
.option("--profile", "Print deterministic timing diagnostics")
|
|
737
2286
|
.exitOverride();
|
|
738
2287
|
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
739
2288
|
activeExtensionHookContext = null;
|
|
2289
|
+
activeTelemetryCommandContext = null;
|
|
740
2290
|
clearActiveExtensionHooks();
|
|
2291
|
+
clearResolvedGlobalOptions(actionCommand);
|
|
2292
|
+
const bootstrapGlobalOptions = getGlobalOptions(actionCommand);
|
|
2293
|
+
const commandPath = getCommandPath(actionCommand);
|
|
2294
|
+
let commandArgs = actionCommand.args.map(String);
|
|
2295
|
+
let commandOptions = extractCommandScopedOptions(actionCommand, commandArgs);
|
|
2296
|
+
let globalOptions = { ...bootstrapGlobalOptions };
|
|
2297
|
+
await maybeRunFirstUseTelemetryPrompt(commandPath, globalOptions);
|
|
2298
|
+
const fallbackPmRoot = resolvePmRoot(process.cwd(), bootstrapGlobalOptions.path);
|
|
741
2299
|
const runtimeExtensions = await maybeLoadRuntimeExtensions(actionCommand);
|
|
742
2300
|
if (!runtimeExtensions) {
|
|
2301
|
+
activeTelemetryCommandContext = await startTelemetryCommand({
|
|
2302
|
+
command: commandPath,
|
|
2303
|
+
pm_version: CLI_VERSION,
|
|
2304
|
+
args: commandArgs,
|
|
2305
|
+
options: commandOptions,
|
|
2306
|
+
global: globalOptions,
|
|
2307
|
+
pm_root: fallbackPmRoot,
|
|
2308
|
+
});
|
|
2309
|
+
await enforceItemFormatWriteGateAndPreflightMigration(commandPath, commandOptions, fallbackPmRoot, defaultPreflightDecision());
|
|
743
2310
|
return;
|
|
744
2311
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
2312
|
+
setActiveExtensionHooks(runtimeExtensions.hooks);
|
|
2313
|
+
setActiveExtensionCommands(runtimeExtensions.commands);
|
|
2314
|
+
setActiveExtensionParsers(runtimeExtensions.parsers);
|
|
2315
|
+
setActiveExtensionPreflight(runtimeExtensions.preflight);
|
|
2316
|
+
setActiveExtensionServices(runtimeExtensions.services);
|
|
2317
|
+
setActiveExtensionRenderers(runtimeExtensions.renderers);
|
|
2318
|
+
setActiveExtensionRegistrations(runtimeExtensions.registrations);
|
|
2319
|
+
const extensionFlagDefinitions = collectExtensionFlagDefinitionsForCommand(runtimeExtensions.registrations, commandPath);
|
|
2320
|
+
commandOptions = extractCommandScopedOptions(actionCommand, commandArgs, extensionFlagDefinitions);
|
|
2321
|
+
const parserOverride = await runActiveParserOverride({
|
|
2322
|
+
command: commandPath,
|
|
2323
|
+
args: commandArgs,
|
|
2324
|
+
options: commandOptions,
|
|
2325
|
+
global: globalOptions,
|
|
2326
|
+
pm_root: runtimeExtensions.pmRoot,
|
|
2327
|
+
});
|
|
2328
|
+
if (globalOptions.profile && parserOverride.warnings.length > 0) {
|
|
2329
|
+
printError(`profile:extensions parser_warnings=${formatHookWarnings(parserOverride.warnings)}`);
|
|
2330
|
+
}
|
|
2331
|
+
commandArgs = parserOverride.context.args;
|
|
2332
|
+
commandOptions = parserOverride.context.options;
|
|
2333
|
+
globalOptions = parserOverride.context.global;
|
|
2334
|
+
const preflightOverride = await runActivePreflightOverride({
|
|
2335
|
+
command: commandPath,
|
|
2336
|
+
args: commandArgs,
|
|
2337
|
+
options: commandOptions,
|
|
2338
|
+
global: globalOptions,
|
|
2339
|
+
pm_root: runtimeExtensions.pmRoot,
|
|
2340
|
+
decision: defaultPreflightDecision(),
|
|
2341
|
+
});
|
|
2342
|
+
if (globalOptions.profile && preflightOverride.warnings.length > 0) {
|
|
2343
|
+
printError(`profile:extensions preflight_warnings=${formatHookWarnings(preflightOverride.warnings)}`);
|
|
2344
|
+
}
|
|
2345
|
+
commandArgs = preflightOverride.context.args;
|
|
2346
|
+
commandOptions = preflightOverride.context.options;
|
|
2347
|
+
globalOptions = preflightOverride.context.global;
|
|
2348
|
+
const preflightDecision = preflightOverride.decision;
|
|
2349
|
+
await enforceItemFormatWriteGateAndPreflightMigration(commandPath, commandOptions, runtimeExtensions.pmRoot, preflightDecision);
|
|
2350
|
+
const migrationWarnings = preflightDecision.run_extension_migrations
|
|
2351
|
+
? await executeRegisteredRuntimeMigrations(runtimeExtensions.registrations.migrations, runtimeExtensions.pmRoot)
|
|
2352
|
+
: [];
|
|
2353
|
+
if (globalOptions.profile && migrationWarnings.length > 0) {
|
|
2354
|
+
printError(`profile:extensions migration_warnings=${formatHookWarnings(migrationWarnings)}`);
|
|
2355
|
+
}
|
|
2356
|
+
const migrationBlockers = collectMandatoryMigrationBlockers(runtimeExtensions.registrations.migrations);
|
|
748
2357
|
activeExtensionHookContext = {
|
|
749
2358
|
hooks: runtimeExtensions.hooks,
|
|
750
2359
|
commandName: commandPath,
|
|
751
2360
|
commandArgs,
|
|
2361
|
+
commandOptions: { ...commandOptions },
|
|
2362
|
+
globalOptions: { ...globalOptions },
|
|
752
2363
|
pmRoot: runtimeExtensions.pmRoot,
|
|
753
2364
|
profileEnabled: Boolean(globalOptions.profile),
|
|
754
|
-
migrationBlockers
|
|
2365
|
+
migrationBlockers,
|
|
755
2366
|
};
|
|
756
|
-
|
|
757
|
-
setActiveExtensionCommands(runtimeExtensions.commands);
|
|
758
|
-
setActiveExtensionRenderers(runtimeExtensions.renderers);
|
|
759
|
-
const commandOptions = actionCommand.optsWithGlobals();
|
|
2367
|
+
setActiveCommandResult(undefined);
|
|
760
2368
|
setActiveCommandContext({
|
|
761
2369
|
command: commandPath,
|
|
762
2370
|
args: commandArgs,
|
|
@@ -764,15 +2372,27 @@ program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
764
2372
|
global: { ...globalOptions },
|
|
765
2373
|
pm_root: runtimeExtensions.pmRoot,
|
|
766
2374
|
});
|
|
2375
|
+
activeTelemetryCommandContext = await startTelemetryCommand({
|
|
2376
|
+
command: commandPath,
|
|
2377
|
+
pm_version: CLI_VERSION,
|
|
2378
|
+
args: commandArgs,
|
|
2379
|
+
options: commandOptions,
|
|
2380
|
+
global: globalOptions,
|
|
2381
|
+
pm_root: runtimeExtensions.pmRoot,
|
|
2382
|
+
});
|
|
767
2383
|
const hookWarnings = await runBeforeCommandHooks(runtimeExtensions.hooks, {
|
|
768
2384
|
command: commandPath,
|
|
769
2385
|
args: commandArgs,
|
|
2386
|
+
options: { ...commandOptions },
|
|
2387
|
+
global: { ...globalOptions },
|
|
770
2388
|
pm_root: runtimeExtensions.pmRoot,
|
|
771
2389
|
});
|
|
772
2390
|
if (globalOptions.profile && hookWarnings.length > 0) {
|
|
773
2391
|
printError(`profile:extensions hook_warnings=${formatHookWarnings(hookWarnings)}`);
|
|
774
2392
|
}
|
|
775
|
-
|
|
2393
|
+
if (preflightDecision.enforce_mandatory_migration_gate) {
|
|
2394
|
+
enforceMandatoryMigrationWriteGate(commandPath, commandOptions, migrationBlockers);
|
|
2395
|
+
}
|
|
776
2396
|
});
|
|
777
2397
|
program.hook("postAction", async () => {
|
|
778
2398
|
await runAndClearAfterCommandHooks({ ok: true });
|
|
@@ -780,11 +2400,14 @@ program.hook("postAction", async () => {
|
|
|
780
2400
|
program
|
|
781
2401
|
.command("init")
|
|
782
2402
|
.argument("[prefix]", "Optional id prefix")
|
|
2403
|
+
.option("--preset <value>", "Governance preset for new setups: minimal|default|strict")
|
|
783
2404
|
.description("Initialize pm storage and defaults for the current workspace.")
|
|
784
|
-
.action(async (prefix,
|
|
2405
|
+
.action(async (prefix, options, command) => {
|
|
785
2406
|
const globalOptions = getGlobalOptions(command);
|
|
786
2407
|
const startedAt = Date.now();
|
|
787
|
-
const result = await runInit(prefix, globalOptions
|
|
2408
|
+
const result = await runInit(prefix, globalOptions, {
|
|
2409
|
+
preset: typeof options.preset === "string" ? options.preset : undefined,
|
|
2410
|
+
});
|
|
788
2411
|
printResult(result, globalOptions);
|
|
789
2412
|
if (globalOptions.profile) {
|
|
790
2413
|
printError(`profile:command=init took_ms=${Date.now() - startedAt}`);
|
|
@@ -793,9 +2416,12 @@ program
|
|
|
793
2416
|
program
|
|
794
2417
|
.command("config")
|
|
795
2418
|
.argument("<scope>", "Config scope: project|global")
|
|
796
|
-
.argument("<action>", "Config action: get|set")
|
|
797
|
-
.argument("
|
|
798
|
-
.option("--criterion <text>", "
|
|
2419
|
+
.argument("<action>", "Config action: get|set|list|export")
|
|
2420
|
+
.argument("[key]", "Config key for get|set: definition-of-done|item-format|history-missing-stream-policy|sprint-release-format-policy|parent-reference-policy|metadata-validation-profile|metadata-required-fields|lifecycle-stale-blocker-reason-patterns|lifecycle-closure-like-blocked-reason-patterns|lifecycle-closure-like-resolution-patterns|lifecycle-closure-like-actual-result-patterns|governance-preset|governance-ownership-enforcement|governance-create-mode-default|governance-close-validation-default|governance-parent-reference-policy|governance-metadata-validation-profile|governance-force-required-for-stale-lock|test-result-tracking|telemetry-tracking")
|
|
2421
|
+
.option("--criterion <text>", "Criteria value for definition-of-done, metadata-required-fields, or lifecycle pattern keys (repeatable for set)", collect)
|
|
2422
|
+
.option("--clear-criteria", "Clear criteria-list keys for config set operations")
|
|
2423
|
+
.option("--format <value>", "Item format for item-format key: toon|json_markdown")
|
|
2424
|
+
.option("--policy <value>", "Policy key values: history-missing-stream-policy=auto_create|strict_error; sprint-release-format-policy=warn|strict_error; parent-reference-policy=warn|strict_error; governance-preset=minimal|default|strict|custom; governance-ownership-enforcement=none|warn|strict; governance-create-mode-default=progressive|strict; governance-close-validation-default=off|warn|strict; governance-parent-reference-policy=warn|strict_error; governance-metadata-validation-profile=core|strict|custom; governance-force-required-for-stale-lock=enabled|disabled; test-result-tracking=enabled|disabled; telemetry-tracking=enabled|disabled")
|
|
799
2425
|
.description("Read or update pm settings for the current workspace or global profile.")
|
|
800
2426
|
.action(async (scope, action, key, options, command) => {
|
|
801
2427
|
const globalOptions = getGlobalOptions(command);
|
|
@@ -803,28 +2429,271 @@ program
|
|
|
803
2429
|
const criteria = Array.isArray(options.criterion) ? options.criterion : [];
|
|
804
2430
|
const result = await runConfig(scope, action, key, {
|
|
805
2431
|
criterion: criteria,
|
|
2432
|
+
format: typeof options.format === "string" ? options.format : undefined,
|
|
2433
|
+
policy: typeof options.policy === "string" ? options.policy : undefined,
|
|
2434
|
+
clearCriteria: options.clearCriteria === true,
|
|
806
2435
|
}, globalOptions);
|
|
807
2436
|
printResult(result, globalOptions);
|
|
808
2437
|
if (globalOptions.profile) {
|
|
809
2438
|
printError(`profile:command=config took_ms=${Date.now() - startedAt}`);
|
|
810
2439
|
}
|
|
811
2440
|
});
|
|
812
|
-
|
|
2441
|
+
function normalizeExtensionOptions(options, forcedAction) {
|
|
2442
|
+
const isForcedAction = (action) => forcedAction === action;
|
|
2443
|
+
const readBoolean = (...keys) => keys.some((key) => options[key] === true);
|
|
2444
|
+
const readString = (...keys) => {
|
|
2445
|
+
for (const key of keys) {
|
|
2446
|
+
if (typeof options[key] === "string") {
|
|
2447
|
+
return options[key];
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
return undefined;
|
|
2451
|
+
};
|
|
2452
|
+
return {
|
|
2453
|
+
init: isForcedAction("init") || readBoolean("init"),
|
|
2454
|
+
scaffold: readBoolean("scaffold"),
|
|
2455
|
+
install: isForcedAction("install") || readBoolean("install"),
|
|
2456
|
+
uninstall: isForcedAction("uninstall") || readBoolean("uninstall"),
|
|
2457
|
+
explore: isForcedAction("explore") || readBoolean("explore"),
|
|
2458
|
+
manage: isForcedAction("manage") || readBoolean("manage"),
|
|
2459
|
+
doctor: isForcedAction("doctor") || readBoolean("doctor"),
|
|
2460
|
+
adopt: isForcedAction("adopt") || readBoolean("adopt"),
|
|
2461
|
+
adoptAll: isForcedAction("adopt-all") || readBoolean("adoptAll", "adopt_all", "adopt-all"),
|
|
2462
|
+
activate: isForcedAction("activate") || readBoolean("activate"),
|
|
2463
|
+
deactivate: isForcedAction("deactivate") || readBoolean("deactivate"),
|
|
2464
|
+
project: readBoolean("project"),
|
|
2465
|
+
local: readBoolean("local"),
|
|
2466
|
+
global: readBoolean("global"),
|
|
2467
|
+
gh: readString("gh"),
|
|
2468
|
+
github: readString("github"),
|
|
2469
|
+
ref: readString("ref"),
|
|
2470
|
+
detail: readString("detail"),
|
|
2471
|
+
trace: readBoolean("trace"),
|
|
2472
|
+
runtimeProbe: readBoolean("runtimeProbe", "runtime_probe", "runtime-probe"),
|
|
2473
|
+
fixManagedState: readBoolean("fixManagedState", "fix_managed_state", "fix-managed-state"),
|
|
2474
|
+
strictExit: readBoolean("strictExit", "strict_exit", "strict-exit"),
|
|
2475
|
+
failOnWarn: readBoolean("failOnWarn", "fail_on_warn", "fail-on-warn"),
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2478
|
+
async function executeExtensionCommand(target, options, command, forcedAction) {
|
|
2479
|
+
const globalOptions = getGlobalOptions(command);
|
|
2480
|
+
const startedAt = Date.now();
|
|
2481
|
+
const normalizedOptions = normalizeExtensionOptions(options, forcedAction);
|
|
2482
|
+
const result = await runExtension(target, normalizedOptions, globalOptions);
|
|
2483
|
+
printResult(result, globalOptions);
|
|
2484
|
+
const strictExit = Boolean(normalizedOptions.strictExit) || Boolean(normalizedOptions.failOnWarn);
|
|
2485
|
+
if (result.action === "doctor" && strictExit) {
|
|
2486
|
+
const detailsRecord = result.details;
|
|
2487
|
+
const summary = (detailsRecord.summary ?? null);
|
|
2488
|
+
const summaryStatus = summary && typeof summary.status === "string" ? summary.status : undefined;
|
|
2489
|
+
const shouldFail = summaryStatus ? summaryStatus !== "ok" : result.warnings.length > 0;
|
|
2490
|
+
if (shouldFail) {
|
|
2491
|
+
process.exitCode = EXIT_CODE.GENERIC_FAILURE;
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
if (globalOptions.profile) {
|
|
2495
|
+
printError(`profile:command=extension took_ms=${Date.now() - startedAt}`);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
function addExtensionScopeOptions(command) {
|
|
2499
|
+
return command
|
|
2500
|
+
.option("--project", "Use project extension scope (default)")
|
|
2501
|
+
.option("--local", "Alias for --project")
|
|
2502
|
+
.option("--global", "Use global extension scope");
|
|
2503
|
+
}
|
|
2504
|
+
const extensionCommand = program
|
|
2505
|
+
.command("extension")
|
|
2506
|
+
.argument("[target]", "Extension source/name or scaffold target path (for --init/--scaffold)")
|
|
2507
|
+
.option("--init", "Generate a starter extension scaffold at target path")
|
|
2508
|
+
.option("--scaffold", "Alias for --init")
|
|
2509
|
+
.option("--install", "Install extension from local path or GitHub source")
|
|
2510
|
+
.option("--uninstall", "Uninstall an installed extension")
|
|
2511
|
+
.option("--explore", "List discovered extensions in selected scope")
|
|
2512
|
+
.option("--manage", "List managed extensions with update-check metadata")
|
|
2513
|
+
.option("--doctor", "Run consolidated extension diagnostics (summary/deep modes)")
|
|
2514
|
+
.option("--adopt", "Adopt an existing unmanaged extension into managed metadata")
|
|
2515
|
+
.option("--adopt-all", "Adopt all unmanaged extensions into managed metadata")
|
|
2516
|
+
.option("--activate", "Activate an extension in selected scope settings")
|
|
2517
|
+
.option("--deactivate", "Deactivate an extension in selected scope settings")
|
|
2518
|
+
.option("--project", "Use project extension scope (default)")
|
|
2519
|
+
.option("--local", "Alias for --project")
|
|
2520
|
+
.option("--global", "Use global extension scope")
|
|
2521
|
+
.option("--gh <owner/repo[/path]>", "Install from GitHub shorthand source")
|
|
2522
|
+
.option("--github <owner/repo[/path]>", "Alias for --gh")
|
|
2523
|
+
.option("--ref <ref>", "Git ref/branch/tag for GitHub install sources")
|
|
2524
|
+
.option("--detail <mode>", "Detail mode for extension diagnostics (summary|deep)")
|
|
2525
|
+
.option("--trace", "Include actionable registration traces in doctor deep diagnostics")
|
|
2526
|
+
.option("--runtime-probe", "Opt-in runtime activation probe for manage output parity")
|
|
2527
|
+
.option("--fix-managed-state", "Adopt unmanaged extensions before diagnostics/update checks")
|
|
2528
|
+
.option("--strict-exit", "Return non-zero exit when doctor warnings are present (ok=false)")
|
|
2529
|
+
.option("--fail-on-warn", "Alias for --strict-exit (doctor)")
|
|
2530
|
+
.description("Manage extension lifecycle operations for project or global scope.")
|
|
2531
|
+
.action(async (target, _options, command) => {
|
|
2532
|
+
await executeExtensionCommand(target, command.opts(), command);
|
|
2533
|
+
});
|
|
2534
|
+
addExtensionScopeOptions(extensionCommand
|
|
2535
|
+
.command("init")
|
|
2536
|
+
.alias("scaffold")
|
|
2537
|
+
.argument("<target>", "Scaffold target directory path")
|
|
2538
|
+
.description("Generate a starter extension scaffold with manifest and entrypoint.")).action(async (target, _options, command) => {
|
|
2539
|
+
await executeExtensionCommand(target, command.opts(), command, "init");
|
|
2540
|
+
});
|
|
2541
|
+
addExtensionScopeOptions(extensionCommand
|
|
813
2542
|
.command("install")
|
|
814
|
-
.argument("
|
|
815
|
-
.option("--
|
|
816
|
-
.option("--
|
|
817
|
-
.
|
|
818
|
-
.action(async (target,
|
|
2543
|
+
.argument("[target]", "Extension source (local path or GitHub source)")
|
|
2544
|
+
.option("--gh <owner/repo[/path]>", "Install from GitHub shorthand source")
|
|
2545
|
+
.option("--github <owner/repo[/path]>", "Alias for --gh")
|
|
2546
|
+
.option("--ref <ref>", "Git ref/branch/tag for GitHub install sources")
|
|
2547
|
+
.description("Install extension from local path or GitHub source.")).action(async (target, _options, command) => {
|
|
2548
|
+
await executeExtensionCommand(target, command.opts(), command, "install");
|
|
2549
|
+
});
|
|
2550
|
+
addExtensionScopeOptions(extensionCommand.command("uninstall").argument("<target>", "Extension name").description("Uninstall an installed extension.")).action(async (target, _options, command) => {
|
|
2551
|
+
await executeExtensionCommand(target, command.opts(), command, "uninstall");
|
|
2552
|
+
});
|
|
2553
|
+
addExtensionScopeOptions(extensionCommand.command("explore").description("List discovered extensions in selected scope.")).action(async (_options, command) => {
|
|
2554
|
+
await executeExtensionCommand(undefined, command.opts(), command, "explore");
|
|
2555
|
+
});
|
|
2556
|
+
addExtensionScopeOptions(extensionCommand
|
|
2557
|
+
.command("manage")
|
|
2558
|
+
.option("--runtime-probe", "Opt-in runtime activation probe for manage output parity")
|
|
2559
|
+
.option("--fix-managed-state", "Adopt unmanaged extensions before diagnostics/update checks")
|
|
2560
|
+
.description("List managed extensions with update-check metadata.")).action(async (_options, command) => {
|
|
2561
|
+
await executeExtensionCommand(undefined, command.opts(), command, "manage");
|
|
2562
|
+
});
|
|
2563
|
+
addExtensionScopeOptions(extensionCommand
|
|
2564
|
+
.command("doctor")
|
|
2565
|
+
.option("--detail <mode>", "Detail mode for extension diagnostics (summary|deep)")
|
|
2566
|
+
.option("--trace", "Include actionable registration traces in doctor deep diagnostics")
|
|
2567
|
+
.option("--fix-managed-state", "Adopt unmanaged extensions before diagnostics/update checks")
|
|
2568
|
+
.option("--strict-exit", "Return non-zero exit when doctor warnings are present (ok=false)")
|
|
2569
|
+
.option("--fail-on-warn", "Alias for --strict-exit (doctor)")
|
|
2570
|
+
.description("Run consolidated extension diagnostics (summary/deep modes).")).action(async (_options, command) => {
|
|
2571
|
+
await executeExtensionCommand(undefined, command.opts(), command, "doctor");
|
|
2572
|
+
});
|
|
2573
|
+
addExtensionScopeOptions(extensionCommand
|
|
2574
|
+
.command("adopt")
|
|
2575
|
+
.argument("<target>", "Extension name")
|
|
2576
|
+
.option("--gh <owner/repo[/path]>", "GitHub provenance shorthand for adopted extension")
|
|
2577
|
+
.option("--github <owner/repo[/path]>", "Alias for --gh")
|
|
2578
|
+
.option("--ref <ref>", "Git ref/branch/tag for GitHub shorthand source")
|
|
2579
|
+
.description("Adopt an existing unmanaged extension into managed metadata.")).action(async (target, _options, command) => {
|
|
2580
|
+
await executeExtensionCommand(target, command.opts(), command, "adopt");
|
|
2581
|
+
});
|
|
2582
|
+
addExtensionScopeOptions(extensionCommand.command("adopt-all").description("Adopt all unmanaged extensions into managed metadata.")).action(async (_options, command) => {
|
|
2583
|
+
await executeExtensionCommand(undefined, command.opts(), command, "adopt-all");
|
|
2584
|
+
});
|
|
2585
|
+
addExtensionScopeOptions(extensionCommand.command("activate").argument("<target>", "Extension name").description("Activate an extension in selected scope settings.")).action(async (target, _options, command) => {
|
|
2586
|
+
await executeExtensionCommand(target, command.opts(), command, "activate");
|
|
2587
|
+
});
|
|
2588
|
+
addExtensionScopeOptions(extensionCommand.command("deactivate").argument("<target>", "Extension name").description("Deactivate an extension in selected scope settings.")).action(async (target, _options, command) => {
|
|
2589
|
+
await executeExtensionCommand(target, command.opts(), command, "deactivate");
|
|
2590
|
+
});
|
|
2591
|
+
const templatesCommand = program.command("templates").description("Manage reusable create templates.");
|
|
2592
|
+
templatesCommand
|
|
2593
|
+
.command("save")
|
|
2594
|
+
.argument("<name>", "Template name")
|
|
2595
|
+
.option("--title, -t <value>", "Template default item title")
|
|
2596
|
+
.option("--description, -d <value>", "Template default item description")
|
|
2597
|
+
.option("--type <value>", "Template default item type")
|
|
2598
|
+
.option("--status, -s <value>", "Template default item status")
|
|
2599
|
+
.option("--priority, -p <value>", "Template default priority 0..4")
|
|
2600
|
+
.option("--tags <value>", "Template default comma-separated tags")
|
|
2601
|
+
.option("--body, -b <value>", "Template default item markdown body")
|
|
2602
|
+
.option("--deadline <value>", "Template default deadline")
|
|
2603
|
+
.option("--estimate, --estimated-minutes <value>", "Template default estimated minutes")
|
|
2604
|
+
.option("--estimated_minutes <value>", "Alias for --estimated-minutes")
|
|
2605
|
+
.option("--acceptance-criteria <value>", "Template default acceptance criteria")
|
|
2606
|
+
.option("--acceptance_criteria <value>", "Alias for --acceptance-criteria")
|
|
2607
|
+
.option("--ac <value>", "Alias for --acceptance-criteria")
|
|
2608
|
+
.option("--definition-of-ready <value>", "Template default definition of ready")
|
|
2609
|
+
.option("--definition_of_ready <value>", "Alias for --definition-of-ready")
|
|
2610
|
+
.option("--order <value>", "Template default planning order/rank integer")
|
|
2611
|
+
.option("--rank <value>", "Alias for --order")
|
|
2612
|
+
.option("--goal <value>", "Template default goal identifier")
|
|
2613
|
+
.option("--objective <value>", "Template default objective identifier")
|
|
2614
|
+
.option("--value <value>", "Template default business value summary")
|
|
2615
|
+
.option("--impact <value>", "Template default business impact summary")
|
|
2616
|
+
.option("--outcome <value>", "Template default expected outcome summary")
|
|
2617
|
+
.option("--why-now <value>", "Template default why-now rationale")
|
|
2618
|
+
.option("--why_now <value>", "Alias for --why-now")
|
|
2619
|
+
.option("--author <value>", "Template default mutation author")
|
|
2620
|
+
.option("--message <value>", "Template default history message")
|
|
2621
|
+
.option("--assignee <value>", "Template default assignee")
|
|
2622
|
+
.option("--parent <value>", "Template default parent item ID")
|
|
2623
|
+
.option("--reviewer <value>", "Template default reviewer")
|
|
2624
|
+
.option("--risk <value>", "Template default risk level")
|
|
2625
|
+
.option("--confidence <value>", "Template default confidence")
|
|
2626
|
+
.option("--sprint <value>", "Template default sprint identifier")
|
|
2627
|
+
.option("--release <value>", "Template default release identifier")
|
|
2628
|
+
.option("--blocked-by <value>", "Template default blocked-by item ID or reason")
|
|
2629
|
+
.option("--blocked_by <value>", "Alias for --blocked-by")
|
|
2630
|
+
.option("--blocked-reason <value>", "Template default blocked reason")
|
|
2631
|
+
.option("--blocked_reason <value>", "Alias for --blocked-reason")
|
|
2632
|
+
.option("--unblock-note <value>", "Template default unblock rationale note")
|
|
2633
|
+
.option("--unblock_note <value>", "Alias for --unblock-note")
|
|
2634
|
+
.option("--reporter <value>", "Template default issue reporter")
|
|
2635
|
+
.option("--severity <value>", "Template default issue severity")
|
|
2636
|
+
.option("--environment <value>", "Template default issue environment context")
|
|
2637
|
+
.option("--repro-steps <value>", "Template default issue reproduction steps")
|
|
2638
|
+
.option("--repro_steps <value>", "Alias for --repro-steps")
|
|
2639
|
+
.option("--resolution <value>", "Template default issue resolution summary")
|
|
2640
|
+
.option("--expected-result <value>", "Template default issue expected behavior")
|
|
2641
|
+
.option("--expected_result <value>", "Alias for --expected-result")
|
|
2642
|
+
.option("--actual-result <value>", "Template default issue observed behavior")
|
|
2643
|
+
.option("--actual_result <value>", "Alias for --actual-result")
|
|
2644
|
+
.option("--affected-version <value>", "Template default affected version identifier")
|
|
2645
|
+
.option("--affected_version <value>", "Alias for --affected-version")
|
|
2646
|
+
.option("--fixed-version <value>", "Template default fixed version identifier")
|
|
2647
|
+
.option("--fixed_version <value>", "Alias for --fixed-version")
|
|
2648
|
+
.option("--component <value>", "Template default issue component ownership")
|
|
2649
|
+
.option("--regression <value>", "Template default regression marker")
|
|
2650
|
+
.option("--customer-impact <value>", "Template default customer impact summary")
|
|
2651
|
+
.option("--customer_impact <value>", "Alias for --customer-impact")
|
|
2652
|
+
.option("--dep <value>", "Template default dependency entry (repeatable; CSV/markdown pairs or - for stdin)", collect)
|
|
2653
|
+
.option("--type-option <value>", "Template default type option entry (repeatable; key=value or markdown pairs; use - for stdin)", collect)
|
|
2654
|
+
.option("--type_option <value>", "Alias for --type-option", collect)
|
|
2655
|
+
.option("--reminder <value>", "Template default reminder entry (repeatable; at=<iso|relative>,text=<text>)", collect)
|
|
2656
|
+
.option("--event <value>", "Template default event entry (repeatable; start/end/title/recur_* fields)", collect)
|
|
2657
|
+
.option("--comment <value>", "Template default comment seed entry (repeatable; text=<value> CSV/markdown pairs or - for stdin)", collect)
|
|
2658
|
+
.option("--note <value>", "Template default note seed entry (repeatable; text=<value> CSV/markdown pairs or - for stdin)", collect)
|
|
2659
|
+
.option("--learning <value>", "Template default learning seed entry (repeatable; text=<value> CSV/markdown pairs or - for stdin)", collect)
|
|
2660
|
+
.option("--file <value>", "Template default linked file entry (repeatable; CSV/markdown pairs or - for stdin)", collect)
|
|
2661
|
+
.option("--test <value>", "Template default linked test entry (repeatable; CSV/markdown pairs or - for stdin)", collect)
|
|
2662
|
+
.option("--doc <value>", "Template default linked doc entry (repeatable; CSV/markdown pairs or - for stdin)", collect)
|
|
2663
|
+
.description("Save or update a named create template.")
|
|
2664
|
+
.action(async (name, options, command) => {
|
|
819
2665
|
const globalOptions = getGlobalOptions(command);
|
|
820
2666
|
const startedAt = Date.now();
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
2667
|
+
const normalized = normalizeCreateOptions(options, { requireType: false });
|
|
2668
|
+
const result = await runTemplatesSave(name, normalized, globalOptions);
|
|
2669
|
+
printResult(result, globalOptions);
|
|
2670
|
+
if (globalOptions.profile) {
|
|
2671
|
+
printError(`profile:command=templates save took_ms=${Date.now() - startedAt}`);
|
|
2672
|
+
}
|
|
2673
|
+
});
|
|
2674
|
+
templatesCommand
|
|
2675
|
+
.command("list")
|
|
2676
|
+
.description("List saved create templates.")
|
|
2677
|
+
.action(async (_options, command) => {
|
|
2678
|
+
const globalOptions = getGlobalOptions(command);
|
|
2679
|
+
const startedAt = Date.now();
|
|
2680
|
+
const result = await runTemplatesList(globalOptions);
|
|
2681
|
+
printResult(result, globalOptions);
|
|
2682
|
+
if (globalOptions.profile) {
|
|
2683
|
+
printError(`profile:command=templates list took_ms=${Date.now() - startedAt}`);
|
|
2684
|
+
}
|
|
2685
|
+
});
|
|
2686
|
+
templatesCommand
|
|
2687
|
+
.command("show")
|
|
2688
|
+
.argument("<name>", "Template name")
|
|
2689
|
+
.description("Show saved create template details.")
|
|
2690
|
+
.action(async (name, _options, command) => {
|
|
2691
|
+
const globalOptions = getGlobalOptions(command);
|
|
2692
|
+
const startedAt = Date.now();
|
|
2693
|
+
const result = await runTemplatesShow(name, globalOptions);
|
|
825
2694
|
printResult(result, globalOptions);
|
|
826
2695
|
if (globalOptions.profile) {
|
|
827
|
-
printError(`profile:command=
|
|
2696
|
+
printError(`profile:command=templates show took_ms=${Date.now() - startedAt}`);
|
|
828
2697
|
}
|
|
829
2698
|
});
|
|
830
2699
|
program
|
|
@@ -832,72 +2701,92 @@ program
|
|
|
832
2701
|
.description("Create a new project management item.")
|
|
833
2702
|
.requiredOption("--title, -t <value>", "Item title")
|
|
834
2703
|
.requiredOption("--description, -d <value>", "Item description (allow empty string)")
|
|
835
|
-
.
|
|
836
|
-
.
|
|
837
|
-
.
|
|
838
|
-
.
|
|
839
|
-
.
|
|
840
|
-
.
|
|
841
|
-
.option("--
|
|
2704
|
+
.option("--type <value>", "Item type (built-ins plus any configured custom types)")
|
|
2705
|
+
.option("--template <value>", "Apply named create template defaults before explicit flags")
|
|
2706
|
+
.option("--create-mode <value>", "Create required-option policy mode: strict|progressive")
|
|
2707
|
+
.option("--create_mode <value>", "Alias for --create-mode")
|
|
2708
|
+
.option("--schedule-preset <value>", "Scheduling preset for Reminder|Meeting|Event: lightweight")
|
|
2709
|
+
.option("--schedule_preset <value>", "Alias for --schedule-preset")
|
|
2710
|
+
.option("--status, -s <value>", "Item status")
|
|
2711
|
+
.option("--priority, -p <value>", "Priority 0..4")
|
|
2712
|
+
.option("--tags <value>", "Comma-separated tags")
|
|
2713
|
+
.option("--body, -b <value>", "Item markdown body (allow empty string)")
|
|
2714
|
+
.option("--deadline <value>", "Deadline (ISO/date string or relative +6h/+1d/+2w/+6m)")
|
|
2715
|
+
.option("--estimate, --estimated-minutes <value>", "Estimated minutes")
|
|
842
2716
|
.option("--estimated_minutes <value>", "Alias for --estimated-minutes")
|
|
843
2717
|
.option("--acceptance-criteria <value>", "Acceptance criteria (allow empty string)")
|
|
844
2718
|
.option("--acceptance_criteria <value>", "Alias for --acceptance-criteria")
|
|
845
2719
|
.option("--ac <value>", "Alias for --acceptance-criteria")
|
|
846
|
-
.option("--definition-of-ready <value>", "Definition of ready (allow empty string
|
|
2720
|
+
.option("--definition-of-ready <value>", "Definition of ready (allow empty string)")
|
|
847
2721
|
.option("--definition_of_ready <value>", "Alias for --definition-of-ready")
|
|
848
|
-
.option("--order <value>", "Planning order/rank integer
|
|
2722
|
+
.option("--order <value>", "Planning order/rank integer")
|
|
849
2723
|
.option("--rank <value>", "Alias for --order")
|
|
850
|
-
.option("--goal <value>", "Goal identifier
|
|
851
|
-
.option("--objective <value>", "Objective identifier
|
|
852
|
-
.option("--value <value>", "Business value summary
|
|
853
|
-
.option("--impact <value>", "Business impact summary
|
|
854
|
-
.option("--outcome <value>", "Expected outcome summary
|
|
855
|
-
.option("--why-now <value>", "Why-now rationale
|
|
2724
|
+
.option("--goal <value>", "Goal identifier")
|
|
2725
|
+
.option("--objective <value>", "Objective identifier")
|
|
2726
|
+
.option("--value <value>", "Business value summary")
|
|
2727
|
+
.option("--impact <value>", "Business impact summary")
|
|
2728
|
+
.option("--outcome <value>", "Expected outcome summary")
|
|
2729
|
+
.option("--why-now <value>", "Why-now rationale")
|
|
856
2730
|
.option("--why_now <value>", "Alias for --why-now")
|
|
857
|
-
.
|
|
858
|
-
.
|
|
859
|
-
.
|
|
860
|
-
.option("--parent <value>", "Parent item ID
|
|
861
|
-
.option("--reviewer <value>", "Reviewer
|
|
862
|
-
.option("--risk <value>", "Risk level: low|med|medium|high|critical
|
|
863
|
-
.option("--confidence <value>", "Confidence level: 0..100|low|med|medium|high
|
|
864
|
-
.option("--sprint <value>", "Sprint identifier
|
|
865
|
-
.option("--release <value>", "Release identifier
|
|
866
|
-
.option("--blocked-by <value>", "Blocked-by item ID or reason
|
|
2731
|
+
.option("--author <value>", "Mutation author")
|
|
2732
|
+
.option("--message <value>", "History message (allow empty string)")
|
|
2733
|
+
.option("--assignee <value>", "Item assignee")
|
|
2734
|
+
.option("--parent <value>", "Parent item ID")
|
|
2735
|
+
.option("--reviewer <value>", "Reviewer")
|
|
2736
|
+
.option("--risk <value>", "Risk level: low|med|medium|high|critical (med persists as medium)")
|
|
2737
|
+
.option("--confidence <value>", "Confidence level: 0..100|low|med|medium|high (med persists as medium)")
|
|
2738
|
+
.option("--sprint <value>", "Sprint identifier")
|
|
2739
|
+
.option("--release <value>", "Release identifier")
|
|
2740
|
+
.option("--blocked-by <value>", "Blocked-by item ID or reason")
|
|
867
2741
|
.option("--blocked_by <value>", "Alias for --blocked-by")
|
|
868
|
-
.option("--blocked-reason <value>", "Blocked reason
|
|
2742
|
+
.option("--blocked-reason <value>", "Blocked reason")
|
|
869
2743
|
.option("--blocked_reason <value>", "Alias for --blocked-reason")
|
|
870
|
-
.option("--unblock-note <value>", "Unblock rationale note
|
|
2744
|
+
.option("--unblock-note <value>", "Unblock rationale note")
|
|
871
2745
|
.option("--unblock_note <value>", "Alias for --unblock-note")
|
|
872
|
-
.option("--reporter <value>", "Issue reporter
|
|
873
|
-
.option("--severity <value>", "Issue severity: low|med|medium|high|critical
|
|
874
|
-
.option("--environment <value>", "Issue environment context
|
|
875
|
-
.option("--repro-steps <value>", "Issue reproduction steps
|
|
2746
|
+
.option("--reporter <value>", "Issue reporter")
|
|
2747
|
+
.option("--severity <value>", "Issue severity: low|med|medium|high|critical (med persists as medium)")
|
|
2748
|
+
.option("--environment <value>", "Issue environment context")
|
|
2749
|
+
.option("--repro-steps <value>", "Issue reproduction steps")
|
|
876
2750
|
.option("--repro_steps <value>", "Alias for --repro-steps")
|
|
877
|
-
.option("--resolution <value>", "Issue resolution summary
|
|
878
|
-
.option("--expected-result <value>", "Issue expected behavior
|
|
2751
|
+
.option("--resolution <value>", "Issue resolution summary")
|
|
2752
|
+
.option("--expected-result <value>", "Issue expected behavior")
|
|
879
2753
|
.option("--expected_result <value>", "Alias for --expected-result")
|
|
880
|
-
.option("--actual-result <value>", "Issue observed behavior
|
|
2754
|
+
.option("--actual-result <value>", "Issue observed behavior")
|
|
881
2755
|
.option("--actual_result <value>", "Alias for --actual-result")
|
|
882
|
-
.option("--affected-version <value>", "Affected version identifier
|
|
2756
|
+
.option("--affected-version <value>", "Affected version identifier")
|
|
883
2757
|
.option("--affected_version <value>", "Alias for --affected-version")
|
|
884
|
-
.option("--fixed-version <value>", "Fixed version identifier
|
|
2758
|
+
.option("--fixed-version <value>", "Fixed version identifier")
|
|
885
2759
|
.option("--fixed_version <value>", "Alias for --fixed-version")
|
|
886
|
-
.option("--component <value>", "Issue component ownership
|
|
887
|
-
.option("--regression <value>", "Regression marker: true|false|1|0
|
|
888
|
-
.option("--customer-impact <value>", "Customer impact summary
|
|
2760
|
+
.option("--component <value>", "Issue component ownership")
|
|
2761
|
+
.option("--regression <value>", "Regression marker: true|false|1|0")
|
|
2762
|
+
.option("--customer-impact <value>", "Customer impact summary")
|
|
889
2763
|
.option("--customer_impact <value>", "Alias for --customer-impact")
|
|
890
|
-
.option("--dep <value>", "Seed dependency entry (
|
|
891
|
-
.option("--
|
|
892
|
-
.option("--
|
|
893
|
-
.option("--
|
|
894
|
-
.option("--
|
|
895
|
-
.option("--
|
|
896
|
-
.option("--
|
|
2764
|
+
.option("--dep <value>", "Seed dependency entry (key=value CSV, markdown key:value lines, or - for stdin; repeatable)", collect)
|
|
2765
|
+
.option("--type-option <value>", "Type option key=value or key=<name>,value=<value> (also accepts key:value and markdown pairs; use - for stdin; repeatable)", collect)
|
|
2766
|
+
.option("--type_option <value>", "Alias for --type-option", collect)
|
|
2767
|
+
.option("--unset <field>", "Clear scalar metadata field by name (repeatable)", collect)
|
|
2768
|
+
.option("--clear-deps", "Clear dependency entries")
|
|
2769
|
+
.option("--clear-comments", "Clear comments")
|
|
2770
|
+
.option("--clear-notes", "Clear notes")
|
|
2771
|
+
.option("--clear-learnings", "Clear learnings")
|
|
2772
|
+
.option("--clear-files", "Clear linked files")
|
|
2773
|
+
.option("--clear-tests", "Clear linked tests")
|
|
2774
|
+
.option("--clear-docs", "Clear linked docs")
|
|
2775
|
+
.option("--clear-reminders", "Clear reminders")
|
|
2776
|
+
.option("--clear-events", "Clear events")
|
|
2777
|
+
.option("--clear-type-options", "Clear type options")
|
|
2778
|
+
.option("--reminder <value>", "Seed reminder entry at=<iso|relative>,text=<text> (also accepts markdown pairs and - for stdin; repeatable)", collect)
|
|
2779
|
+
.option("--event <value>", "Seed event entry start=<iso|relative>,end=<iso|relative>,title=<text>,all_day=<true|false>,recur_* fields (also accepts markdown pairs and - for stdin; repeatable)", collect)
|
|
2780
|
+
.option("--comment <value>", "Seed comment entry (text=<value> CSV/markdown pairs or - for stdin; repeatable)", collect)
|
|
2781
|
+
.option("--note <value>", "Seed note entry (text=<value> CSV/markdown pairs or - for stdin; repeatable)", collect)
|
|
2782
|
+
.option("--learning <value>", "Seed learning entry (text=<value> CSV/markdown pairs or - for stdin; repeatable)", collect)
|
|
2783
|
+
.option("--file <value>", "Seed linked file entry (CSV/markdown pairs or - for stdin; repeatable)", collect)
|
|
2784
|
+
.option("--test <value>", "Seed linked test entry (CSV/markdown pairs or - for stdin; repeatable)", collect)
|
|
2785
|
+
.option("--doc <value>", "Seed linked doc entry (CSV/markdown pairs or - for stdin; repeatable)", collect)
|
|
897
2786
|
.action(async (options, command) => {
|
|
898
2787
|
const globalOptions = getGlobalOptions(command);
|
|
899
2788
|
const startedAt = Date.now();
|
|
900
|
-
const normalized = normalizeCreateOptions(options);
|
|
2789
|
+
const normalized = normalizeCreateOptions(options, { requireType: false });
|
|
901
2790
|
const result = await runCreate(normalized, globalOptions);
|
|
902
2791
|
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
903
2792
|
printResult(result, globalOptions);
|
|
@@ -905,34 +2794,55 @@ program
|
|
|
905
2794
|
printError(`profile:command=create took_ms=${Date.now() - startedAt}`);
|
|
906
2795
|
}
|
|
907
2796
|
});
|
|
908
|
-
function registerListCommand(name, description, status, excludeTerminal) {
|
|
909
|
-
program
|
|
910
|
-
|
|
911
|
-
.
|
|
2797
|
+
function registerListCommand(name, description, status, excludeTerminal, allowStatusFilter) {
|
|
2798
|
+
const command = program.command(name).description(description);
|
|
2799
|
+
if (allowStatusFilter) {
|
|
2800
|
+
command.option("--status <value>", "Filter by status");
|
|
2801
|
+
}
|
|
2802
|
+
command
|
|
912
2803
|
.option("--type <value>", "Filter by item type")
|
|
913
2804
|
.option("--tag <value>", "Filter by tag")
|
|
914
2805
|
.option("--priority <value>", "Filter by priority")
|
|
915
|
-
.option("--deadline-before <value>", "Filter by deadline upper bound")
|
|
916
|
-
.option("--deadline-after <value>", "Filter by deadline lower bound")
|
|
917
|
-
.option("--assignee <value>", "Filter by assignee
|
|
2806
|
+
.option("--deadline-before <value>", "Filter by deadline upper bound (ISO/date string or relative)")
|
|
2807
|
+
.option("--deadline-after <value>", "Filter by deadline lower bound (ISO/date string or relative)")
|
|
2808
|
+
.option("--assignee <value>", "Filter by assignee")
|
|
2809
|
+
.option("--assignee-filter <value>", "Filter assignee presence: assigned|unassigned")
|
|
2810
|
+
.option("--assignee_filter <value>", "Alias for --assignee-filter")
|
|
2811
|
+
.option("--parent <value>", "Filter by parent item ID")
|
|
918
2812
|
.option("--sprint <value>", "Filter by sprint")
|
|
919
2813
|
.option("--release <value>", "Filter by release")
|
|
920
2814
|
.option("--limit <n>", "Limit returned item count")
|
|
921
|
-
.
|
|
922
|
-
|
|
2815
|
+
.option("--offset <n>", "Skip the first n matching rows before limit is applied")
|
|
2816
|
+
.option("--include-body", "Include item body in each returned list row")
|
|
2817
|
+
.option("--compact", "Render compact list projection fields (mutually exclusive with --fields)")
|
|
2818
|
+
.option("--fields <value>", "Render custom comma-separated list fields (mutually exclusive with --compact; valid: --fields id,title; invalid: --compact --fields id,title)")
|
|
2819
|
+
.option("--sort <value>", "Sort field: priority|deadline|updated_at|created_at|title|parent")
|
|
2820
|
+
.option("--order <value>", "Sort order: asc|desc (requires --sort)")
|
|
2821
|
+
.option("--stream", "Emit line-delimited JSON rows (requires --json)")
|
|
2822
|
+
.action(async (options, actionCommand) => {
|
|
2823
|
+
const globalOptions = getGlobalOptions(actionCommand);
|
|
923
2824
|
const startedAt = Date.now();
|
|
924
2825
|
const listOptions = normalizeListOptions(options);
|
|
925
2826
|
if (excludeTerminal)
|
|
926
2827
|
listOptions.excludeTerminal = true;
|
|
927
2828
|
const result = await runList(status, listOptions, globalOptions);
|
|
928
|
-
|
|
2829
|
+
const streamMode = options.stream === true;
|
|
2830
|
+
if (streamMode && !globalOptions.json) {
|
|
2831
|
+
throw new PmCliError("--stream requires --json output mode.", EXIT_CODE.USAGE);
|
|
2832
|
+
}
|
|
2833
|
+
if (streamMode) {
|
|
2834
|
+
printListJsonStream(name, result, globalOptions);
|
|
2835
|
+
}
|
|
2836
|
+
else {
|
|
2837
|
+
printResult(result, globalOptions);
|
|
2838
|
+
}
|
|
929
2839
|
if (globalOptions.profile) {
|
|
930
2840
|
printError(`profile:command=${name} took_ms=${Date.now() - startedAt}`);
|
|
931
2841
|
}
|
|
932
2842
|
});
|
|
933
2843
|
}
|
|
934
|
-
registerListCommand("list", "List active items with optional filters.", undefined, true);
|
|
935
|
-
registerListCommand("list-all", "List all items with optional filters.");
|
|
2844
|
+
registerListCommand("list", "List active items with optional filters.", undefined, true, true);
|
|
2845
|
+
registerListCommand("list-all", "List all items with optional filters.", undefined, false, true);
|
|
936
2846
|
registerListCommand("list-draft", "List draft items with optional filters.", "draft");
|
|
937
2847
|
registerListCommand("list-open", "List open items with optional filters.", "open");
|
|
938
2848
|
registerListCommand("list-in-progress", "List in-progress items with optional filters.", "in_progress");
|
|
@@ -940,70 +2850,176 @@ registerListCommand("list-blocked", "List blocked items with optional filters.",
|
|
|
940
2850
|
registerListCommand("list-closed", "List closed items with optional filters.", "closed");
|
|
941
2851
|
registerListCommand("list-canceled", "List canceled items with optional filters.", "canceled");
|
|
942
2852
|
program
|
|
943
|
-
.command("
|
|
944
|
-
.description("
|
|
945
|
-
.
|
|
946
|
-
.
|
|
947
|
-
.option("--
|
|
948
|
-
.option("--
|
|
949
|
-
.option("--
|
|
950
|
-
.option("--
|
|
951
|
-
.
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
const todosCommand = program.command("todos").description("Built-in todos extension commands.");
|
|
962
|
-
todosCommand
|
|
963
|
-
.command("import")
|
|
964
|
-
.description("Import todos markdown files as PM items.")
|
|
965
|
-
.option("--folder <path>", "Path to todos markdown folder", ".pi/todos")
|
|
966
|
-
.option("--author <value>", "Mutation author")
|
|
967
|
-
.option("--message <value>", "History message for import entries")
|
|
2853
|
+
.command("aggregate")
|
|
2854
|
+
.description("Aggregate grouped item counts for governance queries.")
|
|
2855
|
+
.option("--group-by <value>", "Comma-separated group-by fields (supported: parent,type,priority,status,assignee,tags,sprint,release)")
|
|
2856
|
+
.option("--count", "Return grouped counts (default behavior)")
|
|
2857
|
+
.option("--include-unparented", "Include unparented rows when grouping by parent")
|
|
2858
|
+
.option("--include_unparented", "Alias for --include-unparented")
|
|
2859
|
+
.option("--status <value>", "Filter by item status")
|
|
2860
|
+
.option("--type <value>", "Filter by item type")
|
|
2861
|
+
.option("--tag <value>", "Filter by tag")
|
|
2862
|
+
.option("--priority <value>", "Filter by priority")
|
|
2863
|
+
.option("--deadline-before <value>", "Filter by deadline upper bound (ISO/date string or relative)")
|
|
2864
|
+
.option("--deadline-after <value>", "Filter by deadline lower bound (ISO/date string or relative)")
|
|
2865
|
+
.option("--assignee <value>", "Filter by assignee")
|
|
2866
|
+
.option("--assignee-filter <value>", "Filter assignee presence: assigned|unassigned")
|
|
2867
|
+
.option("--assignee_filter <value>", "Alias for --assignee-filter")
|
|
2868
|
+
.option("--parent <value>", "Filter by parent item ID")
|
|
2869
|
+
.option("--sprint <value>", "Filter by sprint")
|
|
2870
|
+
.option("--release <value>", "Filter by release")
|
|
968
2871
|
.action(async (options, command) => {
|
|
969
2872
|
const globalOptions = getGlobalOptions(command);
|
|
970
2873
|
const startedAt = Date.now();
|
|
971
|
-
const result = await
|
|
972
|
-
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
2874
|
+
const result = await runAggregate(normalizeAggregateOptions(options), globalOptions);
|
|
973
2875
|
printResult(result, globalOptions);
|
|
974
2876
|
if (globalOptions.profile) {
|
|
975
|
-
printError(`profile:command=
|
|
2877
|
+
printError(`profile:command=aggregate took_ms=${Date.now() - startedAt}`);
|
|
976
2878
|
}
|
|
977
2879
|
});
|
|
978
|
-
|
|
979
|
-
.command("
|
|
980
|
-
.description("
|
|
981
|
-
.option("--
|
|
2880
|
+
program
|
|
2881
|
+
.command("dedupe-audit")
|
|
2882
|
+
.description("Audit potential duplicate items with exact, fuzzy, or parent-scoped matching.")
|
|
2883
|
+
.option("--mode <value>", "Dedupe mode: title_exact|title_fuzzy|parent_scope")
|
|
2884
|
+
.option("--limit <n>", "Limit returned duplicate clusters")
|
|
2885
|
+
.option("--threshold <value>", "Fuzzy mode token similarity threshold between 0 and 1")
|
|
2886
|
+
.option("--status <value>", "Filter by item status")
|
|
2887
|
+
.option("--type <value>", "Filter by item type")
|
|
2888
|
+
.option("--tag <value>", "Filter by tag")
|
|
2889
|
+
.option("--priority <value>", "Filter by priority")
|
|
2890
|
+
.option("--deadline-before <value>", "Filter by deadline upper bound (ISO/date string or relative)")
|
|
2891
|
+
.option("--deadline-after <value>", "Filter by deadline lower bound (ISO/date string or relative)")
|
|
2892
|
+
.option("--assignee <value>", "Filter by assignee")
|
|
2893
|
+
.option("--assignee-filter <value>", "Filter assignee presence: assigned|unassigned")
|
|
2894
|
+
.option("--assignee_filter <value>", "Alias for --assignee-filter")
|
|
2895
|
+
.option("--parent <value>", "Filter by parent item ID")
|
|
2896
|
+
.option("--sprint <value>", "Filter by sprint")
|
|
2897
|
+
.option("--release <value>", "Filter by release")
|
|
982
2898
|
.action(async (options, command) => {
|
|
983
2899
|
const globalOptions = getGlobalOptions(command);
|
|
984
2900
|
const startedAt = Date.now();
|
|
985
|
-
const result = await
|
|
2901
|
+
const result = await runDedupeAudit(normalizeDedupeAuditOptions(options), globalOptions);
|
|
986
2902
|
printResult(result, globalOptions);
|
|
987
2903
|
if (globalOptions.profile) {
|
|
988
|
-
printError(`profile:command=
|
|
2904
|
+
printError(`profile:command=dedupe-audit took_ms=${Date.now() - startedAt}`);
|
|
989
2905
|
}
|
|
990
2906
|
});
|
|
2907
|
+
function registerCalendarCommand() {
|
|
2908
|
+
program
|
|
2909
|
+
.command("calendar")
|
|
2910
|
+
.alias("cal")
|
|
2911
|
+
.description("Show deadline/reminder calendar views (agenda/day/week/month).")
|
|
2912
|
+
.option("--view <value>", "Calendar view: agenda|day|week|month (default: agenda)")
|
|
2913
|
+
.option("--date <value>", "Anchor date/time for view calculations (ISO/date string or relative)")
|
|
2914
|
+
.option("--from <value>", "Agenda lower bound (ISO/date string or relative)")
|
|
2915
|
+
.option("--to <value>", "Agenda upper bound (ISO/date string or relative)")
|
|
2916
|
+
.option("--past", "Include past entries in the selected view")
|
|
2917
|
+
.option("--full-period", "For day/week/month views, include the full anchored period without now-clipping")
|
|
2918
|
+
.option("--full_period", "Alias for --full-period")
|
|
2919
|
+
.option("--type <value>", "Filter by item type")
|
|
2920
|
+
.option("--tag <value>", "Filter by tag")
|
|
2921
|
+
.option("--priority <value>", "Filter by priority")
|
|
2922
|
+
.option("--status <value>", "Filter by status")
|
|
2923
|
+
.option("--assignee <value>", "Filter by assignee")
|
|
2924
|
+
.option("--assignee-filter <value>", "Filter assignee presence: assigned|unassigned")
|
|
2925
|
+
.option("--assignee_filter <value>", "Alias for --assignee-filter")
|
|
2926
|
+
.option("--sprint <value>", "Filter by sprint")
|
|
2927
|
+
.option("--release <value>", "Filter by release")
|
|
2928
|
+
.option("--include <value>", "Include sources: deadlines|reminders|events|all (comma or | separated)")
|
|
2929
|
+
.option("--recurrence-lookahead-days <n>", "Bound open-ended recurrence generation lookahead days")
|
|
2930
|
+
.option("--recurrence_lookahead_days <n>", "Alias for --recurrence-lookahead-days")
|
|
2931
|
+
.option("--recurrence-lookback-days <n>", "Bound open-ended recurrence generation lookback days")
|
|
2932
|
+
.option("--recurrence_lookback_days <n>", "Alias for --recurrence-lookback-days")
|
|
2933
|
+
.option("--occurrence-limit <n>", "Cap generated occurrences per recurring event")
|
|
2934
|
+
.option("--occurrence_limit <n>", "Alias for --occurrence-limit")
|
|
2935
|
+
.option("--limit <n>", "Limit returned event count")
|
|
2936
|
+
.option("--format <value>", "Calendar output format override: markdown|toon|json")
|
|
2937
|
+
.action(async (options, actionCommand) => {
|
|
2938
|
+
const globalOptions = getGlobalOptions(actionCommand);
|
|
2939
|
+
const startedAt = Date.now();
|
|
2940
|
+
const normalized = normalizeCalendarOptions(options);
|
|
2941
|
+
const result = await runCalendar(normalized, globalOptions);
|
|
2942
|
+
const outputFormat = resolveCalendarOutputFormat(normalized, globalOptions);
|
|
2943
|
+
if (outputFormat === "markdown") {
|
|
2944
|
+
if (!globalOptions.quiet) {
|
|
2945
|
+
writeStdout(`${renderCalendarMarkdown(result)}\n`);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
else {
|
|
2949
|
+
printResult(result, {
|
|
2950
|
+
...globalOptions,
|
|
2951
|
+
json: outputFormat === "json",
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2954
|
+
if (globalOptions.profile) {
|
|
2955
|
+
printError(`profile:command=calendar took_ms=${Date.now() - startedAt}`);
|
|
2956
|
+
}
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2959
|
+
registerCalendarCommand();
|
|
2960
|
+
function registerContextCommand() {
|
|
2961
|
+
program
|
|
2962
|
+
.command("context")
|
|
2963
|
+
.alias("ctx")
|
|
2964
|
+
.description("Show a token-efficient project context snapshot for next-work decisions.")
|
|
2965
|
+
.option("--date <value>", "Anchor date/time for agenda window calculations (ISO/date string or relative)")
|
|
2966
|
+
.option("--from <value>", "Agenda lower bound (ISO/date string or relative)")
|
|
2967
|
+
.option("--to <value>", "Agenda upper bound (ISO/date string or relative)")
|
|
2968
|
+
.option("--past", "Include past agenda entries in bounded windows")
|
|
2969
|
+
.option("--type <value>", "Filter by item type")
|
|
2970
|
+
.option("--tag <value>", "Filter by tag")
|
|
2971
|
+
.option("--priority <value>", "Filter by priority")
|
|
2972
|
+
.option("--assignee <value>", "Filter by assignee")
|
|
2973
|
+
.option("--assignee-filter <value>", "Filter assignee presence: assigned|unassigned")
|
|
2974
|
+
.option("--assignee_filter <value>", "Alias for --assignee-filter")
|
|
2975
|
+
.option("--sprint <value>", "Filter by sprint")
|
|
2976
|
+
.option("--release <value>", "Filter by release")
|
|
2977
|
+
.option("--limit <n>", "Limit focus and agenda rows per section")
|
|
2978
|
+
.option("--format <value>", "Context output format override: markdown|toon|json")
|
|
2979
|
+
.action(async (options, actionCommand) => {
|
|
2980
|
+
const globalOptions = getGlobalOptions(actionCommand);
|
|
2981
|
+
const startedAt = Date.now();
|
|
2982
|
+
const normalized = normalizeContextOptions(options);
|
|
2983
|
+
const result = await runContext(normalized, globalOptions);
|
|
2984
|
+
const outputFormat = resolveContextOutputFormat(normalized, globalOptions);
|
|
2985
|
+
if (outputFormat === "markdown") {
|
|
2986
|
+
if (!globalOptions.quiet) {
|
|
2987
|
+
writeStdout(`${renderContextMarkdown(result)}\n`);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
else {
|
|
2991
|
+
printResult(result, {
|
|
2992
|
+
...globalOptions,
|
|
2993
|
+
json: outputFormat === "json",
|
|
2994
|
+
});
|
|
2995
|
+
}
|
|
2996
|
+
if (globalOptions.profile) {
|
|
2997
|
+
printError(`profile:command=context took_ms=${Date.now() - startedAt}`);
|
|
2998
|
+
}
|
|
2999
|
+
});
|
|
3000
|
+
}
|
|
3001
|
+
registerContextCommand();
|
|
991
3002
|
program
|
|
992
3003
|
.command("search")
|
|
993
|
-
.argument("<keywords
|
|
3004
|
+
.argument("<keywords...>", "Keyword query tokens")
|
|
994
3005
|
.description("Search items with keyword, semantic, or hybrid modes.")
|
|
995
|
-
.option("--mode <value>", "Search mode: keyword|semantic|hybrid (default: hybrid when semantic config
|
|
3006
|
+
.option("--mode <value>", "Search mode: keyword|semantic|hybrid (default: hybrid when semantic config or local Ollama auto-defaults are available, else keyword)")
|
|
996
3007
|
.option("--include-linked", "Include readable linked docs/files/tests content in keyword and hybrid lexical scoring")
|
|
3008
|
+
.option("--title-exact", "Require exact normalized title match against the full query")
|
|
3009
|
+
.option("--phrase-exact", "Require exact normalized query phrase match in item text fields")
|
|
997
3010
|
.option("--type <value>", "Filter by item type")
|
|
998
3011
|
.option("--tag <value>", "Filter by tag")
|
|
999
3012
|
.option("--priority <value>", "Filter by priority")
|
|
1000
|
-
.option("--deadline-before <value>", "Filter by deadline upper bound")
|
|
1001
|
-
.option("--deadline-after <value>", "Filter by deadline lower bound")
|
|
3013
|
+
.option("--deadline-before <value>", "Filter by deadline upper bound (ISO/date string or relative)")
|
|
3014
|
+
.option("--deadline-after <value>", "Filter by deadline lower bound (ISO/date string or relative)")
|
|
3015
|
+
.option("--compact", "Render compact search hits (default; mutually exclusive with --full/--fields)")
|
|
3016
|
+
.option("--full", "Render full search hits with nested item payloads (mutually exclusive with --compact/--fields)")
|
|
3017
|
+
.option("--fields <value>", "Render custom comma-separated search hit fields (mutually exclusive with --compact/--full; valid: --fields id,title,score; invalid: --full --fields id,title)")
|
|
1002
3018
|
.option("--limit <n>", "Limit returned item count")
|
|
1003
3019
|
.action(async (keywords, options, command) => {
|
|
1004
3020
|
const globalOptions = getGlobalOptions(command);
|
|
1005
3021
|
const startedAt = Date.now();
|
|
1006
|
-
const result = await runSearch(keywords, normalizeSearchOptions(options), globalOptions);
|
|
3022
|
+
const result = await runSearch(normalizeSearchKeywordsInput(keywords), normalizeSearchOptions(options), globalOptions);
|
|
1007
3023
|
printResult(result, globalOptions);
|
|
1008
3024
|
if (globalOptions.profile) {
|
|
1009
3025
|
printError(`profile:command=search took_ms=${Date.now() - startedAt}`);
|
|
@@ -1013,11 +3029,13 @@ program
|
|
|
1013
3029
|
.command("reindex")
|
|
1014
3030
|
.description("Rebuild search artifacts for keyword, semantic, and hybrid modes.")
|
|
1015
3031
|
.option("--mode <value>", "Reindex mode: keyword|semantic|hybrid", "keyword")
|
|
3032
|
+
.option("--progress", "Emit progress updates to stderr (always shown in TTY, opt-in for non-TTY)")
|
|
1016
3033
|
.action(async (options, command) => {
|
|
1017
3034
|
const globalOptions = getGlobalOptions(command);
|
|
1018
3035
|
const startedAt = Date.now();
|
|
1019
3036
|
const result = await runReindex({
|
|
1020
3037
|
mode: typeof options.mode === "string" ? options.mode : undefined,
|
|
3038
|
+
progress: Boolean(options.progress),
|
|
1021
3039
|
}, globalOptions);
|
|
1022
3040
|
printResult(result, globalOptions);
|
|
1023
3041
|
if (globalOptions.profile) {
|
|
@@ -1041,12 +3059,16 @@ program
|
|
|
1041
3059
|
.command("history")
|
|
1042
3060
|
.argument("<id>", "Item id")
|
|
1043
3061
|
.option("--limit <n>", "Return only the latest n history entries")
|
|
3062
|
+
.option("--diff", "Include per-entry changed field summaries from history patches")
|
|
3063
|
+
.option("--verify", "Verify hash chain and replay integrity for the full history stream")
|
|
1044
3064
|
.description("Show item history entries.")
|
|
1045
3065
|
.action(async (id, options, command) => {
|
|
1046
3066
|
const globalOptions = getGlobalOptions(command);
|
|
1047
3067
|
const startedAt = Date.now();
|
|
1048
3068
|
const result = await runHistory(id, {
|
|
1049
3069
|
limit: typeof options.limit === "string" ? options.limit : undefined,
|
|
3070
|
+
diff: Boolean(options.diff),
|
|
3071
|
+
verify: Boolean(options.verify),
|
|
1050
3072
|
}, globalOptions);
|
|
1051
3073
|
printResult(result, globalOptions);
|
|
1052
3074
|
if (globalOptions.profile) {
|
|
@@ -1055,15 +3077,29 @@ program
|
|
|
1055
3077
|
});
|
|
1056
3078
|
program
|
|
1057
3079
|
.command("activity")
|
|
3080
|
+
.option("--id <value>", "Filter by item ID")
|
|
3081
|
+
.option("--op <value>", "Filter by history operation")
|
|
3082
|
+
.option("--author <value>", "Filter by history author")
|
|
3083
|
+
.option("--from <value>", "Lower timestamp bound (ISO/date string or relative)")
|
|
3084
|
+
.option("--to <value>", "Upper timestamp bound (ISO/date string or relative)")
|
|
1058
3085
|
.option("--limit <n>", "Return only the latest n activity entries")
|
|
3086
|
+
.option("--stream [mode]", "Emit line-delimited JSON rows (requires --json). Optional mode: rows|ndjson|jsonl")
|
|
1059
3087
|
.description("Show recent activity across items.")
|
|
1060
3088
|
.action(async (options, command) => {
|
|
1061
3089
|
const globalOptions = getGlobalOptions(command);
|
|
1062
3090
|
const startedAt = Date.now();
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
3091
|
+
const normalized = normalizeActivityOptions(options);
|
|
3092
|
+
const result = await runActivity(normalized, globalOptions);
|
|
3093
|
+
const streamMode = resolveActivityStreamMode(options.stream);
|
|
3094
|
+
if (streamMode && !globalOptions.json) {
|
|
3095
|
+
throw new PmCliError("--stream requires --json output mode.", EXIT_CODE.USAGE);
|
|
3096
|
+
}
|
|
3097
|
+
if (streamMode) {
|
|
3098
|
+
printActivityJsonStream(result, normalized, globalOptions);
|
|
3099
|
+
}
|
|
3100
|
+
else {
|
|
3101
|
+
printResult(result, globalOptions);
|
|
3102
|
+
}
|
|
1067
3103
|
if (globalOptions.profile) {
|
|
1068
3104
|
printError(`profile:command=activity took_ms=${Date.now() - startedAt}`);
|
|
1069
3105
|
}
|
|
@@ -1096,60 +3132,93 @@ program
|
|
|
1096
3132
|
.description("Update item fields and metadata.")
|
|
1097
3133
|
.option("--title, -t <value>", "Set title")
|
|
1098
3134
|
.option("--description, -d <value>", "Set description")
|
|
3135
|
+
.option("--body, -b <value>", "Set body (allow empty string)")
|
|
1099
3136
|
.option("--status, -s <value>", "Set status (use close command for closed)")
|
|
3137
|
+
.option("--close-reason <value>", "Set close reason")
|
|
3138
|
+
.option("--close_reason <value>", "Alias for --close-reason")
|
|
1100
3139
|
.option("--priority, -p <value>", "Set priority")
|
|
1101
3140
|
.option("--type <value>", "Set type")
|
|
1102
3141
|
.option("--tags <value>", "Set comma-separated tags")
|
|
1103
|
-
.option("--deadline <value>", "Set deadline (or
|
|
1104
|
-
.option("--estimate, --estimated-minutes <value>", "Set estimated minutes
|
|
3142
|
+
.option("--deadline <value>", "Set deadline (ISO/date string or relative)")
|
|
3143
|
+
.option("--estimate, --estimated-minutes <value>", "Set estimated minutes")
|
|
1105
3144
|
.option("--estimated_minutes <value>", "Alias for --estimated-minutes")
|
|
1106
|
-
.option("--acceptance-criteria <value>", "Set acceptance criteria
|
|
3145
|
+
.option("--acceptance-criteria <value>", "Set acceptance criteria")
|
|
1107
3146
|
.option("--acceptance_criteria <value>", "Alias for --acceptance-criteria")
|
|
1108
3147
|
.option("--ac <value>", "Alias for --acceptance-criteria")
|
|
1109
|
-
.option("--definition-of-ready <value>", "Set definition of ready
|
|
3148
|
+
.option("--definition-of-ready <value>", "Set definition of ready")
|
|
1110
3149
|
.option("--definition_of_ready <value>", "Alias for --definition-of-ready")
|
|
1111
|
-
.option("--order <value>", "Set planning order/rank integer
|
|
3150
|
+
.option("--order <value>", "Set planning order/rank integer")
|
|
1112
3151
|
.option("--rank <value>", "Alias for --order")
|
|
1113
|
-
.option("--goal <value>", "Set goal identifier
|
|
1114
|
-
.option("--objective <value>", "Set objective identifier
|
|
1115
|
-
.option("--value <value>", "Set business value summary
|
|
1116
|
-
.option("--impact <value>", "Set business impact summary
|
|
1117
|
-
.option("--outcome <value>", "Set expected outcome summary
|
|
1118
|
-
.option("--why-now <value>", "Set why-now rationale
|
|
3152
|
+
.option("--goal <value>", "Set goal identifier")
|
|
3153
|
+
.option("--objective <value>", "Set objective identifier")
|
|
3154
|
+
.option("--value <value>", "Set business value summary")
|
|
3155
|
+
.option("--impact <value>", "Set business impact summary")
|
|
3156
|
+
.option("--outcome <value>", "Set expected outcome summary")
|
|
3157
|
+
.option("--why-now <value>", "Set why-now rationale")
|
|
1119
3158
|
.option("--why_now <value>", "Alias for --why-now")
|
|
1120
3159
|
.option("--author <value>", "Mutation author")
|
|
1121
3160
|
.option("--message <value>", "Mutation message")
|
|
1122
|
-
.option("--assignee <value>", "Set assignee
|
|
1123
|
-
.option("--parent <value>", "Set parent item ID
|
|
1124
|
-
.option("--reviewer <value>", "Set reviewer
|
|
1125
|
-
.option("--risk <value>", "Set risk level: low|med|medium|high|critical (
|
|
1126
|
-
.option("--confidence <value>", "Set confidence level: 0..100|low|med|medium|high (
|
|
1127
|
-
.option("--sprint <value>", "Set sprint identifier
|
|
1128
|
-
.option("--release <value>", "Set release identifier
|
|
1129
|
-
.option("--blocked-by <value>", "Set blocked-by item ID or reason
|
|
3161
|
+
.option("--assignee <value>", "Set assignee")
|
|
3162
|
+
.option("--parent <value>", "Set parent item ID")
|
|
3163
|
+
.option("--reviewer <value>", "Set reviewer")
|
|
3164
|
+
.option("--risk <value>", "Set risk level: low|med|medium|high|critical (med persists as medium)")
|
|
3165
|
+
.option("--confidence <value>", "Set confidence level: 0..100|low|med|medium|high (med persists as medium)")
|
|
3166
|
+
.option("--sprint <value>", "Set sprint identifier")
|
|
3167
|
+
.option("--release <value>", "Set release identifier")
|
|
3168
|
+
.option("--blocked-by <value>", "Set blocked-by item ID or reason")
|
|
1130
3169
|
.option("--blocked_by <value>", "Alias for --blocked-by")
|
|
1131
|
-
.option("--blocked-reason <value>", "Set blocked reason
|
|
3170
|
+
.option("--blocked-reason <value>", "Set blocked reason")
|
|
1132
3171
|
.option("--blocked_reason <value>", "Alias for --blocked-reason")
|
|
1133
|
-
.option("--unblock-note <value>", "Set unblock rationale note
|
|
3172
|
+
.option("--unblock-note <value>", "Set unblock rationale note")
|
|
1134
3173
|
.option("--unblock_note <value>", "Alias for --unblock-note")
|
|
1135
|
-
.option("--reporter <value>", "Set issue reporter
|
|
1136
|
-
.option("--severity <value>", "Set issue severity: low|med|medium|high|critical (
|
|
1137
|
-
.option("--environment <value>", "Set issue environment context
|
|
1138
|
-
.option("--repro-steps <value>", "Set issue reproduction steps
|
|
3174
|
+
.option("--reporter <value>", "Set issue reporter")
|
|
3175
|
+
.option("--severity <value>", "Set issue severity: low|med|medium|high|critical (med persists as medium)")
|
|
3176
|
+
.option("--environment <value>", "Set issue environment context")
|
|
3177
|
+
.option("--repro-steps <value>", "Set issue reproduction steps")
|
|
1139
3178
|
.option("--repro_steps <value>", "Alias for --repro-steps")
|
|
1140
|
-
.option("--resolution <value>", "Set issue resolution summary
|
|
1141
|
-
.option("--expected-result <value>", "Set issue expected behavior
|
|
3179
|
+
.option("--resolution <value>", "Set issue resolution summary")
|
|
3180
|
+
.option("--expected-result <value>", "Set issue expected behavior")
|
|
1142
3181
|
.option("--expected_result <value>", "Alias for --expected-result")
|
|
1143
|
-
.option("--actual-result <value>", "Set issue observed behavior
|
|
3182
|
+
.option("--actual-result <value>", "Set issue observed behavior")
|
|
1144
3183
|
.option("--actual_result <value>", "Alias for --actual-result")
|
|
1145
|
-
.option("--affected-version <value>", "Set affected version identifier
|
|
3184
|
+
.option("--affected-version <value>", "Set affected version identifier")
|
|
1146
3185
|
.option("--affected_version <value>", "Alias for --affected-version")
|
|
1147
|
-
.option("--fixed-version <value>", "Set fixed version identifier
|
|
3186
|
+
.option("--fixed-version <value>", "Set fixed version identifier")
|
|
1148
3187
|
.option("--fixed_version <value>", "Alias for --fixed-version")
|
|
1149
|
-
.option("--component <value>", "Set issue component ownership
|
|
1150
|
-
.option("--regression <value>", "Set regression marker: true|false|1|0
|
|
1151
|
-
.option("--customer-impact <value>", "Set customer impact summary
|
|
3188
|
+
.option("--component <value>", "Set issue component ownership")
|
|
3189
|
+
.option("--regression <value>", "Set regression marker: true|false|1|0")
|
|
3190
|
+
.option("--customer-impact <value>", "Set customer impact summary")
|
|
1152
3191
|
.option("--customer_impact <value>", "Alias for --customer-impact")
|
|
3192
|
+
.option("--dep <value>", "Add dependency entries id=<id>,kind=<value>,author=<value>,created_at=<iso|now>,source_kind=<value> (repeatable)", collect)
|
|
3193
|
+
.option("--dep-remove <value>", "Remove dependencies by id or id=<id>,kind=<value>,source_kind=<value> selectors (repeatable)", collect)
|
|
3194
|
+
.option("--dep_remove <value>", "Alias for --dep-remove", collect)
|
|
3195
|
+
.option("--replace-deps", "Atomically replace dependency entries with the provided --dep values")
|
|
3196
|
+
.option("--replace-tests", "Atomically replace linked test entries with the provided --test values")
|
|
3197
|
+
.option("--comment <value>", "Append comment seed author=<value>,created_at=<iso|now>,text=<value> (also accepts markdown pairs and - for stdin; repeatable)", collect)
|
|
3198
|
+
.option("--note <value>", "Append note seed author=<value>,created_at=<iso|now>,text=<value> (also accepts markdown pairs and - for stdin; repeatable)", collect)
|
|
3199
|
+
.option("--learning <value>", "Append learning seed author=<value>,created_at=<iso|now>,text=<value> (also accepts markdown pairs and - for stdin; repeatable)", collect)
|
|
3200
|
+
.option("--file <value>", "Append linked file path=<value>,scope=<project|global>,note=<text> (also accepts markdown pairs and - for stdin; repeatable)", collect)
|
|
3201
|
+
.option("--test <value>", "Append linked test command=<value>,path=<value>,scope=<project|global>,timeout_seconds=<n>,pm_context_mode=<schema|tracker|auto> (also accepts markdown pairs and - for stdin; repeatable)", collect)
|
|
3202
|
+
.option("--doc <value>", "Append linked doc path=<value>,scope=<project|global>,note=<text> (also accepts markdown pairs and - for stdin; repeatable)", collect)
|
|
3203
|
+
.option("--reminder <value>", "Set reminders at=<iso|relative>,text=<text> (also accepts markdown pairs and - for stdin; repeatable)", collect)
|
|
3204
|
+
.option("--event <value>", "Set events start=<iso|relative>,end=<iso|relative>,title=<text>,all_day=<true|false>,recur_* fields (also accepts markdown pairs and - for stdin; repeatable)", collect)
|
|
3205
|
+
.option("--type-option <value>", "Set type options key=value or key=<name>,value=<value> (also accepts key:value and markdown pairs; use - for stdin; repeatable)", collect)
|
|
3206
|
+
.option("--type_option <value>", "Alias for --type-option", collect)
|
|
3207
|
+
.option("--unset <field>", "Clear scalar metadata field by name (repeatable)", collect)
|
|
3208
|
+
.option("--clear-deps", "Clear dependency entries")
|
|
3209
|
+
.option("--clear-comments", "Clear comments")
|
|
3210
|
+
.option("--clear-notes", "Clear notes")
|
|
3211
|
+
.option("--clear-learnings", "Clear learnings")
|
|
3212
|
+
.option("--clear-files", "Clear linked files")
|
|
3213
|
+
.option("--clear-tests", "Clear linked tests")
|
|
3214
|
+
.option("--clear-docs", "Clear linked docs")
|
|
3215
|
+
.option("--clear-reminders", "Clear reminders")
|
|
3216
|
+
.option("--clear-events", "Clear events")
|
|
3217
|
+
.option("--clear-type-options", "Clear type options")
|
|
3218
|
+
.option("--allow-audit-update", "Allow non-owner metadata-only audit updates without requiring --force")
|
|
3219
|
+
.option("--allow_audit_update", "Alias for --allow-audit-update")
|
|
3220
|
+
.option("--allow-audit-dep-update", "Allow non-owner append-only dependency updates without requiring --force")
|
|
3221
|
+
.option("--allow_audit_dep_update", "Alias for --allow-audit-dep-update")
|
|
1153
3222
|
.option("--force", "Force ownership override")
|
|
1154
3223
|
.action(async (id, options, command) => {
|
|
1155
3224
|
const globalOptions = getGlobalOptions(command);
|
|
@@ -1161,12 +3230,221 @@ program
|
|
|
1161
3230
|
printError(`profile:command=update took_ms=${Date.now() - startedAt}`);
|
|
1162
3231
|
}
|
|
1163
3232
|
});
|
|
3233
|
+
program
|
|
3234
|
+
.command("update-many")
|
|
3235
|
+
.description("Bulk-update matched items with dry-run plans and rollback checkpoints.")
|
|
3236
|
+
.option("--filter-status <value>", "Filter by status before applying updates")
|
|
3237
|
+
.option("--filter-type <value>", "Filter by item type before applying updates")
|
|
3238
|
+
.option("--filter-tag <value>", "Filter by tag before applying updates")
|
|
3239
|
+
.option("--filter-priority <value>", "Filter by priority before applying updates")
|
|
3240
|
+
.option("--filter-deadline-before <value>", "Filter by deadline upper bound before applying updates")
|
|
3241
|
+
.option("--filter-deadline-after <value>", "Filter by deadline lower bound before applying updates")
|
|
3242
|
+
.option("--filter-assignee <value>", "Filter by assignee before applying updates")
|
|
3243
|
+
.option("--filter-assignee-filter <value>", "Filter assignee presence: assigned|unassigned before applying updates")
|
|
3244
|
+
.option("--filter-assignee_filter <value>", "Alias for --filter-assignee-filter")
|
|
3245
|
+
.option("--filter-parent <value>", "Filter by parent item ID before applying updates")
|
|
3246
|
+
.option("--filter-sprint <value>", "Filter by sprint before applying updates")
|
|
3247
|
+
.option("--filter-release <value>", "Filter by release before applying updates")
|
|
3248
|
+
.option("--limit <n>", "Limit matched item count before apply/preview")
|
|
3249
|
+
.option("--offset <n>", "Skip first n matched rows before apply/preview")
|
|
3250
|
+
.option("--dry-run", "Preview per-item diffs and checkpoint intent without mutating")
|
|
3251
|
+
.option("--rollback <value>", "Rollback a prior update-many checkpoint ID")
|
|
3252
|
+
.option("--no-checkpoint", "Disable checkpoint creation during apply mode")
|
|
3253
|
+
.option("--title, -t <value>", "Set title")
|
|
3254
|
+
.option("--description, -d <value>", "Set description")
|
|
3255
|
+
.option("--body, -b <value>", "Set body (allow empty string)")
|
|
3256
|
+
.option("--status, -s <value>", "Set status (use close command for closed)")
|
|
3257
|
+
.option("--priority, -p <value>", "Set priority")
|
|
3258
|
+
.option("--type <value>", "Set type")
|
|
3259
|
+
.option("--tags <value>", "Set comma-separated tags")
|
|
3260
|
+
.option("--deadline <value>", "Set deadline (ISO/date string or relative)")
|
|
3261
|
+
.option("--estimate, --estimated-minutes <value>", "Set estimated minutes")
|
|
3262
|
+
.option("--estimated_minutes <value>", "Alias for --estimated-minutes")
|
|
3263
|
+
.option("--acceptance-criteria <value>", "Set acceptance criteria")
|
|
3264
|
+
.option("--acceptance_criteria <value>", "Alias for --acceptance-criteria")
|
|
3265
|
+
.option("--ac <value>", "Alias for --acceptance-criteria")
|
|
3266
|
+
.option("--definition-of-ready <value>", "Set definition of ready")
|
|
3267
|
+
.option("--definition_of_ready <value>", "Alias for --definition-of-ready")
|
|
3268
|
+
.option("--order <value>", "Set planning order/rank integer")
|
|
3269
|
+
.option("--rank <value>", "Alias for --order")
|
|
3270
|
+
.option("--goal <value>", "Set goal identifier")
|
|
3271
|
+
.option("--objective <value>", "Set objective identifier")
|
|
3272
|
+
.option("--value <value>", "Set business value summary")
|
|
3273
|
+
.option("--impact <value>", "Set business impact summary")
|
|
3274
|
+
.option("--outcome <value>", "Set expected outcome summary")
|
|
3275
|
+
.option("--why-now <value>", "Set why-now rationale")
|
|
3276
|
+
.option("--why_now <value>", "Alias for --why-now")
|
|
3277
|
+
.option("--assignee <value>", "Set assignee")
|
|
3278
|
+
.option("--parent <value>", "Set parent item ID")
|
|
3279
|
+
.option("--reviewer <value>", "Set reviewer")
|
|
3280
|
+
.option("--risk <value>", "Set risk level")
|
|
3281
|
+
.option("--confidence <value>", "Set confidence level")
|
|
3282
|
+
.option("--sprint <value>", "Set sprint identifier")
|
|
3283
|
+
.option("--release <value>", "Set release identifier")
|
|
3284
|
+
.option("--blocked-by <value>", "Set blocked-by item ID or reason")
|
|
3285
|
+
.option("--blocked_by <value>", "Alias for --blocked-by")
|
|
3286
|
+
.option("--blocked-reason <value>", "Set blocked reason")
|
|
3287
|
+
.option("--blocked_reason <value>", "Alias for --blocked-reason")
|
|
3288
|
+
.option("--unblock-note <value>", "Set unblock rationale note")
|
|
3289
|
+
.option("--unblock_note <value>", "Alias for --unblock-note")
|
|
3290
|
+
.option("--reporter <value>", "Set issue reporter")
|
|
3291
|
+
.option("--severity <value>", "Set issue severity")
|
|
3292
|
+
.option("--environment <value>", "Set issue environment context")
|
|
3293
|
+
.option("--repro-steps <value>", "Set issue reproduction steps")
|
|
3294
|
+
.option("--repro_steps <value>", "Alias for --repro-steps")
|
|
3295
|
+
.option("--resolution <value>", "Set issue resolution summary")
|
|
3296
|
+
.option("--expected-result <value>", "Set issue expected behavior")
|
|
3297
|
+
.option("--expected_result <value>", "Alias for --expected-result")
|
|
3298
|
+
.option("--actual-result <value>", "Set issue observed behavior")
|
|
3299
|
+
.option("--actual_result <value>", "Alias for --actual-result")
|
|
3300
|
+
.option("--affected-version <value>", "Set affected version identifier")
|
|
3301
|
+
.option("--affected_version <value>", "Alias for --affected-version")
|
|
3302
|
+
.option("--fixed-version <value>", "Set fixed version identifier")
|
|
3303
|
+
.option("--fixed_version <value>", "Alias for --fixed-version")
|
|
3304
|
+
.option("--component <value>", "Set issue component ownership")
|
|
3305
|
+
.option("--regression <value>", "Set regression marker: true|false|1|0")
|
|
3306
|
+
.option("--customer-impact <value>", "Set customer impact summary")
|
|
3307
|
+
.option("--customer_impact <value>", "Alias for --customer-impact")
|
|
3308
|
+
.option("--dep <value>", "Add dependency entry id=<id>,kind=<kind>,author=<author>,created_at=<timestamp>", collect)
|
|
3309
|
+
.option("--dep-remove <value>", "Remove dependency entries by id/kind/author/timestamp signature", collect)
|
|
3310
|
+
.option("--dep_remove <value>", "Alias for --dep-remove", collect)
|
|
3311
|
+
.option("--replace-deps", "Atomically replace dependency entries with provided --dep values")
|
|
3312
|
+
.option("--replace-tests", "Atomically replace linked tests with provided --test values")
|
|
3313
|
+
.option("--comment <value>", "Add comment seed author=<value>,created_at=<iso|now>,text=<value>", collect)
|
|
3314
|
+
.option("--note <value>", "Add note seed author=<value>,created_at=<iso|now>,text=<value>", collect)
|
|
3315
|
+
.option("--learning <value>", "Add learning seed author=<value>,created_at=<iso|now>,text=<value>", collect)
|
|
3316
|
+
.option("--file <value>", "Add linked file path=<value>,scope=<project|global>,note=<text>", collect)
|
|
3317
|
+
.option("--test <value>", "Add linked test command=<value>,path=<value>,scope=<project|global>", collect)
|
|
3318
|
+
.option("--doc <value>", "Add linked doc path=<value>,scope=<project|global>,note=<text>", collect)
|
|
3319
|
+
.option("--reminder <value>", "Add reminder entry at=<iso|relative>,text=<text>", collect)
|
|
3320
|
+
.option("--event <value>", "Add event entry start=<iso|relative>,end=<iso|relative>,recur_*", collect)
|
|
3321
|
+
.option("--type-option <value>", "Set type options key=value (repeatable)", collect)
|
|
3322
|
+
.option("--type_option <value>", "Alias for --type-option", collect)
|
|
3323
|
+
.option("--unset <field>", "Clear scalar metadata field by name (repeatable)", collect)
|
|
3324
|
+
.option("--clear-deps", "Clear dependency entries")
|
|
3325
|
+
.option("--clear-comments", "Clear comments")
|
|
3326
|
+
.option("--clear-notes", "Clear notes")
|
|
3327
|
+
.option("--clear-learnings", "Clear learnings")
|
|
3328
|
+
.option("--clear-files", "Clear linked files")
|
|
3329
|
+
.option("--clear-tests", "Clear linked tests")
|
|
3330
|
+
.option("--clear-docs", "Clear linked docs")
|
|
3331
|
+
.option("--clear-reminders", "Clear reminders")
|
|
3332
|
+
.option("--clear-events", "Clear events")
|
|
3333
|
+
.option("--clear-type-options", "Clear type options")
|
|
3334
|
+
.option("--allow-audit-update", "Allow non-owner metadata-only audit updates without requiring --force")
|
|
3335
|
+
.option("--allow_audit_update", "Alias for --allow-audit-update")
|
|
3336
|
+
.option("--allow-audit-dep-update", "Allow non-owner append-only dependency updates without requiring --force")
|
|
3337
|
+
.option("--allow_audit_dep_update", "Alias for --allow-audit-dep-update")
|
|
3338
|
+
.option("--author <value>", "Mutation author")
|
|
3339
|
+
.option("--message <value>", "Mutation message")
|
|
3340
|
+
.option("--force", "Force ownership override")
|
|
3341
|
+
.action(async (options, command) => {
|
|
3342
|
+
const globalOptions = getGlobalOptions(command);
|
|
3343
|
+
const startedAt = Date.now();
|
|
3344
|
+
const result = await runUpdateMany({
|
|
3345
|
+
status: typeof options.filterStatus === "string" ? options.filterStatus : undefined,
|
|
3346
|
+
list: {
|
|
3347
|
+
type: typeof options.filterType === "string" ? options.filterType : undefined,
|
|
3348
|
+
tag: typeof options.filterTag === "string" ? options.filterTag : undefined,
|
|
3349
|
+
priority: typeof options.filterPriority === "string" ? options.filterPriority : undefined,
|
|
3350
|
+
deadlineBefore: typeof options.filterDeadlineBefore === "string" ? options.filterDeadlineBefore : undefined,
|
|
3351
|
+
deadlineAfter: typeof options.filterDeadlineAfter === "string" ? options.filterDeadlineAfter : undefined,
|
|
3352
|
+
assignee: typeof options.filterAssignee === "string" ? options.filterAssignee : undefined,
|
|
3353
|
+
assigneeFilter: typeof options.filterAssigneeFilter === "string"
|
|
3354
|
+
? options.filterAssigneeFilter
|
|
3355
|
+
: typeof options.filterAssignee_filter === "string"
|
|
3356
|
+
? options.filterAssignee_filter
|
|
3357
|
+
: undefined,
|
|
3358
|
+
parent: typeof options.filterParent === "string" ? options.filterParent : undefined,
|
|
3359
|
+
sprint: typeof options.filterSprint === "string" ? options.filterSprint : undefined,
|
|
3360
|
+
release: typeof options.filterRelease === "string" ? options.filterRelease : undefined,
|
|
3361
|
+
limit: typeof options.limit === "string" ? options.limit : undefined,
|
|
3362
|
+
offset: typeof options.offset === "string" ? options.offset : undefined,
|
|
3363
|
+
includeBody: true,
|
|
3364
|
+
},
|
|
3365
|
+
update: normalizeUpdateOptions(extractUpdateManyMutationOptionSource(options)),
|
|
3366
|
+
dryRun: options.dryRun === true ? true : undefined,
|
|
3367
|
+
rollback: typeof options.rollback === "string" ? options.rollback : undefined,
|
|
3368
|
+
checkpoint: options.checkpoint === false ? false : undefined,
|
|
3369
|
+
}, globalOptions);
|
|
3370
|
+
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
3371
|
+
printResult(result, globalOptions);
|
|
3372
|
+
if (globalOptions.profile) {
|
|
3373
|
+
printError(`profile:command=update-many took_ms=${Date.now() - startedAt}`);
|
|
3374
|
+
}
|
|
3375
|
+
});
|
|
3376
|
+
program
|
|
3377
|
+
.command("normalize")
|
|
3378
|
+
.description("Normalize lifecycle metadata with deterministic dry-run plans and optional apply mode.")
|
|
3379
|
+
.option("--filter-status <value>", "Filter by status before planning or apply")
|
|
3380
|
+
.option("--filter-type <value>", "Filter by item type before planning or apply")
|
|
3381
|
+
.option("--filter-tag <value>", "Filter by tag before planning or apply")
|
|
3382
|
+
.option("--filter-priority <value>", "Filter by priority before planning or apply")
|
|
3383
|
+
.option("--filter-deadline-before <value>", "Filter by deadline upper bound before planning or apply")
|
|
3384
|
+
.option("--filter-deadline-after <value>", "Filter by deadline lower bound before planning or apply")
|
|
3385
|
+
.option("--filter-assignee <value>", "Filter by assignee before planning or apply")
|
|
3386
|
+
.option("--filter-assignee-filter <value>", "Filter assignee presence: assigned|unassigned before planning or apply")
|
|
3387
|
+
.option("--filter-assignee_filter <value>", "Alias for --filter-assignee-filter")
|
|
3388
|
+
.option("--filter-parent <value>", "Filter by parent item ID before planning or apply")
|
|
3389
|
+
.option("--filter-sprint <value>", "Filter by sprint before planning or apply")
|
|
3390
|
+
.option("--filter-release <value>", "Filter by release before planning or apply")
|
|
3391
|
+
.option("--limit <n>", "Limit matched item count before planning/apply")
|
|
3392
|
+
.option("--offset <n>", "Skip first n matched rows before planning/apply")
|
|
3393
|
+
.option("--dry-run", "Preview normalize findings without mutating (default)")
|
|
3394
|
+
.option("--apply", "Apply normalize changes using update semantics")
|
|
3395
|
+
.option("--author <value>", "Mutation author for apply mode")
|
|
3396
|
+
.option("--message <value>", "Mutation message for apply mode")
|
|
3397
|
+
.option("--allow-audit-update", "Allow non-owner metadata-only audit updates without requiring --force")
|
|
3398
|
+
.option("--allow_audit_update", "Alias for --allow-audit-update")
|
|
3399
|
+
.option("--force", "Force ownership override for apply mode")
|
|
3400
|
+
.action(async (options, command) => {
|
|
3401
|
+
const globalOptions = getGlobalOptions(command);
|
|
3402
|
+
const startedAt = Date.now();
|
|
3403
|
+
const result = await runNormalize({
|
|
3404
|
+
status: typeof options.filterStatus === "string" ? options.filterStatus : undefined,
|
|
3405
|
+
list: {
|
|
3406
|
+
type: typeof options.filterType === "string" ? options.filterType : undefined,
|
|
3407
|
+
tag: typeof options.filterTag === "string" ? options.filterTag : undefined,
|
|
3408
|
+
priority: typeof options.filterPriority === "string" ? options.filterPriority : undefined,
|
|
3409
|
+
deadlineBefore: typeof options.filterDeadlineBefore === "string" ? options.filterDeadlineBefore : undefined,
|
|
3410
|
+
deadlineAfter: typeof options.filterDeadlineAfter === "string" ? options.filterDeadlineAfter : undefined,
|
|
3411
|
+
assignee: typeof options.filterAssignee === "string" ? options.filterAssignee : undefined,
|
|
3412
|
+
assigneeFilter: typeof options.filterAssigneeFilter === "string"
|
|
3413
|
+
? options.filterAssigneeFilter
|
|
3414
|
+
: typeof options.filterAssignee_filter === "string"
|
|
3415
|
+
? options.filterAssignee_filter
|
|
3416
|
+
: undefined,
|
|
3417
|
+
parent: typeof options.filterParent === "string" ? options.filterParent : undefined,
|
|
3418
|
+
sprint: typeof options.filterSprint === "string" ? options.filterSprint : undefined,
|
|
3419
|
+
release: typeof options.filterRelease === "string" ? options.filterRelease : undefined,
|
|
3420
|
+
limit: typeof options.limit === "string" ? options.limit : undefined,
|
|
3421
|
+
offset: typeof options.offset === "string" ? options.offset : undefined,
|
|
3422
|
+
includeBody: true,
|
|
3423
|
+
},
|
|
3424
|
+
dryRun: options.dryRun === true ? true : undefined,
|
|
3425
|
+
apply: options.apply === true ? true : undefined,
|
|
3426
|
+
author: typeof options.author === "string" ? options.author : undefined,
|
|
3427
|
+
message: typeof options.message === "string" ? options.message : undefined,
|
|
3428
|
+
allowAuditUpdate: options.allowAuditUpdate === true || options.allow_audit_update === true || options.allowAudit_update === true
|
|
3429
|
+
? true
|
|
3430
|
+
: undefined,
|
|
3431
|
+
force: options.force === true ? true : undefined,
|
|
3432
|
+
}, globalOptions);
|
|
3433
|
+
if (result.mode === "apply") {
|
|
3434
|
+
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
3435
|
+
}
|
|
3436
|
+
printResult(result, globalOptions);
|
|
3437
|
+
if (globalOptions.profile) {
|
|
3438
|
+
printError(`profile:command=normalize took_ms=${Date.now() - startedAt}`);
|
|
3439
|
+
}
|
|
3440
|
+
});
|
|
1164
3441
|
program
|
|
1165
3442
|
.command("close")
|
|
1166
3443
|
.argument("<id>", "Item id")
|
|
1167
3444
|
.argument("<text>", "Close reason text")
|
|
1168
3445
|
.option("--author <value>", "Mutation author")
|
|
1169
3446
|
.option("--message <value>", "History message")
|
|
3447
|
+
.option("--validate-close [mode]", 'Validate closure metadata before close: "off", "warn", or "strict" (default: settings governance preset)')
|
|
1170
3448
|
.option("--force", "Force ownership override")
|
|
1171
3449
|
.description("Close an item with a required reason.")
|
|
1172
3450
|
.action(async (id, text, options, command) => {
|
|
@@ -1175,6 +3453,11 @@ program
|
|
|
1175
3453
|
const result = await runClose(id, text, {
|
|
1176
3454
|
author: typeof options.author === "string" ? options.author : undefined,
|
|
1177
3455
|
message: typeof options.message === "string" ? options.message : undefined,
|
|
3456
|
+
validateClose: options.validateClose === true
|
|
3457
|
+
? "warn"
|
|
3458
|
+
: typeof options.validateClose === "string"
|
|
3459
|
+
? options.validateClose
|
|
3460
|
+
: undefined,
|
|
1178
3461
|
force: Boolean(options.force),
|
|
1179
3462
|
}, globalOptions);
|
|
1180
3463
|
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
@@ -1207,7 +3490,7 @@ program
|
|
|
1207
3490
|
program
|
|
1208
3491
|
.command("append")
|
|
1209
3492
|
.argument("<id>", "Item id")
|
|
1210
|
-
.requiredOption("--body <value>", "Text to append to body")
|
|
3493
|
+
.requiredOption("--body <value>", "Text to append to body (or - for stdin)")
|
|
1211
3494
|
.option("--author <value>", "Mutation author")
|
|
1212
3495
|
.option("--message <value>", "Mutation message")
|
|
1213
3496
|
.option("--force", "Force ownership override")
|
|
@@ -1224,58 +3507,196 @@ program
|
|
|
1224
3507
|
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
1225
3508
|
printResult(result, globalOptions);
|
|
1226
3509
|
if (globalOptions.profile) {
|
|
1227
|
-
printError(`profile:command=append took_ms=${Date.now() - startedAt}`);
|
|
3510
|
+
printError(`profile:command=append took_ms=${Date.now() - startedAt}`);
|
|
3511
|
+
}
|
|
3512
|
+
});
|
|
3513
|
+
program
|
|
3514
|
+
.command("comments")
|
|
3515
|
+
.argument("<id>", "Item id")
|
|
3516
|
+
.argument("[text]", "Optional comment text shorthand (equivalent to --add; use - for stdin)")
|
|
3517
|
+
.option("--add <text>", "Add one comment entry (plain text fallback, text=<value>, markdown pairs, or - for stdin; CSV-like key fragments are preserved as plain text unless text is explicit)")
|
|
3518
|
+
.option("--limit <n>", "Return only latest n comments")
|
|
3519
|
+
.option("--author [value]", "Comment author (optional; falls back to PM_AUTHOR/settings)")
|
|
3520
|
+
.option("--message <value>", "History message")
|
|
3521
|
+
.option("--allow-audit-comment", "Allow non-owner append-only comment audits without requiring --force")
|
|
3522
|
+
.option("--force", "Force ownership override")
|
|
3523
|
+
.description("List or add comments for an item.")
|
|
3524
|
+
.action(async (id, text, options, command) => {
|
|
3525
|
+
const globalOptions = getGlobalOptions(command);
|
|
3526
|
+
const startedAt = Date.now();
|
|
3527
|
+
const addFromOption = typeof options.add === "string" ? options.add : undefined;
|
|
3528
|
+
const addFromPositional = typeof text === "string" ? text : undefined;
|
|
3529
|
+
if (addFromOption !== undefined && addFromPositional !== undefined) {
|
|
3530
|
+
throw new PmCliError("Specify comment text either as positional [text] or with --add, not both", EXIT_CODE.USAGE);
|
|
3531
|
+
}
|
|
3532
|
+
const add = addFromOption ?? addFromPositional;
|
|
3533
|
+
const result = await runComments(id, {
|
|
3534
|
+
add,
|
|
3535
|
+
limit: typeof options.limit === "string" ? options.limit : undefined,
|
|
3536
|
+
author: typeof options.author === "string" ? options.author : undefined,
|
|
3537
|
+
message: typeof options.message === "string" ? options.message : undefined,
|
|
3538
|
+
allowAuditComment: Boolean(options.allowAuditComment),
|
|
3539
|
+
force: Boolean(options.force),
|
|
3540
|
+
}, globalOptions);
|
|
3541
|
+
if (typeof add === "string") {
|
|
3542
|
+
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
3543
|
+
}
|
|
3544
|
+
printResult(result, globalOptions);
|
|
3545
|
+
if (globalOptions.profile) {
|
|
3546
|
+
printError(`profile:command=comments took_ms=${Date.now() - startedAt}`);
|
|
3547
|
+
}
|
|
3548
|
+
});
|
|
3549
|
+
program
|
|
3550
|
+
.command("comments-audit")
|
|
3551
|
+
.option("--status <value>", "Filter by item status")
|
|
3552
|
+
.option("--type <value>", "Filter by item type")
|
|
3553
|
+
.option("--tag <value>", "Filter by tag")
|
|
3554
|
+
.option("--priority <value>", "Filter by priority")
|
|
3555
|
+
.option("--parent <value>", "Filter by parent item ID")
|
|
3556
|
+
.option("--sprint <value>", "Filter by sprint")
|
|
3557
|
+
.option("--release <value>", "Filter by release")
|
|
3558
|
+
.option("--assignee <value>", "Filter by assignee")
|
|
3559
|
+
.option("--assignee-filter <value>", "Filter assignee presence: assigned|unassigned")
|
|
3560
|
+
.option("--assignee_filter <value>", "Alias for --assignee-filter")
|
|
3561
|
+
.option("--limit-items <n>", "Limit returned item count")
|
|
3562
|
+
.option("--limit <n>", "Alias for --limit-items")
|
|
3563
|
+
.option("--full-history", "Export full comment history rows (cannot be combined with --latest)")
|
|
3564
|
+
.option("--latest <n>", "Return latest n comments per item (default: 1, use 0 for summary-only rows)")
|
|
3565
|
+
.description("Audit latest comments or full comment history across filtered items.")
|
|
3566
|
+
.action(async (options, command) => {
|
|
3567
|
+
const globalOptions = getGlobalOptions(command);
|
|
3568
|
+
const startedAt = Date.now();
|
|
3569
|
+
const result = await runCommentsAudit({
|
|
3570
|
+
status: typeof options.status === "string" ? options.status : undefined,
|
|
3571
|
+
type: typeof options.type === "string" ? options.type : undefined,
|
|
3572
|
+
tag: typeof options.tag === "string" ? options.tag : undefined,
|
|
3573
|
+
priority: typeof options.priority === "string" ? options.priority : undefined,
|
|
3574
|
+
parent: typeof options.parent === "string" ? options.parent : undefined,
|
|
3575
|
+
sprint: typeof options.sprint === "string" ? options.sprint : undefined,
|
|
3576
|
+
release: typeof options.release === "string" ? options.release : undefined,
|
|
3577
|
+
assignee: typeof options.assignee === "string" ? options.assignee : undefined,
|
|
3578
|
+
assigneeFilter: typeof options.assigneeFilter === "string" ? options.assigneeFilter : undefined,
|
|
3579
|
+
limit: typeof options.limit === "string" ? options.limit : undefined,
|
|
3580
|
+
limitItems: typeof options.limitItems === "string" ? options.limitItems : undefined,
|
|
3581
|
+
fullHistory: options.fullHistory === true,
|
|
3582
|
+
latest: typeof options.latest === "string" ? options.latest : undefined,
|
|
3583
|
+
}, globalOptions);
|
|
3584
|
+
printResult(result, globalOptions);
|
|
3585
|
+
if (globalOptions.profile) {
|
|
3586
|
+
printError(`profile:command=comments-audit took_ms=${Date.now() - startedAt}`);
|
|
3587
|
+
}
|
|
3588
|
+
});
|
|
3589
|
+
program
|
|
3590
|
+
.command("notes")
|
|
3591
|
+
.argument("<id>", "Item id")
|
|
3592
|
+
.argument("[text]", "Optional note text shorthand (equivalent to --add; use - for stdin)")
|
|
3593
|
+
.option("--add <text>", "Add one note entry (plain text fallback, text=<value>, markdown pairs, or - for stdin; CSV-like key fragments are preserved as plain text unless text is explicit)")
|
|
3594
|
+
.option("--limit <n>", "Return only latest n notes")
|
|
3595
|
+
.option("--author [value]", "Note author (optional; falls back to PM_AUTHOR/settings)")
|
|
3596
|
+
.option("--message <value>", "History message")
|
|
3597
|
+
.option("--allow-audit-note", "Allow non-owner append-only note audits without requiring --force")
|
|
3598
|
+
.option("--allow-audit-comment", "Backward-compatible alias for --allow-audit-note")
|
|
3599
|
+
.option("--force", "Force ownership override")
|
|
3600
|
+
.description("List or add notes for an item.")
|
|
3601
|
+
.action(async (id, text, options, command) => {
|
|
3602
|
+
const globalOptions = getGlobalOptions(command);
|
|
3603
|
+
const startedAt = Date.now();
|
|
3604
|
+
const addFromOption = typeof options.add === "string" ? options.add : undefined;
|
|
3605
|
+
const addFromPositional = typeof text === "string" ? text : undefined;
|
|
3606
|
+
if (addFromOption !== undefined && addFromPositional !== undefined) {
|
|
3607
|
+
throw new PmCliError("Specify note text either as positional [text] or with --add, not both", EXIT_CODE.USAGE);
|
|
3608
|
+
}
|
|
3609
|
+
const add = addFromOption ?? addFromPositional;
|
|
3610
|
+
const result = await runNotes(id, {
|
|
3611
|
+
add,
|
|
3612
|
+
limit: typeof options.limit === "string" ? options.limit : undefined,
|
|
3613
|
+
author: typeof options.author === "string" ? options.author : undefined,
|
|
3614
|
+
message: typeof options.message === "string" ? options.message : undefined,
|
|
3615
|
+
allowAuditComment: Boolean(options.allowAuditNote || options.allowAuditComment),
|
|
3616
|
+
force: Boolean(options.force),
|
|
3617
|
+
}, globalOptions);
|
|
3618
|
+
if (typeof add === "string") {
|
|
3619
|
+
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
3620
|
+
}
|
|
3621
|
+
printResult(result, globalOptions);
|
|
3622
|
+
if (globalOptions.profile) {
|
|
3623
|
+
printError(`profile:command=notes took_ms=${Date.now() - startedAt}`);
|
|
1228
3624
|
}
|
|
1229
3625
|
});
|
|
1230
3626
|
program
|
|
1231
|
-
.command("
|
|
3627
|
+
.command("learnings")
|
|
1232
3628
|
.argument("<id>", "Item id")
|
|
1233
|
-
.
|
|
1234
|
-
.option("--
|
|
1235
|
-
.option("--
|
|
3629
|
+
.argument("[text]", "Optional learning text shorthand (equivalent to --add; use - for stdin)")
|
|
3630
|
+
.option("--add <text>", "Add one learning entry (plain text fallback, text=<value>, markdown pairs, or - for stdin; CSV-like key fragments are preserved as plain text unless text is explicit)")
|
|
3631
|
+
.option("--limit <n>", "Return only latest n learnings")
|
|
3632
|
+
.option("--author [value]", "Learning author (optional; falls back to PM_AUTHOR/settings)")
|
|
1236
3633
|
.option("--message <value>", "History message")
|
|
3634
|
+
.option("--allow-audit-learning", "Allow non-owner append-only learning audits without requiring --force")
|
|
3635
|
+
.option("--allow-audit-comment", "Backward-compatible alias for --allow-audit-learning")
|
|
1237
3636
|
.option("--force", "Force ownership override")
|
|
1238
|
-
.description("List or add
|
|
1239
|
-
.action(async (id, options, command) => {
|
|
3637
|
+
.description("List or add learnings for an item.")
|
|
3638
|
+
.action(async (id, text, options, command) => {
|
|
1240
3639
|
const globalOptions = getGlobalOptions(command);
|
|
1241
3640
|
const startedAt = Date.now();
|
|
1242
|
-
const
|
|
1243
|
-
|
|
3641
|
+
const addFromOption = typeof options.add === "string" ? options.add : undefined;
|
|
3642
|
+
const addFromPositional = typeof text === "string" ? text : undefined;
|
|
3643
|
+
if (addFromOption !== undefined && addFromPositional !== undefined) {
|
|
3644
|
+
throw new PmCliError("Specify learning text either as positional [text] or with --add, not both", EXIT_CODE.USAGE);
|
|
3645
|
+
}
|
|
3646
|
+
const add = addFromOption ?? addFromPositional;
|
|
3647
|
+
const result = await runLearnings(id, {
|
|
3648
|
+
add,
|
|
1244
3649
|
limit: typeof options.limit === "string" ? options.limit : undefined,
|
|
1245
3650
|
author: typeof options.author === "string" ? options.author : undefined,
|
|
1246
3651
|
message: typeof options.message === "string" ? options.message : undefined,
|
|
3652
|
+
allowAuditComment: Boolean(options.allowAuditLearning || options.allowAuditComment),
|
|
1247
3653
|
force: Boolean(options.force),
|
|
1248
3654
|
}, globalOptions);
|
|
1249
|
-
if (typeof
|
|
3655
|
+
if (typeof add === "string") {
|
|
1250
3656
|
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
1251
3657
|
}
|
|
1252
3658
|
printResult(result, globalOptions);
|
|
1253
3659
|
if (globalOptions.profile) {
|
|
1254
|
-
printError(`profile:command=
|
|
3660
|
+
printError(`profile:command=learnings took_ms=${Date.now() - startedAt}`);
|
|
1255
3661
|
}
|
|
1256
3662
|
});
|
|
1257
|
-
program
|
|
3663
|
+
const filesCommand = program
|
|
1258
3664
|
.command("files")
|
|
3665
|
+
.description("Manage files linked to an item.");
|
|
3666
|
+
filesCommand
|
|
1259
3667
|
.argument("<id>", "Item id")
|
|
1260
|
-
.option("--add <value>", "Add linked file entry", collect)
|
|
1261
|
-
.option("--
|
|
3668
|
+
.option("--add <value>", "Add linked file entry (CSV/markdown pairs or - for stdin)", collect)
|
|
3669
|
+
.option("--add-glob <value>", "Add linked file entries from a glob (plain glob or pattern=<glob>,scope=<scope>,note=<text>; repeatable)", collect)
|
|
3670
|
+
.option("--remove <value>", "Remove linked file by path (path=<value>, path:<value>, plain path, or - for stdin)", collect)
|
|
3671
|
+
.option("--migrate <value>", "Migrate linked file paths in-place (from=<prefix>,to=<prefix>; repeatable)", collect)
|
|
3672
|
+
.option("--list", "List linked files without mutating")
|
|
3673
|
+
.option("--append-stable", "Preserve existing linked-file order and append new links without full-array resorting")
|
|
3674
|
+
.option("--validate-paths", "Validate linked file paths for existence and file shape")
|
|
3675
|
+
.option("--audit", "Audit linked file usage across all items for this item's linked paths")
|
|
1262
3676
|
.option("--author <value>", "Mutation author")
|
|
1263
3677
|
.option("--message <value>", "History message")
|
|
1264
3678
|
.option("--force", "Force ownership override")
|
|
1265
|
-
.description("Manage files linked to an item.")
|
|
1266
3679
|
.action(async (id, options, command) => {
|
|
1267
3680
|
const globalOptions = getGlobalOptions(command);
|
|
1268
3681
|
const startedAt = Date.now();
|
|
1269
3682
|
const addValues = Array.isArray(options.add) ? options.add : [];
|
|
3683
|
+
const addGlobValues = Array.isArray(options.addGlob) ? options.addGlob : [];
|
|
1270
3684
|
const removeValues = Array.isArray(options.remove) ? options.remove : [];
|
|
3685
|
+
const migrateValues = Array.isArray(options.migrate) ? options.migrate : [];
|
|
1271
3686
|
const result = await runFiles(id, {
|
|
1272
3687
|
add: addValues,
|
|
3688
|
+
addGlob: addGlobValues,
|
|
1273
3689
|
remove: removeValues,
|
|
3690
|
+
migrate: migrateValues,
|
|
3691
|
+
list: Boolean(options.list),
|
|
3692
|
+
appendStable: Boolean(options.appendStable),
|
|
3693
|
+
validatePaths: Boolean(options.validatePaths),
|
|
3694
|
+
audit: Boolean(options.audit),
|
|
1274
3695
|
author: typeof options.author === "string" ? options.author : undefined,
|
|
1275
3696
|
message: typeof options.message === "string" ? options.message : undefined,
|
|
1276
3697
|
force: Boolean(options.force),
|
|
1277
3698
|
}, globalOptions);
|
|
1278
|
-
if (addValues.length > 0 || removeValues.length > 0) {
|
|
3699
|
+
if (addValues.length > 0 || addGlobValues.length > 0 || removeValues.length > 0 || migrateValues.length > 0) {
|
|
1279
3700
|
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
1280
3701
|
}
|
|
1281
3702
|
printResult(result, globalOptions);
|
|
@@ -1283,11 +3704,44 @@ program
|
|
|
1283
3704
|
printError(`profile:command=files took_ms=${Date.now() - startedAt}`);
|
|
1284
3705
|
}
|
|
1285
3706
|
});
|
|
3707
|
+
filesCommand
|
|
3708
|
+
.command("discover")
|
|
3709
|
+
.argument("<id>", "Item id")
|
|
3710
|
+
.option("--apply", "Add discovered missing files to the item")
|
|
3711
|
+
.option("--note <value>", "Note to attach to discovered file links")
|
|
3712
|
+
.option("--append-stable", "Preserve existing linked-file order and append discovered links without full-array resorting")
|
|
3713
|
+
.option("--author <value>", "Mutation author")
|
|
3714
|
+
.option("--message <value>", "History message")
|
|
3715
|
+
.option("--force", "Force ownership override")
|
|
3716
|
+
.description("Discover existing file paths referenced in item text and optionally link missing files.")
|
|
3717
|
+
.action(async (id, options, command) => {
|
|
3718
|
+
const globalOptions = getGlobalOptions(command);
|
|
3719
|
+
const startedAt = Date.now();
|
|
3720
|
+
const result = await runFilesDiscover(id, {
|
|
3721
|
+
apply: Boolean(options.apply),
|
|
3722
|
+
note: typeof options.note === "string" ? options.note : undefined,
|
|
3723
|
+
appendStable: Boolean(options.appendStable),
|
|
3724
|
+
author: typeof options.author === "string" ? options.author : undefined,
|
|
3725
|
+
message: typeof options.message === "string" ? options.message : undefined,
|
|
3726
|
+
force: Boolean(options.force),
|
|
3727
|
+
}, globalOptions);
|
|
3728
|
+
if (result.changed) {
|
|
3729
|
+
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
3730
|
+
}
|
|
3731
|
+
printResult(result, globalOptions);
|
|
3732
|
+
if (globalOptions.profile) {
|
|
3733
|
+
printError(`profile:command=files.discover took_ms=${Date.now() - startedAt}`);
|
|
3734
|
+
}
|
|
3735
|
+
});
|
|
1286
3736
|
program
|
|
1287
3737
|
.command("docs")
|
|
1288
3738
|
.argument("<id>", "Item id")
|
|
1289
|
-
.option("--add <value>", "Add linked doc entry", collect)
|
|
1290
|
-
.option("--
|
|
3739
|
+
.option("--add <value>", "Add linked doc entry (CSV/markdown pairs or - for stdin)", collect)
|
|
3740
|
+
.option("--add-glob <value>", "Add linked doc entries from a glob (plain glob or pattern=<glob>,scope=<scope>,note=<text>; repeatable)", collect)
|
|
3741
|
+
.option("--remove <value>", "Remove linked doc by path (path=<value>, path:<value>, plain path, or - for stdin)", collect)
|
|
3742
|
+
.option("--migrate <value>", "Migrate linked doc paths in-place (from=<prefix>,to=<prefix>; repeatable)", collect)
|
|
3743
|
+
.option("--validate-paths", "Validate linked doc paths for existence and file shape")
|
|
3744
|
+
.option("--audit", "Audit linked doc usage across all items for this item's linked paths")
|
|
1291
3745
|
.option("--author <value>", "Mutation author")
|
|
1292
3746
|
.option("--message <value>", "History message")
|
|
1293
3747
|
.option("--force", "Force ownership override")
|
|
@@ -1296,15 +3750,21 @@ program
|
|
|
1296
3750
|
const globalOptions = getGlobalOptions(command);
|
|
1297
3751
|
const startedAt = Date.now();
|
|
1298
3752
|
const addValues = Array.isArray(options.add) ? options.add : [];
|
|
3753
|
+
const addGlobValues = Array.isArray(options.addGlob) ? options.addGlob : [];
|
|
1299
3754
|
const removeValues = Array.isArray(options.remove) ? options.remove : [];
|
|
3755
|
+
const migrateValues = Array.isArray(options.migrate) ? options.migrate : [];
|
|
1300
3756
|
const result = await runDocs(id, {
|
|
1301
3757
|
add: addValues,
|
|
3758
|
+
addGlob: addGlobValues,
|
|
1302
3759
|
remove: removeValues,
|
|
3760
|
+
migrate: migrateValues,
|
|
3761
|
+
validatePaths: Boolean(options.validatePaths),
|
|
3762
|
+
audit: Boolean(options.audit),
|
|
1303
3763
|
author: typeof options.author === "string" ? options.author : undefined,
|
|
1304
3764
|
message: typeof options.message === "string" ? options.message : undefined,
|
|
1305
3765
|
force: Boolean(options.force),
|
|
1306
3766
|
}, globalOptions);
|
|
1307
|
-
if (addValues.length > 0 || removeValues.length > 0) {
|
|
3767
|
+
if (addValues.length > 0 || addGlobValues.length > 0 || removeValues.length > 0 || migrateValues.length > 0) {
|
|
1308
3768
|
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
1309
3769
|
}
|
|
1310
3770
|
printResult(result, globalOptions);
|
|
@@ -1312,13 +3772,48 @@ program
|
|
|
1312
3772
|
printError(`profile:command=docs took_ms=${Date.now() - startedAt}`);
|
|
1313
3773
|
}
|
|
1314
3774
|
});
|
|
3775
|
+
program
|
|
3776
|
+
.command("deps")
|
|
3777
|
+
.argument("<id>", "Item id")
|
|
3778
|
+
.option("--format <value>", "Output format (tree or graph)", "tree")
|
|
3779
|
+
.option("--max-depth <value>", "Maximum dependency traversal depth (0 keeps only the root)")
|
|
3780
|
+
.option("--collapse <value>", "Collapse mode (none or repeated)", "none")
|
|
3781
|
+
.option("--summary", "Return counts only without full tree/graph payload")
|
|
3782
|
+
.description("Show dependency relationships for an item.")
|
|
3783
|
+
.action(async (id, options, command) => {
|
|
3784
|
+
const globalOptions = getGlobalOptions(command);
|
|
3785
|
+
const startedAt = Date.now();
|
|
3786
|
+
const result = await runDeps(id, {
|
|
3787
|
+
format: typeof options.format === "string" ? options.format : undefined,
|
|
3788
|
+
maxDepth: typeof options.maxDepth === "string" ? options.maxDepth : undefined,
|
|
3789
|
+
collapse: typeof options.collapse === "string" ? options.collapse : undefined,
|
|
3790
|
+
summary: options.summary === true,
|
|
3791
|
+
}, globalOptions);
|
|
3792
|
+
printResult(result, globalOptions);
|
|
3793
|
+
if (globalOptions.profile) {
|
|
3794
|
+
printError(`profile:command=deps took_ms=${Date.now() - startedAt}`);
|
|
3795
|
+
}
|
|
3796
|
+
});
|
|
1315
3797
|
program
|
|
1316
3798
|
.command("test")
|
|
1317
3799
|
.argument("<id>", "Item id")
|
|
1318
|
-
.option("--add <value>", "Add linked test entry", collect)
|
|
1319
|
-
.option("--remove <value>", "Remove linked test entry by command/path", collect)
|
|
3800
|
+
.option("--add <value>", "Add linked test entry (CSV/markdown pairs or - for stdin)", collect)
|
|
3801
|
+
.option("--remove <value>", "Remove linked test entry by command/path (command=<value>, path=<value>, markdown pairs, plain value, or - for stdin)", collect)
|
|
1320
3802
|
.option("--run", "Run linked test commands")
|
|
3803
|
+
.option("--background", "Run linked tests in managed background mode")
|
|
1321
3804
|
.option("--timeout <seconds>", "Default run timeout in seconds")
|
|
3805
|
+
.option("--progress", "Emit linked-test progress to stderr (always shown in TTY, opt-in for non-TTY)")
|
|
3806
|
+
.option("--env-set <value>", "Set environment variable(s) for linked-test runs (KEY=VALUE, repeatable)", collect)
|
|
3807
|
+
.option("--env-clear <value>", "Clear environment variable(s) for linked-test runs (NAME, repeatable)", collect)
|
|
3808
|
+
.option("--shared-host-safe", "Apply additive shared-host-safe runtime defaults for linked-test runs")
|
|
3809
|
+
.option("--pm-context <mode>", "PM linked-test context mode: schema|tracker|auto (default: schema)")
|
|
3810
|
+
.option("--override-linked-pm-context", "Force run-level --pm-context to override per-linked-test pm_context_mode metadata")
|
|
3811
|
+
.option("--fail-on-context-mismatch", "Fail linked PM commands when context item counts differ")
|
|
3812
|
+
.option("--fail-on-skipped", "Treat skipped linked tests as dependency failures")
|
|
3813
|
+
.option("--fail-on-empty-test-run", "Treat successful linked-test commands that report zero executed tests as failures")
|
|
3814
|
+
.option("--require-assertions-for-pm", "Require assertion metadata for linked PM command tests")
|
|
3815
|
+
.option("--check-context", "Preflight linked PM command context diagnostics before executing commands")
|
|
3816
|
+
.option("--auto-pm-context", "Auto-remediate PM tracker-read context mismatches by routing those linked commands through tracker context")
|
|
1322
3817
|
.option("--author <value>", "Mutation author")
|
|
1323
3818
|
.option("--message <value>", "History message")
|
|
1324
3819
|
.option("--force", "Force ownership override")
|
|
@@ -1328,19 +3823,59 @@ program
|
|
|
1328
3823
|
const startedAt = Date.now();
|
|
1329
3824
|
const addValues = Array.isArray(options.add) ? options.add : [];
|
|
1330
3825
|
const removeValues = Array.isArray(options.remove) ? options.remove : [];
|
|
3826
|
+
const runInBackground = options.background === true;
|
|
3827
|
+
if (runInBackground && options.run !== true) {
|
|
3828
|
+
throw new PmCliError("--background requires --run", EXIT_CODE.USAGE);
|
|
3829
|
+
}
|
|
3830
|
+
if (runInBackground && (addValues.length > 0 || removeValues.length > 0)) {
|
|
3831
|
+
throw new PmCliError("--background does not support --add/--remove; update linked tests first, then run in background", EXIT_CODE.USAGE);
|
|
3832
|
+
}
|
|
3833
|
+
if (runInBackground) {
|
|
3834
|
+
const result = await runStartBackgroundRun({
|
|
3835
|
+
kind: "test",
|
|
3836
|
+
commandArgs: buildBackgroundTestCommandArgs(id, {
|
|
3837
|
+
...options,
|
|
3838
|
+
add: addValues,
|
|
3839
|
+
remove: removeValues,
|
|
3840
|
+
}),
|
|
3841
|
+
targetId: id,
|
|
3842
|
+
author: typeof options.author === "string" ? options.author : undefined,
|
|
3843
|
+
noExtensions: globalOptions.noExtensions === true,
|
|
3844
|
+
}, globalOptions);
|
|
3845
|
+
printResult(result, globalOptions);
|
|
3846
|
+
if (globalOptions.profile) {
|
|
3847
|
+
printError(`profile:command=test took_ms=${Date.now() - startedAt}`);
|
|
3848
|
+
}
|
|
3849
|
+
return;
|
|
3850
|
+
}
|
|
1331
3851
|
const result = await runTest(id, {
|
|
1332
3852
|
add: addValues,
|
|
1333
3853
|
remove: removeValues,
|
|
1334
3854
|
run: Boolean(options.run),
|
|
1335
3855
|
timeout: typeof options.timeout === "string" ? options.timeout : undefined,
|
|
3856
|
+
progress: Boolean(options.progress),
|
|
3857
|
+
envSet: Array.isArray(options.envSet) ? options.envSet : [],
|
|
3858
|
+
envClear: Array.isArray(options.envClear) ? options.envClear : [],
|
|
3859
|
+
sharedHostSafe: Boolean(options.sharedHostSafe),
|
|
3860
|
+
pmContext: typeof options.pmContext === "string" ? options.pmContext : undefined,
|
|
3861
|
+
overrideLinkedPmContext: Boolean(options.overrideLinkedPmContext),
|
|
3862
|
+
failOnContextMismatch: Boolean(options.failOnContextMismatch),
|
|
3863
|
+
failOnSkipped: Boolean(options.failOnSkipped),
|
|
3864
|
+
failOnEmptyTestRun: Boolean(options.failOnEmptyTestRun),
|
|
3865
|
+
requireAssertionsForPm: Boolean(options.requireAssertionsForPm),
|
|
3866
|
+
checkContext: Boolean(options.checkContext),
|
|
3867
|
+
autoPmContext: Boolean(options.autoPmContext),
|
|
1336
3868
|
author: typeof options.author === "string" ? options.author : undefined,
|
|
1337
3869
|
message: typeof options.message === "string" ? options.message : undefined,
|
|
1338
3870
|
force: Boolean(options.force),
|
|
1339
3871
|
}, globalOptions);
|
|
1340
|
-
if (addValues.length > 0 || removeValues.length > 0) {
|
|
3872
|
+
if (addValues.length > 0 || removeValues.length > 0 || options.run === true) {
|
|
1341
3873
|
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
1342
3874
|
}
|
|
1343
3875
|
printResult(result, globalOptions);
|
|
3876
|
+
if (result.run_results.some((entry) => entry.status === "failed") || result.fail_on_skipped_triggered === true) {
|
|
3877
|
+
process.exitCode = EXIT_CODE.DEPENDENCY_FAILED;
|
|
3878
|
+
}
|
|
1344
3879
|
if (globalOptions.profile) {
|
|
1345
3880
|
printError(`profile:command=test took_ms=${Date.now() - startedAt}`);
|
|
1346
3881
|
}
|
|
@@ -1349,22 +3884,169 @@ program
|
|
|
1349
3884
|
.command("test-all")
|
|
1350
3885
|
.description("Run linked tests across matching items.")
|
|
1351
3886
|
.option("--status <value>", "Filter items by status before running tests")
|
|
3887
|
+
.option("--limit <n>", "Limit matching items before running linked tests")
|
|
3888
|
+
.option("--offset <n>", "Skip matching items before running linked tests")
|
|
3889
|
+
.option("--background", "Run linked tests in managed background mode")
|
|
1352
3890
|
.option("--timeout <seconds>", "Default run timeout in seconds")
|
|
3891
|
+
.option("--progress", "Emit linked-test progress to stderr (always shown in TTY, opt-in for non-TTY)")
|
|
3892
|
+
.option("--env-set <value>", "Set environment variable(s) for linked-test runs (KEY=VALUE, repeatable)", collect)
|
|
3893
|
+
.option("--env-clear <value>", "Clear environment variable(s) for linked-test runs (NAME, repeatable)", collect)
|
|
3894
|
+
.option("--shared-host-safe", "Apply additive shared-host-safe runtime defaults for linked-test runs")
|
|
3895
|
+
.option("--pm-context <mode>", "PM linked-test context mode: schema|tracker|auto (default: schema)")
|
|
3896
|
+
.option("--override-linked-pm-context", "Force run-level --pm-context to override per-linked-test pm_context_mode metadata")
|
|
3897
|
+
.option("--fail-on-context-mismatch", "Fail linked PM commands when context item counts differ")
|
|
3898
|
+
.option("--fail-on-skipped", "Treat skipped linked tests as dependency failures")
|
|
3899
|
+
.option("--fail-on-empty-test-run", "Treat successful linked-test commands that report zero executed tests as failures")
|
|
3900
|
+
.option("--require-assertions-for-pm", "Require assertion metadata for linked PM command tests")
|
|
3901
|
+
.option("--check-context", "Preflight linked PM command context diagnostics before executing commands")
|
|
3902
|
+
.option("--auto-pm-context", "Auto-remediate PM tracker-read context mismatches by routing those linked commands through tracker context")
|
|
1353
3903
|
.action(async (options, command) => {
|
|
1354
3904
|
const globalOptions = getGlobalOptions(command);
|
|
1355
3905
|
const startedAt = Date.now();
|
|
3906
|
+
const runInBackground = options.background === true;
|
|
3907
|
+
if (runInBackground) {
|
|
3908
|
+
const result = await runStartBackgroundRun({
|
|
3909
|
+
kind: "test-all",
|
|
3910
|
+
commandArgs: buildBackgroundTestAllCommandArgs(options),
|
|
3911
|
+
statusFilter: typeof options.status === "string" ? options.status : undefined,
|
|
3912
|
+
noExtensions: globalOptions.noExtensions === true,
|
|
3913
|
+
}, globalOptions);
|
|
3914
|
+
printResult(result, globalOptions);
|
|
3915
|
+
if (globalOptions.profile) {
|
|
3916
|
+
printError(`profile:command=test-all took_ms=${Date.now() - startedAt}`);
|
|
3917
|
+
}
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
1356
3920
|
const result = await runTestAll({
|
|
1357
3921
|
status: typeof options.status === "string" ? options.status : undefined,
|
|
3922
|
+
limit: typeof options.limit === "string" ? options.limit : undefined,
|
|
3923
|
+
offset: typeof options.offset === "string" ? options.offset : undefined,
|
|
1358
3924
|
timeout: typeof options.timeout === "string" ? options.timeout : undefined,
|
|
3925
|
+
progress: Boolean(options.progress),
|
|
3926
|
+
envSet: Array.isArray(options.envSet) ? options.envSet : [],
|
|
3927
|
+
envClear: Array.isArray(options.envClear) ? options.envClear : [],
|
|
3928
|
+
sharedHostSafe: Boolean(options.sharedHostSafe),
|
|
3929
|
+
pmContext: typeof options.pmContext === "string" ? options.pmContext : undefined,
|
|
3930
|
+
overrideLinkedPmContext: Boolean(options.overrideLinkedPmContext),
|
|
3931
|
+
failOnContextMismatch: Boolean(options.failOnContextMismatch),
|
|
3932
|
+
failOnSkipped: Boolean(options.failOnSkipped),
|
|
3933
|
+
failOnEmptyTestRun: Boolean(options.failOnEmptyTestRun),
|
|
3934
|
+
requireAssertionsForPm: Boolean(options.requireAssertionsForPm),
|
|
3935
|
+
checkContext: Boolean(options.checkContext),
|
|
3936
|
+
autoPmContext: Boolean(options.autoPmContext),
|
|
1359
3937
|
}, globalOptions);
|
|
3938
|
+
await invalidateSearchCachesForMutation(globalOptions, {
|
|
3939
|
+
ids: result.results.map((entry) => entry.id),
|
|
3940
|
+
});
|
|
1360
3941
|
printResult(result, globalOptions);
|
|
1361
|
-
if (result.failed > 0) {
|
|
3942
|
+
if (result.failed > 0 || result.fail_on_skipped_triggered === true) {
|
|
1362
3943
|
process.exitCode = EXIT_CODE.DEPENDENCY_FAILED;
|
|
1363
3944
|
}
|
|
1364
3945
|
if (globalOptions.profile) {
|
|
1365
3946
|
printError(`profile:command=test-all took_ms=${Date.now() - startedAt}`);
|
|
1366
3947
|
}
|
|
1367
3948
|
});
|
|
3949
|
+
const testRunsCommand = program
|
|
3950
|
+
.command("test-runs")
|
|
3951
|
+
.description("Manage background linked-test runs.")
|
|
3952
|
+
.action(async (_options, command) => {
|
|
3953
|
+
const globalOptions = getGlobalOptions(command);
|
|
3954
|
+
const startedAt = Date.now();
|
|
3955
|
+
const result = await runTestRunsList({}, globalOptions);
|
|
3956
|
+
printResult(result, globalOptions);
|
|
3957
|
+
if (globalOptions.profile) {
|
|
3958
|
+
printError(`profile:command=test-runs took_ms=${Date.now() - startedAt}`);
|
|
3959
|
+
}
|
|
3960
|
+
});
|
|
3961
|
+
testRunsCommand
|
|
3962
|
+
.command("list")
|
|
3963
|
+
.option("--status <value>", "Filter by background run status")
|
|
3964
|
+
.option("--limit <value>", "Limit number of runs returned")
|
|
3965
|
+
.description("List background test runs.")
|
|
3966
|
+
.action(async (options, command) => {
|
|
3967
|
+
const globalOptions = getGlobalOptions(command);
|
|
3968
|
+
const startedAt = Date.now();
|
|
3969
|
+
const result = await runTestRunsList({
|
|
3970
|
+
status: typeof options.status === "string" ? options.status : undefined,
|
|
3971
|
+
limit: typeof options.limit === "string" ? options.limit : undefined,
|
|
3972
|
+
}, globalOptions);
|
|
3973
|
+
printResult(result, globalOptions);
|
|
3974
|
+
if (globalOptions.profile) {
|
|
3975
|
+
printError(`profile:command=test-runs list took_ms=${Date.now() - startedAt}`);
|
|
3976
|
+
}
|
|
3977
|
+
});
|
|
3978
|
+
testRunsCommand
|
|
3979
|
+
.command("status")
|
|
3980
|
+
.argument("<runId>", "Background run id")
|
|
3981
|
+
.description("Show status, health, and resource snapshot for a background run.")
|
|
3982
|
+
.action(async (runId, _options, command) => {
|
|
3983
|
+
const globalOptions = getGlobalOptions(command);
|
|
3984
|
+
const startedAt = Date.now();
|
|
3985
|
+
const result = await runTestRunsStatus(runId, globalOptions);
|
|
3986
|
+
printResult(result, globalOptions);
|
|
3987
|
+
if (globalOptions.profile) {
|
|
3988
|
+
printError(`profile:command=test-runs status took_ms=${Date.now() - startedAt}`);
|
|
3989
|
+
}
|
|
3990
|
+
});
|
|
3991
|
+
testRunsCommand
|
|
3992
|
+
.command("logs")
|
|
3993
|
+
.argument("<runId>", "Background run id")
|
|
3994
|
+
.option("--stream <value>", "Log stream selector: stdout|stderr|both")
|
|
3995
|
+
.option("--tail <value>", "Tail number of lines per selected stream")
|
|
3996
|
+
.description("Show tailed logs for a background run.")
|
|
3997
|
+
.action(async (runId, options, command) => {
|
|
3998
|
+
const globalOptions = getGlobalOptions(command);
|
|
3999
|
+
const startedAt = Date.now();
|
|
4000
|
+
const result = await runTestRunsLogs(runId, {
|
|
4001
|
+
stream: typeof options.stream === "string" ? options.stream : undefined,
|
|
4002
|
+
tail: typeof options.tail === "string" ? options.tail : undefined,
|
|
4003
|
+
}, globalOptions);
|
|
4004
|
+
printResult(result, globalOptions);
|
|
4005
|
+
if (globalOptions.profile) {
|
|
4006
|
+
printError(`profile:command=test-runs logs took_ms=${Date.now() - startedAt}`);
|
|
4007
|
+
}
|
|
4008
|
+
});
|
|
4009
|
+
testRunsCommand
|
|
4010
|
+
.command("stop")
|
|
4011
|
+
.argument("<runId>", "Background run id")
|
|
4012
|
+
.option("--force", "Force-stop via SIGKILL")
|
|
4013
|
+
.description("Stop a running background test run.")
|
|
4014
|
+
.action(async (runId, options, command) => {
|
|
4015
|
+
const globalOptions = getGlobalOptions(command);
|
|
4016
|
+
const startedAt = Date.now();
|
|
4017
|
+
const result = await runTestRunsStop(runId, {
|
|
4018
|
+
force: options.force === true,
|
|
4019
|
+
}, globalOptions);
|
|
4020
|
+
printResult(result, globalOptions);
|
|
4021
|
+
if (globalOptions.profile) {
|
|
4022
|
+
printError(`profile:command=test-runs stop took_ms=${Date.now() - startedAt}`);
|
|
4023
|
+
}
|
|
4024
|
+
});
|
|
4025
|
+
testRunsCommand
|
|
4026
|
+
.command("resume")
|
|
4027
|
+
.argument("<runId>", "Background run id")
|
|
4028
|
+
.option("--author <value>", "Resume author (falls back to PM_AUTHOR/settings)")
|
|
4029
|
+
.description("Resume a previously terminal background test run by starting a new attempt.")
|
|
4030
|
+
.action(async (runId, options, command) => {
|
|
4031
|
+
const globalOptions = getGlobalOptions(command);
|
|
4032
|
+
const startedAt = Date.now();
|
|
4033
|
+
const result = await runTestRunsResume(runId, {
|
|
4034
|
+
author: typeof options.author === "string" ? options.author : undefined,
|
|
4035
|
+
noExtensions: globalOptions.noExtensions === true,
|
|
4036
|
+
}, globalOptions);
|
|
4037
|
+
printResult(result, globalOptions);
|
|
4038
|
+
if (globalOptions.profile) {
|
|
4039
|
+
printError(`profile:command=test-runs resume took_ms=${Date.now() - startedAt}`);
|
|
4040
|
+
}
|
|
4041
|
+
});
|
|
4042
|
+
program
|
|
4043
|
+
.command("test-runs-worker", { hidden: true })
|
|
4044
|
+
.argument("<runId>", "Background run id")
|
|
4045
|
+
.description("Internal background worker command.")
|
|
4046
|
+
.action(async (runId, _options, command) => {
|
|
4047
|
+
const globalOptions = getGlobalOptions(command);
|
|
4048
|
+
await runTestRunsWorker(runId, globalOptions);
|
|
4049
|
+
});
|
|
1368
4050
|
program
|
|
1369
4051
|
.command("stats")
|
|
1370
4052
|
.description("Show project tracker statistics.")
|
|
@@ -1380,27 +4062,120 @@ program
|
|
|
1380
4062
|
program
|
|
1381
4063
|
.command("health")
|
|
1382
4064
|
.description("Show project tracker health checks.")
|
|
1383
|
-
.
|
|
4065
|
+
.option("--strict-directories", "Treat optional item-type directories as required failures")
|
|
4066
|
+
.option("--check-only", "Run read-only health diagnostics without refreshing vectors")
|
|
4067
|
+
.option("--check-telemetry", "Probe telemetry endpoint health and include network diagnostics")
|
|
4068
|
+
.option("--no-refresh", "Disable automatic vector refresh attempts during health checks")
|
|
4069
|
+
.option("--refresh-vectors", "Explicitly enable vector refresh attempts during health checks")
|
|
4070
|
+
.option("--verbose-stale-items", "Include full stale vectorization ID lists in health output")
|
|
4071
|
+
.option("--strict-exit", "Return non-zero exit when health warnings are present (ok=false)")
|
|
4072
|
+
.option("--fail-on-warn", "Alias for --strict-exit")
|
|
4073
|
+
.action(async (options, command) => {
|
|
1384
4074
|
const globalOptions = getGlobalOptions(command);
|
|
1385
4075
|
const startedAt = Date.now();
|
|
1386
|
-
const result = await runHealth(globalOptions
|
|
4076
|
+
const result = await runHealth(globalOptions, {
|
|
4077
|
+
strictDirectories: Boolean(options.strictDirectories),
|
|
4078
|
+
checkOnly: Boolean(options.checkOnly),
|
|
4079
|
+
checkTelemetry: Boolean(options.checkTelemetry),
|
|
4080
|
+
noRefresh: Boolean(options.noRefresh),
|
|
4081
|
+
refreshVectors: Boolean(options.refreshVectors),
|
|
4082
|
+
verboseStaleItems: Boolean(options.verboseStaleItems),
|
|
4083
|
+
});
|
|
1387
4084
|
printResult(result, globalOptions);
|
|
4085
|
+
const strictExit = Boolean(options.strictExit) || Boolean(options.failOnWarn);
|
|
4086
|
+
if (strictExit && !result.ok) {
|
|
4087
|
+
process.exitCode = EXIT_CODE.GENERIC_FAILURE;
|
|
4088
|
+
}
|
|
1388
4089
|
if (globalOptions.profile) {
|
|
1389
4090
|
printError(`profile:command=health took_ms=${Date.now() - startedAt}`);
|
|
1390
4091
|
}
|
|
1391
4092
|
});
|
|
4093
|
+
program
|
|
4094
|
+
.command("validate")
|
|
4095
|
+
.description("Run standalone metadata, resolution, lifecycle, files, linked-command reference, and history drift validation checks.")
|
|
4096
|
+
.option("--check-metadata", "Run metadata completeness checks")
|
|
4097
|
+
.option("--metadata-profile <value>", "Select metadata validation profile for --check-metadata (core|strict|custom)")
|
|
4098
|
+
.option("--check-resolution", "Run closed-item resolution metadata checks")
|
|
4099
|
+
.option("--check-lifecycle", "Run active-item lifecycle governance drift checks")
|
|
4100
|
+
.option("--check-stale-blockers", "Include stale blocker-pattern diagnostics in lifecycle checks")
|
|
4101
|
+
.option("--dependency-cycle-severity <value>", "Set dependency-cycle warning policy for lifecycle checks (off|warn|error)")
|
|
4102
|
+
.option("--check-files", "Run linked-file and orphaned-file checks")
|
|
4103
|
+
.option("--check-command-references", "Run linked-command PM-ID reference checks")
|
|
4104
|
+
.option("--scan-mode <value>", "Select file candidate scan mode for --check-files (default|tracked-all|tracked-all-strict)")
|
|
4105
|
+
.option("--include-pm-internals", "Include PM storage internals in tracked-all candidate scans")
|
|
4106
|
+
.option("--verbose-file-lists", "Include full file-path lists for validate --check-files details")
|
|
4107
|
+
.option("--strict-exit", "Return non-zero exit when validation warnings are present (ok=false)")
|
|
4108
|
+
.option("--fail-on-warn", "Alias for --strict-exit")
|
|
4109
|
+
.option("--check-history-drift", "Run item/history hash drift checks")
|
|
4110
|
+
.action(async (options, command) => {
|
|
4111
|
+
const globalOptions = getGlobalOptions(command);
|
|
4112
|
+
const startedAt = Date.now();
|
|
4113
|
+
const result = await runValidate({
|
|
4114
|
+
checkMetadata: Boolean(options.checkMetadata),
|
|
4115
|
+
metadataProfile: typeof options.metadataProfile === "string" ? options.metadataProfile : undefined,
|
|
4116
|
+
checkResolution: Boolean(options.checkResolution),
|
|
4117
|
+
checkLifecycle: Boolean(options.checkLifecycle),
|
|
4118
|
+
checkStaleBlockers: Boolean(options.checkStaleBlockers),
|
|
4119
|
+
dependencyCycleSeverity: typeof options.dependencyCycleSeverity === "string" ? options.dependencyCycleSeverity : undefined,
|
|
4120
|
+
checkFiles: Boolean(options.checkFiles),
|
|
4121
|
+
checkCommandReferences: Boolean(options.checkCommandReferences),
|
|
4122
|
+
scanMode: typeof options.scanMode === "string" ? options.scanMode : undefined,
|
|
4123
|
+
includePmInternals: Boolean(options.includePmInternals),
|
|
4124
|
+
verboseFileLists: Boolean(options.verboseFileLists),
|
|
4125
|
+
checkHistoryDrift: Boolean(options.checkHistoryDrift),
|
|
4126
|
+
}, globalOptions);
|
|
4127
|
+
printResult(result, globalOptions);
|
|
4128
|
+
const strictExit = Boolean(options.strictExit) || Boolean(options.failOnWarn);
|
|
4129
|
+
if (strictExit && !result.ok) {
|
|
4130
|
+
process.exitCode = EXIT_CODE.GENERIC_FAILURE;
|
|
4131
|
+
}
|
|
4132
|
+
if (globalOptions.profile) {
|
|
4133
|
+
printError(`profile:command=validate took_ms=${Date.now() - startedAt}`);
|
|
4134
|
+
}
|
|
4135
|
+
});
|
|
1392
4136
|
program
|
|
1393
4137
|
.command("gc")
|
|
4138
|
+
.option("--dry-run", "Preview cleanup targets without deleting files")
|
|
4139
|
+
.option("--scope <value>", "Limit cleanup to one or more scopes (comma-separated or repeatable): index, embeddings, runtime", collect)
|
|
1394
4140
|
.description("Clean optional cache artifacts and show a summary.")
|
|
1395
|
-
.action(async (
|
|
4141
|
+
.action(async (options, command) => {
|
|
1396
4142
|
const globalOptions = getGlobalOptions(command);
|
|
1397
4143
|
const startedAt = Date.now();
|
|
1398
|
-
const result = await runGc(globalOptions
|
|
4144
|
+
const result = await runGc(globalOptions, {
|
|
4145
|
+
dryRun: options.dryRun === true,
|
|
4146
|
+
scope: Array.isArray(options.scope) ? options.scope : [],
|
|
4147
|
+
});
|
|
1399
4148
|
printResult(result, globalOptions);
|
|
1400
4149
|
if (globalOptions.profile) {
|
|
1401
4150
|
printError(`profile:command=gc took_ms=${Date.now() - startedAt}`);
|
|
1402
4151
|
}
|
|
1403
4152
|
});
|
|
4153
|
+
program
|
|
4154
|
+
.command("contracts")
|
|
4155
|
+
.description("Show machine-readable command and schema contracts for agents.")
|
|
4156
|
+
.option("--action <value>", "Filter tool schema branches to a specific action")
|
|
4157
|
+
.option("--command <value>", "Scope contracts output to one CLI command (narrow-by-default)")
|
|
4158
|
+
.option("--schema-only", "Return schema-focused output only")
|
|
4159
|
+
.option("--flags-only", "Return command flag contracts only")
|
|
4160
|
+
.option("--availability-only", "Return action availability surface only")
|
|
4161
|
+
.option("--runtime-only", "Include only actions invocable in the current runtime")
|
|
4162
|
+
.option("--active-only", "Alias for --runtime-only")
|
|
4163
|
+
.action(async (options, command) => {
|
|
4164
|
+
const globalOptions = getGlobalOptions(command);
|
|
4165
|
+
const startedAt = Date.now();
|
|
4166
|
+
const result = await runContracts({
|
|
4167
|
+
action: typeof options.action === "string" ? options.action : undefined,
|
|
4168
|
+
command: typeof options.command === "string" ? options.command : undefined,
|
|
4169
|
+
schemaOnly: Boolean(options.schemaOnly),
|
|
4170
|
+
flagsOnly: Boolean(options.flagsOnly),
|
|
4171
|
+
availabilityOnly: Boolean(options.availabilityOnly),
|
|
4172
|
+
runtimeOnly: Boolean(options.runtimeOnly) || Boolean(options.activeOnly),
|
|
4173
|
+
}, globalOptions);
|
|
4174
|
+
printResult(result, globalOptions);
|
|
4175
|
+
if (globalOptions.profile) {
|
|
4176
|
+
printError(`profile:command=contracts took_ms=${Date.now() - startedAt}`);
|
|
4177
|
+
}
|
|
4178
|
+
});
|
|
1404
4179
|
program
|
|
1405
4180
|
.command("claim")
|
|
1406
4181
|
.argument("<id>", "Item id")
|
|
@@ -1426,6 +4201,7 @@ program
|
|
|
1426
4201
|
.argument("<id>", "Item id")
|
|
1427
4202
|
.option("--author <value>", "Mutation author")
|
|
1428
4203
|
.option("--message <value>", "History message")
|
|
4204
|
+
.option("--allow-audit-release", "Allow non-owner release handoffs without requiring --force")
|
|
1429
4205
|
.option("--force", "Force release override")
|
|
1430
4206
|
.description("Release an item's active claim.")
|
|
1431
4207
|
.action(async (id, options, command) => {
|
|
@@ -1434,6 +4210,7 @@ program
|
|
|
1434
4210
|
const result = await runRelease(id, Boolean(options.force), globalOptions, {
|
|
1435
4211
|
author: typeof options.author === "string" ? options.author : undefined,
|
|
1436
4212
|
message: typeof options.message === "string" ? options.message : undefined,
|
|
4213
|
+
allowAuditRelease: options.allowAuditRelease === true,
|
|
1437
4214
|
});
|
|
1438
4215
|
await invalidateSearchCachesForMutation(globalOptions, result);
|
|
1439
4216
|
printResult(result, globalOptions);
|
|
@@ -1441,54 +4218,457 @@ program
|
|
|
1441
4218
|
printError(`profile:command=release took_ms=${Date.now() - startedAt}`);
|
|
1442
4219
|
}
|
|
1443
4220
|
});
|
|
4221
|
+
program
|
|
4222
|
+
.command("start-task")
|
|
4223
|
+
.argument("<id>", "Item id")
|
|
4224
|
+
.option("--author <value>", "Mutation author")
|
|
4225
|
+
.option("--message <value>", "History message")
|
|
4226
|
+
.option("--force", "Force ownership or terminal override when required")
|
|
4227
|
+
.description("Lifecycle alias: claim an item and move it to in_progress.")
|
|
4228
|
+
.action(async (id, options, command) => {
|
|
4229
|
+
const globalOptions = getGlobalOptions(command);
|
|
4230
|
+
const startedAt = Date.now();
|
|
4231
|
+
const pmRoot = resolvePmRoot(process.cwd(), globalOptions.path);
|
|
4232
|
+
const settings = await readSettings(pmRoot);
|
|
4233
|
+
const statusRegistry = resolveRuntimeStatusRegistry(settings.schema);
|
|
4234
|
+
const inProgressStatus = normalizeStatusInput("in_progress", statusRegistry) ?? statusRegistry.open_status;
|
|
4235
|
+
const force = Boolean(options.force);
|
|
4236
|
+
const mutationOptions = {
|
|
4237
|
+
author: typeof options.author === "string" ? options.author : undefined,
|
|
4238
|
+
message: typeof options.message === "string" ? options.message : undefined,
|
|
4239
|
+
};
|
|
4240
|
+
const claimResult = await runClaim(id, force, globalOptions, mutationOptions);
|
|
4241
|
+
await invalidateSearchCachesForMutation(globalOptions, claimResult);
|
|
4242
|
+
const updateResult = await runUpdate(id, {
|
|
4243
|
+
...mutationOptions,
|
|
4244
|
+
status: inProgressStatus,
|
|
4245
|
+
force,
|
|
4246
|
+
}, globalOptions);
|
|
4247
|
+
await invalidateSearchCachesForMutation(globalOptions, updateResult);
|
|
4248
|
+
printResult({
|
|
4249
|
+
id,
|
|
4250
|
+
action: "start_task",
|
|
4251
|
+
claim: claimResult,
|
|
4252
|
+
update: updateResult,
|
|
4253
|
+
}, globalOptions);
|
|
4254
|
+
if (globalOptions.profile) {
|
|
4255
|
+
printError(`profile:command=start-task took_ms=${Date.now() - startedAt}`);
|
|
4256
|
+
}
|
|
4257
|
+
});
|
|
4258
|
+
program
|
|
4259
|
+
.command("pause-task")
|
|
4260
|
+
.argument("<id>", "Item id")
|
|
4261
|
+
.option("--author <value>", "Mutation author")
|
|
4262
|
+
.option("--message <value>", "History message")
|
|
4263
|
+
.option("--force", "Force ownership override when required")
|
|
4264
|
+
.description("Lifecycle alias: move an item to open and release its claim.")
|
|
4265
|
+
.action(async (id, options, command) => {
|
|
4266
|
+
const globalOptions = getGlobalOptions(command);
|
|
4267
|
+
const startedAt = Date.now();
|
|
4268
|
+
const pmRoot = resolvePmRoot(process.cwd(), globalOptions.path);
|
|
4269
|
+
const settings = await readSettings(pmRoot);
|
|
4270
|
+
const statusRegistry = resolveRuntimeStatusRegistry(settings.schema);
|
|
4271
|
+
const openStatus = statusRegistry.open_status;
|
|
4272
|
+
const force = Boolean(options.force);
|
|
4273
|
+
const mutationOptions = {
|
|
4274
|
+
author: typeof options.author === "string" ? options.author : undefined,
|
|
4275
|
+
message: typeof options.message === "string" ? options.message : undefined,
|
|
4276
|
+
};
|
|
4277
|
+
const updateResult = await runUpdate(id, {
|
|
4278
|
+
...mutationOptions,
|
|
4279
|
+
status: openStatus,
|
|
4280
|
+
force,
|
|
4281
|
+
}, globalOptions);
|
|
4282
|
+
await invalidateSearchCachesForMutation(globalOptions, updateResult);
|
|
4283
|
+
const releaseResult = await runRelease(id, force, globalOptions, mutationOptions);
|
|
4284
|
+
await invalidateSearchCachesForMutation(globalOptions, releaseResult);
|
|
4285
|
+
printResult({
|
|
4286
|
+
id,
|
|
4287
|
+
action: "pause_task",
|
|
4288
|
+
update: updateResult,
|
|
4289
|
+
release: releaseResult,
|
|
4290
|
+
}, globalOptions);
|
|
4291
|
+
if (globalOptions.profile) {
|
|
4292
|
+
printError(`profile:command=pause-task took_ms=${Date.now() - startedAt}`);
|
|
4293
|
+
}
|
|
4294
|
+
});
|
|
4295
|
+
program
|
|
4296
|
+
.command("close-task")
|
|
4297
|
+
.argument("<id>", "Item id")
|
|
4298
|
+
.argument("<reason>", "Close reason text")
|
|
4299
|
+
.option("--author <value>", "Mutation author")
|
|
4300
|
+
.option("--message <value>", "History message")
|
|
4301
|
+
.option("--validate-close <value>", "Close-time validation mode: off|warn|strict")
|
|
4302
|
+
.option("--force", "Force ownership or terminal override when required")
|
|
4303
|
+
.description("Lifecycle alias: close an item with reason and release assignment metadata.")
|
|
4304
|
+
.action(async (id, reason, options, command) => {
|
|
4305
|
+
const globalOptions = getGlobalOptions(command);
|
|
4306
|
+
const startedAt = Date.now();
|
|
4307
|
+
const force = Boolean(options.force);
|
|
4308
|
+
const mutationOptions = {
|
|
4309
|
+
author: typeof options.author === "string" ? options.author : undefined,
|
|
4310
|
+
message: typeof options.message === "string" ? options.message : undefined,
|
|
4311
|
+
};
|
|
4312
|
+
const closeResult = await runClose(id, reason, {
|
|
4313
|
+
...mutationOptions,
|
|
4314
|
+
validateClose: typeof options.validateClose === "string" ? options.validateClose : undefined,
|
|
4315
|
+
force,
|
|
4316
|
+
}, globalOptions);
|
|
4317
|
+
await invalidateSearchCachesForMutation(globalOptions, closeResult);
|
|
4318
|
+
const releaseResult = await runRelease(id, force, globalOptions, mutationOptions);
|
|
4319
|
+
await invalidateSearchCachesForMutation(globalOptions, releaseResult);
|
|
4320
|
+
printResult({
|
|
4321
|
+
id,
|
|
4322
|
+
action: "close_task",
|
|
4323
|
+
close: closeResult,
|
|
4324
|
+
release: releaseResult,
|
|
4325
|
+
}, globalOptions);
|
|
4326
|
+
if (globalOptions.profile) {
|
|
4327
|
+
printError(`profile:command=close-task took_ms=${Date.now() - startedAt}`);
|
|
4328
|
+
}
|
|
4329
|
+
});
|
|
1444
4330
|
program
|
|
1445
4331
|
.command("completion")
|
|
1446
4332
|
.argument("<shell>", "Shell type: bash, zsh, or fish")
|
|
4333
|
+
.option("--eager-tags", "Embed current tracker tags directly in generated scripts (legacy eager mode)")
|
|
1447
4334
|
.description("Generate shell completion for pm.")
|
|
1448
|
-
.action((shell,
|
|
4335
|
+
.action(async (shell, options, command) => {
|
|
1449
4336
|
const globalOptions = getGlobalOptions(command);
|
|
1450
|
-
const
|
|
4337
|
+
const pmRoot = resolvePmRoot(process.cwd(), globalOptions.path);
|
|
4338
|
+
let completionTypes;
|
|
4339
|
+
let completionTags;
|
|
4340
|
+
let completionStatuses;
|
|
4341
|
+
const completionCommandFlags = {};
|
|
4342
|
+
const eagerTags = Boolean(options.eagerTags);
|
|
4343
|
+
if (await pathExists(getSettingsPath(pmRoot))) {
|
|
4344
|
+
const settings = await readSettings(pmRoot);
|
|
4345
|
+
const statusRegistry = resolveRuntimeStatusRegistry(settings.schema);
|
|
4346
|
+
const runtimeFieldRegistry = resolveRuntimeFieldRegistry(settings.schema);
|
|
4347
|
+
const typeRegistry = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations());
|
|
4348
|
+
completionTypes = typeRegistry.types;
|
|
4349
|
+
completionStatuses = statusRegistry.definitions.map((definition) => definition.id);
|
|
4350
|
+
for (const [commandKey, definitions] of runtimeFieldRegistry.command_to_fields.entries()) {
|
|
4351
|
+
if (commandKey !== "list" &&
|
|
4352
|
+
commandKey !== "create" &&
|
|
4353
|
+
commandKey !== "update" &&
|
|
4354
|
+
commandKey !== "search" &&
|
|
4355
|
+
commandKey !== "calendar" &&
|
|
4356
|
+
commandKey !== "context") {
|
|
4357
|
+
continue;
|
|
4358
|
+
}
|
|
4359
|
+
const runtimeFlags = new Set();
|
|
4360
|
+
for (const definition of definitions) {
|
|
4361
|
+
runtimeFlags.add(`--${definition.cli_flag}`);
|
|
4362
|
+
for (const alias of definition.cli_aliases) {
|
|
4363
|
+
if (alias.startsWith("--") || (alias.startsWith("-") && !alias.startsWith("--"))) {
|
|
4364
|
+
runtimeFlags.add(alias);
|
|
4365
|
+
}
|
|
4366
|
+
else {
|
|
4367
|
+
runtimeFlags.add(`--${alias}`);
|
|
4368
|
+
}
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
completionCommandFlags[commandKey] = [...runtimeFlags].sort((left, right) => left.localeCompare(right));
|
|
4372
|
+
}
|
|
4373
|
+
if (completionCommandFlags.update) {
|
|
4374
|
+
completionCommandFlags["update-many"] = [...completionCommandFlags.update];
|
|
4375
|
+
}
|
|
4376
|
+
if (eagerTags) {
|
|
4377
|
+
const items = await listAllFrontMatter(pmRoot, settings.item_format, typeRegistry.type_to_folder, undefined, settings.schema);
|
|
4378
|
+
completionTags = [...new Set(items.flatMap((item) => item.tags ?? []).map((tag) => tag.trim()).filter((tag) => tag.length > 0))]
|
|
4379
|
+
.sort((left, right) => left.localeCompare(right));
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
4382
|
+
const result = runCompletion(shell, completionTypes, completionTags ?? [], eagerTags, {
|
|
4383
|
+
statuses: completionStatuses,
|
|
4384
|
+
command_flags: completionCommandFlags,
|
|
4385
|
+
});
|
|
1451
4386
|
if (globalOptions.json) {
|
|
1452
4387
|
printResult(result, globalOptions);
|
|
1453
4388
|
}
|
|
1454
4389
|
else if (!globalOptions.quiet) {
|
|
1455
|
-
|
|
4390
|
+
writeStdout(`${result.script}\n`);
|
|
1456
4391
|
}
|
|
1457
4392
|
if (globalOptions.profile) {
|
|
1458
4393
|
printError(`profile:command=completion took_ms=0`);
|
|
1459
4394
|
}
|
|
1460
4395
|
});
|
|
4396
|
+
program
|
|
4397
|
+
.command("completion-tags", { hidden: true })
|
|
4398
|
+
.description("Internal dynamic completion tag source.")
|
|
4399
|
+
.action(async (_options, command) => {
|
|
4400
|
+
const globalOptions = getGlobalOptions(command);
|
|
4401
|
+
const startedAt = Date.now();
|
|
4402
|
+
const pmRoot = resolvePmRoot(process.cwd(), globalOptions.path);
|
|
4403
|
+
let tags = [];
|
|
4404
|
+
if (await pathExists(getSettingsPath(pmRoot))) {
|
|
4405
|
+
const settings = await readSettings(pmRoot);
|
|
4406
|
+
const typeRegistry = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations());
|
|
4407
|
+
const items = await listAllFrontMatter(pmRoot, settings.item_format, typeRegistry.type_to_folder, undefined, settings.schema);
|
|
4408
|
+
tags = [...new Set(items.flatMap((item) => item.tags ?? []).map((tag) => tag.trim()).filter((tag) => tag.length > 0))].sort((left, right) => left.localeCompare(right));
|
|
4409
|
+
}
|
|
4410
|
+
if (globalOptions.json) {
|
|
4411
|
+
printResult({
|
|
4412
|
+
tags,
|
|
4413
|
+
count: tags.length,
|
|
4414
|
+
}, globalOptions);
|
|
4415
|
+
}
|
|
4416
|
+
else if (!globalOptions.quiet) {
|
|
4417
|
+
writeStdout(tags.join("\n"));
|
|
4418
|
+
if (tags.length > 0) {
|
|
4419
|
+
writeStdout("\n");
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
if (globalOptions.profile) {
|
|
4423
|
+
printError(`profile:command=completion-tags took_ms=${Date.now() - startedAt}`);
|
|
4424
|
+
}
|
|
4425
|
+
});
|
|
4426
|
+
attachRichHelpText(program, normalizeLegacyExtensionActionSyntax(process.argv.slice(2)));
|
|
4427
|
+
function collectRuntimeCommandPaths(root) {
|
|
4428
|
+
const commandPaths = new Set();
|
|
4429
|
+
const queue = [...root.commands];
|
|
4430
|
+
while (queue.length > 0) {
|
|
4431
|
+
const current = queue.shift();
|
|
4432
|
+
if (!current) {
|
|
4433
|
+
continue;
|
|
4434
|
+
}
|
|
4435
|
+
const normalizedPath = normalizeHelpCommandPath(getCommandPath(current));
|
|
4436
|
+
const hasInternalSegment = normalizedPath.split(" ").some((segment) => segment.startsWith("_"));
|
|
4437
|
+
if (normalizedPath.length > 0 && !hasInternalSegment) {
|
|
4438
|
+
commandPaths.add(normalizedPath);
|
|
4439
|
+
}
|
|
4440
|
+
queue.push(...current.commands);
|
|
4441
|
+
}
|
|
4442
|
+
for (const descriptorPath of activeRuntimeExtensionCommandDescriptors.keys()) {
|
|
4443
|
+
const normalizedPath = normalizeHelpCommandPath(descriptorPath);
|
|
4444
|
+
const hasInternalSegment = normalizedPath.split(" ").some((segment) => segment.startsWith("_"));
|
|
4445
|
+
if (normalizedPath.length > 0 && !hasInternalSegment) {
|
|
4446
|
+
commandPaths.add(normalizedPath);
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
return [...commandPaths].sort((left, right) => left.localeCompare(right));
|
|
4450
|
+
}
|
|
4451
|
+
function scoreCommandPathMatch(commandPath, queryToken) {
|
|
4452
|
+
const normalizedPath = commandPath.trim().toLowerCase();
|
|
4453
|
+
const normalizedToken = queryToken.trim().toLowerCase();
|
|
4454
|
+
if (normalizedToken.length === 0) {
|
|
4455
|
+
return Number.POSITIVE_INFINITY;
|
|
4456
|
+
}
|
|
4457
|
+
const pathSegments = normalizedPath.split(" ");
|
|
4458
|
+
if (normalizedPath === normalizedToken) {
|
|
4459
|
+
return 0;
|
|
4460
|
+
}
|
|
4461
|
+
if (pathSegments.includes(normalizedToken)) {
|
|
4462
|
+
return 1;
|
|
4463
|
+
}
|
|
4464
|
+
if (pathSegments.some((segment) => segment.startsWith(normalizedToken))) {
|
|
4465
|
+
return 2;
|
|
4466
|
+
}
|
|
4467
|
+
if (normalizedPath.includes(normalizedToken)) {
|
|
4468
|
+
return 3;
|
|
4469
|
+
}
|
|
4470
|
+
return Number.POSITIVE_INFINITY;
|
|
4471
|
+
}
|
|
4472
|
+
function buildUnknownCommandGuidanceFromRuntime(rawMessage, root) {
|
|
4473
|
+
const unknownCommandMatch = rawMessage.match(/unknown command '([^']+)'/i);
|
|
4474
|
+
if (!unknownCommandMatch || typeof unknownCommandMatch[1] !== "string") {
|
|
4475
|
+
return undefined;
|
|
4476
|
+
}
|
|
4477
|
+
const normalizedUnknown = normalizeHelpCommandPath(unknownCommandMatch[1]);
|
|
4478
|
+
if (normalizedUnknown.length === 0) {
|
|
4479
|
+
return undefined;
|
|
4480
|
+
}
|
|
4481
|
+
const commandPaths = collectRuntimeCommandPaths(root);
|
|
4482
|
+
if (commandPaths.length === 0) {
|
|
4483
|
+
return undefined;
|
|
4484
|
+
}
|
|
4485
|
+
const primaryToken = normalizedUnknown.split(" ")[0] ?? normalizedUnknown;
|
|
4486
|
+
const rankedCandidates = commandPaths
|
|
4487
|
+
.map((commandPath) => {
|
|
4488
|
+
const directScore = scoreCommandPathMatch(commandPath, normalizedUnknown);
|
|
4489
|
+
const fallbackScore = primaryToken !== normalizedUnknown ? scoreCommandPathMatch(commandPath, primaryToken) : Number.POSITIVE_INFINITY;
|
|
4490
|
+
const score = Math.min(directScore, fallbackScore);
|
|
4491
|
+
return { commandPath, score };
|
|
4492
|
+
})
|
|
4493
|
+
.filter((entry) => Number.isFinite(entry.score))
|
|
4494
|
+
.sort((left, right) => {
|
|
4495
|
+
if (left.score !== right.score) {
|
|
4496
|
+
return left.score - right.score;
|
|
4497
|
+
}
|
|
4498
|
+
return left.commandPath.localeCompare(right.commandPath);
|
|
4499
|
+
})
|
|
4500
|
+
.map((entry) => entry.commandPath);
|
|
4501
|
+
const fallbackTopLevel = [...new Set(commandPaths.map((commandPath) => commandPath.split(" ")[0]).filter((segment) => segment.length > 0))];
|
|
4502
|
+
fallbackTopLevel.sort((left, right) => left.localeCompare(right));
|
|
4503
|
+
const suggestedPaths = (rankedCandidates.length > 0 ? rankedCandidates : fallbackTopLevel).slice(0, 3);
|
|
4504
|
+
const examples = [...new Set(["pm --help", ...suggestedPaths.map((path) => `pm ${path} --help`)])];
|
|
4505
|
+
return {
|
|
4506
|
+
unknownCommandExamples: examples,
|
|
4507
|
+
unknownCommandNextSteps: [
|
|
4508
|
+
'Run "pm --help" to list commands available in this runtime, including active extensions.',
|
|
4509
|
+
"Use one of the suggested command paths above with --help to inspect valid flags and usage.",
|
|
4510
|
+
],
|
|
4511
|
+
};
|
|
4512
|
+
}
|
|
4513
|
+
function resolveChildCommandByToken(parent, token) {
|
|
4514
|
+
const normalizedToken = token.trim().toLowerCase();
|
|
4515
|
+
return parent.commands.find((candidate) => {
|
|
4516
|
+
if (candidate.name().trim().toLowerCase() === normalizedToken) {
|
|
4517
|
+
return true;
|
|
4518
|
+
}
|
|
4519
|
+
const aliases = typeof candidate.aliases === "function" ? candidate.aliases() : [];
|
|
4520
|
+
return aliases.some((alias) => alias.trim().toLowerCase() === normalizedToken);
|
|
4521
|
+
});
|
|
4522
|
+
}
|
|
4523
|
+
function isKnownHelpCommandPath(root, commandPathTokens) {
|
|
4524
|
+
if (commandPathTokens.length === 0) {
|
|
4525
|
+
return true;
|
|
4526
|
+
}
|
|
4527
|
+
let current = root;
|
|
4528
|
+
let matchedAny = false;
|
|
4529
|
+
for (const token of commandPathTokens) {
|
|
4530
|
+
const next = resolveChildCommandByToken(current, token);
|
|
4531
|
+
if (!next) {
|
|
4532
|
+
return matchedAny;
|
|
4533
|
+
}
|
|
4534
|
+
matchedAny = true;
|
|
4535
|
+
current = next;
|
|
4536
|
+
}
|
|
4537
|
+
return matchedAny;
|
|
4538
|
+
}
|
|
4539
|
+
async function resolveCommanderUsageContext(error) {
|
|
4540
|
+
const rawMessage = typeof error === "object" && error !== null ? error.message : undefined;
|
|
4541
|
+
const message = rawMessage ?? "Invalid command usage";
|
|
4542
|
+
const invocationArgv = normalizeLegacyExtensionActionSyntax(process.argv.slice(2));
|
|
4543
|
+
const bootstrapGlobal = parseBootstrapGlobalOptions(invocationArgv);
|
|
4544
|
+
const commandName = parseBootstrapCommandName(invocationArgv);
|
|
4545
|
+
let allowedTypes = BUILTIN_TYPE_HELP_VALUES;
|
|
4546
|
+
try {
|
|
4547
|
+
const pmRoot = resolvePmRoot(process.cwd(), bootstrapGlobal.path);
|
|
4548
|
+
if (await pathExists(getSettingsPath(pmRoot))) {
|
|
4549
|
+
const settings = await readSettings(pmRoot);
|
|
4550
|
+
const typeRegistry = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations());
|
|
4551
|
+
if (typeRegistry.types.length > 0) {
|
|
4552
|
+
allowedTypes = typeRegistry.types.join("|");
|
|
4553
|
+
}
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
catch {
|
|
4557
|
+
// Fall back to built-in type guidance when settings cannot be read.
|
|
4558
|
+
}
|
|
4559
|
+
const unknownCommandGuidance = buildUnknownCommandGuidanceFromRuntime(message, program);
|
|
4560
|
+
return {
|
|
4561
|
+
message,
|
|
4562
|
+
commandName,
|
|
4563
|
+
allowedTypes,
|
|
4564
|
+
...(unknownCommandGuidance ?? {}),
|
|
4565
|
+
};
|
|
4566
|
+
}
|
|
4567
|
+
async function formatCommanderUsageMessage(error) {
|
|
4568
|
+
const usageContext = await resolveCommanderUsageContext(error);
|
|
4569
|
+
const { message, commandName, allowedTypes, unknownCommandExamples, unknownCommandNextSteps } = usageContext;
|
|
4570
|
+
const formatted = formatCommanderErrorForDisplay(message, commandName, allowedTypes, {
|
|
4571
|
+
unknownCommandExamples,
|
|
4572
|
+
unknownCommandNextSteps,
|
|
4573
|
+
});
|
|
4574
|
+
const serviceOverride = await runActiveServiceOverride("help_format", {
|
|
4575
|
+
message: formatted,
|
|
4576
|
+
command: commandName,
|
|
4577
|
+
allowed_types: allowedTypes,
|
|
4578
|
+
});
|
|
4579
|
+
if (serviceOverride.handled && typeof serviceOverride.result === "string") {
|
|
4580
|
+
return serviceOverride.result;
|
|
4581
|
+
}
|
|
4582
|
+
return formatted;
|
|
4583
|
+
}
|
|
4584
|
+
async function formatCommanderUsageJson(error) {
|
|
4585
|
+
const usageContext = await resolveCommanderUsageContext(error);
|
|
4586
|
+
const envelope = formatCommanderErrorForJson(usageContext.message, usageContext.commandName, usageContext.allowedTypes, EXIT_CODE.USAGE, {
|
|
4587
|
+
unknownCommandExamples: usageContext.unknownCommandExamples,
|
|
4588
|
+
unknownCommandNextSteps: usageContext.unknownCommandNextSteps,
|
|
4589
|
+
});
|
|
4590
|
+
return JSON.stringify(envelope, null, 2);
|
|
4591
|
+
}
|
|
1461
4592
|
async function main() {
|
|
4593
|
+
const invocationArgv = normalizeLegacyExtensionActionSyntax(process.argv.slice(2));
|
|
4594
|
+
const invocationProcessArgv = [process.argv[0], process.argv[1], ...invocationArgv];
|
|
1462
4595
|
try {
|
|
4596
|
+
applyBootstrapPagerPolicy(invocationArgv);
|
|
1463
4597
|
await registerDynamicExtensionCommandPaths(program);
|
|
1464
|
-
await program
|
|
4598
|
+
await registerRuntimeSchemaFieldFlags(program);
|
|
4599
|
+
wrapProgramActionsForExtensionHandlers(program);
|
|
4600
|
+
const renderedBootstrapJsonHelp = await maybeRenderBootstrapJsonHelp(program, invocationArgv);
|
|
4601
|
+
if (renderedBootstrapJsonHelp) {
|
|
4602
|
+
return;
|
|
4603
|
+
}
|
|
4604
|
+
await program.parseAsync(invocationProcessArgv);
|
|
1465
4605
|
}
|
|
1466
4606
|
catch (error) {
|
|
1467
4607
|
await runAndClearAfterCommandHooks({
|
|
1468
4608
|
ok: false,
|
|
1469
4609
|
error: describeUnknownError(error),
|
|
1470
4610
|
});
|
|
4611
|
+
const bootstrapGlobal = parseBootstrapGlobalOptions(invocationArgv);
|
|
4612
|
+
const jsonErrors = bootstrapGlobal.json;
|
|
4613
|
+
if (!bootstrapGlobal.noExtensions) {
|
|
4614
|
+
const bootstrapPmRoot = resolvePmRoot(process.cwd(), bootstrapGlobal.path);
|
|
4615
|
+
const bootstrapSnapshot = await loadRuntimeExtensionSnapshot(bootstrapPmRoot);
|
|
4616
|
+
setActiveExtensionServices(bootstrapSnapshot?.services ?? { overrides: [] });
|
|
4617
|
+
}
|
|
1471
4618
|
if (error instanceof PmCliError) {
|
|
1472
|
-
|
|
1473
|
-
|
|
4619
|
+
if (jsonErrors) {
|
|
4620
|
+
printError(JSON.stringify(formatPmCliErrorForJson(error.message, error.exitCode, error.context), null, 2));
|
|
4621
|
+
}
|
|
4622
|
+
else {
|
|
4623
|
+
printError(formatPmCliErrorForDisplay(error.message, error.context));
|
|
4624
|
+
}
|
|
4625
|
+
process.exitCode = error.exitCode;
|
|
4626
|
+
return;
|
|
1474
4627
|
}
|
|
1475
4628
|
if (typeof error === "object" && error !== null && "code" in error) {
|
|
1476
4629
|
const code = error.code;
|
|
1477
|
-
|
|
1478
|
-
|
|
4630
|
+
const rawMessage = typeof error.message === "string" ? (error.message ?? "") : "";
|
|
4631
|
+
const isHelpDisplayCode = code === "commander.helpDisplayed" || code === "commander.help" || code === "commander.helpCommand";
|
|
4632
|
+
if (isHelpDisplayCode || rawMessage.includes("(outputHelp)")) {
|
|
4633
|
+
const helpRequest = parseBootstrapHelpRequest(invocationArgv);
|
|
4634
|
+
if (helpRequest.requested && !isKnownHelpCommandPath(program, helpRequest.commandPathTokens)) {
|
|
4635
|
+
const unknownToken = helpRequest.commandPathTokens[0] ?? parseBootstrapCommandName(invocationArgv) ?? "<command>";
|
|
4636
|
+
const unknownMessage = `unknown command '${unknownToken}'`;
|
|
4637
|
+
if (jsonErrors) {
|
|
4638
|
+
printError(await formatCommanderUsageJson({ message: unknownMessage }));
|
|
4639
|
+
}
|
|
4640
|
+
else {
|
|
4641
|
+
printError(await formatCommanderUsageMessage({ message: unknownMessage }));
|
|
4642
|
+
}
|
|
4643
|
+
process.exitCode = EXIT_CODE.USAGE;
|
|
4644
|
+
return;
|
|
4645
|
+
}
|
|
4646
|
+
process.exitCode = EXIT_CODE.SUCCESS;
|
|
4647
|
+
return;
|
|
1479
4648
|
}
|
|
1480
4649
|
if (code === "commander.version") {
|
|
1481
|
-
process.
|
|
4650
|
+
process.exitCode = EXIT_CODE.SUCCESS;
|
|
4651
|
+
return;
|
|
1482
4652
|
}
|
|
1483
4653
|
if (code?.startsWith("commander.")) {
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
4654
|
+
if (jsonErrors) {
|
|
4655
|
+
printError(await formatCommanderUsageJson(error));
|
|
4656
|
+
}
|
|
4657
|
+
else {
|
|
4658
|
+
printError(await formatCommanderUsageMessage(error));
|
|
4659
|
+
}
|
|
4660
|
+
process.exitCode = EXIT_CODE.USAGE;
|
|
4661
|
+
return;
|
|
1487
4662
|
}
|
|
1488
4663
|
}
|
|
1489
4664
|
const message = describeUnknownError(error);
|
|
1490
|
-
|
|
1491
|
-
|
|
4665
|
+
if (jsonErrors) {
|
|
4666
|
+
printError(JSON.stringify(formatUnknownErrorForJson(message, EXIT_CODE.GENERIC_FAILURE), null, 2));
|
|
4667
|
+
}
|
|
4668
|
+
else {
|
|
4669
|
+
printError(message);
|
|
4670
|
+
}
|
|
4671
|
+
process.exitCode = EXIT_CODE.GENERIC_FAILURE;
|
|
1492
4672
|
}
|
|
1493
4673
|
}
|
|
1494
4674
|
void main();
|