@unbrained/pm-cli 2026.10.21 → 2026.10.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +134 -77
- package/dist/cli/error-guidance.d.ts +12 -0
- package/dist/cli/error-guidance.js +2 -2
- package/dist/cli/register-setup.js +1 -1
- package/dist/cli-bundle/bundle-manifest.json +1 -1
- package/dist/cli-bundle/chunks/{chunk-DIR3YEFD.js → chunk-2LTGJ5DL.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-EMGMLKRV.js → chunk-2UX2TAN2.js} +15 -15
- package/dist/cli-bundle/chunks/{chunk-2B5HAZFK.js → chunk-3S5FLCG7.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-5DKCD4KV.js +126 -0
- package/dist/cli-bundle/chunks/{chunk-V3XONSVC.js → chunk-5V4HU6GK.js} +48 -48
- package/dist/cli-bundle/chunks/{chunk-QPV6UD55.js → chunk-AAX7CNL7.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-TMXWWOPJ.js → chunk-AXGFNNI3.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-F76ZUP73.js +113 -0
- package/dist/cli-bundle/chunks/chunk-HDKY2YX3.js +3 -0
- package/dist/cli-bundle/chunks/{chunk-DR6DUBTU.js → chunk-KY34HTT2.js} +29 -29
- package/dist/cli-bundle/chunks/chunk-MMVX5L32.js +20 -0
- package/dist/cli-bundle/chunks/{chunk-C6SAXKBO.js → chunk-MTRYEVZR.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-QSY55FTA.js +2 -0
- package/dist/cli-bundle/chunks/{chunk-GWIGZDRS.js → chunk-R7KWBFM2.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-5PZ7JNMI.js → chunk-REXCO5AG.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-ZDVTXLA6.js +9 -0
- package/dist/cli-bundle/chunks/{chunk-DJQVNDXO.js → chunk-ZEI3DC5E.js} +6 -6
- package/dist/cli-bundle/chunks/{chunk-MOC5BJHW.js → chunk-ZMJF5HQW.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-ZNDYAZZ6.js +6 -0
- package/dist/cli-bundle/chunks/{indexed-receipt-blobs-65XXSPPR.js → indexed-receipt-blobs-XANVELIV.js} +2 -2
- package/dist/cli-bundle/chunks/{mutation-admission-FNWOVJLN.js → mutation-admission-P6LOBNLO.js} +2 -2
- package/dist/cli-bundle/chunks/{register-list-query-6PT5Y2EZ.js → register-list-query-JOB65KDN.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-TAIQV46L.js → register-mutation-QMNIN3RJ.js} +2 -2
- package/dist/cli-bundle/chunks/{register-operations-ONIRO22G.js → register-operations-Z4VET3I7.js} +2 -2
- package/dist/cli-bundle/chunks/register-setup-GNKWL3VL.js +2 -0
- package/dist/cli-bundle/chunks/{settlement-hook-UVFYSBMU.js → settlement-hook-YUVCO65H.js} +2 -2
- package/dist/cli-bundle/chunks/{stage-FCPVFO3B.js → stage-FW5OXCCR.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-VD64TPXI.js → chunk-4MNUQCGR.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-6GTZ24YV.js +3 -0
- package/dist/cli-bundle/focused-chunks/{chunk-XE7P7N5X.js → chunk-BM652VCB.js} +6 -6
- package/dist/cli-bundle/focused-chunks/{chunk-3LWH2RJC.js → chunk-BUZ2Z4P3.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-CGL36F6I.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-KVYUQS6Y.js → chunk-DWQMYLFM.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-4HJVAUFX.js → chunk-EDXTOAXP.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-FO4U3FIP.js +91 -0
- package/dist/cli-bundle/focused-chunks/chunk-GXKP56W7.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-I2USTQNQ.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-44353U27.js → chunk-JDN34LEZ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-CUWNNU4N.js → chunk-KI5YFKNN.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-7UD7XTOE.js → chunk-LEHGCPZ6.js} +11 -11
- package/dist/cli-bundle/focused-chunks/{chunk-HNCMYF2W.js → chunk-MLET6I3I.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-PDOWLRHC.js → chunk-NSMZY6IK.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-OMXMYTEX.js +26 -0
- package/dist/cli-bundle/focused-chunks/{chunk-JI3RXXRN.js → chunk-P7DPS5M4.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-R536272H.js +9 -0
- package/dist/cli-bundle/focused-chunks/{chunk-5IMM7GOF.js → chunk-UNTCCJPL.js} +3 -3
- package/dist/cli-bundle/focused-chunks/{chunk-32ZKLRWM.js → chunk-XIZYJDYW.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-Z5LDTMZ3.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-ZHMF34DT.js +65 -0
- package/dist/cli-bundle/focused-chunks/{chunk-P5D7MHDS.js → chunk-ZR6ZKQUK.js} +15 -15
- package/dist/cli-bundle/focused-chunks/{indexed-receipt-blobs-CRP6HAZY.js → indexed-receipt-blobs-FVOA3XMI.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{mutation-admission-KM57KDDF.js → mutation-admission-HTFAIHJL.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{settlement-hook-MNCHFCAX.js → settlement-hook-2VXEB6PP.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{stage-UJYGWNZ7.js → stage-X3BUGEJT.js} +2 -2
- package/dist/cli-bundle/main.js +21 -21
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +5 -2
- package/dist/cli-bundle/sdk-core.js +33 -33
- package/dist/cli-bundle/sdk-governance.js +1 -1
- package/dist/cli-bundle/sdk-graph.js +1 -1
- package/dist/cli-bundle/sdk-merge.js +58 -58
- package/dist/cli-bundle/sdk-query.js +1 -1
- package/dist/cli-bundle/sdk-runtime.js +1 -1
- package/dist/cli-bundle/sdk-testing.js +1 -1
- package/dist/cli-bundle/sdk.js +12 -9
- package/dist/core/config/nested-settings.js +1 -1
- package/dist/core/extensions/loader.js +1 -1
- package/dist/core/extensions/version-diagnostics.d.ts +2 -0
- package/dist/core/extensions/version-diagnostics.js +2 -0
- package/dist/core/lock/lock.d.ts +40 -6
- package/dist/core/lock/lock.js +3 -3
- package/dist/core/shared/errors.d.ts +28 -0
- package/dist/core/shared/errors.js +1 -1
- package/dist/core/store/item-store.js +1 -1
- package/dist/mcp/http-server.js +2 -2
- package/dist/mcp/server.d.ts +2 -2
- package/dist/mcp/server.js +2 -2
- package/dist/sdk/agent/command-recovery.d.ts +2 -0
- package/dist/sdk/agent/command-recovery.js +1 -1
- package/dist/sdk/cli-contracts/agent-output-contracts.js +1 -1
- package/dist/sdk/cli-contracts/tool-option-contracts.d.ts +6 -0
- package/dist/sdk/cli-contracts/tool-option-contracts.js +1 -1
- package/dist/sdk/cli-contracts/tool-schema.js +1 -1
- package/dist/sdk/context-intent-contracts.d.ts +13 -0
- package/dist/sdk/context-intent-contracts.js +1 -1
- package/dist/sdk/contracts.d.ts +2 -1
- package/dist/sdk/contracts.js +1 -1
- package/dist/sdk/core.d.ts +2 -2
- package/dist/sdk/core.js +1 -1
- package/dist/sdk/define.d.ts +15 -16
- package/dist/sdk/define.js +1 -1
- package/dist/sdk/dependencies.js +1 -1
- package/dist/sdk/extension/doctor.js +1 -1
- package/dist/sdk/extension/install-receipts.js +1 -1
- package/dist/sdk/extension/install-snapshot.d.ts +11 -0
- package/dist/sdk/extension/install-snapshot.js +1 -1
- package/dist/sdk/extension.d.ts +12 -0
- package/dist/sdk/extension.js +1 -1
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -1
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1 -1
- package/dist/sdk/governance/health.d.ts +1 -0
- package/dist/sdk/governance/health.js +2 -2
- package/dist/sdk/index.d.ts +3 -2
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/lifecycle/hierarchy-mutation-lock.js +1 -1
- package/dist/sdk/lifecycle/restore.js +1 -1
- package/dist/sdk/mcp/executable.d.ts +13 -0
- package/dist/sdk/mcp/executable.js +5 -0
- package/dist/sdk/merge/install.js +2 -2
- package/dist/sdk/public-surface.json +1 -1
- package/dist/sdk/query/context.d.ts +5 -0
- package/dist/sdk/query/context.js +2 -2
- package/dist/sdk/read-output/session-result.d.ts +39 -0
- package/dist/sdk/read-output/session-result.js +2 -0
- package/dist/sdk/read-output-contracts.d.ts +18 -5
- package/dist/sdk/read-output-contracts.js +1 -1
- package/dist/sdk/read-output-session.d.ts +2 -0
- package/dist/sdk/read-output-session.js +1 -1
- package/dist/sdk/relationship-context.js +1 -1
- package/dist/sdk/runtime-input.js +1 -1
- package/dist/sdk/runtime-primitives.d.ts +7 -1
- package/dist/sdk/runtime-primitives.js +1 -1
- package/dist/sdk/runtime.d.ts +11 -9
- package/dist/sdk/runtime.js +1 -1
- package/dist/sdk/workspace-snapshot.d.ts +5 -1
- package/dist/sdk/workspace-snapshot.js +3 -3
- package/dist/sdk/workspace.d.ts +3 -1
- package/dist/sdk/workspace.js +8 -5
- package/docs/CONFIGURATION.md +28 -3
- package/docs/DEVELOPMENT_DEPENDENCY_SECURITY.md +30 -0
- package/docs/EXTENSION_AUTHOR_CONTRACTS.md +2 -1
- package/docs/EXTENSION_LIFECYCLE.md +47 -0
- package/docs/MCP_OPTION_CONFORMANCE.md +27 -1
- package/docs/MCP_REMOTE_TRANSPORT_SECURITY.md +16 -1
- package/docs/MERGE_SAFETY.md +8 -0
- package/docs/READ_OUTPUT_CONTRACTS.md +79 -0
- package/docs/RELATIONSHIP_GRAPH.md +15 -0
- package/docs/RELEASING.md +17 -0
- package/docs/SDK.md +76 -12
- package/docs/SDK_STORAGE_LIFECYCLE.md +2 -1
- package/docs/TESTING.md +16 -7
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +6 -3
- package/marketplace.json +1 -1
- package/package.json +1 -1
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-claude/package.json +1 -1
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/pm-codex/package.json +1 -1
- package/plugins/pm-codex/plugin.json +1 -1
- package/runtime-dependencies.json +1 -1
- package/dist/cli-bundle/chunks/chunk-4OYU5OXQ.js +0 -6
- package/dist/cli-bundle/chunks/chunk-DIKQ2AL5.js +0 -2
- package/dist/cli-bundle/chunks/chunk-KBSVPUFD.js +0 -113
- package/dist/cli-bundle/chunks/chunk-MZZIAC6S.js +0 -20
- package/dist/cli-bundle/chunks/chunk-NNB65OOO.js +0 -3
- package/dist/cli-bundle/chunks/chunk-V5GJTXYW.js +0 -126
- package/dist/cli-bundle/chunks/chunk-ZNKFMIVA.js +0 -6
- package/dist/cli-bundle/chunks/register-setup-OQMUZBJV.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-6QSZHRM7.js +0 -6
- package/dist/cli-bundle/focused-chunks/chunk-E2S42XCT.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-GRSQGMRR.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-IAGFXKMZ.js +0 -3
- package/dist/cli-bundle/focused-chunks/chunk-JMZPZQHR.js +0 -26
- package/dist/cli-bundle/focused-chunks/chunk-O3M6YWVL.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-T27TLSEU.js +0 -65
- package/dist/cli-bundle/focused-chunks/chunk-VE6MTRH5.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-YIWDX6KO.js +0 -91
|
@@ -1,6 +1,6 @@
|
|
|
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]="
|
|
2
|
-
`)}}const _testOnlyWorkspaceSnapshot={WorkspaceLockHeartbeat,withSnapshotFilesystemGuard};async function collectAuthoritativeFiles(root,relative=""){const entries=await readdir(path.join(root,relative),{withFileTypes:!0}),files=[];for(const entry of entries.sort((left,right)=>left.name.localeCompare(right.name))){if(relative.length===0&&EXCLUDED_ROOT_NAMES.has(entry.name))continue;const entryRelative=path.posix.join(relative.replaceAll(path.sep,"/"),entry.name);if(entry.isSymbolicLink())throw new Error(`Workspace snapshots reject symbolic links: ${entryRelative}`);if(entry.isDirectory())files.push(...await collectAuthoritativeFiles(root,entryRelative));else if(entry.isFile())files.push(entryRelative);else throw new Error(`Workspace snapshots reject unsupported filesystem entries: ${entryRelative}`)}return files}async function buildManifest(pmRoot){const files=await collectAuthoritativeFiles(pmRoot),hash=crypto.createHash("sha256"),contents=[];let bytes=0;for(const file of files){const content=await readFile(path.join(pmRoot,file));contents.push(content),bytes+=content.byteLength,hash.update(file),hash.update("\0"),hash.update(String(content.byteLength)),hash.update("\0"),hash.update(content)}const fingerprint=hash.digest("hex");return{manifest:{schema:SNAPSHOT_SCHEMA,fingerprint,files,bytes},contents}}function lineCount(content){return content===void 0||content.byteLength===0?0:content.toString("utf8").split(`
|
|
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]="66cb8c82-3760-5628-a1de-69e417cf0292")}catch{}})();import crypto from"node:crypto";import{cp,lstat,mkdir,readFile,readdir,rename,rm,writeFile}from"node:fs/promises";import path from"node:path";import{isFileMissingError,writeFileAtomic}from"../core/fs/fs-utils.js";import{appendWorkspaceAuditEvent}from"../core/history/workspace-history.js";import{acquireLockLease,waitForStaleCleanupGate}from"../core/lock/lock.js";import{getLockPath}from"../core/store/paths.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{withHostEnvironmentBoundary}from"./environment/host-environment-errors.js";const SNAPSHOT_SCHEMA="https://schema.unbrained.dev/pm/workspace-snapshot/v1",WORKSPACE_SNAPSHOT_ACTIONS=["create","list","inspect","restore","delete"],SNAPSHOT_RUNTIME_PATH=path.join("runtime","workspace-snapshots"),EXCLUDED_ROOT_NAMES=new Set(["checkpoints","locks","runtime","search","transactions"]),SNAPSHOT_TARGET_PATTERN=/^[a-z0-9][a-z0-9._-]*$/,WORKSPACE_WRITER_LOCK_ID="sdk-workspace-transaction",SNAPSHOT_HOST_FAULT_CODES={capacity:{code:"workspace_snapshot_storage_exhausted"},permission:{code:"workspace_snapshot_permission_denied"},resource:{code:"workspace_snapshot_resource_exhausted"}},DEFAULT_ATOMIC_OPERATIONS={renameEntry:rename,removeEntry:async target=>{await rm(target,{recursive:!0,force:!0})}};class WorkspaceLockHeartbeat{lockPath;owner;intervalMs;timer;refresh;failure;constructor(lockPath,owner,ttlSeconds){this.lockPath=lockPath,this.owner=owner,this.intervalMs=Math.max(10,Math.floor(ttlSeconds*1e3/3))}start(){this.scheduleRenewal(),this.timer=setInterval(()=>{this.scheduleRenewal()},this.intervalMs),this.timer.unref()}async refreshNow(){this.scheduleRenewal(),await this.refresh,this.assertHealthy()}assertHealthy(){if(this.failure!==void 0)throw new Error("Workspace snapshot restore lost its writer lock",{cause:this.failure})}async stop(){this.timer!==void 0&&clearInterval(this.timer),await this.refresh,this.assertHealthy()}scheduleRenewal(){this.refresh!==void 0||this.failure!==void 0||(this.refresh=this.renew().catch(error=>{this.failure=error}).finally(()=>{this.refresh=void 0}))}async renew(){const releaseGate=await waitForStaleCleanupGate(this.lockPath,WORKSPACE_WRITER_LOCK_ID);if(releaseGate===null)throw new Error("Workspace writer lock cleanup gate stayed held");try{const parsed=JSON.parse(await readFile(this.lockPath,"utf8"));if(typeof parsed!="object"||parsed===null||Array.isArray(parsed))throw new Error("Workspace writer lock has invalid structure");const lock=parsed;if(lock.id!==WORKSPACE_WRITER_LOCK_ID||lock.owner!==this.owner||lock.pid!==process.pid)throw new Error("Workspace writer lock ownership changed");await writeFileAtomic(this.lockPath,`${JSON.stringify({...lock,created_at:new Date().toISOString()},null,2)}
|
|
2
|
+
`)}finally{await releaseGate()}}}const _testOnlyWorkspaceSnapshot={WorkspaceLockHeartbeat,withSnapshotFilesystemGuard};async function collectAuthoritativeFiles(root,relative=""){const entries=await readdir(path.join(root,relative),{withFileTypes:!0}),files=[];for(const entry of entries.sort((left,right)=>left.name.localeCompare(right.name))){if(relative.length===0&&EXCLUDED_ROOT_NAMES.has(entry.name))continue;const entryRelative=path.posix.join(relative.replaceAll(path.sep,"/"),entry.name);if(entry.isSymbolicLink())throw new Error(`Workspace snapshots reject symbolic links: ${entryRelative}`);if(entry.isDirectory())files.push(...await collectAuthoritativeFiles(root,entryRelative));else if(entry.isFile())files.push(entryRelative);else throw new Error(`Workspace snapshots reject unsupported filesystem entries: ${entryRelative}`)}return files}async function buildManifest(pmRoot){const files=await collectAuthoritativeFiles(pmRoot),hash=crypto.createHash("sha256"),contents=[];let bytes=0;for(const file of files){const content=await readFile(path.join(pmRoot,file));contents.push(content),bytes+=content.byteLength,hash.update(file),hash.update("\0"),hash.update(String(content.byteLength)),hash.update("\0"),hash.update(content)}const fingerprint=hash.digest("hex");return{manifest:{schema:SNAPSHOT_SCHEMA,fingerprint,files,bytes},contents}}function lineCount(content){return content===void 0||content.byteLength===0?0:content.toString("utf8").split(`
|
|
3
3
|
`).filter(line=>line.trim().length>0).length}async function snapshotContents(root,files){const contents=new Map;for(let offset=0;offset<files.length;offset+=32){const batch=files.slice(offset,offset+32),entries=await Promise.all(batch.map(async file=>[file,await readFile(path.join(root,file))]));for(const[file,content]of entries)contents.set(file,content)}return contents}function validateSnapshotTarget(target){if(!SNAPSHOT_TARGET_PATTERN.test(target))throw new PmCliError("Snapshot names and fingerprints must use lowercase letters, digits, dots, underscores, or hyphens",EXIT_CODE.USAGE,{code:"invalid_workspace_snapshot_target",required:"Provide a non-empty lowercase snapshot name or 64-character hexadecimal fingerprint.",why:"Snapshot targets are used as portable reference filenames and content identities.",examples:["pm workspace snapshot list --json","pm workspace snapshot inspect baseline --json"],nextSteps:["List available snapshots, then retry with a returned name or fingerprint."],recovery:{suggested_retry:"pm workspace snapshot list --json"}})}async function withSnapshotFilesystemGuard(operation,run){return withHostEnvironmentBoundary(`workspace_snapshot_${operation}`,run,{codes:{capacity:SNAPSHOT_HOST_FAULT_CODES.capacity.code,permission:SNAPSHOT_HOST_FAULT_CODES.permission.code,resource:SNAPSHOT_HOST_FAULT_CODES.resource.code},why:"Snapshot create and restore copy authoritative tracker state before activation, so the stage needs capacity and write access up front.",nextSteps:["Reclaim rebuildable tracker cache space with pm gc, then retry."],suggestedRetry:"pm gc --json"})}function snapshotStore(pmRoot){return path.join(pmRoot,SNAPSHOT_RUNTIME_PATH)}function workspaceSnapshotNotFound(target){return new PmCliError(`Unknown workspace snapshot: ${target}`,EXIT_CODE.NOT_FOUND,{code:"workspace_snapshot_not_found",required:"Use a snapshot name or fingerprint returned by snapshot list.",why:"The requested reference or immutable snapshot object does not exist.",examples:["pm workspace snapshot list --json"],nextSteps:["List available snapshots and retry with an exact target."],recovery:{suggested_retry:"pm workspace snapshot list --json"}})}async function readSnapshotJson(file,target){try{return JSON.parse(await readFile(file,"utf8"))}catch(error){throw isFileMissingError(error)?workspaceSnapshotNotFound(target):error}}async function removeSnapshotEntry(entry,target,recursive){try{await rm(entry,{recursive})}catch(error){throw isFileMissingError(error)?workspaceSnapshotNotFound(target):error}}async function publishWorkspaceSnapshotObject(temporaryRoot,objectRoot,operations=DEFAULT_ATOMIC_OPERATIONS){try{return await operations.renameEntry(temporaryRoot,objectRoot),!1}catch(error){if(!(error instanceof Error&&"code"in error&&(error.code==="EEXIST"||error.code==="ENOTEMPTY")))throw error;return await operations.removeEntry(temporaryRoot),!0}}async function swapWorkspaceSnapshotRootWithActivation(staging,pmRoot,backup,operations,afterActivation){await operations.renameEntry(pmRoot,backup);try{await operations.renameEntry(staging,pmRoot),await afterActivation?.()}catch(error){throw await operations.renameEntry(backup,pmRoot),await operations.removeEntry(staging),error}await operations.removeEntry(backup)}async function swapWorkspaceSnapshotRoot(staging,pmRoot,backup,operations=DEFAULT_ATOMIC_OPERATIONS){await swapWorkspaceSnapshotRootWithActivation(staging,pmRoot,backup,operations)}async function resolveSnapshotFingerprint(pmRoot,target){return validateSnapshotTarget(target),/^[a-f0-9]{64}$/.test(target)?target:(await readSnapshotJson(path.join(snapshotStore(pmRoot),"refs",`${target}.json`),target)).fingerprint}async function createWorkspaceSnapshot(pmRoot,options={}){if(options.name!==void 0&&(validateSnapshotTarget(options.name),/^[a-f0-9]{64}$/.test(options.name)))throw new PmCliError("Snapshot names must not be 64-character lowercase hexadecimal fingerprints",EXIT_CODE.USAGE,{code:"workspace_snapshot_name_reserved_fingerprint",required:"Choose a human-readable reference name that cannot be mistaken for a content fingerprint.",why:"Exact 64-character hexadecimal values address immutable objects.",examples:["pm workspace snapshot create baseline --json"],nextSteps:["Retry with a shorter descriptive snapshot name."]});const{manifest,contents}=await buildManifest(pmRoot),store=snapshotStore(pmRoot),objectRoot=path.join(store,"objects",manifest.fingerprint);let deduplicated=!1;try{deduplicated=(await lstat(objectRoot)).isDirectory()}catch(error){if(!(error instanceof Error&&"code"in error&&isFileMissingError(error)))throw error}if(!deduplicated){const temporaryRoot=path.join(store,"objects",`.create-${process.pid}-${crypto.randomUUID()}`);await withSnapshotFilesystemGuard("create_object",async()=>{await mkdir(path.join(temporaryRoot,"files"),{recursive:!0});for(const[index,file]of manifest.files.entries()){const target=path.join(temporaryRoot,"files",file);await mkdir(path.dirname(target),{recursive:!0}),await writeFile(target,contents[index])}await writeFile(path.join(temporaryRoot,"manifest.json"),`${JSON.stringify(manifest,null,2)}
|
|
4
4
|
`,"utf8"),await mkdir(path.dirname(objectRoot),{recursive:!0})}),deduplicated=await publishWorkspaceSnapshotObject(temporaryRoot,objectRoot)}if(options.name!==void 0){const reference={name:options.name,fingerprint:manifest.fingerprint},refsRoot=path.join(store,"refs");await mkdir(refsRoot,{recursive:!0});const temporaryRef=path.join(refsRoot,`.ref-${process.pid}-${crypto.randomUUID()}`);await writeFile(temporaryRef,`${JSON.stringify(reference,null,2)}
|
|
5
|
-
`,"utf8"),await rename(temporaryRef,path.join(refsRoot,`${options.name}.json`))}return{manifest,...options.name===void 0?{}:{name:options.name},deduplicated}}async function inspectWorkspaceSnapshot(pmRoot,target){const fingerprint=await resolveSnapshotFingerprint(pmRoot,target),manifest=await readSnapshotJson(path.join(snapshotStore(pmRoot),"objects",fingerprint,"manifest.json"),target);if(manifest.schema!==SNAPSHOT_SCHEMA||manifest.fingerprint!==fingerprint)throw new PmCliError(`Snapshot manifest identity mismatch: ${target}`,EXIT_CODE.CONFLICT,{code:"workspace_snapshot_manifest_mismatch",required:"Use an intact snapshot whose manifest fingerprint matches its object path.",why:"Content identity must be verified before snapshot data is trusted.",examples:["pm workspace snapshot list --json"],nextSteps:["Inspect or recreate the snapshot before restoring it."]});return manifest}async function listWorkspaceSnapshots(pmRoot){const store=snapshotStore(pmRoot),objectNames=await readdir(path.join(store,"objects")).catch(error=>{if(error instanceof Error&&"code"in error&&isFileMissingError(error))return[];throw error}),referenceNames=await readdir(path.join(store,"refs")).catch(error=>{if(error instanceof Error&&"code"in error&&isFileMissingError(error))return[];throw error});return{objects:await Promise.all(objectNames.filter(name=>/^[a-f0-9]{64}$/.test(name)).sort().map(name=>inspectWorkspaceSnapshot(pmRoot,name))),references:await Promise.all(referenceNames.filter(name=>name.endsWith(".json")).sort().map(async name=>JSON.parse(await readFile(path.join(store,"refs",name),"utf8"))))}}async function planWorkspaceSnapshotRestore(pmRoot,target){const targetManifest=await inspectWorkspaceSnapshot(pmRoot,target),{manifest:currentManifest,contents:currentContentList}=await buildManifest(pmRoot),currentContents=new Map(currentManifest.files.map((file,index)=>[file,currentContentList[index]])),targetContents=await snapshotContents(path.join(snapshotStore(pmRoot),"objects",targetManifest.fingerprint,"files"),targetManifest.files),currentFiles=new Set(currentManifest.files),targetFiles=new Set(targetManifest.files),changedFiles=new Set([...currentFiles,...targetFiles]);for(const file of changedFiles){const currentContent=currentContents.get(file),targetContent=targetContents.get(file);currentContent!==void 0&&targetContent!==void 0&¤tContent.equals(targetContent)&&changedFiles.delete(file)}const affectedHistoryStreams=currentManifest.files.filter(file=>file.startsWith("history/")&&file.endsWith(".jsonl")).map(file=>{const currentEntries=lineCount(currentContents.get(file)),targetEntries=lineCount(targetContents.get(file));return{path:file,current_entries:currentEntries,target_entries:targetEntries,removed_entries:Math.max(0,currentEntries-targetEntries)}}).filter(entry=>entry.removed_entries>0).sort((left,right)=>left.path.localeCompare(right.path));return{target_fingerprint:targetManifest.fingerprint,current_fingerprint:currentManifest.fingerprint,changed_file_count:changedFiles.size,removed_file_count:[...currentFiles].filter(file=>!targetFiles.has(file)).length,added_file_count:[...targetFiles].filter(file=>!currentFiles.has(file)).length,removed_item_count:[...currentFiles].filter(file=>file.endsWith(".toon")&&!targetFiles.has(file)).length,affected_history_stream_count:affectedHistoryStreams.length,removed_history_entry_count:affectedHistoryStreams.reduce((total,entry)=>total+entry.removed_entries,0),affected_history_streams:affectedHistoryStreams,changes_authoritative_state:currentManifest.fingerprint!==targetManifest.fingerprint}}async function restoreWorkspaceSnapshotWithRecovery(pmRoot,target,options={}){if(options.force!==!0)throw new PmCliError("Workspace snapshot restore requires explicit force confirmation; inspect the impact with planWorkspaceSnapshotRestore or pm workspace snapshot restore <target> --dry-run, then retry with force",EXIT_CODE.USAGE,{code:"workspace_snapshot_force_required",required:"Preview the destructive impact, then explicitly confirm the restore.",why:"A restore replaces the complete authoritative tracker state.",examples:[`pm workspace snapshot restore ${target} --dry-run --json`,`pm workspace snapshot restore ${target} --force --json`],nextSteps:["Review the dry-run counts before retrying with --force."],recovery:{suggested_retry:`pm workspace snapshot restore ${target} --dry-run --json`}});const author=options.author?.trim()||"pm-sdk",lockTtlSeconds=options.lockTtlSeconds??60,lockWaitMs=options.lockWaitMs??5e3,
|
|
5
|
+
`,"utf8"),await rename(temporaryRef,path.join(refsRoot,`${options.name}.json`))}return{manifest,...options.name===void 0?{}:{name:options.name},deduplicated}}async function inspectWorkspaceSnapshot(pmRoot,target){const fingerprint=await resolveSnapshotFingerprint(pmRoot,target),manifest=await readSnapshotJson(path.join(snapshotStore(pmRoot),"objects",fingerprint,"manifest.json"),target);if(manifest.schema!==SNAPSHOT_SCHEMA||manifest.fingerprint!==fingerprint)throw new PmCliError(`Snapshot manifest identity mismatch: ${target}`,EXIT_CODE.CONFLICT,{code:"workspace_snapshot_manifest_mismatch",required:"Use an intact snapshot whose manifest fingerprint matches its object path.",why:"Content identity must be verified before snapshot data is trusted.",examples:["pm workspace snapshot list --json"],nextSteps:["Inspect or recreate the snapshot before restoring it."]});return manifest}async function listWorkspaceSnapshots(pmRoot){const store=snapshotStore(pmRoot),objectNames=await readdir(path.join(store,"objects")).catch(error=>{if(error instanceof Error&&"code"in error&&isFileMissingError(error))return[];throw error}),referenceNames=await readdir(path.join(store,"refs")).catch(error=>{if(error instanceof Error&&"code"in error&&isFileMissingError(error))return[];throw error});return{objects:await Promise.all(objectNames.filter(name=>/^[a-f0-9]{64}$/.test(name)).sort().map(name=>inspectWorkspaceSnapshot(pmRoot,name))),references:await Promise.all(referenceNames.filter(name=>name.endsWith(".json")).sort().map(async name=>JSON.parse(await readFile(path.join(store,"refs",name),"utf8"))))}}async function planWorkspaceSnapshotRestore(pmRoot,target){const targetManifest=await inspectWorkspaceSnapshot(pmRoot,target),{manifest:currentManifest,contents:currentContentList}=await buildManifest(pmRoot),currentContents=new Map(currentManifest.files.map((file,index)=>[file,currentContentList[index]])),targetContents=await snapshotContents(path.join(snapshotStore(pmRoot),"objects",targetManifest.fingerprint,"files"),targetManifest.files),currentFiles=new Set(currentManifest.files),targetFiles=new Set(targetManifest.files),changedFiles=new Set([...currentFiles,...targetFiles]);for(const file of changedFiles){const currentContent=currentContents.get(file),targetContent=targetContents.get(file);currentContent!==void 0&&targetContent!==void 0&¤tContent.equals(targetContent)&&changedFiles.delete(file)}const affectedHistoryStreams=currentManifest.files.filter(file=>file.startsWith("history/")&&file.endsWith(".jsonl")).map(file=>{const currentEntries=lineCount(currentContents.get(file)),targetEntries=lineCount(targetContents.get(file));return{path:file,current_entries:currentEntries,target_entries:targetEntries,removed_entries:Math.max(0,currentEntries-targetEntries)}}).filter(entry=>entry.removed_entries>0).sort((left,right)=>left.path.localeCompare(right.path));return{target_fingerprint:targetManifest.fingerprint,current_fingerprint:currentManifest.fingerprint,changed_file_count:changedFiles.size,removed_file_count:[...currentFiles].filter(file=>!targetFiles.has(file)).length,added_file_count:[...targetFiles].filter(file=>!currentFiles.has(file)).length,removed_item_count:[...currentFiles].filter(file=>file.endsWith(".toon")&&!targetFiles.has(file)).length,affected_history_stream_count:affectedHistoryStreams.length,removed_history_entry_count:affectedHistoryStreams.reduce((total,entry)=>total+entry.removed_entries,0),affected_history_streams:affectedHistoryStreams,changes_authoritative_state:currentManifest.fingerprint!==targetManifest.fingerprint}}async function restoreWorkspaceSnapshotWithRecovery(pmRoot,target,options={}){if(options.force!==!0)throw new PmCliError("Workspace snapshot restore requires explicit force confirmation; inspect the impact with planWorkspaceSnapshotRestore or pm workspace snapshot restore <target> --dry-run, then retry with force",EXIT_CODE.USAGE,{code:"workspace_snapshot_force_required",required:"Preview the destructive impact, then explicitly confirm the restore.",why:"A restore replaces the complete authoritative tracker state.",examples:[`pm workspace snapshot restore ${target} --dry-run --json`,`pm workspace snapshot restore ${target} --force --json`],nextSteps:["Review the dry-run counts before retrying with --force."],recovery:{suggested_retry:`pm workspace snapshot restore ${target} --dry-run --json`}});const author=options.author?.trim()||"pm-sdk",lockTtlSeconds=options.lockTtlSeconds??60,lockWaitMs=options.lockWaitMs??5e3,workspaceLease=await acquireLockLease(pmRoot,WORKSPACE_WRITER_LOCK_ID,lockTtlSeconds,author,!1,!1,lockWaitMs),releaseWorkspaceLock=workspaceLease.release,heartbeat=new WorkspaceLockHeartbeat(getLockPath(pmRoot,WORKSPACE_WRITER_LOCK_ID),author,lockTtlSeconds);heartbeat.start(),await heartbeat.refreshNow();let staging,swapStarted=!1,heartbeatStopped=!1;try{const plan=await planWorkspaceSnapshotRestore(pmRoot,target),manifest=await inspectWorkspaceSnapshot(pmRoot,target),recovery=await createWorkspaceSnapshot(pmRoot),fingerprint=manifest.fingerprint,store=snapshotStore(pmRoot),source=path.join(store,"objects",fingerprint,"files"),parent=path.dirname(pmRoot),base=path.basename(pmRoot);staging=path.join(parent,`.${base}.restore-${crypto.randomUUID()}`);const backup=path.join(parent,`.${base}.backup-${crypto.randomUUID()}`);await withSnapshotFilesystemGuard("restore_stage",async()=>{await mkdir(staging,{recursive:!0}),await cp(source,staging,{recursive:!0,force:!1}),await mkdir(path.join(staging,"runtime"),{recursive:!0}),await cp(store,path.join(staging,SNAPSHOT_RUNTIME_PATH),{recursive:!0,force:!1})});const audit=await appendWorkspaceAuditEvent({pmRoot:staging,op:"workspace_snapshot_restore",author,message:options.message??`Restore workspace snapshot ${fingerprint}; recovery snapshot ${recovery.manifest.fingerprint}`,context:{target_fingerprint:fingerprint,pre_restore_fingerprint:recovery.manifest.fingerprint,changed_file_count:plan.changed_file_count,removed_file_count:plan.removed_file_count,removed_item_count:plan.removed_item_count,affected_history_stream_count:plan.affected_history_stream_count,removed_history_entry_count:plan.removed_history_entry_count},lockTtlSeconds,lockWaitMs}),auditHistoryPath=path.relative(staging,audit.historyPath).replaceAll("\\","/");await heartbeat.stop(),heartbeatStopped=!0;const stagedWorkspaceLock=getLockPath(staging,WORKSPACE_WRITER_LOCK_ID),stagedRoot=staging;return await workspaceLease.fence(async()=>{await mkdir(path.dirname(stagedWorkspaceLock),{recursive:!0}),await writeFile(stagedWorkspaceLock,await readFile(getLockPath(pmRoot,WORKSPACE_WRITER_LOCK_ID)),{flag:"wx"}),swapStarted=!0,await swapWorkspaceSnapshotRootWithActivation(stagedRoot,pmRoot,backup,DEFAULT_ATOMIC_OPERATIONS,async()=>{heartbeat.start(),heartbeatStopped=!1,await heartbeat.refreshNow()})}),await heartbeat.stop(),heartbeatStopped=!0,{manifest,plan,recovery_fingerprint:recovery.manifest.fingerprint,audit_history_path:auditHistoryPath,audit_operation:"workspace_snapshot_restore"}}catch(error){throw staging!==void 0&&!swapStarted&&await Promise.allSettled([rm(staging,{recursive:!0,force:!0})]),error}finally{try{heartbeatStopped||await heartbeat.stop()}catch{}finally{await releaseWorkspaceLock()}}}async function restoreWorkspaceSnapshot(pmRoot,target){return(await restoreWorkspaceSnapshotWithRecovery(pmRoot,target,{force:!0,message:"Restore requested through the compatibility SDK signature"})).manifest}async function deleteWorkspaceSnapshot(pmRoot,target){validateSnapshotTarget(target);const store=snapshotStore(pmRoot);if(!/^[a-f0-9]{64}$/.test(target))return await removeSnapshotEntry(path.join(store,"refs",`${target}.json`),target,!1),{deleted:"reference",target};const{references}=await listWorkspaceSnapshots(pmRoot);if(references.some(reference=>reference.fingerprint===target))throw new Error(`Snapshot object ${target} is still referenced`);return await removeSnapshotEntry(path.join(store,"objects",target),target,!0),{deleted:"object",target}}export{SNAPSHOT_SCHEMA,WORKSPACE_SNAPSHOT_ACTIONS,_testOnlyWorkspaceSnapshot,createWorkspaceSnapshot,deleteWorkspaceSnapshot,inspectWorkspaceSnapshot,listWorkspaceSnapshots,planWorkspaceSnapshotRestore,publishWorkspaceSnapshotObject,restoreWorkspaceSnapshot,restoreWorkspaceSnapshotWithRecovery,swapWorkspaceSnapshotRoot};
|
|
6
6
|
//# sourceMappingURL=workspace-snapshot.js.map
|
package/dist/sdk/workspace.d.ts
CHANGED
|
@@ -28,10 +28,12 @@ export declare function isPathOutsideRoot(relativePath:string):boolean;
|
|
|
28
28
|
/** Return the canonical pm ignore block rendered for the given workspace-relative tracker root (defaults to `.agents/pm`), primarily for documentation and tests. */
|
|
29
29
|
export declare function getPmGitignoreBlock(trackerRelativeRoot?:string):string;
|
|
30
30
|
/**
|
|
31
|
-
* Idempotently
|
|
31
|
+
* Idempotently reconcile the fenced pm cache rules in a workspace
|
|
32
32
|
* `.gitignore`. When `pmRoot` is provided the ignored runtime directories are
|
|
33
33
|
* rendered under the resolved workspace-relative tracker root, so custom-root
|
|
34
34
|
* workspaces (`--pm-path`/`PM_PATH`) stop committing runtime caches (GH-598).
|
|
35
|
+
* Sibling tracker rules are retained, and an explicitly root-layout tracker
|
|
36
|
+
* uses anchored repository-root rules rather than the default tracker path.
|
|
35
37
|
* A tracker root outside the workspace needs no ignore rules here, so the
|
|
36
38
|
* file is left untouched.
|
|
37
39
|
*/
|
package/dist/sdk/workspace.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
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]="
|
|
2
|
-
`)}
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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]="38df9536-d87a-52ac-958d-091a17c9da9c")}catch{}})();import{isFileMissingError}from"../core/fs/fs-utils.js";import{readFile,realpath,stat,writeFile}from"node:fs/promises";import path from"node:path";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";const PM_GITIGNORE_START="# pm-cli:runtime-cache:start",PM_GITIGNORE_END="# pm-cli:runtime-cache:end",PM_GITIGNORE_DEFAULT_TRACKER_ROOT=".agents/pm";async function physicalInitPath(input){let ancestor=path.resolve(input);const missing=[];for(;;)try{const physical=await realpath(ancestor);if(!(await stat(physical)).isDirectory())throw Object.assign(new Error("Initialization path ancestry must be a directory."),{code:"ENOTDIR"});return path.join(physical,...missing)}catch(error){if(!isFileMissingError(error)||path.dirname(ancestor)===ancestor)throw error;missing.unshift(path.basename(ancestor)),ancestor=path.dirname(ancestor)}}const PM_GITIGNORE_RUNTIME_DIRECTORIES=["runtime/","search/","locks/","transactions/","checkpoints/"],PM_GITIGNORE_TRACKED_FILES=["search/eval-queries.json"];async function withGitignorePermissionRecovery(operation){try{return await operation()}catch(error){throw error instanceof Error&&"code"in error&&typeof error.code=="string"&&["EACCES","EPERM","EROFS"].includes(error.code)?new PmCliError("The workspace .gitignore is not writable.",EXIT_CODE.GENERIC_FAILURE,{code:"init_gitignore_unwritable",reason:error.code.toLowerCase(),required:"Grant the current user read and write access to the workspace .gitignore before initialization.",why:"pm init must publish its managed runtime-cache ignore fence without replacing unrelated entries.",nextSteps:["Grant read and write access to the workspace .gitignore and rerun pm init.","If the workspace is intentionally read-only, initialize pm in a writable workspace or clone."]}):error}}function normalizeTrackerRelativeRoot(trackerRelativeRoot){const root=trackerRelativeRoot.split(path.sep).join("/").replace(/^\.\/+/,"");let end=root.length;for(;end>0&&root[end-1]==="/";)end-=1;return root.slice(0,end)}function isPathOutsideRoot(relativePath){const normalized=relativePath.replaceAll("\\","/");return path.isAbsolute(relativePath)||normalized===".."||normalized.startsWith("../")}function getPmGitignoreBlock(trackerRelativeRoot=PM_GITIGNORE_DEFAULT_TRACKER_ROOT){const root=normalizeTrackerRelativeRoot(trackerRelativeRoot);if(/[\r\n]/u.test(root))throw new PmCliError("A tracker path containing a line break cannot be represented safely in .gitignore.",EXIT_CODE.USAGE,{code:"invalid_argument_value",required:"a tracker path without line breaks",nextSteps:["Choose a tracker path without carriage returns or newlines and rerun pm init."]});const literalRoot=root.replace(/[\\*?[\]#! \t]/gu,"\\$&");return[PM_GITIGNORE_START,...PM_GITIGNORE_RUNTIME_DIRECTORIES.map(directory=>directory==="search/"?`${literalRoot}/${directory}*`:`${literalRoot}/${directory}`),...PM_GITIGNORE_TRACKED_FILES.map(file=>`!${literalRoot}/${file}`),PM_GITIGNORE_END].join(`
|
|
2
|
+
`)}function mergeTrackerIgnoreRules(lines,trackerRoot,rules){const literalRoot=trackerRoot.replace(/[\\*?[\]#! \t]/gu,"\\$&"),prefixes=[...new Set([trackerRoot,literalRoot])].flatMap(root=>PM_GITIGNORE_RUNTIME_DIRECTORIES.flatMap(directory=>[`${root}/${directory}`,`!${root}/${directory}`])),merged=[];let replaced=!1;for(const line of lines)prefixes.some(prefix=>line.startsWith(prefix))?(replaced||merged.push(...rules),replaced=!0):merged.push(line);return replaced||merged.push(...rules),merged}function managedGitignoreBlocks(current){const blocks=[];let start=-1,bodyStart=0,offset=0;for(const line of current.split(`
|
|
3
|
+
`)){const marker=line.endsWith("\r")?line.slice(0,-1):line;marker===PM_GITIGNORE_START?(start=offset,bodyStart=offset+line.length+1):marker===PM_GITIGNORE_END&&start>=0&&(blocks.push({index:start,end:offset+marker.length,lines:current.slice(bodyStart,offset).split(/\r?\n/u).filter(entry=>entry.length>0)}),start=-1),offset+=line.length+1}return blocks}function reconcileGitignoreContent(current,trackerRoot){const newline=current.includes(`\r
|
|
4
|
+
`)?`\r
|
|
5
|
+
`:`
|
|
6
|
+
`,rules=getPmGitignoreBlock(trackerRoot).split(`
|
|
7
|
+
`).slice(1,-1),blocks=managedGitignoreBlocks(current),existingRules=blocks.flatMap(entry=>entry.lines),block=[PM_GITIGNORE_START,...mergeTrackerIgnoreRules(existingRules,trackerRoot,rules),PM_GITIGNORE_END].join(newline);if(blocks.length===0){const separator=current.length===0?"":current.endsWith(`
|
|
8
|
+
`)?newline:`${newline}${newline}`;return`${current}${separator}${block}${newline}`}let next=current;for(const[index,match]of[...blocks.entries()].reverse())next=`${next.slice(0,match.index)}${index===0?block:""}${next.slice(match.end)}`;return next}async function ensurePmGitignore(workspaceRoot,options={}){const gitignorePath=path.join(workspaceRoot,".gitignore");let trackerRelativeRoot=PM_GITIGNORE_DEFAULT_TRACKER_ROOT;if(options.pmRoot!==void 0){const relative=normalizeTrackerRelativeRoot(path.relative(await physicalInitPath(workspaceRoot),await physicalInitPath(options.pmRoot)));if(isPathOutsideRoot(relative))return{path:gitignorePath,changed:!1};trackerRelativeRoot=relative}let current="";try{current=await withGitignorePermissionRecovery(()=>readFile(gitignorePath,"utf8"))}catch(error){if(!(error instanceof Error&&"code"in error&&isFileMissingError(error)))throw error}const next=reconcileGitignoreContent(current,trackerRelativeRoot);return next===current?{path:gitignorePath,changed:!1}:(await withGitignorePermissionRecovery(()=>writeFile(gitignorePath,next,"utf8")),{path:gitignorePath,changed:!0})}export{PM_GITIGNORE_DEFAULT_TRACKER_ROOT,PM_GITIGNORE_END,PM_GITIGNORE_RUNTIME_DIRECTORIES,PM_GITIGNORE_START,PM_GITIGNORE_TRACKED_FILES,ensurePmGitignore,getPmGitignoreBlock,isPathOutsideRoot};
|
|
6
9
|
//# sourceMappingURL=workspace.js.map
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -82,7 +82,7 @@ If the existing project `settings.json` has invalid JSON, a configuration write
|
|
|
82
82
|
| `item_format` | item storage format (`toon` writes; legacy markdown is read/migrate only) |
|
|
83
83
|
| `output.default_format` | default renderer, usually `toon` |
|
|
84
84
|
| `locks.ttl_seconds` | stale lock threshold |
|
|
85
|
-
| `locks.wait_ms` |
|
|
85
|
+
| `locks.wait_ms` | wait budget before a contended lock returns `lock_conflict`; item-store mutations, restore and the hierarchy lock restart it on each owner handoff (120 s cap), other locks use it as a fixed total (default `3000`; `0` fails fast) |
|
|
86
86
|
| `history.missing_stream` | `auto_create` or `strict_error` |
|
|
87
87
|
| `history.compact_policy.enabled` | enable the compaction advisory: `pm health` warns on over-threshold streams (default `false`); when disabled, `max_entries`/`trigger` are inert |
|
|
88
88
|
| `history.compact_policy.max_entries` | when the policy is enabled, the entry count above which a stream is flagged by `pm health` and the default `pm history-compact --all-over` threshold (default `500`) |
|
|
@@ -115,7 +115,18 @@ receipt before removal; it records the original owner and is an attempted
|
|
|
115
115
|
recovery audit, not proof that a later mutation committed. Keep these local
|
|
116
116
|
runtime receipts out of version control.
|
|
117
117
|
|
|
118
|
-
For
|
|
118
|
+
For item-store mutations (update, close, claim, comments, notes and the other
|
|
119
|
+
item-store writes), restore and the hierarchy-integrity lock (parented creates,
|
|
120
|
+
parent changes and integrity-sensitive dependency additions) the wait budget
|
|
121
|
+
measures stalls, not queue length: while other writers keep acquiring and releasing the
|
|
122
|
+
lock, a waiter keeps waiting (up to 120 s total), so many concurrent agents on
|
|
123
|
+
one tracker queue instead of failing. Other lock users, including package import
|
|
124
|
+
commits and internal short-lived locks, keep a fixed total budget. A lock held by
|
|
125
|
+
the same owner for the whole budget still refuses with `lock_conflict`, whose
|
|
126
|
+
recovery reports `waited_ms` and `owner_handoffs`. A writer whose lease expired
|
|
127
|
+
and was taken over refuses its commit with `lock_lost` instead of overwriting
|
|
128
|
+
the newer write; re-read and apply the change again. For an owner that is
|
|
129
|
+
expected to hold the lock longer, set `PM_LOCK_WAIT_MS` to a larger bounded
|
|
119
130
|
millisecond budget, or configure `locks.wait_ms`, and retry the same operation.
|
|
120
131
|
CLI recovery preserves its normalized arguments and quoted values, including
|
|
121
132
|
whitespace and empty argument strings. Malformed argument vectors are omitted
|
|
@@ -167,7 +178,7 @@ underscore aliases remain accepted for compatibility.
|
|
|
167
178
|
| `PM_AUTHOR` | optional explicit session-wide mutation author override |
|
|
168
179
|
| `PM_PATH` | override project tracker root for tests or sandboxes |
|
|
169
180
|
| `PM_GLOBAL_PATH` | override global profile root for tests or sandboxes |
|
|
170
|
-
| `PM_LOCK_WAIT_MS` | per-process override for `locks.wait_ms
|
|
181
|
+
| `PM_LOCK_WAIT_MS` | per-process override for the `locks.wait_ms` budget (a no-progress window only where that lock uses the progress policy); non-negative integer milliseconds, with `0` restoring fail-fast lock conflicts |
|
|
171
182
|
| `PM_OLLAMA_MODEL` | choose default Ollama embedding model |
|
|
172
183
|
| `PM_DISABLE_OLLAMA_AUTO_DEFAULTS` | disable implicit Ollama search defaults |
|
|
173
184
|
| `PM_CONTEXT_USAGE_DISABLED` | set to `1` to disable the derived context/next usage-feedback ledger with zero filesystem reads or writes |
|
|
@@ -177,6 +188,20 @@ For command-level tracker targeting, prefer `--pm-path <tracker-root>`. The old
|
|
|
177
188
|
itself, not the repository workspace. During `pm init`, explicit tracker paths
|
|
178
189
|
that look like workspace roots are rejected unless `--force` is provided.
|
|
179
190
|
|
|
191
|
+
Initialization maintains the repository's runtime-cache `.gitignore` fence for
|
|
192
|
+
every tracker sharing that repository. Initializing or reinitializing a nested
|
|
193
|
+
tracker preserves the enclosing tracker's exclusions and unrelated user rules.
|
|
194
|
+
Equivalent physical and symlink-addressed paths share the same containment
|
|
195
|
+
check, including tracker directories that initialization has not created yet.
|
|
196
|
+
A symlink leading outside the repository remains an external tracker and does
|
|
197
|
+
not add rules here. This boundary is tracked by
|
|
198
|
+
[pm-p8s5k7e6lhda](../.agents/pm/issues/pm-p8s5k7e6lhda.toon).
|
|
199
|
+
Custom tracker paths are literal Git paths, including spaces and wildcard
|
|
200
|
+
characters. An intentional root-layout tracker uses repository-root exclusions.
|
|
201
|
+
Paths containing line breaks are refused because `.gitignore` cannot safely
|
|
202
|
+
represent them. Curated `search/eval-queries.json` files remain trackable in
|
|
203
|
+
each tracker. See [merge safety](MERGE_SAFETY.md#runtime-receipts-and-retention).
|
|
204
|
+
|
|
180
205
|
Implicit discovery checks the current directory and its ancestors up to the
|
|
181
206
|
nearest `.git` directory or file, including worktrees and submodules. An
|
|
182
207
|
uninitialized repository selects its own `.agents/pm` and returns initialization
|
|
@@ -36,6 +36,11 @@ immediately before SDK assurance evaluation, which verifies advisory evidence
|
|
|
36
36
|
before slower providers. The blocking registry command settles before OSV starts
|
|
37
37
|
its network deadlines.
|
|
38
38
|
|
|
39
|
+
CI retains this single receipt after successful and failed static jobs, with a
|
|
40
|
+
separate artifact name for each run attempt and seven-day retention. Inspect both
|
|
41
|
+
feeds before diagnosing a blocked collector: a positive advisory is a refusal,
|
|
42
|
+
even when the other feed is clean.
|
|
43
|
+
|
|
39
44
|
The command then verifies the installed CodSpeed bundle policy. Network/audit errors,
|
|
40
45
|
missing packages, incomplete policy entries, changed package files, and unreviewed
|
|
41
46
|
upgrades all fail. `quality:static` includes this command, so the existing CI,
|
|
@@ -50,6 +55,31 @@ defaults to the `latest` image. The workflow contract checks both values on
|
|
|
50
55
|
every TruffleHog step. Recheck the action's executable selectors when renewing
|
|
51
56
|
its commit pin.
|
|
52
57
|
|
|
58
|
+
## Vitest mocker advisory pair
|
|
59
|
+
|
|
60
|
+
[pm-vitest4112](../.agents/pm/tasks/pm-vitest4112.toon) coordinates the
|
|
61
|
+
[manual-registration finding](../.agents/pm/issues/pm-vitestmanual.toon) and
|
|
62
|
+
[Windows redirect finding](../.agents/pm/issues/pm-vitestredirect.toon).
|
|
63
|
+
[GHSA-qxg6-89mg-rqv8](https://github.com/advisories/GHSA-qxg6-89mg-rqv8)
|
|
64
|
+
and [GHSA-xp26-3454-mjgp](https://github.com/advisories/GHSA-xp26-3454-mjgp)
|
|
65
|
+
affect the previously locked `@vitest/mocker` 4.1.11. The compatible Vitest and
|
|
66
|
+
coverage-provider pair now requires 4.1.12, whose shared mock-source generator
|
|
67
|
+
serializes module URLs and export names, including asynchronous rebinding, and
|
|
68
|
+
whose public redirect handler normalizes backslashes before the file allowlist
|
|
69
|
+
check. Both fixes apply to public mocker/interceptor WebSocket handlers. The
|
|
70
|
+
repository's Node test configurations do not register these handlers, so the
|
|
71
|
+
affected development dependency does not establish an exploitable CLI or SDK
|
|
72
|
+
route.
|
|
73
|
+
|
|
74
|
+
The retained hosted OSV receipt identified both advisories while the registry
|
|
75
|
+
audit returned zero. Keep full-lock admission mandatory and verify the patched
|
|
76
|
+
graph against both feeds. This patch keeps Vitest 4, the CodSpeed peer contract
|
|
77
|
+
and the Vite 7 override; the broader Vitest 5 migration remains separate. The
|
|
78
|
+
installed-handler regressions run in coverage and the required native Windows
|
|
79
|
+
cohort, whose aggregate refuses failed, canceled or skipped partitions. The
|
|
80
|
+
October 15 patch releases meet pnpm's one-day minimum age on October 21 without
|
|
81
|
+
an exclusion. Dependabot's seven-day scheduling cooldown is unchanged.
|
|
82
|
+
|
|
53
83
|
## Indexed source-map offsets
|
|
54
84
|
|
|
55
85
|
[pm-dba47](../.agents/pm/issues/pm-dba47.toon) tracks Dependabot alert 47 and
|
|
@@ -76,8 +76,9 @@ Create path:
|
|
|
76
76
|
`mutateItem` path:
|
|
77
77
|
|
|
78
78
|
- Used by `pm update`, `pm close`, `pm claim`, `pm release`, `pm restore`, `pm delete`, and other mutations of existing items.
|
|
79
|
-
- Locates the item, acquires the lock, reads the item through the `onRead` hook, enforces ownership and history policy, applies the mutation, consults `item_store_write`, writes the item atomically, appends history, fires `onWrite` for the item and history stream, records the after-command affected-item snapshot, then releases the lock.
|
|
79
|
+
- Locates the item, acquires the lock, reads the item through the `onRead` hook, enforces ownership and history policy, applies the mutation, enters the commit fence, consults `item_store_write`, writes the item atomically, appends history, fires `onWrite` for the item and history stream, records the after-command affected-item snapshot, then releases the lock.
|
|
80
80
|
- `item_store_write` handlers can redirect the target path, replace serialized contents, or skip the physical item write.
|
|
81
|
+
- The commit fence verifies the lock lease and the stored item revision first. When the lease was taken over (`lock_lost`) or the item changed after the read (`stale_revision`), the mutation refuses before `item_store_write` or `item_store_delete` (non-dry-run) runs, so a handler that persists the payload itself never commits a refused write.
|
|
81
82
|
|
|
82
83
|
Both paths guarantee:
|
|
83
84
|
|
|
@@ -4,6 +4,9 @@ Tracked by [pm-ig5cfe](../.agents/pm/issues/pm-ig5cfe.toon),
|
|
|
4
4
|
[pm-495lkc](../.agents/pm/issues/pm-495lkc.toon), and
|
|
5
5
|
[pm-miy5k6](../.agents/pm/issues/pm-miy5k6.toon).
|
|
6
6
|
Static inventory is tracked by [pm-lhhnx9](../.agents/pm/issues/pm-lhhnx9.toon).
|
|
7
|
+
Verified replacements and disabled compatibility findings are tracked by
|
|
8
|
+
[pm-c9wz5ef4ulnv](../.agents/pm/issues/pm-c9wz5ef4ulnv.toon) and
|
|
9
|
+
[pm-5xn8we1wue0m](../.agents/pm/issues/pm-5xn8we1wue0m.toon).
|
|
7
10
|
|
|
8
11
|
## Read-only configured inventory
|
|
9
12
|
|
|
@@ -32,6 +35,45 @@ each. Use `pm install npm:<package>` to force npm identity or the reported bare
|
|
|
32
35
|
alias command to force the bundled package. Install-all results carry the same
|
|
33
36
|
receipt on every package row.
|
|
34
37
|
|
|
38
|
+
## Verified replacements
|
|
39
|
+
|
|
40
|
+
`pm package install` and the SDK's `runExtension(source, { install: true }, global)`
|
|
41
|
+
retain the previous installation through fresh-module runtime verification and
|
|
42
|
+
contribution persistence. When a copied replacement fails to load or activate,
|
|
43
|
+
pm restores the previous package tree and exact managed/settings content. A
|
|
44
|
+
settings restoration appends a compensating workspace-history event.
|
|
45
|
+
|
|
46
|
+
The failed attempt still returns `ok: false` and its candidate diagnostics, with
|
|
47
|
+
`previous_state_preserved: true`, `overwritten: false`, and
|
|
48
|
+
`rollback.status: "restored"`. The previous command remains available in a new
|
|
49
|
+
process. Multi-target and install-all receipts retain these fields per package.
|
|
50
|
+
SDK `ExtensionCommandResult` / `PackageCommandResult` consumers can narrow
|
|
51
|
+
`details.rollback.status` to access typed recovery arguments on failed restoration.
|
|
52
|
+
These two diagnostic keys are now reserved typed fields. Code constructing
|
|
53
|
+
lifecycle result objects or test doubles must use the boolean and rollback
|
|
54
|
+
shapes, or rename custom metadata stored under those keys. Ordinary result
|
|
55
|
+
readers remain compatible; the runtime implementation is unchanged by this
|
|
56
|
+
declaration refinement.
|
|
57
|
+
|
|
58
|
+
If restoration fails, `previous_state_preserved` is false. The retained backup
|
|
59
|
+
and executable `rollback.recovery_args` / `rollback.recovery_command` identify a
|
|
60
|
+
source for reinstalling the previous package. Resolve the reported filesystem
|
|
61
|
+
failure first. Fresh installations and installations whose source is already
|
|
62
|
+
the destination retain failed code for diagnosis and report
|
|
63
|
+
`previous_state_preserved: false`; they have no earlier package version to
|
|
64
|
+
recover. Package code can have external side effects during activation; this
|
|
65
|
+
snapshot protects the installation and its managed/settings content.
|
|
66
|
+
|
|
67
|
+
## Disabled package compatibility
|
|
68
|
+
|
|
69
|
+
An explicitly disabled package remains visible in inventory and discovery.
|
|
70
|
+
Its host-version warnings use `extension_disabled_pm_*` codes and are advisory
|
|
71
|
+
in health, including `--strict-exit`. Each finding identifies the package,
|
|
72
|
+
required bound and scoped activation command to use after satisfying that
|
|
73
|
+
bound. Enabled packages retain the ordinary `extension_pm_*` compatibility
|
|
74
|
+
checks and blocking policy. Deactivation does not permit incompatible code to
|
|
75
|
+
load or suppress malformed-manifest, missing-entry or other unrelated findings.
|
|
76
|
+
|
|
35
77
|
## Durable Extension Migrations
|
|
36
78
|
|
|
37
79
|
Active packages register schema migrations through `api.registerMigration`.
|
|
@@ -62,3 +104,8 @@ compose without warnings, and runtime invokes only the matching owner. Empty or
|
|
|
62
104
|
omitted command ownership retains the legacy global behavior and collides with
|
|
63
105
|
every other override. Activation summaries and persisted contribution
|
|
64
106
|
inventories expose `preflight_ownership` for static doctor and tooling output.
|
|
107
|
+
|
|
108
|
+
Rollback failures retain the initial error as their cause and expose an original
|
|
109
|
+
typed refusal as `context.original_code`, while keeping the rollback code and
|
|
110
|
+
exit policy. This diagnostic boundary is tracked by
|
|
111
|
+
[pm-4vymxc](../.agents/pm/issues/pm-4vymxc.toon).
|
|
@@ -1,12 +1,38 @@
|
|
|
1
1
|
# MCP Option Conformance
|
|
2
2
|
|
|
3
|
-
Tracked by [pm-v0s2rm](
|
|
3
|
+
Tracked by [pm-v0s2rm](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/chores/pm-v0s2rm.toon), [pm-gh1538](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-gh1538.toon), and [pm-ap7ilf](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-ap7ilf.toon).
|
|
4
|
+
These repository-only tracking records are excluded from npm.
|
|
4
5
|
|
|
5
6
|
An integration test can pass while an undeclared option is ignored. That leaves
|
|
6
7
|
the test asserting a behavior it never exercised. The repository therefore
|
|
7
8
|
treats emitted MCP unknown-option diagnostics as test failures, while the
|
|
8
9
|
production server retains its advisory warning behavior for clients.
|
|
9
10
|
|
|
11
|
+
`pm_create` and `pm_update` accept the canonical mutation fields advertised by
|
|
12
|
+
their input schemas either at the top level or inside `options`. `pm_run` with
|
|
13
|
+
the corresponding action uses the same SDK normalization. Explicit nested
|
|
14
|
+
options take precedence, including CLI
|
|
15
|
+
aliases for the same field. Repeatable inputs, numeric values, and linked-resource
|
|
16
|
+
aliases use the shared mutation contracts before persistence. Native SDK `run`
|
|
17
|
+
callers continue to put update fields inside `options`.
|
|
18
|
+
|
|
19
|
+
Malformed option containers, values discarded from known scalar fields, and malformed repeatable
|
|
20
|
+
entries refuse before execution with `invalid_argument_value` and
|
|
21
|
+
`changed: false`. Declared boolean mutation fields, their aliases, and receipt
|
|
22
|
+
controls must be booleans, including `false`; this covers clear/replace flags,
|
|
23
|
+
parent/dependency exceptions, duplicate allowance, and `force`. These checks preserve project-defined
|
|
24
|
+
custom fields and template-based type resolution while preventing a bad field
|
|
25
|
+
from disappearing as a sibling field is written.
|
|
26
|
+
Array values that become CLI CSV inputs, such as `tags`, must contain only
|
|
27
|
+
strings and are checked before conversion. Validation follows nested precedence:
|
|
28
|
+
an explicit valid nested value replaces a flat value before either is normalized.
|
|
29
|
+
|
|
30
|
+
For example, `{ "id": "pm-one", "options": {}, "priority": 4 }` changes priority;
|
|
31
|
+
with `"options": { "priority": 2 }`, priority becomes `2`. Create also honors its
|
|
32
|
+
advertised explicit `id`, `deadline`, and other metadata. Regressions check the
|
|
33
|
+
discovered schema, persisted values and verified history, and compare item and
|
|
34
|
+
history bytes after invalid mutations to prove atomic refusal.
|
|
35
|
+
|
|
10
36
|
Every run using [vitest.config.ts](../vitest.config.ts) installs
|
|
11
37
|
[McpContractReporter](../scripts/mcp-contract-reporter.mts). The reporter records
|
|
12
38
|
the emitting file, full test name, option, tool, and action, and rejects the run
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
Tracker references: [pm-3g3f8z](../.agents/pm/features/pm-3g3f8z.toon),
|
|
4
4
|
[pm-v7e337](../.agents/pm/features/pm-v7e337.toon),
|
|
5
5
|
[pm-3zh9s4](../.agents/pm/features/pm-3zh9s4.toon), and
|
|
6
|
-
[pm-vzcisw](../.agents/pm/chores/pm-vzcisw.toon)
|
|
6
|
+
[pm-vzcisw](../.agents/pm/chores/pm-vzcisw.toon), and executable probes
|
|
7
|
+
[pm-gh1540](../.agents/pm/issues/pm-gh1540.toon).
|
|
7
8
|
|
|
8
9
|
pm exposes the same MCP 2026-07-28 dispatcher through two adapters:
|
|
9
10
|
|
|
@@ -20,6 +21,20 @@ thin bindings over those contracts.
|
|
|
20
21
|
|
|
21
22
|
## Run the HTTP adapter
|
|
22
23
|
|
|
24
|
+
Both executables accept `--help` / `-h` and `--version` / `-V`. These probes
|
|
25
|
+
return before reading transport settings, opening stdin, or binding a listener,
|
|
26
|
+
including when server configuration is invalid. Help includes configuration,
|
|
27
|
+
supported protocol revisions, and a version-pinned client or launch example.
|
|
28
|
+
No arguments starts the selected transport. Unsupported arguments return usage
|
|
29
|
+
exit code 64 with a help command. Server configuration uses environment variables.
|
|
30
|
+
|
|
31
|
+
Embedded launchers can import `resolvePmMcpExecutableInvocation` and its
|
|
32
|
+
`PmMcpExecutableTransport` / `PmMcpExecutableInvocation` types from
|
|
33
|
+
`@unbrained/pm-cli/sdk/contracts` or the aggregate SDK. This pure function takes
|
|
34
|
+
the argument vector, transport, and package version, and returns either a
|
|
35
|
+
`serve` decision or an `exit` decision with stdout, stderr, and exit code.
|
|
36
|
+
It does not read the environment, workspace, or process streams.
|
|
37
|
+
|
|
23
38
|
The safe default binds only `127.0.0.1:3000`:
|
|
24
39
|
|
|
25
40
|
```bash
|
package/docs/MERGE_SAFETY.md
CHANGED
|
@@ -667,6 +667,14 @@ Never remove conflict markers manually while leaving the authoritative item and
|
|
|
667
667
|
|
|
668
668
|
`transactions/` and `checkpoints/` are per-branch crash-recovery state, not shared project context. `pm init` places both below the resolved tracker root in the managed `.gitignore` block, including custom `--pm-path` roots.
|
|
669
669
|
|
|
670
|
+
Each tracker sharing a repository retains its own cache exclusions and merge
|
|
671
|
+
patterns in either initialization order. Tracker names containing Git wildcard
|
|
672
|
+
characters are escaped as literal paths, while the generated item/history globs
|
|
673
|
+
remain active. Unrelated user rules and curated evaluation files are preserved.
|
|
674
|
+
These contracts are tracked by
|
|
675
|
+
[pm-nl60tbxzkjwp](../.agents/pm/issues/pm-nl60tbxzkjwp.toon) and
|
|
676
|
+
[pm-47wuv8awpqvj](../.agents/pm/issues/pm-47wuv8awpqvj.toon).
|
|
677
|
+
|
|
670
678
|
Ignore rules do not remove files that were already committed. `pm health`
|
|
671
679
|
(`integrity`) and `pm validate` (`storage_integrity`) therefore scan Git's
|
|
672
680
|
index for tracked files below `runtime/`, `search/`, `locks/`,
|
|
@@ -85,8 +85,87 @@ If brief still exceeds the ceiling, normal compaction and eventual refusal
|
|
|
85
85
|
apply. `estimated_tokens` measures the emitted envelope;
|
|
86
86
|
`omitted_result_estimated_tokens` measures the useful result that could not fit.
|
|
87
87
|
|
|
88
|
+
## Typed producer pages
|
|
89
|
+
|
|
90
|
+
Tracked by [pm-oybmsd](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-oybmsd.toon),
|
|
91
|
+
[pm-jqgkis](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-jqgkis.toon), and
|
|
92
|
+
[pm-qal1uw](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/tasks/pm-qal1uw.toon)
|
|
93
|
+
(repository-only tracker records excluded from npm).
|
|
94
|
+
Native `PmClient.list`, `search`, and `context` reads and their standalone
|
|
95
|
+
convenience functions return sparse metadata when `after` resumes a producer
|
|
96
|
+
page. Retain the first page's filters, projection, sorting, totals and completeness
|
|
97
|
+
certificate. Subsequent pages carry `continuation_contract` with
|
|
98
|
+
`metadata: "reference"` and `restore_with: "omit --after"`; repeated metadata
|
|
99
|
+
is optional in `PmReadOutputProducerContinuation<Result>`. Follow `next_cursor`
|
|
100
|
+
until it is empty, rather than relying on the optional `has_more` field.
|
|
101
|
+
|
|
102
|
+
Calls whose options contain no producer cursor retain their first-page types.
|
|
103
|
+
Omitting the options argument has the same precise type as passing `{}` for
|
|
104
|
+
client and standalone `list`, `context`, and `search` calls; it does not imply
|
|
105
|
+
that an explicit budget, session, or output cursor was supplied.
|
|
106
|
+
Options with an optional cursor can return either page shape. When a budget,
|
|
107
|
+
session, or output cursor is possible, first use `isReadOutputBudgetExceeded`
|
|
108
|
+
to narrow the omission branch before accessing rows. This remains required for
|
|
109
|
+
unions of option shapes: a budgeted variant cannot be erased by an unbudgeted
|
|
110
|
+
variant. The low-level query producers retain their original result contracts;
|
|
111
|
+
metadata suppression belongs to the shared native SDK transport.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
const first = await client.list({ brief: true, limit: "10" });
|
|
115
|
+
consume(first.items);
|
|
116
|
+
let cursor = first.next_cursor;
|
|
117
|
+
while (cursor) {
|
|
118
|
+
const page = await client.list({ brief: true, limit: "10", after: cursor });
|
|
119
|
+
consume(page.items);
|
|
120
|
+
cursor = page.next_cursor;
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
88
124
|
## Cross-Call Context Sessions
|
|
89
125
|
|
|
126
|
+
Typed session results are tracked by
|
|
127
|
+
[pm-t1p1jv](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-t1p1jv.toon)
|
|
128
|
+
(repository-only tracker record excluded from npm). After narrowing budget
|
|
129
|
+
omission with `isReadOutputBudgetExceeded`, use `isReadOutputSessionReference`
|
|
130
|
+
from the SDK or `sdk/core` before accessing row prose or a search hit's nested
|
|
131
|
+
`item`. A reference contains only `id` and `context_ref`. Calls with no session,
|
|
132
|
+
including calls that specify only an output budget, retain their ordinary row
|
|
133
|
+
types. The direct `applyReadOutputDimensions` helper also infers session
|
|
134
|
+
capability from its options, including the accepted `output_session` spelling.
|
|
135
|
+
Unspecified or explicitly undefined session options retain ordinary rows;
|
|
136
|
+
optional, union, or dynamically typed session options admit references.
|
|
137
|
+
`PmReadOutputSessionResult<Result>` models literal `row_contract.row_keys`
|
|
138
|
+
paths (including nested arrays and keyed maps). Dynamic path declarations
|
|
139
|
+
conservatively admit references in nested collections, and envelopes without a
|
|
140
|
+
row declaration use the top-level array fallback. Keep package row paths literal
|
|
141
|
+
with `as const` to preserve precision in package result declarations.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
const page = await client.context({ outputSession: sessionState });
|
|
145
|
+
if (!isReadOutputBudgetExceeded(page)) {
|
|
146
|
+
for (const row of page.high_level) {
|
|
147
|
+
if (isReadOutputSessionReference(row)) consumeReference(row.context_ref);
|
|
148
|
+
else consumeTitle(row.title);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Read-option recovery is tracked by
|
|
154
|
+
[pm-7gpx34](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-7gpx34.toon)
|
|
155
|
+
(repository-only tracker record excluded from npm). For explicit static encoding,
|
|
156
|
+
use `--output-format toon|json` across read commands. Completeness uses
|
|
157
|
+
`--output-include full` on list, search and get; context detail depth uses
|
|
158
|
+
`--depth full`. These controls retain the normal row and token ceilings.
|
|
159
|
+
When `context --full`, `list/search --depth full`, or `--toon` on list, search,
|
|
160
|
+
context or get is refused,
|
|
161
|
+
the error provides `recovery.suggested_retry_args` for the same command with the
|
|
162
|
+
supported spelling. The SDK's `resolveReadOptionRetryArgs` exposes the same
|
|
163
|
+
token-preserving correction. Conflicting controls or ambiguous operands do not
|
|
164
|
+
receive a guessed retry.
|
|
165
|
+
An explicit `--json` conflicts with `--toon`, so that invocation receives no
|
|
166
|
+
encoding-changing retry. Non-conflicting TOON refusals include the corrected
|
|
167
|
+
command in human output without requiring JSON diagnostics.
|
|
168
|
+
|
|
90
169
|
`--output-session <json>` / `outputSession` composes the four per-call
|
|
91
170
|
dimensions across a request group. The caller supplies versioned state and
|
|
92
171
|
passes the returned `read_session.next_state` to the next read:
|
|
@@ -238,6 +238,21 @@ Every returned node is explainable: `role` names its semantic family (`prerequis
|
|
|
238
238
|
|
|
239
239
|
`meta.completeness` reports result quality: the exact in-memory kernel emits `complete` or `truncated`, and the contract reserves `sampled`, `approximate`, `stale_index`, and `redacted` for index-backed or policy-filtered providers so consumers can branch on one field.
|
|
240
240
|
|
|
241
|
+
The packer orders direct prerequisite and ancestor endpoints before optional
|
|
242
|
+
descendants or related nodes, reserves their direct relationship costs, and
|
|
243
|
+
emits root-incident edges first. An oversized edge is skipped so later feasible
|
|
244
|
+
edges can still be returned. The same order governs cursor pages; the cursor
|
|
245
|
+
fingerprint identifies this packing policy and rejects earlier-policy cursors. An
|
|
246
|
+
edge limit or a budget too small for an endpoint and its edges can still omit
|
|
247
|
+
evidence: inspect the direct-family totals, omitted counts and completeness
|
|
248
|
+
before using the packet to make a decision. The native adapter also reports
|
|
249
|
+
`relationship_context_bounds` in `omission_receipt` whenever the producer is
|
|
250
|
+
truncated. Increase the relevant bounds or follow the node cursor for more
|
|
251
|
+
detail; a full projection alone does not remove traversal limits.
|
|
252
|
+
|
|
253
|
+
Tracked by [pm-gh1534](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-gh1534.toon)
|
|
254
|
+
(repository tracker records are excluded from npm).
|
|
255
|
+
|
|
241
256
|
Node, edge, depth, kind, direction, and token bounds are independent. The cursor fingerprint covers semantic filters and traversal shape, so it is rejected when reused for a different root or query. Output remains a plain object suitable for TOON, JSON, JSONL, MCP, or a custom UI; adapters own rendering and do not reimplement traversal.
|
|
242
257
|
|
|
243
258
|
The native adapter is `pm deps <id> --format context`. It is also available through `PmClient.deps`, `runAction({ action: "deps" })`, and the MCP `pm_deps` tool. `--max-depth`, `--node-limit`, `--edge-limit`, `--token-budget`, `--cursor`, `--direction`, and repeatable or comma-separated `--kind` map directly to the public SDK context options; `--summary` keeps only counts. Unknown `--kind` values fail fast with the registered-kind list instead of silently matching nothing. Tree and graph formats share those first four bounds, use safe defaults when omitted, and report deterministic truncation metadata instead of recursively expanding a cyclic or multiply-linked graph without limit.
|
package/docs/RELEASING.md
CHANGED
|
@@ -171,6 +171,15 @@ configuration belong in private operator documentation.
|
|
|
171
171
|
result before candidate provenance, proves publication, and exits without
|
|
172
172
|
evaluating the generated release commit as a new candidate
|
|
173
173
|
|
|
174
|
+
Dependency advisory diagnostics are tracked by
|
|
175
|
+
[pm-z20n3s](../.agents/pm/tasks/pm-z20n3s.toon). Both Auto Release and Release
|
|
176
|
+
retain the original `.cache/dependency-security/receipt.json` after the gate,
|
|
177
|
+
including failures, as `dependency-advisory-<run-id>-<attempt>` for seven days.
|
|
178
|
+
Only that public receipt is uploaded, with explicit hidden-file inclusion.
|
|
179
|
+
It records registry and OSV findings or unavailable evidence without raw
|
|
180
|
+
stderr, credentials, telemetry, or sibling caches. Missing receipts warn rather
|
|
181
|
+
than masking the original failure. Retention never changes the gate verdict.
|
|
182
|
+
|
|
174
183
|
Pipeline entrypoint:
|
|
175
184
|
|
|
176
185
|
```bash
|
|
@@ -315,6 +324,14 @@ missing locked package. npm 12 ignores published shrinkwraps; shipping
|
|
|
315
324
|
a shrinkwrap alone therefore cannot enforce this contract. SDK consumers
|
|
316
325
|
supply the bounded optional Node types peer themselves.
|
|
317
326
|
|
|
327
|
+
Physical entrypoint verification is tracked by
|
|
328
|
+
[pm-tl7vlu](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-tl7vlu.toon)
|
|
329
|
+
(repository-only tracker record excluded from npm). The dependency advisory CLI
|
|
330
|
+
resolves both its invocation and module to physical paths, so directory aliases
|
|
331
|
+
and Node's symlink-preserving main-module mode still execute verification and
|
|
332
|
+
return a receipt or failure status. Module imports expose the collector without
|
|
333
|
+
starting verification.
|
|
334
|
+
|
|
318
335
|
Push the final implementation commit through a reviewed pull request first,
|
|
319
336
|
wait for DeepScan and CodeFactor to finish on that reviewed SHA, then run the
|
|
320
337
|
canonical registry-owned preflight:
|