@unbrained/pm-cli 2026.9.8 → 2026.9.9

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.
Files changed (144) hide show
  1. package/.agents/skills/pm-user/SKILL.md +2 -2
  2. package/.agents/skills/pm-user/references/ECOSYSTEM_PLANNING.md +1 -1
  3. package/.claude-plugin/marketplace.json +2 -2
  4. package/CHANGELOG.md +21 -2
  5. package/dist/cli/register-list-query.js +4 -4
  6. package/dist/cli/register-mutation.d.ts +0 -5
  7. package/dist/cli/register-mutation.js +2 -2
  8. package/dist/cli/register-operations.js +1 -1
  9. package/dist/cli-bundle/bundle-manifest.json +150 -150
  10. package/dist/cli-bundle/chunks/{chunk-G5ADGQ2D.js → chunk-3IPRM75M.js} +2 -2
  11. package/dist/cli-bundle/chunks/{chunk-LEZOUXG6.js → chunk-4VL5P22L.js} +2 -2
  12. package/dist/cli-bundle/chunks/chunk-AAFBV3U3.js +29 -0
  13. package/dist/cli-bundle/chunks/{chunk-XOD2PCB6.js → chunk-D3YVILJJ.js} +2 -2
  14. package/dist/cli-bundle/chunks/chunk-ECBLPN7B.js +3 -0
  15. package/dist/cli-bundle/chunks/chunk-J6ERHBV2.js +235 -0
  16. package/dist/cli-bundle/chunks/{chunk-RHSFOMHU.js → chunk-JUNJ5EKI.js} +2 -2
  17. package/dist/cli-bundle/chunks/{chunk-JAAEYHUA.js → chunk-L5RCDKQ2.js} +2 -2
  18. package/dist/cli-bundle/chunks/{chunk-FLDWYEND.js → chunk-OHNLSGGV.js} +62 -60
  19. package/dist/cli-bundle/chunks/chunk-WQKMSFBV.js +5 -0
  20. package/dist/cli-bundle/chunks/{chunk-DAM4CBBR.js → chunk-XZ6T7YPP.js} +72 -57
  21. package/dist/cli-bundle/chunks/{chunk-YVEQ2AMO.js → chunk-Z3XVXUVF.js} +2 -2
  22. package/dist/cli-bundle/chunks/{register-list-query-KSK3R63M.js → register-list-query-UIHRCS6D.js} +7 -7
  23. package/dist/cli-bundle/chunks/register-mutation-WHWMGRVX.js +20 -0
  24. package/dist/cli-bundle/chunks/register-operations-64YZYTC5.js +2 -0
  25. package/dist/cli-bundle/chunks/{register-setup-BQ5SVD4B.js → register-setup-PERI45EM.js} +2 -2
  26. package/dist/cli-bundle/focused-chunks/{chunk-KOOMJ54F.js → chunk-2W4XIYOB.js} +2 -2
  27. package/dist/cli-bundle/focused-chunks/chunk-2WSBXIGF.js +12 -0
  28. package/dist/cli-bundle/focused-chunks/{chunk-3WRPWLA7.js → chunk-5FHTCOHS.js} +2 -2
  29. package/dist/cli-bundle/focused-chunks/chunk-7N6DOREF.js +26 -0
  30. package/dist/cli-bundle/focused-chunks/{chunk-RMWES4M6.js → chunk-7PC4R5QB.js} +2 -2
  31. package/dist/cli-bundle/focused-chunks/{chunk-HJVARYM3.js → chunk-BUCJUZVG.js} +2 -2
  32. package/dist/cli-bundle/focused-chunks/chunk-FE3BABLJ.js +2 -0
  33. package/dist/cli-bundle/focused-chunks/chunk-IWITF53W.js +2 -0
  34. package/dist/cli-bundle/focused-chunks/{chunk-RXT4P5QH.js → chunk-OQUGRHKV.js} +2 -2
  35. package/dist/cli-bundle/focused-chunks/{chunk-OPFHFGDD.js → chunk-PNKIANFZ.js} +2 -2
  36. package/dist/cli-bundle/focused-chunks/{chunk-EE5QSM4Z.js → chunk-QQQ4SAUO.js} +2 -2
  37. package/dist/cli-bundle/focused-chunks/chunk-TTYXF5YD.js +177 -0
  38. package/dist/cli-bundle/focused-chunks/{chunk-7MBUII7U.js → chunk-XNR3DM6J.js} +2 -2
  39. package/dist/cli-bundle/focused-chunks/chunk-XSS3APUF.js +9 -0
  40. package/dist/cli-bundle/focused-chunks/{chunk-COPBO5HW.js → chunk-YCDQ2N23.js} +2 -2
  41. package/dist/cli-bundle/focused-chunks/{chunk-DUN4IXYN.js → chunk-YJUXGS57.js} +2 -2
  42. package/dist/cli-bundle/focused-chunks/chunk-ZTCE4UO6.js +58 -0
  43. package/dist/cli-bundle/main.js +2 -2
  44. package/dist/cli-bundle/sdk-authoring.js +1 -1
  45. package/dist/cli-bundle/sdk-contracts.js +2 -2
  46. package/dist/cli-bundle/sdk-core.js +37 -37
  47. package/dist/cli-bundle/sdk-governance.js +1 -1
  48. package/dist/cli-bundle/sdk-graph.js +1 -1
  49. package/dist/cli-bundle/sdk-merge.js +42 -42
  50. package/dist/cli-bundle/sdk-query.js +1 -1
  51. package/dist/cli-bundle/sdk-runtime.js +1 -1
  52. package/dist/cli-bundle/sdk-testing.js +1 -1
  53. package/dist/cli-bundle/sdk.js +9 -9
  54. package/dist/core/config/nested-settings.js +1 -1
  55. package/dist/core/policy/workflow-policy-schema.d.ts +312 -0
  56. package/dist/core/policy/workflow-policy-schema.js +2 -0
  57. package/dist/core/policy/workflow-policy-store.d.ts +24 -0
  58. package/dist/core/policy/workflow-policy-store.js +2 -0
  59. package/dist/core/policy/workflow-policy.d.ts +139 -0
  60. package/dist/core/policy/workflow-policy.js +2 -0
  61. package/dist/core/shared/constants.js +1 -1
  62. package/dist/core/store/item-store.js +1 -1
  63. package/dist/core/store/settings-validator.d.ts +1 -0
  64. package/dist/core/store/settings-validator.js +1 -1
  65. package/dist/core/store/settings.js +1 -1
  66. package/dist/mcp/tool-definitions.js +1 -1
  67. package/dist/sdk/cli-contracts/enum-contracts.d.ts +1 -1
  68. package/dist/sdk/cli-contracts/enum-contracts.js +1 -1
  69. package/dist/sdk/cli-contracts/flag-contracts.js +1 -1
  70. package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +1 -1
  71. package/dist/sdk/cli-contracts/tool-parameter-tables.js +1 -1
  72. package/dist/sdk/cli-contracts/tool-schema.d.ts +1 -1
  73. package/dist/sdk/cli-contracts/tool-schema.js +1 -1
  74. package/dist/sdk/completion.js +19 -4
  75. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -1
  76. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1 -1
  77. package/dist/sdk/governance/validate.d.ts +3 -1
  78. package/dist/sdk/governance/validate.js +1 -1
  79. package/dist/sdk/governance/workflow-completeness.d.ts +32 -0
  80. package/dist/sdk/governance/workflow-completeness.js +2 -0
  81. package/dist/sdk/governance/workflow-policy.d.ts +47 -0
  82. package/dist/sdk/governance/workflow-policy.js +4 -0
  83. package/dist/sdk/governance.d.ts +1 -0
  84. package/dist/sdk/governance.js +1 -1
  85. package/dist/sdk/index.d.ts +0 -1
  86. package/dist/sdk/index.js +1 -1
  87. package/dist/sdk/lifecycle/create.js +1 -1
  88. package/dist/sdk/lifecycle/recurrence-parsers.d.ts +1 -1
  89. package/dist/sdk/lifecycle/recurrence-parsers.js +1 -1
  90. package/dist/sdk/lifecycle/restore.js +1 -1
  91. package/dist/sdk/lifecycle/update.js +1 -1
  92. package/dist/sdk/package-import-adapters.js +1 -1
  93. package/dist/sdk/public-surface.json +1 -1
  94. package/dist/sdk/query/search-contracts.d.ts +27 -0
  95. package/dist/sdk/query/search-contracts.js +1 -1
  96. package/dist/sdk/query/search.d.ts +6 -4
  97. package/dist/sdk/query/search.js +2 -2
  98. package/dist/sdk/read-output-contracts.d.ts +1 -1
  99. package/dist/sdk/read-output-contracts.js +1 -1
  100. package/dist/sdk/runtime-public-contracts.d.ts +2 -1
  101. package/dist/sdk/runtime.d.ts +4 -0
  102. package/dist/sdk/runtime.js +1 -1
  103. package/dist/sdk/schema.d.ts +2 -1
  104. package/dist/sdk/schema.js +1 -1
  105. package/dist/types.d.ts +4 -0
  106. package/dist/types.js +1 -1
  107. package/docs/COMMANDS.md +10 -0
  108. package/docs/CONFIGURATION.md +12 -1
  109. package/docs/README.md +1 -0
  110. package/docs/READ_OUTPUT_CONTRACTS.md +5 -0
  111. package/docs/TESTING.md +10 -0
  112. package/docs/WORKFLOW_POLICIES.md +129 -0
  113. package/docs/generated/FLAG_LEXICON_BUDGETS.md +2 -2
  114. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +9 -3
  115. package/marketplace.json +2 -2
  116. package/package.json +5 -5
  117. package/packages/pm-beads/package.json +1 -1
  118. package/packages/pm-calendar/package.json +1 -1
  119. package/packages/pm-command-kit/package.json +1 -1
  120. package/packages/pm-digital-twin/package.json +1 -1
  121. package/packages/pm-governance-audit/package.json +1 -1
  122. package/packages/pm-guide-shell/package.json +1 -1
  123. package/packages/pm-kanban/package.json +1 -1
  124. package/packages/pm-lifecycle-hooks/package.json +1 -1
  125. package/packages/pm-linked-test-adapters/package.json +1 -1
  126. package/packages/pm-search-advanced/package.json +1 -1
  127. package/packages/pm-templates/package.json +1 -1
  128. package/packages/pm-todos/package.json +1 -1
  129. package/packages/pm-vcs/package.json +1 -1
  130. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  131. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  132. package/dist/cli-bundle/chunks/chunk-3BZZA6A3.js +0 -5
  133. package/dist/cli-bundle/chunks/chunk-AMRDVX36.js +0 -235
  134. package/dist/cli-bundle/chunks/chunk-KR3RP4FH.js +0 -3
  135. package/dist/cli-bundle/chunks/chunk-QOEKOECV.js +0 -29
  136. package/dist/cli-bundle/chunks/register-mutation-IQ5C7U6O.js +0 -20
  137. package/dist/cli-bundle/chunks/register-operations-DWVHR2NC.js +0 -2
  138. package/dist/cli-bundle/focused-chunks/chunk-IAUOUGVR.js +0 -177
  139. package/dist/cli-bundle/focused-chunks/chunk-MZDXX7JY.js +0 -56
  140. package/dist/cli-bundle/focused-chunks/chunk-QHNETP77.js +0 -9
  141. package/dist/cli-bundle/focused-chunks/chunk-SDOL6FM2.js +0 -12
  142. package/dist/cli-bundle/focused-chunks/chunk-SQV4AAUC.js +0 -2
  143. package/dist/cli-bundle/focused-chunks/chunk-WE3IUNIW.js +0 -26
  144. package/dist/cli-bundle/focused-chunks/chunk-YALOUBWJ.js +0 -2
