@unbrained/pm-cli 2026.8.13 → 2026.8.14

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 (136) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +141 -112
  3. package/dist/cli/main.js +39 -3
  4. package/dist/cli-bundle/bundle-manifest.json +144 -176
  5. package/dist/cli-bundle/chunks/{chunk-DEDU3DG7.js → chunk-BBOFSVAK.js} +66 -65
  6. package/dist/cli-bundle/chunks/chunk-BFALV3YP.js +5 -0
  7. package/dist/cli-bundle/chunks/{chunk-Q47C67TQ.js → chunk-FIW4P64F.js} +2 -2
  8. package/dist/cli-bundle/chunks/{chunk-QGTUPEU5.js → chunk-G5JUB66K.js} +41 -41
  9. package/dist/cli-bundle/chunks/{chunk-3ZMOWAFM.js → chunk-SBRKH6OR.js} +2 -2
  10. package/dist/cli-bundle/chunks/chunk-TMJ7URWS.js +13 -0
  11. package/dist/cli-bundle/chunks/{chunk-F7FJYGK3.js → chunk-TXMEUUUY.js} +2 -2
  12. package/dist/cli-bundle/chunks/{chunk-YLBAPY22.js → chunk-XAVMOK6B.js} +2 -2
  13. package/dist/cli-bundle/chunks/{chunk-TF3TPKPR.js → chunk-YAMTJL6Y.js} +33 -33
  14. package/dist/cli-bundle/chunks/{chunk-IMGIN7R6.js → chunk-YSVSMJ2B.js} +2 -2
  15. package/dist/cli-bundle/chunks/{register-list-query-4HIZ7EA3.js → register-list-query-T3EHHNNV.js} +2 -2
  16. package/dist/cli-bundle/chunks/{register-mutation-W64FZ5HN.js → register-mutation-E27WCLSZ.js} +2 -2
  17. package/dist/cli-bundle/chunks/{register-operations-C44ET7R5.js → register-operations-4R6I7CPY.js} +2 -2
  18. package/dist/cli-bundle/chunks/{register-setup-634MFRXU.js → register-setup-3YXZ444E.js} +2 -2
  19. package/dist/cli-bundle/focused-chunks/{chunk-OOYYKQWX.js → chunk-4DQ2YERC.js} +2 -2
  20. package/dist/cli-bundle/focused-chunks/chunk-F43TT2QQ.js +8 -0
  21. package/dist/cli-bundle/focused-chunks/{chunk-2RI2FESN.js → chunk-K7RWWOAG.js} +2 -2
  22. package/dist/cli-bundle/focused-chunks/chunk-KFSZ3BP6.js +16 -0
  23. package/dist/cli-bundle/focused-chunks/{chunk-3DJSGWVB.js → chunk-KSEDUWVZ.js} +2 -2
  24. package/dist/cli-bundle/focused-chunks/{chunk-NFHFZH2Z.js → chunk-KYUQGTSB.js} +2 -2
  25. package/dist/cli-bundle/focused-chunks/{chunk-IIS2FRKL.js → chunk-LZ4MV3RH.js} +2 -2
  26. package/dist/cli-bundle/focused-chunks/chunk-PGXJQLCT.js +155 -0
  27. package/dist/cli-bundle/focused-chunks/{chunk-E3AVJ655.js → chunk-PPFGQDSI.js} +2 -2
  28. package/dist/cli-bundle/focused-chunks/chunk-Q4YY3XNF.js +2 -0
  29. package/dist/cli-bundle/focused-chunks/chunk-QK753U3U.js +46 -0
  30. package/dist/cli-bundle/focused-chunks/{chunk-QDZUABWL.js → chunk-QUMB2J27.js} +2 -2
  31. package/dist/cli-bundle/focused-chunks/chunk-SM7MTG27.js +2 -0
  32. package/dist/cli-bundle/focused-chunks/{chunk-I75MANL7.js → chunk-WCVE2NIG.js} +2 -2
  33. package/dist/cli-bundle/focused-chunks/chunk-XDNAW5OI.js +18 -0
  34. package/dist/cli-bundle/focused-chunks/{chunk-2JP7W7GO.js → chunk-YOOI3UZX.js} +2 -2
  35. package/dist/cli-bundle/focused-chunks/chunk-ZM6TZVHC.js +2 -0
  36. package/dist/cli-bundle/main.js +13 -13
  37. package/dist/cli-bundle/sdk-authoring.js +1 -1
  38. package/dist/cli-bundle/sdk-contracts.js +1 -1
  39. package/dist/cli-bundle/sdk-core.js +44 -41
  40. package/dist/cli-bundle/sdk-governance.js +1 -1
  41. package/dist/cli-bundle/sdk-graph.js +1 -1
  42. package/dist/cli-bundle/sdk-merge.js +109 -1
  43. package/dist/cli-bundle/sdk-query.js +1 -1
  44. package/dist/cli-bundle/sdk-runtime.js +1 -1
  45. package/dist/cli-bundle/sdk-testing.js +1 -1
  46. package/dist/cli-bundle/sdk.js +2 -2
  47. package/dist/core/history/drift-scan.d.ts +6 -0
  48. package/dist/core/history/drift-scan.js +74 -50
  49. package/dist/core/history/workspace-history.d.ts +59 -0
  50. package/dist/core/history/workspace-history.js +197 -10
  51. package/dist/core/output/output.js +12 -4
  52. package/dist/sdk/cli-contracts/runtime-contracts.d.ts +6 -1
  53. package/dist/sdk/cli-contracts/runtime-contracts.js +155 -60
  54. package/dist/sdk/cli-program.js +3 -3
  55. package/dist/sdk/context-relevance.js +12 -4
  56. package/dist/sdk/governance/assurance.js +4 -4
  57. package/dist/sdk/governance/health.js +15 -3
  58. package/dist/sdk/governance/validate-history-drift.d.ts +7 -0
  59. package/dist/sdk/governance/validate-history-drift.js +68 -0
  60. package/dist/sdk/governance/validate.js +4 -46
  61. package/dist/sdk/graph/assembly.d.ts +23 -0
  62. package/dist/sdk/graph/assembly.js +54 -2
  63. package/dist/sdk/graph/durable-cache.js +50 -20
  64. package/dist/sdk/graph/governance-contracts.d.ts +1 -1
  65. package/dist/sdk/graph/governance-contracts.js +4 -2
  66. package/dist/sdk/graph/governance.d.ts +21 -1
  67. package/dist/sdk/graph/governance.js +94 -32
  68. package/dist/sdk/graph/index.d.ts +2 -2
  69. package/dist/sdk/graph/index.js +4 -4
  70. package/dist/sdk/graph/mutation-advisory.js +15 -7
  71. package/dist/sdk/graph/remediation.js +12 -2
  72. package/dist/sdk/history-read.d.ts +8 -0
  73. package/dist/sdk/history-read.js +2 -2
  74. package/dist/sdk/index.d.ts +1 -1
  75. package/dist/sdk/index.js +3 -3
  76. package/dist/sdk/query/history.js +20 -3
  77. package/dist/sdk/read-output-budget.d.ts +2 -0
  78. package/dist/sdk/read-output-budget.js +12 -5
  79. package/dist/sdk/read-output-contracts.d.ts +61 -5
  80. package/dist/sdk/read-output-contracts.js +210 -28
  81. package/dist/sdk/read-output-rows.d.ts +11 -0
  82. package/dist/sdk/read-output-rows.js +47 -2
  83. package/dist/sdk/runtime-primitives.d.ts +1 -1
  84. package/dist/sdk/runtime-primitives.js +3 -3
  85. package/dist/sdk/runtime.js +5 -3
  86. package/docs/ASSURANCE.md +10 -1
  87. package/docs/COMMANDS.md +31 -2
  88. package/docs/READ_OUTPUT_CONTRACTS.md +44 -8
  89. package/docs/RELATIONSHIP_GRAPH.md +20 -5
  90. package/docs/SDK.md +12 -5
  91. package/docs/SDK_CONTEXT_INTEGRITY.md +32 -1
  92. package/docs/TESTING.md +23 -1
  93. package/docs/performance/sdk-entrypoint-import-costs.md +20 -13
  94. package/marketplace.json +2 -2
  95. package/package.json +6 -6
  96. package/packages/pm-beads/extensions/beads/index.ts +1 -0
  97. package/packages/pm-beads/package.json +1 -1
  98. package/packages/pm-calendar/extensions/calendar/index.ts +1 -0
  99. package/packages/pm-calendar/package.json +1 -1
  100. package/packages/pm-command-kit/package.json +1 -1
  101. package/packages/pm-digital-twin/extensions/digital-twin/index.ts +7 -0
  102. package/packages/pm-digital-twin/package.json +1 -1
  103. package/packages/pm-governance-audit/extensions/governance-audit/index.ts +4 -0
  104. package/packages/pm-governance-audit/package.json +1 -1
  105. package/packages/pm-guide-shell/extensions/guide-shell/index.ts +5 -0
  106. package/packages/pm-guide-shell/package.json +1 -1
  107. package/packages/pm-kanban/package.json +1 -1
  108. package/packages/pm-lifecycle-hooks/package.json +1 -1
  109. package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/index.ts +6 -0
  110. package/packages/pm-linked-test-adapters/package.json +1 -1
  111. package/packages/pm-search-advanced/extensions/search-advanced/index.ts +2 -0
  112. package/packages/pm-search-advanced/package.json +1 -1
  113. package/packages/pm-templates/extensions/templates/index.ts +4 -0
  114. package/packages/pm-templates/package.json +1 -1
  115. package/packages/pm-todos/extensions/todos/index.ts +2 -0
  116. package/packages/pm-todos/package.json +1 -1
  117. package/packages/pm-vcs/extensions/vcs/index.ts +7 -0
  118. package/packages/pm-vcs/package.json +1 -1
  119. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  120. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  121. package/scripts/bundle-cli.mjs +17 -4
  122. package/sdk/public-surface.json +209 -29
  123. package/dist/cli-bundle/chunks/chunk-DL5M5J3V.js +0 -13
  124. package/dist/cli-bundle/chunks/chunk-HIUZVCMN.js +0 -5
  125. package/dist/cli-bundle/focused-chunks/chunk-27N23W6M.js +0 -31
  126. package/dist/cli-bundle/focused-chunks/chunk-3BNVU6LC.js +0 -8
  127. package/dist/cli-bundle/focused-chunks/chunk-6MLIRBLV.js +0 -5
  128. package/dist/cli-bundle/focused-chunks/chunk-6XYLENDV.js +0 -155
  129. package/dist/cli-bundle/focused-chunks/chunk-CP4K4WFS.js +0 -2
  130. package/dist/cli-bundle/focused-chunks/chunk-EBTNSFYE.js +0 -2
  131. package/dist/cli-bundle/focused-chunks/chunk-HC2OL2H2.js +0 -2
  132. package/dist/cli-bundle/focused-chunks/chunk-OGC4XSBC.js +0 -17
  133. package/dist/cli-bundle/focused-chunks/chunk-PKPOZXLL.js +0 -2
  134. package/dist/cli-bundle/focused-chunks/chunk-QQX5TDFT.js +0 -16
  135. package/dist/cli-bundle/focused-chunks/chunk-TEZIXMTH.js +0 -2
  136. package/dist/cli-bundle/focused-chunks/chunk-WEH6NSL6.js +0 -18
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a6714da6-6e2b-5647-bfde-607a54d7e4cd")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="32538934-2cbb-50fe-b721-ad9197cef7d8")}catch(e){}}();
3
3
  import fs from "node:fs/promises";
