@unbrained/pm-cli 2026.10.9 → 2026.10.10

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 (82) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +19 -0
  3. package/dist/cli/register-mutation.js +2 -2
  4. package/dist/cli-bundle/bundle-manifest.json +115 -115
  5. package/dist/cli-bundle/chunks/{chunk-P5NS7EFC.js → chunk-2JYTSTQP.js} +2 -2
  6. package/dist/cli-bundle/chunks/{chunk-ZNXRGATT.js → chunk-BXSXT5Z6.js} +2 -2
  7. package/dist/cli-bundle/chunks/{chunk-QQHNXCO3.js → chunk-ESRACL6K.js} +2 -2
  8. package/dist/cli-bundle/chunks/{chunk-5MOFELIF.js → chunk-K2UTE75T.js} +2 -2
  9. package/dist/cli-bundle/chunks/{chunk-55MK3QQM.js → chunk-KNW3RSLS.js} +3 -3
  10. package/dist/cli-bundle/chunks/chunk-MEWBSWE3.js +2 -0
  11. package/dist/cli-bundle/chunks/{chunk-KOGF6O6W.js → chunk-OBLO45UM.js} +2 -2
  12. package/dist/cli-bundle/chunks/{chunk-XW3BLWFF.js → chunk-RBPOO46D.js} +2 -2
  13. package/dist/cli-bundle/chunks/{chunk-E4XR5LKU.js → chunk-RUT3CCAG.js} +2 -2
  14. package/dist/cli-bundle/chunks/{chunk-CPZOW6L2.js → chunk-S445XEYT.js} +2 -2
  15. package/dist/cli-bundle/chunks/{chunk-5NR6TDYB.js → chunk-SSY4OSJR.js} +2 -2
  16. package/dist/cli-bundle/chunks/{chunk-5TCQ5J3V.js → chunk-YMLF6QT7.js} +75 -75
  17. package/dist/cli-bundle/chunks/{chunk-NIF7JOPW.js → chunk-ZWRLAOA4.js} +2 -2
  18. package/dist/cli-bundle/chunks/{register-list-query-2YOWMNWY.js → register-list-query-T6BOR67M.js} +2 -2
  19. package/dist/cli-bundle/chunks/register-mutation-6NXR7SAJ.js +20 -0
  20. package/dist/cli-bundle/chunks/{register-operations-BMK65YM3.js → register-operations-PDOBQZUJ.js} +2 -2
  21. package/dist/cli-bundle/chunks/{register-setup-24TFFK6N.js → register-setup-MAYFPKBX.js} +2 -2
  22. package/dist/cli-bundle/focused-chunks/{chunk-362645PL.js → chunk-52UJEHEV.js} +2 -2
  23. package/dist/cli-bundle/focused-chunks/{chunk-EZ4367G4.js → chunk-EIOQNLBQ.js} +2 -2
  24. package/dist/cli-bundle/focused-chunks/{chunk-SAN3KPX4.js → chunk-GVGFUD52.js} +2 -2
  25. package/dist/cli-bundle/focused-chunks/{chunk-DSLNUL2Q.js → chunk-JGFSSR3G.js} +16 -16
  26. package/dist/cli-bundle/focused-chunks/{chunk-IARAEFMA.js → chunk-KNJR2WUO.js} +2 -2
  27. package/dist/cli-bundle/focused-chunks/chunk-MJQ253F3.js +2 -0
  28. package/dist/cli-bundle/focused-chunks/{chunk-MCVWZUGK.js → chunk-SIEGIPNT.js} +28 -28
  29. package/dist/cli-bundle/main.js +2 -2
  30. package/dist/cli-bundle/sdk-contracts.js +1 -1
  31. package/dist/cli-bundle/sdk-core.js +1 -1
  32. package/dist/cli-bundle/sdk-governance.js +1 -1
  33. package/dist/cli-bundle/sdk-graph.js +1 -1
  34. package/dist/cli-bundle/sdk-merge.js +51 -51
  35. package/dist/cli-bundle/sdk-query.js +1 -1
  36. package/dist/cli-bundle/sdk-runtime.js +1 -1
  37. package/dist/cli-bundle/sdk-testing.js +1 -1
  38. package/dist/cli-bundle/sdk.js +9 -9
  39. package/dist/core/item/item-format.d.ts +2 -0
  40. package/dist/core/item/item-format.js +3 -3
  41. package/dist/core/schema/runtime-field-values.d.ts +2 -2
  42. package/dist/core/schema/runtime-field-values.js +1 -1
  43. package/dist/core/store/item-format-migration.js +1 -1
  44. package/dist/core/store/item-store.js +1 -1
  45. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -1
  46. package/dist/sdk/governance/reliability.d.ts +74 -0
  47. package/dist/sdk/governance/reliability.js +2 -0
  48. package/dist/sdk/governance.d.ts +1 -0
  49. package/dist/sdk/governance.js +1 -1
  50. package/dist/sdk/public-surface.json +1 -1
  51. package/dist/sdk/structured-mutations.d.ts +5 -1
  52. package/dist/sdk/structured-mutations.js +1 -1
  53. package/docs/QUALITY_EVIDENCE.md +52 -1
  54. package/docs/RELEASE_RELIABILITY.md +54 -3
  55. package/docs/SDK.md +16 -2
  56. package/docs/TESTING.md +36 -0
  57. package/docs/agent-task-token-baseline.json +15 -14
  58. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +4 -3
  59. package/marketplace.json +2 -2
  60. package/package.json +1 -1
  61. package/packages/pm-beads/package.json +1 -1
  62. package/packages/pm-calendar/package.json +1 -1
  63. package/packages/pm-command-kit/package.json +1 -1
  64. package/packages/pm-digital-twin/package.json +1 -1
  65. package/packages/pm-governance-audit/package.json +1 -1
  66. package/packages/pm-guide-shell/package.json +1 -1
  67. package/packages/pm-kanban/package.json +1 -1
  68. package/packages/pm-lifecycle-hooks/package.json +1 -1
  69. package/packages/pm-linked-test-adapters/package.json +1 -1
  70. package/packages/pm-search-advanced/package.json +1 -1
  71. package/packages/pm-templates/package.json +1 -1
  72. package/packages/pm-todos/package.json +1 -1
  73. package/packages/pm-vcs/package.json +1 -1
  74. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  75. package/plugins/pm-claude/package.json +2 -2
  76. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  77. package/plugins/pm-codex/package.json +2 -2
  78. package/plugins/pm-codex/plugin.json +1 -1
  79. package/runtime-dependencies.json +2 -2
  80. package/dist/cli-bundle/chunks/chunk-N7GKWO4G.js +0 -2
  81. package/dist/cli-bundle/chunks/register-mutation-ILOWFMTV.js +0 -20
  82. package/dist/cli-bundle/focused-chunks/chunk-2ATAD56F.js +0 -2
@@ -1,4 +1,5 @@
1
1
  import type { BulkItemMutation } from "./item-transaction.js";
2
+ import { type RuntimeFieldDefinitionResolved } from "../core/schema/runtime-schema.js";
2
3
  /** Controls for resolving one versioned mutation document. */