@@ -1,2 +1,2 @@
1
- !(function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0a1b3ce0-e799-570c-9cec-4e67de048831")}catch(e2){}})();import{assertInitializedTracker}from"./environment/tracker-preflight.js";import path from"node:path";import{mkdir}from"node:fs/promises";import{resolveAuthor}from"../core/shared/author.js";import{pathExists,readFileIfExists}from"../core/fs/fs-utils.js";import{writeWorkspaceJsonWithHistory}from"../core/history/workspace-history.js";import{acquireLock}from"../core/lock/lock.js";import{refreshMergeAttributeFenceIfInstalled}from"./merge/install.js";import{assertAliasesAvailable,assertTypeFolderAvailable,buildInvalidTypeHint,escapeForDoubleQuotes,normalizeAddTypeInput,parseItemTypesFile,removeItemType,serializeItemTypesFile,upsertItemType}from"../core/schema/item-types-file.js";import{assertStatusTokensAvailable,BUILTIN_STATUS_IDS,normalizeAddStatusInput,normalizeStatusToken,parseStatusDefsFile,removeStatusDef,serializeStatusDefsFile,upsertStatusDef}from"../core/schema/status-defs-file.js";import{normalizeAddFieldInput,normalizeFieldKey,parseFieldsFile,removeField,serializeFieldsFile,upsertField}from"../core/schema/fields-file.js";import{normalizeTypePresetName,resolveTypePresetDefinitions,TYPE_PRESET_NAMES}from"../core/schema/type-presets.js";import{inferTypesFromTitles}from"../core/schema/type-inference.js";import{DEFAULT_RUNTIME_SCHEMA_FILE_PATHS,filePathForSchemaSection,normalizeRuntimeSchemaSettings,resolveRuntimeFieldRegistry,resolveRuntimeStatusRegistry}from"../core/schema/runtime-schema.js";import{normalizeItemTypeFolder,resolveItemTypeRegistry,toDefaultFolder}from"../core/item/type-registry.js";import{listAllItemMetadataLight}from"../core/store/item-store.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{nowIso}from"../core/shared/time.js";import{getActiveExtensionRegistrations,runActiveOnWriteHooks}from"../core/extensions/index.js";import{resolvePmRoot}from"../core/store/paths.js";import{readSettings,resolveGovernanceKnobs}from"../core/store/settings.js";import{resolvePmToolCustomFieldCollision}from"./cli-contracts/tool-schema.js";const SCHEMA_SUBCOMMANDS=["add-type","remove-type","add-status","remove-status","add-field","remove-field","list-fields","show-field","apply-preset","list","show","show-status","rename-type","rename-field","remap-status"];import{formatSchemaEvolutionMigrationHuman,planSchemaEvolutionMigration,runSchemaEvolutionMigration}from"./schema-migration.js";const SCHEMA_TYPES_LOCK_ID="schema-types";const SCHEMA_STATUSES_LOCK_ID="schema-statuses";const SCHEMA_FIELDS_LOCK_ID="schema-fields";function writeAuditedSchemaFile(params){return writeWorkspaceJsonWithHistory({pmRoot:params.pmRoot,filePath:params.filePath,raw:params.raw,op:params.op,author:params.author,lockTtlSeconds:params.settings.locks.ttl_seconds,lockWaitMs:params.settings.locks.wait_ms})}async function runSchemaAddType(name,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);let normalized;try{normalized=normalizeAddTypeInput({name,description:options.description,defaultStatus:options.defaultStatus,folder:options.folder,aliases:options.alias})}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const typesPath=filePathForSchemaSection(pmRoot,schema.files.types,DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.types);const warnings=[];const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const releaseLock=await acquireLock(pmRoot,SCHEMA_TYPES_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);let upsert;try{const previousRaw=await readFileIfExists(typesPath);let parsed;try{parsed=parseItemTypesFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}try{assertAliasesAvailable(normalized,parsed);const reservedFolders=new Map;const resolvedRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());for(const[typeName,folder]of Object.entries(resolvedRegistry.type_to_folder)){const folderKey=folder.trim().toLowerCase();if(folderKey.length>0&&!reservedFolders.has(folderKey)){reservedFolders.set(folderKey,typeName)}}assertTypeFolderAvailable(normalized,parsed,reservedFolders)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}upsert=upsertItemType(parsed,normalized);if(upsert.replaced&&upsert.previousName!==void 0&&upsert.previousName!==upsert.definition.name){warnings.push(`type_recased:${upsert.previousName}->${upsert.definition.name}`)}await writeAuditedSchemaFile({pmRoot,filePath:typesPath,raw:serializeItemTypesFile(upsert.file),op:"schema:add-type",author,settings});await ensureTypeFolderScaffold(pmRoot,[upsert.definition],warnings,"schema:add-type-folder");warnings.push(...await runActiveOnWriteHooks({path:typesPath,scope:"project",op:"schema:add-type"}))}finally{await releaseLock()}await appendMergeFenceWarnings(pmRoot,warnings);return{action:"add-type",registered:true,replaced:upsert.replaced,type:upsert.definition,file:{path:typesPath,definitions:upsert.file.definitions.length},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}async function ensureInitialized(pmRoot){await assertInitializedTracker(pmRoot)}async function appendMergeFenceWarnings(pmRoot,warnings){try{const fence=await refreshMergeAttributeFenceIfInstalled(pmRoot);if(fence.status==="refreshed"){warnings.push("merge_fence_refreshed")}else if(fence.status==="not_installed"){warnings.push('merge_fence_not_installed:type folders lack git merge drivers; run "pm merge install"')}}catch{warnings.push('merge_fence_refresh_failed:run "pm merge install" to reconcile')}}function finalizeWarnings(warnings){return[...new Set(warnings)].sort((left,right)=>left.localeCompare(right))}async function countItemsUsingType(pmRoot,settings,schema,typeName){const registry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());const lowerName=typeName.trim().toLowerCase();const items=await listAllItemMetadataLight(pmRoot,settings.item_format,registry.type_to_folder,[],schema);return items.filter(item=>typeof item.type==="string"&&item.type.toLowerCase()===lowerName).length}async function countItemsUsingStatus(pmRoot,settings,schema,statusId){const registry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());const statusRegistry=resolveRuntimeStatusRegistry(schema);const normalizedId=normalizeStatusToken(statusId);const items=await listAllItemMetadataLight(pmRoot,settings.item_format,registry.type_to_folder,[],schema);return items.filter(item=>{const itemStatus=typeof item.status==="string"?item.status:"";const resolved=statusRegistry.alias_to_id.get(normalizeStatusToken(itemStatus))??normalizeStatusToken(itemStatus);return resolved===normalizedId}).length}async function appendRemovedStatusWarnings(warnings,pmRoot,settings,schema,removedId){if(removedId.length===0||BUILTIN_STATUS_IDS.has(removedId)){return}const usingStatus=await countItemsUsingStatus(pmRoot,settings,schema,removedId);if(usingStatus>0){warnings.push(`items_using_status:${usingStatus}`)}const referencingSlots=workflowSlotsReferencing(schema.workflow,removedId);if(referencingSlots.length>0){warnings.push(`status_referenced_by_workflow:${referencingSlots.join(",")}`)}}function workflowSlotsReferencing(workflow,statusId){const slots=[["draft_status",workflow.draft_status],["open_status",workflow.open_status],["in_progress_status",workflow.in_progress_status],["blocked_status",workflow.blocked_status],["close_status",workflow.close_status],["canceled_status",workflow.canceled_status]];return slots.filter(([,value])=>value!==void 0&&normalizeStatusToken(value)===statusId).map(([slot])=>slot)}async function runSchemaRemoveType(name,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const typesPath=filePathForSchemaSection(pmRoot,schema.files.types,DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.types);const warnings=[];const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const releaseLock=await acquireLock(pmRoot,SCHEMA_TYPES_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);let removal;try{const previousRaw=await readFileIfExists(typesPath);let parsed;try{parsed=parseItemTypesFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}try{removal=removeItemType(parsed,name)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}if(removal.removed){const removedName=(removal.definition?.name??name??"").trim();if(removedName.length>0){const usingType=await countItemsUsingType(pmRoot,settings,schema,removedName);if(usingType>0){warnings.push(`items_using_type:${usingType}`)}}await writeAuditedSchemaFile({pmRoot,filePath:typesPath,raw:serializeItemTypesFile(removal.file),op:"schema:remove-type",author,settings});warnings.push(...await runActiveOnWriteHooks({path:typesPath,scope:"project",op:"schema:remove-type"}))}}finally{await releaseLock()}if(removal.removed){await appendMergeFenceWarnings(pmRoot,warnings)}return{action:"remove-type",removed:removal.removed,...removal.definition?{type:removal.definition}:{},file:{path:typesPath,definitions:removal.file.definitions.length},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}function statusesPathFor(pmRoot,schema){return filePathForSchemaSection(pmRoot,schema.files.statuses,DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.statuses)}function fieldsPathFor(pmRoot,schema){return filePathForSchemaSection(pmRoot,schema.files.fields,DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.fields)}function typesPathFor(pmRoot,schema){return filePathForSchemaSection(pmRoot,schema.files.types,DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.types)}async function ensureTypeFolderScaffold(pmRoot,definitions,warnings,op){for(const definition of definitions){const folder=normalizeItemTypeFolder(definition.folder??toDefaultFolder(definition.name));const target=path.join(pmRoot,folder);if(await pathExists(target)){continue}await mkdir(target,{recursive:true});warnings.push(...await runActiveOnWriteHooks({path:target,scope:"project",op}))}}function toSchemaFieldSummary(field){return{key:field.key,type:field.type,commands:[...field.commands],cli_flag:`--${field.cli_flag}`,cli_aliases:field.cli_aliases.map(alias=>`--${alias}`),...field.description?{description:field.description}:{},required:field.required,required_on_create:field.required_on_create,allow_unset:field.allow_unset,required_types:[...field.required_types]}}function toExistingStatusDefinition(resolvedExisting){if(!resolvedExisting){return void 0}return{id:resolvedExisting.id,...resolvedExisting.roles.length>0?{roles:[...resolvedExisting.roles]}:{},...resolvedExisting.aliases.length>0?{aliases:[...resolvedExisting.aliases]}:{},...resolvedExisting.description?{description:resolvedExisting.description}:{},...typeof resolvedExisting.order==="number"?{order:resolvedExisting.order}:{}}}function buildStatusFileAliasMap(statuses){const fileAliasToId=new Map;for(const definition of statuses){const defId=normalizeStatusToken(definition.id);if(defId.length===0){continue}fileAliasToId.set(defId,defId);for(const alias of Array.isArray(definition.aliases)?definition.aliases:[]){const aliasToken=normalizeStatusToken(alias);if(aliasToken.length>0&&!fileAliasToId.has(aliasToken)){fileAliasToId.set(aliasToken,defId)}}}return fileAliasToId}async function runSchemaAddStatus(id,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);let normalized;try{normalized=normalizeAddStatusInput({id,roles:options.role,aliases:options.alias,description:options.description,order:options.order})}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const statusesPath=statusesPathFor(pmRoot,schema);const statusRegistry=resolveRuntimeStatusRegistry(schema);try{assertStatusTokensAvailable(normalized,statusRegistry.alias_to_id)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}const baseDefinition=toExistingStatusDefinition(statusRegistry.by_id.get(normalized.id));const effectiveRoles=normalized.roles??baseDefinition?.roles??[];if(effectiveRoles.length===0){throw new PmCliError(`Status "${normalized.id}" requires at least one lifecycle role. Re-run with --role active, --role blocked, --role draft, or an appropriate terminal/default role.`,EXIT_CODE.USAGE,{code:"status_lifecycle_role_required",examples:[`pm schema add-status "${escapeForDoubleQuotes(normalized.id)}" --role active`,`pm schema add-status "${escapeForDoubleQuotes(normalized.id)}" --role blocked`]})}const warnings=[];const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const releaseLock=await acquireLock(pmRoot,SCHEMA_STATUSES_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);let upsert;try{const previousRaw=await readFileIfExists(statusesPath);let parsed;try{parsed=parseStatusDefsFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}const fileAliasToId=buildStatusFileAliasMap(parsed.statuses);try{assertStatusTokensAvailable(normalized,fileAliasToId)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}upsert=upsertStatusDef(parsed,normalized,baseDefinition);await writeAuditedSchemaFile({pmRoot,filePath:statusesPath,raw:serializeStatusDefsFile(upsert.file),op:"schema:add-status",author,settings});warnings.push(...await runActiveOnWriteHooks({path:statusesPath,scope:"project",op:"schema:add-status"}))}finally{await releaseLock()}return{action:"add-status",registered:true,replaced:upsert.replaced,status:upsert.definition,file:{path:statusesPath,statuses:upsert.file.statuses.length},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}async function runSchemaRemoveStatus(id,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const statusesPath=statusesPathFor(pmRoot,schema);const warnings=[];const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const releaseLock=await acquireLock(pmRoot,SCHEMA_STATUSES_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);let removal;try{const previousRaw=await readFileIfExists(statusesPath);let parsed;try{parsed=parseStatusDefsFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}try{removal=removeStatusDef(parsed,id)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}if(removal.removed){const removedId=normalizeStatusToken(removal.definition?.id??id);await appendRemovedStatusWarnings(warnings,pmRoot,settings,schema,removedId);await writeAuditedSchemaFile({pmRoot,filePath:statusesPath,raw:serializeStatusDefsFile(removal.file),op:"schema:remove-status",author,settings});warnings.push(...await runActiveOnWriteHooks({path:statusesPath,scope:"project",op:"schema:remove-status"}))}}finally{await releaseLock()}return{action:"remove-status",removed:removal.removed,...removal.definition?{status:removal.definition}:{},file:{path:statusesPath,statuses:removal.file.statuses.length},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}function toSchemaTypeSummary(definition){return{name:definition.name,folder:definition.folder,aliases:Array.isArray(definition.aliases)?[...definition.aliases]:[],...definition.default_status?{default_status:definition.default_status}:{},...definition.description?{description:definition.description}:{}}}function toSchemaTypeDefinition(definition,source,extensionProvenance){return{...toSchemaTypeSummary(definition),source,...extensionProvenance?{extension:extensionProvenance}:{},required_create_fields:[...definition.required_create_fields],required_create_repeatables:[...definition.required_create_repeatables],options:structuredClone(definition.options),command_option_policies:structuredClone(definition.command_option_policies)}}function toSchemaStatusSummary(definition){return{id:definition.id,source:BUILTIN_STATUS_IDS.has(definition.id)?"builtin":"custom",roles:Array.isArray(definition.roles)?[...definition.roles]:[],aliases:Array.isArray(definition.aliases)?[...definition.aliases]:[],...definition.description?{description:definition.description}:{},...typeof definition.order==="number"?{order:definition.order}:{}}}function classifyTypeSource(name,customNames,extensionNames){const lowerName=name.toLowerCase();if(extensionNames.has(lowerName)){return"extension"}if(customNames.has(lowerName)){return"custom"}return"builtin"}function collectExtensionTypeProvenance(){const provenance=new Map;const registrations=getActiveExtensionRegistrations();for(const registration of registrations?.item_types??[]){const rawTypes=registration.types;if(!Array.isArray(rawTypes)){continue}for(const rawType of rawTypes){if(typeof rawType!=="object"||rawType===null||Array.isArray(rawType)){continue}const name=rawType.name;if(typeof name==="string"&&name.trim().length>0){provenance.set(name.trim().toLowerCase(),{layer:registration.layer,name:registration.name})}}}return provenance}function buildSchemaStatusSummaries(schema){const registry=resolveRuntimeStatusRegistry(schema);const builtin=[];const custom=[];for(const definition of registry.definitions){const summary=toSchemaStatusSummary(definition);if(summary.source==="builtin"){builtin.push(summary)}else{custom.push(summary)}}return{builtin,custom}}async function loadSchemaInspectionContext(global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const typesPath=filePathForSchemaSection(pmRoot,schema.files.types,DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.types);const statusesPath=statusesPathFor(pmRoot,schema);const registry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());const extensionProvenance=collectExtensionTypeProvenance();const customNames=new Set((settings.item_types?.definitions??[]).map(definition=>definition.name.trim().toLowerCase()).filter(name=>name.length>0&&!extensionProvenance.has(name)));return{pmRoot,typesPath,statusesPath,byType:registry.by_type,customNames,extensionProvenance,schema}}async function runSchemaList(global2){const context=await loadSchemaInspectionContext(global2);const builtin=[];const custom=[];const extension=[];for(const definition of Object.values(context.byType).sort((left,right)=>left.name.localeCompare(right.name))){const source=classifyTypeSource(definition.name,context.customNames,new Set(context.extensionProvenance.keys()));const summary=toSchemaTypeSummary(definition);if(source==="extension"){extension.push(summary)}else if(source==="custom"){custom.push(summary)}else{builtin.push(summary)}}const statusSummaries=buildSchemaStatusSummaries(context.schema);const fieldSummaries=resolveRuntimeFieldRegistry(context.schema).definitions.map(toSchemaFieldSummary);return{action:"list",builtin,custom,extension,counts:{builtin:builtin.length,custom:custom.length,extension:extension.length,total:builtin.length+custom.length+extension.length},statuses:{builtin:statusSummaries.builtin,custom:statusSummaries.custom,counts:{builtin:statusSummaries.builtin.length,custom:statusSummaries.custom.length,total:statusSummaries.builtin.length+statusSummaries.custom.length}},fields:{custom:fieldSummaries,counts:{total:fieldSummaries.length}},file:{path:context.typesPath},generated_at:nowIso()}}async function runSchemaShow(name,global2){const typeName=(name??"").trim();if(typeName.length===0){throw new PmCliError("Type name must not be empty.",EXIT_CODE.USAGE)}const context=await loadSchemaInspectionContext(global2);const match=Object.values(context.byType).find(definition=>definition.name.toLowerCase()===typeName.toLowerCase()||definition.aliases.some(alias=>alias.toLowerCase()===typeName.toLowerCase()));if(!match){throw new PmCliError(`Unknown item type "${typeName}". Run pm schema list to inspect registered types, or pm schema add-type "${escapeForDoubleQuotes(typeName)}" to register it.`,EXIT_CODE.NOT_FOUND,{code:"unknown_item_type"})}return{action:"show",type:toSchemaTypeDefinition(match,classifyTypeSource(match.name,context.customNames,new Set(context.extensionProvenance.keys())),context.extensionProvenance.get(match.name.toLowerCase())),file:{path:context.typesPath},generated_at:nowIso()}}async function runSchemaShowStatus(id,global2){const statusToken=normalizeStatusToken(id);if(statusToken.length===0){throw new PmCliError("Status id must not be empty.",EXIT_CODE.USAGE)}const context=await loadSchemaInspectionContext(global2);const statusRegistry=resolveRuntimeStatusRegistry(context.schema);const resolvedId=statusRegistry.alias_to_id.get(statusToken)??statusToken;const match=statusRegistry.by_id.get(resolvedId);if(!match){throw new PmCliError(`Unknown status "${id}". Run pm schema list to inspect registered statuses, or pm schema add-status "${escapeForDoubleQuotes(id??"")}" to register it.`,EXIT_CODE.NOT_FOUND,{code:"unknown_status"})}return{action:"show-status",status:toSchemaStatusSummary(match),file:{path:context.statusesPath},generated_at:nowIso()}}async function runSchemaAddField(key,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);let normalized;try{normalized=normalizeAddFieldInput({key,type:options.type,commands:options.commands,description:options.description,cliFlag:options.cliFlag,aliases:options.alias,required:options.required,requiredOnCreate:options.requiredOnCreate,allowUnset:options.allowUnset,requiredTypes:options.requiredTypes})}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const fieldsPath=fieldsPathFor(pmRoot,schema);const warnings=[];const mcpCollision=resolvePmToolCustomFieldCollision(normalized.key);if(mcpCollision){warnings.push(`custom_field_mcp_input_collision:${mcpCollision.field}:${mcpCollision.property}:${mcpCollision.owner}:use=${mcpCollision.nested_path}`)}const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const releaseLock=await acquireLock(pmRoot,SCHEMA_FIELDS_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);let upsert;try{const previousRaw=await readFileIfExists(fieldsPath);let parsed;try{parsed=parseFieldsFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}upsert=upsertField(parsed,normalized);await writeAuditedSchemaFile({pmRoot,filePath:fieldsPath,raw:serializeFieldsFile(upsert.file),op:"schema:add-field",author,settings});warnings.push(...await runActiveOnWriteHooks({path:fieldsPath,scope:"project",op:"schema:add-field"}))}finally{await releaseLock()}return{action:"add-field",registered:true,replaced:upsert.replaced,field:upsert.definition,file:{path:fieldsPath,fields:upsert.file.fields.length},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}async function countItemsUsingField(pmRoot,settings,schema,metadataKey){const registry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());const items=await listAllItemMetadataLight(pmRoot,settings.item_format,registry.type_to_folder,[],schema);return items.filter(item=>{const value=item[metadataKey];if(value===void 0||value===null){return false}if(Array.isArray(value)){return value.length>0}if(typeof value==="string"){return value.trim().length>0}return true}).length}async function runSchemaRemoveField(key,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const fieldsPath=fieldsPathFor(pmRoot,schema);const warnings=[];const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const releaseLock=await acquireLock(pmRoot,SCHEMA_FIELDS_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);let removal;try{const previousRaw=await readFileIfExists(fieldsPath);let parsed;try{parsed=parseFieldsFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}try{removal=removeField(parsed,key)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}if(removal.removed){const removedKey=normalizeFieldKey(removal.definition?.key??key);const metadataKey=normalizeFieldKey(removal.definition?.metadata_key??removal.definition?.front_matter_key??removedKey);if(metadataKey.length>0){const usingField=await countItemsUsingField(pmRoot,settings,schema,metadataKey);if(usingField>0){warnings.push(`items_using_field:${usingField}`)}}await writeAuditedSchemaFile({pmRoot,filePath:fieldsPath,raw:serializeFieldsFile(removal.file),op:"schema:remove-field",author,settings});warnings.push(...await runActiveOnWriteHooks({path:fieldsPath,scope:"project",op:"schema:remove-field"}))}}finally{await releaseLock()}return{action:"remove-field",removed:removal.removed,...removal.definition?{field:removal.definition}:{},file:{path:fieldsPath,fields:removal.file.fields.length},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}async function runSchemaListFields(global2){const context=await loadSchemaInspectionContext(global2);const fields=resolveRuntimeFieldRegistry(context.schema).definitions.map(toSchemaFieldSummary);return{action:"list-fields",fields,counts:{total:fields.length},file:{path:fieldsPathFor(context.pmRoot,context.schema)},generated_at:nowIso()}}async function runSchemaShowField(key,global2){const fieldKey=normalizeFieldKey(key);if(fieldKey.length===0){throw new PmCliError("Field key must not be empty.",EXIT_CODE.USAGE)}const context=await loadSchemaInspectionContext(global2);const match=resolveRuntimeFieldRegistry(context.schema).by_key.get(fieldKey);if(!match){throw new PmCliError(`Unknown custom field "${key}". Run pm schema list-fields to inspect registered fields, or pm schema add-field "${escapeForDoubleQuotes(key??"")}" to register it.`,EXIT_CODE.NOT_FOUND,{code:"unknown_field"})}return{action:"show-field",field:toSchemaFieldSummary(match),file:{path:fieldsPathFor(context.pmRoot,context.schema)},generated_at:nowIso()}}async function runSchemaApplyPreset(preset,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);let presetName;try{const normalized=normalizeTypePresetName(preset);if(normalized===void 0){throw new Error(`Type preset name is required. Allowed: ${TYPE_PRESET_NAMES.join(", ")}.`)}presetName=normalized}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const typesPath=typesPathFor(pmRoot,schema);const warnings=[];const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const registered=[];const replaced=[];let definitionsCount;const releaseLock=await acquireLock(pmRoot,SCHEMA_TYPES_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);try{const previousRaw=await readFileIfExists(typesPath);let parsed;try{parsed=parseItemTypesFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}let nextFile=parsed;for(const normalized of resolveTypePresetDefinitions(presetName)){const upsert=upsertItemType(nextFile,normalized);nextFile=upsert.file;(upsert.replaced?replaced:registered).push(upsert.definition.name)}definitionsCount=nextFile.definitions.length;await writeAuditedSchemaFile({pmRoot,filePath:typesPath,raw:serializeItemTypesFile(nextFile),op:"schema:apply-preset",author,settings});await ensureTypeFolderScaffold(pmRoot,resolveTypePresetDefinitions(presetName),warnings,"schema:apply-preset-folder");warnings.push(...await runActiveOnWriteHooks({path:typesPath,scope:"project",op:"schema:apply-preset"}))}finally{await releaseLock()}return{action:"apply-preset",preset:presetName,registered,replaced,file:{path:typesPath,definitions:definitionsCount},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}function resolveSchemaInferenceMinCount(value){if(value===void 0){return 10}if(!Number.isInteger(value)||value<1){throw new PmCliError("--min-count must be a positive integer (>= 1).",EXIT_CODE.USAGE)}return value}async function runSchemaInferTypes(options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const typesPath=typesPathFor(pmRoot,schema);const minCount=resolveSchemaInferenceMinCount(options.minCount);const registry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());const items=await listAllItemMetadataLight(pmRoot,settings.item_format,registry.type_to_folder,[],schema);const titles=items.map(item=>typeof item.title==="string"?item.title:"");const candidates=inferTypesFromTitles(titles,{minCount});const warnings=[];const registered=[];const replaced=[];const skipped=[];let definitionsCount;if(!options.apply){let parsed;try{parsed=parseItemTypesFile(await readFileIfExists(typesPath))}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}definitionsCount=parsed.definitions.length;return{action:"infer-types",applied:false,min_count:minCount,candidates,registered,replaced,skipped,file:{path:typesPath,definitions:definitionsCount},warnings,generated_at:nowIso()}}const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const releaseLock=await acquireLock(pmRoot,SCHEMA_TYPES_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);try{const previousRaw=await readFileIfExists(typesPath);let parsed;try{parsed=parseItemTypesFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}let nextFile=parsed;for(const candidate of candidates){if(candidate.shadows_builtin){skipped.push({name:candidate.name,reason:"shadows_builtin"});continue}let normalized;try{normalized=normalizeAddTypeInput({name:candidate.name})}catch{skipped.push({name:candidate.name,reason:"invalid_type_token"});continue}const upsert=upsertItemType(nextFile,normalized);nextFile=upsert.file;(upsert.replaced?replaced:registered).push(upsert.definition.name)}definitionsCount=nextFile.definitions.length;await writeAuditedSchemaFile({pmRoot,filePath:typesPath,raw:serializeItemTypesFile(nextFile),op:"schema:infer-types",author,settings});await ensureTypeFolderScaffold(pmRoot,nextFile.definitions.filter(definition=>registered.includes(definition.name)||replaced.includes(definition.name)),warnings,"schema:infer-types-folder");warnings.push(...await runActiveOnWriteHooks({path:typesPath,scope:"project",op:"schema:infer-types"}))}finally{await releaseLock()}return{action:"infer-types",applied:true,min_count:minCount,candidates,registered,replaced,skipped,file:{path:typesPath,definitions:definitionsCount},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}function formatSchemaAddTypeHuman(result){const verb=result.replaced?"Updated":"Registered";const aliasSuffix=result.type.aliases&&result.type.aliases.length>0?` (aliases: ${result.type.aliases.join(", ")})`:"";return`${verb} custom item type "${result.type.name}"${aliasSuffix} in ${result.file.path}. Run: pm create "${escapeForDoubleQuotes(result.type.name)}" "<title>"`}function formatSchemaRemoveTypeHuman(result){if(!result.removed){return`No custom item type matched; nothing removed from ${result.file.path}.`}const name=result.type?.name??"(unknown)";return`Removed custom item type "${name}" from ${result.file.path}.`}function formatSchemaAddStatusHuman(result){const verb=result.replaced?"Updated":"Registered";const roleSuffix=result.status.roles&&result.status.roles.length>0?` (roles: ${result.status.roles.join(", ")})`:"";const aliasSuffix=result.status.aliases&&result.status.aliases.length>0?` (aliases: ${result.status.aliases.join(", ")})`:"";return`${verb} status "${result.status.id}"${roleSuffix}${aliasSuffix} in ${result.file.path}.`}function formatSchemaRemoveStatusHuman(result){if(!result.removed){return`No custom status matched; nothing removed from ${result.file.path}.`}const id=result.status?.id??"(unknown)";return`Removed custom status "${id}" from ${result.file.path}.`}function formatSchemaListHuman(result){const lines=[`Schema types: ${result.counts.total} total (${result.counts.builtin} builtin, ${result.counts.custom} custom, ${result.counts.extension} extension)`];for(const[label,entries]of[["builtin",result.builtin],["custom",result.custom],["extension",result.extension]]){if(entries.length===0){continue}lines.push(`${label}: ${entries.map(entry=>entry.name).join(", ")}`)}lines.push(`statuses: ${result.statuses.counts.total} total (${result.statuses.counts.builtin} builtin, ${result.statuses.counts.custom} custom)`);for(const[label,entries]of[["builtin statuses",result.statuses.builtin],["custom statuses",result.statuses.custom]]){if(entries.length===0){continue}lines.push(`${label}: ${entries.map(entry=>entry.id).join(", ")}`)}lines.push(`custom fields: ${result.fields.counts.total} total`);if(result.fields.custom.length>0){lines.push(`fields: ${result.fields.custom.map(field=>`${field.key} (${field.type}, ${field.cli_flag})`).join(", ")}`)}lines.push(`Inspect one: pm schema show <Type>`);lines.push(`Inspect one status: pm schema show-status <status>`);lines.push(`Inspect one field: pm schema show-field <key>`);return lines.join("\n")}function formatSchemaShowHuman(result){const parts=[`type: ${result.type.name}`,`source: ${result.type.source}`,`folder: ${result.type.folder}`];if(result.type.default_status){parts.push(`default_status: ${result.type.default_status}`)}if(result.type.aliases.length>0){parts.push(`aliases: ${result.type.aliases.join(", ")}`)}if(result.type.description){parts.push(`description: ${result.type.description}`)}if(result.type.options.length>0){parts.push(`options: ${result.type.options.map(option=>option.key).join(", ")}`)}return parts.join("\n")}function formatSchemaShowStatusHuman(result){const parts=[`status: ${result.status.id}`,`source: ${result.status.source}`];if(result.status.roles.length>0){parts.push(`roles: ${result.status.roles.join(", ")}`)}if(result.status.aliases.length>0){parts.push(`aliases: ${result.status.aliases.join(", ")}`)}if(result.status.description){parts.push(`description: ${result.status.description}`)}if(typeof result.status.order==="number"){parts.push(`order: ${result.status.order}`)}return parts.join("\n")}function formatFieldSummaryLine(field){const flags=[];if(field.required){flags.push("required")}if(field.required_on_create){flags.push("required_on_create")}if(!field.allow_unset){flags.push("no_unset")}const flagSuffix=flags.length>0?` [${flags.join(", ")}]`:"";return`${field.key} (${field.type}) ${field.cli_flag} commands=${field.commands.join("/")}${flagSuffix}`}function formatSchemaAddFieldHuman(result){const verb=result.replaced?"Updated":"Registered";return`${verb} custom field "${result.field.key}" in ${result.file.path}. Use it: pm create "<title>" --${(result.field.cli_flag??result.field.key).replaceAll("_","-")} <value>`}function formatSchemaRemoveFieldHuman(result){if(!result.removed){return`No custom field matched; nothing removed from ${result.file.path}.`}const key=result.field?.key??"(unknown)";return`Removed custom field "${key}" from ${result.file.path}.`}function formatSchemaListFieldsHuman(result){const lines=[`Custom fields: ${result.counts.total} total`];for(const field of result.fields){lines.push(` ${formatFieldSummaryLine(field)}`)}lines.push(`Inspect one: pm schema show-field <key>`);return lines.join("\n")}function formatSchemaShowFieldHuman(result){const field=result.field;const parts=[`field: ${field.key}`,`type: ${field.type}`,`cli_flag: ${field.cli_flag}`,`commands: ${field.commands.join(", ")}`];if(field.cli_aliases.length>0){parts.push(`cli_aliases: ${field.cli_aliases.join(", ")}`)}if(field.description){parts.push(`description: ${field.description}`)}if(field.required){parts.push(`required: true`)}if(field.required_on_create){parts.push(`required_on_create: true`)}if(!field.allow_unset){parts.push(`allow_unset: false`)}if(field.required_types.length>0){parts.push(`required_types: ${field.required_types.join(", ")}`)}return parts.join("\n")}function formatSchemaApplyPresetHuman(result){const registered=result.registered.length>0?`registered: ${result.registered.join(", ")}`:"";const replaced=result.replaced.length>0?`updated: ${result.replaced.join(", ")}`:"";const detail=[registered,replaced].filter(part=>part.length>0).join("; ");return`Applied "${result.preset}" type preset to ${result.file.path}${detail.length>0?` (${detail})`:""}.`}function formatSchemaInferTypesHuman(result){if(result.candidates.length===0){return`No title-prefix conventions found with at least ${result.min_count} items. Lower the threshold with --min-count <n>.`}const lines=result.applied?[`Inferred and registered custom types from title prefixes (min-count ${result.min_count}):`]:[`Inferred custom type candidates from title prefixes (min-count ${result.min_count}, dry-run):`];for(const candidate of result.candidates){const note=candidate.shadows_builtin?" [shadows built-in, skipped]":"";lines.push(` ${candidate.name} <- "${candidate.prefix}" (${candidate.count} items)${note}`)}if(!result.applied){lines.push(`Re-run with --apply to register the non-shadowing candidates.`)}else{if(result.registered.length>0){lines.push(`registered: ${result.registered.join(", ")}`)}if(result.replaced.length>0){lines.push(`updated: ${result.replaced.join(", ")}`)}}return lines.join("\n")}const _testOnlySchemaCommand={workflowSlotsReferencing};export{SCHEMA_SUBCOMMANDS,_testOnlySchemaCommand,buildInvalidTypeHint,ensureTypeFolderScaffold,formatSchemaAddFieldHuman,formatSchemaAddStatusHuman,formatSchemaAddTypeHuman,formatSchemaApplyPresetHuman,formatSchemaEvolutionMigrationHuman,formatSchemaInferTypesHuman,formatSchemaListFieldsHuman,formatSchemaListHuman,formatSchemaRemoveFieldHuman,formatSchemaRemoveStatusHuman,formatSchemaRemoveTypeHuman,formatSchemaShowFieldHuman,formatSchemaShowHuman,formatSchemaShowStatusHuman,planSchemaEvolutionMigration,runSchemaAddField,runSchemaAddStatus,runSchemaAddType,runSchemaApplyPreset,runSchemaEvolutionMigration,runSchemaInferTypes,runSchemaList,runSchemaListFields,runSchemaRemoveField,runSchemaRemoveStatus,runSchemaRemoveType,runSchemaShow,runSchemaShowField,runSchemaShowStatus};
1
+ !(function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3b47d4d5-a224-5e46-8f91-9988c353c26b")}catch(e2){}})();import{assertInitializedTracker}from"./environment/tracker-preflight.js";import path from"node:path";import{mkdir}from"node:fs/promises";import{resolveAuthor}from"../core/shared/author.js";import{pathExists,readFileIfExists}from"../core/fs/fs-utils.js";import{writeWorkspaceJsonWithHistory}from"../core/history/workspace-history.js";import{acquireLock}from"../core/lock/lock.js";import{refreshMergeAttributeFenceIfInstalled}from"./merge/install.js";import{assertAliasesAvailable,assertTypeFolderAvailable,buildInvalidTypeHint,escapeForDoubleQuotes,normalizeAddTypeInput,parseItemTypesFile,removeItemType,serializeItemTypesFile,upsertItemType}from"../core/schema/item-types-file.js";import{assertStatusTokensAvailable,BUILTIN_STATUS_IDS,normalizeAddStatusInput,normalizeStatusToken,parseStatusDefsFile,removeStatusDef,serializeStatusDefsFile,upsertStatusDef}from"../core/schema/status-defs-file.js";import{normalizeAddFieldInput,normalizeFieldKey,parseFieldsFile,removeField,serializeFieldsFile,upsertField}from"../core/schema/fields-file.js";import{normalizeTypePresetName,resolveTypePresetDefinitions,TYPE_PRESET_NAMES}from"../core/schema/type-presets.js";import{inferTypesFromTitles}from"../core/schema/type-inference.js";import{DEFAULT_RUNTIME_SCHEMA_FILE_PATHS,filePathForSchemaSection,normalizeRuntimeSchemaSettings,resolveRuntimeFieldRegistry,resolveRuntimeStatusRegistry}from"../core/schema/runtime-schema.js";import{normalizeItemTypeFolder,resolveItemTypeRegistry,toDefaultFolder}from"../core/item/type-registry.js";import{listAllItemMetadataLight}from"../core/store/item-store.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{nowIso}from"../core/shared/time.js";import{getActiveExtensionRegistrations,runActiveOnWriteHooks}from"../core/extensions/index.js";import{resolvePmRoot}from"../core/store/paths.js";import{readSettings,resolveGovernanceKnobs}from"../core/store/settings.js";import{resolvePmToolCustomFieldCollision}from"./cli-contracts/tool-schema.js";import{WORKFLOW_POLICY_ACTIONS}from"./governance/workflow-policy.js";export*from"./governance/workflow-policy.js";const SCHEMA_SUBCOMMANDS=[...WORKFLOW_POLICY_ACTIONS,"add-type","remove-type","add-status","remove-status","add-field","remove-field","list-fields","show-field","apply-preset","list","show","show-status","rename-type","rename-field","remap-status"];import{formatSchemaEvolutionMigrationHuman,planSchemaEvolutionMigration,runSchemaEvolutionMigration}from"./schema-migration.js";const SCHEMA_TYPES_LOCK_ID="schema-types";const SCHEMA_STATUSES_LOCK_ID="schema-statuses";const SCHEMA_FIELDS_LOCK_ID="schema-fields";function writeAuditedSchemaFile(params){return writeWorkspaceJsonWithHistory({pmRoot:params.pmRoot,filePath:params.filePath,raw:params.raw,op:params.op,author:params.author,lockTtlSeconds:params.settings.locks.ttl_seconds,lockWaitMs:params.settings.locks.wait_ms})}async function runSchemaAddType(name,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);let normalized;try{normalized=normalizeAddTypeInput({name,description:options.description,defaultStatus:options.defaultStatus,folder:options.folder,aliases:options.alias})}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const typesPath=filePathForSchemaSection(pmRoot,schema.files.types,DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.types);const warnings=[];const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const releaseLock=await acquireLock(pmRoot,SCHEMA_TYPES_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);let upsert;try{const previousRaw=await readFileIfExists(typesPath);let parsed;try{parsed=parseItemTypesFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}try{assertAliasesAvailable(normalized,parsed);const reservedFolders=new Map;const resolvedRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());for(const[typeName,folder]of Object.entries(resolvedRegistry.type_to_folder)){const folderKey=folder.trim().toLowerCase();if(folderKey.length>0&&!reservedFolders.has(folderKey)){reservedFolders.set(folderKey,typeName)}}assertTypeFolderAvailable(normalized,parsed,reservedFolders)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}upsert=upsertItemType(parsed,normalized);if(upsert.replaced&&upsert.previousName!==void 0&&upsert.previousName!==upsert.definition.name){warnings.push(`type_recased:${upsert.previousName}->${upsert.definition.name}`)}await writeAuditedSchemaFile({pmRoot,filePath:typesPath,raw:serializeItemTypesFile(upsert.file),op:"schema:add-type",author,settings});await ensureTypeFolderScaffold(pmRoot,[upsert.definition],warnings,"schema:add-type-folder");warnings.push(...await runActiveOnWriteHooks({path:typesPath,scope:"project",op:"schema:add-type"}))}finally{await releaseLock()}await appendMergeFenceWarnings(pmRoot,warnings);return{action:"add-type",registered:true,replaced:upsert.replaced,type:upsert.definition,file:{path:typesPath,definitions:upsert.file.definitions.length},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}async function ensureInitialized(pmRoot){await assertInitializedTracker(pmRoot)}async function appendMergeFenceWarnings(pmRoot,warnings){try{const fence=await refreshMergeAttributeFenceIfInstalled(pmRoot);if(fence.status==="refreshed"){warnings.push("merge_fence_refreshed")}else if(fence.status==="not_installed"){warnings.push('merge_fence_not_installed:type folders lack git merge drivers; run "pm merge install"')}}catch{warnings.push('merge_fence_refresh_failed:run "pm merge install" to reconcile')}}function finalizeWarnings(warnings){return[...new Set(warnings)].sort((left,right)=>left.localeCompare(right))}async function countItemsUsingType(pmRoot,settings,schema,typeName){const registry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());const lowerName=typeName.trim().toLowerCase();const items=await listAllItemMetadataLight(pmRoot,settings.item_format,registry.type_to_folder,[],schema);return items.filter(item=>typeof item.type==="string"&&item.type.toLowerCase()===lowerName).length}async function countItemsUsingStatus(pmRoot,settings,schema,statusId){const registry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());const statusRegistry=resolveRuntimeStatusRegistry(schema);const normalizedId=normalizeStatusToken(statusId);const items=await listAllItemMetadataLight(pmRoot,settings.item_format,registry.type_to_folder,[],schema);return items.filter(item=>{const itemStatus=typeof item.status==="string"?item.status:"";const resolved=statusRegistry.alias_to_id.get(normalizeStatusToken(itemStatus))??normalizeStatusToken(itemStatus);return resolved===normalizedId}).length}async function appendRemovedStatusWarnings(warnings,pmRoot,settings,schema,removedId){if(removedId.length===0||BUILTIN_STATUS_IDS.has(removedId)){return}const usingStatus=await countItemsUsingStatus(pmRoot,settings,schema,removedId);if(usingStatus>0){warnings.push(`items_using_status:${usingStatus}`)}const referencingSlots=workflowSlotsReferencing(schema.workflow,removedId);if(referencingSlots.length>0){warnings.push(`status_referenced_by_workflow:${referencingSlots.join(",")}`)}}function workflowSlotsReferencing(workflow,statusId){const slots=[["draft_status",workflow.draft_status],["open_status",workflow.open_status],["in_progress_status",workflow.in_progress_status],["blocked_status",workflow.blocked_status],["close_status",workflow.close_status],["canceled_status",workflow.canceled_status]];return slots.filter(([,value])=>value!==void 0&&normalizeStatusToken(value)===statusId).map(([slot])=>slot)}async function runSchemaRemoveType(name,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const typesPath=filePathForSchemaSection(pmRoot,schema.files.types,DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.types);const warnings=[];const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const releaseLock=await acquireLock(pmRoot,SCHEMA_TYPES_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);let removal;try{const previousRaw=await readFileIfExists(typesPath);let parsed;try{parsed=parseItemTypesFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}try{removal=removeItemType(parsed,name)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}if(removal.removed){const removedName=(removal.definition?.name??name??"").trim();if(removedName.length>0){const usingType=await countItemsUsingType(pmRoot,settings,schema,removedName);if(usingType>0){warnings.push(`items_using_type:${usingType}`)}}await writeAuditedSchemaFile({pmRoot,filePath:typesPath,raw:serializeItemTypesFile(removal.file),op:"schema:remove-type",author,settings});warnings.push(...await runActiveOnWriteHooks({path:typesPath,scope:"project",op:"schema:remove-type"}))}}finally{await releaseLock()}if(removal.removed){await appendMergeFenceWarnings(pmRoot,warnings)}return{action:"remove-type",removed:removal.removed,...removal.definition?{type:removal.definition}:{},file:{path:typesPath,definitions:removal.file.definitions.length},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}function statusesPathFor(pmRoot,schema){return filePathForSchemaSection(pmRoot,schema.files.statuses,DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.statuses)}function fieldsPathFor(pmRoot,schema){return filePathForSchemaSection(pmRoot,schema.files.fields,DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.fields)}function typesPathFor(pmRoot,schema){return filePathForSchemaSection(pmRoot,schema.files.types,DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.types)}async function ensureTypeFolderScaffold(pmRoot,definitions,warnings,op){for(const definition of definitions){const folder=normalizeItemTypeFolder(definition.folder??toDefaultFolder(definition.name));const target=path.join(pmRoot,folder);if(await pathExists(target)){continue}await mkdir(target,{recursive:true});warnings.push(...await runActiveOnWriteHooks({path:target,scope:"project",op}))}}function toSchemaFieldSummary(field){return{key:field.key,type:field.type,commands:[...field.commands],cli_flag:`--${field.cli_flag}`,cli_aliases:field.cli_aliases.map(alias=>`--${alias}`),...field.description?{description:field.description}:{},required:field.required,required_on_create:field.required_on_create,allow_unset:field.allow_unset,required_types:[...field.required_types]}}function toExistingStatusDefinition(resolvedExisting){if(!resolvedExisting){return void 0}return{id:resolvedExisting.id,...resolvedExisting.roles.length>0?{roles:[...resolvedExisting.roles]}:{},...resolvedExisting.aliases.length>0?{aliases:[...resolvedExisting.aliases]}:{},...resolvedExisting.description?{description:resolvedExisting.description}:{},...typeof resolvedExisting.order==="number"?{order:resolvedExisting.order}:{}}}function buildStatusFileAliasMap(statuses){const fileAliasToId=new Map;for(const definition of statuses){const defId=normalizeStatusToken(definition.id);if(defId.length===0){continue}fileAliasToId.set(defId,defId);for(const alias of Array.isArray(definition.aliases)?definition.aliases:[]){const aliasToken=normalizeStatusToken(alias);if(aliasToken.length>0&&!fileAliasToId.has(aliasToken)){fileAliasToId.set(aliasToken,defId)}}}return fileAliasToId}async function runSchemaAddStatus(id,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);let normalized;try{normalized=normalizeAddStatusInput({id,roles:options.role,aliases:options.alias,description:options.description,order:options.order})}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const statusesPath=statusesPathFor(pmRoot,schema);const statusRegistry=resolveRuntimeStatusRegistry(schema);try{assertStatusTokensAvailable(normalized,statusRegistry.alias_to_id)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}const baseDefinition=toExistingStatusDefinition(statusRegistry.by_id.get(normalized.id));const effectiveRoles=normalized.roles??baseDefinition?.roles??[];if(effectiveRoles.length===0){throw new PmCliError(`Status "${normalized.id}" requires at least one lifecycle role. Re-run with --role active, --role blocked, --role draft, or an appropriate terminal/default role.`,EXIT_CODE.USAGE,{code:"status_lifecycle_role_required",examples:[`pm schema add-status "${escapeForDoubleQuotes(normalized.id)}" --role active`,`pm schema add-status "${escapeForDoubleQuotes(normalized.id)}" --role blocked`]})}const warnings=[];const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const releaseLock=await acquireLock(pmRoot,SCHEMA_STATUSES_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);let upsert;try{const previousRaw=await readFileIfExists(statusesPath);let parsed;try{parsed=parseStatusDefsFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}const fileAliasToId=buildStatusFileAliasMap(parsed.statuses);try{assertStatusTokensAvailable(normalized,fileAliasToId)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}upsert=upsertStatusDef(parsed,normalized,baseDefinition);await writeAuditedSchemaFile({pmRoot,filePath:statusesPath,raw:serializeStatusDefsFile(upsert.file),op:"schema:add-status",author,settings});warnings.push(...await runActiveOnWriteHooks({path:statusesPath,scope:"project",op:"schema:add-status"}))}finally{await releaseLock()}return{action:"add-status",registered:true,replaced:upsert.replaced,status:upsert.definition,file:{path:statusesPath,statuses:upsert.file.statuses.length},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}async function runSchemaRemoveStatus(id,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const statusesPath=statusesPathFor(pmRoot,schema);const warnings=[];const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const releaseLock=await acquireLock(pmRoot,SCHEMA_STATUSES_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);let removal;try{const previousRaw=await readFileIfExists(statusesPath);let parsed;try{parsed=parseStatusDefsFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}try{removal=removeStatusDef(parsed,id)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}if(removal.removed){const removedId=normalizeStatusToken(removal.definition?.id??id);await appendRemovedStatusWarnings(warnings,pmRoot,settings,schema,removedId);await writeAuditedSchemaFile({pmRoot,filePath:statusesPath,raw:serializeStatusDefsFile(removal.file),op:"schema:remove-status",author,settings});warnings.push(...await runActiveOnWriteHooks({path:statusesPath,scope:"project",op:"schema:remove-status"}))}}finally{await releaseLock()}return{action:"remove-status",removed:removal.removed,...removal.definition?{status:removal.definition}:{},file:{path:statusesPath,statuses:removal.file.statuses.length},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}function toSchemaTypeSummary(definition){return{name:definition.name,folder:definition.folder,aliases:Array.isArray(definition.aliases)?[...definition.aliases]:[],...definition.default_status?{default_status:definition.default_status}:{},...definition.description?{description:definition.description}:{}}}function toSchemaTypeDefinition(definition,source,extensionProvenance){return{...toSchemaTypeSummary(definition),source,...extensionProvenance?{extension:extensionProvenance}:{},required_create_fields:[...definition.required_create_fields],required_create_repeatables:[...definition.required_create_repeatables],options:structuredClone(definition.options),command_option_policies:structuredClone(definition.command_option_policies)}}function toSchemaStatusSummary(definition){return{id:definition.id,source:BUILTIN_STATUS_IDS.has(definition.id)?"builtin":"custom",roles:Array.isArray(definition.roles)?[...definition.roles]:[],aliases:Array.isArray(definition.aliases)?[...definition.aliases]:[],...definition.description?{description:definition.description}:{},...typeof definition.order==="number"?{order:definition.order}:{}}}function classifyTypeSource(name,customNames,extensionNames){const lowerName=name.toLowerCase();if(extensionNames.has(lowerName)){return"extension"}if(customNames.has(lowerName)){return"custom"}return"builtin"}function collectExtensionTypeProvenance(){const provenance=new Map;const registrations=getActiveExtensionRegistrations();for(const registration of registrations?.item_types??[]){const rawTypes=registration.types;if(!Array.isArray(rawTypes)){continue}for(const rawType of rawTypes){if(typeof rawType!=="object"||rawType===null||Array.isArray(rawType)){continue}const name=rawType.name;if(typeof name==="string"&&name.trim().length>0){provenance.set(name.trim().toLowerCase(),{layer:registration.layer,name:registration.name})}}}return provenance}function buildSchemaStatusSummaries(schema){const registry=resolveRuntimeStatusRegistry(schema);const builtin=[];const custom=[];for(const definition of registry.definitions){const summary=toSchemaStatusSummary(definition);if(summary.source==="builtin"){builtin.push(summary)}else{custom.push(summary)}}return{builtin,custom}}async function loadSchemaInspectionContext(global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const typesPath=filePathForSchemaSection(pmRoot,schema.files.types,DEFAULT_RUNTIME_SCHEMA_FILE_PATHS.types);const statusesPath=statusesPathFor(pmRoot,schema);const registry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());const extensionProvenance=collectExtensionTypeProvenance();const customNames=new Set((settings.item_types?.definitions??[]).map(definition=>definition.name.trim().toLowerCase()).filter(name=>name.length>0&&!extensionProvenance.has(name)));return{pmRoot,typesPath,statusesPath,byType:registry.by_type,customNames,extensionProvenance,schema}}async function runSchemaList(global2){const context=await loadSchemaInspectionContext(global2);const builtin=[];const custom=[];const extension=[];for(const definition of Object.values(context.byType).sort((left,right)=>left.name.localeCompare(right.name))){const source=classifyTypeSource(definition.name,context.customNames,new Set(context.extensionProvenance.keys()));const summary=toSchemaTypeSummary(definition);if(source==="extension"){extension.push(summary)}else if(source==="custom"){custom.push(summary)}else{builtin.push(summary)}}const statusSummaries=buildSchemaStatusSummaries(context.schema);const fieldSummaries=resolveRuntimeFieldRegistry(context.schema).definitions.map(toSchemaFieldSummary);return{action:"list",builtin,custom,extension,counts:{builtin:builtin.length,custom:custom.length,extension:extension.length,total:builtin.length+custom.length+extension.length},statuses:{builtin:statusSummaries.builtin,custom:statusSummaries.custom,counts:{builtin:statusSummaries.builtin.length,custom:statusSummaries.custom.length,total:statusSummaries.builtin.length+statusSummaries.custom.length}},fields:{custom:fieldSummaries,counts:{total:fieldSummaries.length}},file:{path:context.typesPath},generated_at:nowIso()}}async function runSchemaShow(name,global2){const typeName=(name??"").trim();if(typeName.length===0){throw new PmCliError("Type name must not be empty.",EXIT_CODE.USAGE)}const context=await loadSchemaInspectionContext(global2);const match=Object.values(context.byType).find(definition=>definition.name.toLowerCase()===typeName.toLowerCase()||definition.aliases.some(alias=>alias.toLowerCase()===typeName.toLowerCase()));if(!match){throw new PmCliError(`Unknown item type "${typeName}". Run pm schema list to inspect registered types, or pm schema add-type "${escapeForDoubleQuotes(typeName)}" to register it.`,EXIT_CODE.NOT_FOUND,{code:"unknown_item_type"})}return{action:"show",type:toSchemaTypeDefinition(match,classifyTypeSource(match.name,context.customNames,new Set(context.extensionProvenance.keys())),context.extensionProvenance.get(match.name.toLowerCase())),file:{path:context.typesPath},generated_at:nowIso()}}async function runSchemaShowStatus(id,global2){const statusToken=normalizeStatusToken(id);if(statusToken.length===0){throw new PmCliError("Status id must not be empty.",EXIT_CODE.USAGE)}const context=await loadSchemaInspectionContext(global2);const statusRegistry=resolveRuntimeStatusRegistry(context.schema);const resolvedId=statusRegistry.alias_to_id.get(statusToken)??statusToken;const match=statusRegistry.by_id.get(resolvedId);if(!match){throw new PmCliError(`Unknown status "${id}". Run pm schema list to inspect registered statuses, or pm schema add-status "${escapeForDoubleQuotes(id??"")}" to register it.`,EXIT_CODE.NOT_FOUND,{code:"unknown_status"})}return{action:"show-status",status:toSchemaStatusSummary(match),file:{path:context.statusesPath},generated_at:nowIso()}}async function runSchemaAddField(key,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);let normalized;try{normalized=normalizeAddFieldInput({key,type:options.type,commands:options.commands,description:options.description,cliFlag:options.cliFlag,aliases:options.alias,required:options.required,requiredOnCreate:options.requiredOnCreate,allowUnset:options.allowUnset,requiredTypes:options.requiredTypes})}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const fieldsPath=fieldsPathFor(pmRoot,schema);const warnings=[];const mcpCollision=resolvePmToolCustomFieldCollision(normalized.key);if(mcpCollision){warnings.push(`custom_field_mcp_input_collision:${mcpCollision.field}:${mcpCollision.property}:${mcpCollision.owner}:use=${mcpCollision.nested_path}`)}const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const releaseLock=await acquireLock(pmRoot,SCHEMA_FIELDS_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);let upsert;try{const previousRaw=await readFileIfExists(fieldsPath);let parsed;try{parsed=parseFieldsFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}upsert=upsertField(parsed,normalized);await writeAuditedSchemaFile({pmRoot,filePath:fieldsPath,raw:serializeFieldsFile(upsert.file),op:"schema:add-field",author,settings});warnings.push(...await runActiveOnWriteHooks({path:fieldsPath,scope:"project",op:"schema:add-field"}))}finally{await releaseLock()}return{action:"add-field",registered:true,replaced:upsert.replaced,field:upsert.definition,file:{path:fieldsPath,fields:upsert.file.fields.length},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}async function countItemsUsingField(pmRoot,settings,schema,metadataKey){const registry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());const items=await listAllItemMetadataLight(pmRoot,settings.item_format,registry.type_to_folder,[],schema);return items.filter(item=>{const value=item[metadataKey];if(value===void 0||value===null){return false}if(Array.isArray(value)){return value.length>0}if(typeof value==="string"){return value.trim().length>0}return true}).length}async function runSchemaRemoveField(key,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const fieldsPath=fieldsPathFor(pmRoot,schema);const warnings=[];const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const releaseLock=await acquireLock(pmRoot,SCHEMA_FIELDS_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);let removal;try{const previousRaw=await readFileIfExists(fieldsPath);let parsed;try{parsed=parseFieldsFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}try{removal=removeField(parsed,key)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}if(removal.removed){const removedKey=normalizeFieldKey(removal.definition?.key??key);const metadataKey=normalizeFieldKey(removal.definition?.metadata_key??removal.definition?.front_matter_key??removedKey);if(metadataKey.length>0){const usingField=await countItemsUsingField(pmRoot,settings,schema,metadataKey);if(usingField>0){warnings.push(`items_using_field:${usingField}`)}}await writeAuditedSchemaFile({pmRoot,filePath:fieldsPath,raw:serializeFieldsFile(removal.file),op:"schema:remove-field",author,settings});warnings.push(...await runActiveOnWriteHooks({path:fieldsPath,scope:"project",op:"schema:remove-field"}))}}finally{await releaseLock()}return{action:"remove-field",removed:removal.removed,...removal.definition?{field:removal.definition}:{},file:{path:fieldsPath,fields:removal.file.fields.length},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}async function runSchemaListFields(global2){const context=await loadSchemaInspectionContext(global2);const fields=resolveRuntimeFieldRegistry(context.schema).definitions.map(toSchemaFieldSummary);return{action:"list-fields",fields,counts:{total:fields.length},file:{path:fieldsPathFor(context.pmRoot,context.schema)},generated_at:nowIso()}}async function runSchemaShowField(key,global2){const fieldKey=normalizeFieldKey(key);if(fieldKey.length===0){throw new PmCliError("Field key must not be empty.",EXIT_CODE.USAGE)}const context=await loadSchemaInspectionContext(global2);const match=resolveRuntimeFieldRegistry(context.schema).by_key.get(fieldKey);if(!match){throw new PmCliError(`Unknown custom field "${key}". Run pm schema list-fields to inspect registered fields, or pm schema add-field "${escapeForDoubleQuotes(key??"")}" to register it.`,EXIT_CODE.NOT_FOUND,{code:"unknown_field"})}return{action:"show-field",field:toSchemaFieldSummary(match),file:{path:fieldsPathFor(context.pmRoot,context.schema)},generated_at:nowIso()}}async function runSchemaApplyPreset(preset,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);let presetName;try{const normalized=normalizeTypePresetName(preset);if(normalized===void 0){throw new Error(`Type preset name is required. Allowed: ${TYPE_PRESET_NAMES.join(", ")}.`)}presetName=normalized}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.USAGE)}const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const typesPath=typesPathFor(pmRoot,schema);const warnings=[];const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const registered=[];const replaced=[];let definitionsCount;const releaseLock=await acquireLock(pmRoot,SCHEMA_TYPES_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);try{const previousRaw=await readFileIfExists(typesPath);let parsed;try{parsed=parseItemTypesFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}let nextFile=parsed;for(const normalized of resolveTypePresetDefinitions(presetName)){const upsert=upsertItemType(nextFile,normalized);nextFile=upsert.file;(upsert.replaced?replaced:registered).push(upsert.definition.name)}definitionsCount=nextFile.definitions.length;await writeAuditedSchemaFile({pmRoot,filePath:typesPath,raw:serializeItemTypesFile(nextFile),op:"schema:apply-preset",author,settings});await ensureTypeFolderScaffold(pmRoot,resolveTypePresetDefinitions(presetName),warnings,"schema:apply-preset-folder");warnings.push(...await runActiveOnWriteHooks({path:typesPath,scope:"project",op:"schema:apply-preset"}))}finally{await releaseLock()}return{action:"apply-preset",preset:presetName,registered,replaced,file:{path:typesPath,definitions:definitionsCount},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}function resolveSchemaInferenceMinCount(value){if(value===void 0){return 10}if(!Number.isInteger(value)||value<1){throw new PmCliError("--min-count must be a positive integer (>= 1).",EXIT_CODE.USAGE)}return value}async function runSchemaInferTypes(options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await ensureInitialized(pmRoot);const settings=await readSettings(pmRoot);const schema=normalizeRuntimeSchemaSettings(settings.schema);const typesPath=typesPathFor(pmRoot,schema);const minCount=resolveSchemaInferenceMinCount(options.minCount);const registry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());const items=await listAllItemMetadataLight(pmRoot,settings.item_format,registry.type_to_folder,[],schema);const titles=items.map(item=>typeof item.title==="string"?item.title:"");const candidates=inferTypesFromTitles(titles,{minCount});const warnings=[];const registered=[];const replaced=[];const skipped=[];let definitionsCount;if(!options.apply){let parsed;try{parsed=parseItemTypesFile(await readFileIfExists(typesPath))}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}definitionsCount=parsed.definitions.length;return{action:"infer-types",applied:false,min_count:minCount,candidates,registered,replaced,skipped,file:{path:typesPath,definitions:definitionsCount},warnings,generated_at:nowIso()}}const author=resolveAuthor(options.author,settings.author_default);const governance=resolveGovernanceKnobs(settings);const releaseLock=await acquireLock(pmRoot,SCHEMA_TYPES_LOCK_ID,settings.locks.ttl_seconds,author,Boolean(options.force),governance.force_required_for_stale_lock,settings.locks.wait_ms);try{const previousRaw=await readFileIfExists(typesPath);let parsed;try{parsed=parseItemTypesFile(previousRaw)}catch(error){throw new PmCliError(error instanceof Error?error.message:String(error),EXIT_CODE.GENERIC_FAILURE)}let nextFile=parsed;for(const candidate of candidates){if(candidate.shadows_builtin){skipped.push({name:candidate.name,reason:"shadows_builtin"});continue}let normalized;try{normalized=normalizeAddTypeInput({name:candidate.name})}catch{skipped.push({name:candidate.name,reason:"invalid_type_token"});continue}const upsert=upsertItemType(nextFile,normalized);nextFile=upsert.file;(upsert.replaced?replaced:registered).push(upsert.definition.name)}definitionsCount=nextFile.definitions.length;await writeAuditedSchemaFile({pmRoot,filePath:typesPath,raw:serializeItemTypesFile(nextFile),op:"schema:infer-types",author,settings});await ensureTypeFolderScaffold(pmRoot,nextFile.definitions.filter(definition=>registered.includes(definition.name)||replaced.includes(definition.name)),warnings,"schema:infer-types-folder");warnings.push(...await runActiveOnWriteHooks({path:typesPath,scope:"project",op:"schema:infer-types"}))}finally{await releaseLock()}return{action:"infer-types",applied:true,min_count:minCount,candidates,registered,replaced,skipped,file:{path:typesPath,definitions:definitionsCount},warnings:finalizeWarnings(warnings),generated_at:nowIso()}}function formatSchemaAddTypeHuman(result){const verb=result.replaced?"Updated":"Registered";const aliasSuffix=result.type.aliases&&result.type.aliases.length>0?` (aliases: ${result.type.aliases.join(", ")})`:"";return`${verb} custom item type "${result.type.name}"${aliasSuffix} in ${result.file.path}. Run: pm create "${escapeForDoubleQuotes(result.type.name)}" "<title>"`}function formatSchemaRemoveTypeHuman(result){if(!result.removed){return`No custom item type matched; nothing removed from ${result.file.path}.`}const name=result.type?.name??"(unknown)";return`Removed custom item type "${name}" from ${result.file.path}.`}function formatSchemaAddStatusHuman(result){const verb=result.replaced?"Updated":"Registered";const roleSuffix=result.status.roles&&result.status.roles.length>0?` (roles: ${result.status.roles.join(", ")})`:"";const aliasSuffix=result.status.aliases&&result.status.aliases.length>0?` (aliases: ${result.status.aliases.join(", ")})`:"";return`${verb} status "${result.status.id}"${roleSuffix}${aliasSuffix} in ${result.file.path}.`}function formatSchemaRemoveStatusHuman(result){if(!result.removed){return`No custom status matched; nothing removed from ${result.file.path}.`}const id=result.status?.id??"(unknown)";return`Removed custom status "${id}" from ${result.file.path}.`}function formatSchemaListHuman(result){const lines=[`Schema types: ${result.counts.total} total (${result.counts.builtin} builtin, ${result.counts.custom} custom, ${result.counts.extension} extension)`];for(const[label,entries]of[["builtin",result.builtin],["custom",result.custom],["extension",result.extension]]){if(entries.length===0){continue}lines.push(`${label}: ${entries.map(entry=>entry.name).join(", ")}`)}lines.push(`statuses: ${result.statuses.counts.total} total (${result.statuses.counts.builtin} builtin, ${result.statuses.counts.custom} custom)`);for(const[label,entries]of[["builtin statuses",result.statuses.builtin],["custom statuses",result.statuses.custom]]){if(entries.length===0){continue}lines.push(`${label}: ${entries.map(entry=>entry.id).join(", ")}`)}lines.push(`custom fields: ${result.fields.counts.total} total`);if(result.fields.custom.length>0){lines.push(`fields: ${result.fields.custom.map(field=>`${field.key} (${field.type}, ${field.cli_flag})`).join(", ")}`)}lines.push(`Inspect one: pm schema show <Type>`);lines.push(`Inspect one status: pm schema show-status <status>`);lines.push(`Inspect one field: pm schema show-field <key>`);return lines.join("\n")}function formatSchemaShowHuman(result){const parts=[`type: ${result.type.name}`,`source: ${result.type.source}`,`folder: ${result.type.folder}`];if(result.type.default_status){parts.push(`default_status: ${result.type.default_status}`)}if(result.type.aliases.length>0){parts.push(`aliases: ${result.type.aliases.join(", ")}`)}if(result.type.description){parts.push(`description: ${result.type.description}`)}if(result.type.options.length>0){parts.push(`options: ${result.type.options.map(option=>option.key).join(", ")}`)}return parts.join("\n")}function formatSchemaShowStatusHuman(result){const parts=[`status: ${result.status.id}`,`source: ${result.status.source}`];if(result.status.roles.length>0){parts.push(`roles: ${result.status.roles.join(", ")}`)}if(result.status.aliases.length>0){parts.push(`aliases: ${result.status.aliases.join(", ")}`)}if(result.status.description){parts.push(`description: ${result.status.description}`)}if(typeof result.status.order==="number"){parts.push(`order: ${result.status.order}`)}return parts.join("\n")}function formatFieldSummaryLine(field){const flags=[];if(field.required){flags.push("required")}if(field.required_on_create){flags.push("required_on_create")}if(!field.allow_unset){flags.push("no_unset")}const flagSuffix=flags.length>0?` [${flags.join(", ")}]`:"";return`${field.key} (${field.type}) ${field.cli_flag} commands=${field.commands.join("/")}${flagSuffix}`}function formatSchemaAddFieldHuman(result){const verb=result.replaced?"Updated":"Registered";return`${verb} custom field "${result.field.key}" in ${result.file.path}. Use it: pm create "<title>" --${(result.field.cli_flag??result.field.key).replaceAll("_","-")} <value>`}function formatSchemaRemoveFieldHuman(result){if(!result.removed){return`No custom field matched; nothing removed from ${result.file.path}.`}const key=result.field?.key??"(unknown)";return`Removed custom field "${key}" from ${result.file.path}.`}function formatSchemaListFieldsHuman(result){const lines=[`Custom fields: ${result.counts.total} total`];for(const field of result.fields){lines.push(` ${formatFieldSummaryLine(field)}`)}lines.push(`Inspect one: pm schema show-field <key>`);return lines.join("\n")}function formatSchemaShowFieldHuman(result){const field=result.field;const parts=[`field: ${field.key}`,`type: ${field.type}`,`cli_flag: ${field.cli_flag}`,`commands: ${field.commands.join(", ")}`];if(field.cli_aliases.length>0){parts.push(`cli_aliases: ${field.cli_aliases.join(", ")}`)}if(field.description){parts.push(`description: ${field.description}`)}if(field.required){parts.push(`required: true`)}if(field.required_on_create){parts.push(`required_on_create: true`)}if(!field.allow_unset){parts.push(`allow_unset: false`)}if(field.required_types.length>0){parts.push(`required_types: ${field.required_types.join(", ")}`)}return parts.join("\n")}function formatSchemaApplyPresetHuman(result){const registered=result.registered.length>0?`registered: ${result.registered.join(", ")}`:"";const replaced=result.replaced.length>0?`updated: ${result.replaced.join(", ")}`:"";const detail=[registered,replaced].filter(part=>part.length>0).join("; ");return`Applied "${result.preset}" type preset to ${result.file.path}${detail.length>0?` (${detail})`:""}.`}function formatSchemaInferTypesHuman(result){if(result.candidates.length===0){return`No title-prefix conventions found with at least ${result.min_count} items. Lower the threshold with --min-count <n>.`}const lines=result.applied?[`Inferred and registered custom types from title prefixes (min-count ${result.min_count}):`]:[`Inferred custom type candidates from title prefixes (min-count ${result.min_count}, dry-run):`];for(const candidate of result.candidates){const note=candidate.shadows_builtin?" [shadows built-in, skipped]":"";lines.push(` ${candidate.name} <- "${candidate.prefix}" (${candidate.count} items)${note}`)}if(!result.applied){lines.push(`Re-run with --apply to register the non-shadowing candidates.`)}else{if(result.registered.length>0){lines.push(`registered: ${result.registered.join(", ")}`)}if(result.replaced.length>0){lines.push(`updated: ${result.replaced.join(", ")}`)}}return lines.join("\n")}const _testOnlySchemaCommand={workflowSlotsReferencing};export{SCHEMA_SUBCOMMANDS,_testOnlySchemaCommand,buildInvalidTypeHint,ensureTypeFolderScaffold,formatSchemaAddFieldHuman,formatSchemaAddStatusHuman,formatSchemaAddTypeHuman,formatSchemaApplyPresetHuman,formatSchemaEvolutionMigrationHuman,formatSchemaInferTypesHuman,formatSchemaListFieldsHuman,formatSchemaListHuman,formatSchemaRemoveFieldHuman,formatSchemaRemoveStatusHuman,formatSchemaRemoveTypeHuman,formatSchemaShowFieldHuman,formatSchemaShowHuman,formatSchemaShowStatusHuman,planSchemaEvolutionMigration,runSchemaAddField,runSchemaAddStatus,runSchemaAddType,runSchemaApplyPreset,runSchemaEvolutionMigration,runSchemaInferTypes,runSchemaList,runSchemaListFields,runSchemaRemoveField,runSchemaRemoveStatus,runSchemaRemoveType,runSchemaShow,runSchemaShowField,runSchemaShowStatus};
2
2
  //# sourceMappingURL=schema.js.map
