@aefree/pi-plastic 0.7.5 → 0.8.0

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 (148) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +48 -174
  3. package/docs/2026-10-06-development.md +101 -0
  4. package/docs/2026-10-06-diffs-and-patches.md +136 -0
  5. package/docs/2026-10-06-discovery.md +135 -0
  6. package/docs/2026-10-06-integrations.md +49 -0
  7. package/docs/2026-10-06-merging.md +153 -0
  8. package/docs/2026-10-06-safety-and-receipts.md +95 -0
  9. package/docs/2026-10-06-setup.md +101 -0
  10. package/docs/2026-10-06-shelvesets-and-reviews.md +113 -0
  11. package/docs/2026-10-06-status.md +116 -0
  12. package/docs/2026-10-06-tools-and-loading.md +64 -0
  13. package/docs/2026-10-06-workspace-operations.md +142 -0
  14. package/docs/README.md +65 -0
  15. package/extensions/bash-cm-diff-guard.ts +1 -1
  16. package/index.ts +4 -687
  17. package/package.json +12 -6
  18. package/skills/using-plastic/SKILL.md +11 -5
  19. package/skills/using-plastic/references/branch-management.md +5 -1
  20. package/skills/using-plastic/references/changeset-operations.md +9 -8
  21. package/skills/using-plastic/references/code-review-creation.md +25 -18
  22. package/skills/using-plastic/references/integration.md +9 -5
  23. package/skills/using-plastic/references/quick-reference.md +4 -4
  24. package/skills/using-plastic/references/reviewing-changes.md +10 -10
  25. package/skills/using-plastic/references/shelving.md +26 -11
  26. package/skills/using-plastic/references/troubleshooting.md +4 -4
  27. package/src/diff/local-snapshot.ts +74 -0
  28. package/src/diff/text.ts +187 -0
  29. package/src/domain/add-contract.ts +24 -0
  30. package/src/domain/branch-create-contract.ts +38 -0
  31. package/src/domain/branch-delete-contract.ts +15 -0
  32. package/src/domain/branches.ts +263 -0
  33. package/src/domain/checkin-contract.ts +70 -0
  34. package/src/domain/closeout-contract.ts +30 -0
  35. package/src/domain/copied-merge.ts +42 -0
  36. package/src/domain/diff-base-xml.ts +65 -0
  37. package/src/domain/diff-pending-xml.ts +34 -0
  38. package/src/domain/merge-output.ts +327 -0
  39. package/src/domain/paths.ts +253 -0
  40. package/src/domain/pending.ts +499 -0
  41. package/src/domain/removal-contract.ts +25 -0
  42. package/src/domain/revisions.ts +301 -0
  43. package/src/domain/server-merge-contract.ts +45 -0
  44. package/src/domain/server-merge-resolutions.ts +61 -0
  45. package/src/domain/status-xml.ts +122 -0
  46. package/src/domain/switch-contract.ts +60 -0
  47. package/src/domain/switch-policy.ts +95 -0
  48. package/src/domain/undo-contract.ts +24 -0
  49. package/src/domain/update-contract.ts +16 -0
  50. package/src/domain/workspace-merge-contract.ts +41 -0
  51. package/src/execution/add-command.ts +118 -0
  52. package/src/execution/branch-create-command.ts +118 -0
  53. package/src/execution/branch-list-command.ts +75 -0
  54. package/src/execution/checkin-command.ts +116 -0
  55. package/src/execution/cli-version.ts +31 -0
  56. package/src/execution/cm.ts +59 -0
  57. package/src/execution/code-review-find-command.ts +75 -0
  58. package/src/execution/context.ts +20 -0
  59. package/src/execution/diff-revisions-command.ts +74 -0
  60. package/src/execution/process.ts +260 -0
  61. package/src/execution/removal-command.ts +118 -0
  62. package/src/execution/server-merge-command.ts +92 -0
  63. package/src/execution/shelveset-list-command.ts +75 -0
  64. package/src/execution/status-command.ts +29 -0
  65. package/src/execution/status-xml-command.ts +72 -0
  66. package/src/execution/status-xml-limits.ts +1 -0
  67. package/src/execution/switch-command.ts +118 -0
  68. package/src/execution/test-seams.ts +10 -0
  69. package/src/execution/undo-command.ts +118 -0
  70. package/src/execution/update-command.ts +118 -0
  71. package/src/execution/workspace-list-command.ts +75 -0
  72. package/src/execution/workspace-merge-command.ts +118 -0
  73. package/src/operations/add-receipt.ts +37 -0
  74. package/src/operations/arguments.ts +3 -0
  75. package/src/operations/branch-create.ts +74 -0
  76. package/src/operations/branch-delete-receipt.ts +25 -0
  77. package/src/operations/branch-list.ts +94 -0
  78. package/src/operations/branch-reads.ts +94 -0
  79. package/src/operations/branches.ts +111 -0
  80. package/src/operations/checkin-receipt.ts +140 -0
  81. package/src/operations/checkin.ts +102 -0
  82. package/src/operations/closeout-receipt.ts +74 -0
  83. package/src/operations/closeout.ts +19 -0
  84. package/src/operations/code-review-find.ts +97 -0
  85. package/src/operations/consolidated-diff.ts +144 -0
  86. package/src/operations/diff-revisions.ts +157 -0
  87. package/src/operations/diff.ts +53 -0
  88. package/src/operations/merge.ts +26 -0
  89. package/src/operations/object-delete-receipt.ts +21 -0
  90. package/src/operations/object-write-receipt.ts +105 -0
  91. package/src/operations/patch-receipt.ts +96 -0
  92. package/src/operations/patch-support.ts +375 -0
  93. package/src/operations/patch.ts +25 -0
  94. package/src/operations/removal-receipt.ts +42 -0
  95. package/src/operations/reviews.ts +90 -0
  96. package/src/operations/server-merge.ts +130 -0
  97. package/src/operations/shelveset-list.ts +90 -0
  98. package/src/operations/shelvesets.ts +90 -0
  99. package/src/operations/status.ts +129 -0
  100. package/src/operations/switch-receipt.ts +85 -0
  101. package/src/operations/switch.ts +25 -0
  102. package/src/operations/undo-receipt.ts +37 -0
  103. package/src/operations/update-receipt.ts +35 -0
  104. package/src/operations/workspace-list.ts +92 -0
  105. package/src/operations/workspace-merge-receipt.ts +50 -0
  106. package/src/operations/workspace.ts +120 -0
  107. package/src/pi/add-output.ts +52 -0
  108. package/src/pi/arguments.ts +298 -0
  109. package/src/pi/branch-create-output.ts +83 -0
  110. package/src/pi/branch-delete-output.ts +46 -0
  111. package/src/pi/branch-list-output.ts +59 -0
  112. package/src/pi/branch-output.ts +63 -0
  113. package/src/pi/capability-diagnostics.ts +23 -0
  114. package/src/pi/checkin-output.ts +68 -0
  115. package/src/pi/closeout-output.ts +87 -0
  116. package/src/pi/code-review-find-output.ts +50 -0
  117. package/src/pi/diff-output.ts +66 -0
  118. package/src/pi/diff-revisions-output.ts +55 -0
  119. package/src/pi/object-delete-output.ts +15 -0
  120. package/src/pi/object-write-output.ts +54 -0
  121. package/src/pi/patch-output.ts +69 -0
  122. package/src/pi/register.ts +188 -0
  123. package/src/pi/removal-output.ts +59 -0
  124. package/src/pi/schemas.ts +146 -0
  125. package/src/pi/server-merge-output.ts +83 -0
  126. package/src/pi/shelveset-list-output.ts +50 -0
  127. package/src/pi/status-output.ts +149 -0
  128. package/src/pi/switch-output.ts +80 -0
  129. package/src/pi/tool-registry.ts +72 -0
  130. package/src/pi/undo-output.ts +52 -0
  131. package/src/pi/update-output.ts +48 -0
  132. package/src/pi/workspace-list-output.ts +53 -0
  133. package/src/pi/workspace-merge-output.ts +46 -0
  134. package/src/plastic-core.ts +14 -5775
  135. package/src/plastic-renderers.ts +102 -22
  136. package/src/plastic-tool-loading.ts +7 -12
  137. package/src/presentation/add-results.ts +5 -0
  138. package/src/presentation/branch-create.ts +7 -0
  139. package/src/presentation/branch-delete-results.ts +7 -0
  140. package/src/presentation/checkin-results.ts +14 -0
  141. package/src/presentation/closeout-results.ts +2 -0
  142. package/src/presentation/diff-results.ts +237 -0
  143. package/src/presentation/removal-results.ts +7 -0
  144. package/src/presentation/results.ts +84 -0
  145. package/src/presentation/switch-results.ts +7 -0
  146. package/src/presentation/undo-results.ts +5 -0
  147. package/src/presentation/update-results.ts +5 -0
  148. package/src/presentation/workspace-merge-results.ts +6 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aefree/pi-plastic",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22.19.0"
