omp packages

pi-papercuts0.4.0

Agent complaint box for Pi/OMP: file friction into an append-only .papercuts.jsonl, then keep working.

adityavg13 Published 15 days ago MIT ~342 downloads/mo

Built for omp

The author tagged this package oh-my-pi.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

pi-papercuts

npm license node pi-package

Agent files one-line friction notes into .papercuts.jsonl and keeps working. Review the backlog later.

Port of treygoff24/papercuts (MIT) for Pi and OMP. Pure Node · Node 22+.

pi install npm:pi-papercuts
omp install npm:pi-papercuts
# from a checkout:
pi install ./packages/pi-papercuts
omp install ./packages/pi-papercuts

Use

papercuts({ action: "add", text: "what broke + what would have prevented it", tags: ["tooling"], severity: "major" })
papercuts({ action: "list" })
papercuts({ action: "list", format: "md" })
papercuts({ action: "resolve", ids: ["pc_9f2c"], note: "fixed" })
papercuts({ action: "doctor" })
papercuts({ action: "schema" })
papercuts({ action: "prune" })
Action What it does
add File a cut (text required). Optional tags, severity. Evidence is free-note or cmd/exit/stderr, not both. Alias log → add.
list Open cuts by default. Filters: status, agent, tag, severity, limit, format (json | md).
resolve Mark ids resolved (pc_ + ≥4 hex). Append-only.
prune Archive resolved cuts; keep the working log lean.
doctor Validate the log.
schema Machine contract for agents.

Severity: minor (default), major, blocker. New IDs hash a JSON tuple of timestamp, agent, text, severity and the sorted tag array. They retain the pc_ + 12-hex format; old logged IDs remain readable/resolvable but are not regenerated by the new encoding. Distinct tag boundaries and lone surrogates are preserved. Hash collisions are still possible with 48-bit IDs.

Dedupe applies to an identical ID in the working log, including the timestamp. Evidence is not part of identity. A later timestamp is a new cut, and pruning forgets working-log dedupe for archived cuts. Duplicate receipts return the first stored record, including its original evidence and tag order, never unsaved replacement fields. Unknown legacy severity values sort after the three known severity levels without dropping their records.

Interactive hosts show a compact themed call/result row -- expand for full text, tags, and log path. Pi 0.99 programmatic callers receive the same versioned envelope in structuredContent (validated by outputSchema); UI details and model-facing text stay intact. Rejected usage, busy and I/O operations set native isError, so nested execution does not report them as successful writes. The combined tool includes mutations and does not claim to be read-only. Markdown lists retain this programmatic envelope too. The required action and action-specific field types are checked before storage; known null placeholders are absent, but unknown keys are rejected even when null. TUI previews strip terminal controls and do not split valid surrogate pairs.


Layout

index.js binds the host signature and error boundary. lib/contract.js owns wire schemas/envelopes, lib/params.js narrows action-specific arguments, lib/actions.js implements validated actions, and lib/render.js owns compact/expanded TUI output. lib/worker-client.js manages lazy background execution and graceful shutdown; lib/worker.js runs the unchanged synchronous actions serially off the UI thread. lib/store.js remains the sole persistence boundary, including canonical-path locks, append durability, tear healing, and archive-before-rewrite pruning.

Rendering

Renderers reuse their own previous Text component when freshly formatted text is unchanged, preserving Pi's width/layout cache without caching theme colors. Pi's self-rendering API also preserves the surrounding Box cache: padding, pending/success/error backgrounds and click expansion match the default shell. Each transcript row retains at most two frames, one per expansion state, so collapsing and reopening a result preserves both layouts. If the call renderer fails, its detached frame cannot swallow the host-visible error result. Failed result rendering clears the old receipt before the host shows its fallback. Changed arguments, nested result values, expansion, theme invalidation and resize remain visible. Common-width resolve batches build one ordered prefix index per call; longer legacy IDs still participate in ambiguity checks. The temporary index retains only requested-prefix hits rather than unrelated cuts. Mixed-width requests retain prefix scanning.