4
4
  import { createHash } from "node:crypto";
5
5
  import path from "node:path";
@@ -7,7 +7,7 @@ import { getHistoryPath } from "../store/paths.js";
7
7
  import { isFileMissingError, writeFileAtomic } from "../fs/fs-utils.js";
8
8
  import { CURRENT_HISTORY_ITEM_HASH_VERSION, hashDocumentForVersion, } from "./history.js";
9
9
  import { verifyHistoryChainWithVersion } from "./replay.js";
10
- import { getWorkspaceHistoryPath, WORKSPACE_HISTORY_ID, } from "./workspace-history.js";
10
+ import { getWorkspaceHistoryPath, inspectWorkspaceHistoryState, WORKSPACE_HISTORY_ID, } from "./workspace-history.js";
11
11
  const DRIFT_CACHE_VERSION = 4;
12
12
  const DRIFT_CACHE_FILENAME = "history-drift-cache.json";
13
13
  function getDriftCachePath(pmRoot) {
@@ -173,6 +173,61 @@ async function resolveStreamVerification(params) {
173
173
  cacheDirty: true,
174
174
  };
175
175
  }
176
+ /** Scan one item stream and return whether its cache entry needs persistence. */
177
+ async function scanItemHistory(pmRoot, item, cache, accumulator) {
178
+ const historyPath = getHistoryPath(pmRoot, item.id);
179
+ let stat;
180
+ try {
181
+ stat = await fs.stat(historyPath);
182
+ }
183
+ catch (error) {
184
+ (isFileMissingError(error)
185
+ ? accumulator.missingStreams
186
+ : accumulator.unreadableStreams).push(item.id);
187
+ return false;
188
+ }
189
+ const resolved = await resolveStreamVerification({
190
+ itemId: item.id,
191
+ historyPath,
192
+ stat,
193
+ cached: cache.previousEntries[item.id],
194
+ verifyCacheHitByContent: cache.verifyCacheHitByContent,
195
+ accumulator,
196
+ });
197
+ if (!resolved.verification)
198
+ return resolved.cacheDirty;
199
+ if (!resolved.verification.chainOk)
200
+ accumulator.chainMismatches.push(item.id);
201
+ cache.nextEntries[item.id] = {
202
+ mtime_ms: stat.mtimeMs,
203
+ ctime_ms: stat.ctimeMs,
204
+ size: stat.size,
205
+ content_hash: resolved.verification.contentHash,
206
+ latest_after_hash: resolved.verification.latestAfterHash,
207
+ chain_ok: resolved.verification.chainOk,
208
+ item_hash_version: resolved.verification.itemHashVersion,
209
+ };
210
+ const { body, ...itemMetadata } = item;
211
+ const currentHash = hashDocumentForVersion({ metadata: itemMetadata, body }, resolved.verification.itemHashVersion);
212
+ if (currentHash !== resolved.verification.latestAfterHash)
213
+ accumulator.hashMismatches.push(item.id);
214
+ return resolved.cacheDirty;
215
+ }
216
+ /** Compare the verified workspace stream with every governed singleton file. */
217
+ async function scanWorkspaceStateAgreement(pmRoot, accumulator) {
218
+ if (accumulator.unreadableStreams.includes(WORKSPACE_HISTORY_ID) ||
219
+ accumulator.chainMismatches.includes(WORKSPACE_HISTORY_ID))
220
+ return;
221
+ try {
222
+ const agreement = await inspectWorkspaceHistoryState(pmRoot);
223
+ accumulator.workspaceStateMismatches.push(...agreement.mismatched_documents);
224
+ accumulator.workspaceStateMissing.push(...agreement.missing_documents);
225
+ accumulator.workspaceStateUnreadable.push(...agreement.unreadable_documents);
226
+ }
227
+ catch {
228
+ accumulator.unreadableStreams.push(WORKSPACE_HISTORY_ID);
229
+ }
230
+ }
176
231
  /**
177
232
  * Scan every item's history stream for drift (missing/unreadable streams, broken
178
233
  * hash chains, and item/history hash mismatches).
@@ -189,6 +244,9 @@ export async function scanHistoryDrift(pmRoot, items, options = {}) {
189
244
  unreadableStreams: [],
190
245
  hashMismatches: [],
191
246
  chainMismatches: [],
247
+ workspaceStateMismatches: [],
248
+ workspaceStateMissing: [],
249
+ workspaceStateUnreadable: [],
192
250
  };
193
251
  const cache = await loadDriftCache(pmRoot);
194
252
  const previousEntries = cache?.entries ?? {};
@@ -197,57 +255,18 @@ export async function scanHistoryDrift(pmRoot, items, options = {}) {
197
255
  // Metadata mode does not open a stat-matched stream, so unreadable-after-stat
198
256
  // failures are intentionally deferred to strict validate/history-repair scans.
199
257
  const verifyCacheHitByContent = options.cacheHitVerification !== "metadata";
258
+ const cacheState = {
259
+ previousEntries,
260
+ nextEntries,
261
+ verifyCacheHitByContent,
262
+ };
200
263
  for (const item of items) {
201
- const historyPath = getHistoryPath(pmRoot, item.id);
202
- let stat;
203
- try {
204
- stat = await fs.stat(historyPath);
205
- }
206
- catch (error) {
207
- if (isFileMissingError(error)) {
208
- accumulator.missingStreams.push(item.id);
209
- }
210
- else {
211
- accumulator.unreadableStreams.push(item.id);
212
- }
213
- continue;
214
- }
215
- const cached = previousEntries[item.id];
216
- const resolved = await resolveStreamVerification({
217
- itemId: item.id,
218
- historyPath,
219
- stat,
220
- cached,
221
- verifyCacheHitByContent,
222
- accumulator,
223
- });
224
- cacheDirty ||= resolved.cacheDirty;
225
- if (!resolved.verification) {
226
- continue;
227
- }
228
- if (!resolved.verification.chainOk) {
229
- accumulator.chainMismatches.push(item.id);
230
- }
231
- nextEntries[item.id] = {
232
- mtime_ms: stat.mtimeMs,
233
- ctime_ms: stat.ctimeMs,
234
- size: stat.size,
235
- content_hash: resolved.verification.contentHash,
236
- latest_after_hash: resolved.verification.latestAfterHash,
237
- chain_ok: resolved.verification.chainOk,
238
- item_hash_version: resolved.verification.itemHashVersion,
239
- };
240
- const { body, ...itemMetadata } = item;
241
- const currentHash = hashDocumentForVersion({
242
- metadata: itemMetadata,
243
- body,
244
- }, resolved.verification.itemHashVersion);
245
- if (currentHash !== resolved.verification.latestAfterHash) {
246
- accumulator.hashMismatches.push(item.id);
247
- }
264
+ if (await scanItemHistory(pmRoot, item, cacheState, accumulator))
265
+ cacheDirty = true;
248
266
  }
249
267
  const workspaceCacheDirty = await scanWorkspaceHistory(pmRoot, previousEntries, nextEntries, verifyCacheHitByContent, accumulator);
250
268
  cacheDirty ||= workspaceCacheDirty;
269
+ await scanWorkspaceStateAgreement(pmRoot, accumulator);
251
270
  if (cacheDirty ||
252
271
  Object.keys(previousEntries).length !== Object.keys(nextEntries).length) {
253
272
  const cachePath = getDriftCachePath(pmRoot);
@@ -265,9 +284,14 @@ export async function scanHistoryDrift(pmRoot, items, options = {}) {
265
284
  ...accumulator.unreadableStreams,
266
285
  ...accumulator.hashMismatches,
267
286
  ...accumulator.chainMismatches,
287
+ ...(accumulator.workspaceStateMismatches.length > 0 ||
288
+ accumulator.workspaceStateMissing.length > 0 ||
289
+ accumulator.workspaceStateUnreadable.length > 0
290
+ ? [WORKSPACE_HISTORY_ID]
291
+ : []),
268
292
  ]),
269
293
  ].sort((a, b) => a.localeCompare(b));
270
294
  return { ...accumulator, driftedItems };
271
295
  }
272
296
  //# sourceMappingURL=drift-scan.js.map
273
- //# debugId=a6714da6-6e2b-5647-bfde-607a54d7e4cd
297
+ //# debugId=32538934-2cbb-50fe-b721-ad9197cef7d8
@@ -76,6 +76,47 @@ export interface WorkspaceAuditEventOptions {
76
76
  /** Maximum lock wait in milliseconds. */