@@ -16,6 +16,7 @@
16
16
  "extensions/",
17
17
  "src/",
18
18
  "skills/",
19
+ "docs/",
19
20
  "CHANGELOG.md",
20
21
  "LICENSE",
21
22
  "README.md"
@@ -35,18 +36,20 @@
35
36
  "scripts": {
36
37
  "typecheck": "tsc -p tsconfig.json",
37
38
  "pretest": "npm run typecheck",
38
- "test": "tsx tests/tool-definition.test.ts && tsx tests/release-registry-preflight.test.ts && tsx tests/plastic-tool-validation.ts && tsx tests/plastic-status-machine-readable.test.ts && tsx tests/plastic-checkin-path-resolution.ts && tsx tests/plastic-diff-workflow.test.ts && tsx tests/plastic-workspace-diff.test.ts && tsx tests/plastic-process-lifecycle.test.ts && tsx tests/plastic-patch-command.test.ts && tsx tests/plastic-capability-diagnostics.test.ts && tsx tests/plastic-extension-registration.test.ts && tsx tests/plastic-dynamic-tool-loading.test.ts && tsx tests/plastic-tool-rendering.test.ts && tsx tests/plastic-branch-query.test.ts && tsx tests/plastic-workspace.test.ts && tsx tests/plastic-mutation-execution.test.ts && tsx tests/plastic-merge-branches.test.ts && tsx tests/file-discovery-filter.test.ts && tsx tests/file-discovery-filter-registration.test.ts && tsx tests/packed-copy-owner-conflict.test.ts && tsx tests/plastic-branch-status.test.ts && tsx tests/plastic-constrained-sampling.test.ts && tsx tests/bash-cm-diff-guard-validation.ts",
39
+ "test:docs": "tsx tests/plastic-documentation.test.ts",
40
+ "test": "tsx tests/plastic-documentation.test.ts && tsx tests/eval-mutation-guard.test.ts && tsx tests/tool-definition.test.ts && tsx tests/release-registry-preflight.test.ts && tsx tests/plastic-tool-validation.ts && tsx tests/plastic-status-machine-readable.test.ts && tsx tests/plastic-status-output.test.ts && tsx tests/plastic-status-xml.test.ts && tsx tests/plastic-branch-output.test.ts && tsx tests/plastic-checkin-output.test.ts && tsx tests/plastic-branch-create-output.test.ts && tsx tests/plastic-switch-output.test.ts && tsx tests/plastic-update-output.test.ts && tsx tests/plastic-add-output.test.ts && tsx tests/plastic-undo-output.test.ts && tsx tests/plastic-removal-output.test.ts && tsx tests/plastic-workspace-merge-output.test.ts && tsx tests/plastic-workspace-merge-closeout.test.ts && tsx tests/plastic-closeout-output.test.ts && tsx tests/plastic-closeout-parent.test.ts && tsx tests/plastic-copied-closeout.test.ts && tsx tests/plastic-copied-cardinality.test.ts && tsx tests/plastic-branch-delete-output.test.ts && tsx tests/plastic-object-delete-output.test.ts && tsx tests/plastic-object-write-output.test.ts && tsx tests/plastic-receipt-integration.test.ts && tsx tests/plastic-checkin-core-closeout.test.ts && tsx tests/plastic-selected-collector-retirement.test.ts && tsx tests/plastic-checkin-path-resolution.ts && tsx tests/plastic-diff-workflow.test.ts && tsx tests/plastic-diff-revisions-output.test.ts && tsx tests/plastic-local-snapshot.test.ts && tsx tests/plastic-workspace-diff.test.ts && tsx tests/plastic-process-lifecycle.test.ts && tsx tests/plastic-module-boundaries.test.ts && tsx tests/plastic-patch-command.test.ts && tsx tests/plastic-patch-output.test.ts && tsx tests/plastic-capability-diagnostics.test.ts && tsx tests/plastic-extension-registration.test.ts && tsx tests/plastic-dynamic-tool-loading.test.ts && tsx tests/plastic-tool-rendering.test.ts && tsx tests/plastic-rendering-runtime-isolation.test.ts && tsx tests/plastic-branch-query.test.ts && tsx tests/plastic-workspace.test.ts && tsx tests/plastic-workspace-list-output.test.ts && tsx tests/plastic-shelveset-list-output.test.ts && tsx tests/plastic-code-review-find-output.test.ts && tsx tests/plastic-mutation-execution.test.ts && tsx tests/plastic-merge-branches.test.ts && tsx tests/plastic-server-merge-output.test.ts && tsx tests/plastic-server-merge-resolutions.test.ts && tsx tests/file-discovery-filter.test.ts && tsx tests/file-discovery-filter-registration.test.ts && tsx tests/packed-copy-owner-conflict.test.ts && tsx tests/plastic-branch-status.test.ts && tsx tests/plastic-constrained-sampling.test.ts && tsx tests/bash-cm-diff-guard-validation.ts",
39
41
  "preview:tools": "tsx tests/plastic-tool-rendering.test.ts --preview",
42
+ "test:host:status": "tsx tests/pi-host-status-output.test.ts",
40
43
  "test:live": "tsx tests/plastic-live-smoke.test.ts",
41
44
  "test:live:branch-create": "tsx tests/plastic-branch-create-live.test.ts",
42
45
  "eval:tool-loading": "tsx evals/tool-loading/run-eval.ts"
43
46
  },