There is no persistent log cache: cooperating writes keep the full snapshot-to- append lock, reads still observe current file contents, and acknowledgements still require fsync. Each extension registration owns one lazy worker, so independent sessions cannot shut down each other's execution. schema and input validation stay local. The first storage call pays worker startup latency without blocking on its I/O. Results are copied back to the host, so unbounded result payloads and cold layout of very large expanded lists can still block a frame.

The worker is unreferenced when idle and drains accepted work on session_shutdown. Already-aborted calls are rejected before dispatch; after dispatch, cancellation waits for the transaction's receipt rather than interrupting a durable write. Worker failures reject pending calls without replay: a write may have committed before a lost receipt, so inspect the log before retrying. Later calls can restart the worker. Per-request PAPERCUTS_FILE, PAPERCUTS_AGENT and PAPERCUTS_NOW overrides are snapshotted before dispatch, along with the execution directory.

Storage

  1. file param
  2. PAPERCUTS_FILE
  3. nearest .git → <root>/.papercuts.jsonl (exact basename only; foo.papercuts.jsonl is not the repo log)
  4. else ~/.papercuts/log.jsonl
echo .papercuts.jsonl >> .gitignore

Relative file and PAPERCUTS_FILE overrides resolve against the active execution directory, not the host process directory. Reads reject non-regular files without waiting for a FIFO writer. Missing files remain empty backlogs. Pruning retains the working log's basic permission bits even under a tighter umask; ownership, ACLs, and extended attributes are not preserved by that replacement.

Optional: PAPERCUTS_AGENT, PAPERCUTS_NOW (tests).

With pi-deferred-context-engine, pin papercuts if you want it always active.

Tests

npm test --prefix packages/pi-papercuts

Hermetic Node 22 tests under tests/. Local bench tests/performance.mjs is not shipped.

No-claim boundaries

  • Does not auto-detect failures -- the agent must call it (habit / AGENTS.md).
  • Not a secret store. Size caps only; resolve does not erase cut text.
  • Outside git, log goes under home unless PAPERCUTS_FILE is set.
  • Log paths must be normal files, without hard-link aliases. Existing symlink targets are resolved before locking; dangling log symlinks are refused.
  • All writers/pruners must run this version's locking protocol. Older versions or external appenders are not coordinated. Stop them before pruning with this version.
  • Canonical log/archive paths ending in .lock (case-insensitive) are reserved and refused, including symlinks into that namespace. This prevents event data from being deleted as lock cleanup.
  • Writes acquire exclusive <canonical-log>.lock files; add/resolve hold the lock from snapshot through append, and prune also locks its archive. Contention returns a retryable busy error without acknowledging a write. Locks contain a PID/time and are not stolen automatically: after a crash, confirm the writer has stopped before manually removing a stale lock.
  • Prune archives before replacing the working log. A crash can leave duplicate archive events or a temp file; duplicate events are harmless to fold. File contents are flushed, but directory durability after power loss and network filesystems are not guaranteed.
  • Appends separate an unterminated tail from the new event; malformed old lines remain on disk until explicit pruning.
  • Invalid UTF-8 (strict per-record decode, not replacement characters), invalid/missing IDs, malformed tag arrays, and wrong-typed ts/agent or cut text/severity fields are torn. These text fields must be strings when present; sparse legacy records, unknown severity strings, and opaque extra metadata remain supported. doctor flags torn records; list/resolve skip them without rewriting those lines. Explicit prune removes them.
  • Text and tag byte caps keep the original UTF-16 slice, including lone surrogates. A Buffer round-trip would replace those code units and change content-addressed IDs.
  • Open-cut list order is severity, then RFC3339 timestamp (not lexical), then remaining text. Truncated list leftovers are total - shown.length.

More: residual risks.

License

MIT · AdityaVG13/pi-stack