package/dist/types.d.ts CHANGED
@@ -1035,6 +1035,8 @@ export interface AgentGuidanceSettings {
1035
1035
  }
1036
1036
  /** Restricts search mutation refresh policy values accepted by command, SDK, and storage contracts. */
1037
1037
  export type SearchMutationRefreshPolicy = "cache_only" | "semantic_configured" | "semantic_auto";
1038
+ /** Restricts the declared default search mode: `auto` derives hybrid-or-keyword from workspace state, a concrete mode pins it. */
1039
+ export type SearchDefaultMode = "auto" | "keyword" | "semantic" | "hybrid";
1038
1040
  /** Documents the search query expansion settings payload exchanged by command, SDK, and package integrations. */
1039
1041
  export interface SearchQueryExpansionSettings {
1040
1042
  /** Whether enabled applies to this operation. */
@@ -1208,6 +1210,8 @@ export interface PmSettings {
1208
1210
  embedding_timeout_ms: number;
1209
1211
  scanner_max_batch_retries: number;
1210
1212
  provider?: string;
1213
+ /** Mode a search runs in when the caller passes none: `auto` (default) resolves to hybrid when semantic search is explicitly configured and runnable, keyword otherwise; a concrete mode pins it. Optional on the type so existing settings literals stay valid; readers always normalize it. */
1214
+ default_mode?: SearchDefaultMode;
1211
1215
  /** Optional allow-list of corpus field names embedded for semantic search (see DEFAULT_SEARCH_CORPUS_FIELDS in core/search/corpus.ts). When unset or empty, the full default field set is used (backward compatible). When set, only the named fields are embedded — letting teams opt structured signals (priority, assignee, risk, acceptance_criteria, etc.) in/out for token efficiency. Changing this re-flags items stale on the next refresh. */
1212
1216
  corpus_fields?: string[];
1213
1217
  mutation_refresh_policy: SearchMutationRefreshPolicy;
package/dist/types.js CHANGED
@@ -1,2 +1,2 @@
1
- !(function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="39eb895c-059b-5b65-b1ef-b73bfa13f093")}catch(e2){}})();const BUILTIN_ITEM_TYPE_VALUES=["Epic","Feature","Task","Chore","Issue","Decision","Event","Reminder","Milestone","Meeting","Plan"];const ITEM_TYPE_VALUES=BUILTIN_ITEM_TYPE_VALUES;const STATUS_VALUES=["draft","open","in_progress","blocked","closed","canceled"];const RUNTIME_STATUS_ROLE_VALUES=["draft","active","blocked","terminal","terminal_done","terminal_canceled","default_open","default_close","default_cancel"];const RUNTIME_FIELD_TYPE_VALUES=["string","number","boolean","string_array","array","object"];const RUNTIME_FIELD_COMMAND_VALUES=["create","update","update_many","list","search","calendar","context"];const RUNTIME_UNKNOWN_FIELD_POLICY_VALUES=["allow","warn","reject"];const DEPENDENCY_KIND_VALUES=["blocks","parent","child","parent_child","child_of","related","related_to","discovered_from","blocked_by","incident_from","epic","implements","recurs_from","supersedes","task","verifies"];const SCOPE_VALUES=["project","global"];const RISK_VALUES=["low","medium","high","critical"];const ISSUE_SEVERITY_VALUES=["low","medium","high","critical"];const CONFIDENCE_TEXT_VALUES=["low","medium","high"];const ITEM_FORMAT_VALUES=["toon","json_markdown"];const SPRINT_RELEASE_FORMAT_POLICY_VALUES=["warn","strict_error"];const PARENT_REFERENCE_POLICY_VALUES=["warn","strict_error"];const VALIDATE_METADATA_PROFILE_VALUES=["core","strict","custom"];const GOVERNANCE_PRESET_VALUES=["minimal","default","strict","custom"];const GOVERNANCE_OWNERSHIP_ENFORCEMENT_VALUES=["none","warn","strict"];const GOVERNANCE_CREATE_MODE_DEFAULT_VALUES=["progressive","strict"];const GOVERNANCE_CLOSE_VALIDATION_DEFAULT_VALUES=["off","warn","strict"];const GOVERNANCE_WORKFLOW_ENFORCEMENT_VALUES=["off","warn","strict"];const GOVERNANCE_DUPLICATE_DETECTION_MODE_VALUES=["off","advisory","strict"];const VALIDATE_METADATA_REQUIRED_FIELD_VALUES=["author","acceptance_criteria","estimated_minutes","close_reason","reviewer","risk","confidence","sprint","release"];const RECURRENCE_FREQUENCY_VALUES=["daily","weekly","monthly","yearly"];const RECURRENCE_WEEKDAY_VALUES=["mon","tue","wed","thu","fri","sat","sun"];function weekdayOrderIndex(value){return RECURRENCE_WEEKDAY_VALUES.indexOf(value)}const PLAN_MODE_VALUES=["draft","research","review","approved","executing","paused","completed","superseded"];const PLAN_STEP_STATUS_VALUES=["pending","in_progress","completed","blocked","skipped","superseded"];const PLAN_HARNESS_VALUES=["codex","claude-code","cursor","generic"];const PLAN_STEP_LINK_KIND_VALUES=["related","blocks","blocked_by","depends_on","discovered_from","implements","recurs_from","verifies","supersedes"];const CONTEXT_DEPTH_VALUES=["brief","standard","deep","full"];const CONTEXT_SECTION_VALUES=["hierarchy","activity","progress","recently_created","unparented","blockers","files","workload","staleness","tests"];export{BUILTIN_ITEM_TYPE_VALUES,CONFIDENCE_TEXT_VALUES,CONTEXT_DEPTH_VALUES,CONTEXT_SECTION_VALUES,DEPENDENCY_KIND_VALUES,GOVERNANCE_CLOSE_VALIDATION_DEFAULT_VALUES,GOVERNANCE_CREATE_MODE_DEFAULT_VALUES,GOVERNANCE_DUPLICATE_DETECTION_MODE_VALUES,GOVERNANCE_OWNERSHIP_ENFORCEMENT_VALUES,GOVERNANCE_PRESET_VALUES,GOVERNANCE_WORKFLOW_ENFORCEMENT_VALUES,ISSUE_SEVERITY_VALUES,ITEM_FORMAT_VALUES,ITEM_TYPE_VALUES,PARENT_REFERENCE_POLICY_VALUES,PLAN_HARNESS_VALUES,PLAN_MODE_VALUES,PLAN_STEP_LINK_KIND_VALUES,PLAN_STEP_STATUS_VALUES,RECURRENCE_FREQUENCY_VALUES,RECURRENCE_WEEKDAY_VALUES,RISK_VALUES,RUNTIME_FIELD_COMMAND_VALUES,RUNTIME_FIELD_TYPE_VALUES,RUNTIME_STATUS_ROLE_VALUES,RUNTIME_UNKNOWN_FIELD_POLICY_VALUES,SCOPE_VALUES,SPRINT_RELEASE_FORMAT_POLICY_VALUES,STATUS_VALUES,VALIDATE_METADATA_PROFILE_VALUES,VALIDATE_METADATA_REQUIRED_FIELD_VALUES,weekdayOrderIndex};
1
+ !(function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="2c4c36ad-958a-59e6-9bd7-99458b182dbe")}catch(e2){}})();const BUILTIN_ITEM_TYPE_VALUES=["Epic","Feature","Task","Chore","Issue","Decision","Event","Reminder","Milestone","Meeting","Plan"];const ITEM_TYPE_VALUES=BUILTIN_ITEM_TYPE_VALUES;const STATUS_VALUES=["draft","open","in_progress","blocked","closed","canceled"];const RUNTIME_STATUS_ROLE_VALUES=["draft","active","blocked","terminal","terminal_done","terminal_canceled","default_open","default_close","default_cancel"];const RUNTIME_FIELD_TYPE_VALUES=["string","number","boolean","string_array","array","object"];const RUNTIME_FIELD_COMMAND_VALUES=["create","update","update_many","list","search","calendar","context"];const RUNTIME_UNKNOWN_FIELD_POLICY_VALUES=["allow","warn","reject"];const DEPENDENCY_KIND_VALUES=["blocks","parent","child","parent_child","child_of","related","related_to","discovered_from","blocked_by","incident_from","epic","implements","recurs_from","supersedes","task","verifies"];const SCOPE_VALUES=["project","global"];const RISK_VALUES=["low","medium","high","critical"];const ISSUE_SEVERITY_VALUES=["low","medium","high","critical"];const CONFIDENCE_TEXT_VALUES=["low","medium","high"];const ITEM_FORMAT_VALUES=["toon","json_markdown"];const SPRINT_RELEASE_FORMAT_POLICY_VALUES=["warn","strict_error"];const PARENT_REFERENCE_POLICY_VALUES=["warn","strict_error"];const VALIDATE_METADATA_PROFILE_VALUES=["core","strict","custom"];const GOVERNANCE_PRESET_VALUES=["minimal","default","strict","custom"];const GOVERNANCE_OWNERSHIP_ENFORCEMENT_VALUES=["none","warn","strict"];const GOVERNANCE_CREATE_MODE_DEFAULT_VALUES=["progressive","strict"];const GOVERNANCE_CLOSE_VALIDATION_DEFAULT_VALUES=["off","warn","strict"];const GOVERNANCE_WORKFLOW_ENFORCEMENT_VALUES=["off","warn","strict"];const GOVERNANCE_DUPLICATE_DETECTION_MODE_VALUES=["off","advisory","strict"];const VALIDATE_METADATA_REQUIRED_FIELD_VALUES=["author","acceptance_criteria","estimated_minutes","close_reason","reviewer","risk","confidence","sprint","release"];const RECURRENCE_FREQUENCY_VALUES=["daily","weekly","monthly","yearly"];const RECURRENCE_WEEKDAY_VALUES=["mon","tue","wed","thu","fri","sat","sun"];function weekdayOrderIndex(value){return RECURRENCE_WEEKDAY_VALUES.indexOf(value)}const PLAN_MODE_VALUES=["draft","research","review","approved","executing","paused","completed","superseded"];const PLAN_STEP_STATUS_VALUES=["pending","in_progress","completed","blocked","skipped","superseded"];const PLAN_HARNESS_VALUES=["codex","claude-code","cursor","generic"];const PLAN_STEP_LINK_KIND_VALUES=["related","blocks","blocked_by","depends_on","discovered_from","implements","recurs_from","verifies","supersedes"];const CONTEXT_DEPTH_VALUES=["brief","standard","deep","full"];const CONTEXT_SECTION_VALUES=["hierarchy","activity","progress","recently_created","unparented","blockers","files","workload","staleness","tests"];export{BUILTIN_ITEM_TYPE_VALUES,CONFIDENCE_TEXT_VALUES,CONTEXT_DEPTH_VALUES,CONTEXT_SECTION_VALUES,DEPENDENCY_KIND_VALUES,GOVERNANCE_CLOSE_VALIDATION_DEFAULT_VALUES,GOVERNANCE_CREATE_MODE_DEFAULT_VALUES,GOVERNANCE_DUPLICATE_DETECTION_MODE_VALUES,GOVERNANCE_OWNERSHIP_ENFORCEMENT_VALUES,GOVERNANCE_PRESET_VALUES,GOVERNANCE_WORKFLOW_ENFORCEMENT_VALUES,ISSUE_SEVERITY_VALUES,ITEM_FORMAT_VALUES,ITEM_TYPE_VALUES,PARENT_REFERENCE_POLICY_VALUES,PLAN_HARNESS_VALUES,PLAN_MODE_VALUES,PLAN_STEP_LINK_KIND_VALUES,PLAN_STEP_STATUS_VALUES,RECURRENCE_FREQUENCY_VALUES,RECURRENCE_WEEKDAY_VALUES,RISK_VALUES,RUNTIME_FIELD_COMMAND_VALUES,RUNTIME_FIELD_TYPE_VALUES,RUNTIME_STATUS_ROLE_VALUES,RUNTIME_UNKNOWN_FIELD_POLICY_VALUES,SCOPE_VALUES,SPRINT_RELEASE_FORMAT_POLICY_VALUES,STATUS_VALUES,VALIDATE_METADATA_PROFILE_VALUES,VALIDATE_METADATA_REQUIRED_FIELD_VALUES,weekdayOrderIndex};
2
2
  //# sourceMappingURL=types.js.map
