pm-csv 2026.10.22 → 2026.10.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,146 @@
1
+ # Edit a CSV checkout safely
2
+
3
+ `csv export` and `csv import --key` remain format conversion and keyed upsert.
4
+ For a spreadsheet edit session, use the separate checkout/apply protocol:
5
+
6
+ ```sh
7
+ pm csv checkout --output planning.csv --json
8
+ # Edit title, body, priority, assignee, sprint or release cells in a spreadsheet.
9
+ pm csv apply planning.csv --dry-run --json
10
+ pm csv apply planning.csv --json
11
+ # Identical retry: applied=0, with no additional item/history writes.
12
+ pm csv apply planning.csv
13
+ ```
14
+
15
+ For a selection, pass `--ids item-a,item-b` to checkout. The default is a certified
16
+ complete tracker read. A checkout accepts up to 1000 distinct canonical item IDs;
17
+ split larger selections. These examples use illustrative IDs; use IDs from your
18
+ tracker. Files are UTF-8 comma-separated CSV, with quoted multiline cells, and
19
+ can be saved with CRLF or a BOM. Newline-only LF/CRLF conversions in editable
20
+ cells count as unchanged and retain the exact baseline value, including when a
21
+ concurrent writer has changed that field. Genuine content edits retain their
22
+ normal conflict checks. Keep column names/order and digest cells intact.
23
+ Rows may be reordered or omitted. Omission never deletes an item. Blank optional
24
+ scalar cells unset that metadata; blank body clears it. Priority must be 0..4,
25
+ and title must be nonblank. Scalar edits must be trimmed. Body uses the host's
26
+ canonical trailing-whitespace normalization. Checkout prefixes editable values
27
+ that start with `=`, `+`, `-`, `@`, tab, carriage return or an apostrophe
28
+ with an apostrophe. Apply removes one recognized display prefix before comparison
29
+ and storage, preserving raw baseline values and digests. Keep these prefixes when
30
+ saving the CSV. For new formula-leading or apostrophe-leading values, use the
31
+ same escape convention (for example, `'=SUM(1,2)` or `''literal`). Import all
32
+ columns as text to preserve the literal CSV strings and digests; spreadsheet
33
+ applications can otherwise change values or strip the display escape.
34
+
35
+ Checkout writes `planning.csv` and `planning.csv.session.json` exclusively, with
36
+ owner-only file permissions. Existing files are never overwritten. If CSV
37
+ creation fails after the baseline is written, the refusal explains that an
38
+ incomplete baseline may remain; choose new filenames after checking the files.
39
+ Keep the JSON baseline unchanged, or specify it with `--session baseline.json`.
40
+ The baseline contains exported values, so protect it like the CSV. Digests detect
41
+ accidental alteration and bind the CSV to its baseline; they are not signatures
42
+ or authorization credentials. Tracker permissions and SDK ownership policy still
43
+ apply. Do not edit digest columns to override a conflict.
44
+
45
+ Each row has `_session`, stable `id`, `_revision` (verified history digest),
46
+ `_item_digest` (full document digest), read-only `status`, and a `_digest_<field>`
47
+ column for every editable field. Custom fields, relationships, collections, type,
48
+ and lifecycle transitions are outside this scalar protocol. Use native commands
49
+ for those operations, then export again. In particular, use `pm close` / `pm
50
+ reopen` for status changes. Editing fields on an already terminal keyed item
51
+ preserves its identity, status, completion timestamp and closure evidence.
52
+ This does not change legacy `csv import --key` retry behavior.
53
+
54
+ For each edited field, apply compares **baseline / edited CSV / current tracker**:
55
+
56
+ | Current tracker | Result |
57
+ | --- | --- |
58
+ | Equals baseline | Apply the edited value |
59
+ | Equals edited value | Already converged; no item/history write |
60
+ | Differs from both | Conflict; refuse the whole table during preflight |
61
+ | Changed only in an unedited field | Preserve that change and count the row as concurrent |
62
+
63
+ Preflight validates the complete table and all selected items before starting a
64
+ transaction. A malformed, changed or conflicting digest causes zero item/history
65
+ writes and creates no transaction journal. A successful dry run is a preview,
66
+ not a reservation: a native writer can change state immediately afterward.
67
+ The patch rechecks each item while holding the SDK item lock. Every native
68
+ mutation callback used by apply and compensation records a monotonic start time
69
+ and the effective `locks.ttl_seconds`. Before returning (including no-op returns),
70
+ it refuses with typed `csv_lease_expired` when elapsed time reaches the TTL minus
71
+ ten percent, capped at a one-second safety margin. Checkout, prior-document
72
+ capture and representation restoration also check this budget before returning
73
+ or writing. The remedy is: re-run apply; another writer may have changed the item.
74
+
75
+ Run `node --test test/repros/sdk-lease-overrun.ts` for two passing real-SDK
76
+ regressions. A disposable non-git tracker uses a real one-second lease; a newer
77
+ native SDK writer or separate CLI writer takes over while the first callback is
78
+ suspended. The package guard refuses the stale callback and preserves the newer
79
+ value. These cases also run in the normal release suite through
80
+ `test/lease-guard.test.ts`, alongside actual apply and compensation wiring cases.
81
+
82
+ The deadline is a package mitigation, not atomic persistence fencing. The SDK
83
+ reads before entering the callback and persists after its return; arbitrary host
84
+ delays in those windows can exhaust the lease despite the margin. A callback
85
+ check cannot atomically close that later write window. The root fix belongs
86
+ upstream in [pm-cli issue 1549](https://github.com/unbraind/pm-cli/issues/1549),
87
+ verified by the owner on SDK 2026.10.22; the package dependency pin is unchanged.
88
+
89
+ Ordinary failures trigger the existing SDK transaction coordinator and CSV
90
+ full-document compensation; compensation checks for intervening writers before
91
+ restoring and depends on the same host lease contract. Such a late
92
+ failure can append history or leave mutations pending reconciliation: it is not a
93
+ database rollback or a promise of zero history writes after application starts.
94
+ Respect the `csv_edit_apply_failed` remedy and inspect the named transaction.
95
+ Interrupted runs use stable session/table/row identities and can resume without
96
+ retargeting already applied rows. Reusing an old committed session after a newer
97
+ writer changes an edited value is refused; export again.
98
+
99
+ Successful command results are compact TOON by default or JSON with `--json`:
100
+
101
+ ```json
102
+ {"rows":2,"changed":1,"unchanged":1,"concurrent":1,"applied":0}
103
+ ```
104
+
105
+ Counts are per row. `changed` is the preflight plan and `applied` counts actual
106
+ mutations in this attempt; convergence after preflight can reduce `applied`.
107
+ `concurrent` includes unedited and convergent concurrent
108
+ changes. An applied batch also returns its transaction ID. Refusals have stable
109
+ codes (`csv_session_invalid`, `csv_edit_invalid`, `csv_edit_conflict`,
110
+ `csv_edit_apply_failed`, `csv_lease_expired`) and remedies. SDK `EditRefusal` exposes `code`, `remedy`,
111
+ `conflicts` and `conflictCount`. Transaction refusals retain nested conflict keys
112
+ and expose the original error as `cause`, including lease expiration, interruption
113
+ and compensation failures. Checkout/apply IO and snapshot read refusals also retain their causes.
114
+ Diagnostics show at most twenty item/field keys,
115
+ with the total count, and never include cell values. Command error messages carry
116
+ the same code, remedy and bounded conflicts; the host controls error rendering.
117
+
118
+ ## Typed SDK example
119
+
120
+ ```ts
121
+ import { exportEditSession, applyEditSession } from "pm-csv";
122
+
123
+ const { session, table } = await exportEditSession(pmRoot, itemIds);
124
+ const titleColumn = table[0].indexOf("title");
125
+ table[1][titleColumn] = "Reviewed title";
126
+ const preview = await applyEditSession(pmRoot, session, table, {
127
+ author: "spreadsheet-editor", dryRun: true,
128
+ });
129
+ const result = await applyEditSession(pmRoot, session, table, {
130
+ author: "spreadsheet-editor",
131
+ });
132
+ ```
133
+
134
+ `parseEditSession` validates untrusted JSON before use. `EditSession`,
135
+ `EditSessionRow`, `EditField`, `EditReceipt`, `EditConflict` and `EditRefusalCode`
136
+ are exported types. SDK tables are arrays of CSV string cells; use a CSV codec
137
+ when serializing, rather than joining cells with commas. The SDK accepts an
138
+ optional transaction `onTransition` observer for durable execution diagnostics.
139
+
140
+ ## Without git
141
+
142
+ Checkout, conflict checking and application work in a plain tracker initialized
143
+ with `pm init`, including with no `git` executable on PATH. This protocol neither
144
+ invokes Git nor needs branches. It uses the SDK's existing tracker storage,
145
+ ownership checks, item locks, immutable history and transaction journal. Tests
146
+ use real non-git trackers; hosted backend acceptance is separate evidence.
@@ -0,0 +1,47 @@
1
+ # Public tracker copy validation
2
+
3
+ Source: public `unbraind/pm-csv`, main commit
4
+ `1f7e523b6d5539264d3316362665506486eeea3a` (2026.10.21).
5
+ Repository visibility was checked before copying. Only the committed `.agents/pm`
6
+ subtree was archived to a newly created non-git scratch directory. The source
7
+ tracker was read-only throughout; all test mutations targeted the disposable
8
+ copy. No exported tracker content is committed as a fixture.
9
+
10
+ Procedure: `git archive origin/main .agents/pm` into a scratch directory, enumerate
11
+ items with the pinned SDK and explicit settings/type registry, exercise the built
12
+ `dist` SDK, then install the built extension into the scratch tracker's extensions
13
+ directory and invoke the pinned CLI. CLI PATH contained only Node and pm, and
14
+ `GIT_CEILING_DIRECTORIES` excluded ancestor discovery.
15
+
16
+ | Measurement | Result |
17
+ | --- | ---: |
18
+ | Public tracker items exported by SDK and actual CLI | 118 |
19
+ | SDK enumeration warnings | 0 |
20
+ | CSV columns / editable fields | 17 / 6 |
21
+ | Baseline JSON bytes / CSV bytes | 114257 / 97461 |
22
+ | Unedited full-table apply | 118 unchanged, 0 applied |
23
+ | Edited rows (open, in progress, terminal) | 3 |
24
+ | Concurrent unedited-field change | 1 preserved |
25
+ | Dry run | 3 changed, 0 applied |
26
+ | Apply | 3 applied |
27
+ | Identical retry | 3 unchanged, 0 applied |
28
+ | Second editor changing an already edited title | `csv_edit_conflict` |
29
+ | Altered revision digest | `csv_edit_invalid` |
30
+ | Whole-corpus byte/hash comparisons showing zero content changes | 4 |
31
+ | Full-table preview JSON / no-op TOON receipt bytes | 89 / 61 |
32
+ | Measured validation duration (single sample) | 31.32 seconds |
33
+
34
+ The four content comparisons cover unedited import, identical replay,
35
+ conflicting second editor and altered revision digest. Each comparison hashes
36
+ all 118 raw item documents and their immutable history streams, rather than
37
+ trusting command counters. Matching bytes do not prove that no physical write occurred;
38
+ an identical-content rewrite would remain invisible to these comparisons.
39
+ Separate targeted regression tests check that stale preflight creates no
40
+ transaction journal. The original public snapshot and the
41
+ copy contain real heterogeneous lifecycle states; the three edited rows were
42
+ selected by status rather than fabricated as replacement tracker data.
43
+
44
+ The compact receipts contain counts and transaction IDs, not exported values.
45
+ This is bounded filesystem-backend validation, not hosted Postgres/MCP or a
46
+ throughput benchmark. The full npm/Bun release-gate evidence is recorded in the
47
+ PR, using CI to avoid heavy local gates on the saturated host.
package/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pm-csv",
3
- "version": "2026.10.22",
3
+ "version": "2026.10.23",
4
4
  "description": "CSV importer and exporter for pm-cli",
5
5
  "author": "@unbraind",
6
6
  "entry": "./dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pm-csv",
3
- "version": "2026.10.22",
3
+ "version": "2026.10.23",
4
4
  "description": "CSV importer and exporter for pm-cli",
5
5
  "repository": {
6
6
  "type": "git",
@@ -116,6 +116,8 @@
116
116
  "docs": [
117
117
  "README.md",
118
118
  "docs/atomic-compensation.md",
119
+ "docs/spreadsheet-roundtrip.md",
120
+ "docs/row-lineage.md",
119
121
  "CHANGELOG.md"
120
122
  ],
121
123
  "examples": [