77
77
  lockWaitMs: number;
78
78
  }
79
+ /** Agreement between the replayed workspace stream and governed singleton files. */
80
+ export interface WorkspaceHistoryStateAgreement {
81
+ /** Whether every governed singleton is readable and equals its replayed value. */
82
+ ok: boolean;
83
+ /** Number of singleton documents represented by the latest replayed state. */
84
+ document_count: number;
85
+ /** Governed documents whose on-disk JSON equals the replayed value. */
86
+ matching_documents: string[];
87
+ /** Governed documents whose on-disk JSON differs from the replayed value. */
88
+ mismatched_documents: string[];
89
+ /** Governed documents absent from disk. */
90
+ missing_documents: string[];
91
+ /** Governed documents that cannot be read as safe workspace-local JSON. */
92
+ unreadable_documents: string[];
93
+ }
94
+ /** Authorized append-only reconciliation of one out-of-band singleton state. */
95
+ export interface WorkspaceJsonReconciliationOptions extends Omit<WorkspaceJsonWriteOptions, "raw" | "recordCreation"> {
96
+ /** Closed Decision item authorizing acceptance of the on-disk state. */
97
+ authorizationDecision: string;
98
+ }
99
+ /** Restore one governed singleton from a recorded workspace history version. */
100
+ export interface WorkspaceJsonRestoreOptions extends Omit<WorkspaceJsonWriteOptions, "raw" | "recordCreation"> {
101
+ /** One-based workspace history version whose document value is restored. */
102
+ targetVersion: number;
103
+ }
104
+ /** Receipt returned after a singleton is restored from workspace history. */
105
+ export interface WorkspaceJsonRestoreResult {
106
+ /** Tracker-relative singleton identity. */
107
+ document_path: string;
108
+ /** One-based workspace history version used as the restore source. */
109
+ restored_from_version: number;
110
+ /** Whether the on-disk singleton and latest history state changed. */
111
+ changed: boolean;
112
+ }
113
+ /**
114
+ * Compare every singleton represented by the verified workspace stream with
115
+ * its current on-disk JSON. Hash-chain verification proves recorded ordering;
116
+ * this state-agreement check separately proves that every current singleton
117
+ * mutation is represented by that chain.
118
+ */
119
+ export declare function inspectWorkspaceHistoryState(pmRoot: string): Promise<WorkspaceHistoryStateAgreement>;
79
120
  /**
80
121
  * Append one workspace document mutation under the dedicated workspace-history
81
122
  * lock. Existing history is verified and replayed before the new entry is
@@ -103,3 +144,21 @@ export declare function mutateWorkspaceJsonWithHistory<Result>(params: Workspace
103
144
  * under the workspace-history lock, with compensation if history append fails.
104
145
  */