3
4
  export interface ResolveItemMutationDocumentOptions {
4
5
  /** Stable transaction identity used to derive replay-safe create ids. */
@@ -38,7 +39,10 @@ export declare function resolveItemMutationDocument(input: string, options: Reso
38
39
  /**
39
40
  * Convert a direct item JSON document or `pm get --json` envelope into the
40
41
  * create/update option shape. Explicit CLI flags override document values.
42
+ * Supply the workspace's resolved schema fields to route metadata keys through
43
+ * declared option names and aliases. Extension assignments retain raw strings
44
+ * and JSON containers, with explicit assignments applied after document values.
41
45
  */
42
- export declare function itemDocumentToMutationOptions(input: string, mode: "create" | "update", explicitOptions?: Record<string, unknown>): Record<string, unknown>;
46
+ export declare function itemDocumentToMutationOptions(input: string, mode: "create" | "update", explicitOptions?: Record<string, unknown>, runtimeFields?: readonly RuntimeFieldDefinitionResolved[]): Record<string, unknown>;
43
47
  /** Validate an already-decoded MCP mutation array through the same strict parser as CLI stdin. */
44
48
  export declare function validateItemMutationRows(value: unknown): BulkItemMutation[];
@@ -1,2 +1,2 @@
1
- (function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="62e0ef49-30c8-56e0-985e-49c83d14695e")}catch{}})();import crypto from"node:crypto";import{normalizeItemId,normalizePrefix}from"../core/item/id.js";import{preserveMutationStdinTokenLiterals}from"../core/item/parse.js";import{EXIT_CODE,ITEM_PROJECT_CONTEXT_KEYS}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{levenshteinDistanceWithinLimit}from"../core/shared/levenshtein.js";import{CLOSE_FLAG_CONTRACTS,RELEASE_FLAG_CONTRACTS}from"./cli-contracts/flag-contracts.js";import{CREATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS,UPDATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS}from"./cli-contracts/commander-mutation-options.js";const MUTATION_ROW_KEYS=["op","id","reason","options"],REFERENCED_MUTATION_ROW_KEYS=[...MUTATION_ROW_KEYS,"ref"],MUTATION_DOCUMENT_KEYS=["schema_version","mutations"],MUTATION_REFERENCE_SOURCE="[a-z][a-z0-9._-]{0,63}",MUTATION_REFERENCE_PATTERN=new RegExp(`^${MUTATION_REFERENCE_SOURCE}$`,"u"),MUTATION_DEPENDENCY_REFERENCE_PATTERN=new RegExp(`(^|,)id=@(${MUTATION_REFERENCE_SOURCE})(?=,|$)`,"gu"),flagToOptionKey=flag=>flag.slice(2).replaceAll(/-([a-z])/gu,(_match,letter)=>letter.toUpperCase()),ITEM_ENVELOPE_KEYS=["item","linked","schedule","claim_state","children","omission_receipt","row_contract","context_intent"],READ_ONLY_ITEM_KEYS=new Set(["created_at","updated_at","closed_at","version","format_version","path","author","collection_counts","notes_count","tests_count"]),UPDATE_READ_ONLY_ITEM_KEYS=new Set([...READ_ONLY_ITEM_KEYS,"completed_at","id"]),ITEM_FIELD_KEYS=new Set(["id","title","description","type","status","priority","tags","body","deadline","estimated_minutes","acceptance_criteria","definition_of_ready","order","rank","assignee",...ITEM_PROJECT_CONTEXT_KEYS,"affected_version","fixed_version","component","regression","customer_impact","close_reason","completed_at","dependencies","comments","notes","learnings","files","tests","docs","reminders","events","type_options"]),MUTATION_OPTION_KEYS={create:CREATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS.map(contract=>contract.target),update:UPDATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS.map(contract=>contract.target),close:CLOSE_FLAG_CONTRACTS.map(contract=>flagToOptionKey(contract.flag)).filter(key=>key!=="reason"),release:RELEASE_FLAG_CONTRACTS.map(contract=>flagToOptionKey(contract.flag))};function parseAtomicMutationControls(value){const controls={},compensation=value.createCompensation;if(compensation!==void 0){if(compensation!=="close"&&compensation!=="delete")throw new PmCliError("createCompensation must be close or delete.",EXIT_CODE.USAGE);controls.createCompensation=compensation}for(const key of["lockTtlSeconds","lockWaitMs"]){const raw=value[key];if(raw===void 0)continue;const parsed=typeof raw=="number"||typeof raw=="string"&&raw.trim().length>0?Number(raw):Number.NaN;if(!Number.isSafeInteger(parsed)||parsed<=0)throw new PmCliError(`${key} must be a positive safe integer.`,EXIT_CODE.USAGE);controls[key]=parsed}return controls}function isPlainObject(value){if(typeof value!="object"||value===null||Array.isArray(value))return!1;const prototype=Object.getPrototypeOf(value);return prototype===Object.prototype||prototype===null}function nearestKey(key,candidates){const limit=Math.max(1,Math.min(3,Math.floor(key.length/4)+1));let best;for(const candidate of candidates){const distance=levenshteinDistanceWithinLimit(key,candidate,limit);distance!==null&&(best===void 0||distance<best.distance)&&(best={key:candidate,distance})}return best?.key}function unknownKeyError(scope,key,candidates){const suggestion=nearestKey(key,candidates);return new PmCliError(suggestion===void 0?`${scope} does not recognize key "${key}". Allowed keys: ${candidates.join(", ")}.`:`${scope} does not recognize key "${key}". Did you mean "${suggestion}"?`,EXIT_CODE.USAGE,{code:"stdin_json_unknown_key",required:`Use documented ${scope} keys only.`,why:"Strict structured input prevents silent typos from creating incomplete or unintended mutations.",nextSteps:[suggestion===void 0?`Remove or correct "${key}".`:`Replace "${key}" with "${suggestion}".`]})}function parseJsonValue(input,label){try{return JSON.parse(input)}catch(error){const detail=error instanceof Error?error.message:String(error);throw new PmCliError(`${label} must be valid JSON: ${detail}`,EXIT_CODE.USAGE,{code:"stdin_json_invalid",required:`Pipe one valid JSON ${label.toLowerCase()} into stdin.`,why:"Structured mutation input must parse deterministically before any tracker write begins."})}}function validateMutationObject(value,index){if(!isPlainObject(value))throw new PmCliError(`Mutation ${index+1} must be an object.`,EXIT_CODE.USAGE);for(const key of Object.keys(value))if(!MUTATION_ROW_KEYS.includes(key))throw unknownKeyError(`mutation ${index+1}`,key,MUTATION_ROW_KEYS);return value}function validateMutationOptions(options,op,index){if(options!==void 0){if(!isPlainObject(options))throw new PmCliError(`Mutation ${index+1} options must be an object.`,EXIT_CODE.USAGE);for(const key of Object.keys(options))if(!MUTATION_OPTION_KEYS[op].includes(key))throw unknownKeyError(`mutation ${index+1} ${op} options`,key,MUTATION_OPTION_KEYS[op]);return options}}function validateMutationRow(value,index){const mutation=validateMutationObject(value,index),{op,id,reason}=mutation;if(op!=="create"&&op!=="update"&&op!=="close"&&op!=="release")throw new PmCliError(`Mutation ${index+1} op must be create, update, close, or release.`,EXIT_CODE.USAGE);const options=validateMutationOptions(mutation.options,op,index);if(typeof id!="string"||id.trim().length===0)throw new PmCliError(`Mutation ${index+1} requires a non-empty id.`,EXIT_CODE.USAGE);const normalizedId=id.trim();if(op==="close"){if(typeof reason!="string"||reason.trim().length===0)throw new PmCliError(`Mutation ${index+1} close requires a non-empty reason.`,EXIT_CODE.USAGE);return{op,id:normalizedId,reason:reason.trim(),...options===void 0?{}:{options}}}if(op==="release")return{op,id:normalizedId,...options===void 0?{}:{options}};if(!isPlainObject(options))throw new PmCliError(`Mutation ${index+1} ${op} requires an options object.`,EXIT_CODE.USAGE);return{op,id:normalizedId,options}}function parseItemMutationBatch(input){const parsed=parseJsonValue(input,"Mutation batch"),rows=Array.isArray(parsed)?parsed:isPlainObject(parsed)&&Array.isArray(parsed.mutations)?parsed.mutations:void 0;if(rows===void 0||rows.length===0)throw new PmCliError("Mutation batch must be a non-empty JSON array or an object with a non-empty mutations array.",EXIT_CODE.USAGE);return rows.map((row,index)=>{const mutation=validateMutationRow(row,index);return mutation.options!==void 0&&preserveMutationStdinTokenLiterals(mutation.options),mutation})}function deriveReferencedItemId(transactionId,reference,idPrefix){const digest=crypto.createHash("sha256").update(transactionId).update("\0").update(reference).digest("hex"),token=BigInt(`0x${digest.slice(0,16)}`).toString(36).padStart(13,"0").slice(-12);return`${normalizePrefix(idPrefix)}${token}`}function replaceExactMutationReference(value,references){if(typeof value!="string"||!value.startsWith("@"))return value;const reference=value.slice(1),resolved=references[reference];if(resolved===void 0)throw new PmCliError(`Unknown mutation reference "${value}".`,EXIT_CODE.USAGE);return resolved}function replaceDependencyReference(value,references){if(typeof value!="string")return value;const resolvedValue=value.replace(MUTATION_DEPENDENCY_REFERENCE_PATTERN,(_match,prefix,reference)=>{const resolved=references[reference];if(resolved===void 0)throw new PmCliError(`Unknown mutation reference "@${reference}".`,EXIT_CODE.USAGE);return`${prefix}id=${resolved}`});if(/(^|,)id=@/u.test(resolvedValue))throw new PmCliError(`Malformed mutation reference in dependency "${value}".`,EXIT_CODE.USAGE);return resolvedValue}function referencedAliases(value){return typeof value!="string"?[]:value.startsWith("@")?[value.slice(1)]:[...value.matchAll(MUTATION_DEPENDENCY_REFERENCE_PATTERN)].map(match=>match[1]).filter(reference=>reference!==void 0)}function assertAcyclicCreateReferences(rows){const graph=new Map;for(const row of rows){if(row.op!=="create"||typeof row.ref!="string")continue;const mutationOptions=isPlainObject(row.options)?row.options:{},dependencies=Array.isArray(mutationOptions.dep)?mutationOptions.dep:mutationOptions.dep===void 0?[]:[mutationOptions.dep];graph.set(row.ref,[mutationOptions.parent,mutationOptions.blockedBy,...dependencies].flatMap(referencedAliases))}const visiting=new Set,visited=new Set,visit=reference=>{if(visiting.has(reference))throw new PmCliError(`Mutation reference cycle includes "@${reference}".`,EXIT_CODE.USAGE);if(!visited.has(reference)){visiting.add(reference);for(const dependency of graph.get(reference))graph.has(dependency)&&visit(dependency);visiting.delete(reference),visited.add(reference)}};for(const reference of graph.keys())visit(reference)}function parseMutationDocumentRows(input){const parsed=parseJsonValue(input,"Mutation document"),envelope=Array.isArray(parsed)?{schema_version:1,mutations:parsed}:parsed;if(!isPlainObject(envelope))throw new PmCliError("Mutation document must be a JSON array or object.",EXIT_CODE.USAGE);for(const key of Object.keys(envelope))if(!MUTATION_DOCUMENT_KEYS.includes(key))throw unknownKeyError("mutation document",key,MUTATION_DOCUMENT_KEYS);if(envelope.schema_version!==void 0&&envelope.schema_version!==1)throw new PmCliError("Mutation document schema_version must be 1.",EXIT_CODE.USAGE);if(!Array.isArray(envelope.mutations)||envelope.mutations.length===0)throw new PmCliError("Mutation document requires a non-empty mutations array.",EXIT_CODE.USAGE);return envelope.mutations.map((row,index)=>{if(!isPlainObject(row))throw new PmCliError(`Mutation ${index+1} must be an object.`,EXIT_CODE.USAGE);for(const key of Object.keys(row))if(!REFERENCED_MUTATION_ROW_KEYS.includes(key))throw unknownKeyError(`mutation ${index+1}`,key,REFERENCED_MUTATION_ROW_KEYS);return{...row}})}function collectMutationReferences(rows,options){const references=Object.create(null);for(const[index,row]of rows.entries())if(row.ref!==void 0){if(row.op!=="create")throw new PmCliError(`Mutation ${index+1} ref is only valid for create operations.`,EXIT_CODE.USAGE);if(typeof row.ref!="string"||!MUTATION_REFERENCE_PATTERN.test(row.ref))throw new PmCliError(`Mutation ${index+1} ref must match ${MUTATION_REFERENCE_PATTERN.source}.`,EXIT_CODE.USAGE);if(references[row.ref]!==void 0)throw new PmCliError(`Duplicate mutation ref "${row.ref}".`,EXIT_CODE.USAGE);references[row.ref]=typeof row.id=="string"&&row.id.trim().length>0?normalizeItemId(row.id,options.idPrefix):deriveReferencedItemId(options.transactionId,row.ref,options.idPrefix)}return references}function resolveItemMutationDocument(input,options){const rows=parseMutationDocumentRows(input),references=collectMutationReferences(rows,options);assertAcyclicCreateReferences(rows);const resolvedRows=rows.map(row=>{const resolved={...row},reference=typeof resolved.ref=="string"?resolved.ref:void 0;if(delete resolved.ref,reference!==void 0?resolved.id=references[reference]:resolved.id=replaceExactMutationReference(resolved.id,references),isPlainObject(resolved.options)){const mutationOptions={...resolved.options};for(const key of["parent","blockedBy"])mutationOptions[key]!==void 0&&(mutationOptions[key]=replaceExactMutationReference(mutationOptions[key],references));Array.isArray(mutationOptions.dep)?mutationOptions.dep=mutationOptions.dep.map(entry=>replaceDependencyReference(entry,references)):mutationOptions.dep!==void 0&&(mutationOptions.dep=replaceDependencyReference(mutationOptions.dep,references)),resolved.options=mutationOptions}return resolved}),mutations=parseItemMutationBatch(JSON.stringify(resolvedRows)),createIds=mutations.filter(mutation=>mutation.op==="create").map(mutation=>mutation.id);if(new Set(createIds).size!==createIds.length)throw new PmCliError("Duplicate resolved create id in mutation document.",EXIT_CODE.USAGE);return{schema_version:1,mutations,references}}function serializePairs(value,keys){return keys.filter(key=>value[key]!==void 0&&value[key]!==null).map(key=>{const rendered=String(value[key]);return`${key}=${/[",\n\r\\]/u.test(rendered)?JSON.stringify(rendered):rendered}`}).join(",")}function serializeObjectArray(value,keys,label){if(value!==void 0){if(!Array.isArray(value)||!value.every(isPlainObject))throw new PmCliError(`${label} must be an array of objects.`,EXIT_CODE.USAGE);return value.map(entry=>serializePairs(entry,keys))}}function scalarItemOption(key,value){return key==="tags"&&Array.isArray(value)&&value.every(entry=>typeof entry=="string")?JSON.stringify(value):typeof value=="number"||typeof value=="boolean"?String(value):value}function appendFacetOptions(target,source,skipPersistedAnnotations=!1){const mappings=[["dependencies","dep",["id","kind","created_at","author","source_kind"]],["comments","comment",["text","created_at","author"]],["notes","note",["text","created_at","author"]],["learnings","learning",["text","created_at","author"]],["files","file",["path","scope","note"]],["tests","test",["command","scope","timeout_seconds"]],["docs","doc",["path","scope","note"]],["reminders","reminder",["at","text","created_at"]],["events","event",["start_at","end_at","title","location"]],["type_options","typeOption",["key","value"]]];for(const[sourceKey,targetKey,keys]of mappings){const sourceValue=source[sourceKey],serialized=serializeObjectArray(skipPersistedAnnotations&&["comments","notes","learnings"].includes(sourceKey)&&Array.isArray(sourceValue)?sourceValue.filter(entry=>!isPlainObject(entry)||entry.created_at===void 0):sourceValue,keys,sourceKey);serialized!==void 0&&serialized.length>0&&(target[targetKey]=serialized)}}function validateItemDocumentKeys(parsed,envelope){for(const key of Object.keys(parsed)){if(envelope){if(!ITEM_ENVELOPE_KEYS.includes(key))throw unknownKeyError("item envelope",key,ITEM_ENVELOPE_KEYS);continue}if(!ITEM_FIELD_KEYS.has(key)&&nearestKey(key,[...ITEM_FIELD_KEYS])===void 0&&nearestKey(key,ITEM_ENVELOPE_KEYS)==="item")throw unknownKeyError("item envelope",key,ITEM_ENVELOPE_KEYS)}}function resolveItemDocument(parsed){const envelope=Object.hasOwn(parsed,"item");validateItemDocumentKeys(parsed,envelope);const item=envelope?parsed.item:parsed;if(!isPlainObject(item))throw new PmCliError("Item document item must be an object.",EXIT_CODE.USAGE);return{item,...envelope&&isPlainObject(parsed.linked)?{linked:parsed.linked}:{}}}function appendItemDocumentEntry(documentOptions,key,value,mode){if(!(mode==="update"?UPDATE_READ_ONLY_ITEM_KEYS:READ_ONLY_ITEM_KEYS).has(key)){if(!ITEM_FIELD_KEYS.has(key)){if(nearestKey(key,[...ITEM_FIELD_KEYS])!==void 0)throw unknownKeyError("item document",key,[...ITEM_FIELD_KEYS]);const fields=Array.isArray(documentOptions.field)?documentOptions.field:[];fields.push(serializePairs({key,value:typeof value=="string"?value:JSON.stringify(value)},["key","value"])),documentOptions.field=fields;return}["dependencies","comments","notes","learnings","files","tests","docs","reminders","events","type_options"].includes(key)||(documentOptions[key.replaceAll(/_([a-z])/gu,(_match,letter)=>letter.toUpperCase())]=scalarItemOption(key,value))}}function itemDocumentToMutationOptions(input,mode,explicitOptions={}){const parsed=parseJsonValue(input,"Item document");if(!isPlainObject(parsed))throw new PmCliError("Item document must be a JSON object.",EXIT_CODE.USAGE);const{item,linked}=resolveItemDocument(parsed),documentOptions={};for(const[key,value]of Object.entries(item))appendItemDocumentEntry(documentOptions,key,value,mode);if(appendFacetOptions(documentOptions,item,mode==="update"),linked!==void 0&&appendFacetOptions(documentOptions,linked),mode==="update"){const annotations=Object.fromEntries(["comments","notes","learnings"].filter(key=>Object.hasOwn(item,key)).map(key=>[key,item[key].filter(entry=>isPlainObject(entry)&&entry.created_at!==void 0)]));Object.keys(annotations).length>0&&(documentOptions.documentAnnotationSnapshot=annotations)}return{...documentOptions,...Object.fromEntries(Object.entries(explicitOptions).filter(([,value])=>value!==void 0)),stdinJson:void 0}}function validateItemMutationRows(value){return parseItemMutationBatch(JSON.stringify(value))}export{itemDocumentToMutationOptions,parseAtomicMutationControls,parseItemMutationBatch,resolveItemMutationDocument,validateItemMutationRows};
1
+ (function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ccd80f77-f619-5cd8-a792-cc5d9c733ebf")}catch{}})();import crypto from"node:crypto";import{normalizeItemId,normalizePrefix}from"../core/item/id.js";import{preserveMutationStdinTokenLiterals}from"../core/item/parse.js";import{EXIT_CODE,ITEM_PROJECT_CONTEXT_KEYS}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{levenshteinDistanceWithinLimit}from"../core/shared/levenshtein.js";import{CLOSE_FLAG_CONTRACTS,RELEASE_FLAG_CONTRACTS}from"./cli-contracts/flag-contracts.js";import{CREATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS,UPDATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS}from"./cli-contracts/commander-mutation-options.js";import{runtimeFieldOptionTarget}from"../core/schema/runtime-schema.js";import{readRuntimeFieldOptionValue}from"../core/schema/runtime-field-values.js";const MUTATION_ROW_KEYS=["op","id","reason","options"],REFERENCED_MUTATION_ROW_KEYS=[...MUTATION_ROW_KEYS,"ref"],MUTATION_DOCUMENT_KEYS=["schema_version","mutations"],MUTATION_REFERENCE_SOURCE="[a-z][a-z0-9._-]{0,63}",MUTATION_REFERENCE_PATTERN=new RegExp(`^${MUTATION_REFERENCE_SOURCE}$`,"u"),MUTATION_DEPENDENCY_REFERENCE_PATTERN=new RegExp(`(^|,)id=@(${MUTATION_REFERENCE_SOURCE})(?=,|$)`,"gu"),flagToOptionKey=flag=>flag.slice(2).replaceAll(/-([a-z])/gu,(_match,letter)=>letter.toUpperCase()),ITEM_ENVELOPE_KEYS=["item","linked","schedule","claim_state","children","omission_receipt","row_contract","context_intent"],READ_ONLY_ITEM_KEYS=new Set(["created_at","updated_at","closed_at","version","format_version","path","author","collection_counts","notes_count","tests_count"]),UPDATE_READ_ONLY_ITEM_KEYS=new Set([...READ_ONLY_ITEM_KEYS,"completed_at","id"]),ITEM_FIELD_KEYS=new Set(["id","title","description","type","status","priority","tags","body","deadline","estimated_minutes","acceptance_criteria","definition_of_ready","order","rank","assignee",...ITEM_PROJECT_CONTEXT_KEYS,"affected_version","fixed_version","component","regression","customer_impact","close_reason","completed_at","dependencies","comments","notes","learnings","files","tests","docs","reminders","events","type_options"]),MUTATION_OPTION_KEYS={create:CREATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS.map(contract=>contract.target),update:UPDATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS.map(contract=>contract.target),close:CLOSE_FLAG_CONTRACTS.map(contract=>flagToOptionKey(contract.flag)).filter(key=>key!=="reason"),release:RELEASE_FLAG_CONTRACTS.map(contract=>flagToOptionKey(contract.flag))};function parseAtomicMutationControls(value){const controls={},compensation=value.createCompensation;if(compensation!==void 0){if(compensation!=="close"&&compensation!=="delete")throw new PmCliError("createCompensation must be close or delete.",EXIT_CODE.USAGE);controls.createCompensation=compensation}for(const key of["lockTtlSeconds","lockWaitMs"]){const raw=value[key];if(raw===void 0)continue;const parsed=typeof raw=="number"||typeof raw=="string"&&raw.trim().length>0?Number(raw):Number.NaN;if(!Number.isSafeInteger(parsed)||parsed<=0)throw new PmCliError(`${key} must be a positive safe integer.`,EXIT_CODE.USAGE);controls[key]=parsed}return controls}function isPlainObject(value){if(typeof value!="object"||value===null||Array.isArray(value))return!1;const prototype=Object.getPrototypeOf(value);return prototype===Object.prototype||prototype===null}function nearestKey(key,candidates){const limit=Math.max(1,Math.min(3,Math.floor(key.length/4)+1));let best;for(const candidate of candidates){const distance=levenshteinDistanceWithinLimit(key,candidate,limit);distance!==null&&(best===void 0||distance<best.distance)&&(best={key:candidate,distance})}return best?.key}function unknownKeyError(scope,key,candidates){const suggestion=nearestKey(key,candidates);return new PmCliError(suggestion===void 0?`${scope} does not recognize key "${key}". Allowed keys: ${candidates.join(", ")}.`:`${scope} does not recognize key "${key}". Did you mean "${suggestion}"?`,EXIT_CODE.USAGE,{code:"stdin_json_unknown_key",required:`Use documented ${scope} keys only.`,why:"Strict structured input prevents silent typos from creating incomplete or unintended mutations.",nextSteps:[suggestion===void 0?`Remove or correct "${key}".`:`Replace "${key}" with "${suggestion}".`]})}function parseJsonValue(input,label){try{return JSON.parse(input)}catch(error){const detail=error instanceof Error?error.message:String(error);throw new PmCliError(`${label} must be valid JSON: ${detail}`,EXIT_CODE.USAGE,{code:"stdin_json_invalid",required:`Pipe one valid JSON ${label.toLowerCase()} into stdin.`,why:"Structured mutation input must parse deterministically before any tracker write begins."})}}function validateMutationObject(value,index){if(!isPlainObject(value))throw new PmCliError(`Mutation ${index+1} must be an object.`,EXIT_CODE.USAGE);for(const key of Object.keys(value))if(!MUTATION_ROW_KEYS.includes(key))throw unknownKeyError(`mutation ${index+1}`,key,MUTATION_ROW_KEYS);return value}function validateMutationOptions(options,op,index){if(options!==void 0){if(!isPlainObject(options))throw new PmCliError(`Mutation ${index+1} options must be an object.`,EXIT_CODE.USAGE);for(const key of Object.keys(options))if(!MUTATION_OPTION_KEYS[op].includes(key))throw unknownKeyError(`mutation ${index+1} ${op} options`,key,MUTATION_OPTION_KEYS[op]);return options}}function validateMutationRow(value,index){const mutation=validateMutationObject(value,index),{op,id,reason}=mutation;if(op!=="create"&&op!=="update"&&op!=="close"&&op!=="release")throw new PmCliError(`Mutation ${index+1} op must be create, update, close, or release.`,EXIT_CODE.USAGE);const options=validateMutationOptions(mutation.options,op,index);if(typeof id!="string"||id.trim().length===0)throw new PmCliError(`Mutation ${index+1} requires a non-empty id.`,EXIT_CODE.USAGE);const normalizedId=id.trim();if(op==="close"){if(typeof reason!="string"||reason.trim().length===0)throw new PmCliError(`Mutation ${index+1} close requires a non-empty reason.`,EXIT_CODE.USAGE);return{op,id:normalizedId,reason:reason.trim(),...options===void 0?{}:{options}}}if(op==="release")return{op,id:normalizedId,...options===void 0?{}:{options}};if(!isPlainObject(options))throw new PmCliError(`Mutation ${index+1} ${op} requires an options object.`,EXIT_CODE.USAGE);return{op,id:normalizedId,options}}function parseItemMutationBatch(input){const parsed=parseJsonValue(input,"Mutation batch"),rows=Array.isArray(parsed)?parsed:isPlainObject(parsed)&&Array.isArray(parsed.mutations)?parsed.mutations:void 0;if(rows===void 0||rows.length===0)throw new PmCliError("Mutation batch must be a non-empty JSON array or an object with a non-empty mutations array.",EXIT_CODE.USAGE);return rows.map((row,index)=>{const mutation=validateMutationRow(row,index);return mutation.options!==void 0&&preserveMutationStdinTokenLiterals(mutation.options),mutation})}function deriveReferencedItemId(transactionId,reference,idPrefix){const digest=crypto.createHash("sha256").update(transactionId).update("\0").update(reference).digest("hex"),token=BigInt(`0x${digest.slice(0,16)}`).toString(36).padStart(13,"0").slice(-12);return`${normalizePrefix(idPrefix)}${token}`}function replaceExactMutationReference(value,references){if(typeof value!="string"||!value.startsWith("@"))return value;const reference=value.slice(1),resolved=references[reference];if(resolved===void 0)throw new PmCliError(`Unknown mutation reference "${value}".`,EXIT_CODE.USAGE);return resolved}function replaceDependencyReference(value,references){if(typeof value!="string")return value;const resolvedValue=value.replace(MUTATION_DEPENDENCY_REFERENCE_PATTERN,(_match,prefix,reference)=>{const resolved=references[reference];if(resolved===void 0)throw new PmCliError(`Unknown mutation reference "@${reference}".`,EXIT_CODE.USAGE);return`${prefix}id=${resolved}`});if(/(^|,)id=@/u.test(resolvedValue))throw new PmCliError(`Malformed mutation reference in dependency "${value}".`,EXIT_CODE.USAGE);return resolvedValue}function referencedAliases(value){return typeof value!="string"?[]:value.startsWith("@")?[value.slice(1)]:[...value.matchAll(MUTATION_DEPENDENCY_REFERENCE_PATTERN)].map(match=>match[1]).filter(reference=>reference!==void 0)}function assertAcyclicCreateReferences(rows){const graph=new Map;for(const row of rows){if(row.op!=="create"||typeof row.ref!="string")continue;const mutationOptions=isPlainObject(row.options)?row.options:{},dependencies=Array.isArray(mutationOptions.dep)?mutationOptions.dep:mutationOptions.dep===void 0?[]:[mutationOptions.dep];graph.set(row.ref,[mutationOptions.parent,mutationOptions.blockedBy,...dependencies].flatMap(referencedAliases))}const visiting=new Set,visited=new Set,visit=reference=>{if(visiting.has(reference))throw new PmCliError(`Mutation reference cycle includes "@${reference}".`,EXIT_CODE.USAGE);if(!visited.has(reference)){visiting.add(reference);for(const dependency of graph.get(reference))graph.has(dependency)&&visit(dependency);visiting.delete(reference),visited.add(reference)}};for(const reference of graph.keys())visit(reference)}function parseMutationDocumentRows(input){const parsed=parseJsonValue(input,"Mutation document"),envelope=Array.isArray(parsed)?{schema_version:1,mutations:parsed}:parsed;if(!isPlainObject(envelope))throw new PmCliError("Mutation document must be a JSON array or object.",EXIT_CODE.USAGE);for(const key of Object.keys(envelope))if(!MUTATION_DOCUMENT_KEYS.includes(key))throw unknownKeyError("mutation document",key,MUTATION_DOCUMENT_KEYS);if(envelope.schema_version!==void 0&&envelope.schema_version!==1)throw new PmCliError("Mutation document schema_version must be 1.",EXIT_CODE.USAGE);if(!Array.isArray(envelope.mutations)||envelope.mutations.length===0)throw new PmCliError("Mutation document requires a non-empty mutations array.",EXIT_CODE.USAGE);return envelope.mutations.map((row,index)=>{if(!isPlainObject(row))throw new PmCliError(`Mutation ${index+1} must be an object.`,EXIT_CODE.USAGE);for(const key of Object.keys(row))if(!REFERENCED_MUTATION_ROW_KEYS.includes(key))throw unknownKeyError(`mutation ${index+1}`,key,REFERENCED_MUTATION_ROW_KEYS);return{...row}})}function collectMutationReferences(rows,options){const references=Object.create(null);for(const[index,row]of rows.entries())if(row.ref!==void 0){if(row.op!=="create")throw new PmCliError(`Mutation ${index+1} ref is only valid for create operations.`,EXIT_CODE.USAGE);if(typeof row.ref!="string"||!MUTATION_REFERENCE_PATTERN.test(row.ref))throw new PmCliError(`Mutation ${index+1} ref must match ${MUTATION_REFERENCE_PATTERN.source}.`,EXIT_CODE.USAGE);if(references[row.ref]!==void 0)throw new PmCliError(`Duplicate mutation ref "${row.ref}".`,EXIT_CODE.USAGE);references[row.ref]=typeof row.id=="string"&&row.id.trim().length>0?normalizeItemId(row.id,options.idPrefix):deriveReferencedItemId(options.transactionId,row.ref,options.idPrefix)}return references}function resolveItemMutationDocument(input,options){const rows=parseMutationDocumentRows(input),references=collectMutationReferences(rows,options);assertAcyclicCreateReferences(rows);const resolvedRows=rows.map(row=>{const resolved={...row},reference=typeof resolved.ref=="string"?resolved.ref:void 0;if(delete resolved.ref,reference!==void 0?resolved.id=references[reference]:resolved.id=replaceExactMutationReference(resolved.id,references),isPlainObject(resolved.options)){const mutationOptions={...resolved.options};for(const key of["parent","blockedBy"])mutationOptions[key]!==void 0&&(mutationOptions[key]=replaceExactMutationReference(mutationOptions[key],references));Array.isArray(mutationOptions.dep)?mutationOptions.dep=mutationOptions.dep.map(entry=>replaceDependencyReference(entry,references)):mutationOptions.dep!==void 0&&(mutationOptions.dep=replaceDependencyReference(mutationOptions.dep,references)),resolved.options=mutationOptions}return resolved}),mutations=parseItemMutationBatch(JSON.stringify(resolvedRows)),createIds=mutations.filter(mutation=>mutation.op==="create").map(mutation=>mutation.id);if(new Set(createIds).size!==createIds.length)throw new PmCliError("Duplicate resolved create id in mutation document.",EXIT_CODE.USAGE);return{schema_version:1,mutations,references}}function serializePairs(value,keys){return keys.filter(key=>value[key]!==void 0&&value[key]!==null).map(key=>{const rendered=String(value[key]);return`${key}=${/[",\n\r\\]/u.test(rendered)?JSON.stringify(rendered):rendered}`}).join(",")}function serializeObjectArray(value,keys,label){if(value!==void 0){if(!Array.isArray(value)||!value.every(isPlainObject))throw new PmCliError(`${label} must be an array of objects.`,EXIT_CODE.USAGE);return value.map(entry=>serializePairs(entry,keys))}}function scalarItemOption(key,value){return key==="tags"&&Array.isArray(value)&&value.every(entry=>typeof entry=="string")?JSON.stringify(value):typeof value=="number"||typeof value=="boolean"?String(value):value}function appendFacetOptions(target,source,skipPersistedAnnotations=!1){const mappings=[["dependencies","dep",["id","kind","created_at","author","source_kind"]],["comments","comment",["text","created_at","author"]],["notes","note",["text","created_at","author"]],["learnings","learning",["text","created_at","author"]],["files","file",["path","scope","note"]],["tests","test",["command","scope","timeout_seconds"]],["docs","doc",["path","scope","note"]],["reminders","reminder",["at","text","created_at"]],["events","event",["start_at","end_at","title","location"]],["type_options","typeOption",["key","value"]]];for(const[sourceKey,targetKey,keys]of mappings){const sourceValue=source[sourceKey],serialized=serializeObjectArray(skipPersistedAnnotations&&["comments","notes","learnings"].includes(sourceKey)&&Array.isArray(sourceValue)?sourceValue.filter(entry=>!isPlainObject(entry)||entry.created_at===void 0):sourceValue,keys,sourceKey);serialized!==void 0&&serialized.length>0&&(target[targetKey]=serialized)}}function validateItemDocumentKeys(parsed,envelope){for(const key of Object.keys(parsed)){if(envelope){if(!ITEM_ENVELOPE_KEYS.includes(key))throw unknownKeyError("item envelope",key,ITEM_ENVELOPE_KEYS);continue}if(!ITEM_FIELD_KEYS.has(key)&&nearestKey(key,[...ITEM_FIELD_KEYS])===void 0&&nearestKey(key,ITEM_ENVELOPE_KEYS)==="item")throw unknownKeyError("item envelope",key,ITEM_ENVELOPE_KEYS)}}function resolveItemDocument(parsed){const envelope=Object.hasOwn(parsed,"item");validateItemDocumentKeys(parsed,envelope);const item=envelope?parsed.item:parsed;if(!isPlainObject(item))throw new PmCliError("Item document item must be an object.",EXIT_CODE.USAGE);return{item,...envelope&&isPlainObject(parsed.linked)?{linked:parsed.linked}:{}}}function appendItemDocumentEntry(documentOptions,key,value,mode,runtimeFields,explicitOptions){if((mode==="update"?UPDATE_READ_ONLY_ITEM_KEYS:READ_ONLY_ITEM_KEYS).has(key))return;const definition=runtimeFields.find(field=>field.metadata_key===key&&field.commands.includes(mode));if(definition!==void 0){const explicitValue=readRuntimeFieldOptionValue(explicitOptions,definition);documentOptions[runtimeFieldOptionTarget(definition)]=explicitValue!==void 0?explicitValue:definition.type==="array"||definition.type==="object"?JSON.stringify(value):value;return}if(!ITEM_FIELD_KEYS.has(key)){if(nearestKey(key,[...ITEM_FIELD_KEYS])!==void 0)throw unknownKeyError("item document",key,[...ITEM_FIELD_KEYS]);const fields=Array.isArray(documentOptions.field)?documentOptions.field:[];fields.push(`${key}=${typeof value=="string"?value:JSON.stringify(value)}`),documentOptions.field=fields;return}["dependencies","comments","notes","learnings","files","tests","docs","reminders","events","type_options"].includes(key)||(documentOptions[key.replaceAll(/_([a-z])/gu,(_match,letter)=>letter.toUpperCase())]=scalarItemOption(key,value))}function itemDocumentToMutationOptions(input,mode,explicitOptions={},runtimeFields=[]){const parsed=parseJsonValue(input,"Item document");if(!isPlainObject(parsed))throw new PmCliError("Item document must be a JSON object.",EXIT_CODE.USAGE);const{item,linked}=resolveItemDocument(parsed),documentOptions={};for(const[key,value]of Object.entries(item))appendItemDocumentEntry(documentOptions,key,value,mode,runtimeFields,explicitOptions);if(appendFacetOptions(documentOptions,item,mode==="update"),linked!==void 0&&appendFacetOptions(documentOptions,linked),mode==="update"){const annotations=Object.fromEntries(["comments","notes","learnings"].filter(key=>Object.hasOwn(item,key)).map(key=>[key,item[key].filter(entry=>isPlainObject(entry)&&entry.created_at!==void 0)]));Object.keys(annotations).length>0&&(documentOptions.documentAnnotationSnapshot=annotations)}return{...documentOptions,...Object.fromEntries(Object.entries(explicitOptions).filter(([,value])=>value!==void 0)),...Array.isArray(documentOptions.field)&&Array.isArray(explicitOptions.field)?{field:[...documentOptions.field,...explicitOptions.field]}:{},stdinJson:void 0}}function validateItemMutationRows(value){return parseItemMutationBatch(JSON.stringify(value))}export{itemDocumentToMutationOptions,parseAtomicMutationControls,parseItemMutationBatch,resolveItemMutationDocument,validateItemMutationRows};
2
2
  //# sourceMappingURL=structured-mutations.js.map
@@ -1,7 +1,58 @@
1
1
  # Test strength and documentation content
2
2
 
3
3
  Tracked by [pm-zclzll](../.agents/pm/tasks/pm-zclzll.toon) and
4
- [pm-dvwm](../.agents/pm/chores/pm-dvwm.toon).
4
+ [pm-dvwm](../.agents/pm/chores/pm-dvwm.toon). Release acceptance and token
5
+ comparison contracts: [pm-gh1428](../.agents/pm/issues/pm-gh1428.toon),
6
+ [pm-gh1429](../.agents/pm/issues/pm-gh1429.toon), and
7
+ [pm-agent-token-path](../.agents/pm/issues/pm-agent-token-path.toon).
8
+
9
+ ## Installed context and immutable release recovery
10
+
11
+ The installed-agent journey requests `context --for orient --limit 1` with an
12
+ explicit 512-token output budget. A complete successful result must report the
13
+ actual population: zero items before creation and one closed item after the
14
+ lifecycle. An omitted result cannot establish either fact. When the installed
15
+ version refuses an infeasible budget, the verifier checks exit 2, the omission
16
+ and budget receipts, and the exact advertised `outputBudget: "unbounded"`
17
+ recovery. It then repeats the same intent and amount with that recovery. Older
18
+ control releases may return complete bounded context immediately. Every step
19
+ keeps its existing character ceiling and process deadline; reports retain the
20
+ observed exit status and all refusal/recovery output costs.
21
+
22
+ Unexpected process failures retain classified status, signal, spawn error and
23
+ both output streams, with credential redaction before 512-character excerpts.
24
+ Unit tests own malformed receipts and failure diagnostics. Real npm/Bun
25
+ installations and the hosted candidate/control platform matrix own compatibility
26
+ with the actual SDK-backed CLI. A stubbed empty context object cannot satisfy
27
+ the population assertions.
28
+
29
+ Exact-tag recovery preserves reviewed artifact controls before selecting tagged
30
+ source. The preserved directory retains `scripts/release` and its parent script
31
+ dependencies, including temporary lifecycle and cleanup modules. The executable
32
+ workflow regression copies those real controls, packs a foreign workspace with
33
+ real npm, and checks the committed artifact budget. Removing a transitive import
34
+ must fail with its original module error. Preserved controls stay outside the
35
+ source checkout; published immutable versions are verified without republishing.
36
+
37
+ ## Portable agent-task token comparisons
38
+
39
+ Version 5 of the agent-task baseline declares
40
+ `comparison_scope: "plan_recovery_tracker_root_canonicalized"`. Each replay
41
+ continues to report exact `emitted_bytes`, `estimated_tokens`, and accounting
42
+ receipt bytes for the actual transport. Its `comparison_emitted_bytes` and
43
+ `comparison_estimated_tokens` replace only the validated tracker suffixes in
44
+ Plan inspection, next-action and plan-detail recovery commands with the fixed
45
+ `/replay/.agents/pm` comparison root. The actual payload retains executable
46
+ commands targeting its real fixture. Other facts, paths and commands remain
47
+ part of the cost.
48
+
49
+ Step, completed-task and composite ceilings use those comparison measurements.
50
+ The orientation selector uses the same cost basis. Missing, negative,
51
+ non-integer or non-finite measurements and mismatched comparison scopes fail
52
+ closed. Short and long temporary roots must produce the same verdict while
53
+ their real emitted-byte counts may differ. Output growth still fails the
54
+ unchanged ceilings; normalization does not waive accounting, payload parity,
55
+ required-field or executable-recovery checks.
5
56
 
6
57
  ## SDK mutation testing
7
58
 
@@ -7,7 +7,7 @@ installed-package diagnostics are tracked by
7
7
  [pm-q7c36n](../.agents/pm/issues/pm-q7c36n.toon).
8
8
 
9
9
  The **Release Reliability** workflow evaluates the preceding 30 days after a
10
- scheduled Auto Release completes, daily at nominal 04:35 UTC, and on manual
10
+ default-branch Auto Release or Nightly Validation completes, daily at nominal 04:35 UTC, and on manual
11
11
  dispatch. It saves a JSON report and fails its own check when policy is breached.
12
12
  The report is independent of candidate correctness: historical failures must
13
13
  remain visible while a reviewed repair can still pass publication gates.
@@ -48,6 +48,11 @@ be uploaded, the collector reads the declared run name. Historical dispatches
48
48
  without either declaration appear in `pre_attribution_gap`; the failed
49
49
  2026-09-28 morning dispatch cannot be attributed from its old run record.
50
50
  Dispatcher rows retain creation time, queue delay, outcome, and failure stage.
51
+ The `origins` map also retains independent `native_schedule`, `operator`, and
52
+ `blocker_retry` series, each with original counts, failure stages and outcome
53
+ evidence completeness. Undeclared historical runs remain explicit attribution
54
+ gaps; event or actor never supplies an invented origin. These diagnostic series
55
+ do not alter the native scheduled policy denominator.
51
56
  They omit nominal occurrence and dispatch delay because the native 02:35 UTC
52
57
  cron is not the dispatcher's clock; the scheduled timing policy does not apply
53
58
  to those rows.
@@ -85,8 +90,54 @@ hosted acceptance job limit.
85
90
 
86
91
  The report is a rolling observation, not a permanent archive of all releases.
87
92
  Retain exported receipts externally if a longer historical window is needed.
88
- It does not implement the separate nightly per-leg reliability programme or
89
- change the manual same-day version policy.
93
+ It does not change the manual same-day version policy or propagate upstream
94
+ origin through historical lightweight tags. Those remaining outcomes retain
95
+ their existing PM owners.
96
+
97
+ ## Nightly recurrence and public SDK
98
+
99
+ [pm-2zjs0g](../.agents/pm/issues/pm-2zjs0g.toon) owns the nightly recurrence meter.
100
+ The same reporting workflow builds trusted default-branch code and writes a
101
+ separate `nightly-reliability-<run>-<attempt>` artifact, including when release
102
+ reporting is already red. Its [versioned policy](../config/nightly-reliability-policy.json)
103
+ requires seven completed original attempts per family and at most 10% failures
104
+ over 30 days. Breaches fail the operational report and appear in the workflow
105
+ summary; they remain distinct from checks evaluating a candidate repair.
106
+
107
+ The collector derives current leg identities from `nightly.yml` and each run's
108
+ applicable population from the workflow at its original head SHA. Immutable
109
+ definitions are cached within one census; legacy OS/Node matrices and today's
110
+ labelled shards both retain their actual generations. Newly introduced families
111
+ never gain synthetic pending attempts in older runs. It retains historical
112
+ observed legs, retrieves attempt 1 after reruns, and reads both open
113
+ and closed alert issues plus their complete comment pages. Original failures,
114
+ distinct affected UTC days and observed closure durations survive alert repair.
115
+ Every new alert carries the stable PM recurrence owner and a platform/Node family
116
+ identity. Counts do not depend on a maintained list of occurrence IDs. An absent
117
+ job is pending evidence; an empty family has a null rate and fails coverage.
118
+
119
+ Alert closure is the recorded latency endpoint, not proof of a product fix.
120
+ When one alert episode links several failed runs, its shared closure is the
121
+ endpoint for each recorded occurrence's alert-closure latency. This does not
122
+ assert that an independent per-attempt product repair was verified.
123
+ Product versus infrastructure cause stays `unknown` until independent evidence
124
+ classifies it. Repairs at or beyond the report cutoff remain unresolved in that
125
+ historical window. API errors, incomplete search results, missing pages,
126
+ duplicate identities and ambiguous jobs refuse a complete-census claim.
127
+
128
+ Packages can reuse the harness-independent evaluator through
129
+ `evaluateReliabilityWindow` from `@unbrained/pm-cli/sdk/governance`. Supply
130
+ `ReliabilityAttempt` records with unique `id`, stable `family`, `started_at`,
131
+ original `outcome` and optional `repaired_at`/`cause`, plus an explicit
132
+ `ReliabilityWindowPolicy` population, half-open window, maximum failure fraction
133
+ and minimum completed count. The typed result preserves per-family findings,
134
+ unknown causes and repair observations. No GitHub dependency, filesystem tracker,
135
+ clock read or network call is required by this SDK primitive.
136
+
137
+ For reproducible offline evaluation, retain the report's `attempts` and `policy`
138
+ with its exact window; rerun the SDK evaluator against those records. The GitHub
139
+ adapter accepts a caller-chosen clock and a 1–90 day policy window, allowing
140
+ historical family reconstruction while keeping the production 30-day policy fixed.
90
141
 
91
142
  Provider contracts: [workflow run attempts](https://docs.github.com/en/rest/actions/workflow-runs#get-a-workflow-run-attempt)
92
143
  and [completion-triggered workflows](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#workflow_run).
package/docs/SDK.md CHANGED
@@ -1552,8 +1552,22 @@ accepts either a direct `ItemDocument` or the envelope returned by
1552
1552
  `pm get <id> --json`, strips read-only metadata, maps canonical snake-case item
1553
1553
  fields to typed mutation options, and serializes linked facets through the same
1554
1554
  repeatable option forms as create/update. Explicit options win over document
1555
- values. Unknown near-miss keys fail; application-defined fields are routed
1556
- through the public `field` escape hatch.
1555
+ values. Pass `resolveRuntimeFieldRegistry(settings.schema).definitions` as the
1556
+ fourth argument to route workspace metadata keys through their declared SDK
1557
+ option names. CLI stdin automatically resolves the selected workspace schema.
1558
+ Schema aliases override document values through the same field registry.
1559
+ Declared extension fields use raw `name=value` assignments; explicit `field`
1560
+ entries override only matching names, preserving other document fields. Strings
1561
+ retain commas, equals signs, quotes, Unicode, and newlines; numbers, booleans,
1562
+ objects, and arrays retain their types. Unknown fields and near-miss built-in
1563
+ keys refuse before item or history writes.
1564
+
1565
+ Tracked by [pm-stdin-schema-fields](../.agents/pm/issues/pm-stdin-schema-fields.toon).
1566
+ Stored schema containers retain their decoded shape; repeated CLI container
1567
+ flags still select the final supplied value. Normal SDK item operations refuse
1568
+ ahead-of-runtime storage markers before schema or body validation and retain
1569
+ item identity/path in the structured error. Integrity diagnostics preserve
1570
+ future markers for inspection.
1557
1571
 
1558
1572
  The built-in adapters stay deliberately thin:
1559
1573
 
package/docs/TESTING.md CHANGED
@@ -142,6 +142,42 @@ failed, skipped, stale, ambiguous, and different-tree results all fail.
142
142
  `pnpm release:gates` includes the same non-skippable verification, so run it
143
143
  only after the pushed reviewed head's hosted analyzers are terminal.
144
144
 
145
+ ## Published Workspace Compatibility
146
+
147
+ Tracked by [pm-pae3wo](../.agents/pm/tasks/pm-pae3wo.toon).
148
+ Run `node scripts/release/workspace-compatibility.mjs --json` after building.
149
+ The policy in `scripts/release/workspace-compatibility-policy.json` requires
150
+ the two immediately preceding stable calendar releases. Each installed release
151
+ writes a disposable workspace read by the current runtime, and the current
152
+ runtime writes one read by that release. Exact scalar and container values must
153
+ survive CLI and public SDK reads, ordinary writes with their registration
154
+ disabled, history restoration, native Git merges, reconciliation, and history
155
+ verification. Missing releases, metadata loss, or invalid merged histories fail
156
+ the gate. CI, nightly quality, release publication, and local preflight require
157
+ this matrix alongside the independent legacy Markdown migration check.
158
+
159
+ Metadata probes and installs explicitly use the public npm registry with
160
+ separate empty user/global configurations and a fresh temporary cache. Inherited
161
+ npm settings and registry-auth environment variables are removed, so host
162
+ configuration cannot change the artifacts selected by the declared window.
163
+
164
+ The current item-operation boundary refuses a stored `pm_format_version`
165
+ ahead of its supported version with `item_format_version_unsupported` and
166
+ upgrade guidance. Reads, updates, deletion, and restoration preserve the
167
+ original item/history on refusal, including documents whose metadata/body
168
+ shapes are invalid for the current runtime. Admission precedes schema validation
169
+ so restore cannot misclassify newer data as ordinary corruption. Static fixture
170
+ modules read separate JSON data; published SDK probes use the public package
171
+ entrypoint from their own consumer directory. Raw parsers still preserve future version
172
+ markers for integrity inspection; inventory diagnostics are not permission to
173
+ rewrite an unsupported item.
174
+
175
+ Run the matrix from the built workspace root. Exact-tag release recovery retains
176
+ the reviewed matrix controls, policy and static fixtures outside the checkout,
177
+ then tests the selected tagged runtime through its manifest and public SDK. A
178
+ historical tag need not contain newly added gate scripts; recovery still runs
179
+ the mandatory matrix without rewriting the tagged source or skipping policy.
180
+
145
181
  ## Focused Test Runs
146
182
 
147
183
  ```bash