44
47
  "devDependencies": {
45
48
  "@aefree/pi-file-discovery": "^0.1.1",
46
- "@earendil-works/pi-coding-agent": "0.87.0",
47
- "@earendil-works/pi-tui": "0.87.0",
48
- "@types/node": "22.20.2",
49
- "tsx": "4.23.5",
49
+ "@earendil-works/pi-coding-agent": "1.1.0",
50
+ "@earendil-works/pi-tui": "1.1.0",
51
+ "@types/node": "22.20.4",
52
+ "tsx": "4.23.15",
50
53
  "typebox": "^1.3.8",
51
54
  "typescript": "7.0.2"
52
55
  },
@@ -79,5 +82,8 @@
79
82
  "typebox": {
80
83
  "optional": true
81
84
  }
85
+ },
86
+ "dependencies": {
87
+ "@xmldom/xmldom": "0.9.12"
82
88
  }
83
89
  }
@@ -18,21 +18,23 @@ Treat `plastic_merge` success as provisional until `plastic_status` confirms the
18
18
 
19
19
  For the common closeout flow of merging a finished source branch into its parent branch, prefer `plastic_mergeToBranch` when available. When `target` is omitted, it resolves the source branch's Plastic parent branch instead of assuming `/dev`. It switches to the target, optionally updates, runs the safe merge, checks merge state, and checks in the merge result. Pass `target` only when the user explicitly wants a different integration branch.
20
20
 