105
146
  export declare function writeWorkspaceJsonWithHistory(params: WorkspaceJsonWriteOptions): Promise<boolean>;
147
+ /**
148
+ * Append an authorized reconciliation event that adopts a reviewed on-disk
149
+ * singleton after an out-of-band change. The existing stream is never edited:
150
+ * its latest replayed state becomes the event before-state and the reviewed
151
+ * singleton becomes the after-state, with the authorizing Decision recorded in
152
+ * structured context.
153
+ */
154
+ export declare function reconcileWorkspaceJsonHistory(options: WorkspaceJsonReconciliationOptions): Promise<{
155
+ changed: boolean;
156
+ entry?: HistoryEntry;
157
+ historyPath: string;
158
+ }>;
159
+ /**
160
+ * Restore one governed JSON singleton to the value recorded at a one-based
161
+ * workspace history version, then append the restore as a new history state so
162
+ * replay remains forward-only and independently verifiable.
163
+ */
164
+ export declare function restoreWorkspaceJsonFromHistory(options: WorkspaceJsonRestoreOptions): Promise<WorkspaceJsonRestoreResult>;
@@ -5,7 +5,7 @@
5
5
  * HistoryEntry format used by item mutations.
6
6
  */
7
7
 
8
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="86b45d6b-2192-5d31-8f73-94d6e6b17dfc")}catch(e){}}();
8
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0f666995-78f8-5741-ae0c-0d792feea599")}catch(e){}}();
9
9
  import fs from "node:fs/promises";
