@plinth-music/cli 0.15.0 → 0.17.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 CHANGED
@@ -2,6 +2,73 @@
2
2
 
3
3
  Notable changes to `@plinth-music/cli`. Grouped by what a user notices, not by PR.
4
4
 
5
+ ## 0.17.0 - 2026-09-03
6
+
7
+ One code PR since `0.16.0` (`v0.16.0..a6209c8`, #165; #167 is tests and comments,
8
+ #163 / #164 / #166 docs). A minor because sync gains a new refusal and a file on your
9
+ disk can now move on its own.
10
+
11
+ - ⚠️ **Renaming a task or project in Plinth now moves the mirror file instead of
12
+ writing a second one.** The server regenerates a row's slug when its name changes.
13
+ Until now the mirror wrote the new name to a new file and left the old one orphaned,
14
+ and an edit to that orphan created a duplicate row in Plinth. The file is moved now,
15
+ and a local copy that had diverged from the cloud is stashed to
16
+ `~/.plinth/_conflicts/<workspace>/` before the move rather than pushed.
17
+ - ⚠️ **A rename that would land on a file already sitting there is refused, not
18
+ overwritten.** Nothing is moved, `plinth status` names it, and Plinth re-offers the
19
+ rename on every poll until the name is free. Where the file in the way belongs to
20
+ another record the message says so and tells you **not** to delete it — deleting it
21
+ would delete that record in Plinth. Where it is a file of yours, rename or move it
22
+ and the rename lands on the next poll.
23
+ - **An unreadable file at a renamed row's old path no longer stalls the whole sync.**
24
+ It used to abort the pull, and in the daemon back that off to a five-minute retry
25
+ indefinitely, leaving the mirror silently stale behind a notice naming no file. Only
26
+ that row is held now, and `plinth status` names the file and says to check its
27
+ permissions.
28
+
29
+ ⚠️ **One boundary changed underneath this release, with no CLI change in it.** Plinth's
30
+ MCP server renamed its calendar tools (`create_calendar_event` and its siblings became
31
+ `create_calendar_entry`), which is what lets the cockpit write calendar entries. The
32
+ side effect is that the generated deny list no longer covers calendar *deletion* on
33
+ Plinth's own server, which it had been doing by accident of the name. No such tool
34
+ exists today, so the hole is latent rather than open. #167 measured this and pinned it
35
+ with tests; no deny pattern was added, removed or edited, and the list stays at 91.
36
+
37
+ The desktop app pins `@plinth-music/cli` exact (0.16.0 as of v1.9.4) and picks none of
38
+ this up until its own release repins.
39
+
40
+ ## 0.16.0 - 2026-09-02
41
+
42
+ Three code PRs since `0.15.0` (`v0.15.0..5c60627`, #157, #159, #161; #158 and #160 are
43
+ docs). A minor because the mirror gains a whole new surface: memory rendered as one file
44
+ per fact.
45
+
46
+ - **Memory can now arrive as individual facts, and the mirror renders them.** Once a
47
+ workspace's memory has been moved to per-fact rows (a migration Plinth runs, not
48
+ something you do), `workspace-memory.md` and `user-memory.md` become generated indexes,
49
+ one line per fact, and every fact gets its own file: shared facts under
50
+ `memory/<slug>.md`, your own under `memory/private/<slug>.md`, each carrying the fact's
51
+ id, description, type, status and source in its frontmatter. The agent reads the index at
52
+ session start and opens a fact's file only when it needs the body. Nothing changes for a
53
+ workspace that has not been migrated: its memory files sync exactly as before.
54
+ - ⚠️ **A hand edit to a generated memory file is refused, kept, and reported.** The daemon
55
+ will not push it; the edit is stashed beside the file, `plinth status` names it, and the
56
+ message says where the write goes instead: the memory fact tools the agent already has.
57
+ Nothing is lost silently. Until a store is generated, the files are yours to edit as
58
+ today.
59
+ - **`plinth status` reports three new things about your master context files**
60
+ (`_artist.md` and the editable block of the workspace `CLAUDE.md`): a single list item or
61
+ paragraph past 2,000 characters, an entry under an "upcoming" label whose latest date has
62
+ passed, and a "Current state" heading with no as-of date or one older than 30 days.
63
+ Reports only; nothing is edited for you. `/plinth:close` points at the three while the
64
+ file is open.
65
+ - **Archived tasks no longer count as open in the per-artist `CLAUDE.md`.** An archived
66
+ task was being listed and counted as open work.
67
+ - Dead-link reporting and `plinth backlinks` resolve `[[memory/<slug>]]` and
68
+ `[[memory/private/<slug>]]`.
69
+
70
+ The desktop app pins `0.15.0` exact and picks none of this up until its own release repins.
71
+
5
72
  ## 0.15.0 - 2026-08-31