package/docs/COMMANDS.md CHANGED
@@ -254,6 +254,16 @@ Use only one updated-at window per list call: `--today`, `--recent`, and `--upda
254
254
 
255
255
  Keyword mode now applies the configured `search.max_results` default (50) when `--limit` is omitted, so a broad query no longer returns every hit. When the limit drops rows the result adds a top-level `total` (pre-limit match count).
256
256
 
257
+ ### Default search mode (pm-n8a6e7)
258
+
259
+ A bare `pm search` runs in the mode the SDK resolves from `search.default_mode`, never from a literal in the CLI. `auto` (the default) picks `hybrid` exactly when the stored settings name the active embedding provider (`search.provider`, the same `configured` verdict `pm health` reports) and a vector store or extension adapter resolves, or when an extension search provider is active; every other workspace runs `keyword`. Auto-detected Ollama/LanceDB runtime defaults do not count as configured, so an unconfigured host never pays an embedding round-trip it did not ask for. A concrete `search.default_mode` (`keyword`, `semantic`, `hybrid`) pins the answer, and `--mode` always wins. The result reports `mode` (what ran, after any runtime fallback) beside `mode_source` (`explicit`, `settings`, or `auto`), so a caller can tell a chosen mode from a derived one (compact responses carry `mode_source` only for `settings`, since `explicit` is the caller's own choice and `auto` is the documented default); when the provider is unreachable a hybrid default still degrades to keyword hits with the usual `search_semantic_fallback` warning.
260
+
261
+ ```bash
262
+ pm search "context budget" # auto: hybrid here because search.provider is configured and the index resolves
263
+ pm config project set search_default_mode keyword # pin keyword for a workspace that prefers exact-token results
264
+ pm search "context budget" --mode semantic # explicit request, mode_source: explicit
265
+ ```
266
+
257
267
  ```bash