10
10
  import path from "node:path";
11
11
  import { readFileIfExists, writeFileAtomic } from "../fs/fs-utils.js";
@@ -37,14 +37,83 @@ function workspaceDocument(documents, timestamp) {
37
37
  };
38
38
  return { metadata, body: "" };
39
39
  }
40
- function replayWorkspaceEntries(entries) {
40
+ function replayWorkspaceEntries(entries, count = entries.length) {
41
41
  let replay = cloneEmptyReplayDocument();
42
- for (const entry of entries) {
42
+ for (const entry of entries.slice(0, count)) {
43
43
  const applied = tryApplyReplayPatch(replay, entry.patch);
44
44
  replay = applied.document;
45
45
  }
46
46
  return replayToItemDocument(replay);
47
47
  }
48
+ /** Read the governed document map from one replayed workspace document. */
49
+ function workspaceDocuments(document) {
50
+ return "documents" in document.metadata &&
51
+ typeof document.metadata.documents === "object" &&
52
+ document.metadata.documents !== null &&
53
+ !Array.isArray(document.metadata.documents)
54
+ ? document.metadata.documents
55
+ : {};
56
+ }
57
+ /** Resolve a governed singleton path and reject paths outside the tracker root. */
58
+ function resolveGovernedDocumentPath(pmRoot, filePath) {
59
+ const absolutePath = path.resolve(filePath);
60
+ const documentPath = path.relative(pmRoot, absolutePath).replaceAll("\\", "/");
61
+ if (documentPath.length === 0 ||
62
+ documentPath === ".." ||
63
+ documentPath.startsWith("../") ||
64
+ path.isAbsolute(documentPath)) {
65
+ throw new TypeError("Workspace history document path must stay inside the tracker root");
66
+ }
67
+ return { documentPath, absolutePath };
68
+ }
69
+ /**
70
+ * Compare every singleton represented by the verified workspace stream with
71
+ * its current on-disk JSON. Hash-chain verification proves recorded ordering;
72
+ * this state-agreement check separately proves that every current singleton
73
+ * mutation is represented by that chain.
74
+ */
75
+ export async function inspectWorkspaceHistoryState(pmRoot) {
76
+ const entries = await readHistoryEntries(getWorkspaceHistoryPath(pmRoot), WORKSPACE_HISTORY_ID);
77
+ const verification = verifyHistoryChain(entries);
78
+ if (!verification.ok) {
79
+ throw new TypeError(`Workspace history verification failed: ${verification.errors.join(", ")}`);
80
+ }
81
+ const documents = workspaceDocuments(entries.length === 0
82
+ ? EMPTY_CANONICAL_DOCUMENT
83
+ : replayWorkspaceEntries(entries));
84
+ const matching = [];
85
+ const mismatched = [];
86
+ const missing = [];
87
+ const unreadable = [];
88
+ for (const documentPath of Object.keys(documents).sort()) {
89
+ let raw;
90
+ try {
91
+ const resolved = resolveGovernedDocumentPath(pmRoot, path.resolve(pmRoot, documentPath));
92
+ raw = await readFileIfExists(resolved.absolutePath);
93
+ if (raw === null) {
94
+ missing.push(documentPath);
95
+ continue;
96
+ }
97
+ const current = JSON.parse(raw);
98
+ (stableStringify(current) === stableStringify(documents[documentPath])
99
+ ? matching
100
+ : mismatched).push(documentPath);
101
+ }
102
+ catch {
103
+ unreadable.push(documentPath);
104
+ }
105
+ }
106
+ return {
107
+ ok: mismatched.length === 0 &&
108
+ missing.length === 0 &&
109
+ unreadable.length === 0,
110
+ document_count: Object.keys(documents).length,
111
+ matching_documents: matching,
112
+ mismatched_documents: mismatched,
113
+ missing_documents: missing,
114
+ unreadable_documents: unreadable,
115
+ };
116
+ }
48
117
  /**
49
118
  * Append one workspace document mutation while the caller holds the dedicated
50
119
  * workspace-history lock.
@@ -66,12 +135,7 @@ async function appendWorkspaceHistoryChangeLocked(change) {
66
135
  const beforeDocument = entries.length === 0
67
136
  ? EMPTY_CANONICAL_DOCUMENT
68
137
  : replayWorkspaceEntries(entries);
69
- const priorDocuments = "documents" in beforeDocument.metadata &&
70
- typeof beforeDocument.metadata.documents === "object" &&
71
- beforeDocument.metadata.documents !== null &&
72
- !Array.isArray(beforeDocument.metadata.documents)
73
- ? beforeDocument.metadata.documents
74
- : {};
138
+ const priorDocuments = workspaceDocuments(beforeDocument);
75
139
  const recordedBefore = priorDocuments[change.documentPath];
76
140
  if (recordedBefore !== undefined &&
77
141
  stableStringify(recordedBefore) !== stableStringify(change.before)) {
@@ -220,5 +284,128 @@ export async function writeWorkspaceJsonWithHistory(params) {
220
284
  await release();
221
285
  }
222
286
  }
287
+ /**
288
+ * Append an authorized reconciliation event that adopts a reviewed on-disk
289
+ * singleton after an out-of-band change. The existing stream is never edited:
290
+ * its latest replayed state becomes the event before-state and the reviewed
291
+ * singleton becomes the after-state, with the authorizing Decision recorded in
292
+ * structured context.
293
+ */
294
+ export async function reconcileWorkspaceJsonHistory(options) {
295
+ const authorizationDecision = options.authorizationDecision.trim();
296
+ if (!authorizationDecision) {
297
+ throw new TypeError("Workspace history reconciliation requires an authorization decision");
298
+ }
299
+ const release = await acquireLock(options.pmRoot, "workspace-history", options.lockTtlSeconds, options.author, false, false, options.lockWaitMs);
300
+ try {
301
+ const { documentPath, absolutePath } = resolveGovernedDocumentPath(options.pmRoot, options.filePath);
302
+ const historyPath = getWorkspaceHistoryPath(options.pmRoot);
303
+ const entries = await readHistoryEntries(historyPath, WORKSPACE_HISTORY_ID);
304
+ const verification = verifyHistoryChain(entries);
305
+ if (!verification.ok) {
306
+ throw new TypeError(`Workspace history verification failed: ${verification.errors.join(", ")}`);
307
+ }
308
+ const beforeDocument = replayWorkspaceEntries(entries);
309
+ const priorDocuments = workspaceDocuments(beforeDocument);
310
+ if (!Object.hasOwn(priorDocuments, documentPath)) {
311
+ throw new TypeError(`Workspace history does not govern document ${documentPath}`);
312
+ }
313
+ const raw = await readFileIfExists(absolutePath);
314
+ if (raw === null) {
315
+ throw new TypeError(`Workspace history document is missing: ${documentPath}`);
316
+ }
317
+ const current = JSON.parse(raw);
318
+ if (stableStringify(priorDocuments[documentPath]) === stableStringify(current)) {
319
+ return { changed: false, historyPath };
320
+ }
321
+ const timestamp = new Date().toISOString();
322
+ const entry = createHistoryEntry({
323
+ nowIso: timestamp,
324
+ author: options.author,
325
+ op: options.op,
326
+ before: beforeDocument,
327
+ after: workspaceDocument({ ...priorDocuments, [documentPath]: current }, beforeDocument.metadata.created_at),
328
+ message: options.message,
329
+ context: {
330
+ reason: "out_of_band_workspace_state_reconciliation",
331
+ document_path: documentPath,
332
+ authorization_decision: authorizationDecision,
333
+ },
334
+ });
335
+ await appendHistoryEntry(historyPath, entry);
336
+ return { changed: true, entry, historyPath };
337
+ }
338
+ finally {
339
+ await release();
340
+ }
341
+ }
342
+ /**
343
+ * Restore one governed JSON singleton to the value recorded at a one-based
344
+ * workspace history version, then append the restore as a new history state so
345
+ * replay remains forward-only and independently verifiable.
346
+ */
347
+ export async function restoreWorkspaceJsonFromHistory(options) {
348
+ const release = await acquireLock(options.pmRoot, "workspace-history", options.lockTtlSeconds, options.author, false, false, options.lockWaitMs);
349
+ try {
350
+ const { documentPath, absolutePath } = resolveGovernedDocumentPath(options.pmRoot, options.filePath);
351
+ const entries = await readHistoryEntries(getWorkspaceHistoryPath(options.pmRoot), WORKSPACE_HISTORY_ID);
352
+ if (!Number.isInteger(options.targetVersion) ||
353
+ options.targetVersion < 1 ||
354
+ options.targetVersion > entries.length) {
355
+ throw new TypeError(`Invalid workspace history target version: ${String(options.targetVersion)}`);
356
+ }
357
+ const verification = verifyHistoryChain(entries);
358
+ if (!verification.ok) {
359
+ throw new TypeError(`Workspace history verification failed: ${verification.errors.join(", ")}`);
360
+ }
361
+ const target = workspaceDocuments(replayWorkspaceEntries(entries, options.targetVersion))[documentPath];
362
+ if (target === undefined) {
363
+ throw new TypeError(`Workspace history version ${options.targetVersion} does not contain ${documentPath}`);
364
+ }
365
+ const beforeRaw = await readFileIfExists(absolutePath);
366
+ const targetRaw = `${JSON.stringify(target, null, 2)}\n`;
367
+ if (beforeRaw === targetRaw)
368
+ return {
369
+ document_path: documentPath,
370
+ restored_from_version: options.targetVersion,
371
+ changed: false,
372
+ };
373
+ const beforeDocument = replayWorkspaceEntries(entries);
374
+ const timestamp = new Date().toISOString();
375
+ const entry = createHistoryEntry({
376
+ nowIso: timestamp,
377
+ author: options.author,
378
+ op: options.op,
379
+ before: beforeDocument,
380
+ after: workspaceDocument({ ...workspaceDocuments(beforeDocument), [documentPath]: target }, beforeDocument.metadata.created_at),
381
+ message: options.message,
382
+ context: {
383
+ reason: "workspace_state_restore",
384
+ document_path: documentPath,
385
+ restored_from_version: options.targetVersion,
386
+ replaced_out_of_band_state: true,
387
+ },
388
+ });
389
+ await writeFileAtomic(absolutePath, targetRaw);
390
+ try {
391
+ await appendHistoryEntry(getWorkspaceHistoryPath(options.pmRoot), entry);
392
+ }
393
+ catch (error) {
394
+ if (beforeRaw === null)
395
+ await fs.rm(absolutePath, { force: true });
396
+ else
397
+ await writeFileAtomic(absolutePath, beforeRaw);
398
+ throw error;
399
+ }
400
+ return {
401
+ document_path: documentPath,
402
+ restored_from_version: options.targetVersion,
403
+ changed: true,
404
+ };
405
+ }
406
+ finally {
407
+ await release();
408
+ }
409
+ }
223
410
  //# sourceMappingURL=workspace-history.js.map
224
- //# debugId=86b45d6b-2192-5d31-8f73-94d6e6b17dfc
411
+ //# debugId=0f666995-78f8-5741-ae0c-0d792feea599
@@ -4,7 +4,7 @@
4
4
  * Formats compact human and machine output for Output.
5
5
  */
6
6
 
7
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="5aaf8b90-5b95-5102-9ea4-b877c696d593")}catch(e){}}();
7
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="88eb8f29-72a2-536f-8fc9-394ed3e0e0fb")}catch(e){}}();
8
8
  import { getActiveCommandContext, runActiveCommandOverride, runActiveRendererOverride, runActiveServiceOverrideSync, setActiveCommandResult, } from "../extensions/index.js";