21
- Use `plastic_mergeBranches` only for the separate workspace-free server-side route. It requires explicit same-repository/server `br:/...@<repository>@<server>` source and target selectors plus a nonempty message. Its preflight renders the command only; it does not contact Plastic or analyze conflicts. A completed result proves one emitted root-mount target changeset for the dispatch, but does not verify the source merge link, target-head exclusivity, rollback, or xlink effects. No-op and conflict output do not prove that no effect occurred. Do not use it for xlink handling, conflict-policy selection, shelve merges, retries, target-head unification, or a workspace fallback; undetected server-side xlink effects remain possible.
21
+ Use `plastic_mergeBranches` for the separate workspace-free server-side route with explicit same-repository/server `br:/...@<repository>@<server>` source and target selectors. Read the [agent conflict-resolution workflow](../../docs/2026-10-06-merging.md#agent-conflict-resolution-workflow) before resolving server file conflicts. `mode="analyze"` contacts the server read-only without a message or applying merge; inspect exact `conflictPaths`, completeness and omissions. Default apply requires a nonempty message and can submit reviewed `fileConflictsResolutionsFile` JSON with per-file keep/source/destination or merged result-file decisions. Verify contributors independently, preserve baselines, validate merged bytes, and keep all inputs immutable until completion. Optional `unlistedConflictPolicy` is an explicit discard policy, never an automatic repair. Preflight only validates local inputs/renders argv; it does not contact Plastic. A completed result proves one emitted root-mount target changeset, not source links, head exclusivity, rollback or Xlink effects. Native receipts distinguish analyzed/preflight/completed/no-op/conflict/uncertain/failed outcomes; inspect attempt/capture/effect before continuing. Full @cloud/@unity qualifiers are preserved; requested/emitted server names stay separate with alias equivalence unverified. No-op/conflict output does not prove absent effects. Do not use this for directory-conflict repair, shelve merges, retries, head unification or workspace fallback; Xlink ownership/effects remain unverified.
22
22
 
23
23
  Plastic merges are workspace- and branch-oriented, not Git fast-forwards: the merge is applied to the branch currently loaded in the workspace, and the merge checkin must be created on that target branch. Switching to the target after a checkin on the source branch does not move or integrate that changeset. Treat any source/target/final-branch mismatch as a failed closeout; inspect target-branch history and rerun the merge from the actual target rather than merely switching branches.
24
24
 
25
- `plastic_diff` is intentionally disabled. Route changed-path listing to `plastic_status()`, one intentional workspace-file comparison to `plastic_diffFile(path="<workspace-path>")`, an explicitly scoped pending review to `plastic_workspaceDiff(paths=[...])`, and an explicit historical pair to `plastic_diffRevisions`. Do not use diffs as routine post-edit validation or checkin preflight. Prefer focused tests, `plastic_status` for pending scope, and direct reads for current file content; diff only when change-boundary evidence is needed. `plastic_diffFile` uses machine-readable status: added and explicitly selected private/new files compare against an empty base; changed, moved, and deleted items use their reported base revision when available. An added empty file reports `status="added-empty"`, not generic unchanged. A `--nodata` base failure means Plastic cannot provide historical bytes; refresh/update the workspace or use two known file-qualified revisions. Explicit `revision` behavior remains a separate historical request. Binary content is reported rather than decoded as text. Text backends receive ASCII-safe package materializations while returned headers preserve logical Unicode labels.
25
+ Use one `plastic_diff` with explicit `mode="file"|"revisions"|"workspace"`. File mode requires `path`; omit `revision` for a verified loaded base, or supply a supported historical selector. Revisions mode requires `leftRevision` and `rightRevision`; exported selectors remain unresolved identities unless loaded metadata is independently observed. Workspace mode requires nonempty `paths` or `allPending=true`; `includePrivate` applies only to all-pending. Old specialized names are removed, not aliases. Prefer focused tests, `plastic_status` for pending scope, and direct reads; diff only when change-boundary evidence is needed, not routine validation or checkin preflight.
26
26
 
27
- `plastic_workspaceDiff` requires selected paths or explicit `allPending=true`, runs status once, has small default file/per-file/combined-output bounds, and returns per-file outcomes without aborting when one file is unavailable. Selected private paths are included; whole-workspace private review additionally requires `includePrivate=true`. Keep `maxChars` small unless more output is intentional. Text-diff selection and patch-backend selection are distinct: text tools use `PI_PLASTIC_DIFF_EXECUTABLE` or bare PATH `diff`. On macOS, bare `diff` can resolve either to GNU Diffutils or Apple’s BSD `/usr/bin/diff`; Pi uses the resolved executable and does not auto-select `gdiff`.
27
+ Loaded bases use strict UTF-8 status/fileinfo/ls observations and a qualified genuine revision ID, verified against the loaded file hash. Controlled deletions use the frozen loaded workspace tree; local moves use observed OldPath metadata. Added/explicitly selected private files use a deliberate empty base; an added empty file reports `added-empty`. Unavailable bytes, ownership or unsupported observations are errors, never empty bases or guessed IDs. Workspace results retain per-file failures with `ok=false`, counts, unmatched paths, skipped/limited/unattempted/omitted outcomes and completeness; inspect these before concluding review is complete. Defaults: 3 all-pending items (20 scoped), 3000 characters per workspace excerpt; file/revisions excerpts default to 8000. Keep bounds small. Comparisons use stable bounded local snapshots and owned ASCII-safe operands, not an atomic workspace/repository snapshot. Binary inputs use exact byte equality. Text comparison uses the configured `PI_PLASTIC_DIFF_EXECUTABLE` or PATH `diff`, with no backend retry or automatic substitution.
28
28
 
29
- Use `plastic_patch` for review patch generation with `clean` and `integration` filters; it is generation-only and does not apply patches. `toolPath` is its highest-priority one-call patch-backend override, followed by `PI_PLASTIC_PATCH_EXECUTABLE`. On Windows, configure one to a verified patch-capable non-GUI executable such as Git's `diff.exe`: patch generation deliberately does not reuse `PI_PLASTIC_DIFF_EXECUTABLE`/GnuWin32. On macOS with Plastic 11, Apple BSD `/usr/bin/diff` works for text-only `plastic_diffFile` but rejects the `--binary` argument used by `cm patch`; set the patch-specific policy to a verified GNU Diffutils-compatible non-GUI executable. The non-Windows fallback to the text setting or bare `diff` is only valid when that executable accepts the local Plastic patch arguments. Interactive Pi sessions issue one path-redacted package capability warning per runtime for an invalid configured patch override or missing Windows setting; fix `PI_PLASTIC_PATCH_EXECUTABLE` and retry, while headless sessions retain `plastic_patch` tool-time validation. Unqualified `br:/...` patch selectors are qualified only from the current workspace's exact selector repository; otherwise pass `br:/<branch>@<repository>@<server>`. For a large patch, pass an intentional new `output` path: the package stages and atomically publishes it, never overwriting an existing path. Do not use `cm cat --raw`: it is unsupported for this workflow. Typed retrieval owns cleanup; the low-level byte-preserving fallback is `cm cat --file` only when a typed tool cannot represent the case. Plastic/server output may represent moves as moves or delete/add records, so inspect generated patches rather than assuming a move-aware encoding.
29
+ Use `plastic_patch` for review patch generation with `clean` and `integration` filters; it is generation-only and does not apply patches. `toolPath` is its highest-priority one-call patch-backend override, followed by `PI_PLASTIC_PATCH_EXECUTABLE`. On Windows, configure one to a verified patch-capable non-GUI executable such as Git's `diff.exe`: patch generation deliberately does not reuse `PI_PLASTIC_DIFF_EXECUTABLE`/GnuWin32. On macOS with Plastic 11, Apple BSD `/usr/bin/diff` has prior text-only evidence but rejects the `--binary` argument used by `cm patch`; set the patch-specific policy to a verified GNU Diffutils-compatible non-GUI executable. The non-Windows fallback to the text setting or bare `diff` is only valid when that executable accepts the local Plastic patch arguments. Interactive Pi sessions issue one path-redacted package capability warning per runtime for an invalid configured patch override or missing Windows setting; fix `PI_PLASTIC_PATCH_EXECUTABLE` and retry, while headless sessions retain `plastic_patch` tool-time validation. Unqualified `br:/...` patch selectors are qualified only from the current workspace's exact selector repository; otherwise pass `br:/<branch>@<repository>@<server>`. For a large patch, pass an intentional new `output` path: the package stages and atomically publishes it, never overwriting an existing path. Do not use `cm cat --raw`: it is unsupported for this workflow. Typed retrieval owns cleanup; the low-level byte-preserving fallback is `cm cat --file` only when a typed tool cannot represent the case. Plastic/server output may represent moves as moves or delete/add records, so inspect generated patches rather than assuming a move-aware encoding.
30
30
 
31
31
  Prefer runtime `plastic_*` tools first. Keep `cm` shell commands as manual fallback.
32
32
 
33
33
  Directly invoked mutating `plastic_*` tools run without package-owned approval tokens or UI confirmation. Inspect exact targets and rely on the tools' command, workspace, path-containment, and non-interactive process guards. Do not call preflight and then the same operation as routine ceremony. Use preflight when mutation scope is ambiguous or broad, moved/deleted paths may need rewriting, a compound operation needs preview, or the user explicitly asks for one. A command process is attempted once without implicit retry; after an ambiguous failure, inspect Plastic status before deciding whether to retry manually.
34
34
 
35
- Create normal work branches beneath an intended parent: use `<parent-branch>/<new-branch>`, not `/<new-branch>`. The parent does not need to be the branch loaded in the workspace. Prefer `plastic_branchCreate(branch="<new-branch>", parent="<parent-branch>")`; omitting `parent` uses the current branch only as a convenience. Rare top-level branch creation requires the explicit `allowRootBranch=true` override.
35
+ Create normal work branches beneath an intended parent: use `<parent-branch>/<new-branch>`, not `/<new-branch>`. The parent does not need to be the branch loaded in the workspace. Prefer `plastic_branchCreate(branch="<new-branch>", parent="<parent-branch>")`; omitting `parent` requires directly observed standard-status loaded-branch identity, not a changeset owner. Qualifier-bearing relative names/parents are rejected: use a full qualified hierarchical target. Successful branch-create receipts prove command completion only, not created identity or repository effects; no automatic verification/switch follows. Rare top-level branch creation requires the explicit `allowRootBranch=true` override.
36
+
37
+ Checkin receipts distinguish observed root changesets from unverified scope/head/alias/Xlink/link effects. Checkin preflight reads pending scope but does not mutate. Eligible complete admitted failures may trigger existing safe-private add/retry or path fallback; capture/timeout/abort uncertainty must not be retried automatically. Failed post/recovery reads stay unknown, and even an empty pending read after failed checkin is not completion proof. Earlier child effects remain recorded; inspect them before another attempt.
36
38
 
37
39
  ## External File Loading
38
40
 
@@ -43,6 +45,10 @@ CRITICAL: Use relative path references and load files only when needed for the c
43
45
  - Read the reference files only when relevant.
44
46
  - For long files, use Read with `offset`/`limit` to load only needed sections.
45
47
 
48
+ ## Public Tool Documentation (Load On Demand)
49
+
50
+ For parameter semantics, structured receipts, source restrictions, and bounds, start at the [package documentation index](../../docs/README.md), then read only the relevant tool-family section. [Safety and receipts](../../docs/2026-10-06-safety-and-receipts.md) explains completion versus verified effects. Resolve these same-package links relative to this skill directory; no external reference-reader tool is needed. Do not load the entire manual routinely.
51
+
46
52
  ## Reference Files (Load On Demand)
47
53
 
48
54
  Quick reference -> ../using-plastic/references/quick-reference.md
@@ -29,7 +29,9 @@ plastic_branchCreate(
29
29
  plastic_switchBranch(branch="<parent-branch>/<new-branch>") # only when work should continue there
30
30
  ```
31
31
 
32
- Omitting `parent` for a relative branch name uses the current workspace branch as a convenience. A full hierarchical path is also accepted regardless of the loaded branch. Rare top-level branch creation requires `allowRootBranch=true`; use that override only when the user explicitly intends a new top-level hierarchy.
32
+ Omitting `parent` for a relative name requires directly observed standard-status loaded-branch identity. A changeset owner (including compact cs-only status) is not the loaded parent; provide an explicit parent when it cannot be observed. Qualifier-bearing relative names/parents are rejected rather than stripped; full hierarchical qualified targets stay verbatim. Rare top-level creation requires `allowRootBranch=true` only when explicitly intended.
33
+
34
+ A successful receipt proves `command-completed` only, with null observed created identity and unverified repository/domain effects, even for empty stdout. It does not switch the workspace or run a hidden verification query. After an uncertain attempt, inspect the receipt and state before retrying; command completion is not independent creation proof.
33
35
 
34
36
  Manual shell fallback requires constructing the hierarchical path explicitly:
35
37
 
@@ -45,6 +47,8 @@ Pending-change behavior in unattended runs:
45
47
  - `pendingChanges="bring"` is only blocked when tracked pending changes exist (Plastic requires interactive prompts).
46
48
  - If pending changes are private-only, `plastic_switchBranch` performs a direct non-interactive switch and keeps private files local.
47
49
  - `pendingChanges="shelve"` shelves tracked changes; for private-only pending changes, shelve is skipped and switch proceeds.
50
+ - Defaulted cancel permits private-only direct switching; explicit cancel blocks switching with any pending items. Native cancellation is not a successful switch; core non-preflight cancellation rejects to stop compound callers.
51
+ - Inspect native switch outcome, step effects and target verification. A shelve or switch command completing does not prove preservation, reusable shelveset identity, alias equivalence or rollback. Failed/malformed pending or recovery observations stop; compact changeset ownership is not the loaded branch. Qualification-ambiguous same-name requests are not skipped as already loaded; unverified post-qualification returns an error after retaining possible effects. Preflight is sequential local inspection only, not server-verified switchability.
48
52
 
49
53
  ## Merge Branches (Non-Interactive)
50
54
 
@@ -38,8 +38,9 @@ Unattended behavior notes:
38
38
  the tool auto-runs `cm add` for safe in-scope private paths, then retries checkin once.
39
39
  - Sensitive private paths (for example `.env*`, private key extensions, `.npmrc`, credentials/secrets JSON)
40
40
  are excluded from auto-add and reported as warnings/errors for explicit handling.
41
- - The JSON response includes decision metadata (`decisionPath`, pre/post pending summaries, and auto-add info)
42
- so agents can continue unattended without asking for confirmation.
41
+ - The closed receipt includes per-step attempts/capture/effects, pending summaries (null when unverified), and private-add/fallback decisions. Eligibility requires complete admitted native failure, never launch/capture/timeout/abort uncertainty.
42
+ - A failed checkin followed by an empty or failed pending read remains uncertain, not completed. Successful earlier add/checkin effects remain recorded after later failure. Completion requires an admitted root changeset, not exit zero or empty output. Scope exhaustion, branch head, alias equivalence and Xlink/link effects stay unverified.
43
+ - `preflight=true` performs one pending read, no mutation. Text/JSON present the same bounded receipt; registered native errors set `isError`.
43
44
 
44
45
  Note: avoid `updateAfter=true` in unattended runs because it can trigger interactive update-merge. Use `plastic_update()` followed by `plastic_merge(...)` (default auto strategy) when a merge is required.
45
46
 
@@ -75,18 +76,18 @@ Never run `cm diff` in Pi. Do not replace it with a routine safe-diff call: use
75
76
 
76
77
  ```text
77
78
  plastic_status(machineReadable=true) # list pending paths/statuses
78
- plastic_diffFile(path="<workspace-path>", maxChars=4000) # one intentional file comparison
79
- plastic_workspaceDiff(paths=["<workspace-path>"], maxChars=3000) # explicitly scoped review
80
- plastic_workspaceDiff(allPending=true) # explicit small whole-workspace review
81
- plastic_diffFile(path="<workspace-path>", revision="cs:<number>")
82
- plastic_diffRevisions(leftRevision="<left-file-qualified-revspec>", rightRevision="<right-file-qualified-revspec>")
79
+ plastic_diff(mode="file", path="<workspace-path>", maxChars=4000) # one intentional file comparison
80
+ plastic_diff(mode="workspace", paths=["<workspace-path>"], maxChars=3000) # explicitly scoped review
81
+ plastic_diff(mode="workspace", allPending=true) # explicit small whole-workspace review
82
+ plastic_diff(mode="file", path="<workspace-path>", revision="cs:<number>")
83
+ plastic_diff(mode="revisions", leftRevision="<left-file-qualified-revspec>", rightRevision="<right-file-qualified-revspec>")
83
84
  ```
84
85
 
85
86
  The typed diff tools own historical `cm cat --file` materialization and cleanup; do not create agent-authored temporary files for ordinary diffs. `cm cat --raw` is unsupported for this workflow. Use typed retrieval first; `cm cat --file` is the byte-preserving low-level fallback only when a typed tool cannot represent the case.
86
87
 
87
88
  ## Historical Export Fallback
88
89
 
89
- Use this fallback only when typed retrieval cannot represent the required case. For an ordinary workspace-base comparison, omit `revision` from `plastic_diffFile`; for a changeset comparison, supply its `revision="cs:<number>"` argument.
90
+ Use this fallback only when typed retrieval cannot represent the required case. For an ordinary workspace-base comparison, omit `revision` from `plastic_diff` in file mode; for a changeset comparison, supply its `revision="cs:<number>"` argument.
90
91
 
91
92
  `cm cat <revspec> --file=<output-file>` writes historical bytes to the output file and can overwrite it. `--file` does not select the source. Piping this command to `Set-Content` or redirecting stdout does not change the destination or protect it: with `--file`, the file contents are written there rather than to stdout.
92
93
 
@@ -1,30 +1,37 @@
1
1
  # Code Review Creation
2
2
 
3
- ## CLI (Preferred)
3
+ ## Tool-first flow
4
4
 
5
- ```bash
6
- branch=$(cm status | head -1 | cut -d@ -f1 | xargs)
7
- review_id=$(cm codereview "br:${branch}" "Feature: Brief description" --format="{id}")
8
-
9
- # Assign reviewer immediately after creating the review
10
- cm codereview -e "$review_id" --reviewer="reviewer-name"
5
+ Use `plastic_codeReviewCreate` with an explicitly inspected target and title. Optional `status`, `assignee`, and `repository` retain their CLI meanings. Set an assignee only when the user requests that assignment; creating a review does not authorize assigning another person.
11
6
 
12
- # List reviews for this branch
13
- cm find review "where target='br:${branch}'" --format="{id} {status} {title}"
7
+ ```text
8
+ plastic_codeReviewCreate(target="br:/main/owned-feature", title="Feature: Brief description", output="json")
9
+ # Inspect the receipt before using its observedCreatedIdentity in a separately authorized update.
10
+ plastic_codeReviewUpdate(id="<observed ID>", status="Reviewed", output="json")
14
11
  ```
15
12
 
16
- Agent preference: use runtime `plastic_*` methods first for status/branch lookup; keep shell commands as manual fallback.
13
+ Default numeric/GUID creation scalars can be emitted identity evidence. Requested repository scope and requested review state remain unverified. An update completing with empty stdout is command completion, not proof that the status changed. No automatic readback, retry, assignment, or review conversation follows.
17
14
 
18
- Reviewer assignment is required in the workflow. If your server uses different reviewer flags, run `cm codereview --help` and use the server-specific reviewer option right after review creation.
15
+ `output="text"|"json"` controls receipt presentation independently of creation `format`, which is passed to CM unchanged. Custom formatted stdout always remains opaque, even when it contains a numeric ID. Do not chain a substring of custom output into an update; an explicitly planned unique-title query can provide separate identity evidence.
19
16
 
20
- Tool-first flow (preferred):
17
+ ## Manual CLI fallback
21
18
 
22
- ```text
23
- plastic_status()
24
- # Then use cm codereview ... as fallback for review create/assign
19
+ Inspect the exact branch/changeset/shelveset selector first. Do not derive it by splitting or trimming human-readable status output.
20
+
21
+ ```bash
22
+ cm codereview "br:/main/owned-feature" "Feature: Brief description"
23
+ # Only if separately authorized, using an observed ID:
24
+ cm codereview -e "<observed ID>" --status="Reviewed"
25
+ # Only when an assignment was explicitly requested:
26
+ cm codereview -e "<observed ID>" --assignee="<requested user>"
25
27
  ```
26
28
 
27
- ## CLI Not Available
29
+ Installed CM11 help documents creation formatter `{0}` as ID and `{1}` as GUID. A bounded owned fixture validated `--format="{0}|{1}"`; a named `{id}|{title}` creation formatter failed with `Invalid format string`. Creation formatting and `cm find review` field formatting are different protocols: the tested find query accepts `{id}|{status}|{title}`, but `{guid}` failed. Do not infer support from an inconsistent help example.
30
+
31
+ ```bash
32
+ cm codereview "br:/main/owned-feature" "Owned review title" --format="{0}|{1}"
33
+ # Separate, explicitly scoped observation; not an automatic creation/update verification:
34
+ cm find review "where title='Owned review title'" --format="{id}|{status}|{title}" --nototal
35
+ ```
28
36
 
29
- - Document branch and changeset in plan/issue tracker.
30
- - Or create review via Plastic GUI and assign a reviewer before sharing the review link.
37
+ Failed commands may have effects; retain receipts and inspect the exact owned fixture before deciding what to do. If CLI access is unavailable, report that limitation or use the GUI only when requested. Do not silently create or assign a replacement review.
@@ -21,6 +21,10 @@ Plastic does not perform a Git-style fast-forward. It applies the merge into the
21
21
  - Only then create the merge checkin.
22
22
  - Verify the merge checkin was created on the target branch. If the workspace or changeset is on the source branch, stop: do not switch to the target and claim completion. Inspect target history and rerun the merge while the target is actually loaded.
23
23
 
24
+ ## Server-side File Conflict Resolution
25
+
26
+ Use `plastic_mergeBranches(mode="analyze", source="<qualified-source>", target="<qualified-target>")` for read-only conflict discovery, not `preflight`. Then independently inspect contributors, produce merged files, validate them, and submit a reviewed absolute `fileConflictsResolutionsFile` with a nonempty message. Follow the [full workflow and safety boundaries](../../../docs/2026-10-06-merging.md#agent-conflict-resolution-workflow). Exact destination conflict paths are JSON keys, not local workspace paths. Never guess the roles/repository of numeric FILE_CONFLICT fields, export over workspace files, automatically discard unlisted conflicts, or retry uncertain applying results. Keep all inputs immutable through completion; directory conflicts and shelve modes remain separate.
27
+
24
28
  ## Review Workflow
25
29
 
26
30
  - Create or reuse review workspace.
@@ -42,11 +46,11 @@ plastic_patch(source="<branch-spec>", clean=true, integration=true, output="<new
42
46
  - When change-boundary evidence is needed, inspect specific files first and opt into multi-file output explicitly:
43
47
 
44
48
  ```text
45
- plastic_diffFile(path="<workspace-path>", maxChars=4000) # one intentional file comparison
46
- plastic_workspaceDiff(paths=["<workspace-path>"], maxChars=3000) # explicitly scoped pending review
47
- plastic_workspaceDiff(allPending=true) # explicit small whole-workspace review
48
- plastic_diffFile(path="<workspace-path>", revision="cs:<number>")
49
- plastic_diffRevisions(leftRevision="<left-file-qualified-revspec>", rightRevision="<right-file-qualified-revspec>")
49
+ plastic_diff(mode="file", path="<workspace-path>", maxChars=4000) # one intentional file comparison
50
+ plastic_diff(mode="workspace", paths=["<workspace-path>"], maxChars=3000) # explicitly scoped pending review
51
+ plastic_diff(mode="workspace", allPending=true) # explicit small whole-workspace review
52
+ plastic_diff(mode="file", path="<workspace-path>", revision="cs:<number>")
53
+ plastic_diff(mode="revisions", leftRevision="<left-file-qualified-revspec>", rightRevision="<right-file-qualified-revspec>")
50
54
  ```
51
55
 
52
56
  ## Plan Workflow
@@ -18,10 +18,10 @@ Note: command examples target current `cm` 11.x CLI syntax; legacy aliases may d
18
18
  | Shelve | `plastic_shelvesetCreate(comment="description")` | `cm shelveset create -c="description"` | Save work temporarily |
19
19
  | Patch for review | `plastic_patch(source="<branch-spec>", clean=true, integration=true, output="<new-patch-file>")` | `cm patch <branch-spec> --clean --integration --output=<patch-file>` | Uses `toolPath` then `PI_PLASTIC_PATCH_EXECUTABLE`; Windows requires a verified patch-capable non-GUI executable. New output is staged and atomically published without overwrite; use repository-qualified `br:/...@<repository>@<server>` if selector resolution is unavailable |
20
20
  | Changed-file listing | `plastic_status(machineReadable=true)` | `cm status --all` | Lists changed, added, moved, deleted, and private items without GUI diff |
21
- | Diff (one workspace file) | `plastic_diffFile(path="<workspace-path>", maxChars=4000)` | None recommended | Intentional focused comparison only; not routine validation |
22
- | Diff (pending review) | `plastic_workspaceDiff(paths=["<workspace-path>"], maxChars=3000)` | None recommended | Requires selected paths or explicit `allPending=true`; use status for changed-path listing |
23
- | Diff (workspace vs supported revision) | `plastic_diffFile(path="<workspace-path>", revision="cs:<number>")` | None recommended | Also accepts a branch, label, file-qualified, or global revision spec |
24
- | Diff (revision vs revision) | `plastic_diffRevisions(leftRevision="<left-revspec>", rightRevision="<right-revspec>")` | None recommended | Requires two file-qualified revisions; avoids GUI and package temp-file recipes |
21
+ | Diff (one workspace file) | `plastic_diff(mode="file", path="<workspace-path>", maxChars=4000)` | None recommended | Intentional focused comparison only; not routine validation |
22
+ | Diff (pending review) | `plastic_diff(mode="workspace", paths=["<workspace-path>"], maxChars=3000)` | None recommended | Requires selected paths or explicit `allPending=true`; use status for changed-path listing |
23
+ | Diff (workspace vs supported revision) | `plastic_diff(mode="file", path="<workspace-path>", revision="cs:<number>")` | None recommended | Also accepts a branch, label, file-qualified, or global revision spec |
24
+ | Diff (revision vs revision) | `plastic_diff(mode="revisions", leftRevision="<left-revspec>", rightRevision="<right-revspec>")` | None recommended | Requires two file-qualified revisions; avoids GUI and package temp-file recipes |
25
25
 
26
26
  ## Merge verification checklist
27
27
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  Never run `cm diff` in Pi. It may open a GUI window and block CLI automation.
6
6
 
7
- `plastic_diff` is disabled by design.
7
+ `plastic_diff` requires an explicit mode and performs bounded non-GUI comparison.
8
8
 
9
9
  ## Focused Patch Generation
10
10
 
@@ -24,14 +24,14 @@ Do not use diffs as routine post-edit validation or checkin preflight. Prefer fo
24
24
 
25
25
  ```text
26
26
  plastic_status(machineReadable=true) # “what changed?” / changed-path listing
27
- plastic_diffFile(path="<workspace-path>", maxChars=4000) # one intentional file comparison
28
- plastic_workspaceDiff(paths=["<workspace-path>"], maxChars=3000) # explicitly scoped pending review
29
- plastic_workspaceDiff(allPending=true) # explicit small whole-workspace review
30
- plastic_diffFile(path="<workspace-path>", revision="cs:<number>") # explicit historical comparison
31
- plastic_diffRevisions(leftRevision="<left-file-qualified-revspec>", rightRevision="<right-file-qualified-revspec>")
27
+ plastic_diff(mode="file", path="<workspace-path>", maxChars=4000) # one intentional file comparison
28
+ plastic_diff(mode="workspace", paths=["<workspace-path>"], maxChars=3000) # explicitly scoped pending review
29
+ plastic_diff(mode="workspace", allPending=true) # explicit small whole-workspace review
30
+ plastic_diff(mode="file", path="<workspace-path>", revision="cs:<number>") # explicit historical comparison
31
+ plastic_diff(mode="revisions", leftRevision="<left-file-qualified-revspec>", rightRevision="<right-file-qualified-revspec>")
32
32
  ```
33
33
 
34
- The diff tools materialize historical bytes and remove their temporary files internally. Added and explicitly selected private/new files use an empty base; an added empty file is reported as `added-empty`, not generic unchanged. Changed, moved, and deleted records use their status revision ID when available. A `--nodata` item cannot supply historical/base bytes: refresh the workspace or use two known file-qualified revisions. `plastic_workspaceDiff` requires selected paths or `allPending=true` and returns per-file unavailable outcomes instead of aborting the batch. Text-diff tools use bounded GNU/POSIX text diff, materialize backend operands to ASCII-safe package paths while preserving logical Unicode output labels, treat valid Unity YAML as text, and report genuine binary content explicitly. Do not construct `cm cat` temporary-file recipes for ordinary review. `cm cat --raw` is unsupported in this workflow; typed retrieval owns cleanup, and `cm cat --file` is the byte-preserving low-level fallback only when needed.
34
+ The consolidated tool owns historical exports, stable bounded local snapshots and cleanup. Added and explicitly selected private files use a deliberate empty base; added-empty is distinct from unchanged. Loaded bases use strict fileinfo/ls metadata, a genuine qualified file revision ID, and hash-verified exported bytes, never status changesets relabeled as revisions. Controlled deletions use the frozen loaded workspace tree; local moves use observed OldPath metadata. Workspace mode retains per-file failures with ok=false; inspect counts, exclusions, unmatched paths and read/projection completeness, including limited, skipped and unattempted comparisons. Default all-pending review selects at most three items. Directory selections include pending descendants; directory rows are explicitly skipped. No failed export becomes an empty side, no atomic snapshot is claimed, and general mixed-Xlink ownership remains unverified. Text uses one configured GNU/POSIX backend with ASCII-safe operands and logical Unicode labels; binary inputs use byte equality. Do not construct cm cat recipes for ordinary review.
35
35
 
36
36
  `cm cat --file` is a filesystem write: its argument is the output destination, never the source file. Pointing it at a working file overwrites pending changes even if stdout is piped to a temporary file. Reviewers must use typed comparisons for ordinary baseline review; a necessary unsupported export must follow the [historical export rules](changeset-operations.md#historical-export-fallback), with a new absolute temporary destination outside the workspace and a verified file revision selector.
37
37
 
@@ -52,9 +52,9 @@ Use the Read tool for full context instead of GUI diffs.
52
52
  Use the common no-revision path first; `base`, `head`, and `cs:head` are rejected rather than guessed:
53
53
 
54
54
  ```text
55
- plastic_diffFile(path="<workspace-path>", maxChars=4000)
56
- plastic_diffFile(path="<workspace-path>", revision="br:/<branch>", maxChars=4000)
57
- plastic_diffRevisions(leftRevision="<left-file-qualified-revspec>", rightRevision="<right-file-qualified-revspec>", maxChars=4000)
55
+ plastic_diff(mode="file", path="<workspace-path>", maxChars=4000)
56
+ plastic_diff(mode="file", path="<workspace-path>", revision="br:/<branch>", maxChars=4000)
57
+ plastic_diff(mode="revisions", leftRevision="<left-file-qualified-revspec>", rightRevision="<right-file-qualified-revspec>", maxChars=4000)
58
58
  ```
59
59
 
60
60
  ## Pending Changes
@@ -1,25 +1,40 @@
1
1
  # Shelving
2
2
 
3
- ## Save Work
3
+ Prefer runtime `plastic_*` tools. Inspect the intended paths and shelveset spelling before mutation; do not broaden into existing objects or workspace-wide recovery automatically.
4
4
 
5
- ```bash
6
- cm shelveset create -c="Working on feature X"
5
+ ## Save work
6
+
7
+ ```text
8
+ plastic_shelvesetCreate(paths=["owned.txt"], comment="Working on feature X", summaryFormat=true, output="json")
7
9
  ```
8
10
 
9
- ## List Shelves
11
+ A supported full emitted shelveset scalar can provide created identity evidence; repository scope, workspace exclusivity and rollback remain unverified. Preserve every qualifier. Paths and requested `all`/`dependencies` controls are passed unchanged. Comment and commentsFile are exclusive. Comment-less creation is refused when PLASTICEDITOR is configured; supply a comment rather than overriding the environment.
10
12
 
11
- ```bash
12
- cm find shelve "where owner='me'" --format="{shelveid} {date} {comment}"
13
+ ## Inspect and apply
14
+
15
+ ```text
16
+ plastic_shelvesetList()
17
+ plastic_shelvesetApply(shelveset="<observed exact shelveset selector>", output="json")
13
18
  ```
14
19
 
15
- ## Restore Work
20
+ Command completion does not prove which items applied or that the workspace is restored/clean. Inspect the receipt and independently scoped workspace observations. `preview=true` really dispatches CM and can still prompt/fail on conflicts; `preflight=true` only renders argv with zero CLI calls and does not analyze conflicts. Do not run both routinely.
16
21
 
17
- ```bash
18
- cm shelveset apply sh:5
22
+ A fresh checked-out tracked text change has conflict-free same-base preview/apply evidence on Windows CM11. An earlier added-item preview prompted evil-twin resolution and failed. This does not establish a safe general conflict-resolution policy. Never force a choice, replay an uncertain apply or undo broad workspace scope automatically. Owned parent restoration is a separate authorized action, not producer rollback.
23
+
24
+ ## Delete
25
+
26
+ ```text
27
+ plastic_shelvesetDelete(shelveset="<explicitly owned exact selector>", output="json")
19
28
  ```
20
29
 
21
- ## Delete Shelve
30
+ Delete only when requested. Exit0 does not prove deletion/absence or history restoration; the receipt leaves those effects unverified.
31
+
32
+ ## Manual CLI fallback
22
33
 
23
34
  ```bash
24
- cm shelveset delete sh:5
35
+ cm shelveset create "owned.txt" -c="Working on feature X" --summaryformat
36
+ cm shelveset apply "<observed exact shelveset selector>"
37
+ cm shelveset delete "<explicitly owned exact selector>"
25
38
  ```
39
+
40
+ These are separate mutations, not a default chained workflow. Preserve prior positive effects and failed probes; missing identity or unsafe interaction is a stop signal, not permission to guess a selector or change strategy.
@@ -48,11 +48,11 @@ cm codereview -e <review-id> --reviewer="reviewer-name"
48
48
  - `cm diff` is blocked in Pi because it can launch GUI windows and hang the CLI.
49
49
  - Use text-only alternatives:
50
50
  - `plastic_status(machineReadable=true)` for changed paths/statuses.
51
- - `plastic_diffFile(path="<workspace-path>", maxChars=4000)` when one intentional file comparison is needed.
52
- - `plastic_workspaceDiff(paths=["<workspace-path>"], maxChars=3000)` for an explicitly scoped pending review, or `allPending=true` for an intentional small whole-workspace review.
51
+ - `plastic_diff(mode="file", path="<workspace-path>", maxChars=4000)` when one intentional file comparison is needed.
52
+ - `plastic_diff(mode="workspace", paths=["<workspace-path>"], maxChars=3000)` for an explicitly scoped pending review, or `allPending=true` for an intentional small whole-workspace review.
53
53
  - Use `plastic_status` instead when only pending paths are needed; do not diff routinely.
54
- - `plastic_diffFile(path="<workspace-path>", revision="cs:<number>")` for an explicit supported revision.
55
- - `plastic_diffRevisions(leftRevision="<left-file-qualified-revspec>", rightRevision="<right-file-qualified-revspec>")` for two historical files.
54
+ - `plastic_diff(mode="file", path="<workspace-path>", revision="cs:<number>")` for an explicit supported revision.
55
+ - `plastic_diff(mode="revisions", leftRevision="<left-file-qualified-revspec>", rightRevision="<right-file-qualified-revspec>")` for two historical files.
56
56
 
57
57
  ## GUI/Prompt Opens During Merge
58
58
 
@@ -0,0 +1,74 @@
1
+ import { constants, promises as fs, type Stats } from "node:fs";
2
+ import { isAbsolute, relative, sep } from "node:path";
3
+
4
+ export const LOCAL_SNAPSHOT_MAX_BYTES = 4 * 1024 * 1024;
5
+ export type LocalSnapshotErrorCode = "missing_file" | "unsupported_file" | "outside_workspace" | "file_limit" | "file_changed" | "aborted" | "read_failed";
6
+ export class LocalSnapshotError extends Error {
7
+ constructor(readonly code: LocalSnapshotErrorCode) { super("A bounded local file snapshot could not be verified."); }
8
+ }
9
+ interface SnapshotHandle {
10
+ stat(): Promise<Stats>;
11
+ read(buffer: Buffer, offset: number, length: number, position: number): Promise<{ bytesRead: number }>;
12
+ close(): Promise<void>;
13
+ }
14
+ export interface LocalSnapshotFilesystem {
15
+ lstat(path: string): Promise<Stats>;
16
+ realpath(path: string): Promise<string>;
17
+ open(path: string): Promise<SnapshotHandle>;
18
+ }
19
+ const filesystem: LocalSnapshotFilesystem = {
20
+ lstat: path => fs.lstat(path),
21
+ realpath: path => fs.realpath(path),
22
+ open: path => fs.open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)),
23
+ };
24
+ const same = (a: Stats, b: Stats): boolean =>
25
+ a.dev === b.dev && a.ino === b.ino && a.size === b.size && a.mtimeMs === b.mtimeMs && a.ctimeMs === b.ctimeMs;
26
+ const regular = (s: Stats) => s.isFile() && !s.isSymbolicLink();
27
+ const abort = (signal?: AbortSignal) => { if (signal?.aborted) throw new LocalSnapshotError("aborted"); };
28
+
29
+ // The caller supplies an absolute file and the discovered workspace root. This
30
+ // helper never discovers a different workspace, retries, or writes a local file.
31
+ // Timestamp/identity checks detect observable changes; they are not an atomic
32
+ // filesystem snapshot or protection against an adversary restoring timestamps.
33
+ export async function snapshotLocalFile(
34
+ filePath: string,
35
+ workspaceRoot: string,
36
+ signal?: AbortSignal,
37
+ io: LocalSnapshotFilesystem = filesystem,
38
+ ): Promise<{ bytes: Buffer; physicalPath: string }> {
39
+ abort(signal);
40
+ if (!isAbsolute(filePath) || !isAbsolute(workspaceRoot)) throw new LocalSnapshotError("outside_workspace");
41
+ try {
42
+ const root = await io.realpath(workspaceRoot);
43
+ const initial = await io.lstat(filePath);
44
+ if (!regular(initial)) throw new LocalSnapshotError("unsupported_file");
45
+ const physicalPath = await io.realpath(filePath);
46
+ const scope = relative(root, physicalPath);
47
+ if (!scope || scope === ".." || scope.startsWith(".." + sep) || isAbsolute(scope)) throw new LocalSnapshotError("outside_workspace");
48
+ abort(signal);
49
+ const handle = await io.open(filePath);
50
+ try {
51
+ const before = await handle.stat();
52
+ if (!regular(before) || !same(initial, before)) throw new LocalSnapshotError("file_changed");
53
+ if (!Number.isSafeInteger(before.size) || before.size < 0 || before.size > LOCAL_SNAPSHOT_MAX_BYTES) throw new LocalSnapshotError("file_limit");
54
+ const buffer = Buffer.alloc(before.size + 1);
55
+ let length = 0;
56
+ while (length < buffer.length) {
57
+ abort(signal);
58
+ const read = await handle.read(buffer, length, buffer.length - length, length);
59
+ if (!Number.isInteger(read.bytesRead) || read.bytesRead < 0 || read.bytesRead > buffer.length - length) throw new LocalSnapshotError("read_failed");
60
+ if (!read.bytesRead) break;
61
+ length += read.bytesRead;
62
+ }
63
+ const after = await handle.stat();
64
+ const current = await io.lstat(filePath);
65
+ if (length !== before.size || !same(before, after) || !regular(current) || !same(before, current) || await io.realpath(filePath) !== physicalPath) throw new LocalSnapshotError("file_changed");
66
+ abort(signal);
67
+ return { bytes: buffer.subarray(0, length), physicalPath };
68
+ } finally { await handle.close(); }
69
+ } catch (error) {
70
+ if (error instanceof LocalSnapshotError) throw error;
71
+ const code = (error as NodeJS.ErrnoException)?.code;
72
+ throw new LocalSnapshotError(code === "ENOENT" ? "missing_file" : code === "ELOOP" ? "unsupported_file" : "read_failed");
73
+ }
74
+ }