258
268
  pm search "reminder validation queue" --match-mode and # require all three tokens
259
269
  pm search "exact title phrase" --match-mode exact # contiguous-phrase match
@@ -388,6 +388,16 @@ Advanced relevance tuning is opt-in:
388
388
  When query expansion or rerank providers fail, `pm search` degrades gracefully and keeps the query runnable; warning codes are emitted in the result payload.
389
389
  `pm search` filter metadata now reports `query_expansion_*` and `rerank_*` fields so automation can detect when advanced tuning is active.
390
390
 
391
+ `search.default_mode` decides the mode a bare `pm search` runs in (`--mode` always wins):
392
+
393
+ | Value | Behavior |
394
+ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
395
+ | `auto` | default; `hybrid` when `search.provider` names the active embedding provider and a vector store or extension adapter resolves (or an extension search provider is active), `keyword` otherwise |
396
+ | `keyword` | pin exact-token ranking for every bare search |
397
+ | `semantic`, `hybrid` | pin the semantic path; an unreachable provider still degrades to keyword hits with a `search_semantic_fallback` warning |
398
+
399
+ Every search result reports `mode_source` (`explicit`, `settings`, or `auto`) beside the effective `mode`.
400
+
391
401
  Mutation commands invalidate keyword search caches immediately. Semantic vector refresh is controlled by `search.mutation_refresh_policy`:
392
402
 
393
403
  | Policy | Behavior |
@@ -399,6 +409,7 @@ Mutation commands invalidate keyword search caches immediately. Semantic vector
399
409
  Useful commands:
400
410
 
401
411
  ```bash
412
+ pm config project set search_default_mode keyword
402
413
  pm config project set search_mutation_refresh_policy cache_only
403
414
  pm config project set search_embedding_corpus_max_characters 12000
404
415
  pm config project set search_query_expansion_enabled true
@@ -468,7 +479,7 @@ pm config project set qdrant_api_key '<QDRANT_API_KEY>' # omit on unauthentica
468
479
 
469
480
  After changing any of these, run `pm reindex --mode hybrid` so the vector index reflects the new provider/store. `pm search ... --mode semantic|hybrid` emits a `vector_index_stale` warning when items have been modified since the last reindex.
470
481
 
471
- **Refresh contract (important for agents):** by default a mutation (`pm create`/`pm update`) does **not** synchronously re-embed the changed item, so a brand-new item is absent from `--mode semantic|hybrid` results until the next `pm reindex`. This keeps writes fast. Control the tradeoff with `search.mutation_refresh_policy`: `cache_only` (never refresh on write — fastest), `semantic_auto` (refresh when implicit Ollama/LanceDB defaults are active), or `semantic_configured` (refresh only when semantic search is explicitly configured). Keyword mode (`--mode keyword`, the default) always reflects writes immediately because it reads items directly.
482
+ **Refresh contract (important for agents):** by default a mutation (`pm create`/`pm update`) does **not** synchronously re-embed the changed item, so a brand-new item is absent from `--mode semantic|hybrid` results until the next `pm reindex`. This keeps writes fast. Control the tradeoff with `search.mutation_refresh_policy`: `cache_only` (never refresh on write — fastest), `semantic_auto` (refresh when implicit Ollama/LanceDB defaults are active), or `semantic_configured` (refresh only when semantic search is explicitly configured). Keyword mode (`--mode keyword`, or the `auto` default on a workspace without a configured semantic provider) always reflects writes immediately because it reads items directly.
472
483
 
473
484
  ## Custom Item Types
474
485
 
package/docs/README.md CHANGED
@@ -62,6 +62,7 @@ pm guide release --json
62
62
  - [Universal Read Output Contracts](READ_OUTPUT_CONTRACTS.md) - cross-command include, amount, cost, and encoding controls for CLI, SDK, MCP, and packages.
63
63
  - [Diagnostic Output Contracts](DIAGNOSTIC_OUTPUT_CONTRACTS.md) - action-first error budgets, deterministic degradation, SDK projection, and executable refusal assurance.
64
64
  - [History Maintenance and Recovery](HISTORY_MAINTENANCE.md) - native history commands, permanent aliases, shared SDK transforms, and transactional recovery.
65
+ - [Declarative Workflow Policies](WORKFLOW_POLICIES.md) - portable lifecycle requirements, independent approvals, and completeness reports.
65
66
  - [Mutation Integrity](MUTATION_INTEGRITY.md) - shared CLI/SDK/MCP author, secret, append-only disposition, and stale-work guardrails.
66
67
  - [Agent Provenance ADR Amendment](AGENT_PROVENANCE_ADR.md) - extensible model, effort, role, and host provenance with privacy and compatibility boundaries.
67
68
  - [SDK Agent Session and Episode Context](SDK_AGENT_SESSION_CONTEXT.md) - inherited role/topic context, cross-process episode identity, and deterministic history grouping.
@@ -167,6 +167,11 @@ Cursor recovery carries the opaque value once in `recovery.cursor` and declares
167
167
  the accepting `cli`, `sdk`, and `mcp` binding beside it. This avoids
168
168
  serializing the same cursor once per transport while retaining an executable,
169
169
  machine-readable binding for each surface.
170
+ When `restore_with` is `"recovery"`, consumers must resolve the adjacent
171
+ `recovery` object: pass its `cursor` value through `cli` (`--output-cursor`)
172
+ or the `sdk`/`mcp` field (`outputCursor`) while repeating the original query.
173
+ The string is a reference, not a shell command. Other `restore_with` values
174
+ provide retry guidance when no declared row collection can continue.
170
175
 
171
176
  Budget-compacted declared row paths are independently resumable. The
172
177
  disclosure's `continuations` entries name the row path, retained/remaining/total
package/docs/TESTING.md CHANGED
@@ -88,6 +88,16 @@ so PR-local CodeFactor maintainability annotations fail locally before commit or
88
88
  push. Existing legacy high-complexity test fixtures are tracked separately and
89
89
  must not be used as precedent for new changed production/script code.
90
90
 
91
+ `lint:duplicates` runs three zero-clone profiles: the broad repository profile,
92
+ the production cross-format profile, and a supplemental long-table profile at
93
+ 28 lines / 80 tokens. The last profile detects the historical GH-508 normalizer
94
+ tables below the other profiles' token floors; it does not replace their shorter
95
+ line thresholds. Its real detector regression rejects the historical table rows
96
+ and accepts a shared declaration. The earlier 12-line / 60-token exploratory
97
+ census informed calibration, but is not the enforced profile. These bounded
98
+ profiles do not claim complete equivalence with hosted analyzers.
99
+ See [pm-xspd](../.agents/pm/chores/pm-xspd.toon) for calibration evidence.
100
+
91
101
  The local parity rules catch analyzer classes before push. The mandatory hosted
92
102
  proof runs after the final commit is pushed and both apps have finished:
93
103
 
@@ -0,0 +1,129 @@
1
+ # Declarative workflow policies
2
+
3
+ Tracker: [pm-mj42og](../.agents/pm/features/pm-mj42og.toon) and [pm-htbetn](../.agents/pm/features/pm-htbetn.toon).
4
+
5
+ Projects can describe their lifecycle requirements as versioned data. The SDK
6
+ uses the same evaluator for item mutations, policy previews, and completeness
7
+ reports. Existing projects have no implicit policies. A declaration can advise,
8
+ warn, or refuse; refusal also requires an explicit workspace opt-in.
9
+
10
+ ## Configure a requirement
11
+
12
+ ```bash
13
+ pm schema policy-put close-evidence --definition '{"id":"close-evidence","effect":"refuse","subject":{"statuses":["closed"]},"rule":{"kind":"require_fields","fields":["resolution","actual_result"]}}'
14
+ pm schema policies
15
+ pm schema policy-check pm-example --definition '{"status":"closed"}'
16
+ pm schema policy-mode refuse
17
+ ```
18
+
19
+ Use canonical status ids and metadata field names. `body` selects the Markdown
20
+ body. Custom fields can use dotted paths with at most eight segments. Empty
21
+ strings, arrays, objects, null, and absent values do not satisfy required fields;
22
+ zero and false are meaningful values.
23
+
24
+ `policy-put` replaces a declaration with the same id. `policy-remove <id>` removes
25
+ one declaration. `policy-mode advise` disables refusal while retaining diagnostics.
26
+ Registry writes are locked and recorded in workspace history. `--dry-run` previews
27
+ registry changes without writing. Malformed registries fail closed.
28
+
29
+ ## Selectors and rules
30
+
31
+ All selectors in `subject` must match; values within one selector are alternatives.
32
+ Supported selectors are `types` (case insensitive), `tags`, `statuses`, `operations`,
33
+ `parents`, and `dependency: { "kind": "implements", "id": "pm-example" }` (id optional).
34
+ Parent and dependency selectors inspect direct relationships. Status selectors
35
+ match the destination; other selectors inspect both snapshots so a mutation cannot
36
+ escape a protected scope by removing its tag or changing its type.
37
+
38
+ | Rule kind | Required properties | Behavior |
39
+ | --- | --- | --- |
40
+ | `require_fields` | `fields` | Checks evidence on lifecycle, scope, or required-field changes |
41
+ | `transition` | `allowed` | Allows listed `[from, to]` status pairs; `$create` and `$delete` represent absent records |
42
+ | `authors` | `authors` | Requires a listed mutation actor |
43
+ | `field_writers` | `fields`, `authors` | Requires a listed actor when protected fields change |
44
+ | `approval` | `fields`, `authors` | Requires independent approval when an existing item changes status or is deleted |
45
+
46
+ Operation selectors use exact history operation names. Unknown declaration keys
47
+ are errors. Policies contain no executable expressions or regular expressions.
48
+ At most 256 declarations and 64 terms per selector/rule are accepted. Registry
49
+ reads are bounded to 1 MiB, and approval-history reads to 4 MiB.
50
+
51
+ Actor names are provenance supplied by the caller or local configuration. They
52
+ are not authenticated identities, and these rules are not an access-control
53
+ boundary against a caller who can edit the tracker or choose an author name.
54
+ Authenticated, signed identity ([pm-u14c](../.agents/pm/stories/pm-u14c.toon))
55
+ is a prerequisite for stronger authorization guarantees.
56
+
57
+ ## Approvals
58
+
59
+ Prepare the declared fields, then have a different workflow actor record approval:
60
+
61
+ ```bash
62
+ pm schema policy-put review --definition '{"id":"review","effect":"refuse","subject":{"statuses":["closed"]},"rule":{"kind":"approval","fields":["body","resolution"],"authors":["reviewer"]}}'
63
+ pm schema policy-approve pm-example --policy review --author reviewer --message 'Reviewed acceptance evidence'
64
+ ```
65
+
66
+ Approval events bind the item id, selected field values, and complete policy
67
+ revision. Changing reviewed content or the declaration invalidates the approval.
68
+ Unrelated metadata changes do not. The actor performing the transition must differ
69
+ from the approval actor. Only sealed approval events from a verified item history
70
+ chain count. `policy-check` previews transitions; `policy-approve` always records
71
+ an event and rejects `--dry-run`.
72
+
73
+ Approval rules apply to existing items. Creation and initial imports have no
74
+ reviewable history and skip approval requirements; use `transition` rules with
75
+ `["$create", "open"]` to constrain their initial status. Deletion requires approval
76
+ of the current record, using the same content and independent-actor checks.
77
+
78
+ Refused mutations preserve item state and item history, and append a workspace
79
+ refusal event. Accepted mutations attach bounded policy decisions to their normal
80
+ history event. Diagnostics contain policy ids and missing field paths, without
81
+ copying reviewed content into errors.
82
+ If the mandatory refusal audit cannot be appended, the mutation instead raises
83
+ `workflow_policy_audit_failed` and preserves the original failure in the SDK
84
+ error's `cause`. Item data remains unchanged; resolve the workspace history lock
85
+ or storage failure before retrying. Package imports do not downgrade this error
86
+ to an item-lock warning.
87
+
88
+ ## Completeness reports and SDK
89
+
90
+ ```bash
91
+ pm schema policy-presets
92
+ pm validate --check-completeness --strict-exit
93
+ ```
94
+
95
+ Presets return optional advisory declarations for familiar item types. They do
96
+ not install policies. Author the declarations appropriate to the project's own
97
+ types and lifecycle using `policy-put`. Completeness reports group counts by type,
98
+ return bounded violation examples, and use only `require_fields` declarations
99
+ without `subject.operations`. The report identifies this scope as
100
+ `operation_independent_require_fields`. All item selectors, including status,
101
+ still apply. `policy-check` without a proposed definition uses the same state-only
102
+ scope. Operation-scoped rules remain enforced during matching mutations; a clean
103
+ state report does not imply permission to perform every future operation.
104
+ To check a lifecycle field invariant in both state reports and mutations, select
105
+ its `statuses` without restricting `operations`.
106
+ Default validation includes this check; projects without declarations remain
107
+ unaffected.
108
+ An unreadable or malformed `schema/policies.json` produces an error-level
109
+ `completeness` result with a repair hint. Other requested validation checks still
110
+ run, and `--strict-exit` fails until the policy registry is repaired.
111
+
112
+ The published `@unbrained/pm-cli/sdk/governance` entrypoint exports
113
+ `runWorkflowPolicyAction`, `evaluateWorkflowPolicies`, policy types, parsers,
114
+ `WORKFLOW_POLICY_SCHEMA`, and `WORKFLOW_POLICY_DOCUMENT_SCHEMA`. `PmClient` exposes
115
+ `workflowPolicy(action, name, options)`. CLI `schema` and MCP `pm_schema` use the
116
+ same action vocabulary and SDK implementation; MCP options accept camelCase
117
+ keys and structured JSON definitions.
118
+
119
+ For repeated pure evaluation, `createWorkflowPolicyEvaluator(document)` returns
120
+ an evaluator that owns a private normalized policy snapshot and hashes each
121
+ declaration once. Later edits to the supplied document do not change that
122
+ snapshot; create another evaluator to adopt them. Completeness validation uses
123
+ one such snapshot for the whole supplied corpus.
124
+
125
+ The schema contract version is 4.16. Existing schema operations remain available;
126
+ `SchemaResult` now also includes `WorkflowPolicyActionResult`. Consumers that
127
+ exhaustively narrow schema results should handle the `policy_result: true`
128
+ discriminant. Validation options include `checkCompleteness`, and validation
129
+ results can contain a `completeness` check.
@@ -38,7 +38,7 @@ This file is generated by `listPmFlagLexicon()`. Compatibility aliases do not co
38
38
  | `history-compact` | evidence | 30 | 30 |
39
39
  | `history-author-acknowledge` | evidence | 28 | 28 |
40
40
  | `merge` | workspace | 27 | 27 |
41
- | `schema` | workspace | 40 | 40 |
41
+ | `schema` | workspace | 43 | 43 |
42
42
  | `profile` | workspace | 22 | 22 |
43
43
  | `activity` | context | 35 | 35 |
44
44
  | `restore` | intake | 23 | 23 |
@@ -60,7 +60,7 @@ This file is generated by `listPmFlagLexicon()`. Compatibility aliases do not co
60
60
  | `telemetry` | workspace | 21 | 21 |
61
61
  | `stats` | context | 29 | 29 |
62
62
  | `health` | workspace | 36 | 36 |
63
- | `validate` | quality | 45 | 45 |
63
+ | `validate` | quality | 46 | 46 |
64
64
  | `assurance` | quality | 30 | 30 |
65
65
  | `gc` | workspace | 22 | 22 |
66
66
  | `workspace` | workspace | 23 | 23 |
@@ -4,12 +4,12 @@ Tracker: `pm-f05lsg`.
4
4
 
5
5
  Every catalog code is listed. An `uncovered` row is an explicit closure obligation, never an omission or implied approval.
6
6
 
7
- - Catalog error codes: 365
7
+ - Catalog error codes: 371
8
8
  - Executable error codes: 19
9
9
  - Executable-code ratchet floor: 18
10
10
  - Required executable canonical codes: `bulk_ids_input_empty`, `bulk_ids_input_missing_path`, `bulk_ids_input_unreadable`, `invalid_argument_value`, `manifest_unknown_key`, `missing_lifecycle_target`, `missing_required_argument`, `no_version_bounds_declared`, `projection_options_mutually_exclusive`, `tracker_not_initialized`, `tracker_root_missing`, `tracker_root_not_directory`, `tracker_root_unreadable`, `unknown_context_intent`, `unknown_field_projection`, `unknown_option`, `unknown_subcommand`
11
- - Uncovered error codes: 346
12
- - Coverage fraction: 0.052055
11
+ - Uncovered error codes: 352
12
+ - Coverage fraction: 0.051213
13
13
  - Closed-domain probes: 19
14
14
  - Grammar probes: 97
15
15
 
@@ -365,6 +365,12 @@ Every catalog code is listed. An `uncovered` row is an explicit closure obligati
365
365
  | `vectorization_stale_items_remaining` | `vectorization_stale_items_remaining` | uncovered | none | 0 |
366
366
  | `visible_surface_ceiling_exceeded` | `visible_surface_ceiling_exceeded` | uncovered | none | 0 |
367
367
  | `workflow_duplicate_type` | `workflow_duplicate_type` | uncovered | none | 0 |
368
+ | `workflow_policy_approval_history_invalid` | `workflow_policy_approval_history_invalid` | uncovered | none | 0 |
369
+ | `workflow_policy_audit_failed` | `workflow_policy_audit_failed` | uncovered | none | 0 |
370
+ | `workflow_policy_input_unreadable` | `workflow_policy_input_unreadable` | uncovered | none | 0 |
371
+ | `workflow_policy_invalid` | `workflow_policy_invalid` | uncovered | none | 0 |
372
+ | `workflow_policy_refused` | `workflow_policy_refused` | uncovered | none | 0 |
373
+ | `workflow_policy_registry_invalid` | `workflow_policy_registry_invalid` | uncovered | none | 0 |
368
374
  | `workflow_status_unknown` | `workflow_status_unknown` | uncovered | none | 0 |
369
375
  | `workflow_transition_malformed` | `workflow_transition_malformed` | uncovered | none | 0 |
370
376
  | `workflow_type_empty` | `workflow_type_empty` | uncovered | none | 0 |
package/marketplace.json CHANGED
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Official marketplace for pm CLI — native git-based project management for Claude Code and AI coding agents.",
9
- "version": "2026.9.8"
9
+ "version": "2026.9.9"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "pm-claude",
14
14
  "source": "./plugins/pm-claude",
15
15
  "description": "Native pm CLI integration for Claude Code — 28 MCP tools, 5 workflow skills, 14 slash commands, 4 subagents, hybrid TUI task tracking, session context injection, and coordination subagents for git-based project management without leaving Claude Code.",
16
- "version": "2026.9.8",
16
+ "version": "2026.9.9",
17
17
  "author": {
18
18
  "name": "unbrained",
19
19
  "url": "https://github.com/unbraind/pm-cli"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-cli",
3
- "version": "2026.9.8",
3
+ "version": "2026.9.9",
4
4
  "description": "Git-native project management CLI for humans and agents.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.10.0",
@@ -119,7 +119,7 @@
119
119
  "lint:eslint": "eslint . --max-warnings=0",
120
120
  "lint:eslint:prune": "eslint . --prune-suppressions",
121
121
  "lint:complexity:baseline": "eslint . --suppress-rule complexity --suppress-rule sonarjs/cognitive-complexity",
122
- "lint:duplicates": "jscpd --config .jscpd.json && jscpd --config .jscpd.source.json",
122
+ "lint:duplicates": "jscpd --config .jscpd.json && jscpd --config .jscpd.source.json && jscpd --config .jscpd.tables.json",
123
123
  "lint:codefactor": "pnpm quality:static",
124
124
  "quality:static": "pnpm lint:eslint && pnpm lint:duplicates && pnpm build && node scripts/contracts-snapshot.mjs --check && node scripts/generate-agent-capability-surfaces.mjs --check && node scripts/generate-error-code-catalog.mjs --check && node scripts/release/repository-assurance.mjs repository-static-quality --trigger ci --json && node scripts/release/audit-package-boundary.mjs && node scripts/release/package-sdk-contract-parity.mjs && node scripts/release/surface-replication-gate.mjs && node scripts/release/command-grammar-gate.mjs && node scripts/release/flag-invocation-parity.mjs && node scripts/release/flag-lexicon-gate.mjs && node scripts/release/refusal-closure-gate.mjs && node scripts/release/agent-task-token-gate.mjs && pnpm quality:mcp-deprecations && node dist/cli.js assurance run tracker-context-quality --trigger ci --dry-run --json --output-budget unbounded && node scripts/release/gate-registry.mjs && node scripts/sdk-surface-snapshot.mjs --check && node scripts/bench/sdk-entrypoint-costs.mjs --check && node scripts/bench/cli-transport-floor.mjs --check && node dist/cli.js assurance run graph-composition --trigger ci --dry-run --json --output-budget unbounded && node dist/cli.js assurance run record-integrity --trigger ci --dry-run --json --output-budget unbounded",
125
125
  "quality:command-grammar": "pnpm build && node scripts/release/command-grammar-gate.mjs && node scripts/release/flag-invocation-parity.mjs && node scripts/release/flag-lexicon-gate.mjs",
@@ -220,7 +220,7 @@
220
220
  "@eslint/js": "^10.0.1",
221
221
  "@modelcontextprotocol/ext-apps": "1.7.5",
222
222
  "@sentry/cli": "^3.7.0",
223
- "@types/node": "^26.4.1",
223
+ "@types/node": "^26.5.0",
224
224
  "@types/npm-package-arg": "^6.1.4",
225
225
  "@vitest/coverage-v8": "^4.1.11",
226
226
  "esbuild": "0.28.2",
@@ -231,11 +231,11 @@
231
231
  "github-slugger": "2.0.0",
232
232
  "greptile": "^3.5.2",
233
233
  "jscpd": "^5.1.2",
234
- "marked": "18.0.11",
234
+ "marked": "18.0.12",
235
235
  "parse5": "8.0.1",
236
236
  "tsx": "^4.23.13",
237
237
  "typescript": "^6.0.3",
238
- "typescript-eslint": "^8.69.0",
238
+ "typescript-eslint": "^8.70.0",
239
239
  "vitest": "^4.1.11"
240
240
  }
241
241
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-beads",
3
- "version": "2026.9.8",
3
+ "version": "2026.9.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for importing Beads JSONL records.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-calendar",
3
- "version": "2026.9.8",
3
+ "version": "2026.9.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for calendar and agenda views.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-command-kit",
3
- "version": "2026.9.8",
3
+ "version": "2026.9.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm exemplar package for the commands capability: registerCommand, registerFlags, and registerParser.",