@@ -1,8 +1,9 @@
1
1
  {
2
- "version": 4,
2
+ "version": 5,
3
3
  "transcript_version": 2,
4
4
  "transcript_digest": "sha256:6d4041acfaa4075dde1972d6127a74f18916f7661738b4f5d5fd47a61c179a78",
5
5
  "estimator": "ceil(utf8_bytes / 4)",
6
+ "comparison_scope": "plan_recovery_tracker_root_canonicalized",
6
7
  "measurement_scope": "output_before_token_accounting",
7
8
  "published_with_release": true,
8
9
  "orientation": {
@@ -12,12 +13,12 @@
12
13
  "active_ownership",
13
14
  "workspace_state"
14
15
  ],
15
- "measured_winner_tokens": 1102,
16
+ "measured_winner_tokens": 1087,
16
17
  "protocols": [
17
18
  {
18
19
  "task_id": "orientation-context-intent",
19
20
  "command_count": 1,
20
- "estimated_tokens": 1102,
21
+ "estimated_tokens": 1087,
21
22
  "capabilities": [
22
23
  "actionable_work",
23
24
  "active_ownership",
@@ -47,7 +48,7 @@
47
48
  {
48
49
  "task_id": "orientation-operating-rules",
49
50
  "command_count": 4,
50
- "estimated_tokens": 2340,
51
+ "estimated_tokens": 2325,
51
52
  "capabilities": [
52
53
  "actionable_work",
53
54
  "active_ownership",
@@ -109,11 +110,11 @@
109
110
  "tasks": [
110
111
  {
111
112
  "id": "orientation-context-intent",
112
- "max_estimated_tokens": 1102,
113
+ "max_estimated_tokens": 1087,
113
114
  "steps": [
114
115
  {
115
116
  "id": "context",
116
- "max_estimated_tokens": 1102,
117
+ "max_estimated_tokens": 1087,
117
118
  "accounting_mode": "self_reported"
118
119
  }
119
120
  ]
@@ -136,11 +137,11 @@
136
137
  },
137
138
  {
138
139
  "id": "orientation-operating-rules",
139
- "max_estimated_tokens": 2340,
140
+ "max_estimated_tokens": 2325,
140
141
  "steps": [
141
142
  {
142
143
  "id": "context",
143
- "max_estimated_tokens": 1102,
144
+ "max_estimated_tokens": 1087,
144
145
  "accounting_mode": "self_reported"
145
146
  },
146
147
  {
@@ -162,7 +163,7 @@
162
163
  },
163
164
  {
164
165
  "id": "context-bootstrap",
165
- "max_estimated_tokens": 2025,
166
+ "max_estimated_tokens": 2010,
166
167
  "steps": [
167
168
  {
168
169
  "id": "triage",
@@ -171,7 +172,7 @@
171
172
  },
172
173
  {
173
174
  "id": "orient",
174
- "max_estimated_tokens": 1102,
175
+ "max_estimated_tokens": 1087,
175
176
  "accounting_mode": "self_reported"
176
177
  },
177
178
  {
@@ -262,7 +263,7 @@
262
263
  },
263
264
  {
264
265
  "id": "plan-execution-mutations",
265
- "max_estimated_tokens": 848,
266
+ "max_estimated_tokens": 806,
266
267
  "steps": [
267
268
  {
268
269
  "id": "create-plan",
@@ -271,12 +272,12 @@
271
272
  },
272
273
  {
273
274
  "id": "add-step",
274
- "max_estimated_tokens": 275,
275
+ "max_estimated_tokens": 254,
275
276
  "accounting_mode": "self_reported"
276
277
  },
277
278
  {
278
279
  "id": "complete-step",
279
- "max_estimated_tokens": 274,
280
+ "max_estimated_tokens": 253,
280
281
  "accounting_mode": "self_reported"
281
282
  },
282
283
  {
@@ -287,5 +288,5 @@
287
288
  ]
288
289
  }
289
290
  ],
290
- "composite_max_estimated_tokens": 13882
291
+ "composite_max_estimated_tokens": 13795
291
292
  }
@@ -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: 396
7
+ - Catalog error codes: 397
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: 377
12
- - Coverage fraction: 0.047980
11
+ - Uncovered error codes: 378
12
+ - Coverage fraction: 0.047859
13
13
  - Closed-domain probes: 19
14
14
  - Grammar probes: 117
15
15
 
@@ -177,6 +177,7 @@ Every catalog code is listed. An `uncovered` row is an explicit closure obligati
177
177
  | `item_document_encoding_invalid` | `item_document_encoding_invalid` | uncovered | none | 0 |
178
178
  | `item_document_invalid` | `item_document_invalid` | uncovered | none | 0 |
179
179
  | `item_document_roundtrip_failed` | `item_document_roundtrip_failed` | uncovered | none | 0 |
180
+ | `item_format_version_unsupported` | `item_format_version_unsupported` | uncovered | none | 0 |
180
181
  | `item_id_collision` | `item_id_collision` | uncovered | none | 0 |
181
182
  | `item_identity_ambiguous` | `item_identity_ambiguous` | uncovered | none | 0 |
182
183
  | `item_identity_conflict` | `item_identity_conflict` | uncovered | none | 0 |
package/marketplace.json CHANGED
@@ -5,13 +5,13 @@
5
5
  "url": "https://github.com/unbraind/pm-cli"
6
6
  },
7
7
  "description": "Official marketplace for pm CLI — native git-based project management for Claude Code and AI coding agents.",
8
- "version": "2026.10.9",
8
+ "version": "2026.10.10",
9
9
  "plugins": [
10
10
  {
11
11
  "name": "pm-claude",
12
12
  "source": "./plugins/pm-claude",
13
13
  "description": "Native pm CLI integration for Claude Code — 32 MCP tools, 14 workflow skills with slash invocations, 4 subagents, hybrid TUI task tracking, session context injection, and coordination subagents for git-based project management without leaving Claude Code.",
14
- "version": "2026.10.9",
14
+ "version": "2026.10.10",
15
15
  "author": {
16
16
  "name": "unbrained",
17
17
  "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.10.9",
3
+ "version": "2026.10.10",
4
4
  "description": "Git-native project management CLI for humans and agents.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.10.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-beads",
3
- "version": "2026.10.9",
3
+ "version": "2026.10.10",
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.10.9",
3
+ "version": "2026.10.10",
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.10.9",
3
+ "version": "2026.10.10",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm exemplar package for the commands capability: registerCommand, registerFlags, and registerParser.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-digital-twin",
3
- "version": "2026.10.9",
3
+ "version": "2026.10.10",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Beyond-PM exemplar: a temporal production-facility digital twin built entirely on public pm SDK and extension primitives.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-governance-audit",
3
- "version": "2026.10.9",
3
+ "version": "2026.10.10",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for governance audit and normalization workflows.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-guide-shell",
3
- "version": "2026.10.9",
3
+ "version": "2026.10.10",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for guide and shell completion workflows.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-kanban",
3
- "version": "2026.10.9",
3
+ "version": "2026.10.10",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm archetype package: a complete Kanban continuous-flow profile built on public SDK primitives.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-lifecycle-hooks",
3
- "version": "2026.10.9",
3
+ "version": "2026.10.10",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package demonstrating safe lifecycle hook registration.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-linked-test-adapters",
3
- "version": "2026.10.9",
3
+ "version": "2026.10.10",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for optional linked-test run management adapters and reporters.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-search-advanced",
3
- "version": "2026.10.9",
3
+ "version": "2026.10.10",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for semantic, hybrid, and reindex search workflows.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-templates",
3
- "version": "2026.10.9",
3
+ "version": "2026.10.10",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for reusable create templates.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-todos",
3
- "version": "2026.10.9",
3
+ "version": "2026.10.10",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for Todo markdown import and export.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-vcs-sdk-exemplar",
3
- "version": "2026.10.9",
3
+ "version": "2026.10.10",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Beyond-PM exemplar: a VCS-style changeset workflow built entirely on public pm SDK and extension primitives.",
@@ -3,7 +3,7 @@
3
3
  "name": "pm-claude",
4
4
  "displayName": "pm CLI",
5
5
  "description": "Native pm CLI integration for Claude Code — 32 MCP tools, 14 skills with slash invocations, 4 subagents (coordinator, delivery-chain, triage, verification), hybrid TUI task tracking (pm as persistent store + Claude Code task panel as live view), session context injection, and full git-based project management without leaving Claude Code.",
6
- "version": "2026.10.9",
6
+ "version": "2026.10.10",
7
7
  "author": {
8
8
  "name": "unbrained",
9
9
  "url": "https://github.com/unbraind/pm-cli"
@@ -2,8 +2,8 @@
2
2
  "name": "pm-claude-runtime",
3
3
  "private": true,
4
4
  "type": "module",
5
- "version": "2026.10.9",
5
+ "version": "2026.10.10",
6
6
  "dependencies": {
7
- "@unbrained/pm-cli": "2026.10.9"
7
+ "@unbrained/pm-cli": "2026.10.10"
8
8
  }
9
9
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pm-codex",
3
- "version": "2026.10.9",
3
+ "version": "2026.10.10",
4
4
  "description": "Native Codex integration for pm-cli with bundled MCP tools, skills, commands, and agent workflows.",
5
5
  "author": {
6
6
  "name": "unbrained",