@aefree/pi-plastic 0.7.6 → 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.
- package/CHANGELOG.md +37 -0
- package/README.md +48 -174
- package/docs/2026-10-06-development.md +101 -0
- package/docs/2026-10-06-diffs-and-patches.md +136 -0
- package/docs/2026-10-06-discovery.md +135 -0
- package/docs/2026-10-06-integrations.md +49 -0
- package/docs/2026-10-06-merging.md +153 -0
- package/docs/2026-10-06-safety-and-receipts.md +95 -0
- package/docs/2026-10-06-setup.md +101 -0
- package/docs/2026-10-06-shelvesets-and-reviews.md +113 -0
- package/docs/2026-10-06-status.md +116 -0
- package/docs/2026-10-06-tools-and-loading.md +64 -0
- package/docs/2026-10-06-workspace-operations.md +142 -0
- package/docs/README.md +65 -0
- package/extensions/bash-cm-diff-guard.ts +1 -1
- package/index.ts +4 -687
- package/package.json +10 -4
- package/skills/using-plastic/SKILL.md +11 -5
- package/skills/using-plastic/references/branch-management.md +5 -1
- package/skills/using-plastic/references/changeset-operations.md +9 -8
- package/skills/using-plastic/references/code-review-creation.md +25 -18
- package/skills/using-plastic/references/integration.md +9 -5
- package/skills/using-plastic/references/quick-reference.md +4 -4
- package/skills/using-plastic/references/reviewing-changes.md +10 -10
- package/skills/using-plastic/references/shelving.md +26 -11
- package/skills/using-plastic/references/troubleshooting.md +4 -4
- package/src/diff/local-snapshot.ts +74 -0
- package/src/diff/text.ts +187 -0
- package/src/domain/add-contract.ts +24 -0
- package/src/domain/branch-create-contract.ts +38 -0
- package/src/domain/branch-delete-contract.ts +15 -0
- package/src/domain/branches.ts +263 -0
- package/src/domain/checkin-contract.ts +70 -0
- package/src/domain/closeout-contract.ts +30 -0
- package/src/domain/copied-merge.ts +42 -0
- package/src/domain/diff-base-xml.ts +65 -0
- package/src/domain/diff-pending-xml.ts +34 -0
- package/src/domain/merge-output.ts +327 -0
- package/src/domain/paths.ts +253 -0
- package/src/domain/pending.ts +499 -0
- package/src/domain/removal-contract.ts +25 -0
- package/src/domain/revisions.ts +301 -0
- package/src/domain/server-merge-contract.ts +45 -0
- package/src/domain/server-merge-resolutions.ts +61 -0
- package/src/domain/status-xml.ts +122 -0
- package/src/domain/switch-contract.ts +60 -0
- package/src/domain/switch-policy.ts +95 -0
- package/src/domain/undo-contract.ts +24 -0
- package/src/domain/update-contract.ts +16 -0
- package/src/domain/workspace-merge-contract.ts +41 -0
- package/src/execution/add-command.ts +118 -0
- package/src/execution/branch-create-command.ts +118 -0
- package/src/execution/branch-list-command.ts +75 -0
- package/src/execution/checkin-command.ts +116 -0
- package/src/execution/cli-version.ts +31 -0
- package/src/execution/cm.ts +59 -0
- package/src/execution/code-review-find-command.ts +75 -0
- package/src/execution/context.ts +20 -0
- package/src/execution/diff-revisions-command.ts +74 -0
- package/src/execution/process.ts +260 -0
- package/src/execution/removal-command.ts +118 -0
- package/src/execution/server-merge-command.ts +92 -0
- package/src/execution/shelveset-list-command.ts +75 -0
- package/src/execution/status-command.ts +29 -0
- package/src/execution/status-xml-command.ts +72 -0
- package/src/execution/status-xml-limits.ts +1 -0
- package/src/execution/switch-command.ts +118 -0
- package/src/execution/test-seams.ts +10 -0
- package/src/execution/undo-command.ts +118 -0
- package/src/execution/update-command.ts +118 -0
- package/src/execution/workspace-list-command.ts +75 -0
- package/src/execution/workspace-merge-command.ts +118 -0
- package/src/operations/add-receipt.ts +37 -0
- package/src/operations/arguments.ts +3 -0
- package/src/operations/branch-create.ts +74 -0
- package/src/operations/branch-delete-receipt.ts +25 -0
- package/src/operations/branch-list.ts +94 -0
- package/src/operations/branch-reads.ts +94 -0
- package/src/operations/branches.ts +111 -0
- package/src/operations/checkin-receipt.ts +140 -0
- package/src/operations/checkin.ts +102 -0
- package/src/operations/closeout-receipt.ts +74 -0
- package/src/operations/closeout.ts +19 -0
- package/src/operations/code-review-find.ts +97 -0
- package/src/operations/consolidated-diff.ts +144 -0
- package/src/operations/diff-revisions.ts +157 -0
- package/src/operations/diff.ts +53 -0
- package/src/operations/merge.ts +26 -0
- package/src/operations/object-delete-receipt.ts +21 -0
- package/src/operations/object-write-receipt.ts +105 -0
- package/src/operations/patch-receipt.ts +96 -0
- package/src/operations/patch-support.ts +375 -0
- package/src/operations/patch.ts +25 -0
- package/src/operations/removal-receipt.ts +42 -0
- package/src/operations/reviews.ts +90 -0
- package/src/operations/server-merge.ts +130 -0
- package/src/operations/shelveset-list.ts +90 -0
- package/src/operations/shelvesets.ts +90 -0
- package/src/operations/status.ts +129 -0
- package/src/operations/switch-receipt.ts +85 -0
- package/src/operations/switch.ts +25 -0
- package/src/operations/undo-receipt.ts +37 -0
- package/src/operations/update-receipt.ts +35 -0
- package/src/operations/workspace-list.ts +92 -0
- package/src/operations/workspace-merge-receipt.ts +50 -0
- package/src/operations/workspace.ts +120 -0
- package/src/pi/add-output.ts +52 -0
- package/src/pi/arguments.ts +298 -0
- package/src/pi/branch-create-output.ts +83 -0
- package/src/pi/branch-delete-output.ts +46 -0
- package/src/pi/branch-list-output.ts +59 -0
- package/src/pi/branch-output.ts +63 -0
- package/src/pi/capability-diagnostics.ts +23 -0
- package/src/pi/checkin-output.ts +68 -0
- package/src/pi/closeout-output.ts +87 -0
- package/src/pi/code-review-find-output.ts +50 -0
- package/src/pi/diff-output.ts +66 -0
- package/src/pi/diff-revisions-output.ts +55 -0
- package/src/pi/object-delete-output.ts +15 -0
- package/src/pi/object-write-output.ts +54 -0
- package/src/pi/patch-output.ts +69 -0
- package/src/pi/register.ts +188 -0
- package/src/pi/removal-output.ts +59 -0
- package/src/pi/schemas.ts +146 -0
- package/src/pi/server-merge-output.ts +83 -0
- package/src/pi/shelveset-list-output.ts +50 -0
- package/src/pi/status-output.ts +149 -0
- package/src/pi/switch-output.ts +80 -0
- package/src/pi/tool-registry.ts +72 -0
- package/src/pi/undo-output.ts +52 -0
- package/src/pi/update-output.ts +48 -0
- package/src/pi/workspace-list-output.ts +53 -0
- package/src/pi/workspace-merge-output.ts +46 -0
- package/src/plastic-core.ts +14 -5775
- package/src/plastic-renderers.ts +102 -22
- package/src/plastic-tool-loading.ts +7 -12
- package/src/presentation/add-results.ts +5 -0
- package/src/presentation/branch-create.ts +7 -0
- package/src/presentation/branch-delete-results.ts +7 -0
- package/src/presentation/checkin-results.ts +14 -0
- package/src/presentation/closeout-results.ts +2 -0
- package/src/presentation/diff-results.ts +237 -0
- package/src/presentation/removal-results.ts +7 -0
- package/src/presentation/results.ts +84 -0
- package/src/presentation/switch-results.ts +7 -0
- package/src/presentation/undo-results.ts +5 -0
- package/src/presentation/update-results.ts +5 -0
- 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.
|
|
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,16 +36,18 @@
|
|
|
35
36
|
"scripts": {
|
|
36
37
|
"typecheck": "tsc -p tsconfig.json",
|
|
37
38
|
"pretest": "npm run typecheck",
|
|
38
|
-
"test": "tsx tests/
|
|
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": "
|
|
47
|
-
"@earendil-works/pi-tui": "
|
|
49
|
+
"@earendil-works/pi-coding-agent": "1.1.0",
|
|
50
|
+
"@earendil-works/pi-tui": "1.1.0",
|
|
48
51
|
"@types/node": "22.20.4",
|
|
49
52
|
"tsx": "4.23.15",
|
|
50
53
|
"typebox": "^1.3.8",
|
|
@@ -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`
|
|
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`
|
|
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
|
-
|
|
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`
|
|
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`
|
|
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
|
|
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
|
|
42
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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 `
|
|
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
|
-
##
|
|
3
|
+
## Tool-first flow
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
+
## Manual CLI fallback
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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) | `
|
|
22
|
-
| Diff (pending review) | `
|
|
23
|
-
| Diff (workspace vs supported revision) | `
|
|
24
|
-
| Diff (revision vs revision) | `
|
|
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`
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
- `
|
|
52
|
-
- `
|
|
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
|
-
- `
|
|
55
|
-
- `
|
|
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
|
+
}
|