6
73
 
7
74
  One code PR since `0.14.0` (`v0.14.0..f293dd0`, #153). A minor because sync gains a
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Plinth capstone sync daemon and CLI. Mirrors your Plinth workspace to `~/Plinth/<workspace>/` and keeps it bidirectionally synced. AI-native: pair with Claude Code or any LLM agent for context-aware artist management.
4
4
 
5
- **Status: early access.** Two-way sync runs as a daemon across documents, projects, tasks, artists, meetings, threads, a user-writable `files/` subtree, the three memory files and `voice.md` (your voice profile, round-tripped to the server and injected read-only into every session). See `V1_SPEC` §5 M3 in the plinth monorepo for the full milestone plan, and [`CHANGELOG.md`](CHANGELOG.md) for what landed when.
5
+ **Status: early access.** Two-way sync runs as a daemon across documents, projects, tasks, artists, meetings, threads, a user-writable `files/` subtree, the three memory files and `voice.md` (your voice profile, round-tripped to the server and injected read-only into every session). See `V1_SPEC` §5 M3 in the plinth monorepo for the full milestone plan, and [`CHANGELOG.md`](CHANGELOG.md) for what landed when. Since 0.16.0 a workspace whose memory has been migrated to per-fact rows receives its memory as generated indexes plus one file per fact under `memory/` (shared) and `memory/private/` (yours); those files are read-only on disk and a hand edit is refused, stashed and reported rather than lost.
6
6
 
7
7
  ## Install
8
8
 
@@ -23,9 +23,9 @@ plinth start # run the daemon: continuous pull + watch + pu
23
23
  ## Commands
24
24
 
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
- - `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. 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.
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. Four 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,000-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.
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. Four 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,000-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. 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 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.
31
31
  - `plinth grounding` — print the session-start grounding block (current date, **who the member is**, entity resolution, write confirmation, workspace rules, workspace memory, user memory). It opens by naming the member: display name, email, role, workspace and machine timezone, and that tasks the agent creates are assigned to them unless told otherwise. That renders from a principal **persisted at login**, not a call at render time — this command runs on every session start, so a round trip would tax all of them and fail offline. An existing login self-heals on the next sync, so no re-login is needed, and a server that does not yet carry `role` renders a shorter true line rather than a dangling clause. Invoked by the generated Claude Code SessionStart hook. **Bounded at 20,000 bytes total.** Only the two memory indexes give: a generated safety primitive is never cut, and if the generated block alone exceeded the budget the indexes collapse to a pointer and the block goes over rather than losing a rule. An index that does not fit whole keeps the **most recently banked** facts, by the date each line carries rather than by position — `workspace-memory.md` supersedes in place, so an updated line keeps its original slot and position does not track age — and renders them in the file's own order, with a note saying how many of how many are shown. **A fact that does not fit whole is named by its title** — its own first sentence, a boundary the member already wrote, with its `[[pointer]]` kept — so the agent knows the fact exists and where to read it. ⚠️ **Naming is preferred over carrying, and that trades depth for breadth:** the naming pass runs first and packs, and whole facts compete for what is left, so on a full index no fact may ride whole at all. A title pays in proportion to how well the hook was written; a fact whose first sentence runs long compresses barely at all, which is why *shorten each fact's first sentence* buys room where *trim the file* does not. `voice.md` is a **pointer, not a payload**: it is read on demand when drafting, never injected, which is what took a live workspace's block from 43,578 bytes to 19,911.
@@ -1,5 +1,5 @@
1
1
  {
2
- "sha": "71f22efc85823f5d401c67709224d315e7504974",
2
+ "sha": "bc49180c1d2e3f217f3eedda18bd075870cc36aa",
3
3
  "dirty": false,
4
- "built_at": "2026-08-31T19:26:38.895Z"
4
+ "built_at": "2026-09-03T21:05:22.806Z"
5
5
  }