@plinth-music/cli 0.21.1 → 0.22.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 +55 -0
- package/README.md +1 -1
- package/dist/build-stamp.json +2 -2
- package/dist/cli.js +2622 -307
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,61 @@
|
|
|
2
2
|
|
|
3
3
|
Notable changes to `@plinth-music/cli`. Grouped by what a user notices, not by PR.
|
|
4
4
|
|
|
5
|
+
## 0.22.0 - 2026-09-22
|
|
6
|
+
|
|
7
|
+
Four code PRs since `0.21.1` (`v0.21.1..c1bb82e`, #199, #200, #201, #202; #197 and #198
|
|
8
|
+
docs). A minor: two new sync surfaces (memory markers on mirrored sources, and the
|
|
9
|
+
freshness of the fact copy) and a change to the generated workspace `CLAUDE.md`.
|
|
10
|
+
|
|
11
|
+
⚠️ **An edit you have not pushed yet is no longer overwritten by a pull without a copy
|
|
12
|
+
being kept** (#202). This closes the exposure the 0.21.1 entry left open. If you saved a
|
|
13
|
+
file under `files/` in the second or so before the daemon's poll brought a newer cloud
|
|
14
|
+
copy, the cloud copy replaced yours and nothing kept it. The watcher then saw the
|
|
15
|
+
daemon's own bytes and treated them as the pull's echo, so the log said nothing either.
|
|
16
|
+
The cloud copy still wins, but your bytes are now kept first in
|
|
17
|
+
`~/.plinth/_conflicts/<workspace>/`, listed in `views/conflicts.md` with the command that
|
|
18
|
+
restores them, and logged as `[FILE_CONFLICT … via=pull]`. A file the pull cannot keep a
|
|
19
|
+
copy of is left as it is, logged as `[FILE_STASH_FAILED …]`, and tried again on the next
|
|
20
|
+
poll; the daemon carries on. Every watched change that is deliberately not pushed now
|
|
21
|
+
leaves a `[FILE_PUSH_SKIPPED … reason=…]` line in `~/.plinth/daemon.log`, so a missing
|
|
22
|
+
edit can be traced.
|
|
23
|
+
|
|
24
|
+
**Mirrored documents and extracted file texts say whether their memory context is
|
|
25
|
+
current** (#199). The daemon asks Plinth about sources it already holds and marks each
|
|
26
|
+
one in its frontmatter: current, invalidated, refresh failed, not found, untracked or
|
|
27
|
+
unknown, with any correction shown beside the unchanged original text. Nothing is
|
|
28
|
+
re-downloaded and no source body is rewritten. The marker goes to unknown on the first
|
|
29
|
+
failed or offline poll, with no grace period. A file the daemon could not mark is listed
|
|
30
|
+
under **Memory context** in `plinth status`. The web side that stops these markers being
|
|
31
|
+
stored as a document's own data (Plinth #439) is live.
|
|
32
|
+
|
|
33
|
+
**Memory fact files carry their correction trail, and the mirror says whether its fact
|
|
34
|
+
copy is up to date** (#200). `memory/_freshness.md` and a **Memory facts** row in `plinth
|
|
35
|
+
status` say current, behind or not checked, dated to when that was established. When
|
|
36
|
+
Plinth says nothing has changed, the daemon skips downloading the fact feed. Any other
|
|
37
|
+
answer, or any failure, falls back to downloading every poll, as before.
|
|
38
|
+
|
|
39
|
+
**The generated workspace `CLAUDE.md` tells the agent where to file documents** (#201): a
|
|
40
|
+
project's files go in `files/<Artist>/<Project>/`, and the folder path links them on
|
|
41
|
+
sync. It also carries a short house-style section. A running daemon on this version
|
|
42
|
+
rewrites the file within one sync.
|
|
43
|
+
|
|
44
|
+
**What is not proven.**
|
|
45
|
+
|
|
46
|
+
- #199 and #200 are proven by the suite, mutation rounds and critic review. Neither has
|
|
47
|
+
had an installed build driven against a live workspace.
|
|
48
|
+
- #202 was reproduced before and after against a test workspace from source, with one
|
|
49
|
+
and with two daemons, but not on the packaged app. The original incident's timing (a
|
|
50
|
+
save about four seconds before the pull) was not reproduced as a loss, so its cause is
|
|
51
|
+
not confirmed; the new log lines distinguish the cases next time.
|
|
52
|
+
- Known gaps in #202, all left for a follow-up:
|
|
53
|
+
- two saves within one poll can still file a conflict when none happened, though the
|
|
54
|
+
copy is kept;
|
|
55
|
+
- a save reverted to exactly the last synced bytes within one poll can still be
|
|
56
|
+
overwritten without a copy;
|
|
57
|
+
- a file the pull cannot keep a copy of is downloaded again on every poll until it can.
|
|
58
|
+
The likeliest cause is a path over about 219 characters.
|
|
59
|
+
|
|
5
60
|
## 0.21.1 - 2026-09-18
|
|
6
61
|
|
|
7
62
|
One code PR since `0.21.0` (`v0.21.0..80aa258`, #195; #192, #193 and #194 docs). A
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ plinth start # run the daemon: continuous pull + watch + pu
|
|
|
25
25
|
- `plinth login --workspace <slug> [--reset-sync]` — issue a personal access token in the browser, paste it back, store it in the OS keychain (macOS Keychain / Linux Secret Service / Windows Credential Manager). Logging in again to the same workspace is a token rotation and **keeps your sync cursors**, so the next sync picks up where the last one left off; it clears them only when the token belongs to a different workspace or user. `--reset-sync` discards them deliberately, making the next sync a full re-pull — use it after rebuilding a mirror from scratch, which is otherwise unreachable now that rotation preserves them.
|
|
26
26
|
- `plinth sync [--workspace <slug>]` — one-shot pull into `~/Plinth/<workspace>/`. Also generates the workspace-root `CLAUDE.md` (a thin TOC + entity model + skill pointers), an empty `CLAUDE.local.md` stub on first sync, and the two session-boundary commands (below), and silently refreshes `CLAUDE.md` on a schema-version change. Defaults to the active workspace if `--workspace` is omitted. **Every mirrored task, project, document, artist, meeting and thread file carries its row `id:` in frontmatter**, so an agent holding a file can address the MCP write for that row directly instead of going through `search_entities` and risking a duplicate on an empty result; files already on disk when you upgrade get it from a one-time backfill, since a cursor feed never re-sends an unchanged row. The key is server-authoritative and stripped before the wire, so it moves no hash and pushes nothing. Since 0.17.0 a row **renamed in Plinth moves its mirror file** rather than leaving a second copy at the old name; a file already sitting at the new name is never overwritten, and `plinth status` names the clash until it clears. It also **scaffolds `files/` and the six entity folders**, so a new workspace arrives with somewhere to put things instead of an empty directory, and the folder map in the generated `CLAUDE.md` reads the disk rather than asserting what ought to be there.
|
|
27
27
|
- `plinth start` — run the long-running sync daemon: pull the latest, then watch the mirror for local edits and push them. It also projects each mapped artist's Google calendar into `views/calendar/` in the mirror — a file per artist plus an `upcoming.md` — on its own 15-minute timer (`PLINTH_CALENDAR_INTERVAL_MS`), with one run at boot. That is deliberately not the 15-second pull cadence: one tick makes a Google call per mapped calendar, which would breach the server route's rate limit inside a minute. A refresh that fails keeps the last known bodies and rewrites only the header, so a stale view says it is stale rather than reading as an empty diary. Logs to `~/.plinth/daemon.log`. **One daemon per workspace:** it holds an exclusive kernel lock on `~/.plinth/daemon-<workspace>.lock` for its life (macOS only; other platforms are refused and told why), so a second `plinth start`, or a standalone `plinth sync` while it runs, exits 1 naming the holder's pid. **Never delete the lock file** — the lock lives on the inode, and removing the path is what lets a second daemon in beside a live one. It also writes `views/conflicts.md` on every sync: every local edit the cloud won and the daemon set aside in `~/.plinth/_conflicts/`, each with a `cp` command that restores it; the zero case is written too, so an absent file means an old daemon, not a clean one.
|
|
28
|
-
- `plinth status` — local sync state: daemon liveness, last sync and per-type counts, any destructive batches being held, writes awaiting retry or permanently rejected, **every local write that has not reached Plinth** (scanned from the mirror against the last acknowledged state, with the remedy that clears each — start the daemon, or open and save a file that was created while it was down), the state of `voice.md`, and any conflict stashes in `~/.plinth/_conflicts/<workspace>/` — local copies that were set aside when the cloud version won. Warns when a source install's `dist/` is behind its checkout.
|
|
28
|
+
- `plinth status` — local sync state: daemon liveness, last sync and per-type counts, any destructive batches being held, writes awaiting retry or permanently rejected, **every local write that has not reached Plinth** (scanned from the mirror against the last acknowledged state, with the remedy that clears each — start the daemon, or open and save a file that was created while it was down), the state of `voice.md`, and any conflict stashes in `~/.plinth/_conflicts/<workspace>/` — local copies that were set aside when the cloud version won. Warns when a source install's `dist/` is behind its checkout. Six further rows. **Commands** warns when the customisable block in a generated command still names machinery that command retired — the block is preserved verbatim forever and renders after the generated body, so a hand-written step that runs a retired ritual wins, and the generator may never edit it. **Its verdict is derived from the command registry, not asserted**: a token that still resolves to a live subcommand is described as still working and left to you, because the one token it lists is `memory-gate`, which the grounding block hands every session. **Links** names `[[pointers]]` in `workspace-memory.md` and `artists/*/memory.md` that resolve to nothing — a memory fact is a hook plus a pointer to its source, so a dead pointer can mean the hook is the only surviving copy of the fact. It reuses `plinth backlinks`'s resolution ladder, reports prose links only (never one inside a code fence), never reports an ambiguous target, and strips anchors before resolving. **Injection** reports the session-start block's size against its 20,588-byte budget and **where those bytes go** — seven categories that sum to the block by construction, marking which are yours to change — plus how many facts of each index ride whole, how many ride as their opening line only, and which file to shorten. **Too long** names each memory file past the size it is useful at: a rule file (`voice.md`, `workspace-rules.md`) at **6,000 bytes**, a fact store (`artists/<slug>/memory.md`) at **12,000**. The two bars are not one bar: what a long rule file spends is the model's instruction-following budget, which reference facts do not touch. A file under its bar renders nothing at all. **Memory facts** says whether this machine's copy of the fact store is up to date, behind or not checked, dated to when that answer was established, and says so when the daemon is not running and nothing has been checked since. **Memory context** lists mirrored files the daemon could not mark with their current memory state; the edits in them are untouched. Since 0.16.0 it also reports master-context walls (one block past 2,000 characters), played entries still under an "upcoming" label, and a "Current state" heading with no as-of or a stale one.
|
|
29
29
|
- `plinth confirm` — review and release destructive batches the daemon has quarantined (apply, or `--discard`).
|
|
30
30
|
- `plinth context [--workspace <slug>] [--json] [--observed <transcript.jsonl>]` — **the one complete cost account**: every source that can reach a session in this workspace, grouped by how it gets there, with Plinth's share separated from yours. `plinth status`'s **Injection** row measures the hook and nothing else, so a root `CLAUDE.md` can pass its bar and still be the largest thing a session loads — the bar counts LINES OF THE GENERATED BODY, and on a real workspace 29,055 of that file's 40,268 bytes are the preserved member block it cannot see. This reports the root and `CLAUDE.local.md` (loaded by Claude Code itself), `@`-imports, the rendered hook and each file's share of it, per-artist scoped files, demand-read stores and fact bodies, command bodies with their discovery metadata counted separately, and configuration — **including the per-artist `.claude/settings.json`, which is the one that applies inside an artist folder, because settings do not walk up**. ⚠️ **Bytes on disk, rendered hook bytes, loaded bytes and billed tokens are four different quantities and are never summed into one.** A file that reaches a session only through the hook contributes its injected share and never its size on disk, so trimming advice cannot double-charge it. There is exactly one combined figure and it prints its assumptions beside it, because by default nothing here observes a session: **actual loading, tokens and cached usage are reported as not measured, never as zero**, and the harness's own tool-schema overhead is named as unknown rather than guessed from a catalogue size. **`--observed <transcript.jsonl>` measures the first two of those, for the one session you name and no other.** It is opt-in and explicit — without it no file outside the mirror is opened, and with it nothing is discovered or crawled and no sibling transcript is read; a path that turns out not to be the file that was checked is refused rather than measured. It adds that session's recorded deliveries and its usage — fresh input, cache read, cache written and output tokens, kept as four figures because they are billed differently — beside the session id, the runtime, the interval covered and a digest of the bytes read. **A usage figure appears only if every counted request supplied it**; some-but-not-all reads `unknown`, requests with no usage record and duplicates that disagree are counted and named rather than summed, and anything partial puts PARTIAL in the headline. **No token is attributed to any file** — the transcript records the standing context as one aggregate in the first request's cache fields — and the unmeasured limits (inheritance, resume, compaction, duplicate hook delivery, tool-schema cost) still print, so supplying an observation never reads as having measured them. Missing, unreadable, refused, unresolved and unlistable are five distinct states, since all five otherwise read as "0 bytes". **Read-only**: it writes no file, starts no daemon and makes no network call — proven against a v1 `state.json`, which the ordinary state read would migrate and rewrite.
|
|
31
31
|
- `plinth refresh-context [--workspace <slug>]` — force-regenerate the workspace-root `CLAUDE.md` **and the two generated commands** from the current Plinth schema + workspace. The user-customisable block between the `<!-- BEGIN: user-customisable -->` / `<!-- END -->` markers is always preserved verbatim in every generated file; `CLAUDE.local.md` is never touched.
|
package/dist/build-stamp.json
CHANGED