9
9
  import { EXIT_CODE } from "../shared/constants.js";
10
10
  import { isHostOutputSuppressed } from "./output-control.js";
@@ -371,7 +371,15 @@ function formatEffectiveOutput(effectiveResult, nativeOutput, options) {
371
371
  };
372
372
  const format = resolveReadOutputEncoding(command ?? "", commandOptions) ??
373
373
  resolveOutputFormat(options);
374
- const effectiveOptions = { ...options, command, commandOptions };
374
+ const resolvedCommandOptions = {
375
+ ...commandOptions,
376
+ resolvedOutputFormat: format,
377
+ };
378
+ const effectiveOptions = {
379
+ ...options,
380
+ command,
381
+ commandOptions: resolvedCommandOptions,
382
+ };
375
383
  const service = resolveOutputService(effectiveResult, nativeOutput, effectiveOptions, format);
376
384
  if (service.rendered !== null)
377
385
  return service.rendered;
@@ -379,7 +387,7 @@ function formatEffectiveOutput(effectiveResult, nativeOutput, options) {
379
387
  const projectedOutputResult = command === "test"
380
388
  ? projectLinkedTestEvidence(outputResult, options.lean === true)
381
389
  : outputResult;
382
- const intentOutputResult = attachReadOutputContracts(command, commandOptions, projectedOutputResult);
390
+ const intentOutputResult = attachReadOutputContracts(command, resolvedCommandOptions, projectedOutputResult);
383
391
  const renderResolvedOutput = (value) => {
384
392
  if (format === "toon") {
385
393
  const markdownDefault = renderDefaultMarkdownResult(value);
@@ -456,4 +464,4 @@ export const outputTestOnly = {
456
464
  renderToonValue,
457
465
  };
458
466
  //# sourceMappingURL=output.js.map
459
- //# debugId=5aaf8b90-5b95-5102-9ea4-b877c696d593
467
+ //# debugId=88eb8f29-72a2-536f-8fc9-394ed3e0e0fb
@@ -231,8 +231,13 @@ interface CommandSummarySurface {
231
231
  command: string;
232
232
  aliases?: string[];
233
233
  intent: string;
234
+ intent_source: "command" | "description" | "generated";
234
235
  flags?: string[];
235
236
  default_max_estimated_tokens: number;
237
+ default_max_estimated_tokens_by_format: {
238
+ toon: number;
239
+ json: number;
240
+ };
236
241
  }
237
242
  declare function packageOwnedActionForCommand(command: string): string;
238
243
  declare function resolveActionCommandPath(action: PmToolAction): string | null;
@@ -275,7 +280,7 @@ declare function toRuntimeShortFlagToken(token: string): string | null;
275
280
  declare function buildRuntimeFieldFlagContracts(fieldRegistry: RuntimeFieldRegistry): Map<string, CliFlagContract[]>;
276
281
  declare function attachCreateRequiredOptionContracts(schema: Record<string, unknown>, metadata: Record<string, unknown>): Record<string, unknown>;
277
282
  declare function readContractsSettings(pmRoot: string, readSettingsFn?: typeof readSettings): Promise<Awaited<ReturnType<typeof readSettings>>>;
278
- declare function buildCommandSummarySurface(commands: readonly string[]): CommandSummarySurface[];
283
+ declare function buildCommandSummarySurface(commands: readonly string[], extensionContracts?: readonly ExtensionCommandContract[]): CommandSummarySurface[];
279
284
  declare function buildCommandOutputContracts(commands: readonly string[]): NonNullable<ContractsResult["command_output_contracts"]>;
280
285
  declare function buildCommandExitContractGroups(commands: readonly string[]): NonNullable<ContractsResult["command_exit_contracts"]>["command_sets"];
281
286
  /** Implements run contracts for the public runtime surface of this module. */